134e93683SPaul Cercueil // SPDX-License-Identifier: GPL-2.0
234e93683SPaul Cercueil /*
3f19d838dS周琰杰 (Zhou Yanjie) * Ingenic SoCs TCU IRQ driver
434e93683SPaul Cercueil * Copyright (C) 2019 Paul Cercueil <paul@crapouillou.net>
5f19d838dS周琰杰 (Zhou Yanjie) * Copyright (C) 2020 周琰杰 (Zhou Yanjie) <zhouyanjie@wanyeetech.com>
634e93683SPaul Cercueil */
734e93683SPaul Cercueil
834e93683SPaul Cercueil #include <linux/bitops.h>
934e93683SPaul Cercueil #include <linux/clk.h>
1034e93683SPaul Cercueil #include <linux/clockchips.h>
1134e93683SPaul Cercueil #include <linux/clocksource.h>
12*bd08b691SRob Herring #include <linux/cpuhotplug.h>
1334e93683SPaul Cercueil #include <linux/interrupt.h>
1434e93683SPaul Cercueil #include <linux/mfd/ingenic-tcu.h>
1534e93683SPaul Cercueil #include <linux/mfd/syscon.h>
1634e93683SPaul Cercueil #include <linux/of.h>
1734e93683SPaul Cercueil #include <linux/of_irq.h>
18f19d838dS周琰杰 (Zhou Yanjie) #include <linux/overflow.h>
1934e93683SPaul Cercueil #include <linux/platform_device.h>
2034e93683SPaul Cercueil #include <linux/regmap.h>
2134e93683SPaul Cercueil #include <linux/sched_clock.h>
2234e93683SPaul Cercueil
2334e93683SPaul Cercueil #include <dt-bindings/clock/ingenic,tcu.h>
2434e93683SPaul Cercueil
25f19d838dS周琰杰 (Zhou Yanjie) static DEFINE_PER_CPU(call_single_data_t, ingenic_cevt_csd);
26f19d838dS周琰杰 (Zhou Yanjie)
2734e93683SPaul Cercueil struct ingenic_soc_info {
2834e93683SPaul Cercueil unsigned int num_channels;
2934e93683SPaul Cercueil };
3034e93683SPaul Cercueil
31f19d838dS周琰杰 (Zhou Yanjie) struct ingenic_tcu_timer {
32f19d838dS周琰杰 (Zhou Yanjie) unsigned int cpu;
33f19d838dS周琰杰 (Zhou Yanjie) unsigned int channel;
34f19d838dS周琰杰 (Zhou Yanjie) struct clock_event_device cevt;
35f19d838dS周琰杰 (Zhou Yanjie) struct clk *clk;
36f19d838dS周琰杰 (Zhou Yanjie) char name[8];
37f19d838dS周琰杰 (Zhou Yanjie) };
38f19d838dS周琰杰 (Zhou Yanjie)
3934e93683SPaul Cercueil struct ingenic_tcu {
4034e93683SPaul Cercueil struct regmap *map;
41f19d838dS周琰杰 (Zhou Yanjie) struct device_node *np;
42f19d838dS周琰杰 (Zhou Yanjie) struct clk *cs_clk;
43f19d838dS周琰杰 (Zhou Yanjie) unsigned int cs_channel;
4434e93683SPaul Cercueil struct clocksource cs;
4534e93683SPaul Cercueil unsigned long pwm_channels_mask;
46f19d838dS周琰杰 (Zhou Yanjie) struct ingenic_tcu_timer timers[];
4734e93683SPaul Cercueil };
4834e93683SPaul Cercueil
4934e93683SPaul Cercueil static struct ingenic_tcu *ingenic_tcu;
5034e93683SPaul Cercueil
ingenic_tcu_timer_read(void)5134e93683SPaul Cercueil static u64 notrace ingenic_tcu_timer_read(void)
5234e93683SPaul Cercueil {
5334e93683SPaul Cercueil struct ingenic_tcu *tcu = ingenic_tcu;
5434e93683SPaul Cercueil unsigned int count;
5534e93683SPaul Cercueil
5634e93683SPaul Cercueil regmap_read(tcu->map, TCU_REG_TCNTc(tcu->cs_channel), &count);
5734e93683SPaul Cercueil
5834e93683SPaul Cercueil return count;
5934e93683SPaul Cercueil }
6034e93683SPaul Cercueil
ingenic_tcu_timer_cs_read(struct clocksource * cs)6134e93683SPaul Cercueil static u64 notrace ingenic_tcu_timer_cs_read(struct clocksource *cs)
6234e93683SPaul Cercueil {
6334e93683SPaul Cercueil return ingenic_tcu_timer_read();
6434e93683SPaul Cercueil }
6534e93683SPaul Cercueil
66f19d838dS周琰杰 (Zhou Yanjie) static inline struct ingenic_tcu *
to_ingenic_tcu(struct ingenic_tcu_timer * timer)67f19d838dS周琰杰 (Zhou Yanjie) to_ingenic_tcu(struct ingenic_tcu_timer *timer)
6834e93683SPaul Cercueil {
69f19d838dS周琰杰 (Zhou Yanjie) return container_of(timer, struct ingenic_tcu, timers[timer->cpu]);
70f19d838dS周琰杰 (Zhou Yanjie) }
71f19d838dS周琰杰 (Zhou Yanjie)
72f19d838dS周琰杰 (Zhou Yanjie) static inline struct ingenic_tcu_timer *
to_ingenic_tcu_timer(struct clock_event_device * evt)73f19d838dS周琰杰 (Zhou Yanjie) to_ingenic_tcu_timer(struct clock_event_device *evt)
74f19d838dS周琰杰 (Zhou Yanjie) {
75f19d838dS周琰杰 (Zhou Yanjie) return container_of(evt, struct ingenic_tcu_timer, cevt);
7634e93683SPaul Cercueil }
7734e93683SPaul Cercueil
ingenic_tcu_cevt_set_state_shutdown(struct clock_event_device * evt)7834e93683SPaul Cercueil static int ingenic_tcu_cevt_set_state_shutdown(struct clock_event_device *evt)
7934e93683SPaul Cercueil {
80f19d838dS周琰杰 (Zhou Yanjie) struct ingenic_tcu_timer *timer = to_ingenic_tcu_timer(evt);
81f19d838dS周琰杰 (Zhou Yanjie) struct ingenic_tcu *tcu = to_ingenic_tcu(timer);
8234e93683SPaul Cercueil
83f19d838dS周琰杰 (Zhou Yanjie) regmap_write(tcu->map, TCU_REG_TECR, BIT(timer->channel));
8434e93683SPaul Cercueil
8534e93683SPaul Cercueil return 0;
8634e93683SPaul Cercueil }
8734e93683SPaul Cercueil
ingenic_tcu_cevt_set_next(unsigned long next,struct clock_event_device * evt)8834e93683SPaul Cercueil static int ingenic_tcu_cevt_set_next(unsigned long next,
8934e93683SPaul Cercueil struct clock_event_device *evt)
9034e93683SPaul Cercueil {
91f19d838dS周琰杰 (Zhou Yanjie) struct ingenic_tcu_timer *timer = to_ingenic_tcu_timer(evt);
92f19d838dS周琰杰 (Zhou Yanjie) struct ingenic_tcu *tcu = to_ingenic_tcu(timer);
9334e93683SPaul Cercueil
9434e93683SPaul Cercueil if (next > 0xffff)
9534e93683SPaul Cercueil return -EINVAL;
9634e93683SPaul Cercueil
97f19d838dS周琰杰 (Zhou Yanjie) regmap_write(tcu->map, TCU_REG_TDFRc(timer->channel), next);
98f19d838dS周琰杰 (Zhou Yanjie) regmap_write(tcu->map, TCU_REG_TCNTc(timer->channel), 0);
99f19d838dS周琰杰 (Zhou Yanjie) regmap_write(tcu->map, TCU_REG_TESR, BIT(timer->channel));
10034e93683SPaul Cercueil
10134e93683SPaul Cercueil return 0;
10234e93683SPaul Cercueil }
10334e93683SPaul Cercueil
ingenic_per_cpu_event_handler(void * info)104f19d838dS周琰杰 (Zhou Yanjie) static void ingenic_per_cpu_event_handler(void *info)
105f19d838dS周琰杰 (Zhou Yanjie) {
106f19d838dS周琰杰 (Zhou Yanjie) struct clock_event_device *cevt = (struct clock_event_device *) info;
107f19d838dS周琰杰 (Zhou Yanjie)
108f19d838dS周琰杰 (Zhou Yanjie) cevt->event_handler(cevt);
109f19d838dS周琰杰 (Zhou Yanjie) }
110f19d838dS周琰杰 (Zhou Yanjie)
ingenic_tcu_cevt_cb(int irq,void * dev_id)11134e93683SPaul Cercueil static irqreturn_t ingenic_tcu_cevt_cb(int irq, void *dev_id)
11234e93683SPaul Cercueil {
113f19d838dS周琰杰 (Zhou Yanjie) struct ingenic_tcu_timer *timer = dev_id;
114f19d838dS周琰杰 (Zhou Yanjie) struct ingenic_tcu *tcu = to_ingenic_tcu(timer);
115f19d838dS周琰杰 (Zhou Yanjie) call_single_data_t *csd;
11634e93683SPaul Cercueil
117f19d838dS周琰杰 (Zhou Yanjie) regmap_write(tcu->map, TCU_REG_TECR, BIT(timer->channel));
11834e93683SPaul Cercueil
119f19d838dS周琰杰 (Zhou Yanjie) if (timer->cevt.event_handler) {
120f19d838dS周琰杰 (Zhou Yanjie) csd = &per_cpu(ingenic_cevt_csd, timer->cpu);
121f19d838dS周琰杰 (Zhou Yanjie) csd->info = (void *) &timer->cevt;
122f19d838dS周琰杰 (Zhou Yanjie) csd->func = ingenic_per_cpu_event_handler;
123f19d838dS周琰杰 (Zhou Yanjie) smp_call_function_single_async(timer->cpu, csd);
124f19d838dS周琰杰 (Zhou Yanjie) }
12534e93683SPaul Cercueil
12634e93683SPaul Cercueil return IRQ_HANDLED;
12734e93683SPaul Cercueil }
12834e93683SPaul Cercueil
ingenic_tcu_get_clock(struct device_node * np,int id)1295bd7cb29SDaniel Lezcano static struct clk *ingenic_tcu_get_clock(struct device_node *np, int id)
13034e93683SPaul Cercueil {
13134e93683SPaul Cercueil struct of_phandle_args args;
13234e93683SPaul Cercueil
13334e93683SPaul Cercueil args.np = np;
13434e93683SPaul Cercueil args.args_count = 1;
13534e93683SPaul Cercueil args.args[0] = id;
13634e93683SPaul Cercueil
13734e93683SPaul Cercueil return of_clk_get_from_provider(&args);
13834e93683SPaul Cercueil }
13934e93683SPaul Cercueil
ingenic_tcu_setup_cevt(unsigned int cpu)140f19d838dS周琰杰 (Zhou Yanjie) static int ingenic_tcu_setup_cevt(unsigned int cpu)
14134e93683SPaul Cercueil {
142f19d838dS周琰杰 (Zhou Yanjie) struct ingenic_tcu *tcu = ingenic_tcu;
143f19d838dS周琰杰 (Zhou Yanjie) struct ingenic_tcu_timer *timer = &tcu->timers[cpu];
144f19d838dS周琰杰 (Zhou Yanjie) unsigned int timer_virq;
14534e93683SPaul Cercueil struct irq_domain *domain;
14634e93683SPaul Cercueil unsigned long rate;
14734e93683SPaul Cercueil int err;
14834e93683SPaul Cercueil
149f19d838dS周琰杰 (Zhou Yanjie) timer->clk = ingenic_tcu_get_clock(tcu->np, timer->channel);
150f19d838dS周琰杰 (Zhou Yanjie) if (IS_ERR(timer->clk))
151f19d838dS周琰杰 (Zhou Yanjie) return PTR_ERR(timer->clk);
15234e93683SPaul Cercueil
153f19d838dS周琰杰 (Zhou Yanjie) err = clk_prepare_enable(timer->clk);
15434e93683SPaul Cercueil if (err)
15534e93683SPaul Cercueil goto err_clk_put;
15634e93683SPaul Cercueil
157f19d838dS周琰杰 (Zhou Yanjie) rate = clk_get_rate(timer->clk);
15834e93683SPaul Cercueil if (!rate) {
15934e93683SPaul Cercueil err = -EINVAL;
16034e93683SPaul Cercueil goto err_clk_disable;
16134e93683SPaul Cercueil }
16234e93683SPaul Cercueil
163f19d838dS周琰杰 (Zhou Yanjie) domain = irq_find_host(tcu->np);
16434e93683SPaul Cercueil if (!domain) {
16534e93683SPaul Cercueil err = -ENODEV;
16634e93683SPaul Cercueil goto err_clk_disable;
16734e93683SPaul Cercueil }
16834e93683SPaul Cercueil
169f19d838dS周琰杰 (Zhou Yanjie) timer_virq = irq_create_mapping(domain, timer->channel);
17034e93683SPaul Cercueil if (!timer_virq) {
17134e93683SPaul Cercueil err = -EINVAL;
17234e93683SPaul Cercueil goto err_clk_disable;
17334e93683SPaul Cercueil }
17434e93683SPaul Cercueil
175f19d838dS周琰杰 (Zhou Yanjie) snprintf(timer->name, sizeof(timer->name), "TCU%u", timer->channel);
17634e93683SPaul Cercueil
17734e93683SPaul Cercueil err = request_irq(timer_virq, ingenic_tcu_cevt_cb, IRQF_TIMER,
178f19d838dS周琰杰 (Zhou Yanjie) timer->name, timer);
17934e93683SPaul Cercueil if (err)
18034e93683SPaul Cercueil goto err_irq_dispose_mapping;
18134e93683SPaul Cercueil
182f19d838dS周琰杰 (Zhou Yanjie) timer->cpu = smp_processor_id();
183f19d838dS周琰杰 (Zhou Yanjie) timer->cevt.cpumask = cpumask_of(smp_processor_id());
184f19d838dS周琰杰 (Zhou Yanjie) timer->cevt.features = CLOCK_EVT_FEAT_ONESHOT;
185f19d838dS周琰杰 (Zhou Yanjie) timer->cevt.name = timer->name;
186f19d838dS周琰杰 (Zhou Yanjie) timer->cevt.rating = 200;
187f19d838dS周琰杰 (Zhou Yanjie) timer->cevt.set_state_shutdown = ingenic_tcu_cevt_set_state_shutdown;
188f19d838dS周琰杰 (Zhou Yanjie) timer->cevt.set_next_event = ingenic_tcu_cevt_set_next;
18934e93683SPaul Cercueil
190f19d838dS周琰杰 (Zhou Yanjie) clockevents_config_and_register(&timer->cevt, rate, 10, 0xffff);
19134e93683SPaul Cercueil
19234e93683SPaul Cercueil return 0;
19334e93683SPaul Cercueil
19434e93683SPaul Cercueil err_irq_dispose_mapping:
19534e93683SPaul Cercueil irq_dispose_mapping(timer_virq);
19634e93683SPaul Cercueil err_clk_disable:
197f19d838dS周琰杰 (Zhou Yanjie) clk_disable_unprepare(timer->clk);
19834e93683SPaul Cercueil err_clk_put:
199f19d838dS周琰杰 (Zhou Yanjie) clk_put(timer->clk);
20034e93683SPaul Cercueil return err;
20134e93683SPaul Cercueil }
20234e93683SPaul Cercueil
ingenic_tcu_clocksource_init(struct device_node * np,struct ingenic_tcu * tcu)20334e93683SPaul Cercueil static int __init ingenic_tcu_clocksource_init(struct device_node *np,
20434e93683SPaul Cercueil struct ingenic_tcu *tcu)
20534e93683SPaul Cercueil {
20634e93683SPaul Cercueil unsigned int channel = tcu->cs_channel;
20734e93683SPaul Cercueil struct clocksource *cs = &tcu->cs;
20834e93683SPaul Cercueil unsigned long rate;
20934e93683SPaul Cercueil int err;
21034e93683SPaul Cercueil
21134e93683SPaul Cercueil tcu->cs_clk = ingenic_tcu_get_clock(np, channel);
21234e93683SPaul Cercueil if (IS_ERR(tcu->cs_clk))
21334e93683SPaul Cercueil return PTR_ERR(tcu->cs_clk);
21434e93683SPaul Cercueil
21534e93683SPaul Cercueil err = clk_prepare_enable(tcu->cs_clk);
21634e93683SPaul Cercueil if (err)
21734e93683SPaul Cercueil goto err_clk_put;
21834e93683SPaul Cercueil
21934e93683SPaul Cercueil rate = clk_get_rate(tcu->cs_clk);
22034e93683SPaul Cercueil if (!rate) {
22134e93683SPaul Cercueil err = -EINVAL;
22234e93683SPaul Cercueil goto err_clk_disable;
22334e93683SPaul Cercueil }
22434e93683SPaul Cercueil
22534e93683SPaul Cercueil /* Reset channel */
22634e93683SPaul Cercueil regmap_update_bits(tcu->map, TCU_REG_TCSRc(channel),
22734e93683SPaul Cercueil 0xffff & ~TCU_TCSR_RESERVED_BITS, 0);
22834e93683SPaul Cercueil
22934e93683SPaul Cercueil /* Reset counter */
23034e93683SPaul Cercueil regmap_write(tcu->map, TCU_REG_TDFRc(channel), 0xffff);
23134e93683SPaul Cercueil regmap_write(tcu->map, TCU_REG_TCNTc(channel), 0);
23234e93683SPaul Cercueil
23334e93683SPaul Cercueil /* Enable channel */
23434e93683SPaul Cercueil regmap_write(tcu->map, TCU_REG_TESR, BIT(channel));
23534e93683SPaul Cercueil
23634e93683SPaul Cercueil cs->name = "ingenic-timer";
23734e93683SPaul Cercueil cs->rating = 200;
23834e93683SPaul Cercueil cs->flags = CLOCK_SOURCE_IS_CONTINUOUS;
23934e93683SPaul Cercueil cs->mask = CLOCKSOURCE_MASK(16);
24034e93683SPaul Cercueil cs->read = ingenic_tcu_timer_cs_read;
24134e93683SPaul Cercueil
24234e93683SPaul Cercueil err = clocksource_register_hz(cs, rate);
24334e93683SPaul Cercueil if (err)
24434e93683SPaul Cercueil goto err_clk_disable;
24534e93683SPaul Cercueil
24634e93683SPaul Cercueil return 0;
24734e93683SPaul Cercueil
24834e93683SPaul Cercueil err_clk_disable:
24934e93683SPaul Cercueil clk_disable_unprepare(tcu->cs_clk);
25034e93683SPaul Cercueil err_clk_put:
25134e93683SPaul Cercueil clk_put(tcu->cs_clk);
25234e93683SPaul Cercueil return err;
25334e93683SPaul Cercueil }
25434e93683SPaul Cercueil
25534e93683SPaul Cercueil static const struct ingenic_soc_info jz4740_soc_info = {
25634e93683SPaul Cercueil .num_channels = 8,
25734e93683SPaul Cercueil };
25834e93683SPaul Cercueil
25934e93683SPaul Cercueil static const struct ingenic_soc_info jz4725b_soc_info = {
26034e93683SPaul Cercueil .num_channels = 6,
26134e93683SPaul Cercueil };
26234e93683SPaul Cercueil
26334e93683SPaul Cercueil static const struct of_device_id ingenic_tcu_of_match[] = {
26434e93683SPaul Cercueil { .compatible = "ingenic,jz4740-tcu", .data = &jz4740_soc_info, },
26534e93683SPaul Cercueil { .compatible = "ingenic,jz4725b-tcu", .data = &jz4725b_soc_info, },
2668a3f717fSPaul Cercueil { .compatible = "ingenic,jz4760-tcu", .data = &jz4740_soc_info, },
26734e93683SPaul Cercueil { .compatible = "ingenic,jz4770-tcu", .data = &jz4740_soc_info, },
268a7cd3955S周琰杰 (Zhou Yanjie) { .compatible = "ingenic,x1000-tcu", .data = &jz4740_soc_info, },
26934e93683SPaul Cercueil { /* sentinel */ }
27034e93683SPaul Cercueil };
27134e93683SPaul Cercueil
ingenic_tcu_init(struct device_node * np)27234e93683SPaul Cercueil static int __init ingenic_tcu_init(struct device_node *np)
27334e93683SPaul Cercueil {
27434e93683SPaul Cercueil const struct of_device_id *id = of_match_node(ingenic_tcu_of_match, np);
27534e93683SPaul Cercueil const struct ingenic_soc_info *soc_info = id->data;
276f19d838dS周琰杰 (Zhou Yanjie) struct ingenic_tcu_timer *timer;
27734e93683SPaul Cercueil struct ingenic_tcu *tcu;
27834e93683SPaul Cercueil struct regmap *map;
279f19d838dS周琰杰 (Zhou Yanjie) unsigned int cpu;
280f19d838dS周琰杰 (Zhou Yanjie) int ret, last_bit = -1;
28134e93683SPaul Cercueil long rate;
28234e93683SPaul Cercueil
28334e93683SPaul Cercueil of_node_clear_flag(np, OF_POPULATED);
28434e93683SPaul Cercueil
28534e93683SPaul Cercueil map = device_node_to_regmap(np);
28634e93683SPaul Cercueil if (IS_ERR(map))
28734e93683SPaul Cercueil return PTR_ERR(map);
28834e93683SPaul Cercueil
289f19d838dS周琰杰 (Zhou Yanjie) tcu = kzalloc(struct_size(tcu, timers, num_possible_cpus()),
290f19d838dS周琰杰 (Zhou Yanjie) GFP_KERNEL);
29134e93683SPaul Cercueil if (!tcu)
29234e93683SPaul Cercueil return -ENOMEM;
29334e93683SPaul Cercueil
294f19d838dS周琰杰 (Zhou Yanjie) /*
295f19d838dS周琰杰 (Zhou Yanjie) * Enable all TCU channels for PWM use by default except channels 0/1,
296f19d838dS周琰杰 (Zhou Yanjie) * and channel 2 if target CPU is JZ4780/X2000 and SMP is selected.
297f19d838dS周琰杰 (Zhou Yanjie) */
298f19d838dS周琰杰 (Zhou Yanjie) tcu->pwm_channels_mask = GENMASK(soc_info->num_channels - 1,
299f19d838dS周琰杰 (Zhou Yanjie) num_possible_cpus() + 1);
30034e93683SPaul Cercueil of_property_read_u32(np, "ingenic,pwm-channels-mask",
30134e93683SPaul Cercueil (u32 *)&tcu->pwm_channels_mask);
30234e93683SPaul Cercueil
303f19d838dS周琰杰 (Zhou Yanjie) /* Verify that we have at least num_possible_cpus() + 1 free channels */
304f19d838dS周琰杰 (Zhou Yanjie) if (hweight8(tcu->pwm_channels_mask) >
305f19d838dS周琰杰 (Zhou Yanjie) soc_info->num_channels - num_possible_cpus() + 1) {
30634e93683SPaul Cercueil pr_crit("%s: Invalid PWM channel mask: 0x%02lx\n", __func__,
30734e93683SPaul Cercueil tcu->pwm_channels_mask);
30834e93683SPaul Cercueil ret = -EINVAL;
30934e93683SPaul Cercueil goto err_free_ingenic_tcu;
31034e93683SPaul Cercueil }
31134e93683SPaul Cercueil
31234e93683SPaul Cercueil tcu->map = map;
313f19d838dS周琰杰 (Zhou Yanjie) tcu->np = np;
31434e93683SPaul Cercueil ingenic_tcu = tcu;
31534e93683SPaul Cercueil
316f19d838dS周琰杰 (Zhou Yanjie) for (cpu = 0; cpu < num_possible_cpus(); cpu++) {
317f19d838dS周琰杰 (Zhou Yanjie) timer = &tcu->timers[cpu];
318f19d838dS周琰杰 (Zhou Yanjie)
319f19d838dS周琰杰 (Zhou Yanjie) timer->cpu = cpu;
320f19d838dS周琰杰 (Zhou Yanjie) timer->channel = find_next_zero_bit(&tcu->pwm_channels_mask,
321f19d838dS周琰杰 (Zhou Yanjie) soc_info->num_channels,
322f19d838dS周琰杰 (Zhou Yanjie) last_bit + 1);
323f19d838dS周琰杰 (Zhou Yanjie) last_bit = timer->channel;
324f19d838dS周琰杰 (Zhou Yanjie) }
325f19d838dS周琰杰 (Zhou Yanjie)
32634e93683SPaul Cercueil tcu->cs_channel = find_next_zero_bit(&tcu->pwm_channels_mask,
32734e93683SPaul Cercueil soc_info->num_channels,
328f19d838dS周琰杰 (Zhou Yanjie) last_bit + 1);
32934e93683SPaul Cercueil
33034e93683SPaul Cercueil ret = ingenic_tcu_clocksource_init(np, tcu);
33134e93683SPaul Cercueil if (ret) {
33234e93683SPaul Cercueil pr_crit("%s: Unable to init clocksource: %d\n", __func__, ret);
33334e93683SPaul Cercueil goto err_free_ingenic_tcu;
33434e93683SPaul Cercueil }
33534e93683SPaul Cercueil
336f19d838dS周琰杰 (Zhou Yanjie) /* Setup clock events on each CPU core */
337f19d838dS周琰杰 (Zhou Yanjie) ret = cpuhp_setup_state(CPUHP_AP_ONLINE_DYN, "Ingenic XBurst: online",
338f19d838dS周琰杰 (Zhou Yanjie) ingenic_tcu_setup_cevt, NULL);
339f19d838dS周琰杰 (Zhou Yanjie) if (ret < 0) {
340f19d838dS周琰杰 (Zhou Yanjie) pr_crit("%s: Unable to start CPU timers: %d\n", __func__, ret);
34134e93683SPaul Cercueil goto err_tcu_clocksource_cleanup;
342f19d838dS周琰杰 (Zhou Yanjie) }
34334e93683SPaul Cercueil
34434e93683SPaul Cercueil /* Register the sched_clock at the end as there's no way to undo it */
34534e93683SPaul Cercueil rate = clk_get_rate(tcu->cs_clk);
34634e93683SPaul Cercueil sched_clock_register(ingenic_tcu_timer_read, 16, rate);
34734e93683SPaul Cercueil
34834e93683SPaul Cercueil return 0;
34934e93683SPaul Cercueil
35034e93683SPaul Cercueil err_tcu_clocksource_cleanup:
35134e93683SPaul Cercueil clocksource_unregister(&tcu->cs);
35234e93683SPaul Cercueil clk_disable_unprepare(tcu->cs_clk);
35334e93683SPaul Cercueil clk_put(tcu->cs_clk);
35434e93683SPaul Cercueil err_free_ingenic_tcu:
35534e93683SPaul Cercueil kfree(tcu);
35634e93683SPaul Cercueil return ret;
35734e93683SPaul Cercueil }
35834e93683SPaul Cercueil
35934e93683SPaul Cercueil TIMER_OF_DECLARE(jz4740_tcu_intc, "ingenic,jz4740-tcu", ingenic_tcu_init);
36034e93683SPaul Cercueil TIMER_OF_DECLARE(jz4725b_tcu_intc, "ingenic,jz4725b-tcu", ingenic_tcu_init);
3618a3f717fSPaul Cercueil TIMER_OF_DECLARE(jz4760_tcu_intc, "ingenic,jz4760-tcu", ingenic_tcu_init);
36234e93683SPaul Cercueil TIMER_OF_DECLARE(jz4770_tcu_intc, "ingenic,jz4770-tcu", ingenic_tcu_init);
363a7cd3955S周琰杰 (Zhou Yanjie) TIMER_OF_DECLARE(x1000_tcu_intc, "ingenic,x1000-tcu", ingenic_tcu_init);
36434e93683SPaul Cercueil
ingenic_tcu_probe(struct platform_device * pdev)36534e93683SPaul Cercueil static int __init ingenic_tcu_probe(struct platform_device *pdev)
36634e93683SPaul Cercueil {
36734e93683SPaul Cercueil platform_set_drvdata(pdev, ingenic_tcu);
36834e93683SPaul Cercueil
36934e93683SPaul Cercueil return 0;
37034e93683SPaul Cercueil }
37134e93683SPaul Cercueil
ingenic_tcu_suspend(struct device * dev)37234e93683SPaul Cercueil static int ingenic_tcu_suspend(struct device *dev)
37334e93683SPaul Cercueil {
37434e93683SPaul Cercueil struct ingenic_tcu *tcu = dev_get_drvdata(dev);
375f19d838dS周琰杰 (Zhou Yanjie) unsigned int cpu;
37634e93683SPaul Cercueil
37734e93683SPaul Cercueil clk_disable(tcu->cs_clk);
378f19d838dS周琰杰 (Zhou Yanjie)
379f19d838dS周琰杰 (Zhou Yanjie) for (cpu = 0; cpu < num_online_cpus(); cpu++)
380f19d838dS周琰杰 (Zhou Yanjie) clk_disable(tcu->timers[cpu].clk);
381f19d838dS周琰杰 (Zhou Yanjie)
38234e93683SPaul Cercueil return 0;
38334e93683SPaul Cercueil }
38434e93683SPaul Cercueil
ingenic_tcu_resume(struct device * dev)38534e93683SPaul Cercueil static int ingenic_tcu_resume(struct device *dev)
38634e93683SPaul Cercueil {
38734e93683SPaul Cercueil struct ingenic_tcu *tcu = dev_get_drvdata(dev);
388f19d838dS周琰杰 (Zhou Yanjie) unsigned int cpu;
38934e93683SPaul Cercueil int ret;
39034e93683SPaul Cercueil
391f19d838dS周琰杰 (Zhou Yanjie) for (cpu = 0; cpu < num_online_cpus(); cpu++) {
392f19d838dS周琰杰 (Zhou Yanjie) ret = clk_enable(tcu->timers[cpu].clk);
39334e93683SPaul Cercueil if (ret)
394f19d838dS周琰杰 (Zhou Yanjie) goto err_timer_clk_disable;
39534e93683SPaul Cercueil }
39634e93683SPaul Cercueil
397f19d838dS周琰杰 (Zhou Yanjie) ret = clk_enable(tcu->cs_clk);
398f19d838dS周琰杰 (Zhou Yanjie) if (ret)
399f19d838dS周琰杰 (Zhou Yanjie) goto err_timer_clk_disable;
400f19d838dS周琰杰 (Zhou Yanjie)
40134e93683SPaul Cercueil return 0;
402f19d838dS周琰杰 (Zhou Yanjie)
403f19d838dS周琰杰 (Zhou Yanjie) err_timer_clk_disable:
404f19d838dS周琰杰 (Zhou Yanjie) for (; cpu > 0; cpu--)
405f19d838dS周琰杰 (Zhou Yanjie) clk_disable(tcu->timers[cpu - 1].clk);
406f19d838dS周琰杰 (Zhou Yanjie) return ret;
40734e93683SPaul Cercueil }
40834e93683SPaul Cercueil
40934e93683SPaul Cercueil static const struct dev_pm_ops ingenic_tcu_pm_ops = {
41034e93683SPaul Cercueil /* _noirq: We want the TCU clocks to be gated last / ungated first */
41134e93683SPaul Cercueil .suspend_noirq = ingenic_tcu_suspend,
41234e93683SPaul Cercueil .resume_noirq = ingenic_tcu_resume,
41334e93683SPaul Cercueil };
41434e93683SPaul Cercueil
41534e93683SPaul Cercueil static struct platform_driver ingenic_tcu_driver = {
41634e93683SPaul Cercueil .driver = {
41734e93683SPaul Cercueil .name = "ingenic-tcu-timer",
41834e93683SPaul Cercueil .pm = pm_sleep_ptr(&ingenic_tcu_pm_ops),
41934e93683SPaul Cercueil .of_match_table = ingenic_tcu_of_match,
42034e93683SPaul Cercueil },
42134e93683SPaul Cercueil };
42234e93683SPaul Cercueil builtin_platform_driver_probe(ingenic_tcu_driver, ingenic_tcu_probe);
42334e93683SPaul Cercueil