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