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/param.h>
29 #include <sys/systm.h>
30 #include <sys/bus.h>
31 #include <sys/kernel.h>
32 #include <sys/module.h>
33 #include <sys/rman.h>
34 #include <sys/sysctl.h>
35 #include <sys/watchdog.h>
36
37 #include <dev/superio/superio.h>
38
39 #include <machine/bus.h>
40 #include <machine/resource.h>
41
42 #define NCTHWM_FAN_MAX 5
43
44 #define NCTHWM_BANK_SELECT 0x4e
45 #define NCTHWM_VENDOR_ID 0x4f
46
47 #define NCTHWM_VERBOSE_PRINTF(dev, ...) \
48 do { \
49 if (__predict_false(bootverbose)) \
50 device_printf(dev, __VA_ARGS__); \
51 } while (0)
52
53 struct ncthwm_softc {
54 device_t dev;
55 struct ncthwm_device *nctdevp;
56 struct resource *iores;
57 int iorid;
58 };
59
60 struct ncthwm_fan_info
61 {
62 const char *name;
63 uint8_t low_byte_offset;
64 uint8_t high_byte_offset;
65 };
66
67 struct ncthwm_device {
68 uint16_t devid;
69 const char *descr;
70 uint8_t base_offset;
71 uint8_t fan_bank;
72 uint8_t fan_count;
73 struct ncthwm_fan_info fan_info[NCTHWM_FAN_MAX];
74 } ncthwm_devices[] = {
75 {
76 .devid = 0xc562,
77 .descr = "HWM on Nuvoton NCT6779D",
78 .base_offset = 5,
79 .fan_bank = 4,
80 .fan_count = 5,
81 .fan_info = {
82 { .name = "SYSFAN", .low_byte_offset = 0xc1, .high_byte_offset = 0xc0 },
83 { .name = "CPUFAN", .low_byte_offset = 0xc3, .high_byte_offset = 0xc2 },
84 { .name = "AUXFAN0", .low_byte_offset = 0xc5, .high_byte_offset = 0xc4 },
85 { .name = "AUXFAN1", .low_byte_offset = 0xc7, .high_byte_offset = 0xc6 },
86 { .name = "AUXFAN2", .low_byte_offset = 0xc9, .high_byte_offset = 0xc8 },
87 },
88 }, {
89 .devid = 0xd42a,
90 .descr = "HWM on Nuvoton NCT6796D-E",
91 .base_offset = 5,
92 .fan_bank = 4,
93 .fan_count = 5,
94 .fan_info = {
95 { .name = "SYSFAN", .low_byte_offset = 0xc1, .high_byte_offset = 0xc0 },
96 { .name = "CPUFAN", .low_byte_offset = 0xc3, .high_byte_offset = 0xc2 },
97 { .name = "AUXFAN0", .low_byte_offset = 0xc5, .high_byte_offset = 0xc4 },
98 { .name = "AUXFAN1", .low_byte_offset = 0xc7, .high_byte_offset = 0xc6 },
99 { .name = "AUXFAN2", .low_byte_offset = 0xc9, .high_byte_offset = 0xc8 },
100 },
101 }
102 };
103
104 static struct ncthwm_device *
ncthwm_lookup_device(device_t dev)105 ncthwm_lookup_device(device_t dev)
106 {
107 int i;
108 uint16_t devid;
109
110 devid = superio_devid(dev);
111 for (i = 0; i < nitems(ncthwm_devices); i++) {
112 if (devid == ncthwm_devices[i].devid)
113 return (ncthwm_devices + i);
114 }
115 return (NULL);
116 }
117
118 static void
ncthwm_write(struct ncthwm_softc * sc,uint8_t reg,uint8_t val)119 ncthwm_write(struct ncthwm_softc *sc, uint8_t reg, uint8_t val)
120 {
121 bus_write_1(sc->iores, 0, reg);
122 bus_write_1(sc->iores, 1, val);
123 }
124
125 static uint8_t
ncthwm_read(struct ncthwm_softc * sc,uint8_t reg)126 ncthwm_read(struct ncthwm_softc *sc, uint8_t reg)
127 {
128 bus_write_1(sc->iores, 0, reg);
129 return (bus_read_1(sc->iores, 1));
130 }
131
132 static int
ncthwm_query_fan_speed(SYSCTL_HANDLER_ARGS)133 ncthwm_query_fan_speed(SYSCTL_HANDLER_ARGS)
134 {
135 struct ncthwm_softc *sc;
136 struct ncthwm_fan_info *fan;
137 uint16_t val;
138
139 sc = arg1;
140 if (sc == NULL)
141 return (EINVAL);
142
143 KASSERT(sc->nctdevp != NULL, ("Unreachable"));
144
145 if (sc->nctdevp->fan_count <= arg2)
146 return (EINVAL);
147 fan = &sc->nctdevp->fan_info[arg2];
148
149 KASSERT(sc->iores != NULL, ("Unreachable"));
150
151 ncthwm_write(sc, NCTHWM_BANK_SELECT, sc->nctdevp->fan_bank);
152 val = ncthwm_read(sc, fan->high_byte_offset) << 8;
153 val |= ncthwm_read(sc, fan->low_byte_offset);
154
155 NCTHWM_VERBOSE_PRINTF(sc->dev, "%s: read %u from bank %u offset 0x%x-0x%x\n",
156 fan->name, val, sc->nctdevp->fan_bank, fan->high_byte_offset, fan->low_byte_offset);
157
158 return (sysctl_handle_16(oidp, &val, 0, req));
159 }
160
161 static int
ncthwm_probe(device_t dev)162 ncthwm_probe(device_t dev)
163 {
164 struct ncthwm_device *nctdevp;
165 uint8_t ldn;
166
167 ldn = superio_get_ldn(dev);
168
169 if (superio_vendor(dev) != SUPERIO_VENDOR_NUVOTON) {
170 NCTHWM_VERBOSE_PRINTF(dev, "ldn 0x%x not a Nuvoton device\n", ldn);
171 return (ENXIO);
172 }
173 if (superio_get_type(dev) != SUPERIO_DEV_HWM) {
174 NCTHWM_VERBOSE_PRINTF(dev, "ldn 0x%x not a HWM device\n", ldn);
175 return (ENXIO);
176 }
177
178 nctdevp = ncthwm_lookup_device(dev);
179 if (nctdevp == NULL) {
180 NCTHWM_VERBOSE_PRINTF(dev, "ldn 0x%x not supported\n", ldn);
181 return (ENXIO);
182 }
183 device_set_desc(dev, nctdevp->descr);
184 return (BUS_PROBE_DEFAULT);
185 }
186
187 static int
ncthwm_attach(device_t dev)188 ncthwm_attach(device_t dev)
189 {
190 struct ncthwm_softc *sc;
191 int i;
192 uint16_t iobase;
193
194 sc = device_get_softc(dev);
195 sc->dev = dev;
196
197 sc->nctdevp = ncthwm_lookup_device(dev);
198 if (sc->nctdevp == NULL) {
199 device_printf(dev, "device not supported\n");
200 return (ENXIO);
201 }
202
203 iobase = superio_get_iobase(dev) + sc->nctdevp->base_offset;
204 sc->iorid = 0;
205 if (bus_set_resource(dev, SYS_RES_IOPORT, sc->iorid, iobase, 2) != 0) {
206 device_printf(dev, "failed to set I/O port resource at 0x%x\n", iobase);
207 return (ENXIO);
208 }
209 sc->iores = bus_alloc_resource_any(dev, SYS_RES_IOPORT,
210 &sc->iorid, RF_ACTIVE);
211 if (sc->iores == NULL) {
212 device_printf(dev, "can't map I/O space at 0x%x\n", iobase);
213 return (ENXIO);
214 }
215 NCTHWM_VERBOSE_PRINTF(dev, "iobase 0x%x iores %p\n", iobase, sc->iores);
216
217 /* Register FAN sysctl */
218 for (i = 0; i < sc->nctdevp->fan_count; i++) {
219 SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev),
220 SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO,
221 sc->nctdevp->fan_info[i].name,
222 CTLTYPE_U16 | CTLFLAG_RD, sc, i,
223 ncthwm_query_fan_speed, "SU", "Fan speed in RPM");
224 }
225
226 return (0);
227 }
228
229 static int
ncthwm_detach(device_t dev)230 ncthwm_detach(device_t dev)
231 {
232 struct ncthwm_softc *sc = device_get_softc(dev);
233
234 if (sc->iores)
235 bus_release_resource(dev, SYS_RES_IOPORT, sc->iorid, sc->iores);
236
237 return (0);
238 }
239
240 static device_method_t ncthwm_methods[] = {
241 /* Methods from the device interface */
242 DEVMETHOD(device_probe, ncthwm_probe),
243 DEVMETHOD(device_attach, ncthwm_attach),
244 DEVMETHOD(device_detach, ncthwm_detach),
245
246 /* Terminate method list */
247 { 0, 0 }
248 };
249
250 static driver_t ncthwm_driver = {
251 "ncthwm",
252 ncthwm_methods,
253 sizeof (struct ncthwm_softc)
254 };
255
256 DRIVER_MODULE(ncthwm, superio, ncthwm_driver, NULL, NULL);
257 MODULE_DEPEND(ncthwm, superio, 1, 1, 1);
258 MODULE_VERSION(ncthwm, 1);
259