aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/clk/sunxi
diff options
context:
space:
mode:
authorChen-Yu Tsai <wens@csie.org>2016-01-25 08:15:46 -0500
committerMaxime Ripard <maxime.ripard@free-electrons.com>2016-01-29 05:30:28 -0500
commit3ca2377b6fed7c3db0c064ea73327cc6895e175d (patch)
tree1d13e37f81f548ea1e136adbc8a70ebbc5bbabfc /drivers/clk/sunxi
parenta78bb35552a800949b2bf68f372d3d6ccabdd790 (diff)
clk: sunxi: rewrite sun6i-ar100 using factors clk
sun6i's AR100 clock is a classic factors clk case: AR100 = ((parent mux) >> p) / (m + 1) Signed-off-by: Chen-Yu Tsai <wens@csie.org> Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com>
Diffstat (limited to 'drivers/clk/sunxi')
-rw-r--r--drivers/clk/sunxi/clk-sun6i-ar100.c235
1 files changed, 61 insertions, 174 deletions
diff --git a/drivers/clk/sunxi/clk-sun6i-ar100.c b/drivers/clk/sunxi/clk-sun6i-ar100.c
index 20887686bdbe..a7f5777834eb 100644
--- a/drivers/clk/sunxi/clk-sun6i-ar100.c
+++ b/drivers/clk/sunxi/clk-sun6i-ar100.c
@@ -8,211 +8,97 @@
8 * 8 *
9 */ 9 */
10 10
11#include <linux/bitops.h>
11#include <linux/clk-provider.h> 12#include <linux/clk-provider.h>
12#include <linux/module.h> 13#include <linux/module.h>
13#include <linux/of.h> 14#include <linux/of.h>
14#include <linux/platform_device.h> 15#include <linux/platform_device.h>
16#include <linux/spinlock.h>
15 17
16#define SUN6I_AR100_MAX_PARENTS 4 18#include "clk-factors.h"
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 int ar100_determine_rate(struct clk_hw *hw,
48 struct clk_rate_request *req)
49{
50 int nparents = clk_hw_get_num_parents(hw);
51 long best_rate = -EINVAL;
52 int i;
53
54 req->best_parent_hw = NULL;
55
56 for (i = 0; i < nparents; i++) {
57 unsigned long parent_rate;
58 unsigned long tmp_rate;
59 struct clk_hw *parent;
60 unsigned long div;
61 int shift;
62
63 parent = clk_hw_get_parent_by_index(hw, i);
64 parent_rate = clk_hw_get_rate(parent);
65 div = DIV_ROUND_UP(parent_rate, req->rate);
66
67 /*
68 * The AR100 clk contains 2 divisors:
69 * - one power of 2 divisor
70 * - one regular divisor
71 *
72 * First check if we can safely shift (or divide by a power
73 * of 2) without losing precision on the requested rate.
74 */
75 shift = ffs(div) - 1;
76 if (shift > SUN6I_AR100_SHIFT_MAX)
77 shift = SUN6I_AR100_SHIFT_MAX;
78
79 div >>= shift;
80
81 /*
82 * Then if the divisor is still bigger than what the HW
83 * actually supports, use a bigger shift (or power of 2
84 * divider) value and accept to lose some precision.
85 */
86 while (div > SUN6I_AR100_DIV_MAX) {
87 shift++;
88 div >>= 1;
89 if (shift > SUN6I_AR100_SHIFT_MAX)
90 break;
91 }
92
93 /*
94 * If the shift value (or power of 2 divider) is bigger
95 * than what the HW actually support, skip this parent.
96 */
97 if (shift > SUN6I_AR100_SHIFT_MAX)
98 continue;
99
100 tmp_rate = (parent_rate >> shift) / div;
101 if (!req->best_parent_hw || tmp_rate > best_rate) {
102 req->best_parent_hw = parent;
103 req->best_parent_rate = parent_rate;
104 best_rate = tmp_rate;
105 }
106 }
107
108 if (best_rate < 0)
109 return best_rate;
110
111 req->rate = best_rate;
112
113 return 0;
114}
115
116static int ar100_set_parent(struct clk_hw *hw, u8 index)
117{
118 struct ar100_clk *clk = to_ar100_clk(hw);
119 u32 val = readl(clk->reg);
120
121 if (index >= SUN6I_AR100_MAX_PARENTS)
122 return -EINVAL;
123
124 val &= ~(SUN6I_AR100_MUX_MASK << SUN6I_AR100_MUX_SHIFT);
125 val |= (index << SUN6I_AR100_MUX_SHIFT);
126 writel(val, clk->reg);
127
128 return 0;
129}
130 19
131static u8 ar100_get_parent(struct clk_hw *hw) 20/**
132{ 21 * sun6i_get_ar100_factors - Calculates factors p, m for AR100
133 struct ar100_clk *clk = to_ar100_clk(hw); 22 *
134 return (readl(clk->reg) >> SUN6I_AR100_MUX_SHIFT) & 23 * AR100 rate is calculated as follows
135 SUN6I_AR100_MUX_MASK; 24 * rate = (parent_rate >> p) / (m + 1);
136} 25 */
137 26static void sun6i_get_ar100_factors(struct factors_request *req)
138static int ar100_set_rate(struct clk_hw *hw, unsigned long rate,
139 unsigned long parent_rate)
140{ 27{
141 unsigned long div = parent_rate / rate; 28 unsigned long div;
142 struct ar100_clk *clk = to_ar100_clk(hw);
143 u32 val = readl(clk->reg);
144 int shift; 29 int shift;
145 30
146 if (parent_rate % rate) 31 /* clock only divides */
147 return -EINVAL; 32 if (req->rate > req->parent_rate)
33 req->rate = req->parent_rate;
148 34
149 shift = ffs(div) - 1; 35 div = DIV_ROUND_UP(req->parent_rate, req->rate);
150 if (shift > SUN6I_AR100_SHIFT_MAX)
151 shift = SUN6I_AR100_SHIFT_MAX;
152 36
153 div >>= shift; 37 if (div < 32)
38 shift = 0;
39 else if (div >> 1 < 32)
40 shift = 1;
41 else if (div >> 2 < 32)
42 shift = 2;
43 else
44 shift = 3;
154 45
155 if (div > SUN6I_AR100_DIV_MAX) 46 div >>= shift;
156 return -EINVAL;
157 47
158 val &= ~((SUN6I_AR100_SHIFT_MASK << SUN6I_AR100_SHIFT_SHIFT) | 48 if (div > 32)
159 (SUN6I_AR100_DIV_MASK << SUN6I_AR100_DIV_SHIFT)); 49 div = 32;
160 val |= (shift << SUN6I_AR100_SHIFT_SHIFT) |
161 (div << SUN6I_AR100_DIV_SHIFT);
162 writel(val, clk->reg);
163 50
164 return 0; 51 req->rate = (req->parent_rate >> shift) / div;
52 req->m = div - 1;
53 req->p = shift;
165} 54}
166 55
167static struct clk_ops ar100_ops = { 56static const struct clk_factors_config sun6i_ar100_config = {
168 .recalc_rate = ar100_recalc_rate, 57 .mwidth = 5,
169 .determine_rate = ar100_determine_rate, 58 .mshift = 8,
170 .set_parent = ar100_set_parent, 59 .pwidth = 2,
171 .get_parent = ar100_get_parent, 60 .pshift = 4,
172 .set_rate = ar100_set_rate,
173}; 61};
174 62
63static const struct factors_data sun6i_ar100_data __initconst = {
64 .mux = 16,
65 .muxmask = GENMASK(1, 0),
66 .table = &sun6i_ar100_config,
67 .getter = sun6i_get_ar100_factors,
68};
69
70static DEFINE_SPINLOCK(sun6i_ar100_lock);
71
175static int sun6i_a31_ar100_clk_probe(struct platform_device *pdev) 72static int sun6i_a31_ar100_clk_probe(struct platform_device *pdev)
176{ 73{
177 const char *parents[SUN6I_AR100_MAX_PARENTS];
178 struct device_node *np = pdev->dev.of_node; 74 struct device_node *np = pdev->dev.of_node;
179 const char *clk_name = np->name;
180 struct clk_init_data init;
181 struct ar100_clk *ar100;
182 struct resource *r; 75 struct resource *r;
76 void __iomem *reg;
183 struct clk *clk; 77 struct clk *clk;
184 int nparents;
185
186 ar100 = devm_kzalloc(&pdev->dev, sizeof(*ar100), GFP_KERNEL);
187 if (!ar100)
188 return -ENOMEM;
189 78
190 r = platform_get_resource(pdev, IORESOURCE_MEM, 0); 79 r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
191 ar100->reg = devm_ioremap_resource(&pdev->dev, r); 80 reg = devm_ioremap_resource(&pdev->dev, r);
192 if (IS_ERR(ar100->reg)) 81 if (IS_ERR(reg))
193 return PTR_ERR(ar100->reg); 82 return PTR_ERR(reg);
194 83
195 nparents = of_clk_get_parent_count(np); 84 clk = sunxi_factors_register(np, &sun6i_ar100_data, &sun6i_ar100_lock,
196 if (nparents > SUN6I_AR100_MAX_PARENTS) 85 reg);
197 nparents = SUN6I_AR100_MAX_PARENTS; 86 if (!clk)
198 87 return -ENOMEM;
199 of_clk_parent_fill(np, parents, nparents);
200 88
201 of_property_read_string(np, "clock-output-names", &clk_name); 89 platform_set_drvdata(pdev, clk);
202 90
203 init.name = clk_name; 91 return 0;
204 init.ops = &ar100_ops; 92}
205 init.parent_names = parents;
206 init.num_parents = nparents;
207 init.flags = 0;
208 93
209 ar100->hw.init = &init; 94static int sun6i_a31_ar100_clk_remove(struct platform_device *pdev)
95{
96 struct device_node *np = pdev->dev.of_node;
97 struct clk *clk = platform_get_drvdata(pdev);
210 98
211 clk = clk_register(&pdev->dev, &ar100->hw); 99 sunxi_factors_unregister(np, clk);
212 if (IS_ERR(clk))
213 return PTR_ERR(clk);
214 100
215 return of_clk_add_provider(np, of_clk_src_simple_get, clk); 101 return 0;
216} 102}
217 103
218static const struct of_device_id sun6i_a31_ar100_clk_dt_ids[] = { 104static const struct of_device_id sun6i_a31_ar100_clk_dt_ids[] = {
@@ -227,6 +113,7 @@ static struct platform_driver sun6i_a31_ar100_clk_driver = {
227 .of_match_table = sun6i_a31_ar100_clk_dt_ids, 113 .of_match_table = sun6i_a31_ar100_clk_dt_ids,
228 }, 114 },
229 .probe = sun6i_a31_ar100_clk_probe, 115 .probe = sun6i_a31_ar100_clk_probe,
116 .remove = sun6i_a31_ar100_clk_remove,
230}; 117};
231module_platform_driver(sun6i_a31_ar100_clk_driver); 118module_platform_driver(sun6i_a31_ar100_clk_driver);
232 119