aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorBoris BREZILLON <boris.brezillon@free-electrons.com>2014-05-15 04:55:11 -0400
committerMaxime Ripard <maxime.ripard@free-electrons.com>2014-06-11 04:25:02 -0400
commitc8a76cac19eebf65f629e3676e57743f9dfeea8f (patch)
tree3988645d9fe86f15009e324b8e0ac575d2c27d7d
parentefb3184c08927c54ed984c85f69f54ff1dbd2c37 (diff)
clk: sunxi: add PRCM (Power/Reset/Clock Management) clks support
The PRCM (Power/Reset/Clock Management) unit provides several clock devices: - AR100 clk: used to clock the Power Management co-processor - AHB0 clk: used to clock the AHB0 bus - APB0 clk and gates: used to clk peripherals connected to the APB0 bus Add support for these clks in a separate driver so that they can be probed as platform devices instead of registered during early init. This is needed to be able to probe PRCM MFD subdevices. Signed-off-by: Boris BREZILLON <boris.brezillon@free-electrons.com> Acked-by: Maxime Ripard <maxime.ripard@free-electrons.com> Signed-off-by: Emilio López <emilio@elopez.com.ar>
-rw-r--r--drivers/clk/sunxi/Makefile2
-rw-r--r--drivers/clk/sunxi/clk-sun6i-apb0-gates.c99
-rw-r--r--drivers/clk/sunxi/clk-sun6i-apb0.c77
-rw-r--r--drivers/clk/sunxi/clk-sun6i-ar100.c233
4 files changed, 411 insertions, 0 deletions
diff --git a/drivers/clk/sunxi/Makefile b/drivers/clk/sunxi/Makefile
index 839029e36144..762fd64dbd1f 100644
--- a/drivers/clk/sunxi/Makefile
+++ b/drivers/clk/sunxi/Makefile
@@ -5,3 +5,5 @@
5obj-y += clk-sunxi.o clk-factors.o 5obj-y += clk-sunxi.o clk-factors.o
6obj-y += clk-a10-hosc.o 6obj-y += clk-a10-hosc.o
7obj-y += clk-a20-gmac.o 7obj-y += clk-a20-gmac.o
8
9obj-$(CONFIG_MFD_SUN6I_PRCM) += clk-sun6i-ar100.o clk-sun6i-apb0.o clk-sun6i-apb0-gates.o
diff --git a/drivers/clk/sunxi/clk-sun6i-apb0-gates.c b/drivers/clk/sunxi/clk-sun6i-apb0-gates.c
new file mode 100644
index 000000000000..44cd27c5c401
--- /dev/null
+++ b/drivers/clk/sunxi/clk-sun6i-apb0-gates.c
@@ -0,0 +1,99 @@
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 APB0 clock gates 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_APB0_GATES_MAX_SIZE 32
17
18static int sun6i_a31_apb0_gates_clk_probe(struct platform_device *pdev)
19{
20 struct device_node *np = pdev->dev.of_node;
21 struct clk_onecell_data *clk_data;
22 const char *clk_parent;
23 const char *clk_name;
24 struct resource *r;
25 void __iomem *reg;
26 int gate_id;
27 int ngates;
28 int i;
29
30 r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
31 reg = devm_ioremap_resource(&pdev->dev, r);
32 if (!reg)
33 return PTR_ERR(reg);
34
35 clk_parent = of_clk_get_parent_name(np, 0);
36 if (!clk_parent)
37 return -EINVAL;
38
39 ngates = of_property_count_strings(np, "clock-output-names");
40 if (ngates < 0)
41 return ngates;
42
43 if (!ngates || ngates > SUN6I_APB0_GATES_MAX_SIZE)
44 return -EINVAL;
45
46 clk_data = devm_kzalloc(&pdev->dev, sizeof(struct clk_onecell_data),
47 GFP_KERNEL);
48 if (!clk_data)
49 return -ENOMEM;
50
51 clk_data->clks = devm_kzalloc(&pdev->dev,
52 SUN6I_APB0_GATES_MAX_SIZE *
53 sizeof(struct clk *),
54 GFP_KERNEL);
55 if (!clk_data->clks)
56 return -ENOMEM;
57
58 for (i = 0; i < ngates; i++) {
59 of_property_read_string_index(np, "clock-output-names",
60 i, &clk_name);
61
62 gate_id = i;
63 of_property_read_u32_index(np, "clock-indices", i, &gate_id);
64
65 WARN_ON(gate_id >= SUN6I_APB0_GATES_MAX_SIZE);
66 if (gate_id >= SUN6I_APB0_GATES_MAX_SIZE)
67 continue;
68
69 clk_data->clks[gate_id] = clk_register_gate(&pdev->dev,
70 clk_name,
71 clk_parent, 0,
72 reg, gate_id,
73 0, NULL);
74 WARN_ON(IS_ERR(clk_data->clks[gate_id]));
75 }
76
77 clk_data->clk_num = ngates;
78
79 return of_clk_add_provider(np, of_clk_src_onecell_get, clk_data);
80}
81
82const struct of_device_id sun6i_a31_apb0_gates_clk_dt_ids[] = {
83 { .compatible = "allwinner,sun6i-a31-apb0-gates-clk" },
84 { /* sentinel */ }
85};
86
87static struct platform_driver sun6i_a31_apb0_gates_clk_driver = {
88 .driver = {
89 .name = "sun6i-a31-apb0-gates-clk",
90 .owner = THIS_MODULE,
91 .of_match_table = sun6i_a31_apb0_gates_clk_dt_ids,
92 },
93 .probe = sun6i_a31_apb0_gates_clk_probe,
94};
95module_platform_driver(sun6i_a31_apb0_gates_clk_driver);
96
97MODULE_AUTHOR("Boris BREZILLON <boris.brezillon@free-electrons.com>");
98MODULE_DESCRIPTION("Allwinner A31 APB0 gate clocks driver");
99MODULE_LICENSE("GPL v2");
diff --git a/drivers/clk/sunxi/clk-sun6i-apb0.c b/drivers/clk/sunxi/clk-sun6i-apb0.c
new file mode 100644
index 000000000000..11f17c34c2ae
--- /dev/null
+++ b/drivers/clk/sunxi/clk-sun6i-apb0.c
@@ -0,0 +1,77 @@
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 APB0 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/*
17 * The APB0 clk has a configurable divisor.
18 *
19 * We must use a clk_div_table and not a regular power of 2
20 * divisor here, because the first 2 values divide the clock
21 * by 2.
22 */
23static const struct clk_div_table sun6i_a31_apb0_divs[] = {
24 { .val = 0, .div = 2, },
25 { .val = 1, .div = 2, },
26 { .val = 2, .div = 4, },
27 { .val = 3, .div = 8, },
28 { /* sentinel */ },
29};
30
31static int sun6i_a31_apb0_clk_probe(struct platform_device *pdev)
32{
33 struct device_node *np = pdev->dev.of_node;
34 const char *clk_name = np->name;
35 const char *clk_parent;
36 struct resource *r;
37 void __iomem *reg;
38 struct clk *clk;
39
40 r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
41 reg = devm_ioremap_resource(&pdev->dev, r);
42 if (IS_ERR(reg))
43 return PTR_ERR(reg);
44
45 clk_parent = of_clk_get_parent_name(np, 0);
46 if (!clk_parent)
47 return -EINVAL;
48
49 of_property_read_string(np, "clock-output-names", &clk_name);
50
51 clk = clk_register_divider_table(&pdev->dev, clk_name, clk_parent,
52 0, reg, 0, 2, 0, sun6i_a31_apb0_divs,
53 NULL);
54 if (IS_ERR(clk))
55 return PTR_ERR(clk);
56
57 return of_clk_add_provider(np, of_clk_src_simple_get, clk);
58}
59
60const struct of_device_id sun6i_a31_apb0_clk_dt_ids[] = {
61 { .compatible = "allwinner,sun6i-a31-apb0-clk" },
62 { /* sentinel */ }
63};
64
65static struct platform_driver sun6i_a31_apb0_clk_driver = {
66 .driver = {
67 .name = "sun6i-a31-apb0-clk",
68 .owner = THIS_MODULE,
69 .of_match_table = sun6i_a31_apb0_clk_dt_ids,
70 },
71 .probe = sun6i_a31_apb0_clk_probe,
72};
73module_platform_driver(sun6i_a31_apb0_clk_driver);
74
75MODULE_AUTHOR("Boris BREZILLON <boris.brezillon@free-electrons.com>");
76MODULE_DESCRIPTION("Allwinner A31 APB0 clock Driver");
77MODULE_LICENSE("GPL v2");
diff --git a/drivers/clk/sunxi/clk-sun6i-ar100.c b/drivers/clk/sunxi/clk-sun6i-ar100.c
new file mode 100644
index 000000000000..f73cc051f0dd
--- /dev/null
+++ b/drivers/clk/sunxi/clk-sun6i-ar100.c
@@ -0,0 +1,233 @@
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 *best_parent_rate,
49 struct clk **best_parent_clk)
50{
51 int nparents = __clk_get_num_parents(hw->clk);
52 long best_rate = -EINVAL;
53 int i;
54
55 *best_parent_clk = NULL;
56
57 for (i = 0; i < nparents; i++) {
58 unsigned long parent_rate;
59 unsigned long tmp_rate;
60 struct clk *parent;
61 unsigned long div;
62 int shift;
63
64 parent = clk_get_parent_by_index(hw->clk, i);
65 parent_rate = __clk_get_rate(parent);
66 div = DIV_ROUND_UP(parent_rate, rate);
67
68 /*
69 * The AR100 clk contains 2 divisors:
70 * - one power of 2 divisor
71 * - one regular divisor
72 *
73 * First check if we can safely shift (or divide by a power
74 * of 2) without losing precision on the requested rate.
75 */
76 shift = ffs(div) - 1;
77 if (shift > SUN6I_AR100_SHIFT_MAX)
78 shift = SUN6I_AR100_SHIFT_MAX;
79
80 div >>= shift;
81
82 /*
83 * Then if the divisor is still bigger than what the HW
84 * actually supports, use a bigger shift (or power of 2
85 * divider) value and accept to lose some precision.
86 */
87 while (div > SUN6I_AR100_DIV_MAX) {
88 shift++;
89 div >>= 1;
90 if (shift > SUN6I_AR100_SHIFT_MAX)
91 break;
92 }
93
94 /*
95 * If the shift value (or power of 2 divider) is bigger
96 * than what the HW actually support, skip this parent.
97 */
98 if (shift > SUN6I_AR100_SHIFT_MAX)
99 continue;
100
101 tmp_rate = (parent_rate >> shift) / div;
102 if (!*best_parent_clk || tmp_rate > best_rate) {
103 *best_parent_clk = parent;
104 *best_parent_rate = parent_rate;
105 best_rate = tmp_rate;
106 }
107 }
108
109 return best_rate;
110}
111
112static int ar100_set_parent(struct clk_hw *hw, u8 index)
113{
114 struct ar100_clk *clk = to_ar100_clk(hw);
115 u32 val = readl(clk->reg);
116
117 if (index >= SUN6I_AR100_MAX_PARENTS)
118 return -EINVAL;
119
120 val &= ~(SUN6I_AR100_MUX_MASK << SUN6I_AR100_MUX_SHIFT);
121 val |= (index << SUN6I_AR100_MUX_SHIFT);
122 writel(val, clk->reg);
123
124 return 0;
125}
126
127static u8 ar100_get_parent(struct clk_hw *hw)
128{
129 struct ar100_clk *clk = to_ar100_clk(hw);
130 return (readl(clk->reg) >> SUN6I_AR100_MUX_SHIFT) &
131 SUN6I_AR100_MUX_MASK;
132}
133
134static int ar100_set_rate(struct clk_hw *hw, unsigned long rate,
135 unsigned long parent_rate)
136{
137 unsigned long div = parent_rate / rate;
138 struct ar100_clk *clk = to_ar100_clk(hw);
139 u32 val = readl(clk->reg);
140 int shift;
141
142 if (parent_rate % rate)
143 return -EINVAL;
144
145 shift = ffs(div) - 1;
146 if (shift > SUN6I_AR100_SHIFT_MAX)
147 shift = SUN6I_AR100_SHIFT_MAX;
148
149 div >>= shift;
150
151 if (div > SUN6I_AR100_DIV_MAX)
152 return -EINVAL;
153
154 val &= ~((SUN6I_AR100_SHIFT_MASK << SUN6I_AR100_SHIFT_SHIFT) |
155 (SUN6I_AR100_DIV_MASK << SUN6I_AR100_DIV_SHIFT));
156 val |= (shift << SUN6I_AR100_SHIFT_SHIFT) |
157 (div << SUN6I_AR100_DIV_SHIFT);
158 writel(val, clk->reg);
159
160 return 0;
161}
162
163struct clk_ops ar100_ops = {
164 .recalc_rate = ar100_recalc_rate,
165 .determine_rate = ar100_determine_rate,
166 .set_parent = ar100_set_parent,
167 .get_parent = ar100_get_parent,
168 .set_rate = ar100_set_rate,
169};
170
171static int sun6i_a31_ar100_clk_probe(struct platform_device *pdev)
172{
173 const char *parents[SUN6I_AR100_MAX_PARENTS];
174 struct device_node *np = pdev->dev.of_node;
175 const char *clk_name = np->name;
176 struct clk_init_data init;
177 struct ar100_clk *ar100;
178 struct resource *r;
179 struct clk *clk;
180 int nparents;
181 int i;
182
183 ar100 = devm_kzalloc(&pdev->dev, sizeof(*ar100), GFP_KERNEL);
184 if (!ar100)
185 return -ENOMEM;
186
187 r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
188 ar100->reg = devm_ioremap_resource(&pdev->dev, r);
189 if (IS_ERR(ar100->reg))
190 return PTR_ERR(ar100->reg);
191
192 nparents = of_clk_get_parent_count(np);
193 if (nparents > SUN6I_AR100_MAX_PARENTS)
194 nparents = SUN6I_AR100_MAX_PARENTS;
195
196 for (i = 0; i < nparents; i++)
197 parents[i] = of_clk_get_parent_name(np, i);
198
199 of_property_read_string(np, "clock-output-names", &clk_name);
200
201 init.name = clk_name;
202 init.ops = &ar100_ops;
203 init.parent_names = parents;
204 init.num_parents = nparents;
205 init.flags = 0;
206
207 ar100->hw.init = &init;
208
209 clk = clk_register(&pdev->dev, &ar100->hw);
210 if (IS_ERR(clk))
211 return PTR_ERR(clk);
212
213 return of_clk_add_provider(np, of_clk_src_simple_get, clk);
214}
215
216const struct of_device_id sun6i_a31_ar100_clk_dt_ids[] = {
217 { .compatible = "allwinner,sun6i-a31-ar100-clk" },
218 { /* sentinel */ }
219};
220
221static struct platform_driver sun6i_a31_ar100_clk_driver = {
222 .driver = {
223 .name = "sun6i-a31-ar100-clk",
224 .owner = THIS_MODULE,
225 .of_match_table = sun6i_a31_ar100_clk_dt_ids,
226 },
227 .probe = sun6i_a31_ar100_clk_probe,
228};
229module_platform_driver(sun6i_a31_ar100_clk_driver);
230
231MODULE_AUTHOR("Boris BREZILLON <boris.brezillon@free-electrons.com>");
232MODULE_DESCRIPTION("Allwinner A31 AR100 clock Driver");
233MODULE_LICENSE("GPL v2");