1/*	$NetBSD: tcpcib.c,v 1.4 2021/08/07 16:19:08 thorpej Exp $	*/
2/*	$OpenBSD: tcpcib.c,v 1.4 2012/10/17 22:32:01 deraadt Exp $	*/
3
4/*
5 * Copyright (c) 2012 Matt Dainty <matt@bodgit-n-scarper.com>
6 *
7 * Permission to use, copy, modify, and distribute this software for any
8 * purpose with or without fee is hereby granted, provided that the above
9 * copyright notice and this permission notice appear in all copies.
10 *
11 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
12 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
13 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
14 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
15 * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER IN
16 * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
17 * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
18 */
19
20/*
21 * Intel Atom E600 series LPC bridge also containing HPET and watchdog
22 */
23
24#include <sys/cdefs.h>
25__KERNEL_RCSID(0, "$NetBSD: tcpcib.c,v 1.4 2021/08/07 16:19:08 thorpej Exp $");
26
27#include <sys/param.h>
28#include <sys/systm.h>
29#include <sys/device.h>
30#include <sys/timetc.h>
31#include <sys/bus.h>
32
33#include <dev/pci/pcireg.h>
34#include <dev/pci/pcivar.h>
35#include <dev/pci/pcidevs.h>
36
37#include <dev/ic/i82801lpcvar.h>
38
39#include <dev/sysmon/sysmonvar.h>
40
41#include "pcibvar.h"
42
43#define	E600_LPC_SMBA		0x40		/* SMBus Base Address */
44#define	E600_LPC_GBA		0x44		/* GPIO Base Address */
45#define	E600_LPC_WDTBA		0x84		/* WDT Base Address */
46
47#define	E600_WDT_SIZE		64		/* I/O region size */
48#define	E600_WDT_PV1		0x00		/* Preload Value 1 Register */
49#define	E600_WDT_PV2		0x04		/* Preload Value 2 Register */
50#define	E600_WDT_RR0		0x0c		/* Reload Register 0 */
51#define	E600_WDT_RR1		0x0d		/* Reload Register 1 */
52#define	E600_WDT_RR1_RELOAD	(1 << 0)	/* WDT Reload Flag */
53#define	E600_WDT_RR1_TIMEOUT	(1 << 1)	/* WDT Timeout Flag */
54#define	E600_WDT_WDTCR		0x10		/* WDT Configuration Register */
55#define	E600_WDT_WDTCR_PRE	(1 << 2)	/* WDT Prescalar Select */
56#define	E600_WDT_WDTCR_RESET	(1 << 3)	/* WDT Reset Select */
57#define	E600_WDT_WDTCR_ENABLE	(1 << 4)	/* WDT Reset Enable */
58#define	E600_WDT_WDTCR_TIMEOUT	(1 << 5)	/* WDT Timeout Output Enable */
59#define	E600_WDT_DCR		0x14		/* Down Counter Register */
60#define	E600_WDT_WDTLR		0x18		/* WDT Lock Register */
61#define	E600_WDT_WDTLR_LOCK	(1 << 0)	/* Watchdog Timer Lock */
62#define	E600_WDT_WDTLR_ENABLE	(1 << 1)	/* Watchdog Timer Enable */
63#define	E600_WDT_WDTLR_TIMEOUT	(1 << 2)	/* WDT Timeout Configuration */
64
65#define	E600_HPET_BASE		0xfed00000	/* HPET register base */
66#define	E600_HPET_SIZE		0x00000400	/* HPET register size */
67
68#define	E600_HPET_GCID		0x000		/* Capabilities and ID */
69#define	E600_HPET_GCID_WIDTH	(1 << 13)	/* Counter Size */
70#define	E600_HPET_PERIOD	0x004		/* Counter Tick Period */
71#define	E600_HPET_GC		0x010		/* General Configuration */
72#define	E600_HPET_GC_ENABLE	(1 << 0)	/* Overall Enable */
73#define	E600_HPET_GIS		0x020		/* General Interrupt Status */
74#define	E600_HPET_MCV		0x0f0		/* Main Counter Value */
75#define	E600_HPET_T0C		0x100		/* Timer 0 Config and Capabilities */
76#define	E600_HPET_T0CV		0x108		/* Timer 0 Comparator Value */
77#define	E600_HPET_T1C		0x120		/* Timer 1 Config and Capabilities */
78#define	E600_HPET_T1CV		0x128		/* Timer 1 Comparator Value */
79#define	E600_HPET_T2C		0x140		/* Timer 2 Config and Capabilities */
80#define	E600_HPET_T2CV		0x148		/* Timer 2 Comparator Value */
81
82struct tcpcib_softc {
83	/* we call pcibattach() which assumes this starts like this: */
84	struct pcib_softc	sc_pcib;
85
86	/* Watchdog interface */
87	bool sc_wdt_valid;
88	struct sysmon_wdog sc_wdt_smw;
89	bus_space_tag_t sc_wdt_iot;
90	bus_space_handle_t sc_wdt_ioh;
91
92	/* High Precision Event Timer */
93	device_t sc_hpetbus;
94	bus_space_tag_t sc_hpet_memt;
95};
96
97static int	tcpcib_match(device_t, cfdata_t, void *);
98static void	tcpcib_attach(device_t, device_t, void *);
99static int	tcpcib_detach(device_t, int);
100static int	tcpcib_rescan(device_t, const char *, const int *);
101static void	tcpcib_childdet(device_t, device_t);
102static bool	tcpcib_suspend(device_t, const pmf_qual_t *);
103static bool	tcpcib_resume(device_t, const pmf_qual_t *);
104
105static int	tcpcib_wdt_setmode(struct sysmon_wdog *);
106static int	tcpcib_wdt_tickle(struct sysmon_wdog *);
107static void	tcpcib_wdt_init(struct tcpcib_softc *, int);
108static void	tcpcib_wdt_start(struct tcpcib_softc *);
109static void	tcpcib_wdt_stop(struct tcpcib_softc *);
110
111CFATTACH_DECL2_NEW(tcpcib, sizeof(struct tcpcib_softc),
112    tcpcib_match, tcpcib_attach, tcpcib_detach, NULL,
113    tcpcib_rescan, tcpcib_childdet);
114
115static struct tcpcib_device {
116	pcireg_t vendor, product;
117} tcpcib_devices[] = {
118	{ PCI_VENDOR_INTEL,	PCI_PRODUCT_INTEL_E600_LPC }
119};
120
121static void
122tcpcib_wdt_unlock(struct tcpcib_softc *sc)
123{
124	/* Register unlocking sequence */
125	bus_space_write_1(sc->sc_wdt_iot, sc->sc_wdt_ioh, E600_WDT_RR0, 0x80);
126	bus_space_write_1(sc->sc_wdt_iot, sc->sc_wdt_ioh, E600_WDT_RR0, 0x86);
127}
128
129static void
130tcpcib_wdt_init(struct tcpcib_softc *sc, int period)
131{
132	uint32_t preload;
133
134	/* Set new timeout */
135	preload = (period * 33000000) >> 15;
136	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	bus_space_write_1(sc->sc_wdt_iot, sc->sc_wdt_ioh, E600_WDT_WDTCR,
143	    E600_WDT_WDTCR_ENABLE);
144	tcpcib_wdt_unlock(sc);
145	bus_space_write_4(sc->sc_wdt_iot, sc->sc_wdt_ioh, E600_WDT_PV1, 0);
146	tcpcib_wdt_unlock(sc);
147	bus_space_write_4(sc->sc_wdt_iot, sc->sc_wdt_ioh, E600_WDT_PV2,
148	    preload);
149	tcpcib_wdt_unlock(sc);
150	bus_space_write_1(sc->sc_wdt_iot, sc->sc_wdt_ioh, E600_WDT_RR1,
151	    E600_WDT_RR1_RELOAD);
152}
153
154static void
155tcpcib_wdt_start(struct tcpcib_softc *sc)
156{
157	/* Enable watchdog */
158	bus_space_write_1(sc->sc_wdt_iot, sc->sc_wdt_ioh, E600_WDT_WDTLR,
159	    E600_WDT_WDTLR_ENABLE);
160}
161
162static void
163tcpcib_wdt_stop(struct tcpcib_softc *sc)
164{
165	/* Disable watchdog, with a reload before for safety */
166	tcpcib_wdt_unlock(sc);
167	bus_space_write_1(sc->sc_wdt_iot, sc->sc_wdt_ioh, E600_WDT_RR1,
168	    E600_WDT_RR1_RELOAD);
169	bus_space_write_1(sc->sc_wdt_iot, sc->sc_wdt_ioh, E600_WDT_WDTLR, 0);
170}
171
172static int
173tcpcib_match(device_t parent, cfdata_t match, void *aux)
174{
175	struct pci_attach_args *pa = aux;
176	unsigned int n;
177
178	if (PCI_CLASS(pa->pa_class) != PCI_CLASS_BRIDGE ||
179	    PCI_SUBCLASS(pa->pa_class) != PCI_SUBCLASS_BRIDGE_ISA)
180		return 0;
181
182	for (n = 0; n < __arraycount(tcpcib_devices); n++) {
183		if (PCI_VENDOR(pa->pa_id) == tcpcib_devices[n].vendor &&
184		    PCI_PRODUCT(pa->pa_id) == tcpcib_devices[n].product)
185			return 10;	/* beat pcib(4) */
186	}
187
188	return 0;
189}
190
191static void
192tcpcib_attach(device_t parent, device_t self, void *aux)
193{
194	struct tcpcib_softc *sc = device_private(self);
195	struct pci_attach_args *pa = aux;
196	uint32_t reg, wdtbase;
197
198	pmf_device_register(self, tcpcib_suspend, tcpcib_resume);
199
200	/* Provide core pcib(4) functionality */
201	pcibattach(parent, self, aux);
202
203	/* High Precision Event Timer */
204	sc->sc_hpet_memt = pa->pa_memt;
205	tcpcib_rescan(self, "hpetichbus", NULL);
206
207	/* Map Watchdog I/O space */
208	reg = pci_conf_read(pa->pa_pc, pa->pa_tag, E600_LPC_WDTBA);
209	wdtbase = reg & 0xffff;
210	sc->sc_wdt_iot = pa->pa_iot;
211	if (reg & (1 << 31) && wdtbase) {
212		if (PCI_MAPREG_IO_ADDR(wdtbase) == 0 ||
213		    bus_space_map(sc->sc_wdt_iot, PCI_MAPREG_IO_ADDR(wdtbase),
214		    E600_WDT_SIZE, 0, &sc->sc_wdt_ioh)) {
215			aprint_error_dev(self,
216			    "can't map watchdog I/O space\n");
217			return;
218		}
219		aprint_normal_dev(self, "watchdog");
220
221		/* Check for reboot on timeout */
222		reg = bus_space_read_1(sc->sc_wdt_iot, sc->sc_wdt_ioh,
223		    E600_WDT_RR1);
224		if (reg & E600_WDT_RR1_TIMEOUT) {
225			aprint_normal(", reboot on timeout");
226
227			/* Clear timeout bit */
228			tcpcib_wdt_unlock(sc);
229			bus_space_write_1(sc->sc_wdt_iot, sc->sc_wdt_ioh,
230			    E600_WDT_RR1, E600_WDT_RR1_TIMEOUT);
231		}
232
233		/* Check it's not locked already */
234		reg = bus_space_read_1(sc->sc_wdt_iot, sc->sc_wdt_ioh,
235		    E600_WDT_WDTLR);
236		if (reg & E600_WDT_WDTLR_LOCK) {
237			aprint_error(", locked\n");
238			return;
239		}
240
241		/* Disable watchdog */
242		tcpcib_wdt_stop(sc);
243
244		/* Register new watchdog */
245		sc->sc_wdt_smw.smw_name = device_xname(self);
246		sc->sc_wdt_smw.smw_cookie = sc;
247		sc->sc_wdt_smw.smw_setmode = tcpcib_wdt_setmode;
248		sc->sc_wdt_smw.smw_tickle = tcpcib_wdt_tickle;
249		sc->sc_wdt_smw.smw_period = 600; /* seconds */
250		if (sysmon_wdog_register(&sc->sc_wdt_smw)) {
251			aprint_error(", unable to register wdog timer\n");
252			return;
253		}
254
255		sc->sc_wdt_valid = true;
256		aprint_normal("\n");
257	}
258
259}
260
261static int
262tcpcib_detach(device_t self, int flags)
263{
264	return pcibdetach(self, flags);
265}
266
267static int
268tcpcib_rescan(device_t self, const char *ifattr, const int *locators)
269{
270	struct tcpcib_softc *sc = device_private(self);
271
272	if (ifattr_match(ifattr, "hpetichbus") && sc->sc_hpetbus == NULL) {
273		struct lpcib_hpet_attach_args hpet_arg;
274		hpet_arg.hpet_mem_t = sc->sc_hpet_memt;
275		hpet_arg.hpet_reg = E600_HPET_BASE;
276		sc->sc_hpetbus =
277		    config_found(self, &hpet_arg, NULL,
278				 CFARGS(.iattr = "hpetichbus"));
279	}
280
281	return pcibrescan(self, ifattr, locators);
282}
283
284static void
285tcpcib_childdet(device_t self, device_t child)
286{
287	struct tcpcib_softc *sc = device_private(self);
288
289	if (sc->sc_hpetbus == child) {
290		sc->sc_hpetbus = NULL;
291		return;
292	}
293
294	pcibchilddet(self, child);
295}
296
297static bool
298tcpcib_suspend(device_t self, const pmf_qual_t *qual)
299{
300	struct tcpcib_softc *sc = device_private(self);
301
302	if (sc->sc_wdt_valid)
303		tcpcib_wdt_stop(sc);
304
305	return true;
306}
307
308static bool
309tcpcib_resume(device_t self, const pmf_qual_t *qual)
310{
311	struct tcpcib_softc *sc = device_private(self);
312	struct sysmon_wdog *smw = &sc->sc_wdt_smw;
313
314	if (sc->sc_wdt_valid) {
315		if ((smw->smw_mode & WDOG_MODE_MASK) != WDOG_MODE_DISARMED &&
316		    smw->smw_period > 0) {
317			tcpcib_wdt_init(sc, smw->smw_period);
318			tcpcib_wdt_start(sc);
319		} else {
320			tcpcib_wdt_stop(sc);
321		}
322	}
323
324	return true;
325}
326
327static int
328tcpcib_wdt_setmode(struct sysmon_wdog *smw)
329{
330	struct tcpcib_softc *sc = smw->smw_cookie;
331	unsigned int period;
332
333	period = smw->smw_period;
334	if ((smw->smw_mode & WDOG_MODE_MASK) == WDOG_MODE_DISARMED) {
335		tcpcib_wdt_stop(sc);
336	} else {
337		/* 600 seconds is the maximum supported timeout value */
338		if (period > 600)
339			return EINVAL;
340
341		tcpcib_wdt_stop(sc);
342		tcpcib_wdt_init(sc, period);
343		tcpcib_wdt_start(sc);
344		tcpcib_wdt_tickle(smw);
345	}
346
347	return 0;
348}
349
350static int
351tcpcib_wdt_tickle(struct sysmon_wdog *smw)
352{
353	struct tcpcib_softc *sc = smw->smw_cookie;
354
355	/* Reset timer */
356	tcpcib_wdt_unlock(sc);
357	bus_space_write_1(sc->sc_wdt_iot, sc->sc_wdt_ioh,
358	    E600_WDT_RR1, E600_WDT_RR1_RELOAD);
359
360	return 0;
361}
362