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, ® }, 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