Line data Source code
1 : /* $OpenBSD: tcpcib.c,v 1.8 2014/12/10 12:27:57 mikeb Exp $ */
2 :
3 : /*
4 : * Copyright (c) 2012 Matt Dainty <matt@bodgit-n-scarper.com>
5 : *
6 : * Permission to use, copy, modify, and distribute this software for any
7 : * purpose with or without fee is hereby granted, provided that the above
8 : * copyright notice and this permission notice appear in all copies.
9 : *
10 : * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 : * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 : * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13 : * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 : * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER IN
15 : * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
16 : * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17 : */
18 :
19 : /*
20 : * Intel Atom E600 series LPC bridge also containing HPET and watchdog
21 : */
22 :
23 : #include <sys/param.h>
24 : #include <sys/systm.h>
25 : #include <sys/device.h>
26 : #include <sys/timetc.h>
27 :
28 : #include <machine/bus.h>
29 :
30 : #include <dev/pci/pcireg.h>
31 : #include <dev/pci/pcivar.h>
32 : #include <dev/pci/pcidevs.h>
33 :
34 : #define E600_LPC_SMBA 0x40 /* SMBus Base Address */
35 : #define E600_LPC_GBA 0x44 /* GPIO Base Address */
36 : #define E600_LPC_WDTBA 0x84 /* WDT Base Address */
37 :
38 : #define E600_WDT_SIZE 64 /* I/O region size */
39 : #define E600_WDT_PV1 0x00 /* Preload Value 1 Register */
40 : #define E600_WDT_PV2 0x04 /* Preload Value 2 Register */
41 : #define E600_WDT_RR0 0x0c /* Reload Register 0 */
42 : #define E600_WDT_RR1 0x0d /* Reload Register 1 */
43 : #define E600_WDT_RR1_RELOAD (1 << 0) /* WDT Reload Flag */
44 : #define E600_WDT_RR1_TIMEOUT (1 << 1) /* WDT Timeout Flag */
45 : #define E600_WDT_WDTCR 0x10 /* WDT Configuration Register */
46 : #define E600_WDT_WDTCR_PRE (1 << 2) /* WDT Prescalar Select */
47 : #define E600_WDT_WDTCR_RESET (1 << 3) /* WDT Reset Select */
48 : #define E600_WDT_WDTCR_ENABLE (1 << 4) /* WDT Reset Enable */
49 : #define E600_WDT_WDTCR_TIMEOUT (1 << 5) /* WDT Timeout Output Enable */
50 : #define E600_WDT_DCR 0x14 /* Down Counter Register */
51 : #define E600_WDT_WDTLR 0x18 /* WDT Lock Register */
52 : #define E600_WDT_WDTLR_LOCK (1 << 0) /* Watchdog Timer Lock */
53 : #define E600_WDT_WDTLR_ENABLE (1 << 1) /* Watchdog Timer Enable */
54 : #define E600_WDT_WDTLR_TIMEOUT (1 << 2) /* WDT Timeout Configuration */
55 :
56 : #define E600_HPET_BASE 0xfed00000 /* HPET register base */
57 : #define E600_HPET_SIZE 0x00000400 /* HPET register size */
58 :
59 : #define E600_HPET_GCID 0x000 /* Capabilities and ID */
60 : #define E600_HPET_GCID_WIDTH (1 << 13) /* Counter Size */
61 : #define E600_HPET_PERIOD 0x004 /* Counter Tick Period */
62 : #define E600_HPET_GC 0x010 /* General Configuration */
63 : #define E600_HPET_GC_ENABLE (1 << 0) /* Overall Enable */
64 : #define E600_HPET_GIS 0x020 /* General Interrupt Status */
65 : #define E600_HPET_MCV 0x0f0 /* Main Counter Value */
66 : #define E600_HPET_T0C 0x100 /* Timer 0 Config and Capabilities */
67 : #define E600_HPET_T0CV 0x108 /* Timer 0 Comparator Value */
68 : #define E600_HPET_T1C 0x120 /* Timer 1 Config and Capabilities */
69 : #define E600_HPET_T1CV 0x128 /* Timer 1 Comparator Value */
70 : #define E600_HPET_T2C 0x140 /* Timer 2 Config and Capabilities */
71 : #define E600_HPET_T2CV 0x148 /* Timer 2 Comparator Value */
72 :
73 : struct tcpcib_softc {
74 : struct device sc_dev;
75 :
76 : /* Keep track of which parts of the hardware are active */
77 : int sc_active;
78 : #define E600_WDT_ACTIVE (1 << 0)
79 : #define E600_HPET_ACTIVE (1 << 1)
80 :
81 : /* Watchdog interface */
82 : bus_space_tag_t sc_wdt_iot;
83 : bus_space_handle_t sc_wdt_ioh;
84 :
85 : int sc_wdt_period;
86 :
87 : /* High Precision Event Timer */
88 : bus_space_tag_t sc_hpet_iot;
89 : bus_space_handle_t sc_hpet_ioh;
90 :
91 : struct timecounter sc_hpet_timecounter;
92 : };
93 :
94 : struct cfdriver tcpcib_cd = {
95 : NULL, "tcpcib", DV_DULL
96 : };
97 :
98 : int tcpcib_match(struct device *, void *, void *);
99 : void tcpcib_attach(struct device *, struct device *, void *);
100 : int tcpcib_activate(struct device *, int);
101 :
102 : int tcpcib_wdt_cb(void *, int);
103 : void tcpcib_wdt_init(struct tcpcib_softc *, int);
104 : void tcpcib_wdt_start(struct tcpcib_softc *);
105 : void tcpcib_wdt_stop(struct tcpcib_softc *);
106 :
107 : u_int tcpcib_hpet_get_timecount(struct timecounter *tc);
108 :
109 : struct cfattach tcpcib_ca = {
110 : sizeof(struct tcpcib_softc), tcpcib_match, tcpcib_attach,
111 : NULL, tcpcib_activate
112 : };
113 :
114 : /* from arch/<*>/pci/pcib.c */
115 : void pcibattach(struct device *parent, struct device *self, void *aux);
116 :
117 : const struct pci_matchid tcpcib_devices[] = {
118 : { PCI_VENDOR_INTEL, PCI_PRODUCT_INTEL_E600_LPC }
119 : };
120 :
121 : static __inline void
122 0 : tcpcib_wdt_unlock(struct tcpcib_softc *sc)
123 : {
124 : /* Register unlocking sequence */
125 0 : bus_space_write_1(sc->sc_wdt_iot, sc->sc_wdt_ioh, E600_WDT_RR0, 0x80);
126 0 : bus_space_write_1(sc->sc_wdt_iot, sc->sc_wdt_ioh, E600_WDT_RR0, 0x86);
127 0 : }
128 :
129 : void
130 0 : tcpcib_wdt_init(struct tcpcib_softc *sc, int period)
131 : {
132 : u_int32_t preload;
133 :
134 : /* Set new timeout */
135 0 : preload = (period * 33000000) >> 15;
136 0 : preload--;
137 :
138 : /*
139 : * Set watchdog to perform a cold reset toggling the GPIO pin and the
140 : * prescaler set to 1ms-10m resolution
141 : */
142 0 : bus_space_write_1(sc->sc_wdt_iot, sc->sc_wdt_ioh, E600_WDT_WDTCR,
143 : E600_WDT_WDTCR_ENABLE);
144 0 : tcpcib_wdt_unlock(sc);
145 0 : bus_space_write_4(sc->sc_wdt_iot, sc->sc_wdt_ioh, E600_WDT_PV1, 0);
146 0 : tcpcib_wdt_unlock(sc);
147 0 : bus_space_write_4(sc->sc_wdt_iot, sc->sc_wdt_ioh, E600_WDT_PV2,
148 : preload);
149 0 : tcpcib_wdt_unlock(sc);
150 0 : bus_space_write_1(sc->sc_wdt_iot, sc->sc_wdt_ioh, E600_WDT_RR1,
151 : E600_WDT_RR1_RELOAD);
152 0 : }
153 :
154 : void
155 0 : tcpcib_wdt_start(struct tcpcib_softc *sc)
156 : {
157 : /* Enable watchdog */
158 0 : bus_space_write_1(sc->sc_wdt_iot, sc->sc_wdt_ioh, E600_WDT_WDTLR,
159 : E600_WDT_WDTLR_ENABLE);
160 0 : }
161 :
162 : void
163 0 : tcpcib_wdt_stop(struct tcpcib_softc *sc)
164 : {
165 : /* Disable watchdog, with a reload before for safety */
166 0 : tcpcib_wdt_unlock(sc);
167 0 : bus_space_write_1(sc->sc_wdt_iot, sc->sc_wdt_ioh, E600_WDT_RR1,
168 : E600_WDT_RR1_RELOAD);
169 0 : bus_space_write_1(sc->sc_wdt_iot, sc->sc_wdt_ioh, E600_WDT_WDTLR, 0);
170 0 : }
171 :
172 : int
173 0 : tcpcib_match(struct device *parent, void *match, void *aux)
174 : {
175 0 : if (pci_matchbyid((struct pci_attach_args *)aux, tcpcib_devices,
176 : sizeof(tcpcib_devices) / sizeof(tcpcib_devices[0])))
177 0 : return (2);
178 :
179 0 : return (0);
180 0 : }
181 :
182 : void
183 0 : tcpcib_attach(struct device *parent, struct device *self, void *aux)
184 : {
185 0 : struct tcpcib_softc *sc = (struct tcpcib_softc *)self;
186 0 : struct pci_attach_args *pa = aux;
187 0 : struct timecounter *tc = &sc->sc_hpet_timecounter;
188 : u_int32_t reg, wdtbase;
189 :
190 0 : sc->sc_active = 0;
191 :
192 : /* High Precision Event Timer */
193 0 : sc->sc_hpet_iot = pa->pa_memt;
194 0 : if (bus_space_map(sc->sc_hpet_iot, E600_HPET_BASE, E600_HPET_SIZE, 0,
195 0 : &sc->sc_hpet_ioh) == 0) {
196 0 : tc->tc_get_timecount = tcpcib_hpet_get_timecount;
197 : /* XXX 64-bit counter is not supported! */
198 0 : tc->tc_counter_mask = 0xffffffff;
199 :
200 0 : reg = bus_space_read_4(sc->sc_hpet_iot, sc->sc_hpet_ioh,
201 : E600_HPET_PERIOD);
202 : /* femtosecs -> Hz */
203 0 : tc->tc_frequency = 1000000000000000ULL / reg;
204 :
205 0 : tc->tc_name = sc->sc_dev.dv_xname;
206 0 : tc->tc_quality = 2000;
207 0 : tc->tc_priv = sc;
208 0 : tc_init(tc);
209 :
210 : /* Enable counting */
211 0 : bus_space_write_4(sc->sc_hpet_iot, sc->sc_hpet_ioh,
212 : E600_HPET_GC, E600_HPET_GC_ENABLE);
213 :
214 0 : sc->sc_active |= E600_HPET_ACTIVE;
215 :
216 0 : printf(": %llu Hz timer", tc->tc_frequency);
217 0 : }
218 :
219 : /* Map Watchdog I/O space */
220 0 : reg = pci_conf_read(pa->pa_pc, pa->pa_tag, E600_LPC_WDTBA);
221 0 : wdtbase = reg & 0xffff;
222 0 : sc->sc_wdt_iot = pa->pa_iot;
223 0 : if (reg & (1U << 31) && wdtbase) {
224 0 : if (PCI_MAPREG_IO_ADDR(wdtbase) == 0 ||
225 0 : bus_space_map(sc->sc_wdt_iot, PCI_MAPREG_IO_ADDR(wdtbase),
226 0 : E600_WDT_SIZE, 0, &sc->sc_wdt_ioh)) {
227 0 : printf("%c can't map watchdog I/O space",
228 0 : sc->sc_active ? ',' : ':');
229 0 : goto corepcib;
230 : }
231 0 : printf("%c watchdog", sc->sc_active ? ',' : ':');
232 :
233 : /* Check for reboot on timeout */
234 0 : reg = bus_space_read_1(sc->sc_wdt_iot, sc->sc_wdt_ioh,
235 : E600_WDT_RR1);
236 0 : if (reg & E600_WDT_RR1_TIMEOUT) {
237 0 : printf(", reboot on timeout");
238 :
239 : /* Clear timeout bit */
240 0 : tcpcib_wdt_unlock(sc);
241 0 : bus_space_write_1(sc->sc_wdt_iot, sc->sc_wdt_ioh,
242 : E600_WDT_RR1, E600_WDT_RR1_TIMEOUT);
243 0 : }
244 :
245 : /* Check it's not locked already */
246 0 : reg = bus_space_read_1(sc->sc_wdt_iot, sc->sc_wdt_ioh,
247 : E600_WDT_WDTLR);
248 0 : if (reg & E600_WDT_WDTLR_LOCK) {
249 0 : printf(", locked");
250 0 : goto corepcib;
251 : }
252 :
253 : /* Disable watchdog */
254 0 : tcpcib_wdt_stop(sc);
255 0 : sc->sc_wdt_period = 0;
256 :
257 0 : sc->sc_active |= E600_WDT_ACTIVE;
258 :
259 : /* Register new watchdog */
260 0 : wdog_register(tcpcib_wdt_cb, sc);
261 0 : }
262 :
263 : corepcib:
264 : /* Provide core pcib(4) functionality */
265 0 : pcibattach(parent, self, aux);
266 0 : }
267 :
268 : int
269 0 : tcpcib_activate(struct device *self, int act)
270 : {
271 0 : struct tcpcib_softc *sc = (struct tcpcib_softc *)self;
272 : int rv = 0;
273 :
274 0 : switch (act) {
275 : case DVACT_SUSPEND:
276 0 : rv = config_activate_children(self, act);
277 : /* Watchdog is running, disable it */
278 0 : if (sc->sc_active & E600_WDT_ACTIVE && sc->sc_wdt_period != 0)
279 0 : tcpcib_wdt_stop(sc);
280 : break;
281 : case DVACT_RESUME:
282 0 : if (sc->sc_active & E600_WDT_ACTIVE) {
283 : /*
284 : * Watchdog was running prior to suspend so reenable
285 : * it, otherwise make sure it stays disabled
286 : */
287 0 : if (sc->sc_wdt_period != 0) {
288 0 : tcpcib_wdt_init(sc, sc->sc_wdt_period);
289 0 : tcpcib_wdt_start(sc);
290 0 : } else
291 0 : tcpcib_wdt_stop(sc);
292 : }
293 0 : if (sc->sc_active & E600_HPET_ACTIVE)
294 0 : bus_space_write_4(sc->sc_hpet_iot, sc->sc_hpet_ioh,
295 : E600_HPET_GC, E600_HPET_GC_ENABLE);
296 0 : rv = config_activate_children(self, act);
297 0 : break;
298 : case DVACT_POWERDOWN:
299 0 : if (sc->sc_active & E600_WDT_ACTIVE)
300 0 : wdog_shutdown(self);
301 0 : rv = config_activate_children(self, act);
302 0 : break;
303 : default:
304 0 : rv = config_activate_children(self, act);
305 0 : break;
306 : }
307 0 : return (rv);
308 : }
309 :
310 : int
311 0 : tcpcib_wdt_cb(void *arg, int period)
312 : {
313 0 : struct tcpcib_softc *sc = arg;
314 :
315 0 : if (period == 0) {
316 0 : if (sc->sc_wdt_period != 0)
317 0 : tcpcib_wdt_stop(sc);
318 : } else {
319 : /* 600 seconds is the maximum supported timeout value */
320 0 : if (period > 600)
321 0 : period = 600;
322 0 : if (sc->sc_wdt_period != period)
323 0 : tcpcib_wdt_init(sc, period);
324 0 : if (sc->sc_wdt_period == 0) {
325 0 : tcpcib_wdt_start(sc);
326 0 : } else {
327 : /* Reset timer */
328 0 : tcpcib_wdt_unlock(sc);
329 0 : bus_space_write_1(sc->sc_wdt_iot, sc->sc_wdt_ioh,
330 : E600_WDT_RR1, E600_WDT_RR1_RELOAD);
331 : }
332 : }
333 0 : sc->sc_wdt_period = period;
334 :
335 0 : return (period);
336 : }
337 :
338 : u_int
339 0 : tcpcib_hpet_get_timecount(struct timecounter *tc)
340 : {
341 0 : struct tcpcib_softc *sc = tc->tc_priv;
342 :
343 : /* XXX 64-bit counter is not supported! */
344 0 : return bus_space_read_4(sc->sc_hpet_iot, sc->sc_hpet_ioh,
345 : E600_HPET_MCV);
346 : }
|