xref: /freebsd/sys/dev/ncthwm/ncthwm.c (revision 7ef62cebc2f965b0f640263e179276928885e33d)
1 /*-
2  * SPDX-License-Identifier: BSD-2-Clause
3  *
4  * Copyright (c) 2016-2022 Stormshield
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions
8  * are met:
9  * 1. Redistributions of source code must retain the above copyright
10  *    notice, this list of conditions and the following disclaimer.
11  * 2. Redistributions in binary form must reproduce the above copyright
12  *    notice, this list of conditions and the following disclaimer in the
13  *    documentation and/or other materials provided with the distribution.
14  *
15  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
16  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
19  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
21  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
22  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
23  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
24  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
25  * SUCH DAMAGE.
26  */
27 
28 #include <sys/cdefs.h>
29 __FBSDID("$FreeBSD$");
30 
31 #include <sys/param.h>
32 #include <sys/systm.h>
33 #include <sys/bus.h>
34 #include <sys/kernel.h>
35 #include <sys/module.h>
36 #include <sys/rman.h>
37 #include <sys/sysctl.h>
38 #include <sys/watchdog.h>
39 
40 #include <dev/superio/superio.h>
41 
42 #include <machine/bus.h>
43 #include <machine/resource.h>
44 
45 #define NCTHWM_FAN_MAX                 5
46 
47 #define NCTHWM_BANK_SELECT 0x4e
48 #define NCTHWM_VENDOR_ID   0x4f
49 
50 #define NCTHWM_VERBOSE_PRINTF(dev, ...)         \
51 	do {                                        \
52 		if (__predict_false(bootverbose))       \
53 			device_printf(dev, __VA_ARGS__);    \
54 	} while (0)
55 
56 struct ncthwm_softc {
57 	device_t              dev;
58 	struct ncthwm_device *nctdevp;
59 	struct resource      *iores;
60 	int                   iorid;
61 };
62 
63 struct ncthwm_fan_info
64 {
65 	const char *name;
66 	uint8_t     low_byte_offset;
67 	uint8_t     high_byte_offset;
68 };
69 
70 struct ncthwm_device {
71 	uint16_t                 devid;
72 	const char              *descr;
73 	uint8_t                  base_offset;
74 	uint8_t                  fan_bank;
75 	uint8_t                  fan_count;
76 	struct ncthwm_fan_info   fan_info[NCTHWM_FAN_MAX];
77 } ncthwm_devices[] = {
78 	{
79 		.devid       = 0xc562,
80 		.descr       = "HWM on Nuvoton NCT6779D",
81 		.base_offset = 5,
82 		.fan_bank    = 4,
83 		.fan_count   = 5,
84 		.fan_info = {
85 			{ .name = "SYSFAN",  .low_byte_offset = 0xc1, .high_byte_offset = 0xc0 },
86 			{ .name = "CPUFAN",  .low_byte_offset = 0xc3, .high_byte_offset = 0xc2 },
87 			{ .name = "AUXFAN0", .low_byte_offset = 0xc5, .high_byte_offset = 0xc4 },
88 			{ .name = "AUXFAN1", .low_byte_offset = 0xc7, .high_byte_offset = 0xc6 },
89 			{ .name = "AUXFAN2", .low_byte_offset = 0xc9, .high_byte_offset = 0xc8 },
90 		},
91 	}, {
92 		.devid       = 0xd42a,
93 		.descr       = "HWM on Nuvoton NCT6796D-E",
94 		.base_offset = 5,
95 		.fan_bank    = 4,
96 		.fan_count   = 5,
97 		.fan_info = {
98 			{ .name = "SYSFAN",  .low_byte_offset = 0xc1, .high_byte_offset = 0xc0 },
99 			{ .name = "CPUFAN",  .low_byte_offset = 0xc3, .high_byte_offset = 0xc2 },
100 			{ .name = "AUXFAN0", .low_byte_offset = 0xc5, .high_byte_offset = 0xc4 },
101 			{ .name = "AUXFAN1", .low_byte_offset = 0xc7, .high_byte_offset = 0xc6 },
102 			{ .name = "AUXFAN2", .low_byte_offset = 0xc9, .high_byte_offset = 0xc8 },
103 		},
104 	}
105 };
106 
107 static struct ncthwm_device *
108 ncthwm_lookup_device(device_t dev)
109 {
110 	int      i;
111 	uint16_t devid;
112 
113 	devid = superio_devid(dev);
114 	for (i = 0; i < nitems(ncthwm_devices); i++) {
115 		if (devid == ncthwm_devices[i].devid)
116 			return (ncthwm_devices + i);
117 	}
118 	return (NULL);
119 }
120 
121 static void
122 ncthwm_write(struct ncthwm_softc *sc, uint8_t reg, uint8_t val)
123 {
124 	bus_write_1(sc->iores, 0, reg);
125 	bus_write_1(sc->iores, 1, val);
126 }
127 
128 static uint8_t
129 ncthwm_read(struct ncthwm_softc *sc, uint8_t reg)
130 {
131 	bus_write_1(sc->iores, 0, reg);
132 	return (bus_read_1(sc->iores, 1));
133 }
134 
135 static int
136 ncthwm_query_fan_speed(SYSCTL_HANDLER_ARGS)
137 {
138 	struct ncthwm_softc    *sc;
139 	struct ncthwm_fan_info *fan;
140 	uint16_t                val;
141 
142 	sc = arg1;
143 	if (sc == NULL)
144 		return (EINVAL);
145 
146 	KASSERT(sc->nctdevp != NULL, ("Unreachable"));
147 
148 	if (sc->nctdevp->fan_count <= arg2)
149 		return (EINVAL);
150 	fan = &sc->nctdevp->fan_info[arg2];
151 
152 	KASSERT(sc->iores != NULL, ("Unreachable"));
153 
154 	ncthwm_write(sc, NCTHWM_BANK_SELECT, sc->nctdevp->fan_bank);
155 	val  = ncthwm_read(sc, fan->high_byte_offset) << 8;
156 	val |= ncthwm_read(sc, fan->low_byte_offset);
157 
158 	NCTHWM_VERBOSE_PRINTF(sc->dev, "%s: read %u from bank %u offset 0x%x-0x%x\n",
159 		fan->name, val, sc->nctdevp->fan_bank, fan->high_byte_offset, fan->low_byte_offset);
160 
161 	return (sysctl_handle_16(oidp, &val, 0, req));
162 }
163 
164 static int
165 ncthwm_probe(device_t dev)
166 {
167 	struct ncthwm_device *nctdevp;
168 	uint8_t               ldn;
169 
170 	ldn = superio_get_ldn(dev);
171 
172 	if (superio_vendor(dev) != SUPERIO_VENDOR_NUVOTON) {
173 		NCTHWM_VERBOSE_PRINTF(dev, "ldn 0x%x not a Nuvoton device\n", ldn);
174 		return (ENXIO);
175 	}
176 	if (superio_get_type(dev) != SUPERIO_DEV_HWM) {
177 		NCTHWM_VERBOSE_PRINTF(dev, "ldn 0x%x not a HWM device\n", ldn);
178 		return (ENXIO);
179 	}
180 
181 	nctdevp = ncthwm_lookup_device(dev);
182 	if (nctdevp == NULL) {
183 		NCTHWM_VERBOSE_PRINTF(dev, "ldn 0x%x not supported\n", ldn);
184 		return (ENXIO);
185 	}
186 	device_set_desc(dev, nctdevp->descr);
187 	return (BUS_PROBE_DEFAULT);
188 }
189 
190 static int
191 ncthwm_attach(device_t dev)
192 {
193 	struct ncthwm_softc *sc;
194 	int                  i;
195 	uint16_t             iobase;
196 
197 	sc      = device_get_softc(dev);
198 	sc->dev = dev;
199 
200 	sc->nctdevp = ncthwm_lookup_device(dev);
201 	if (sc->nctdevp == NULL) {
202 		device_printf(dev, "device not supported\n");
203 		return (ENXIO);
204 	}
205 
206 	iobase    = superio_get_iobase(dev) + sc->nctdevp->base_offset;
207 	sc->iorid = 0;
208 	if (bus_set_resource(dev, SYS_RES_IOPORT, sc->iorid, iobase, 2) != 0) {
209 		device_printf(dev, "failed to set I/O port resource at 0x%x\n", iobase);
210 		return (ENXIO);
211 	}
212 	sc->iores = bus_alloc_resource_any(dev, SYS_RES_IOPORT,
213 		&sc->iorid, RF_ACTIVE);
214 	if (sc->iores == NULL) {
215 		device_printf(dev, "can't map I/O space at 0x%x\n", iobase);
216 		return (ENXIO);
217 	}
218 	NCTHWM_VERBOSE_PRINTF(dev, "iobase 0x%x iores %p\n", iobase, sc->iores);
219 
220 	/* Register FAN sysctl */
221 	for (i = 0; i < sc->nctdevp->fan_count; i++) {
222 		SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev),
223 			SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO,
224 			sc->nctdevp->fan_info[i].name,
225 			CTLTYPE_U16 | CTLFLAG_RD, sc, i,
226 			ncthwm_query_fan_speed, "SU", "Fan speed in RPM");
227 	}
228 
229 	return (0);
230 }
231 
232 static int
233 ncthwm_detach(device_t dev)
234 {
235 	struct ncthwm_softc *sc = device_get_softc(dev);
236 
237 	if (sc->iores)
238 		bus_release_resource(dev, SYS_RES_IOPORT, sc->iorid, sc->iores);
239 
240 	return (0);
241 }
242 
243 static device_method_t ncthwm_methods[] = {
244 	/* Methods from the device interface */
245 	DEVMETHOD(device_probe,		ncthwm_probe),
246 	DEVMETHOD(device_attach,	ncthwm_attach),
247 	DEVMETHOD(device_detach,	ncthwm_detach),
248 
249 	/* Terminate method list */
250 	{ 0, 0 }
251 };
252 
253 static driver_t ncthwm_driver = {
254 	"ncthwm",
255 	ncthwm_methods,
256 	sizeof (struct ncthwm_softc)
257 };
258 
259 DRIVER_MODULE(ncthwm, superio, ncthwm_driver, NULL, NULL);
260 MODULE_DEPEND(ncthwm, superio, 1, 1, 1);
261 MODULE_VERSION(ncthwm, 1);
262