xref: /freebsd/sys/dev/ichwd/i6300esbwd.c (revision 2b74ff5fceb6623f6114ce39baee9f6ec5f79277)
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
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 		*error = EINVAL;
116 		return;
117 	}
118 	timeout = 1 << (cmd - WD_TO_1MS);
119 
120 	/* reset the timer to prevent timeout a timeout is about to occur */
121 	i6300esbwd_unlock_res(sc);
122 	bus_write_2(sc->res, WDT_RELOAD_REG, WDT_RELOAD);
123 
124 	if (!cmd) {
125 		/*
126 		 * when the lock is enabled, we are unable to overwrite LOCK
127 		 * register
128 		 */
129 		if (sc->locked)
130 			*error = EPERM;
131 		else
132 			i6300esbwd_lock_write(sc,
133 			    i6300esbwd_lock_read(sc) & ~WDT_ENABLE);
134 		return;
135 	}
136 
137 	i6300esbwd_unlock_res(sc);
138 	bus_write_4(sc->res, WDT_PRELOAD_1_REG, timeout);
139 
140 	i6300esbwd_unlock_res(sc);
141 	bus_write_4(sc->res, WDT_PRELOAD_2_REG, timeout);
142 
143 	i6300esbwd_unlock_res(sc);
144 	bus_write_2(sc->res, WDT_RELOAD_REG, WDT_RELOAD);
145 
146 	if (!sc->locked) {
147 		i6300esbwd_lock_write(sc, WDT_ENABLE);
148 		regval = i6300esbwd_lock_read(sc);
149 		sc->locked = regval & WDT_LOCK;
150 	}
151 }
152 
153 static int
i6300esbwd_probe(device_t dev)154 i6300esbwd_probe(device_t dev)
155 {
156 	const struct i6300esbwd_pci_id *pci_id;
157 	uint16_t pci_dev_id;
158 	int err = ENXIO;
159 
160 	if (pci_get_vendor(dev) != VENDORID_INTEL)
161 		goto end;
162 
163 	pci_dev_id = pci_get_device(dev);
164 	for (pci_id = i6300esbwd_pci_devices;
165 	    pci_id < i6300esbwd_pci_devices + nitems(i6300esbwd_pci_devices);
166 	    ++pci_id) {
167 		if (pci_id->id == pci_dev_id) {
168 			device_set_desc(dev, pci_id->name);
169 			err = BUS_PROBE_DEFAULT;
170 			break;
171 		}
172 	}
173 
174 end:
175 	return (err);
176 }
177 
178 static int
i6300esbwd_attach(device_t dev)179 i6300esbwd_attach(device_t dev)
180 {
181 	struct i6300esbwd_softc *sc = device_get_softc(dev);
182 	uint16_t regval;
183 
184 	sc->dev = dev;
185 	sc->res_id = PCIR_BAR(0);
186 	sc->res = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &sc->res_id,
187 	    RF_ACTIVE);
188 	if (sc->res == NULL) {
189 		device_printf(dev, "unable to map memory region\n");
190 		return (ENXIO);
191 	}
192 
193 	i6300esbwd_cfg_write(sc, WDT_INT_TYPE_DISABLED_VAL);
194 	regval = i6300esbwd_lock_read(sc);
195 	if (regval & WDT_LOCK)
196 		sc->locked = true;
197 	else {
198 		sc->locked = false;
199 		i6300esbwd_lock_write(sc, WDT_TOUT_CNF_WT_MODE);
200 	}
201 
202 	i6300esbwd_unlock_res(sc);
203 	bus_write_2(sc->res, WDT_RELOAD_REG, WDT_RELOAD | WDT_TIMEOUT);
204 
205 	sc->ev_tag = EVENTHANDLER_REGISTER(watchdog_list, i6300esbwd_event, sc,
206 	    0);
207 
208 	SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev),
209 	    SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "locked",
210 	    CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_NEEDGIANT, sc, 0,
211 	    i6300esbwd_sysctl_locked, "I",
212 	    "Lock the timer so that we cannot disable it");
213 
214 	return (0);
215 }
216 
217 static int
i6300esbwd_detach(device_t dev)218 i6300esbwd_detach(device_t dev)
219 {
220 	struct i6300esbwd_softc *sc = device_get_softc(dev);
221 
222 	if (sc->ev_tag)
223 		EVENTHANDLER_DEREGISTER(watchdog_list, sc->ev_tag);
224 
225 	if (sc->res)
226 		bus_release_resource(dev, SYS_RES_MEMORY, sc->res_id, sc->res);
227 
228 	return (0);
229 }
230 
231 static device_method_t i6300esbwd_methods[] = {
232 	DEVMETHOD(device_probe, i6300esbwd_probe),
233 	DEVMETHOD(device_attach, i6300esbwd_attach),
234 	DEVMETHOD(device_detach, i6300esbwd_detach),
235 	DEVMETHOD(device_shutdown, i6300esbwd_detach),
236 	DEVMETHOD_END
237 };
238 
239 static driver_t i6300esbwd_driver = {
240 	"i6300esbwd",
241 	i6300esbwd_methods,
242 	sizeof(struct i6300esbwd_softc),
243 };
244 
245 DRIVER_MODULE(i6300esbwd, pci, i6300esbwd_driver, NULL, NULL);
246