diff options
author | Maxime Ripard <maxime.ripard@free-electrons.com> | 2016-04-25 09:22:42 -0400 |
---|---|---|
committer | Stephen Boyd <sboyd@codeaurora.org> | 2016-05-12 17:47:52 -0400 |
commit | 98b8525abb7f0d9a5ab942a2e044011d7e635490 (patch) | |
tree | 50d827b216986059492effd907f25105fd2c66d4 | |
parent | d3da3eaef7f4d0317d01c08824b65e5aee1315ef (diff) |
clk: sunxi: Add display and TCON0 clocks driver
The A10 SoCs and its relatives has a special clock controller to drive the
display engines (both frontend and backend), that have a lot in common with
the clock to drive the first TCON channel.
Add a driver to support both.
Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com>
Acked-by: Rob Herring <robh@kernel.org>
[sboyd@codeaurora.org: Silence variable sized array warning]
Signed-off-by: Stephen Boyd <sboyd@codeaurora.org>
-rw-r--r-- | Documentation/devicetree/bindings/clock/sunxi.txt | 2 | ||||
-rw-r--r-- | drivers/clk/sunxi/Makefile | 1 | ||||
-rw-r--r-- | drivers/clk/sunxi/clk-sun4i-display.c | 261 |
3 files changed, 264 insertions, 0 deletions
diff --git a/Documentation/devicetree/bindings/clock/sunxi.txt b/Documentation/devicetree/bindings/clock/sunxi.txt index f70f500256bb..8f7619d8c8d8 100644 --- a/Documentation/devicetree/bindings/clock/sunxi.txt +++ b/Documentation/devicetree/bindings/clock/sunxi.txt | |||
@@ -64,6 +64,7 @@ Required properties: | |||
64 | "allwinner,sun8i-a83t-bus-gates-clk" - for the bus gates on A83T | 64 | "allwinner,sun8i-a83t-bus-gates-clk" - for the bus gates on A83T |
65 | "allwinner,sun8i-h3-bus-gates-clk" - for the bus gates on H3 | 65 | "allwinner,sun8i-h3-bus-gates-clk" - for the bus gates on H3 |
66 | "allwinner,sun9i-a80-apbs-gates-clk" - for the APBS gates on A80 | 66 | "allwinner,sun9i-a80-apbs-gates-clk" - for the APBS gates on A80 |
67 | "allwinner,sun4i-a10-display-clk" - for the display clocks on the A10 | ||
67 | "allwinner,sun4i-a10-dram-gates-clk" - for the DRAM gates on A10 | 68 | "allwinner,sun4i-a10-dram-gates-clk" - for the DRAM gates on A10 |
68 | "allwinner,sun5i-a13-dram-gates-clk" - for the DRAM gates on A13 | 69 | "allwinner,sun5i-a13-dram-gates-clk" - for the DRAM gates on A13 |
69 | "allwinner,sun5i-a13-mbus-clk" - for the MBUS clock on A13 | 70 | "allwinner,sun5i-a13-mbus-clk" - for the MBUS clock on A13 |
@@ -75,6 +76,7 @@ Required properties: | |||
75 | "allwinner,sun8i-a23-mbus-clk" - for the MBUS clock on A23 | 76 | "allwinner,sun8i-a23-mbus-clk" - for the MBUS clock on A23 |
76 | "allwinner,sun7i-a20-out-clk" - for the external output clocks | 77 | "allwinner,sun7i-a20-out-clk" - for the external output clocks |
77 | "allwinner,sun7i-a20-gmac-clk" - for the GMAC clock module on A20/A31 | 78 | "allwinner,sun7i-a20-gmac-clk" - for the GMAC clock module on A20/A31 |
79 | "allwinner,sun4i-a10-tcon-ch0-clk" - for the TCON channel 0 clock on the A10 | ||
78 | "allwinner,sun4i-a10-tcon-ch1-clk" - for the TCON channel 1 clock on the A10 | 80 | "allwinner,sun4i-a10-tcon-ch1-clk" - for the TCON channel 1 clock on the A10 |
79 | "allwinner,sun4i-a10-usb-clk" - for usb gates + resets on A10 / A20 | 81 | "allwinner,sun4i-a10-usb-clk" - for usb gates + resets on A10 / A20 |
80 | "allwinner,sun5i-a13-usb-clk" - for usb gates + resets on A13 | 82 | "allwinner,sun5i-a13-usb-clk" - for usb gates + resets on A13 |
diff --git a/drivers/clk/sunxi/Makefile b/drivers/clk/sunxi/Makefile index 2d77407a0037..39d2044a1f49 100644 --- a/drivers/clk/sunxi/Makefile +++ b/drivers/clk/sunxi/Makefile | |||
@@ -11,6 +11,7 @@ obj-y += clk-a10-ve.o | |||
11 | obj-y += clk-a20-gmac.o | 11 | obj-y += clk-a20-gmac.o |
12 | obj-y += clk-mod0.o | 12 | obj-y += clk-mod0.o |
13 | obj-y += clk-simple-gates.o | 13 | obj-y += clk-simple-gates.o |
14 | obj-y += clk-sun4i-display.o | ||
14 | obj-y += clk-sun4i-pll3.o | 15 | obj-y += clk-sun4i-pll3.o |
15 | obj-y += clk-sun4i-tcon-ch1.o | 16 | obj-y += clk-sun4i-tcon-ch1.o |
16 | obj-y += clk-sun8i-bus-gates.o | 17 | obj-y += clk-sun8i-bus-gates.o |
diff --git a/drivers/clk/sunxi/clk-sun4i-display.c b/drivers/clk/sunxi/clk-sun4i-display.c new file mode 100644 index 000000000000..445a7498d6df --- /dev/null +++ b/drivers/clk/sunxi/clk-sun4i-display.c | |||
@@ -0,0 +1,261 @@ | |||
1 | /* | ||
2 | * Copyright 2015 Maxime Ripard | ||
3 | * | ||
4 | * Maxime Ripard <maxime.ripard@free-electrons.com> | ||
5 | * | ||
6 | * This program is free software; you can redistribute it and/or modify | ||
7 | * it under the terms of the GNU General Public License as published by | ||
8 | * the Free Software Foundation; either version 2 of the License, or | ||
9 | * (at your option) any later version. | ||
10 | * | ||
11 | * This program is distributed in the hope that it will be useful, | ||
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
14 | * GNU General Public License for more details. | ||
15 | */ | ||
16 | |||
17 | #include <linux/clk-provider.h> | ||
18 | #include <linux/kernel.h> | ||
19 | #include <linux/of_address.h> | ||
20 | #include <linux/reset-controller.h> | ||
21 | #include <linux/slab.h> | ||
22 | #include <linux/spinlock.h> | ||
23 | |||
24 | struct sun4i_a10_display_clk_data { | ||
25 | bool has_div; | ||
26 | u8 num_rst; | ||
27 | u8 parents; | ||
28 | |||
29 | u8 offset_en; | ||
30 | u8 offset_div; | ||
31 | u8 offset_mux; | ||
32 | u8 offset_rst; | ||
33 | |||
34 | u8 width_div; | ||
35 | u8 width_mux; | ||
36 | }; | ||
37 | |||
38 | struct reset_data { | ||
39 | void __iomem *reg; | ||
40 | spinlock_t *lock; | ||
41 | struct reset_controller_dev rcdev; | ||
42 | u8 offset; | ||
43 | }; | ||
44 | |||
45 | static DEFINE_SPINLOCK(sun4i_a10_display_lock); | ||
46 | |||
47 | static inline struct reset_data *rcdev_to_reset_data(struct reset_controller_dev *rcdev) | ||
48 | { | ||
49 | return container_of(rcdev, struct reset_data, rcdev); | ||
50 | }; | ||
51 | |||
52 | static int sun4i_a10_display_assert(struct reset_controller_dev *rcdev, | ||
53 | unsigned long id) | ||
54 | { | ||
55 | struct reset_data *data = rcdev_to_reset_data(rcdev); | ||
56 | unsigned long flags; | ||
57 | u32 reg; | ||
58 | |||
59 | spin_lock_irqsave(data->lock, flags); | ||
60 | |||
61 | reg = readl(data->reg); | ||
62 | writel(reg & ~BIT(data->offset + id), data->reg); | ||
63 | |||
64 | spin_unlock_irqrestore(data->lock, flags); | ||
65 | |||
66 | return 0; | ||
67 | } | ||
68 | |||
69 | static int sun4i_a10_display_deassert(struct reset_controller_dev *rcdev, | ||
70 | unsigned long id) | ||
71 | { | ||
72 | struct reset_data *data = rcdev_to_reset_data(rcdev); | ||
73 | unsigned long flags; | ||
74 | u32 reg; | ||
75 | |||
76 | spin_lock_irqsave(data->lock, flags); | ||
77 | |||
78 | reg = readl(data->reg); | ||
79 | writel(reg | BIT(data->offset + id), data->reg); | ||
80 | |||
81 | spin_unlock_irqrestore(data->lock, flags); | ||
82 | |||
83 | return 0; | ||
84 | } | ||
85 | |||
86 | static int sun4i_a10_display_status(struct reset_controller_dev *rcdev, | ||
87 | unsigned long id) | ||
88 | { | ||
89 | struct reset_data *data = rcdev_to_reset_data(rcdev); | ||
90 | |||
91 | return !(readl(data->reg) & BIT(data->offset + id)); | ||
92 | } | ||
93 | |||
94 | static const struct reset_control_ops sun4i_a10_display_reset_ops = { | ||
95 | .assert = sun4i_a10_display_assert, | ||
96 | .deassert = sun4i_a10_display_deassert, | ||
97 | .status = sun4i_a10_display_status, | ||
98 | }; | ||
99 | |||
100 | static int sun4i_a10_display_reset_xlate(struct reset_controller_dev *rcdev, | ||
101 | const struct of_phandle_args *spec) | ||
102 | { | ||
103 | /* We only have a single reset signal */ | ||
104 | return 0; | ||
105 | } | ||
106 | |||
107 | static void __init sun4i_a10_display_init(struct device_node *node, | ||
108 | const struct sun4i_a10_display_clk_data *data) | ||
109 | { | ||
110 | const char *parents[4]; | ||
111 | const char *clk_name = node->name; | ||
112 | struct reset_data *reset_data; | ||
113 | struct clk_divider *div = NULL; | ||
114 | struct clk_gate *gate; | ||
115 | struct resource res; | ||
116 | struct clk_mux *mux; | ||
117 | void __iomem *reg; | ||
118 | struct clk *clk; | ||
119 | int ret; | ||
120 | |||
121 | of_property_read_string(node, "clock-output-names", &clk_name); | ||
122 | |||
123 | reg = of_io_request_and_map(node, 0, of_node_full_name(node)); | ||
124 | if (IS_ERR(reg)) { | ||
125 | pr_err("%s: Could not map the clock registers\n", clk_name); | ||
126 | return; | ||
127 | } | ||
128 | |||
129 | ret = of_clk_parent_fill(node, parents, data->parents); | ||
130 | if (ret != data->parents) { | ||
131 | pr_err("%s: Could not retrieve the parents\n", clk_name); | ||
132 | goto unmap; | ||
133 | } | ||
134 | |||
135 | mux = kzalloc(sizeof(*mux), GFP_KERNEL); | ||
136 | if (!mux) | ||
137 | goto unmap; | ||
138 | |||
139 | mux->reg = reg; | ||
140 | mux->shift = data->offset_mux; | ||
141 | mux->mask = (1 << data->width_mux) - 1; | ||
142 | mux->lock = &sun4i_a10_display_lock; | ||
143 | |||
144 | gate = kzalloc(sizeof(*gate), GFP_KERNEL); | ||
145 | if (!gate) | ||
146 | goto free_mux; | ||
147 | |||
148 | gate->reg = reg; | ||
149 | gate->bit_idx = data->offset_en; | ||
150 | gate->lock = &sun4i_a10_display_lock; | ||
151 | |||
152 | if (data->has_div) { | ||
153 | div = kzalloc(sizeof(*div), GFP_KERNEL); | ||
154 | if (!div) | ||
155 | goto free_gate; | ||
156 | |||
157 | div->reg = reg; | ||
158 | div->shift = data->offset_div; | ||
159 | div->width = data->width_div; | ||
160 | div->lock = &sun4i_a10_display_lock; | ||
161 | } | ||
162 | |||
163 | clk = clk_register_composite(NULL, clk_name, | ||
164 | parents, data->parents, | ||
165 | &mux->hw, &clk_mux_ops, | ||
166 | data->has_div ? &div->hw : NULL, | ||
167 | data->has_div ? &clk_divider_ops : NULL, | ||
168 | &gate->hw, &clk_gate_ops, | ||
169 | 0); | ||
170 | if (IS_ERR(clk)) { | ||
171 | pr_err("%s: Couldn't register the clock\n", clk_name); | ||
172 | goto free_div; | ||
173 | } | ||
174 | |||
175 | ret = of_clk_add_provider(node, of_clk_src_simple_get, clk); | ||
176 | if (ret) { | ||
177 | pr_err("%s: Couldn't register DT provider\n", clk_name); | ||
178 | goto free_clk; | ||
179 | } | ||
180 | |||
181 | if (!data->num_rst) | ||
182 | return; | ||
183 | |||
184 | reset_data = kzalloc(sizeof(*reset_data), GFP_KERNEL); | ||
185 | if (!reset_data) | ||
186 | goto free_of_clk; | ||
187 | |||
188 | reset_data->reg = reg; | ||
189 | reset_data->offset = data->offset_rst; | ||
190 | reset_data->lock = &sun4i_a10_display_lock; | ||
191 | reset_data->rcdev.nr_resets = data->num_rst; | ||
192 | reset_data->rcdev.ops = &sun4i_a10_display_reset_ops; | ||
193 | reset_data->rcdev.of_node = node; | ||
194 | |||
195 | if (data->num_rst == 1) { | ||
196 | reset_data->rcdev.of_reset_n_cells = 0; | ||
197 | reset_data->rcdev.of_xlate = &sun4i_a10_display_reset_xlate; | ||
198 | } else { | ||
199 | reset_data->rcdev.of_reset_n_cells = 1; | ||
200 | } | ||
201 | |||
202 | if (reset_controller_register(&reset_data->rcdev)) { | ||
203 | pr_err("%s: Couldn't register the reset controller\n", | ||
204 | clk_name); | ||
205 | goto free_reset; | ||
206 | } | ||
207 | |||
208 | return; | ||
209 | |||
210 | free_reset: | ||
211 | kfree(reset_data); | ||
212 | free_of_clk: | ||
213 | of_clk_del_provider(node); | ||
214 | free_clk: | ||
215 | clk_unregister_composite(clk); | ||
216 | free_div: | ||
217 | kfree(div); | ||
218 | free_gate: | ||
219 | kfree(gate); | ||
220 | free_mux: | ||
221 | kfree(mux); | ||
222 | unmap: | ||
223 | iounmap(reg); | ||
224 | of_address_to_resource(node, 0, &res); | ||
225 | release_mem_region(res.start, resource_size(&res)); | ||
226 | } | ||
227 | |||
228 | static const struct sun4i_a10_display_clk_data sun4i_a10_tcon_ch0_data __initconst = { | ||
229 | .num_rst = 2, | ||
230 | .parents = 4, | ||
231 | .offset_en = 31, | ||
232 | .offset_rst = 29, | ||
233 | .offset_mux = 24, | ||
234 | .width_mux = 2, | ||
235 | }; | ||
236 | |||
237 | static void __init sun4i_a10_tcon_ch0_setup(struct device_node *node) | ||
238 | { | ||
239 | sun4i_a10_display_init(node, &sun4i_a10_tcon_ch0_data); | ||
240 | } | ||
241 | CLK_OF_DECLARE(sun4i_a10_tcon_ch0, "allwinner,sun4i-a10-tcon-ch0-clk", | ||
242 | sun4i_a10_tcon_ch0_setup); | ||
243 | |||
244 | static const struct sun4i_a10_display_clk_data sun4i_a10_display_data __initconst = { | ||
245 | .has_div = true, | ||
246 | .num_rst = 1, | ||
247 | .parents = 3, | ||
248 | .offset_en = 31, | ||
249 | .offset_rst = 30, | ||
250 | .offset_mux = 24, | ||
251 | .offset_div = 0, | ||
252 | .width_mux = 2, | ||
253 | .width_div = 4, | ||
254 | }; | ||
255 | |||
256 | static void __init sun4i_a10_display_setup(struct device_node *node) | ||
257 | { | ||
258 | sun4i_a10_display_init(node, &sun4i_a10_display_data); | ||
259 | } | ||
260 | CLK_OF_DECLARE(sun4i_a10_display, "allwinner,sun4i-a10-display-clk", | ||
261 | sun4i_a10_display_setup); | ||