amdtemp.c revision 178949
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 178949 2008-05-11 23:14:07Z 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};
58
59#define VENDORID_AMD		0x1022
60#define DEVICEID_AMD_MISC	0x1103
61
62static struct k8temp_product {
63	uint16_t	k8temp_vendorid;
64	uint16_t	k8temp_deviceid;
65} k8temp_products[] = {
66	{ VENDORID_AMD,	DEVICEID_AMD_MISC },
67	{ 0, 0 }
68};
69
70/*
71 * Register control
72 */
73#define	K8TEMP_REG		0xe4
74#define	K8TEMP_REG_SELSENSOR	0x40
75#define	K8TEMP_REG_SELCORE	0x04
76
77#define K8TEMP_MINTEMP		49	/* -49 C is the mininum temperature */
78
79typedef enum {
80	SENSOR0_CORE0,
81	SENSOR0_CORE1,
82	SENSOR1_CORE0,
83	SENSOR1_CORE1,
84	CORE0,
85	CORE1
86} k8sensor_t;
87
88/*
89 * Device methods.
90 */
91static void 	k8temp_identify(driver_t *driver, device_t parent);
92static int	k8temp_probe(device_t dev);
93static int	k8temp_attach(device_t dev);
94static int	k8temp_detach(device_t dev);
95static int 	k8temp_match(device_t dev);
96static int32_t	k8temp_gettemp(device_t dev, k8sensor_t sensor);
97static int	k8temp_sysctl(SYSCTL_HANDLER_ARGS);
98
99static device_method_t k8temp_methods[] = {
100	/* Device interface */
101	DEVMETHOD(device_identify,	k8temp_identify),
102	DEVMETHOD(device_probe,		k8temp_probe),
103	DEVMETHOD(device_attach,	k8temp_attach),
104	DEVMETHOD(device_detach,	k8temp_detach),
105
106	{0, 0}
107};
108
109static driver_t k8temp_driver = {
110	"k8temp",
111	k8temp_methods,
112	sizeof(struct k8temp_softc),
113};
114
115static devclass_t k8temp_devclass;
116DRIVER_MODULE(k8temp, hostb, k8temp_driver, k8temp_devclass, NULL, NULL);
117
118static int
119k8temp_match(device_t dev)
120{
121	int i;
122	uint16_t vendor, devid;
123
124        vendor = pci_get_vendor(dev);
125	devid = pci_get_device(dev);
126
127	for (i = 0; k8temp_products[i].k8temp_vendorid != 0; i++) {
128		if (vendor == k8temp_products[i].k8temp_vendorid &&
129		    devid == k8temp_products[i].k8temp_deviceid)
130			return (1);
131	}
132
133	return (0);
134}
135
136static void
137k8temp_identify(driver_t *driver, device_t parent)
138{
139	device_t child;
140
141	/* Make sure we're not being doubly invoked. */
142	if (device_find_child(parent, "k8temp", -1) != NULL)
143		return;
144
145	if (k8temp_match(parent)) {
146		child = device_add_child(parent, "k8temp", -1);
147		if (child == NULL)
148			device_printf(parent, "add k8temp child failed\n");
149	}
150
151}
152
153static int
154k8temp_probe(device_t dev)
155{
156	uint32_t regs[4];
157
158	if (resource_disabled("k8temp", 0))
159		return (ENXIO);
160
161	do_cpuid(1, regs);
162	switch (regs[0]) {
163	case 0xf40:
164	case 0xf50:
165	case 0xf51:
166		return (ENXIO);
167	}
168	device_set_desc(dev, "AMD K8 Thermal Sensors");
169
170	return (BUS_PROBE_GENERIC);
171}
172
173static int
174k8temp_attach(device_t dev)
175{
176	device_t nexus, acpi, cpu;
177	struct k8temp_softc *sc = device_get_softc(dev);
178	int i;
179	struct sysctl_ctx_list *sysctlctx;
180	struct sysctl_oid *sysctlnode;
181
182	/*
183	 * dev.cpu.N.temperature.
184	 */
185	nexus = device_find_child(root_bus, "nexus", 0);
186	acpi = device_find_child(nexus, "acpi", 0);
187
188	for (i = 0; i < 2; i++) {
189		cpu = device_find_child(acpi, "cpu",
190		    device_get_unit(dev) * 2 + i);
191		if (cpu) {
192			sysctlctx = device_get_sysctl_ctx(cpu);
193
194			sc->sc_sysctl_cpu[i] = SYSCTL_ADD_PROC(sysctlctx,
195			    SYSCTL_CHILDREN(device_get_sysctl_tree(cpu)),
196			    OID_AUTO, "temperature", CTLTYPE_INT | CTLFLAG_RD,
197			    dev, CORE0, k8temp_sysctl, "I",
198			    "Max of sensor 0 / 1");
199		}
200	}
201
202	/*
203	 * dev.k8temp.N tree.
204	 */
205	sysctlctx = device_get_sysctl_ctx(dev);
206	sysctlnode = SYSCTL_ADD_NODE(sysctlctx,
207	    SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "sensor0",
208	    CTLFLAG_RD, 0, "Sensor 0");
209
210	SYSCTL_ADD_PROC(sysctlctx,
211	    SYSCTL_CHILDREN(sysctlnode),
212	    OID_AUTO, "core0", CTLTYPE_INT | CTLFLAG_RD,
213	    dev, SENSOR0_CORE0, k8temp_sysctl, "I",
214	    "Sensor 0 / Core 0 temperature");
215
216	SYSCTL_ADD_PROC(sysctlctx,
217	    SYSCTL_CHILDREN(sysctlnode),
218	    OID_AUTO, "core1", CTLTYPE_INT | CTLFLAG_RD,
219	    dev, SENSOR0_CORE1, k8temp_sysctl, "I",
220	    "Sensor 0 / Core 1 temperature");
221
222	sysctlnode = SYSCTL_ADD_NODE(sysctlctx,
223	    SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "sensor1",
224	    CTLFLAG_RD, 0, "Sensor 1");
225
226	SYSCTL_ADD_PROC(sysctlctx,
227	    SYSCTL_CHILDREN(sysctlnode),
228	    OID_AUTO, "core0", CTLTYPE_INT | CTLFLAG_RD,
229	    dev, SENSOR0_CORE0, k8temp_sysctl, "I",
230	    "Sensor 1 / Core 0 temperature");
231
232	SYSCTL_ADD_PROC(sysctlctx,
233	    SYSCTL_CHILDREN(sysctlnode),
234	    OID_AUTO, "core1", CTLTYPE_INT | CTLFLAG_RD,
235	    dev, SENSOR0_CORE0, k8temp_sysctl, "I",
236	    "Sensor 1 / Core 1 temperature");
237
238	return (0);
239}
240
241int
242k8temp_detach(device_t dev)
243{
244	int i;
245	struct k8temp_softc *sc = device_get_softc(dev);
246
247	for (i = 0; i < 2; i++) {
248		if (sc->sc_sysctl_cpu[i])
249			sysctl_remove_oid(sc->sc_sysctl_cpu[i], 1, 0);
250	}
251
252	/* NewBus removes the dev.k8temp.N tree by itself. */
253
254	return (0);
255}
256
257static int
258k8temp_sysctl(SYSCTL_HANDLER_ARGS)
259{
260	device_t dev = (device_t) arg1;
261	int error;
262	int32_t temp, auxtemp[2];
263
264	switch (arg2) {
265	case CORE0:
266		auxtemp[0] = k8temp_gettemp(dev, SENSOR0_CORE0);
267		auxtemp[1] = k8temp_gettemp(dev, SENSOR1_CORE0);
268		temp = MAX(auxtemp[0], auxtemp[1]);
269		break;
270	case CORE1:
271		auxtemp[0] = k8temp_gettemp(dev, SENSOR0_CORE1);
272		auxtemp[1] = k8temp_gettemp(dev, SENSOR1_CORE1);
273		temp = MAX(auxtemp[0], auxtemp[1]);
274		break;
275	default:
276		temp = k8temp_gettemp(dev, arg2);
277		break;
278	}
279	error = sysctl_handle_int(oidp, &temp, 0, req);
280
281	return (error);
282}
283
284static int32_t
285k8temp_gettemp(device_t dev, k8sensor_t sensor)
286{
287	uint8_t cfg;
288	uint32_t temp;
289
290	cfg = pci_read_config(dev, K8TEMP_REG, 1);
291	switch (sensor) {
292	case SENSOR0_CORE0:
293		cfg &= ~(K8TEMP_REG_SELSENSOR | K8TEMP_REG_SELCORE);
294		break;
295	case SENSOR0_CORE1:
296		cfg &= ~K8TEMP_REG_SELSENSOR;
297		cfg |= K8TEMP_REG_SELCORE;
298		break;
299	case SENSOR1_CORE0:
300		cfg &= ~K8TEMP_REG_SELCORE;
301		cfg |= K8TEMP_REG_SELSENSOR;
302		break;
303	case SENSOR1_CORE1:
304		cfg |= (K8TEMP_REG_SELSENSOR | K8TEMP_REG_SELCORE);
305		break;
306	default:
307		cfg = 0;
308		break;
309	}
310	pci_write_config(dev, K8TEMP_REG, cfg, 1);
311	temp = pci_read_config(dev, K8TEMP_REG, 4);
312	temp = ((temp >> 16) & 0xff) - K8TEMP_MINTEMP;
313
314	return (temp);
315}
316