Commit | Line | Data |
---|---|---|
34e93683 PC |
1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* | |
3 | * JZ47xx SoCs TCU IRQ driver | |
4 | * Copyright (C) 2019 Paul Cercueil <paul@crapouillou.net> | |
5 | */ | |
6 | ||
7 | #include <linux/bitops.h> | |
8 | #include <linux/clk.h> | |
9 | #include <linux/clockchips.h> | |
10 | #include <linux/clocksource.h> | |
11 | #include <linux/interrupt.h> | |
12 | #include <linux/mfd/ingenic-tcu.h> | |
13 | #include <linux/mfd/syscon.h> | |
14 | #include <linux/of.h> | |
15 | #include <linux/of_address.h> | |
16 | #include <linux/of_irq.h> | |
17 | #include <linux/of_platform.h> | |
18 | #include <linux/platform_device.h> | |
19 | #include <linux/regmap.h> | |
20 | #include <linux/sched_clock.h> | |
21 | ||
22 | #include <dt-bindings/clock/ingenic,tcu.h> | |
23 | ||
24 | struct ingenic_soc_info { | |
25 | unsigned int num_channels; | |
26 | }; | |
27 | ||
28 | struct ingenic_tcu { | |
29 | struct regmap *map; | |
30 | struct clk *timer_clk, *cs_clk; | |
31 | unsigned int timer_channel, cs_channel; | |
32 | struct clock_event_device cevt; | |
33 | struct clocksource cs; | |
34 | char name[4]; | |
35 | unsigned long pwm_channels_mask; | |
36 | }; | |
37 | ||
38 | static struct ingenic_tcu *ingenic_tcu; | |
39 | ||
40 | static u64 notrace ingenic_tcu_timer_read(void) | |
41 | { | |
42 | struct ingenic_tcu *tcu = ingenic_tcu; | |
43 | unsigned int count; | |
44 | ||
45 | regmap_read(tcu->map, TCU_REG_TCNTc(tcu->cs_channel), &count); | |
46 | ||
47 | return count; | |
48 | } | |
49 | ||
50 | static u64 notrace ingenic_tcu_timer_cs_read(struct clocksource *cs) | |
51 | { | |
52 | return ingenic_tcu_timer_read(); | |
53 | } | |
54 | ||
55 | static inline struct ingenic_tcu *to_ingenic_tcu(struct clock_event_device *evt) | |
56 | { | |
57 | return container_of(evt, struct ingenic_tcu, cevt); | |
58 | } | |
59 | ||
60 | static int ingenic_tcu_cevt_set_state_shutdown(struct clock_event_device *evt) | |
61 | { | |
62 | struct ingenic_tcu *tcu = to_ingenic_tcu(evt); | |
63 | ||
64 | regmap_write(tcu->map, TCU_REG_TECR, BIT(tcu->timer_channel)); | |
65 | ||
66 | return 0; | |
67 | } | |
68 | ||
69 | static int ingenic_tcu_cevt_set_next(unsigned long next, | |
70 | struct clock_event_device *evt) | |
71 | { | |
72 | struct ingenic_tcu *tcu = to_ingenic_tcu(evt); | |
73 | ||
74 | if (next > 0xffff) | |
75 | return -EINVAL; | |
76 | ||
77 | regmap_write(tcu->map, TCU_REG_TDFRc(tcu->timer_channel), next); | |
78 | regmap_write(tcu->map, TCU_REG_TCNTc(tcu->timer_channel), 0); | |
79 | regmap_write(tcu->map, TCU_REG_TESR, BIT(tcu->timer_channel)); | |
80 | ||
81 | return 0; | |
82 | } | |
83 | ||
84 | static irqreturn_t ingenic_tcu_cevt_cb(int irq, void *dev_id) | |
85 | { | |
86 | struct clock_event_device *evt = dev_id; | |
87 | struct ingenic_tcu *tcu = to_ingenic_tcu(evt); | |
88 | ||
89 | regmap_write(tcu->map, TCU_REG_TECR, BIT(tcu->timer_channel)); | |
90 | ||
91 | if (evt->event_handler) | |
92 | evt->event_handler(evt); | |
93 | ||
94 | return IRQ_HANDLED; | |
95 | } | |
96 | ||
97 | static struct clk * __init ingenic_tcu_get_clock(struct device_node *np, int id) | |
98 | { | |
99 | struct of_phandle_args args; | |
100 | ||
101 | args.np = np; | |
102 | args.args_count = 1; | |
103 | args.args[0] = id; | |
104 | ||
105 | return of_clk_get_from_provider(&args); | |
106 | } | |
107 | ||
108 | static int __init ingenic_tcu_timer_init(struct device_node *np, | |
109 | struct ingenic_tcu *tcu) | |
110 | { | |
111 | unsigned int timer_virq, channel = tcu->timer_channel; | |
112 | struct irq_domain *domain; | |
113 | unsigned long rate; | |
114 | int err; | |
115 | ||
116 | tcu->timer_clk = ingenic_tcu_get_clock(np, channel); | |
117 | if (IS_ERR(tcu->timer_clk)) | |
118 | return PTR_ERR(tcu->timer_clk); | |
119 | ||
120 | err = clk_prepare_enable(tcu->timer_clk); | |
121 | if (err) | |
122 | goto err_clk_put; | |
123 | ||
124 | rate = clk_get_rate(tcu->timer_clk); | |
125 | if (!rate) { | |
126 | err = -EINVAL; | |
127 | goto err_clk_disable; | |
128 | } | |
129 | ||
130 | domain = irq_find_host(np); | |
131 | if (!domain) { | |
132 | err = -ENODEV; | |
133 | goto err_clk_disable; | |
134 | } | |
135 | ||
136 | timer_virq = irq_create_mapping(domain, channel); | |
137 | if (!timer_virq) { | |
138 | err = -EINVAL; | |
139 | goto err_clk_disable; | |
140 | } | |
141 | ||
142 | snprintf(tcu->name, sizeof(tcu->name), "TCU"); | |
143 | ||
144 | err = request_irq(timer_virq, ingenic_tcu_cevt_cb, IRQF_TIMER, | |
145 | tcu->name, &tcu->cevt); | |
146 | if (err) | |
147 | goto err_irq_dispose_mapping; | |
148 | ||
149 | tcu->cevt.cpumask = cpumask_of(smp_processor_id()); | |
150 | tcu->cevt.features = CLOCK_EVT_FEAT_ONESHOT; | |
151 | tcu->cevt.name = tcu->name; | |
152 | tcu->cevt.rating = 200; | |
153 | tcu->cevt.set_state_shutdown = ingenic_tcu_cevt_set_state_shutdown; | |
154 | tcu->cevt.set_next_event = ingenic_tcu_cevt_set_next; | |
155 | ||
156 | clockevents_config_and_register(&tcu->cevt, rate, 10, 0xffff); | |
157 | ||
158 | return 0; | |
159 | ||
160 | err_irq_dispose_mapping: | |
161 | irq_dispose_mapping(timer_virq); | |
162 | err_clk_disable: | |
163 | clk_disable_unprepare(tcu->timer_clk); | |
164 | err_clk_put: | |
165 | clk_put(tcu->timer_clk); | |
166 | return err; | |
167 | } | |
168 | ||
169 | static int __init ingenic_tcu_clocksource_init(struct device_node *np, | |
170 | struct ingenic_tcu *tcu) | |
171 | { | |
172 | unsigned int channel = tcu->cs_channel; | |
173 | struct clocksource *cs = &tcu->cs; | |
174 | unsigned long rate; | |
175 | int err; | |
176 | ||
177 | tcu->cs_clk = ingenic_tcu_get_clock(np, channel); | |
178 | if (IS_ERR(tcu->cs_clk)) | |
179 | return PTR_ERR(tcu->cs_clk); | |
180 | ||
181 | err = clk_prepare_enable(tcu->cs_clk); | |
182 | if (err) | |
183 | goto err_clk_put; | |
184 | ||
185 | rate = clk_get_rate(tcu->cs_clk); | |
186 | if (!rate) { | |
187 | err = -EINVAL; | |
188 | goto err_clk_disable; | |
189 | } | |
190 | ||
191 | /* Reset channel */ | |
192 | regmap_update_bits(tcu->map, TCU_REG_TCSRc(channel), | |
193 | 0xffff & ~TCU_TCSR_RESERVED_BITS, 0); | |
194 | ||
195 | /* Reset counter */ | |
196 | regmap_write(tcu->map, TCU_REG_TDFRc(channel), 0xffff); | |
197 | regmap_write(tcu->map, TCU_REG_TCNTc(channel), 0); | |
198 | ||
199 | /* Enable channel */ | |
200 | regmap_write(tcu->map, TCU_REG_TESR, BIT(channel)); | |
201 | ||
202 | cs->name = "ingenic-timer"; | |
203 | cs->rating = 200; | |
204 | cs->flags = CLOCK_SOURCE_IS_CONTINUOUS; | |
205 | cs->mask = CLOCKSOURCE_MASK(16); | |
206 | cs->read = ingenic_tcu_timer_cs_read; | |
207 | ||
208 | err = clocksource_register_hz(cs, rate); | |
209 | if (err) | |
210 | goto err_clk_disable; | |
211 | ||
212 | return 0; | |
213 | ||
214 | err_clk_disable: | |
215 | clk_disable_unprepare(tcu->cs_clk); | |
216 | err_clk_put: | |
217 | clk_put(tcu->cs_clk); | |
218 | return err; | |
219 | } | |
220 | ||
221 | static const struct ingenic_soc_info jz4740_soc_info = { | |
222 | .num_channels = 8, | |
223 | }; | |
224 | ||
225 | static const struct ingenic_soc_info jz4725b_soc_info = { | |
226 | .num_channels = 6, | |
227 | }; | |
228 | ||
229 | static const struct of_device_id ingenic_tcu_of_match[] = { | |
230 | { .compatible = "ingenic,jz4740-tcu", .data = &jz4740_soc_info, }, | |
231 | { .compatible = "ingenic,jz4725b-tcu", .data = &jz4725b_soc_info, }, | |
232 | { .compatible = "ingenic,jz4770-tcu", .data = &jz4740_soc_info, }, | |
a7cd3955 | 233 | { .compatible = "ingenic,x1000-tcu", .data = &jz4740_soc_info, }, |
34e93683 PC |
234 | { /* sentinel */ } |
235 | }; | |
236 | ||
237 | static int __init ingenic_tcu_init(struct device_node *np) | |
238 | { | |
239 | const struct of_device_id *id = of_match_node(ingenic_tcu_of_match, np); | |
240 | const struct ingenic_soc_info *soc_info = id->data; | |
241 | struct ingenic_tcu *tcu; | |
242 | struct regmap *map; | |
243 | long rate; | |
244 | int ret; | |
245 | ||
246 | of_node_clear_flag(np, OF_POPULATED); | |
247 | ||
248 | map = device_node_to_regmap(np); | |
249 | if (IS_ERR(map)) | |
250 | return PTR_ERR(map); | |
251 | ||
252 | tcu = kzalloc(sizeof(*tcu), GFP_KERNEL); | |
253 | if (!tcu) | |
254 | return -ENOMEM; | |
255 | ||
256 | /* Enable all TCU channels for PWM use by default except channels 0/1 */ | |
257 | tcu->pwm_channels_mask = GENMASK(soc_info->num_channels - 1, 2); | |
258 | of_property_read_u32(np, "ingenic,pwm-channels-mask", | |
259 | (u32 *)&tcu->pwm_channels_mask); | |
260 | ||
261 | /* Verify that we have at least two free channels */ | |
262 | if (hweight8(tcu->pwm_channels_mask) > soc_info->num_channels - 2) { | |
263 | pr_crit("%s: Invalid PWM channel mask: 0x%02lx\n", __func__, | |
264 | tcu->pwm_channels_mask); | |
265 | ret = -EINVAL; | |
266 | goto err_free_ingenic_tcu; | |
267 | } | |
268 | ||
269 | tcu->map = map; | |
270 | ingenic_tcu = tcu; | |
271 | ||
272 | tcu->timer_channel = find_first_zero_bit(&tcu->pwm_channels_mask, | |
273 | soc_info->num_channels); | |
274 | tcu->cs_channel = find_next_zero_bit(&tcu->pwm_channels_mask, | |
275 | soc_info->num_channels, | |
276 | tcu->timer_channel + 1); | |
277 | ||
278 | ret = ingenic_tcu_clocksource_init(np, tcu); | |
279 | if (ret) { | |
280 | pr_crit("%s: Unable to init clocksource: %d\n", __func__, ret); | |
281 | goto err_free_ingenic_tcu; | |
282 | } | |
283 | ||
284 | ret = ingenic_tcu_timer_init(np, tcu); | |
285 | if (ret) | |
286 | goto err_tcu_clocksource_cleanup; | |
287 | ||
288 | /* Register the sched_clock at the end as there's no way to undo it */ | |
289 | rate = clk_get_rate(tcu->cs_clk); | |
290 | sched_clock_register(ingenic_tcu_timer_read, 16, rate); | |
291 | ||
292 | return 0; | |
293 | ||
294 | err_tcu_clocksource_cleanup: | |
295 | clocksource_unregister(&tcu->cs); | |
296 | clk_disable_unprepare(tcu->cs_clk); | |
297 | clk_put(tcu->cs_clk); | |
298 | err_free_ingenic_tcu: | |
299 | kfree(tcu); | |
300 | return ret; | |
301 | } | |
302 | ||
303 | TIMER_OF_DECLARE(jz4740_tcu_intc, "ingenic,jz4740-tcu", ingenic_tcu_init); | |
304 | TIMER_OF_DECLARE(jz4725b_tcu_intc, "ingenic,jz4725b-tcu", ingenic_tcu_init); | |
305 | TIMER_OF_DECLARE(jz4770_tcu_intc, "ingenic,jz4770-tcu", ingenic_tcu_init); | |
a7cd3955 | 306 | TIMER_OF_DECLARE(x1000_tcu_intc, "ingenic,x1000-tcu", ingenic_tcu_init); |
34e93683 PC |
307 | |
308 | static int __init ingenic_tcu_probe(struct platform_device *pdev) | |
309 | { | |
310 | platform_set_drvdata(pdev, ingenic_tcu); | |
311 | ||
312 | return 0; | |
313 | } | |
314 | ||
315 | static int __maybe_unused ingenic_tcu_suspend(struct device *dev) | |
316 | { | |
317 | struct ingenic_tcu *tcu = dev_get_drvdata(dev); | |
318 | ||
319 | clk_disable(tcu->cs_clk); | |
320 | clk_disable(tcu->timer_clk); | |
321 | return 0; | |
322 | } | |
323 | ||
324 | static int __maybe_unused ingenic_tcu_resume(struct device *dev) | |
325 | { | |
326 | struct ingenic_tcu *tcu = dev_get_drvdata(dev); | |
327 | int ret; | |
328 | ||
329 | ret = clk_enable(tcu->timer_clk); | |
330 | if (ret) | |
331 | return ret; | |
332 | ||
333 | ret = clk_enable(tcu->cs_clk); | |
334 | if (ret) { | |
335 | clk_disable(tcu->timer_clk); | |
336 | return ret; | |
337 | } | |
338 | ||
339 | return 0; | |
340 | } | |
341 | ||
342 | static const struct dev_pm_ops __maybe_unused ingenic_tcu_pm_ops = { | |
343 | /* _noirq: We want the TCU clocks to be gated last / ungated first */ | |
344 | .suspend_noirq = ingenic_tcu_suspend, | |
345 | .resume_noirq = ingenic_tcu_resume, | |
346 | }; | |
347 | ||
348 | static struct platform_driver ingenic_tcu_driver = { | |
349 | .driver = { | |
350 | .name = "ingenic-tcu-timer", | |
351 | #ifdef CONFIG_PM_SLEEP | |
352 | .pm = &ingenic_tcu_pm_ops, | |
353 | #endif | |
354 | .of_match_table = ingenic_tcu_of_match, | |
355 | }, | |
356 | }; | |
357 | builtin_platform_driver_probe(ingenic_tcu_driver, ingenic_tcu_probe); |