1*2b74ff5fSShengYi Hung /*
2*2b74ff5fSShengYi Hung * Copyright (c) 2025 The FreeBSD Foundation
3*2b74ff5fSShengYi Hung *
4*2b74ff5fSShengYi Hung * SPDX-License-Identifier: BSD-2-Clause
5*2b74ff5fSShengYi Hung */
6*2b74ff5fSShengYi Hung
7*2b74ff5fSShengYi Hung /*
8*2b74ff5fSShengYi Hung * Reference: Intel 6300ESB Controller Hub Datasheet Section 16
9*2b74ff5fSShengYi Hung */
10*2b74ff5fSShengYi Hung
11*2b74ff5fSShengYi Hung #include <sys/param.h>
12*2b74ff5fSShengYi Hung #include <sys/eventhandler.h>
13*2b74ff5fSShengYi Hung #include <sys/kernel.h>
14*2b74ff5fSShengYi Hung #include <sys/module.h>
15*2b74ff5fSShengYi Hung #include <sys/sysctl.h>
16*2b74ff5fSShengYi Hung #include <sys/errno.h>
17*2b74ff5fSShengYi Hung #include <sys/systm.h>
18*2b74ff5fSShengYi Hung #include <sys/bus.h>
19*2b74ff5fSShengYi Hung #include <machine/bus.h>
20*2b74ff5fSShengYi Hung #include <sys/rman.h>
21*2b74ff5fSShengYi Hung #include <machine/resource.h>
22*2b74ff5fSShengYi Hung #include <sys/watchdog.h>
23*2b74ff5fSShengYi Hung
24*2b74ff5fSShengYi Hung #include <dev/pci/pcireg.h>
25*2b74ff5fSShengYi Hung
26*2b74ff5fSShengYi Hung #include <dev/ichwd/ichwd.h>
27*2b74ff5fSShengYi Hung #include <dev/ichwd/i6300esbwd.h>
28*2b74ff5fSShengYi Hung
29*2b74ff5fSShengYi Hung #include <x86/pci_cfgreg.h>
30*2b74ff5fSShengYi Hung #include <dev/pci/pcivar.h>
31*2b74ff5fSShengYi Hung #include <dev/pci/pci_private.h>
32*2b74ff5fSShengYi Hung
33*2b74ff5fSShengYi Hung struct i6300esbwd_softc {
34*2b74ff5fSShengYi Hung device_t dev;
35*2b74ff5fSShengYi Hung int res_id;
36*2b74ff5fSShengYi Hung struct resource *res;
37*2b74ff5fSShengYi Hung eventhandler_tag ev_tag;
38*2b74ff5fSShengYi Hung bool locked;
39*2b74ff5fSShengYi Hung };
40*2b74ff5fSShengYi Hung
41*2b74ff5fSShengYi Hung static const struct i6300esbwd_pci_id {
42*2b74ff5fSShengYi Hung uint16_t id;
43*2b74ff5fSShengYi Hung const char *name;
44*2b74ff5fSShengYi Hung } i6300esbwd_pci_devices[] = {
45*2b74ff5fSShengYi Hung { DEVICEID_6300ESB_2, "6300ESB Watchdog Timer" },
46*2b74ff5fSShengYi Hung };
47*2b74ff5fSShengYi Hung
48*2b74ff5fSShengYi Hung static uint16_t
i6300esbwd_cfg_read(struct i6300esbwd_softc * sc)49*2b74ff5fSShengYi Hung i6300esbwd_cfg_read(struct i6300esbwd_softc *sc)
50*2b74ff5fSShengYi Hung {
51*2b74ff5fSShengYi Hung return (pci_read_config(sc->dev, WDT_CONFIG_REG, 2));
52*2b74ff5fSShengYi Hung }
53*2b74ff5fSShengYi Hung
54*2b74ff5fSShengYi Hung static void
i6300esbwd_cfg_write(struct i6300esbwd_softc * sc,uint16_t val)55*2b74ff5fSShengYi Hung i6300esbwd_cfg_write(struct i6300esbwd_softc *sc, uint16_t val)
56*2b74ff5fSShengYi Hung {
57*2b74ff5fSShengYi Hung pci_write_config(sc->dev, WDT_CONFIG_REG, val, 2);
58*2b74ff5fSShengYi Hung }
59*2b74ff5fSShengYi Hung
60*2b74ff5fSShengYi Hung static uint8_t
i6300esbwd_lock_read(struct i6300esbwd_softc * sc)61*2b74ff5fSShengYi Hung i6300esbwd_lock_read(struct i6300esbwd_softc *sc)
62*2b74ff5fSShengYi Hung {
63*2b74ff5fSShengYi Hung return (pci_read_config(sc->dev, WDT_LOCK_REG, 1));
64*2b74ff5fSShengYi Hung }
65*2b74ff5fSShengYi Hung
66*2b74ff5fSShengYi Hung static void
i6300esbwd_lock_write(struct i6300esbwd_softc * sc,uint8_t val)67*2b74ff5fSShengYi Hung i6300esbwd_lock_write(struct i6300esbwd_softc *sc, uint8_t val)
68*2b74ff5fSShengYi Hung {
69*2b74ff5fSShengYi Hung pci_write_config(sc->dev, WDT_LOCK_REG, val, 1);
70*2b74ff5fSShengYi Hung }
71*2b74ff5fSShengYi Hung
72*2b74ff5fSShengYi Hung /*
73*2b74ff5fSShengYi Hung * According to Intel 6300ESB I/O Controller Hub Datasheet 16.5.2,
74*2b74ff5fSShengYi Hung * the resource should be unlocked before modifing any registers.
75*2b74ff5fSShengYi Hung * The way to unlock is by write 0x80, 0x86 to the reload register.
76*2b74ff5fSShengYi Hung */
77*2b74ff5fSShengYi Hung static void
i6300esbwd_unlock_res(struct i6300esbwd_softc * sc)78*2b74ff5fSShengYi Hung i6300esbwd_unlock_res(struct i6300esbwd_softc *sc)
79*2b74ff5fSShengYi Hung {
80*2b74ff5fSShengYi Hung bus_write_2(sc->res, WDT_RELOAD_REG, WDT_UNLOCK_SEQ_1_VAL);
81*2b74ff5fSShengYi Hung bus_write_2(sc->res, WDT_RELOAD_REG, WDT_UNLOCK_SEQ_2_VAL);
82*2b74ff5fSShengYi Hung }
83*2b74ff5fSShengYi Hung
84*2b74ff5fSShengYi Hung static int
i6300esbwd_sysctl_locked(SYSCTL_HANDLER_ARGS)85*2b74ff5fSShengYi Hung i6300esbwd_sysctl_locked(SYSCTL_HANDLER_ARGS)
86*2b74ff5fSShengYi Hung {
87*2b74ff5fSShengYi Hung struct i6300esbwd_softc *sc = (struct i6300esbwd_softc *)arg1;
88*2b74ff5fSShengYi Hung int error;
89*2b74ff5fSShengYi Hung int result;
90*2b74ff5fSShengYi Hung
91*2b74ff5fSShengYi Hung result = sc->locked;
92*2b74ff5fSShengYi Hung error = sysctl_handle_int(oidp, &result, 0, req);
93*2b74ff5fSShengYi Hung
94*2b74ff5fSShengYi Hung if (error || !req->newptr)
95*2b74ff5fSShengYi Hung return (error);
96*2b74ff5fSShengYi Hung
97*2b74ff5fSShengYi Hung if (result == 1 && !sc->locked) {
98*2b74ff5fSShengYi Hung i6300esbwd_lock_write(sc, i6300esbwd_lock_read(sc) | WDT_LOCK);
99*2b74ff5fSShengYi Hung sc->locked = true;
100*2b74ff5fSShengYi Hung }
101*2b74ff5fSShengYi Hung
102*2b74ff5fSShengYi Hung return (0);
103*2b74ff5fSShengYi Hung }
104*2b74ff5fSShengYi Hung
105*2b74ff5fSShengYi Hung static void
i6300esbwd_event(void * arg,unsigned int cmd,int * error)106*2b74ff5fSShengYi Hung i6300esbwd_event(void *arg, unsigned int cmd, int *error)
107*2b74ff5fSShengYi Hung {
108*2b74ff5fSShengYi Hung struct i6300esbwd_softc *sc = arg;
109*2b74ff5fSShengYi Hung uint32_t timeout;
110*2b74ff5fSShengYi Hung uint16_t regval;
111*2b74ff5fSShengYi Hung
112*2b74ff5fSShengYi Hung cmd &= WD_INTERVAL;
113*2b74ff5fSShengYi Hung if (cmd != 0 &&
114*2b74ff5fSShengYi Hung (cmd < WD_TO_1MS || (cmd - WD_TO_1MS) >= WDT_PRELOAD_BIT)) {
115*2b74ff5fSShengYi Hung *error = EINVAL;
116*2b74ff5fSShengYi Hung return;
117*2b74ff5fSShengYi Hung }
118*2b74ff5fSShengYi Hung timeout = 1 << (cmd - WD_TO_1MS);
119*2b74ff5fSShengYi Hung
120*2b74ff5fSShengYi Hung /* reset the timer to prevent timeout a timeout is about to occur */
121*2b74ff5fSShengYi Hung i6300esbwd_unlock_res(sc);
122*2b74ff5fSShengYi Hung bus_write_2(sc->res, WDT_RELOAD_REG, WDT_RELOAD);
123*2b74ff5fSShengYi Hung
124*2b74ff5fSShengYi Hung if (!cmd) {
125*2b74ff5fSShengYi Hung /*
126*2b74ff5fSShengYi Hung * when the lock is enabled, we are unable to overwrite LOCK
127*2b74ff5fSShengYi Hung * register
128*2b74ff5fSShengYi Hung */
129*2b74ff5fSShengYi Hung if (sc->locked)
130*2b74ff5fSShengYi Hung *error = EPERM;
131*2b74ff5fSShengYi Hung else
132*2b74ff5fSShengYi Hung i6300esbwd_lock_write(sc,
133*2b74ff5fSShengYi Hung i6300esbwd_lock_read(sc) & ~WDT_ENABLE);
134*2b74ff5fSShengYi Hung return;
135*2b74ff5fSShengYi Hung }
136*2b74ff5fSShengYi Hung
137*2b74ff5fSShengYi Hung i6300esbwd_unlock_res(sc);
138*2b74ff5fSShengYi Hung bus_write_4(sc->res, WDT_PRELOAD_1_REG, timeout);
139*2b74ff5fSShengYi Hung
140*2b74ff5fSShengYi Hung i6300esbwd_unlock_res(sc);
141*2b74ff5fSShengYi Hung bus_write_4(sc->res, WDT_PRELOAD_2_REG, timeout);
142*2b74ff5fSShengYi Hung
143*2b74ff5fSShengYi Hung i6300esbwd_unlock_res(sc);
144*2b74ff5fSShengYi Hung bus_write_2(sc->res, WDT_RELOAD_REG, WDT_RELOAD);
145*2b74ff5fSShengYi Hung
146*2b74ff5fSShengYi Hung if (!sc->locked) {
147*2b74ff5fSShengYi Hung i6300esbwd_lock_write(sc, WDT_ENABLE);
148*2b74ff5fSShengYi Hung regval = i6300esbwd_lock_read(sc);
149*2b74ff5fSShengYi Hung sc->locked = regval & WDT_LOCK;
150*2b74ff5fSShengYi Hung }
151*2b74ff5fSShengYi Hung }
152*2b74ff5fSShengYi Hung
153*2b74ff5fSShengYi Hung static int
i6300esbwd_probe(device_t dev)154*2b74ff5fSShengYi Hung i6300esbwd_probe(device_t dev)
155*2b74ff5fSShengYi Hung {
156*2b74ff5fSShengYi Hung const struct i6300esbwd_pci_id *pci_id;
157*2b74ff5fSShengYi Hung uint16_t pci_dev_id;
158*2b74ff5fSShengYi Hung int err = ENXIO;
159*2b74ff5fSShengYi Hung
160*2b74ff5fSShengYi Hung if (pci_get_vendor(dev) != VENDORID_INTEL)
161*2b74ff5fSShengYi Hung goto end;
162*2b74ff5fSShengYi Hung
163*2b74ff5fSShengYi Hung pci_dev_id = pci_get_device(dev);
164*2b74ff5fSShengYi Hung for (pci_id = i6300esbwd_pci_devices;
165*2b74ff5fSShengYi Hung pci_id < i6300esbwd_pci_devices + nitems(i6300esbwd_pci_devices);
166*2b74ff5fSShengYi Hung ++pci_id) {
167*2b74ff5fSShengYi Hung if (pci_id->id == pci_dev_id) {
168*2b74ff5fSShengYi Hung device_set_desc(dev, pci_id->name);
169*2b74ff5fSShengYi Hung err = BUS_PROBE_DEFAULT;
170*2b74ff5fSShengYi Hung break;
171*2b74ff5fSShengYi Hung }
172*2b74ff5fSShengYi Hung }
173*2b74ff5fSShengYi Hung
174*2b74ff5fSShengYi Hung end:
175*2b74ff5fSShengYi Hung return (err);
176*2b74ff5fSShengYi Hung }
177*2b74ff5fSShengYi Hung
178*2b74ff5fSShengYi Hung static int
i6300esbwd_attach(device_t dev)179*2b74ff5fSShengYi Hung i6300esbwd_attach(device_t dev)
180*2b74ff5fSShengYi Hung {
181*2b74ff5fSShengYi Hung struct i6300esbwd_softc *sc = device_get_softc(dev);
182*2b74ff5fSShengYi Hung uint16_t regval;
183*2b74ff5fSShengYi Hung
184*2b74ff5fSShengYi Hung sc->dev = dev;
185*2b74ff5fSShengYi Hung sc->res_id = PCIR_BAR(0);
186*2b74ff5fSShengYi Hung sc->res = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &sc->res_id,
187*2b74ff5fSShengYi Hung RF_ACTIVE);
188*2b74ff5fSShengYi Hung if (sc->res == NULL) {
189*2b74ff5fSShengYi Hung device_printf(dev, "unable to map memory region\n");
190*2b74ff5fSShengYi Hung return (ENXIO);
191*2b74ff5fSShengYi Hung }
192*2b74ff5fSShengYi Hung
193*2b74ff5fSShengYi Hung i6300esbwd_cfg_write(sc, WDT_INT_TYPE_DISABLED_VAL);
194*2b74ff5fSShengYi Hung regval = i6300esbwd_lock_read(sc);
195*2b74ff5fSShengYi Hung if (regval & WDT_LOCK)
196*2b74ff5fSShengYi Hung sc->locked = true;
197*2b74ff5fSShengYi Hung else {
198*2b74ff5fSShengYi Hung sc->locked = false;
199*2b74ff5fSShengYi Hung i6300esbwd_lock_write(sc, WDT_TOUT_CNF_WT_MODE);
200*2b74ff5fSShengYi Hung }
201*2b74ff5fSShengYi Hung
202*2b74ff5fSShengYi Hung i6300esbwd_unlock_res(sc);
203*2b74ff5fSShengYi Hung bus_write_2(sc->res, WDT_RELOAD_REG, WDT_RELOAD | WDT_TIMEOUT);
204*2b74ff5fSShengYi Hung
205*2b74ff5fSShengYi Hung sc->ev_tag = EVENTHANDLER_REGISTER(watchdog_list, i6300esbwd_event, sc,
206*2b74ff5fSShengYi Hung 0);
207*2b74ff5fSShengYi Hung
208*2b74ff5fSShengYi Hung SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev),
209*2b74ff5fSShengYi Hung SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "locked",
210*2b74ff5fSShengYi Hung CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_NEEDGIANT, sc, 0,
211*2b74ff5fSShengYi Hung i6300esbwd_sysctl_locked, "I",
212*2b74ff5fSShengYi Hung "Lock the timer so that we cannot disable it");
213*2b74ff5fSShengYi Hung
214*2b74ff5fSShengYi Hung return (0);
215*2b74ff5fSShengYi Hung }
216*2b74ff5fSShengYi Hung
217*2b74ff5fSShengYi Hung static int
i6300esbwd_detach(device_t dev)218*2b74ff5fSShengYi Hung i6300esbwd_detach(device_t dev)
219*2b74ff5fSShengYi Hung {
220*2b74ff5fSShengYi Hung struct i6300esbwd_softc *sc = device_get_softc(dev);
221*2b74ff5fSShengYi Hung
222*2b74ff5fSShengYi Hung if (sc->ev_tag)
223*2b74ff5fSShengYi Hung EVENTHANDLER_DEREGISTER(watchdog_list, sc->ev_tag);
224*2b74ff5fSShengYi Hung
225*2b74ff5fSShengYi Hung if (sc->res)
226*2b74ff5fSShengYi Hung bus_release_resource(dev, SYS_RES_MEMORY, sc->res_id, sc->res);
227*2b74ff5fSShengYi Hung
228*2b74ff5fSShengYi Hung return (0);
229*2b74ff5fSShengYi Hung }
230*2b74ff5fSShengYi Hung
231*2b74ff5fSShengYi Hung static device_method_t i6300esbwd_methods[] = {
232*2b74ff5fSShengYi Hung DEVMETHOD(device_probe, i6300esbwd_probe),
233*2b74ff5fSShengYi Hung DEVMETHOD(device_attach, i6300esbwd_attach),
234*2b74ff5fSShengYi Hung DEVMETHOD(device_detach, i6300esbwd_detach),
235*2b74ff5fSShengYi Hung DEVMETHOD(device_shutdown, i6300esbwd_detach),
236*2b74ff5fSShengYi Hung DEVMETHOD_END
237*2b74ff5fSShengYi Hung };
238*2b74ff5fSShengYi Hung
239*2b74ff5fSShengYi Hung static driver_t i6300esbwd_driver = {
240*2b74ff5fSShengYi Hung "i6300esbwd",
241*2b74ff5fSShengYi Hung i6300esbwd_methods,
242*2b74ff5fSShengYi Hung sizeof(struct i6300esbwd_softc),
243*2b74ff5fSShengYi Hung };
244*2b74ff5fSShengYi Hung
245*2b74ff5fSShengYi Hung DRIVER_MODULE(i6300esbwd, pci, i6300esbwd_driver, NULL, NULL);
246