brintos

brintos / linux-shallow public Read only

0
0
Text · 14.5 KiB · bf4d8dd Raw
587 lines · c
1// SPDX-License-Identifier: GPL-2.0-only2/*3 * AXI clkgen driver4 *5 * Copyright 2012-2013 Analog Devices Inc.6 *  Author: Lars-Peter Clausen <lars@metafoo.de>7 */8 9#include <linux/platform_device.h>10#include <linux/clk-provider.h>11#include <linux/slab.h>12#include <linux/io.h>13#include <linux/of.h>14#include <linux/module.h>15#include <linux/err.h>16 17#define AXI_CLKGEN_V2_REG_RESET		0x4018#define AXI_CLKGEN_V2_REG_CLKSEL	0x4419#define AXI_CLKGEN_V2_REG_DRP_CNTRL	0x7020#define AXI_CLKGEN_V2_REG_DRP_STATUS	0x7421 22#define AXI_CLKGEN_V2_RESET_MMCM_ENABLE	BIT(1)23#define AXI_CLKGEN_V2_RESET_ENABLE	BIT(0)24 25#define AXI_CLKGEN_V2_DRP_CNTRL_SEL	BIT(29)26#define AXI_CLKGEN_V2_DRP_CNTRL_READ	BIT(28)27 28#define AXI_CLKGEN_V2_DRP_STATUS_BUSY	BIT(16)29 30#define MMCM_REG_CLKOUT5_2	0x0731#define MMCM_REG_CLKOUT0_1	0x0832#define MMCM_REG_CLKOUT0_2	0x0933#define MMCM_REG_CLKOUT6_2	0x1334#define MMCM_REG_CLK_FB1	0x1435#define MMCM_REG_CLK_FB2	0x1536#define MMCM_REG_CLK_DIV	0x1637#define MMCM_REG_LOCK1		0x1838#define MMCM_REG_LOCK2		0x1939#define MMCM_REG_LOCK3		0x1a40#define MMCM_REG_POWER		0x2841#define MMCM_REG_FILTER1	0x4e42#define MMCM_REG_FILTER2	0x4f43 44#define MMCM_CLKOUT_NOCOUNT	BIT(6)45 46#define MMCM_CLK_DIV_DIVIDE	BIT(11)47#define MMCM_CLK_DIV_NOCOUNT	BIT(12)48 49struct axi_clkgen_limits {50	unsigned int fpfd_min;51	unsigned int fpfd_max;52	unsigned int fvco_min;53	unsigned int fvco_max;54};55 56struct axi_clkgen {57	void __iomem *base;58	struct clk_hw clk_hw;59	struct axi_clkgen_limits limits;60};61 62static uint32_t axi_clkgen_lookup_filter(unsigned int m)63{64	switch (m) {65	case 0:66		return 0x01001990;67	case 1:68		return 0x01001190;69	case 2:70		return 0x01009890;71	case 3:72		return 0x01001890;73	case 4:74		return 0x01008890;75	case 5 ... 8:76		return 0x01009090;77	case 9 ... 11:78		return 0x01000890;79	case 12:80		return 0x08009090;81	case 13 ... 22:82		return 0x01001090;83	case 23 ... 36:84		return 0x01008090;85	case 37 ... 46:86		return 0x08001090;87	default:88		return 0x08008090;89	}90}91 92static const uint32_t axi_clkgen_lock_table[] = {93	0x060603e8, 0x060603e8, 0x080803e8, 0x0b0b03e8,94	0x0e0e03e8, 0x111103e8, 0x131303e8, 0x161603e8,95	0x191903e8, 0x1c1c03e8, 0x1f1f0384, 0x1f1f0339,96	0x1f1f02ee, 0x1f1f02bc, 0x1f1f028a, 0x1f1f0271,97	0x1f1f023f, 0x1f1f0226, 0x1f1f020d, 0x1f1f01f4,98	0x1f1f01db, 0x1f1f01c2, 0x1f1f01a9, 0x1f1f0190,99	0x1f1f0190, 0x1f1f0177, 0x1f1f015e, 0x1f1f015e,100	0x1f1f0145, 0x1f1f0145, 0x1f1f012c, 0x1f1f012c,101	0x1f1f012c, 0x1f1f0113, 0x1f1f0113, 0x1f1f0113,102};103 104static uint32_t axi_clkgen_lookup_lock(unsigned int m)105{106	if (m < ARRAY_SIZE(axi_clkgen_lock_table))107		return axi_clkgen_lock_table[m];108	return 0x1f1f00fa;109}110 111static const struct axi_clkgen_limits axi_clkgen_zynqmp_default_limits = {112	.fpfd_min = 10000,113	.fpfd_max = 450000,114	.fvco_min = 800000,115	.fvco_max = 1600000,116};117 118static const struct axi_clkgen_limits axi_clkgen_zynq_default_limits = {119	.fpfd_min = 10000,120	.fpfd_max = 300000,121	.fvco_min = 600000,122	.fvco_max = 1200000,123};124 125static void axi_clkgen_calc_params(const struct axi_clkgen_limits *limits,126	unsigned long fin, unsigned long fout,127	unsigned int *best_d, unsigned int *best_m, unsigned int *best_dout)128{129	unsigned long d, d_min, d_max, _d_min, _d_max;130	unsigned long m, m_min, m_max;131	unsigned long f, dout, best_f, fvco;132	unsigned long fract_shift = 0;133	unsigned long fvco_min_fract, fvco_max_fract;134 135	fin /= 1000;136	fout /= 1000;137 138	best_f = ULONG_MAX;139	*best_d = 0;140	*best_m = 0;141	*best_dout = 0;142 143	d_min = max_t(unsigned long, DIV_ROUND_UP(fin, limits->fpfd_max), 1);144	d_max = min_t(unsigned long, fin / limits->fpfd_min, 80);145 146again:147	fvco_min_fract = limits->fvco_min << fract_shift;148	fvco_max_fract = limits->fvco_max << fract_shift;149 150	m_min = max_t(unsigned long, DIV_ROUND_UP(fvco_min_fract, fin) * d_min, 1);151	m_max = min_t(unsigned long, fvco_max_fract * d_max / fin, 64 << fract_shift);152 153	for (m = m_min; m <= m_max; m++) {154		_d_min = max(d_min, DIV_ROUND_UP(fin * m, fvco_max_fract));155		_d_max = min(d_max, fin * m / fvco_min_fract);156 157		for (d = _d_min; d <= _d_max; d++) {158			fvco = fin * m / d;159 160			dout = DIV_ROUND_CLOSEST(fvco, fout);161			dout = clamp_t(unsigned long, dout, 1, 128 << fract_shift);162			f = fvco / dout;163			if (abs(f - fout) < abs(best_f - fout)) {164				best_f = f;165				*best_d = d;166				*best_m = m << (3 - fract_shift);167				*best_dout = dout << (3 - fract_shift);168				if (best_f == fout)169					return;170			}171		}172	}173 174	/* Lets see if we find a better setting in fractional mode */175	if (fract_shift == 0) {176		fract_shift = 3;177		goto again;178	}179}180 181struct axi_clkgen_div_params {182	unsigned int low;183	unsigned int high;184	unsigned int edge;185	unsigned int nocount;186	unsigned int frac_en;187	unsigned int frac;188	unsigned int frac_wf_f;189	unsigned int frac_wf_r;190	unsigned int frac_phase;191};192 193static void axi_clkgen_calc_clk_params(unsigned int divider,194	unsigned int frac_divider, struct axi_clkgen_div_params *params)195{196 197	memset(params, 0x0, sizeof(*params));198 199	if (divider == 1) {200		params->nocount = 1;201		return;202	}203 204	if (frac_divider == 0) {205		params->high = divider / 2;206		params->edge = divider % 2;207		params->low = divider - params->high;208	} else {209		params->frac_en = 1;210		params->frac = frac_divider;211 212		params->high = divider / 2;213		params->edge = divider % 2;214		params->low = params->high;215 216		if (params->edge == 0) {217			params->high--;218			params->frac_wf_r = 1;219		}220 221		if (params->edge == 0 || frac_divider == 1)222			params->low--;223		if (((params->edge == 0) ^ (frac_divider == 1)) ||224			(divider == 2 && frac_divider == 1))225			params->frac_wf_f = 1;226 227		params->frac_phase = params->edge * 4 + frac_divider / 2;228	}229}230 231static void axi_clkgen_write(struct axi_clkgen *axi_clkgen,232	unsigned int reg, unsigned int val)233{234	writel(val, axi_clkgen->base + reg);235}236 237static void axi_clkgen_read(struct axi_clkgen *axi_clkgen,238	unsigned int reg, unsigned int *val)239{240	*val = readl(axi_clkgen->base + reg);241}242 243static int axi_clkgen_wait_non_busy(struct axi_clkgen *axi_clkgen)244{245	unsigned int timeout = 10000;246	unsigned int val;247 248	do {249		axi_clkgen_read(axi_clkgen, AXI_CLKGEN_V2_REG_DRP_STATUS, &val);250	} while ((val & AXI_CLKGEN_V2_DRP_STATUS_BUSY) && --timeout);251 252	if (val & AXI_CLKGEN_V2_DRP_STATUS_BUSY)253		return -EIO;254 255	return val & 0xffff;256}257 258static int axi_clkgen_mmcm_read(struct axi_clkgen *axi_clkgen,259	unsigned int reg, unsigned int *val)260{261	unsigned int reg_val;262	int ret;263 264	ret = axi_clkgen_wait_non_busy(axi_clkgen);265	if (ret < 0)266		return ret;267 268	reg_val = AXI_CLKGEN_V2_DRP_CNTRL_SEL | AXI_CLKGEN_V2_DRP_CNTRL_READ;269	reg_val |= (reg << 16);270 271	axi_clkgen_write(axi_clkgen, AXI_CLKGEN_V2_REG_DRP_CNTRL, reg_val);272 273	ret = axi_clkgen_wait_non_busy(axi_clkgen);274	if (ret < 0)275		return ret;276 277	*val = ret;278 279	return 0;280}281 282static int axi_clkgen_mmcm_write(struct axi_clkgen *axi_clkgen,283	unsigned int reg, unsigned int val, unsigned int mask)284{285	unsigned int reg_val = 0;286	int ret;287 288	ret = axi_clkgen_wait_non_busy(axi_clkgen);289	if (ret < 0)290		return ret;291 292	if (mask != 0xffff) {293		axi_clkgen_mmcm_read(axi_clkgen, reg, &reg_val);294		reg_val &= ~mask;295	}296 297	reg_val |= AXI_CLKGEN_V2_DRP_CNTRL_SEL | (reg << 16) | (val & mask);298 299	axi_clkgen_write(axi_clkgen, AXI_CLKGEN_V2_REG_DRP_CNTRL, reg_val);300 301	return 0;302}303 304static void axi_clkgen_mmcm_enable(struct axi_clkgen *axi_clkgen,305	bool enable)306{307	unsigned int val = AXI_CLKGEN_V2_RESET_ENABLE;308 309	if (enable)310		val |= AXI_CLKGEN_V2_RESET_MMCM_ENABLE;311 312	axi_clkgen_write(axi_clkgen, AXI_CLKGEN_V2_REG_RESET, val);313}314 315static struct axi_clkgen *clk_hw_to_axi_clkgen(struct clk_hw *clk_hw)316{317	return container_of(clk_hw, struct axi_clkgen, clk_hw);318}319 320static void axi_clkgen_set_div(struct axi_clkgen *axi_clkgen,321	unsigned int reg1, unsigned int reg2, unsigned int reg3,322	struct axi_clkgen_div_params *params)323{324	axi_clkgen_mmcm_write(axi_clkgen, reg1,325		(params->high << 6) | params->low, 0xefff);326	axi_clkgen_mmcm_write(axi_clkgen, reg2,327		(params->frac << 12) | (params->frac_en << 11) |328		(params->frac_wf_r << 10) | (params->edge << 7) |329		(params->nocount << 6), 0x7fff);330	if (reg3 != 0) {331		axi_clkgen_mmcm_write(axi_clkgen, reg3,332			(params->frac_phase << 11) | (params->frac_wf_f << 10), 0x3c00);333	}334}335 336static int axi_clkgen_set_rate(struct clk_hw *clk_hw,337	unsigned long rate, unsigned long parent_rate)338{339	struct axi_clkgen *axi_clkgen = clk_hw_to_axi_clkgen(clk_hw);340	const struct axi_clkgen_limits *limits = &axi_clkgen->limits;341	unsigned int d, m, dout;342	struct axi_clkgen_div_params params;343	uint32_t power = 0;344	uint32_t filter;345	uint32_t lock;346 347	if (parent_rate == 0 || rate == 0)348		return -EINVAL;349 350	axi_clkgen_calc_params(limits, parent_rate, rate, &d, &m, &dout);351 352	if (d == 0 || dout == 0 || m == 0)353		return -EINVAL;354 355	if ((dout & 0x7) != 0 || (m & 0x7) != 0)356		power |= 0x9800;357 358	axi_clkgen_mmcm_write(axi_clkgen, MMCM_REG_POWER, power, 0x9800);359 360	filter = axi_clkgen_lookup_filter(m - 1);361	lock = axi_clkgen_lookup_lock(m - 1);362 363	axi_clkgen_calc_clk_params(dout >> 3, dout & 0x7, &params);364	axi_clkgen_set_div(axi_clkgen,  MMCM_REG_CLKOUT0_1, MMCM_REG_CLKOUT0_2,365		MMCM_REG_CLKOUT5_2, &params);366 367	axi_clkgen_calc_clk_params(d, 0, &params);368	axi_clkgen_mmcm_write(axi_clkgen, MMCM_REG_CLK_DIV,369		(params.edge << 13) | (params.nocount << 12) |370		(params.high << 6) | params.low, 0x3fff);371 372	axi_clkgen_calc_clk_params(m >> 3, m & 0x7, &params);373	axi_clkgen_set_div(axi_clkgen,  MMCM_REG_CLK_FB1, MMCM_REG_CLK_FB2,374		MMCM_REG_CLKOUT6_2, &params);375 376	axi_clkgen_mmcm_write(axi_clkgen, MMCM_REG_LOCK1, lock & 0x3ff, 0x3ff);377	axi_clkgen_mmcm_write(axi_clkgen, MMCM_REG_LOCK2,378		(((lock >> 16) & 0x1f) << 10) | 0x1, 0x7fff);379	axi_clkgen_mmcm_write(axi_clkgen, MMCM_REG_LOCK3,380		(((lock >> 24) & 0x1f) << 10) | 0x3e9, 0x7fff);381	axi_clkgen_mmcm_write(axi_clkgen, MMCM_REG_FILTER1, filter >> 16, 0x9900);382	axi_clkgen_mmcm_write(axi_clkgen, MMCM_REG_FILTER2, filter, 0x9900);383 384	return 0;385}386 387static int axi_clkgen_determine_rate(struct clk_hw *hw,388				     struct clk_rate_request *req)389{390	struct axi_clkgen *axi_clkgen = clk_hw_to_axi_clkgen(hw);391	const struct axi_clkgen_limits *limits = &axi_clkgen->limits;392	unsigned int d, m, dout;393	unsigned long long tmp;394 395	axi_clkgen_calc_params(limits, req->best_parent_rate, req->rate,396			       &d, &m, &dout);397 398	if (d == 0 || dout == 0 || m == 0)399		return -EINVAL;400 401	tmp = (unsigned long long)req->best_parent_rate * m;402	tmp = DIV_ROUND_CLOSEST_ULL(tmp, dout * d);403 404	req->rate = min_t(unsigned long long, tmp, LONG_MAX);405	return 0;406}407 408static unsigned int axi_clkgen_get_div(struct axi_clkgen *axi_clkgen,409	unsigned int reg1, unsigned int reg2)410{411	unsigned int val1, val2;412	unsigned int div;413 414	axi_clkgen_mmcm_read(axi_clkgen, reg2, &val2);415	if (val2 & MMCM_CLKOUT_NOCOUNT)416		return 8;417 418	axi_clkgen_mmcm_read(axi_clkgen, reg1, &val1);419 420	div = (val1 & 0x3f) + ((val1 >> 6) & 0x3f);421	div <<= 3;422 423	if (val2 & MMCM_CLK_DIV_DIVIDE) {424		if ((val2 & BIT(7)) && (val2 & 0x7000) != 0x1000)425			div += 8;426		else427			div += 16;428 429		div += (val2 >> 12) & 0x7;430	}431 432	return div;433}434 435static unsigned long axi_clkgen_recalc_rate(struct clk_hw *clk_hw,436	unsigned long parent_rate)437{438	struct axi_clkgen *axi_clkgen = clk_hw_to_axi_clkgen(clk_hw);439	unsigned int d, m, dout;440	unsigned long long tmp;441	unsigned int val;442 443	dout = axi_clkgen_get_div(axi_clkgen, MMCM_REG_CLKOUT0_1,444		MMCM_REG_CLKOUT0_2);445	m = axi_clkgen_get_div(axi_clkgen, MMCM_REG_CLK_FB1,446		MMCM_REG_CLK_FB2);447 448	axi_clkgen_mmcm_read(axi_clkgen, MMCM_REG_CLK_DIV, &val);449	if (val & MMCM_CLK_DIV_NOCOUNT)450		d = 1;451	else452		d = (val & 0x3f) + ((val >> 6) & 0x3f);453 454	if (d == 0 || dout == 0)455		return 0;456 457	tmp = (unsigned long long)parent_rate * m;458	tmp = DIV_ROUND_CLOSEST_ULL(tmp, dout * d);459 460	return min_t(unsigned long long, tmp, ULONG_MAX);461}462 463static int axi_clkgen_enable(struct clk_hw *clk_hw)464{465	struct axi_clkgen *axi_clkgen = clk_hw_to_axi_clkgen(clk_hw);466 467	axi_clkgen_mmcm_enable(axi_clkgen, true);468 469	return 0;470}471 472static void axi_clkgen_disable(struct clk_hw *clk_hw)473{474	struct axi_clkgen *axi_clkgen = clk_hw_to_axi_clkgen(clk_hw);475 476	axi_clkgen_mmcm_enable(axi_clkgen, false);477}478 479static int axi_clkgen_set_parent(struct clk_hw *clk_hw, u8 index)480{481	struct axi_clkgen *axi_clkgen = clk_hw_to_axi_clkgen(clk_hw);482 483	axi_clkgen_write(axi_clkgen, AXI_CLKGEN_V2_REG_CLKSEL, index);484 485	return 0;486}487 488static u8 axi_clkgen_get_parent(struct clk_hw *clk_hw)489{490	struct axi_clkgen *axi_clkgen = clk_hw_to_axi_clkgen(clk_hw);491	unsigned int parent;492 493	axi_clkgen_read(axi_clkgen, AXI_CLKGEN_V2_REG_CLKSEL, &parent);494 495	return parent;496}497 498static const struct clk_ops axi_clkgen_ops = {499	.recalc_rate = axi_clkgen_recalc_rate,500	.determine_rate = axi_clkgen_determine_rate,501	.set_rate = axi_clkgen_set_rate,502	.enable = axi_clkgen_enable,503	.disable = axi_clkgen_disable,504	.set_parent = axi_clkgen_set_parent,505	.get_parent = axi_clkgen_get_parent,506};507 508static int axi_clkgen_probe(struct platform_device *pdev)509{510	const struct axi_clkgen_limits *dflt_limits;511	struct axi_clkgen *axi_clkgen;512	struct clk_init_data init;513	const char *parent_names[2];514	const char *clk_name;515	unsigned int i;516	int ret;517 518	dflt_limits = device_get_match_data(&pdev->dev);519	if (!dflt_limits)520		return -ENODEV;521 522	axi_clkgen = devm_kzalloc(&pdev->dev, sizeof(*axi_clkgen), GFP_KERNEL);523	if (!axi_clkgen)524		return -ENOMEM;525 526	axi_clkgen->base = devm_platform_ioremap_resource(pdev, 0);527	if (IS_ERR(axi_clkgen->base))528		return PTR_ERR(axi_clkgen->base);529 530	init.num_parents = of_clk_get_parent_count(pdev->dev.of_node);531	if (init.num_parents < 1 || init.num_parents > 2)532		return -EINVAL;533 534	for (i = 0; i < init.num_parents; i++) {535		parent_names[i] = of_clk_get_parent_name(pdev->dev.of_node, i);536		if (!parent_names[i])537			return -EINVAL;538	}539 540	memcpy(&axi_clkgen->limits, dflt_limits, sizeof(axi_clkgen->limits));541 542	clk_name = pdev->dev.of_node->name;543	of_property_read_string(pdev->dev.of_node, "clock-output-names",544		&clk_name);545 546	init.name = clk_name;547	init.ops = &axi_clkgen_ops;548	init.flags = CLK_SET_RATE_GATE | CLK_SET_PARENT_GATE;549	init.parent_names = parent_names;550 551	axi_clkgen_mmcm_enable(axi_clkgen, false);552 553	axi_clkgen->clk_hw.init = &init;554	ret = devm_clk_hw_register(&pdev->dev, &axi_clkgen->clk_hw);555	if (ret)556		return ret;557 558	return devm_of_clk_add_hw_provider(&pdev->dev, of_clk_hw_simple_get,559					   &axi_clkgen->clk_hw);560}561 562static const struct of_device_id axi_clkgen_ids[] = {563	{564		.compatible = "adi,zynqmp-axi-clkgen-2.00.a",565		.data = &axi_clkgen_zynqmp_default_limits,566	},567	{568		.compatible = "adi,axi-clkgen-2.00.a",569		.data = &axi_clkgen_zynq_default_limits,570	},571	{ }572};573MODULE_DEVICE_TABLE(of, axi_clkgen_ids);574 575static struct platform_driver axi_clkgen_driver = {576	.driver = {577		.name = "adi-axi-clkgen",578		.of_match_table = axi_clkgen_ids,579	},580	.probe = axi_clkgen_probe,581};582module_platform_driver(axi_clkgen_driver);583 584MODULE_LICENSE("GPL v2");585MODULE_AUTHOR("Lars-Peter Clausen <lars@metafoo.de>");586MODULE_DESCRIPTION("Driver for the Analog Devices' AXI clkgen pcore clock generator");587