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