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