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