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 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 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 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 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 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 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 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 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 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 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