1 | // SPDX-License-Identifier: GPL-2.0 |
2 | // Copyright (C) 2012 Broadcom Corporation |
3 | |
4 | #include <linux/init.h> |
5 | #include <linux/irq.h> |
6 | #include <linux/interrupt.h> |
7 | #include <linux/jiffies.h> |
8 | #include <linux/clockchips.h> |
9 | #include <linux/types.h> |
10 | #include <linux/clk.h> |
11 | |
12 | #include <linux/io.h> |
13 | |
14 | #include <linux/of.h> |
15 | #include <linux/of_address.h> |
16 | #include <linux/of_irq.h> |
17 | |
18 | |
19 | #define KONA_GPTIMER_STCS_OFFSET 0x00000000 |
20 | #define KONA_GPTIMER_STCLO_OFFSET 0x00000004 |
21 | #define KONA_GPTIMER_STCHI_OFFSET 0x00000008 |
22 | #define KONA_GPTIMER_STCM0_OFFSET 0x0000000C |
23 | |
24 | #define KONA_GPTIMER_STCS_TIMER_MATCH_SHIFT 0 |
25 | #define KONA_GPTIMER_STCS_COMPARE_ENABLE_SHIFT 4 |
26 | |
27 | struct kona_bcm_timers { |
28 | int tmr_irq; |
29 | void __iomem *tmr_regs; |
30 | }; |
31 | |
32 | static struct kona_bcm_timers timers; |
33 | |
34 | static u32 arch_timer_rate; |
35 | |
36 | /* |
37 | * We use the peripheral timers for system tick, the cpu global timer for |
38 | * profile tick |
39 | */ |
40 | static void kona_timer_disable_and_clear(void __iomem *base) |
41 | { |
42 | uint32_t reg; |
43 | |
44 | /* |
45 | * clear and disable interrupts |
46 | * We are using compare/match register 0 for our system interrupts |
47 | */ |
48 | reg = readl(addr: base + KONA_GPTIMER_STCS_OFFSET); |
49 | |
50 | /* Clear compare (0) interrupt */ |
51 | reg |= 1 << KONA_GPTIMER_STCS_TIMER_MATCH_SHIFT; |
52 | /* disable compare */ |
53 | reg &= ~(1 << KONA_GPTIMER_STCS_COMPARE_ENABLE_SHIFT); |
54 | |
55 | writel(val: reg, addr: base + KONA_GPTIMER_STCS_OFFSET); |
56 | |
57 | } |
58 | |
59 | static int |
60 | kona_timer_get_counter(void __iomem *timer_base, uint32_t *msw, uint32_t *lsw) |
61 | { |
62 | int loop_limit = 3; |
63 | |
64 | /* |
65 | * Read 64-bit free running counter |
66 | * 1. Read hi-word |
67 | * 2. Read low-word |
68 | * 3. Read hi-word again |
69 | * 4.1 |
70 | * if new hi-word is not equal to previously read hi-word, then |
71 | * start from #1 |
72 | * 4.2 |
73 | * if new hi-word is equal to previously read hi-word then stop. |
74 | */ |
75 | |
76 | do { |
77 | *msw = readl(addr: timer_base + KONA_GPTIMER_STCHI_OFFSET); |
78 | *lsw = readl(addr: timer_base + KONA_GPTIMER_STCLO_OFFSET); |
79 | if (*msw == readl(addr: timer_base + KONA_GPTIMER_STCHI_OFFSET)) |
80 | break; |
81 | } while (--loop_limit); |
82 | if (!loop_limit) { |
83 | pr_err("bcm_kona_timer: getting counter failed.\n" ); |
84 | pr_err(" Timer will be impacted\n" ); |
85 | return -ETIMEDOUT; |
86 | } |
87 | |
88 | return 0; |
89 | } |
90 | |
91 | static int kona_timer_set_next_event(unsigned long clc, |
92 | struct clock_event_device *unused) |
93 | { |
94 | /* |
95 | * timer (0) is disabled by the timer interrupt already |
96 | * so, here we reload the next event value and re-enable |
97 | * the timer. |
98 | * |
99 | * This way, we are potentially losing the time between |
100 | * timer-interrupt->set_next_event. CPU local timers, when |
101 | * they come in should get rid of skew. |
102 | */ |
103 | |
104 | uint32_t lsw, msw; |
105 | uint32_t reg; |
106 | int ret; |
107 | |
108 | ret = kona_timer_get_counter(timer_base: timers.tmr_regs, msw: &msw, lsw: &lsw); |
109 | if (ret) |
110 | return ret; |
111 | |
112 | /* Load the "next" event tick value */ |
113 | writel(val: lsw + clc, addr: timers.tmr_regs + KONA_GPTIMER_STCM0_OFFSET); |
114 | |
115 | /* Enable compare */ |
116 | reg = readl(addr: timers.tmr_regs + KONA_GPTIMER_STCS_OFFSET); |
117 | reg |= (1 << KONA_GPTIMER_STCS_COMPARE_ENABLE_SHIFT); |
118 | writel(val: reg, addr: timers.tmr_regs + KONA_GPTIMER_STCS_OFFSET); |
119 | |
120 | return 0; |
121 | } |
122 | |
123 | static int kona_timer_shutdown(struct clock_event_device *evt) |
124 | { |
125 | kona_timer_disable_and_clear(base: timers.tmr_regs); |
126 | return 0; |
127 | } |
128 | |
129 | static struct clock_event_device kona_clockevent_timer = { |
130 | .name = "timer 1" , |
131 | .features = CLOCK_EVT_FEAT_ONESHOT, |
132 | .set_next_event = kona_timer_set_next_event, |
133 | .set_state_shutdown = kona_timer_shutdown, |
134 | .tick_resume = kona_timer_shutdown, |
135 | }; |
136 | |
137 | static void __init kona_timer_clockevents_init(void) |
138 | { |
139 | kona_clockevent_timer.cpumask = cpumask_of(0); |
140 | clockevents_config_and_register(dev: &kona_clockevent_timer, |
141 | freq: arch_timer_rate, min_delta: 6, max_delta: 0xffffffff); |
142 | } |
143 | |
144 | static irqreturn_t kona_timer_interrupt(int irq, void *dev_id) |
145 | { |
146 | struct clock_event_device *evt = &kona_clockevent_timer; |
147 | |
148 | kona_timer_disable_and_clear(base: timers.tmr_regs); |
149 | evt->event_handler(evt); |
150 | return IRQ_HANDLED; |
151 | } |
152 | |
153 | static int __init kona_timer_init(struct device_node *node) |
154 | { |
155 | u32 freq; |
156 | struct clk *external_clk; |
157 | |
158 | external_clk = of_clk_get_by_name(np: node, NULL); |
159 | |
160 | if (!IS_ERR(ptr: external_clk)) { |
161 | arch_timer_rate = clk_get_rate(clk: external_clk); |
162 | clk_prepare_enable(clk: external_clk); |
163 | } else if (!of_property_read_u32(np: node, propname: "clock-frequency" , out_value: &freq)) { |
164 | arch_timer_rate = freq; |
165 | } else { |
166 | pr_err("Kona Timer v1 unable to determine clock-frequency\n" ); |
167 | return -EINVAL; |
168 | } |
169 | |
170 | /* Setup IRQ numbers */ |
171 | timers.tmr_irq = irq_of_parse_and_map(node, index: 0); |
172 | |
173 | /* Setup IO addresses */ |
174 | timers.tmr_regs = of_iomap(node, index: 0); |
175 | |
176 | kona_timer_disable_and_clear(base: timers.tmr_regs); |
177 | |
178 | kona_timer_clockevents_init(); |
179 | if (request_irq(irq: timers.tmr_irq, handler: kona_timer_interrupt, IRQF_TIMER, |
180 | name: "Kona Timer Tick" , NULL)) |
181 | pr_err("%s: request_irq() failed\n" , "Kona Timer Tick" ); |
182 | kona_timer_set_next_event(clc: (arch_timer_rate / HZ), NULL); |
183 | |
184 | return 0; |
185 | } |
186 | |
187 | TIMER_OF_DECLARE(brcm_kona, "brcm,kona-timer" , kona_timer_init); |
188 | /* |
189 | * bcm,kona-timer is deprecated by brcm,kona-timer |
190 | * being kept here for driver compatibility |
191 | */ |
192 | TIMER_OF_DECLARE(bcm_kona, "bcm,kona-timer" , kona_timer_init); |
193 | |