1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /* |
3 | * Copyright 2015 Maxime Ripard |
4 | * |
5 | * Maxime Ripard <maxime.ripard@free-electrons.com> |
6 | */ |
7 | |
8 | #include <linux/clk-provider.h> |
9 | #include <linux/io.h> |
10 | #include <linux/kernel.h> |
11 | #include <linux/of_address.h> |
12 | #include <linux/reset-controller.h> |
13 | #include <linux/slab.h> |
14 | #include <linux/spinlock.h> |
15 | |
16 | struct sun4i_a10_display_clk_data { |
17 | bool has_div; |
18 | u8 num_rst; |
19 | u8 parents; |
20 | |
21 | u8 offset_en; |
22 | u8 offset_div; |
23 | u8 offset_mux; |
24 | u8 offset_rst; |
25 | |
26 | u8 width_div; |
27 | u8 width_mux; |
28 | |
29 | u32 flags; |
30 | }; |
31 | |
32 | struct reset_data { |
33 | void __iomem *reg; |
34 | spinlock_t *lock; |
35 | struct reset_controller_dev rcdev; |
36 | u8 offset; |
37 | }; |
38 | |
39 | static DEFINE_SPINLOCK(sun4i_a10_display_lock); |
40 | |
41 | static inline struct reset_data *rcdev_to_reset_data(struct reset_controller_dev *rcdev) |
42 | { |
43 | return container_of(rcdev, struct reset_data, rcdev); |
44 | }; |
45 | |
46 | static int sun4i_a10_display_assert(struct reset_controller_dev *rcdev, |
47 | unsigned long id) |
48 | { |
49 | struct reset_data *data = rcdev_to_reset_data(rcdev); |
50 | unsigned long flags; |
51 | u32 reg; |
52 | |
53 | spin_lock_irqsave(data->lock, flags); |
54 | |
55 | reg = readl(addr: data->reg); |
56 | writel(val: reg & ~BIT(data->offset + id), addr: data->reg); |
57 | |
58 | spin_unlock_irqrestore(lock: data->lock, flags); |
59 | |
60 | return 0; |
61 | } |
62 | |
63 | static int sun4i_a10_display_deassert(struct reset_controller_dev *rcdev, |
64 | unsigned long id) |
65 | { |
66 | struct reset_data *data = rcdev_to_reset_data(rcdev); |
67 | unsigned long flags; |
68 | u32 reg; |
69 | |
70 | spin_lock_irqsave(data->lock, flags); |
71 | |
72 | reg = readl(addr: data->reg); |
73 | writel(val: reg | BIT(data->offset + id), addr: data->reg); |
74 | |
75 | spin_unlock_irqrestore(lock: data->lock, flags); |
76 | |
77 | return 0; |
78 | } |
79 | |
80 | static int sun4i_a10_display_status(struct reset_controller_dev *rcdev, |
81 | unsigned long id) |
82 | { |
83 | struct reset_data *data = rcdev_to_reset_data(rcdev); |
84 | |
85 | return !(readl(addr: data->reg) & BIT(data->offset + id)); |
86 | } |
87 | |
88 | static const struct reset_control_ops sun4i_a10_display_reset_ops = { |
89 | .assert = sun4i_a10_display_assert, |
90 | .deassert = sun4i_a10_display_deassert, |
91 | .status = sun4i_a10_display_status, |
92 | }; |
93 | |
94 | static int sun4i_a10_display_reset_xlate(struct reset_controller_dev *rcdev, |
95 | const struct of_phandle_args *spec) |
96 | { |
97 | /* We only have a single reset signal */ |
98 | return 0; |
99 | } |
100 | |
101 | static void __init sun4i_a10_display_init(struct device_node *node, |
102 | const struct sun4i_a10_display_clk_data *data) |
103 | { |
104 | const char *parents[4]; |
105 | const char *clk_name = node->name; |
106 | struct reset_data *reset_data; |
107 | struct clk_divider *div = NULL; |
108 | struct clk_gate *gate; |
109 | struct resource res; |
110 | struct clk_mux *mux; |
111 | void __iomem *reg; |
112 | struct clk *clk; |
113 | int ret; |
114 | |
115 | of_property_read_string(np: node, propname: "clock-output-names" , out_string: &clk_name); |
116 | |
117 | reg = of_io_request_and_map(device: node, index: 0, name: of_node_full_name(np: node)); |
118 | if (IS_ERR(ptr: reg)) { |
119 | pr_err("%s: Could not map the clock registers\n" , clk_name); |
120 | return; |
121 | } |
122 | |
123 | ret = of_clk_parent_fill(np: node, parents, size: data->parents); |
124 | if (ret != data->parents) { |
125 | pr_err("%s: Could not retrieve the parents\n" , clk_name); |
126 | goto unmap; |
127 | } |
128 | |
129 | mux = kzalloc(size: sizeof(*mux), GFP_KERNEL); |
130 | if (!mux) |
131 | goto unmap; |
132 | |
133 | mux->reg = reg; |
134 | mux->shift = data->offset_mux; |
135 | mux->mask = (1 << data->width_mux) - 1; |
136 | mux->lock = &sun4i_a10_display_lock; |
137 | |
138 | gate = kzalloc(size: sizeof(*gate), GFP_KERNEL); |
139 | if (!gate) |
140 | goto free_mux; |
141 | |
142 | gate->reg = reg; |
143 | gate->bit_idx = data->offset_en; |
144 | gate->lock = &sun4i_a10_display_lock; |
145 | |
146 | if (data->has_div) { |
147 | div = kzalloc(size: sizeof(*div), GFP_KERNEL); |
148 | if (!div) |
149 | goto free_gate; |
150 | |
151 | div->reg = reg; |
152 | div->shift = data->offset_div; |
153 | div->width = data->width_div; |
154 | div->lock = &sun4i_a10_display_lock; |
155 | } |
156 | |
157 | clk = clk_register_composite(NULL, name: clk_name, |
158 | parent_names: parents, num_parents: data->parents, |
159 | mux_hw: &mux->hw, mux_ops: &clk_mux_ops, |
160 | rate_hw: data->has_div ? &div->hw : NULL, |
161 | rate_ops: data->has_div ? &clk_divider_ops : NULL, |
162 | gate_hw: &gate->hw, gate_ops: &clk_gate_ops, |
163 | flags: data->flags); |
164 | if (IS_ERR(ptr: clk)) { |
165 | pr_err("%s: Couldn't register the clock\n" , clk_name); |
166 | goto free_div; |
167 | } |
168 | |
169 | ret = of_clk_add_provider(np: node, clk_src_get: of_clk_src_simple_get, data: clk); |
170 | if (ret) { |
171 | pr_err("%s: Couldn't register DT provider\n" , clk_name); |
172 | goto free_clk; |
173 | } |
174 | |
175 | if (!data->num_rst) |
176 | return; |
177 | |
178 | reset_data = kzalloc(size: sizeof(*reset_data), GFP_KERNEL); |
179 | if (!reset_data) |
180 | goto free_of_clk; |
181 | |
182 | reset_data->reg = reg; |
183 | reset_data->offset = data->offset_rst; |
184 | reset_data->lock = &sun4i_a10_display_lock; |
185 | reset_data->rcdev.nr_resets = data->num_rst; |
186 | reset_data->rcdev.ops = &sun4i_a10_display_reset_ops; |
187 | reset_data->rcdev.of_node = node; |
188 | |
189 | if (data->num_rst == 1) { |
190 | reset_data->rcdev.of_reset_n_cells = 0; |
191 | reset_data->rcdev.of_xlate = &sun4i_a10_display_reset_xlate; |
192 | } else { |
193 | reset_data->rcdev.of_reset_n_cells = 1; |
194 | } |
195 | |
196 | if (reset_controller_register(rcdev: &reset_data->rcdev)) { |
197 | pr_err("%s: Couldn't register the reset controller\n" , |
198 | clk_name); |
199 | goto free_reset; |
200 | } |
201 | |
202 | return; |
203 | |
204 | free_reset: |
205 | kfree(objp: reset_data); |
206 | free_of_clk: |
207 | of_clk_del_provider(np: node); |
208 | free_clk: |
209 | clk_unregister_composite(clk); |
210 | free_div: |
211 | kfree(objp: div); |
212 | free_gate: |
213 | kfree(objp: gate); |
214 | free_mux: |
215 | kfree(objp: mux); |
216 | unmap: |
217 | iounmap(addr: reg); |
218 | of_address_to_resource(dev: node, index: 0, r: &res); |
219 | release_mem_region(res.start, resource_size(&res)); |
220 | } |
221 | |
222 | static const struct sun4i_a10_display_clk_data sun4i_a10_tcon_ch0_data __initconst = { |
223 | .num_rst = 2, |
224 | .parents = 4, |
225 | .offset_en = 31, |
226 | .offset_rst = 29, |
227 | .offset_mux = 24, |
228 | .width_mux = 2, |
229 | .flags = CLK_SET_RATE_PARENT, |
230 | }; |
231 | |
232 | static void __init sun4i_a10_tcon_ch0_setup(struct device_node *node) |
233 | { |
234 | sun4i_a10_display_init(node, data: &sun4i_a10_tcon_ch0_data); |
235 | } |
236 | CLK_OF_DECLARE(sun4i_a10_tcon_ch0, "allwinner,sun4i-a10-tcon-ch0-clk" , |
237 | sun4i_a10_tcon_ch0_setup); |
238 | |
239 | static const struct sun4i_a10_display_clk_data sun4i_a10_display_data __initconst = { |
240 | .has_div = true, |
241 | .num_rst = 1, |
242 | .parents = 3, |
243 | .offset_en = 31, |
244 | .offset_rst = 30, |
245 | .offset_mux = 24, |
246 | .offset_div = 0, |
247 | .width_mux = 2, |
248 | .width_div = 4, |
249 | }; |
250 | |
251 | static void __init sun4i_a10_display_setup(struct device_node *node) |
252 | { |
253 | sun4i_a10_display_init(node, data: &sun4i_a10_display_data); |
254 | } |
255 | CLK_OF_DECLARE(sun4i_a10_display, "allwinner,sun4i-a10-display-clk" , |
256 | sun4i_a10_display_setup); |
257 | |