1 // SPDX-License-Identifier: GPL-2.0+
2 //
3 // Copyright 2017-2019 NXP
4 
5 #include <linux/interrupt.h>
6 #include <linux/clockchips.h>
7 #include <linux/slab.h>
8 
9 #include "timer-of.h"
10 
11 #define CMP_OFFSET	0x10000
12 
13 #define CNTCV_LO	0x8
14 #define CNTCV_HI	0xc
15 #define CMPCV_LO	(CMP_OFFSET + 0x20)
16 #define CMPCV_HI	(CMP_OFFSET + 0x24)
17 #define CMPCR		(CMP_OFFSET + 0x2c)
18 
19 #define SYS_CTR_EN		0x1
20 #define SYS_CTR_IRQ_MASK	0x2
21 
22 #define SYS_CTR_CLK_DIV		0x3
23 
24 struct sysctr_private {
25 	u32 cmpcr;
26 };
27 
28 static void sysctr_timer_enable(struct clock_event_device *evt, bool enable)
29 {
30 	struct timer_of *to = to_timer_of(evt);
31 	struct sysctr_private *priv = to->private_data;
32 	void __iomem *base = timer_of_base(to);
33 
34 	writel(enable ? priv->cmpcr | SYS_CTR_EN : priv->cmpcr, base + CMPCR);
35 }
36 
37 static void sysctr_irq_acknowledge(struct clock_event_device *evt)
38 {
39 	/*
40 	 * clear the enable bit(EN =0) will clear
41 	 * the status bit(ISTAT = 0), then the interrupt
42 	 * signal will be negated(acknowledged).
43 	 */
44 	sysctr_timer_enable(evt, false);
45 }
46 
47 static inline u64 sysctr_read_counter(struct clock_event_device *evt)
48 {
49 	struct timer_of *to = to_timer_of(evt);
50 	void __iomem *base = timer_of_base(to);
51 	u32 cnt_hi, tmp_hi, cnt_lo;
52 
53 	do {
54 		cnt_hi = readl_relaxed(base + CNTCV_HI);
55 		cnt_lo = readl_relaxed(base + CNTCV_LO);
56 		tmp_hi = readl_relaxed(base + CNTCV_HI);
57 	} while (tmp_hi != cnt_hi);
58 
59 	return  ((u64) cnt_hi << 32) | cnt_lo;
60 }
61 
62 static int sysctr_set_next_event(unsigned long delta,
63 				 struct clock_event_device *evt)
64 {
65 	struct timer_of *to = to_timer_of(evt);
66 	void __iomem *base = timer_of_base(to);
67 	u32 cmp_hi, cmp_lo;
68 	u64 next;
69 
70 	sysctr_timer_enable(evt, false);
71 
72 	next = sysctr_read_counter(evt);
73 
74 	next += delta;
75 
76 	cmp_hi = (next >> 32) & 0x00fffff;
77 	cmp_lo = next & 0xffffffff;
78 
79 	writel_relaxed(cmp_hi, base + CMPCV_HI);
80 	writel_relaxed(cmp_lo, base + CMPCV_LO);
81 
82 	sysctr_timer_enable(evt, true);
83 
84 	return 0;
85 }
86 
87 static int sysctr_set_state_oneshot(struct clock_event_device *evt)
88 {
89 	return 0;
90 }
91 
92 static int sysctr_set_state_shutdown(struct clock_event_device *evt)
93 {
94 	sysctr_timer_enable(evt, false);
95 
96 	return 0;
97 }
98 
99 static irqreturn_t sysctr_timer_interrupt(int irq, void *dev_id)
100 {
101 	struct clock_event_device *evt = dev_id;
102 
103 	sysctr_irq_acknowledge(evt);
104 
105 	evt->event_handler(evt);
106 
107 	return IRQ_HANDLED;
108 }
109 
110 static struct timer_of to_sysctr = {
111 	.flags = TIMER_OF_IRQ | TIMER_OF_CLOCK | TIMER_OF_BASE,
112 	.clkevt = {
113 		.name			= "i.MX system counter timer",
114 		.features		= CLOCK_EVT_FEAT_ONESHOT |
115 						CLOCK_EVT_FEAT_DYNIRQ,
116 		.set_state_oneshot	= sysctr_set_state_oneshot,
117 		.set_next_event		= sysctr_set_next_event,
118 		.set_state_shutdown	= sysctr_set_state_shutdown,
119 		.rating			= 200,
120 	},
121 	.of_irq = {
122 		.handler		= sysctr_timer_interrupt,
123 		.flags			= IRQF_TIMER,
124 	},
125 	.of_clk = {
126 		.name = "per",
127 	},
128 };
129 
130 static int __init sysctr_timer_init(struct device_node *np)
131 {
132 	struct sysctr_private *priv;
133 	void __iomem *base;
134 	int ret;
135 
136 	priv = kzalloc(sizeof(struct sysctr_private), GFP_KERNEL);
137 	if (!priv)
138 		return -ENOMEM;
139 
140 	ret = timer_of_init(np, &to_sysctr);
141 	if (ret) {
142 		kfree(priv);
143 		return ret;
144 	}
145 
146 	if (!of_property_read_bool(np, "nxp,no-divider")) {
147 		/* system counter clock is divided by 3 internally */
148 		to_sysctr.of_clk.rate /= SYS_CTR_CLK_DIV;
149 	}
150 
151 	to_sysctr.clkevt.cpumask = cpu_possible_mask;
152 	to_sysctr.private_data = priv;
153 
154 	base = timer_of_base(&to_sysctr);
155 	priv->cmpcr = readl(base + CMPCR) & ~SYS_CTR_EN;
156 
157 	clockevents_config_and_register(&to_sysctr.clkevt,
158 					timer_of_rate(&to_sysctr),
159 					0xff, 0x7fffffff);
160 	return 0;
161 }
162 TIMER_OF_DECLARE(sysctr_timer, "nxp,sysctr-timer", sysctr_timer_init);
163