max6690.c revision 217452
1/*-
2 * Copyright (c) 2010 Andreas Tobler
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 WARRANTIES
16 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
17 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
18 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
19 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
20 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
21 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
22 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24 * SUCH DAMAGE.
25 */
26
27#include <sys/cdefs.h>
28__FBSDID("$FreeBSD: head/sys/dev/iicbus/max6690.c 217452 2011-01-15 19:16:56Z andreast $");
29
30#include <sys/param.h>
31#include <sys/bus.h>
32#include <sys/systm.h>
33#include <sys/module.h>
34#include <sys/callout.h>
35#include <sys/conf.h>
36#include <sys/cpu.h>
37#include <sys/ctype.h>
38#include <sys/kernel.h>
39#include <sys/reboot.h>
40#include <sys/rman.h>
41#include <sys/sysctl.h>
42#include <sys/limits.h>
43
44#include <machine/bus.h>
45#include <machine/md_var.h>
46
47#include <dev/iicbus/iicbus.h>
48#include <dev/iicbus/iiconf.h>
49
50#include <dev/ofw/openfirm.h>
51#include <dev/ofw/ofw_bus.h>
52
53#define FCU_ZERO_C_TO_K     2732
54
55/* Inlet, Backside, U3 Heatsink sensor: MAX6690. */
56
57#define MAX6690_INT_TEMP    0x0
58#define MAX6690_EXT_TEMP    0x1
59#define MAX6690_EEXT_TEMP   0x10
60#define MAX6690_IEXT_TEMP   0x11
61#define MAX6690_TEMP_MASK   0xe0
62
63struct max6690_sensor {
64	int     id;
65	char    location[32];
66};
67
68/* Regular bus attachment functions */
69static int  max6690_probe(device_t);
70static int  max6690_attach(device_t);
71
72/* Utility functions */
73static int  max6690_sensor_sysctl(SYSCTL_HANDLER_ARGS);
74static void max6690_start(void *xdev);
75static int  max6690_read_1(device_t dev, uint32_t addr, uint8_t reg,
76			   uint8_t *data);
77
78struct max6690_softc {
79	device_t		sc_dev;
80	struct intr_config_hook enum_hook;
81	uint32_t                sc_addr;
82	struct max6690_sensor   *sc_sensors;
83	int                     sc_nsensors;
84};
85static device_method_t  max6690_methods[] = {
86	/* Device interface */
87	DEVMETHOD(device_probe,		max6690_probe),
88	DEVMETHOD(device_attach,	max6690_attach),
89	{ 0, 0 },
90};
91
92static driver_t max6690_driver = {
93	"max6690",
94	max6690_methods,
95	sizeof(struct max6690_softc)
96};
97
98static devclass_t max6690_devclass;
99
100DRIVER_MODULE(max6690, iicbus, max6690_driver, max6690_devclass, 0, 0);
101MALLOC_DEFINE(M_MAX6690, "max6690", "Temp-Monitor MAX6690");
102
103static int
104max6690_read_1(device_t dev, uint32_t addr, uint8_t reg, uint8_t *data)
105{
106	uint8_t buf[4];
107
108	struct iic_msg msg[2] = {
109	    { addr, IIC_M_WR | IIC_M_NOSTOP, 1, &reg },
110	    { addr, IIC_M_RD, 1, buf },
111	};
112
113	if (iicbus_transfer(dev, msg, 2) != 0) {
114		device_printf(dev, "iicbus read failed\n");
115		return (EIO);
116	}
117
118	*data = *((uint8_t*)buf);
119
120	return (0);
121}
122
123static int
124max6690_probe(device_t dev)
125{
126	const char  *name, *compatible;
127	struct max6690_softc *sc;
128
129	name = ofw_bus_get_name(dev);
130	compatible = ofw_bus_get_compat(dev);
131
132	if (!name)
133		return (ENXIO);
134
135	if (strcmp(name, "temp-monitor") != 0 ||
136	    strcmp(compatible, "max6690") != 0)
137		return (ENXIO);
138
139	sc = device_get_softc(dev);
140	sc->sc_dev = dev;
141	sc->sc_addr = iicbus_get_addr(dev);
142
143	device_set_desc(dev, "Temp-Monitor MAX6690");
144
145	return (0);
146}
147
148/*
149 * This function returns the number of sensors. If we call it the second time
150 * and we have allocated memory for sc->sc_sensors, we fill in the properties.
151 */
152static int
153max6690_fill_sensor_prop(device_t dev)
154{
155	phandle_t child;
156	struct max6690_softc *sc;
157	u_int id[8];
158	char location[96];
159	int i = 0, j, len = 0, prop_len, prev_len = 0;
160
161	sc = device_get_softc(dev);
162
163	child = ofw_bus_get_node(dev);
164
165	/* Fill the sensor location property. */
166	prop_len = OF_getprop(child, "hwsensor-location", location,
167			      sizeof(location));
168	while (len < prop_len) {
169		if (sc->sc_sensors != NULL)
170			strcpy(sc->sc_sensors[i].location, location + len);
171		prev_len = strlen(location + len) + 1;
172		len += prev_len;
173		i++;
174	}
175	if (sc->sc_sensors == NULL)
176		return (i);
177
178	/* Fill the sensor id property. */
179	prop_len = OF_getprop(child, "hwsensor-id", id, sizeof(id));
180	for (j = 0; j < i; j++)
181		sc->sc_sensors[j].id = (id[j] & 0xf);
182
183	return (i);
184}
185static int
186max6690_attach(device_t dev)
187{
188	struct max6690_softc *sc;
189
190	sc = device_get_softc(dev);
191
192	sc->enum_hook.ich_func = max6690_start;
193	sc->enum_hook.ich_arg = dev;
194
195	/* We have to wait until interrupts are enabled. I2C read and write
196	 * only works if the interrupts are available.
197	 * The unin/i2c is controlled by the htpic on unin. But this is not
198	 * the master. The openpic on mac-io is controlling the htpic.
199	 * This one gets attached after the mac-io probing and then the
200	 * interrupts will be available.
201	 */
202
203	if (config_intrhook_establish(&sc->enum_hook) != 0)
204		return (ENOMEM);
205
206	return (0);
207}
208
209static void
210max6690_start(void *xdev)
211{
212	struct max6690_softc *sc;
213	struct sysctl_oid *oid, *sensroot_oid;
214	struct sysctl_ctx_list *ctx;
215	char sysctl_name[32];
216	int i, j;
217
218	device_t dev = (device_t)xdev;
219
220	sc = device_get_softc(dev);
221
222	sc->sc_nsensors = 0;
223
224	/* Count the actual number of sensors. */
225	sc->sc_nsensors = max6690_fill_sensor_prop(dev);
226
227	device_printf(dev, "%d sensors detected.\n", sc->sc_nsensors);
228
229	if (sc->sc_nsensors == 0)
230		device_printf(dev, "WARNING: No MAX6690 sensors detected!\n");
231
232	sc->sc_sensors = malloc (sc->sc_nsensors * sizeof(struct max6690_sensor),
233				 M_MAX6690, M_WAITOK | M_ZERO);
234
235	ctx = device_get_sysctl_ctx(dev);
236	sensroot_oid = SYSCTL_ADD_NODE(ctx,
237	    SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "sensor",
238	    CTLFLAG_RD, 0, "MAX6690 Sensor Information");
239
240	/* Now we can fill the properties into the allocated struct. */
241	sc->sc_nsensors = max6690_fill_sensor_prop(dev);
242
243	/* Add sysctls for the sensors. */
244	for (i = 0; i < sc->sc_nsensors; i++) {
245		for (j = 0; j < strlen(sc->sc_sensors[i].location); j++) {
246			sysctl_name[j] = tolower(sc->sc_sensors[i].location[j]);
247			if (isspace(sysctl_name[j]))
248				sysctl_name[j] = '_';
249		}
250		sysctl_name[j] = 0;
251
252		oid = SYSCTL_ADD_NODE(ctx, SYSCTL_CHILDREN(sensroot_oid),
253				      OID_AUTO,
254				      sysctl_name, CTLFLAG_RD, 0,
255				      "Sensor Information");
256		/* I use i to pass the sensor id. */
257		SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(oid), OID_AUTO, "temp",
258				CTLTYPE_INT | CTLFLAG_RD, dev, i % 2,
259				max6690_sensor_sysctl, "IK",
260				"Sensor Temp in ��C");
261
262	}
263	/* Dump sensor location & ID. */
264	if (bootverbose) {
265		device_printf(dev, "Sensors\n");
266		for (i = 0; i < sc->sc_nsensors; i++) {
267			device_printf(dev, "Location : %s ID: %d\n",
268				      sc->sc_sensors[i].location,
269				      sc->sc_sensors[i].id);
270		}
271	}
272
273	config_intrhook_disestablish(&sc->enum_hook);
274}
275
276static int
277max6690_sensor_read(device_t dev, struct max6690_sensor *sens, int *temp)
278{
279	uint8_t reg_int = 0, reg_ext = 0;
280	uint8_t integer;
281	uint8_t fraction;
282	struct max6690_softc *sc;
283
284	sc = device_get_softc(dev);
285
286	/* The internal sensor id's are even, the external ar odd. */
287	if ((sens->id % 2) == 0) {
288		reg_int = MAX6690_INT_TEMP;
289		reg_ext = MAX6690_IEXT_TEMP;
290	} else {
291		reg_int = MAX6690_EXT_TEMP;
292		reg_ext = MAX6690_EEXT_TEMP;
293	}
294
295	max6690_read_1(sc->sc_dev, sc->sc_addr, reg_int, &integer);
296
297	max6690_read_1(sc->sc_dev, sc->sc_addr, reg_ext, &fraction);
298
299	fraction &= MAX6690_TEMP_MASK;
300
301	/* The temperature is in tenth kelvin, the fractional part resolution
302	   is 0.125.
303	*/
304	*temp = (integer * 10) + (fraction >> 5) * 10 / 8;
305
306	return (0);
307}
308
309static int
310max6690_sensor_sysctl(SYSCTL_HANDLER_ARGS)
311{
312	device_t dev;
313	struct max6690_softc *sc;
314	struct max6690_sensor *sens;
315	int value = 0;
316	int error;
317	unsigned int temp;
318
319	dev = arg1;
320	sc = device_get_softc(dev);
321	sens = &sc->sc_sensors[arg2];
322
323	error = max6690_sensor_read(dev, sens, &value);
324	if (error != 0)
325		return (error);
326
327	temp = value + FCU_ZERO_C_TO_K;
328
329	error = sysctl_handle_int(oidp, &temp, 0, req);
330
331	return (error);
332}
333