1/*
2 * Copyright (C) 2014 Free Electrons
3 *
4 * License Terms: GNU General Public License v2
5 * Author: Boris BREZILLON <boris.brezillon@free-electrons.com>
6 *
7 * Allwinner A31 AR100 clock driver
8 *
9 */
10
11#include <linux/clk-provider.h>
12#include <linux/module.h>
13#include <linux/of.h>
14#include <linux/platform_device.h>
15
16#define SUN6I_AR100_MAX_PARENTS		4
17#define SUN6I_AR100_SHIFT_MASK		0x3
18#define SUN6I_AR100_SHIFT_MAX		SUN6I_AR100_SHIFT_MASK
19#define SUN6I_AR100_SHIFT_SHIFT		4
20#define SUN6I_AR100_DIV_MASK		0x1f
21#define SUN6I_AR100_DIV_MAX		(SUN6I_AR100_DIV_MASK + 1)
22#define SUN6I_AR100_DIV_SHIFT		8
23#define SUN6I_AR100_MUX_MASK		0x3
24#define SUN6I_AR100_MUX_SHIFT		16
25
26struct ar100_clk {
27	struct clk_hw hw;
28	void __iomem *reg;
29};
30
31static inline struct ar100_clk *to_ar100_clk(struct clk_hw *hw)
32{
33	return container_of(hw, struct ar100_clk, hw);
34}
35
36static unsigned long ar100_recalc_rate(struct clk_hw *hw,
37				       unsigned long parent_rate)
38{
39	struct ar100_clk *clk = to_ar100_clk(hw);
40	u32 val = readl(clk->reg);
41	int shift = (val >> SUN6I_AR100_SHIFT_SHIFT) & SUN6I_AR100_SHIFT_MASK;
42	int div = (val >> SUN6I_AR100_DIV_SHIFT) & SUN6I_AR100_DIV_MASK;
43
44	return (parent_rate >> shift) / (div + 1);
45}
46
47static long ar100_determine_rate(struct clk_hw *hw, unsigned long rate,
48				 unsigned long min_rate,
49				 unsigned long max_rate,
50				 unsigned long *best_parent_rate,
51				 struct clk_hw **best_parent_clk)
52{
53	int nparents = __clk_get_num_parents(hw->clk);
54	long best_rate = -EINVAL;
55	int i;
56
57	*best_parent_clk = NULL;
58
59	for (i = 0; i < nparents; i++) {
60		unsigned long parent_rate;
61		unsigned long tmp_rate;
62		struct clk *parent;
63		unsigned long div;
64		int shift;
65
66		parent = clk_get_parent_by_index(hw->clk, i);
67		parent_rate = __clk_get_rate(parent);
68		div = DIV_ROUND_UP(parent_rate, rate);
69
70		/*
71		 * The AR100 clk contains 2 divisors:
72		 * - one power of 2 divisor
73		 * - one regular divisor
74		 *
75		 * First check if we can safely shift (or divide by a power
76		 * of 2) without losing precision on the requested rate.
77		 */
78		shift = ffs(div) - 1;
79		if (shift > SUN6I_AR100_SHIFT_MAX)
80			shift = SUN6I_AR100_SHIFT_MAX;
81
82		div >>= shift;
83
84		/*
85		 * Then if the divisor is still bigger than what the HW
86		 * actually supports, use a bigger shift (or power of 2
87		 * divider) value and accept to lose some precision.
88		 */
89		while (div > SUN6I_AR100_DIV_MAX) {
90			shift++;
91			div >>= 1;
92			if (shift > SUN6I_AR100_SHIFT_MAX)
93				break;
94		}
95
96		/*
97		 * If the shift value (or power of 2 divider) is bigger
98		 * than what the HW actually support, skip this parent.
99		 */
100		if (shift > SUN6I_AR100_SHIFT_MAX)
101			continue;
102
103		tmp_rate = (parent_rate >> shift) / div;
104		if (!*best_parent_clk || tmp_rate > best_rate) {
105			*best_parent_clk = __clk_get_hw(parent);
106			*best_parent_rate = parent_rate;
107			best_rate = tmp_rate;
108		}
109	}
110
111	return best_rate;
112}
113
114static int ar100_set_parent(struct clk_hw *hw, u8 index)
115{
116	struct ar100_clk *clk = to_ar100_clk(hw);
117	u32 val = readl(clk->reg);
118
119	if (index >= SUN6I_AR100_MAX_PARENTS)
120		return -EINVAL;
121
122	val &= ~(SUN6I_AR100_MUX_MASK << SUN6I_AR100_MUX_SHIFT);
123	val |= (index << SUN6I_AR100_MUX_SHIFT);
124	writel(val, clk->reg);
125
126	return 0;
127}
128
129static u8 ar100_get_parent(struct clk_hw *hw)
130{
131	struct ar100_clk *clk = to_ar100_clk(hw);
132	return (readl(clk->reg) >> SUN6I_AR100_MUX_SHIFT) &
133	       SUN6I_AR100_MUX_MASK;
134}
135
136static int ar100_set_rate(struct clk_hw *hw, unsigned long rate,
137			  unsigned long parent_rate)
138{
139	unsigned long div = parent_rate / rate;
140	struct ar100_clk *clk = to_ar100_clk(hw);
141	u32 val = readl(clk->reg);
142	int shift;
143
144	if (parent_rate % rate)
145		return -EINVAL;
146
147	shift = ffs(div) - 1;
148	if (shift > SUN6I_AR100_SHIFT_MAX)
149		shift = SUN6I_AR100_SHIFT_MAX;
150
151	div >>= shift;
152
153	if (div > SUN6I_AR100_DIV_MAX)
154		return -EINVAL;
155
156	val &= ~((SUN6I_AR100_SHIFT_MASK << SUN6I_AR100_SHIFT_SHIFT) |
157		 (SUN6I_AR100_DIV_MASK << SUN6I_AR100_DIV_SHIFT));
158	val |= (shift << SUN6I_AR100_SHIFT_SHIFT) |
159	       (div << SUN6I_AR100_DIV_SHIFT);
160	writel(val, clk->reg);
161
162	return 0;
163}
164
165static struct clk_ops ar100_ops = {
166	.recalc_rate = ar100_recalc_rate,
167	.determine_rate = ar100_determine_rate,
168	.set_parent = ar100_set_parent,
169	.get_parent = ar100_get_parent,
170	.set_rate = ar100_set_rate,
171};
172
173static int sun6i_a31_ar100_clk_probe(struct platform_device *pdev)
174{
175	const char *parents[SUN6I_AR100_MAX_PARENTS];
176	struct device_node *np = pdev->dev.of_node;
177	const char *clk_name = np->name;
178	struct clk_init_data init;
179	struct ar100_clk *ar100;
180	struct resource *r;
181	struct clk *clk;
182	int nparents;
183	int i;
184
185	ar100 = devm_kzalloc(&pdev->dev, sizeof(*ar100), GFP_KERNEL);
186	if (!ar100)
187		return -ENOMEM;
188
189	r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
190	ar100->reg = devm_ioremap_resource(&pdev->dev, r);
191	if (IS_ERR(ar100->reg))
192		return PTR_ERR(ar100->reg);
193
194	nparents = of_clk_get_parent_count(np);
195	if (nparents > SUN6I_AR100_MAX_PARENTS)
196		nparents = SUN6I_AR100_MAX_PARENTS;
197
198	for (i = 0; i < nparents; i++)
199		parents[i] = of_clk_get_parent_name(np, i);
200
201	of_property_read_string(np, "clock-output-names", &clk_name);
202
203	init.name = clk_name;
204	init.ops = &ar100_ops;
205	init.parent_names = parents;
206	init.num_parents = nparents;
207	init.flags = 0;
208
209	ar100->hw.init = &init;
210
211	clk = clk_register(&pdev->dev, &ar100->hw);
212	if (IS_ERR(clk))
213		return PTR_ERR(clk);
214
215	return of_clk_add_provider(np, of_clk_src_simple_get, clk);
216}
217
218static const struct of_device_id sun6i_a31_ar100_clk_dt_ids[] = {
219	{ .compatible = "allwinner,sun6i-a31-ar100-clk" },
220	{ /* sentinel */ }
221};
222
223static struct platform_driver sun6i_a31_ar100_clk_driver = {
224	.driver = {
225		.name = "sun6i-a31-ar100-clk",
226		.of_match_table = sun6i_a31_ar100_clk_dt_ids,
227	},
228	.probe = sun6i_a31_ar100_clk_probe,
229};
230module_platform_driver(sun6i_a31_ar100_clk_driver);
231
232MODULE_AUTHOR("Boris BREZILLON <boris.brezillon@free-electrons.com>");
233MODULE_DESCRIPTION("Allwinner A31 AR100 clock Driver");
234MODULE_LICENSE("GPL v2");
235