amdtemp.c revision 185434
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: head/sys/dev/k8temp/k8temp.c 185434 2008-11-29 14:26:22Z rpaulo $");
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
51struct 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
63static 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
80typedef 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 */
92static void 	k8temp_identify(driver_t *driver, device_t parent);
93static int	k8temp_probe(device_t dev);
94static int	k8temp_attach(device_t dev);
95static void	k8temp_intrhook(void *arg);
96static int	k8temp_detach(device_t dev);
97static int 	k8temp_match(device_t dev);
98static int32_t	k8temp_gettemp(device_t dev, k8sensor_t sensor);
99static int	k8temp_sysctl(SYSCTL_HANDLER_ARGS);
100
101static 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
111static driver_t k8temp_driver = {
112	"k8temp",
113	k8temp_methods,
114	sizeof(struct k8temp_softc),
115};
116
117static devclass_t k8temp_devclass;
118DRIVER_MODULE(k8temp, hostb, k8temp_driver, k8temp_devclass, NULL, NULL);
119
120static int
121k8temp_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
138static void
139k8temp_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
155static int
156k8temp_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
175static int
176k8temp_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
235void
236k8temp_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
268int
269k8temp_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
284static int
285k8temp_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
311static int32_t
312k8temp_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