xref: /freebsd/sys/dev/amdtemp/amdtemp.c (revision fc1f75e512df3ae08ea50c496cd6c7e89780b201)
1 /*-
2  * Copyright (c) 2008 Rui Paulo <rpaulo@FreeBSD.org>
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  *    notice, this list of conditions and the following disclaimer in the
12  *    documentation and/or other materials provided with the distribution.
13  *
14  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
15  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
16  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
17  * DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
18  * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
19  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
20  * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
22  * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
23  * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
24  * POSSIBILITY OF SUCH DAMAGE.
25  */
26 
27 /*
28  * Driver for the AMD K8 thermal sensors. Based on a Linux driver by the
29  * same name.
30  */
31 
32 #include <sys/cdefs.h>
33 __FBSDID("$FreeBSD$");
34 
35 #include <sys/param.h>
36 #include <sys/bus.h>
37 #include <sys/systm.h>
38 #include <sys/types.h>
39 #include <sys/module.h>
40 #include <sys/conf.h>
41 #include <sys/kernel.h>
42 #include <sys/sysctl.h>
43 
44 #include <machine/specialreg.h>
45 #include <machine/cpufunc.h>
46 #include <machine/md_var.h>
47 
48 #include <dev/pci/pcireg.h>
49 #include <dev/pci/pcivar.h>
50 
51 struct k8temp_softc {
52 	device_t	sc_dev;
53 	int		sc_temps[4];
54 	int		sc_ntemps;
55 	struct sysctl_oid *sc_oid;
56 	struct sysctl_oid *sc_sysctl_cpu[2];
57 	struct intr_config_hook sc_ich;
58 };
59 
60 #define VENDORID_AMD		0x1022
61 #define DEVICEID_AMD_MISC	0x1103
62 
63 static struct k8temp_product {
64 	uint16_t	k8temp_vendorid;
65 	uint16_t	k8temp_deviceid;
66 } k8temp_products[] = {
67 	{ VENDORID_AMD,	DEVICEID_AMD_MISC },
68 	{ 0, 0 }
69 };
70 
71 /*
72  * Register control
73  */
74 #define	K8TEMP_REG		0xe4
75 #define	K8TEMP_REG_SELSENSOR	0x40
76 #define	K8TEMP_REG_SELCORE	0x04
77 
78 #define K8TEMP_MINTEMP		49	/* -49 C is the mininum temperature */
79 
80 typedef enum {
81 	SENSOR0_CORE0,
82 	SENSOR0_CORE1,
83 	SENSOR1_CORE0,
84 	SENSOR1_CORE1,
85 	CORE0,
86 	CORE1
87 } k8sensor_t;
88 
89 /*
90  * Device methods.
91  */
92 static void 	k8temp_identify(driver_t *driver, device_t parent);
93 static int	k8temp_probe(device_t dev);
94 static int	k8temp_attach(device_t dev);
95 static void	k8temp_intrhook(void *arg);
96 static int	k8temp_detach(device_t dev);
97 static int 	k8temp_match(device_t dev);
98 static int32_t	k8temp_gettemp(device_t dev, k8sensor_t sensor);
99 static int	k8temp_sysctl(SYSCTL_HANDLER_ARGS);
100 
101 static device_method_t k8temp_methods[] = {
102 	/* Device interface */
103 	DEVMETHOD(device_identify,	k8temp_identify),
104 	DEVMETHOD(device_probe,		k8temp_probe),
105 	DEVMETHOD(device_attach,	k8temp_attach),
106 	DEVMETHOD(device_detach,	k8temp_detach),
107 
108 	{0, 0}
109 };
110 
111 static driver_t k8temp_driver = {
112 	"k8temp",
113 	k8temp_methods,
114 	sizeof(struct k8temp_softc),
115 };
116 
117 static devclass_t k8temp_devclass;
118 DRIVER_MODULE(k8temp, hostb, k8temp_driver, k8temp_devclass, NULL, NULL);
119 
120 static int
121 k8temp_match(device_t dev)
122 {
123 	int i;
124 	uint16_t vendor, devid;
125 
126         vendor = pci_get_vendor(dev);
127 	devid = pci_get_device(dev);
128 
129 	for (i = 0; k8temp_products[i].k8temp_vendorid != 0; i++) {
130 		if (vendor == k8temp_products[i].k8temp_vendorid &&
131 		    devid == k8temp_products[i].k8temp_deviceid)
132 			return (1);
133 	}
134 
135 	return (0);
136 }
137 
138 static void
139 k8temp_identify(driver_t *driver, device_t parent)
140 {
141 	device_t child;
142 
143 	/* Make sure we're not being doubly invoked. */
144 	if (device_find_child(parent, "k8temp", -1) != NULL)
145 		return;
146 
147 	if (k8temp_match(parent)) {
148 		child = device_add_child(parent, "k8temp", -1);
149 		if (child == NULL)
150 			device_printf(parent, "add k8temp child failed\n");
151 	}
152 
153 }
154 
155 static int
156 k8temp_probe(device_t dev)
157 {
158 	uint32_t regs[4];
159 
160 	if (resource_disabled("k8temp", 0))
161 		return (ENXIO);
162 
163 	do_cpuid(1, regs);
164 	switch (regs[0]) {
165 	case 0xf40:
166 	case 0xf50:
167 	case 0xf51:
168 		return (ENXIO);
169 	}
170 	device_set_desc(dev, "AMD K8 Thermal Sensors");
171 
172 	return (BUS_PROBE_GENERIC);
173 }
174 
175 static int
176 k8temp_attach(device_t dev)
177 {
178 	struct k8temp_softc *sc = device_get_softc(dev);
179 	struct sysctl_ctx_list *sysctlctx;
180 	struct sysctl_oid *sysctlnode;
181 
182 
183 	/*
184 	 * Setup intrhook function to create dev.cpu sysctl entries. This is
185 	 * needed because the cpu driver may be loaded late on boot, after
186 	 * us.
187 	 */
188 	sc->sc_ich.ich_func = k8temp_intrhook;
189 	sc->sc_ich.ich_arg = dev;
190 	if (config_intrhook_establish(&sc->sc_ich) != 0) {
191 		device_printf(dev, "config_intrhook_establish "
192 		    "failed!\n");
193 		return (ENXIO);
194 	}
195 
196 	/*
197 	 * dev.k8temp.N tree.
198 	 */
199 	sysctlctx = device_get_sysctl_ctx(dev);
200 	sysctlnode = SYSCTL_ADD_NODE(sysctlctx,
201 	    SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "sensor0",
202 	    CTLFLAG_RD, 0, "Sensor 0");
203 
204 	SYSCTL_ADD_PROC(sysctlctx,
205 	    SYSCTL_CHILDREN(sysctlnode),
206 	    OID_AUTO, "core0", CTLTYPE_INT | CTLFLAG_RD,
207 	    dev, SENSOR0_CORE0, k8temp_sysctl, "I",
208 	    "Sensor 0 / Core 0 temperature");
209 
210 	SYSCTL_ADD_PROC(sysctlctx,
211 	    SYSCTL_CHILDREN(sysctlnode),
212 	    OID_AUTO, "core1", CTLTYPE_INT | CTLFLAG_RD,
213 	    dev, SENSOR0_CORE1, k8temp_sysctl, "I",
214 	    "Sensor 0 / Core 1 temperature");
215 
216 	sysctlnode = SYSCTL_ADD_NODE(sysctlctx,
217 	    SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "sensor1",
218 	    CTLFLAG_RD, 0, "Sensor 1");
219 
220 	SYSCTL_ADD_PROC(sysctlctx,
221 	    SYSCTL_CHILDREN(sysctlnode),
222 	    OID_AUTO, "core0", CTLTYPE_INT | CTLFLAG_RD,
223 	    dev, SENSOR1_CORE0, k8temp_sysctl, "I",
224 	    "Sensor 1 / Core 0 temperature");
225 
226 	SYSCTL_ADD_PROC(sysctlctx,
227 	    SYSCTL_CHILDREN(sysctlnode),
228 	    OID_AUTO, "core1", CTLTYPE_INT | CTLFLAG_RD,
229 	    dev, SENSOR1_CORE1, k8temp_sysctl, "I",
230 	    "Sensor 1 / Core 1 temperature");
231 
232 	return (0);
233 }
234 
235 void
236 k8temp_intrhook(void *arg)
237 {
238 	int i;
239 	device_t nexus, acpi, cpu;
240 	device_t dev = (device_t) arg;
241 	struct k8temp_softc *sc;
242 	struct sysctl_ctx_list *sysctlctx;
243 
244 	sc = device_get_softc(dev);
245 
246 	/*
247 	 * dev.cpu.N.temperature.
248 	 */
249 	nexus = device_find_child(root_bus, "nexus", 0);
250 	acpi = device_find_child(nexus, "acpi", 0);
251 
252 	for (i = 0; i < 2; i++) {
253 		cpu = device_find_child(acpi, "cpu",
254 		    device_get_unit(dev) * 2 + i);
255 		if (cpu) {
256 			sysctlctx = device_get_sysctl_ctx(cpu);
257 
258 			sc->sc_sysctl_cpu[i] = SYSCTL_ADD_PROC(sysctlctx,
259 			    SYSCTL_CHILDREN(device_get_sysctl_tree(cpu)),
260 			    OID_AUTO, "temperature", CTLTYPE_INT | CTLFLAG_RD,
261 			    dev, CORE0, k8temp_sysctl, "I",
262 			    "Max of sensor 0 / 1");
263 		}
264 	}
265 	config_intrhook_disestablish(&sc->sc_ich);
266 }
267 
268 int
269 k8temp_detach(device_t dev)
270 {
271 	int i;
272 	struct k8temp_softc *sc = device_get_softc(dev);
273 
274 	for (i = 0; i < 2; i++) {
275 		if (sc->sc_sysctl_cpu[i])
276 			sysctl_remove_oid(sc->sc_sysctl_cpu[i], 1, 0);
277 	}
278 
279 	/* NewBus removes the dev.k8temp.N tree by itself. */
280 
281 	return (0);
282 }
283 
284 static int
285 k8temp_sysctl(SYSCTL_HANDLER_ARGS)
286 {
287 	device_t dev = (device_t) arg1;
288 	int error;
289 	int32_t temp, auxtemp[2];
290 
291 	switch (arg2) {
292 	case CORE0:
293 		auxtemp[0] = k8temp_gettemp(dev, SENSOR0_CORE0);
294 		auxtemp[1] = k8temp_gettemp(dev, SENSOR1_CORE0);
295 		temp = imax(auxtemp[0], auxtemp[1]);
296 		break;
297 	case CORE1:
298 		auxtemp[0] = k8temp_gettemp(dev, SENSOR0_CORE1);
299 		auxtemp[1] = k8temp_gettemp(dev, SENSOR1_CORE1);
300 		temp = imax(auxtemp[0], auxtemp[1]);
301 		break;
302 	default:
303 		temp = k8temp_gettemp(dev, arg2);
304 		break;
305 	}
306 	error = sysctl_handle_int(oidp, &temp, 0, req);
307 
308 	return (error);
309 }
310 
311 static int32_t
312 k8temp_gettemp(device_t dev, k8sensor_t sensor)
313 {
314 	uint8_t cfg;
315 	uint32_t temp;
316 
317 	cfg = pci_read_config(dev, K8TEMP_REG, 1);
318 	switch (sensor) {
319 	case SENSOR0_CORE0:
320 		cfg &= ~(K8TEMP_REG_SELSENSOR | K8TEMP_REG_SELCORE);
321 		break;
322 	case SENSOR0_CORE1:
323 		cfg &= ~K8TEMP_REG_SELSENSOR;
324 		cfg |= K8TEMP_REG_SELCORE;
325 		break;
326 	case SENSOR1_CORE0:
327 		cfg &= ~K8TEMP_REG_SELCORE;
328 		cfg |= K8TEMP_REG_SELSENSOR;
329 		break;
330 	case SENSOR1_CORE1:
331 		cfg |= (K8TEMP_REG_SELSENSOR | K8TEMP_REG_SELCORE);
332 		break;
333 	default:
334 		cfg = 0;
335 		break;
336 	}
337 	pci_write_config(dev, K8TEMP_REG, cfg, 1);
338 	temp = pci_read_config(dev, K8TEMP_REG, 4);
339 	temp = ((temp >> 16) & 0xff) - K8TEMP_MINTEMP;
340 
341 	return (temp);
342 }
343