1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /* |
3 | * Copyright (C) 2014 Oleksij Rempel <linux@rempel-privat.de> |
4 | */ |
5 | |
6 | #include <linux/kernel.h> |
7 | #include <linux/init.h> |
8 | #include <linux/interrupt.h> |
9 | #include <linux/sched.h> |
10 | #include <linux/clk.h> |
11 | #include <linux/clocksource.h> |
12 | #include <linux/clockchips.h> |
13 | #include <linux/io.h> |
14 | #include <linux/of.h> |
15 | #include <linux/of_address.h> |
16 | #include <linux/of_irq.h> |
17 | #include <linux/bitops.h> |
18 | |
19 | #define DRIVER_NAME "asm9260-timer" |
20 | |
21 | /* |
22 | * this device provide 4 offsets for each register: |
23 | * 0x0 - plain read write mode |
24 | * 0x4 - set mode, OR logic. |
25 | * 0x8 - clr mode, XOR logic. |
26 | * 0xc - togle mode. |
27 | */ |
28 | #define SET_REG 4 |
29 | #define CLR_REG 8 |
30 | |
31 | #define HW_IR 0x0000 /* RW. Interrupt */ |
32 | #define BM_IR_CR0 BIT(4) |
33 | #define BM_IR_MR3 BIT(3) |
34 | #define BM_IR_MR2 BIT(2) |
35 | #define BM_IR_MR1 BIT(1) |
36 | #define BM_IR_MR0 BIT(0) |
37 | |
38 | #define HW_TCR 0x0010 /* RW. Timer controller */ |
39 | /* BM_C*_RST |
40 | * Timer Counter and the Prescale Counter are synchronously reset on the |
41 | * next positive edge of PCLK. The counters remain reset until TCR[1] is |
42 | * returned to zero. */ |
43 | #define BM_C3_RST BIT(7) |
44 | #define BM_C2_RST BIT(6) |
45 | #define BM_C1_RST BIT(5) |
46 | #define BM_C0_RST BIT(4) |
47 | /* BM_C*_EN |
48 | * 1 - Timer Counter and Prescale Counter are enabled for counting |
49 | * 0 - counters are disabled */ |
50 | #define BM_C3_EN BIT(3) |
51 | #define BM_C2_EN BIT(2) |
52 | #define BM_C1_EN BIT(1) |
53 | #define BM_C0_EN BIT(0) |
54 | |
55 | #define HW_DIR 0x0020 /* RW. Direction? */ |
56 | /* 00 - count up |
57 | * 01 - count down |
58 | * 10 - ?? 2^n/2 */ |
59 | #define BM_DIR_COUNT_UP 0 |
60 | #define BM_DIR_COUNT_DOWN 1 |
61 | #define BM_DIR0_SHIFT 0 |
62 | #define BM_DIR1_SHIFT 4 |
63 | #define BM_DIR2_SHIFT 8 |
64 | #define BM_DIR3_SHIFT 12 |
65 | #define BM_DIR_DEFAULT (BM_DIR_COUNT_UP << BM_DIR0_SHIFT | \ |
66 | BM_DIR_COUNT_UP << BM_DIR1_SHIFT | \ |
67 | BM_DIR_COUNT_UP << BM_DIR2_SHIFT | \ |
68 | BM_DIR_COUNT_UP << BM_DIR3_SHIFT) |
69 | |
70 | #define HW_TC0 0x0030 /* RO. Timer counter 0 */ |
71 | /* HW_TC*. Timer counter owerflow (0xffff.ffff to 0x0000.0000) do not generate |
72 | * interrupt. This registers can be used to detect overflow */ |
73 | #define HW_TC1 0x0040 |
74 | #define HW_TC2 0x0050 |
75 | #define HW_TC3 0x0060 |
76 | |
77 | #define HW_PR 0x0070 /* RW. prescaler */ |
78 | #define BM_PR_DISABLE 0 |
79 | #define HW_PC 0x0080 /* RO. Prescaler counter */ |
80 | #define HW_MCR 0x0090 /* RW. Match control */ |
81 | /* enable interrupt on match */ |
82 | #define BM_MCR_INT_EN(n) (1 << (n * 3 + 0)) |
83 | /* enable TC reset on match */ |
84 | #define BM_MCR_RES_EN(n) (1 << (n * 3 + 1)) |
85 | /* enable stop TC on match */ |
86 | #define BM_MCR_STOP_EN(n) (1 << (n * 3 + 2)) |
87 | |
88 | #define HW_MR0 0x00a0 /* RW. Match reg */ |
89 | #define HW_MR1 0x00b0 |
90 | #define HW_MR2 0x00C0 |
91 | #define HW_MR3 0x00D0 |
92 | |
93 | #define HW_CTCR 0x0180 /* Counter control */ |
94 | #define BM_CTCR0_SHIFT 0 |
95 | #define BM_CTCR1_SHIFT 2 |
96 | #define BM_CTCR2_SHIFT 4 |
97 | #define BM_CTCR3_SHIFT 6 |
98 | #define BM_CTCR_TM 0 /* Timer mode. Every rising PCLK edge. */ |
99 | #define BM_CTCR_DEFAULT (BM_CTCR_TM << BM_CTCR0_SHIFT | \ |
100 | BM_CTCR_TM << BM_CTCR1_SHIFT | \ |
101 | BM_CTCR_TM << BM_CTCR2_SHIFT | \ |
102 | BM_CTCR_TM << BM_CTCR3_SHIFT) |
103 | |
104 | static struct asm9260_timer_priv { |
105 | void __iomem *base; |
106 | unsigned long ticks_per_jiffy; |
107 | } priv; |
108 | |
109 | static int asm9260_timer_set_next_event(unsigned long delta, |
110 | struct clock_event_device *evt) |
111 | { |
112 | /* configure match count for TC0 */ |
113 | writel_relaxed(delta, priv.base + HW_MR0); |
114 | /* enable TC0 */ |
115 | writel_relaxed(BM_C0_EN, priv.base + HW_TCR + SET_REG); |
116 | return 0; |
117 | } |
118 | |
119 | static inline void __asm9260_timer_shutdown(struct clock_event_device *evt) |
120 | { |
121 | /* stop timer0 */ |
122 | writel_relaxed(BM_C0_EN, priv.base + HW_TCR + CLR_REG); |
123 | } |
124 | |
125 | static int asm9260_timer_shutdown(struct clock_event_device *evt) |
126 | { |
127 | __asm9260_timer_shutdown(evt); |
128 | return 0; |
129 | } |
130 | |
131 | static int asm9260_timer_set_oneshot(struct clock_event_device *evt) |
132 | { |
133 | __asm9260_timer_shutdown(evt); |
134 | |
135 | /* enable reset and stop on match */ |
136 | writel_relaxed(BM_MCR_RES_EN(0) | BM_MCR_STOP_EN(0), |
137 | priv.base + HW_MCR + SET_REG); |
138 | return 0; |
139 | } |
140 | |
141 | static int asm9260_timer_set_periodic(struct clock_event_device *evt) |
142 | { |
143 | __asm9260_timer_shutdown(evt); |
144 | |
145 | /* disable reset and stop on match */ |
146 | writel_relaxed(BM_MCR_RES_EN(0) | BM_MCR_STOP_EN(0), |
147 | priv.base + HW_MCR + CLR_REG); |
148 | /* configure match count for TC0 */ |
149 | writel_relaxed(priv.ticks_per_jiffy, priv.base + HW_MR0); |
150 | /* enable TC0 */ |
151 | writel_relaxed(BM_C0_EN, priv.base + HW_TCR + SET_REG); |
152 | return 0; |
153 | } |
154 | |
155 | static struct clock_event_device event_dev = { |
156 | .name = DRIVER_NAME, |
157 | .rating = 200, |
158 | .features = CLOCK_EVT_FEAT_PERIODIC | |
159 | CLOCK_EVT_FEAT_ONESHOT, |
160 | .set_next_event = asm9260_timer_set_next_event, |
161 | .set_state_shutdown = asm9260_timer_shutdown, |
162 | .set_state_periodic = asm9260_timer_set_periodic, |
163 | .set_state_oneshot = asm9260_timer_set_oneshot, |
164 | .tick_resume = asm9260_timer_shutdown, |
165 | }; |
166 | |
167 | static irqreturn_t asm9260_timer_interrupt(int irq, void *dev_id) |
168 | { |
169 | struct clock_event_device *evt = dev_id; |
170 | |
171 | evt->event_handler(evt); |
172 | |
173 | writel_relaxed(BM_IR_MR0, priv.base + HW_IR); |
174 | |
175 | return IRQ_HANDLED; |
176 | } |
177 | |
178 | /* |
179 | * --------------------------------------------------------------------------- |
180 | * Timer initialization |
181 | * --------------------------------------------------------------------------- |
182 | */ |
183 | static int __init asm9260_timer_init(struct device_node *np) |
184 | { |
185 | int irq; |
186 | struct clk *clk; |
187 | int ret; |
188 | unsigned long rate; |
189 | |
190 | priv.base = of_io_request_and_map(device: np, index: 0, name: np->name); |
191 | if (IS_ERR(ptr: priv.base)) { |
192 | pr_err("%pOFn: unable to map resource\n" , np); |
193 | return PTR_ERR(ptr: priv.base); |
194 | } |
195 | |
196 | clk = of_clk_get(np, index: 0); |
197 | if (IS_ERR(ptr: clk)) { |
198 | pr_err("Failed to get clk!\n" ); |
199 | return PTR_ERR(ptr: clk); |
200 | } |
201 | |
202 | ret = clk_prepare_enable(clk); |
203 | if (ret) { |
204 | pr_err("Failed to enable clk!\n" ); |
205 | return ret; |
206 | } |
207 | |
208 | irq = irq_of_parse_and_map(node: np, index: 0); |
209 | ret = request_irq(irq, handler: asm9260_timer_interrupt, IRQF_TIMER, |
210 | DRIVER_NAME, dev: &event_dev); |
211 | if (ret) { |
212 | pr_err("Failed to setup irq!\n" ); |
213 | return ret; |
214 | } |
215 | |
216 | /* set all timers for count-up */ |
217 | writel_relaxed(BM_DIR_DEFAULT, priv.base + HW_DIR); |
218 | /* disable divider */ |
219 | writel_relaxed(BM_PR_DISABLE, priv.base + HW_PR); |
220 | /* make sure all timers use every rising PCLK edge. */ |
221 | writel_relaxed(BM_CTCR_DEFAULT, priv.base + HW_CTCR); |
222 | /* enable interrupt for TC0 and clean setting for all other lines */ |
223 | writel_relaxed(BM_MCR_INT_EN(0) , priv.base + HW_MCR); |
224 | |
225 | rate = clk_get_rate(clk); |
226 | clocksource_mmio_init(priv.base + HW_TC1, DRIVER_NAME, rate, |
227 | 200, 32, clocksource_mmio_readl_up); |
228 | |
229 | /* Seems like we can't use counter without match register even if |
230 | * actions for MR are disabled. So, set MR to max value. */ |
231 | writel_relaxed(0xffffffff, priv.base + HW_MR1); |
232 | /* enable TC1 */ |
233 | writel_relaxed(BM_C1_EN, priv.base + HW_TCR + SET_REG); |
234 | |
235 | priv.ticks_per_jiffy = DIV_ROUND_CLOSEST(rate, HZ); |
236 | event_dev.cpumask = cpumask_of(0); |
237 | clockevents_config_and_register(dev: &event_dev, freq: rate, min_delta: 0x2c00, max_delta: 0xfffffffe); |
238 | |
239 | return 0; |
240 | } |
241 | TIMER_OF_DECLARE(asm9260_timer, "alphascale,asm9260-timer" , |
242 | asm9260_timer_init); |
243 | |