smusat.c revision 1.4
1/*-
2 * Copyright (c) 2013 Phileas Fogg
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 NETBSD FOUNDATION, INC. AND CONTRIBUTORS
15 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
16 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
17 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
18 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
19 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
20 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
21 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
22 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
23 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
24 * POSSIBILITY OF SUCH DAMAGE.
25 */
26
27#include <sys/param.h>
28#include <sys/systm.h>
29#include <sys/kernel.h>
30#include <sys/malloc.h>
31#include <sys/device.h>
32#include <sys/proc.h>
33#include <sys/mutex.h>
34#include <sys/time.h>
35#include <sys/sysctl.h>
36
37#include <machine/autoconf.h>
38
39#include <dev/ofw/openfirm.h>
40#include <dev/i2c/i2cvar.h>
41#include <dev/sysmon/sysmonvar.h>
42#include <dev/sysmon/sysmon_taskq.h>
43
44#include <macppc/dev/smuiicvar.h>
45
46#include "opt_smusat.h"
47
48extern int smu_get_datablock(int, uint8_t *, size_t);
49
50enum {
51	SMUSAT_SENSOR_TEMP,
52	SMUSAT_SENSOR_CURRENT,
53	SMUSAT_SENSOR_VOLTAGE,
54	SMUSAT_SENSOR_POWER,
55};
56
57struct smusat_softc;
58
59struct smusat_sensor {
60	struct smusat_softc *sc;
61
62	char location[32];
63	int type;
64	int reg;
65	int zone;
66	int shift;
67	int offset;
68	int scale;
69	int current_value;
70};
71
72#define SMUSAT_MAX_SENSORS	16
73#define SMUSAT_MAX_SME_SENSORS	SMUSAT_MAX_SENSORS
74
75struct smusat_softc {
76	device_t sc_dev;
77	int sc_node;
78	i2c_addr_t sc_addr;
79	uint8_t sc_cache[16];
80	time_t sc_last_update;
81	struct i2c_controller *sc_i2c;
82	struct sysctlnode *sc_sysctl_me;
83
84	int sc_num_sensors;
85	struct smusat_sensor sc_sensors[SMUSAT_MAX_SENSORS];
86
87	struct sysmon_envsys *sc_sme;
88	envsys_data_t sc_sme_sensors[SMUSAT_MAX_SME_SENSORS];
89};
90
91#ifdef SMUSAT_DEBUG
92#define DPRINTF printf
93#else
94#define DPRINTF while (0) printf
95#endif
96
97static int smusat_match(device_t, struct cfdata *, void *);
98static void smusat_attach(device_t, device_t, void *);
99static void smusat_setup_sme(struct smusat_softc *);
100static void smusat_sme_refresh(struct sysmon_envsys *, envsys_data_t *);
101static int smusat_sensors_update(struct smusat_softc *);
102static int smusat_sensor_read(struct smusat_sensor *, int *);
103static int smusat_sysctl_sensor_value(SYSCTLFN_ARGS);
104
105CFATTACH_DECL_NEW(smusat, sizeof(struct smusat_softc),
106    smusat_match, smusat_attach, NULL, NULL);
107
108static const char * smusat_compats[] = {
109	"sat",
110	"smu-sat",
111	NULL
112};
113
114static int
115smusat_match(device_t parent, struct cfdata *cf, void *aux)
116{
117	struct i2c_attach_args *ia = aux;
118	int match_result;
119
120	if (iic_use_direct_match(ia, cf, smusat_compats, &match_result))
121		return match_result;
122
123	if (ia->ia_addr == 0x58)
124		return I2C_MATCH_ADDRESS_ONLY;
125
126	return 0;
127}
128
129static void
130smusat_attach(device_t parent, device_t self, void *aux)
131{
132	struct i2c_attach_args *ia = aux;
133	struct smusat_softc *sc = device_private(self);
134	struct smusat_sensor *sensor;
135	struct sysctlnode *sysctl_sensors, *sysctl_sensor, *sysctl_node;
136	char type[32], sysctl_sensor_name[32];
137	int node, i, j;
138
139	sc->sc_dev = self;
140	sc->sc_node = ia->ia_cookie;
141	sc->sc_addr = ia->ia_addr;
142	sc->sc_i2c = ia->ia_tag;
143
144	sysctl_createv(NULL, 0, NULL, (void *) &sc->sc_sysctl_me,
145	    CTLFLAG_READWRITE,
146	   CTLTYPE_NODE, device_xname(sc->sc_dev), NULL,
147	    NULL, 0, NULL, 0,
148	    CTL_MACHDEP, CTL_CREATE, CTL_EOL);
149
150	for (node = OF_child(sc->sc_node);
151	    (node != 0) && (sc->sc_num_sensors < SMUSAT_MAX_SENSORS);
152	    node = OF_peer(node)) {
153		sensor = &sc->sc_sensors[sc->sc_num_sensors];
154		sensor->sc = sc;
155
156		memset(sensor->location, 0, sizeof(sensor->location));
157		OF_getprop(node, "location", sensor->location,
158		    sizeof(sensor->location));
159
160		if (OF_getprop(node, "reg", &sensor->reg,
161		        sizeof(sensor->reg)) <= 0)
162			continue;
163
164		if ((sensor->reg < 0x30) || (sensor->reg > 0x37))
165			continue;
166		sensor->reg -= 0x30;
167
168		if (OF_getprop(node, "zone", &sensor->zone,
169		        sizeof(sensor->zone)) <= 0)
170			continue;
171
172		memset(type, 0, sizeof(type));
173		OF_getprop(node, "device_type", type, sizeof(type));
174
175		if (strcmp(type, "temp-sensor") == 0) {
176			sensor->type = SMUSAT_SENSOR_TEMP;
177			sensor->shift = 10;
178		} else if (strcmp(type, "current-sensor") == 0) {
179			sensor->type = SMUSAT_SENSOR_CURRENT;
180			sensor->shift = 8;
181		} else if (strcmp(type, "voltage-sensor") == 0) {
182			sensor->type = SMUSAT_SENSOR_VOLTAGE;
183			sensor->shift = 4;
184		} else if (strcmp(type, "power-sensor") == 0) {
185			sensor->type = SMUSAT_SENSOR_POWER;
186			sensor->shift = 0;
187		}
188
189		DPRINTF("sensor: location %s reg %x zone %d type %s\n",
190		    sensor->location, sensor->reg, sensor->zone, type);
191
192		sc->sc_num_sensors++;
193	}
194
195	/* Create sysctl nodes for each sensor */
196
197	sysctl_createv(NULL, 0, NULL, (void *) &sysctl_sensors,
198	    CTLFLAG_READWRITE | CTLFLAG_OWNDESC,
199	    CTLTYPE_NODE, "sensors", NULL,
200	    NULL, 0, NULL, 0,
201	    CTL_MACHDEP,
202	    sc->sc_sysctl_me->sysctl_num,
203	    CTL_CREATE, CTL_EOL);
204
205	for (i = 0; i < sc->sc_num_sensors; i++) {
206		sensor = &sc->sc_sensors[i];
207
208		for (j = 0; j < strlen(sensor->location); j++) {
209			sysctl_sensor_name[j] = tolower(sensor->location[j]);
210			if (sysctl_sensor_name[j] == ' ')
211				sysctl_sensor_name[j] = '_';
212		}
213		sysctl_sensor_name[j] = '\0';
214
215		sysctl_createv(NULL, 0, NULL, (void *) &sysctl_sensor,
216		    CTLFLAG_READWRITE | CTLFLAG_OWNDESC,
217		    CTLTYPE_NODE, sysctl_sensor_name, "sensor information",
218		    NULL, 0, NULL, 0,
219		    CTL_MACHDEP,
220		    sc->sc_sysctl_me->sysctl_num,
221		    sysctl_sensors->sysctl_num,
222		    CTL_CREATE, CTL_EOL);
223
224		sysctl_createv(NULL, 0, NULL, (void *) &sysctl_node,
225		    CTLFLAG_READONLY | CTLFLAG_OWNDESC,
226		    CTLTYPE_INT, "zone", "sensor zone",
227		    NULL, 0, &sensor->zone, 0,
228		    CTL_MACHDEP,
229		    sc->sc_sysctl_me->sysctl_num,
230		    sysctl_sensors->sysctl_num,
231		    sysctl_sensor->sysctl_num,
232		    CTL_CREATE, CTL_EOL);
233
234		sysctl_createv(NULL, 0, NULL, (void *) &sysctl_node,
235		    CTLFLAG_READONLY | CTLFLAG_OWNDESC,
236		    CTLTYPE_INT, "value", "sensor current value",
237		    smusat_sysctl_sensor_value, 0, (void *) sensor, 0,
238		    CTL_MACHDEP,
239		    sc->sc_sysctl_me->sysctl_num,
240		    sysctl_sensors->sysctl_num,
241		    sysctl_sensor->sysctl_num,
242		    CTL_CREATE, CTL_EOL);
243	}
244
245	smusat_setup_sme(sc);
246
247	printf("\n");
248}
249
250static void
251smusat_setup_sme(struct smusat_softc *sc)
252{
253	struct smusat_sensor *sensor;
254	envsys_data_t *sme_sensor;
255	int i;
256
257	sc->sc_sme = sysmon_envsys_create();
258
259	for (i = 0; i < sc->sc_num_sensors; i++) {
260		sme_sensor = &sc->sc_sme_sensors[i];
261		sensor = &sc->sc_sensors[i];
262
263		switch (sensor->type) {
264		case SMUSAT_SENSOR_TEMP:
265			sme_sensor->units = ENVSYS_STEMP;
266		break;
267		case SMUSAT_SENSOR_CURRENT:
268			sme_sensor->units = ENVSYS_SAMPS;
269		break;
270		case SMUSAT_SENSOR_VOLTAGE:
271			sme_sensor->units = ENVSYS_SVOLTS_DC;
272		break;
273		case SMUSAT_SENSOR_POWER:
274			sme_sensor->units = ENVSYS_SWATTS;
275		break;
276		default:
277			sme_sensor->units = ENVSYS_INTEGER;
278		}
279
280		sme_sensor->state = ENVSYS_SINVALID;
281		snprintf(sme_sensor->desc, sizeof(sme_sensor->desc),
282		    "%s", sensor->location);
283
284		if (sysmon_envsys_sensor_attach(sc->sc_sme, sme_sensor)) {
285			sysmon_envsys_destroy(sc->sc_sme);
286			return;
287		}
288	}
289
290	sc->sc_sme->sme_name = device_xname(sc->sc_dev);
291	sc->sc_sme->sme_cookie = sc;
292	sc->sc_sme->sme_refresh = smusat_sme_refresh;
293
294	if (sysmon_envsys_register(sc->sc_sme)) {
295		aprint_error_dev(sc->sc_dev,
296		    "unable to register with sysmon\n");
297		sysmon_envsys_destroy(sc->sc_sme);
298	}
299}
300
301static void
302smusat_sme_refresh(struct sysmon_envsys *sme, envsys_data_t *edata)
303{
304	struct smusat_softc *sc = sme->sme_cookie;
305	struct smusat_sensor *sensor;
306	int which = edata->sensor;
307	int ret;
308
309	edata->state = ENVSYS_SINVALID;
310
311	if (which < sc->sc_num_sensors) {
312		sensor = &sc->sc_sensors[which];
313
314		ret = smusat_sensor_read(sensor, NULL);
315		if (ret == 0) {
316			switch (sensor->type) {
317			case SMUSAT_SENSOR_TEMP:
318				edata->value_cur = sensor->current_value *
319				    1000000 + 273150000;
320			break;
321			case SMUSAT_SENSOR_CURRENT:
322				edata->value_cur = sensor->current_value * 1000000;
323			break;
324			case SMUSAT_SENSOR_VOLTAGE:
325				edata->value_cur = sensor->current_value * 1000000;
326			break;
327			case SMUSAT_SENSOR_POWER:
328				edata->value_cur = sensor->current_value * 1000000;
329			break;
330			default:
331				edata->value_cur = sensor->current_value;
332			}
333
334			edata->state = ENVSYS_SVALID;
335		}
336	}
337}
338
339static int
340smusat_sensors_update(struct smusat_softc *sc)
341{
342	u_char reg = 0x3f;
343	int ret;
344
345	iic_acquire_bus(sc->sc_i2c, 0);
346	ret = iic_exec(sc->sc_i2c, I2C_OP_READ, sc->sc_addr, &reg, 1, sc->sc_cache, 16, 0);
347	iic_release_bus(sc->sc_i2c, 0);
348
349	if (ret != 0)
350		return (ret);
351
352	sc->sc_last_update = time_uptime;
353
354	return 0;
355}
356
357static int
358smusat_sensor_read(struct smusat_sensor *sensor, int *value)
359{
360	struct smusat_softc *sc = sensor->sc;
361	int ret, reg;
362
363	if (time_uptime - sc->sc_last_update > 1) {
364		ret = smusat_sensors_update(sc);
365		if (ret != 0)
366			return ret;
367	}
368
369	reg = sensor->reg << 1;
370	sensor->current_value = (sc->sc_cache[reg] << 8) + sc->sc_cache[reg + 1];
371	sensor->current_value <<= sensor->shift;
372	/* Discard the .16 */
373	sensor->current_value >>= 16;
374
375	if (value != NULL)
376		*value = sensor->current_value;
377
378	return 0;
379}
380
381static int
382smusat_sysctl_sensor_value(SYSCTLFN_ARGS)
383{
384	struct sysctlnode node = *rnode;
385	struct smusat_sensor *sensor = node.sysctl_data;
386	int value = 0;
387	int ret;
388
389	node.sysctl_data = &value;
390
391	ret = smusat_sensor_read(sensor, &value);
392	if (ret != 0)
393		return (ret);
394
395	return sysctl_lookup(SYSCTLFN_CALL(&node));
396}
397
398SYSCTL_SETUP(smusat_sysctl_setup, "SMU-SAT sysctl subtree setup")
399{
400	sysctl_createv(NULL, 0, NULL, NULL,
401	    CTLFLAG_PERMANENT, CTLTYPE_NODE, "machdep", NULL,
402	    NULL, 0, NULL, 0, CTL_MACHDEP, CTL_EOL);
403}
404