]>
Commit | Line | Data |
---|---|---|
2c21749d MS |
1 | // SPDX-License-Identifier: GPL-2.0+ |
2 | /* | |
3 | * (C) Copyright 2018 | |
4 | * Mario Six, Guntermann & Drunck GmbH, [email protected] | |
5 | */ | |
6 | ||
7 | #include <common.h> | |
8 | #include <board.h> | |
9 | #include <clk.h> | |
10 | #include <dm.h> | |
c30b7adb | 11 | #include <irq_func.h> |
f7ae49fc | 12 | #include <log.h> |
c3e4430e | 13 | #include <status_led.h> |
036a017f | 14 | #include <time.h> |
2c21749d MS |
15 | #include <timer.h> |
16 | #include <watchdog.h> | |
25a5818f | 17 | #include <asm/ptrace.h> |
cd93d625 | 18 | #include <linux/bitops.h> |
2c21749d MS |
19 | |
20 | DECLARE_GLOBAL_DATA_PTR; | |
21 | ||
22 | /** | |
23 | * struct mpc83xx_timer_priv - Private data structure for MPC83xx timer driver | |
24 | * @decrementer_count: Value to which the decrementer register should be re-set | |
25 | * to when a timer interrupt occurs, thus determines the | |
26 | * interrupt frequency (value for 1e6/HZ microseconds) | |
27 | * @timestamp: Counter for the number of timer interrupts that have | |
28 | * occurred (i.e. can be used to trigger events | |
29 | * periodically in the timer interrupt) | |
30 | */ | |
31 | struct mpc83xx_timer_priv { | |
32 | uint decrementer_count; | |
33 | ulong timestamp; | |
34 | }; | |
35 | ||
36 | /* | |
37 | * Bitmask for enabling the time base in the SPCR (System Priority | |
38 | * Configuration Register) | |
39 | */ | |
40 | static const u32 SPCR_TBEN_MASK = BIT(31 - 9); | |
41 | ||
42 | /** | |
43 | * get_dec() - Get the value of the decrementer register | |
44 | * | |
45 | * Return: The value of the decrementer register | |
46 | */ | |
47 | static inline unsigned long get_dec(void) | |
48 | { | |
49 | unsigned long val; | |
50 | ||
51 | asm volatile ("mfdec %0" : "=r" (val) : ); | |
52 | ||
53 | return val; | |
54 | } | |
55 | ||
56 | /** | |
57 | * set_dec() - Set the value of the decrementer register | |
58 | * @val: The value of the decrementer register to be set | |
59 | */ | |
60 | static inline void set_dec(unsigned long val) | |
61 | { | |
62 | if (val) | |
63 | asm volatile ("mtdec %0"::"r" (val)); | |
64 | } | |
65 | ||
66 | /** | |
67 | * mftbu() - Get value of TBU (upper time base) register | |
68 | * | |
69 | * Return: Value of the TBU register | |
70 | */ | |
71 | static inline u32 mftbu(void) | |
72 | { | |
73 | u32 rval; | |
74 | ||
75 | asm volatile("mftbu %0" : "=r" (rval)); | |
76 | return rval; | |
77 | } | |
78 | ||
79 | /** | |
80 | * mftb() - Get value of TBL (lower time base) register | |
81 | * | |
82 | * Return: Value of the TBL register | |
83 | */ | |
84 | static inline u32 mftb(void) | |
85 | { | |
86 | u32 rval; | |
87 | ||
88 | asm volatile("mftb %0" : "=r" (rval)); | |
89 | return rval; | |
90 | } | |
91 | ||
92 | /* | |
93 | * TODO([email protected]): This should really be done by timer_init, and the | |
94 | * interrupt init should go into a interrupt driver. | |
95 | */ | |
96 | int interrupt_init(void) | |
97 | { | |
98 | immap_t *immr = (immap_t *)CONFIG_SYS_IMMR; | |
99 | struct udevice *csb; | |
100 | struct udevice *board; | |
101 | struct udevice *timer; | |
102 | struct mpc83xx_timer_priv *timer_priv; | |
103 | struct clk clock; | |
104 | int ret; | |
105 | ||
106 | ret = uclass_first_device_err(UCLASS_TIMER, &timer); | |
107 | if (ret) { | |
108 | debug("%s: Could not find timer device (error: %d)", | |
109 | __func__, ret); | |
110 | return ret; | |
111 | } | |
112 | ||
113 | timer_priv = dev_get_priv(timer); | |
114 | ||
115 | if (board_get(&board)) { | |
116 | debug("%s: board device could not be fetched.\n", __func__); | |
117 | return -ENOENT; | |
118 | } | |
119 | ||
120 | ret = uclass_get_device_by_phandle(UCLASS_SIMPLE_BUS, board, | |
121 | "csb", &csb); | |
122 | if (ret) { | |
123 | debug("%s: Could not retrieve CSB device (error: %d)", | |
124 | __func__, ret); | |
125 | return ret; | |
126 | } | |
127 | ||
128 | ret = clk_get_by_index(csb, 0, &clock); | |
129 | if (ret) { | |
130 | debug("%s: Could not retrieve clock (error: %d)", | |
131 | __func__, ret); | |
132 | return ret; | |
133 | } | |
134 | ||
135 | timer_priv->decrementer_count = (clk_get_rate(&clock) / 4) | |
136 | / CONFIG_SYS_HZ; | |
137 | /* Enable e300 time base */ | |
138 | setbits_be32(&immr->sysconf.spcr, SPCR_TBEN_MASK); | |
139 | ||
140 | set_dec(timer_priv->decrementer_count); | |
141 | ||
142 | /* Switch on interrupts */ | |
143 | set_msr(get_msr() | MSR_EE); | |
144 | ||
145 | return 0; | |
146 | } | |
147 | ||
148 | /** | |
149 | * timer_interrupt() - Handler for the timer interrupt | |
150 | * @regs: Array of register values | |
151 | */ | |
152 | void timer_interrupt(struct pt_regs *regs) | |
153 | { | |
154 | struct udevice *timer = gd->timer; | |
155 | struct mpc83xx_timer_priv *priv; | |
156 | ||
157 | /* | |
158 | * During initialization, gd->timer might not be set yet, but the timer | |
159 | * interrupt may already be enabled. In this case, wait for the | |
160 | * initialization to complete | |
161 | */ | |
162 | if (!timer) | |
163 | return; | |
164 | ||
165 | priv = dev_get_priv(timer); | |
166 | ||
167 | /* Restore Decrementer Count */ | |
168 | set_dec(priv->decrementer_count); | |
169 | ||
170 | priv->timestamp++; | |
171 | ||
172 | #if defined(CONFIG_WATCHDOG) || defined(CONFIG_HW_WATCHDOG) | |
173 | if ((timestamp % (CONFIG_SYS_WATCHDOG_FREQ)) == 0) | |
174 | WATCHDOG_RESET(); | |
175 | #endif /* CONFIG_WATCHDOG || CONFIG_HW_WATCHDOG */ | |
176 | ||
177 | #ifdef CONFIG_LED_STATUS | |
178 | status_led_tick(priv->timestamp); | |
179 | #endif /* CONFIG_LED_STATUS */ | |
2c21749d MS |
180 | } |
181 | ||
182 | void wait_ticks(ulong ticks) | |
183 | { | |
184 | ulong end = get_ticks() + ticks; | |
185 | ||
186 | while (end > get_ticks()) | |
187 | WATCHDOG_RESET(); | |
188 | } | |
189 | ||
8af7bb91 | 190 | static u64 mpc83xx_timer_get_count(struct udevice *dev) |
2c21749d MS |
191 | { |
192 | u32 tbu, tbl; | |
193 | ||
194 | /* | |
195 | * To make sure that no tbl overflow occurred between reading tbl and | |
196 | * tbu, read tbu again, and compare it with the previously read tbu | |
197 | * value: If they're different, a tbl overflow has occurred. | |
198 | */ | |
199 | do { | |
200 | tbu = mftbu(); | |
201 | tbl = mftb(); | |
202 | } while (tbu != mftbu()); | |
203 | ||
8af7bb91 | 204 | return (tbu * 0x10000ULL) + tbl; |
2c21749d MS |
205 | } |
206 | ||
207 | static int mpc83xx_timer_probe(struct udevice *dev) | |
208 | { | |
209 | struct timer_dev_priv *uc_priv = dev->uclass_priv; | |
210 | struct clk clock; | |
211 | int ret; | |
212 | ||
213 | ret = interrupt_init(); | |
214 | if (ret) { | |
215 | debug("%s: interrupt_init failed (err = %d)\n", | |
216 | dev->name, ret); | |
217 | return ret; | |
218 | } | |
219 | ||
220 | ret = clk_get_by_index(dev, 0, &clock); | |
221 | if (ret) { | |
222 | debug("%s: Could not retrieve clock (err = %d)\n", | |
223 | dev->name, ret); | |
224 | return ret; | |
225 | } | |
226 | ||
227 | uc_priv->clock_rate = (clk_get_rate(&clock) + 3L) / 4L; | |
228 | ||
229 | return 0; | |
230 | } | |
231 | ||
232 | static const struct timer_ops mpc83xx_timer_ops = { | |
233 | .get_count = mpc83xx_timer_get_count, | |
234 | }; | |
235 | ||
236 | static const struct udevice_id mpc83xx_timer_ids[] = { | |
237 | { .compatible = "fsl,mpc83xx-timer" }, | |
238 | { /* sentinel */ } | |
239 | }; | |
240 | ||
241 | U_BOOT_DRIVER(mpc83xx_timer) = { | |
242 | .name = "mpc83xx_timer", | |
243 | .id = UCLASS_TIMER, | |
244 | .of_match = mpc83xx_timer_ids, | |
245 | .probe = mpc83xx_timer_probe, | |
246 | .ops = &mpc83xx_timer_ops, | |
2c21749d MS |
247 | .priv_auto_alloc_size = sizeof(struct mpc83xx_timer_priv), |
248 | }; |