Blame view

drivers/clocksource/ingenic-timer.c 10.4 KB
34e936830   Paul Cercueil   clocksource: Add ...
1
2
  // SPDX-License-Identifier: GPL-2.0
  /*
f19d838d0   周琰杰 (Zhou Yanjie)   clocksource/drive...
3
   * Ingenic SoCs TCU IRQ driver
34e936830   Paul Cercueil   clocksource: Add ...
4
   * Copyright (C) 2019 Paul Cercueil <paul@crapouillou.net>
f19d838d0   周琰杰 (Zhou Yanjie)   clocksource/drive...
5
   * Copyright (C) 2020 周琰杰 (Zhou Yanjie) <zhouyanjie@wanyeetech.com>
34e936830   Paul Cercueil   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   周琰杰 (Zhou Yanjie)   clocksource/drive...
19
  #include <linux/overflow.h>
34e936830   Paul Cercueil   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   周琰杰 (Zhou Yanjie)   clocksource/drive...
25
  static DEFINE_PER_CPU(call_single_data_t, ingenic_cevt_csd);
34e936830   Paul Cercueil   clocksource: Add ...
26
27
28
  struct ingenic_soc_info {
  	unsigned int num_channels;
  };
f19d838d0   周琰杰 (Zhou Yanjie)   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   Paul Cercueil   clocksource: Add ...
36
37
  struct ingenic_tcu {
  	struct regmap *map;
f19d838d0   周琰杰 (Zhou Yanjie)   clocksource/drive...
38
39
40
  	struct device_node *np;
  	struct clk *cs_clk;
  	unsigned int cs_channel;
34e936830   Paul Cercueil   clocksource: Add ...
41
  	struct clocksource cs;
34e936830   Paul Cercueil   clocksource: Add ...
42
  	unsigned long pwm_channels_mask;
f19d838d0   周琰杰 (Zhou Yanjie)   clocksource/drive...
43
  	struct ingenic_tcu_timer timers[];
34e936830   Paul Cercueil   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   周琰杰 (Zhou Yanjie)   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   Paul Cercueil   clocksource: Add ...
70
  {
f19d838d0   周琰杰 (Zhou Yanjie)   clocksource/drive...
71
  	return container_of(evt, struct ingenic_tcu_timer, cevt);
34e936830   Paul Cercueil   clocksource: Add ...
72
73
74
75
  }
  
  static int ingenic_tcu_cevt_set_state_shutdown(struct clock_event_device *evt)
  {
f19d838d0   周琰杰 (Zhou Yanjie)   clocksource/drive...
76
77
  	struct ingenic_tcu_timer *timer = to_ingenic_tcu_timer(evt);
  	struct ingenic_tcu *tcu = to_ingenic_tcu(timer);
34e936830   Paul Cercueil   clocksource: Add ...
78

f19d838d0   周琰杰 (Zhou Yanjie)   clocksource/drive...
79
  	regmap_write(tcu->map, TCU_REG_TECR, BIT(timer->channel));
34e936830   Paul Cercueil   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   周琰杰 (Zhou Yanjie)   clocksource/drive...
87
88
  	struct ingenic_tcu_timer *timer = to_ingenic_tcu_timer(evt);
  	struct ingenic_tcu *tcu = to_ingenic_tcu(timer);
34e936830   Paul Cercueil   clocksource: Add ...
89
90
91
  
  	if (next > 0xffff)
  		return -EINVAL;
f19d838d0   周琰杰 (Zhou Yanjie)   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   Paul Cercueil   clocksource: Add ...
95
96
97
  
  	return 0;
  }
f19d838d0   周琰杰 (Zhou Yanjie)   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   Paul Cercueil   clocksource: Add ...
104
105
  static irqreturn_t ingenic_tcu_cevt_cb(int irq, void *dev_id)
  {
f19d838d0   周琰杰 (Zhou Yanjie)   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   Paul Cercueil   clocksource: Add ...
109

f19d838d0   周琰杰 (Zhou Yanjie)   clocksource/drive...
110
  	regmap_write(tcu->map, TCU_REG_TECR, BIT(timer->channel));
34e936830   Paul Cercueil   clocksource: Add ...
111

f19d838d0   周琰杰 (Zhou Yanjie)   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   Paul Cercueil   clocksource: Add ...
118
119
120
  
  	return IRQ_HANDLED;
  }
df4411e4b   Daniel Lezcano   clocksource/drive...
121
  static struct clk *ingenic_tcu_get_clock(struct device_node *np, int id)
34e936830   Paul Cercueil   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   周琰杰 (Zhou Yanjie)   clocksource/drive...
131
  static int ingenic_tcu_setup_cevt(unsigned int cpu)
34e936830   Paul Cercueil   clocksource: Add ...
132
  {
f19d838d0   周琰杰 (Zhou Yanjie)   clocksource/drive...
133
134
135
  	struct ingenic_tcu *tcu = ingenic_tcu;
  	struct ingenic_tcu_timer *timer = &tcu->timers[cpu];
  	unsigned int timer_virq;
34e936830   Paul Cercueil   clocksource: Add ...
136
137
138
  	struct irq_domain *domain;
  	unsigned long rate;
  	int err;
f19d838d0   周琰杰 (Zhou Yanjie)   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   Paul Cercueil   clocksource: Add ...
142

f19d838d0   周琰杰 (Zhou Yanjie)   clocksource/drive...
143
  	err = clk_prepare_enable(timer->clk);
34e936830   Paul Cercueil   clocksource: Add ...
144
145
  	if (err)
  		goto err_clk_put;
f19d838d0   周琰杰 (Zhou Yanjie)   clocksource/drive...
146
  	rate = clk_get_rate(timer->clk);
34e936830   Paul Cercueil   clocksource: Add ...
147
148
149
150
  	if (!rate) {
  		err = -EINVAL;
  		goto err_clk_disable;
  	}
f19d838d0   周琰杰 (Zhou Yanjie)   clocksource/drive...
151
  	domain = irq_find_host(tcu->np);
34e936830   Paul Cercueil   clocksource: Add ...
152
153
154
155
  	if (!domain) {
  		err = -ENODEV;
  		goto err_clk_disable;
  	}
f19d838d0   周琰杰 (Zhou Yanjie)   clocksource/drive...
156
  	timer_virq = irq_create_mapping(domain, timer->channel);
34e936830   Paul Cercueil   clocksource: Add ...
157
158
159
160
  	if (!timer_virq) {
  		err = -EINVAL;
  		goto err_clk_disable;
  	}
f19d838d0   周琰杰 (Zhou Yanjie)   clocksource/drive...
161
  	snprintf(timer->name, sizeof(timer->name), "TCU%u", timer->channel);
34e936830   Paul Cercueil   clocksource: Add ...
162
163
  
  	err = request_irq(timer_virq, ingenic_tcu_cevt_cb, IRQF_TIMER,
f19d838d0   周琰杰 (Zhou Yanjie)   clocksource/drive...
164
  			  timer->name, timer);
34e936830   Paul Cercueil   clocksource: Add ...
165
166
  	if (err)
  		goto err_irq_dispose_mapping;
f19d838d0   周琰杰 (Zhou Yanjie)   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   Paul Cercueil   clocksource: Add ...
174

f19d838d0   周琰杰 (Zhou Yanjie)   clocksource/drive...
175
  	clockevents_config_and_register(&timer->cevt, rate, 10, 0xffff);
34e936830   Paul Cercueil   clocksource: Add ...
176
177
178
179
180
181
  
  	return 0;
  
  err_irq_dispose_mapping:
  	irq_dispose_mapping(timer_virq);
  err_clk_disable:
f19d838d0   周琰杰 (Zhou Yanjie)   clocksource/drive...
182
  	clk_disable_unprepare(timer->clk);
34e936830   Paul Cercueil   clocksource: Add ...
183
  err_clk_put:
f19d838d0   周琰杰 (Zhou Yanjie)   clocksource/drive...
184
  	clk_put(timer->clk);
34e936830   Paul Cercueil   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   周琰杰 (Zhou Yanjie)   clocksource/drive...
252
  	{ .compatible = "ingenic,x1000-tcu", .data = &jz4740_soc_info, },
34e936830   Paul Cercueil   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   周琰杰 (Zhou Yanjie)   clocksource/drive...
260
  	struct ingenic_tcu_timer *timer;
34e936830   Paul Cercueil   clocksource: Add ...
261
262
  	struct ingenic_tcu *tcu;
  	struct regmap *map;
f19d838d0   周琰杰 (Zhou Yanjie)   clocksource/drive...
263
264
  	unsigned int cpu;
  	int ret, last_bit = -1;
34e936830   Paul Cercueil   clocksource: Add ...
265
  	long rate;
34e936830   Paul Cercueil   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   周琰杰 (Zhou Yanjie)   clocksource/drive...
272
273
  	tcu = kzalloc(struct_size(tcu, timers, num_possible_cpus()),
  		      GFP_KERNEL);
34e936830   Paul Cercueil   clocksource: Add ...
274
275
  	if (!tcu)
  		return -ENOMEM;
f19d838d0   周琰杰 (Zhou Yanjie)   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   Paul Cercueil   clocksource: Add ...
282
283
  	of_property_read_u32(np, "ingenic,pwm-channels-mask",
  			     (u32 *)&tcu->pwm_channels_mask);
f19d838d0   周琰杰 (Zhou Yanjie)   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   Paul Cercueil   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   周琰杰 (Zhou Yanjie)   clocksource/drive...
295
  	tcu->np = np;
34e936830   Paul Cercueil   clocksource: Add ...
296
  	ingenic_tcu = tcu;
f19d838d0   周琰杰 (Zhou Yanjie)   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   Paul Cercueil   clocksource: Add ...
306
307
  	tcu->cs_channel = find_next_zero_bit(&tcu->pwm_channels_mask,
  					     soc_info->num_channels,
f19d838d0   周琰杰 (Zhou Yanjie)   clocksource/drive...
308
  					     last_bit + 1);
34e936830   Paul Cercueil   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   周琰杰 (Zhou Yanjie)   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   Paul Cercueil   clocksource: Add ...
322
  		goto err_tcu_clocksource_cleanup;
f19d838d0   周琰杰 (Zhou Yanjie)   clocksource/drive...
323
  	}
34e936830   Paul Cercueil   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   周琰杰 (Zhou Yanjie)   clocksource/drive...
343
  TIMER_OF_DECLARE(x1000_tcu_intc,  "ingenic,x1000-tcu",  ingenic_tcu_init);
34e936830   Paul Cercueil   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   周琰杰 (Zhou Yanjie)   clocksource/drive...
355
  	unsigned int cpu;
34e936830   Paul Cercueil   clocksource: Add ...
356
357
  
  	clk_disable(tcu->cs_clk);
f19d838d0   周琰杰 (Zhou Yanjie)   clocksource/drive...
358
359
360
  
  	for (cpu = 0; cpu < num_online_cpus(); cpu++)
  		clk_disable(tcu->timers[cpu].clk);
34e936830   Paul Cercueil   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   周琰杰 (Zhou Yanjie)   clocksource/drive...
367
  	unsigned int cpu;
34e936830   Paul Cercueil   clocksource: Add ...
368
  	int ret;
f19d838d0   周琰杰 (Zhou Yanjie)   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   Paul Cercueil   clocksource: Add ...
374
375
  
  	ret = clk_enable(tcu->cs_clk);
f19d838d0   周琰杰 (Zhou Yanjie)   clocksource/drive...
376
377
  	if (ret)
  		goto err_timer_clk_disable;
34e936830   Paul Cercueil   clocksource: Add ...
378
379
  
  	return 0;
f19d838d0   周琰杰 (Zhou Yanjie)   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   Paul Cercueil   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);