1 /*- 2 * Copyright (c) 2015 Michael Gmelin <freebsd@grem.de> 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 AND CONTRIBUTORS ``AS IS'' AND 15 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 17 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 18 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 19 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 20 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 21 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 22 * LIABILITY, 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$"); 29 30 /* 31 * Driver for intersil I2C ISL29018 Digital Ambient Light Sensor and Proximity 32 * Sensor with Interrupt Function, only tested connected over SMBus (ig4iic). 33 * 34 * Datasheet: 35 * http://www.intersil.com/en/products/optoelectronics/ambient-light-and-proximity-sensors/light-to-digital-sensors/ISL29018.html 36 * http://www.intersil.com/content/dam/Intersil/documents/isl2/isl29018.pdf 37 */ 38 39 #include <sys/param.h> 40 #include <sys/bus.h> 41 #include <sys/conf.h> 42 #include <sys/event.h> 43 #include <sys/fcntl.h> 44 #include <sys/kernel.h> 45 #include <sys/lock.h> 46 #include <sys/lockmgr.h> 47 #include <sys/malloc.h> 48 #include <sys/mbuf.h> 49 #include <sys/module.h> 50 #include <sys/poll.h> 51 #include <sys/sx.h> 52 #include <sys/sysctl.h> 53 #include <sys/systm.h> 54 #include <sys/systm.h> 55 56 #include <dev/iicbus/iiconf.h> 57 #include <dev/iicbus/iicbus.h> 58 #include <dev/isl/isl.h> 59 60 #include "iicbus_if.h" 61 #include "bus_if.h" 62 #include "device_if.h" 63 64 #define ISL_METHOD_ALS 0x10 65 #define ISL_METHOD_IR 0x11 66 #define ISL_METHOD_PROX 0x12 67 #define ISL_METHOD_RESOLUTION 0x13 68 #define ISL_METHOD_RANGE 0x14 69 70 struct isl_softc { 71 device_t dev; 72 struct sx isl_sx; 73 }; 74 75 /* Returns < 0 on problem. */ 76 static int isl_read_sensor(device_t dev, uint8_t cmd_mask); 77 78 static int 79 isl_read_byte(device_t dev, uint8_t reg, uint8_t *val) 80 { 81 uint16_t addr = iicbus_get_addr(dev); 82 struct iic_msg msgs[] = { 83 { addr, IIC_M_WR | IIC_M_NOSTOP, 1, ® }, 84 { addr, IIC_M_RD, 1, val }, 85 }; 86 87 return (iicbus_transfer(dev, msgs, nitems(msgs))); 88 } 89 90 static int 91 isl_write_byte(device_t dev, uint8_t reg, uint8_t val) 92 { 93 uint16_t addr = iicbus_get_addr(dev); 94 uint8_t bytes[] = { reg, val }; 95 struct iic_msg msgs[] = { 96 { addr, IIC_M_WR, nitems(bytes), bytes }, 97 }; 98 99 return (iicbus_transfer(dev, msgs, nitems(msgs))); 100 } 101 102 /* 103 * Initialize the device 104 */ 105 static int 106 init_device(device_t dev, int probe) 107 { 108 int error; 109 110 /* 111 * init procedure: send 0x00 to test ref and cmd reg 1 112 */ 113 error = isl_write_byte(dev, REG_TEST, 0); 114 if (error) 115 goto done; 116 error = isl_write_byte(dev, REG_CMD1, 0); 117 if (error) 118 goto done; 119 120 pause("islinit", hz/100); 121 122 done: 123 if (error && !probe) 124 device_printf(dev, "Unable to initialize\n"); 125 return (error); 126 } 127 128 static int isl_probe(device_t); 129 static int isl_attach(device_t); 130 static int isl_detach(device_t); 131 132 static int isl_sysctl(SYSCTL_HANDLER_ARGS); 133 134 static device_method_t isl_methods[] = { 135 /* device interface */ 136 DEVMETHOD(device_probe, isl_probe), 137 DEVMETHOD(device_attach, isl_attach), 138 DEVMETHOD(device_detach, isl_detach), 139 140 DEVMETHOD_END 141 }; 142 143 static driver_t isl_driver = { 144 "isl", 145 isl_methods, 146 sizeof(struct isl_softc), 147 }; 148 149 #if 0 150 static void 151 isl_identify(driver_t *driver, device_t parent) 152 { 153 154 if (device_find_child(parent, "asl", -1)) { 155 if (bootverbose) 156 printf("asl: device(s) already created\n"); 157 return; 158 } 159 160 /* Check if we can communicate to our slave. */ 161 if (init_device(dev, 0x88, 1) == 0) 162 BUS_ADD_CHILD(parent, ISA_ORDER_SPECULATIVE, "isl", -1); 163 } 164 #endif 165 166 static int 167 isl_probe(device_t dev) 168 { 169 uint32_t addr = iicbus_get_addr(dev); 170 171 if (addr != 0x88) 172 return (ENXIO); 173 if (init_device(dev, 1) != 0) 174 return (ENXIO); 175 device_set_desc(dev, "ISL Digital Ambient Light Sensor"); 176 return (BUS_PROBE_VENDOR); 177 } 178 179 static int 180 isl_attach(device_t dev) 181 { 182 struct isl_softc *sc; 183 struct sysctl_ctx_list *sysctl_ctx; 184 struct sysctl_oid *sysctl_tree; 185 int use_als; 186 int use_ir; 187 int use_prox; 188 189 sc = device_get_softc(dev); 190 sc->dev = dev; 191 192 if (init_device(dev, 0) != 0) 193 return (ENXIO); 194 195 sx_init(&sc->isl_sx, "ISL read lock"); 196 197 sysctl_ctx = device_get_sysctl_ctx(dev); 198 sysctl_tree = device_get_sysctl_tree(dev); 199 200 use_als = isl_read_sensor(dev, CMD1_MASK_ALS_ONCE) >= 0; 201 use_ir = isl_read_sensor(dev, CMD1_MASK_IR_ONCE) >= 0; 202 use_prox = isl_read_sensor(dev, CMD1_MASK_PROX_ONCE) >= 0; 203 204 if (use_als) { 205 SYSCTL_ADD_PROC(sysctl_ctx, 206 SYSCTL_CHILDREN(sysctl_tree), OID_AUTO, "als", 207 CTLTYPE_INT | CTLFLAG_RD | CTLFLAG_NEEDGIANT, sc, 208 ISL_METHOD_ALS, isl_sysctl, "I", 209 "Current ALS sensor read-out"); 210 } 211 212 if (use_ir) { 213 SYSCTL_ADD_PROC(sysctl_ctx, 214 SYSCTL_CHILDREN(sysctl_tree), OID_AUTO, "ir", 215 CTLTYPE_INT | CTLFLAG_RD | CTLFLAG_NEEDGIANT, sc, 216 ISL_METHOD_IR, isl_sysctl, "I", 217 "Current IR sensor read-out"); 218 } 219 220 if (use_prox) { 221 SYSCTL_ADD_PROC(sysctl_ctx, 222 SYSCTL_CHILDREN(sysctl_tree), OID_AUTO, "prox", 223 CTLTYPE_INT | CTLFLAG_RD | CTLFLAG_NEEDGIANT, sc, 224 ISL_METHOD_PROX, isl_sysctl, "I", 225 "Current proximity sensor read-out"); 226 } 227 228 SYSCTL_ADD_PROC(sysctl_ctx, 229 SYSCTL_CHILDREN(sysctl_tree), OID_AUTO, "resolution", 230 CTLTYPE_INT | CTLFLAG_RD | CTLFLAG_NEEDGIANT, sc, 231 ISL_METHOD_RESOLUTION, isl_sysctl, "I", 232 "Current proximity sensor resolution"); 233 234 SYSCTL_ADD_PROC(sysctl_ctx, 235 SYSCTL_CHILDREN(sysctl_tree), OID_AUTO, "range", 236 CTLTYPE_INT | CTLFLAG_RD | CTLFLAG_NEEDGIANT, sc, 237 ISL_METHOD_RANGE, isl_sysctl, "I", 238 "Current proximity sensor range"); 239 240 return (0); 241 } 242 243 static int 244 isl_detach(device_t dev) 245 { 246 struct isl_softc *sc; 247 248 sc = device_get_softc(dev); 249 sx_destroy(&sc->isl_sx); 250 251 return (0); 252 } 253 254 static int 255 isl_sysctl(SYSCTL_HANDLER_ARGS) 256 { 257 static int resolutions[] = { 16, 12, 8, 4}; 258 static int ranges[] = { 1000, 4000, 16000, 64000}; 259 260 struct isl_softc *sc; 261 uint8_t rbyte; 262 int arg; 263 int resolution; 264 int range; 265 266 sc = (struct isl_softc *)oidp->oid_arg1; 267 arg = -1; 268 269 sx_xlock(&sc->isl_sx); 270 if (isl_read_byte(sc->dev, REG_CMD2, &rbyte) != 0) { 271 sx_xunlock(&sc->isl_sx); 272 return (-1); 273 } 274 resolution = resolutions[(rbyte & CMD2_MASK_RESOLUTION) 275 >> CMD2_SHIFT_RESOLUTION]; 276 range = ranges[(rbyte & CMD2_MASK_RANGE) >> CMD2_SHIFT_RANGE]; 277 278 switch (oidp->oid_arg2) { 279 case ISL_METHOD_ALS: 280 arg = (isl_read_sensor(sc->dev, 281 CMD1_MASK_ALS_ONCE) * range) >> resolution; 282 break; 283 case ISL_METHOD_IR: 284 arg = isl_read_sensor(sc->dev, CMD1_MASK_IR_ONCE); 285 break; 286 case ISL_METHOD_PROX: 287 arg = isl_read_sensor(sc->dev, CMD1_MASK_PROX_ONCE); 288 break; 289 case ISL_METHOD_RESOLUTION: 290 arg = (1 << resolution); 291 break; 292 case ISL_METHOD_RANGE: 293 arg = range; 294 break; 295 } 296 sx_xunlock(&sc->isl_sx); 297 298 SYSCTL_OUT(req, &arg, sizeof(arg)); 299 return (0); 300 } 301 302 static int 303 isl_read_sensor(device_t dev, uint8_t cmd_mask) 304 { 305 uint8_t rbyte; 306 uint8_t cmd; 307 int ret; 308 309 if (isl_read_byte(dev, REG_CMD1, &rbyte) != 0) { 310 device_printf(dev, 311 "Couldn't read first byte before issuing command %d\n", 312 cmd_mask); 313 return (-1); 314 } 315 316 cmd = (rbyte & 0x1f) | cmd_mask; 317 if (isl_write_byte(dev, REG_CMD1, cmd) != 0) { 318 device_printf(dev, "Couldn't write command %d\n", cmd_mask); 319 return (-1); 320 } 321 322 pause("islconv", hz/10); 323 324 if (isl_read_byte(dev, REG_DATA1, &rbyte) != 0) { 325 device_printf(dev, 326 "Couldn't read first byte after command %d\n", cmd_mask); 327 return (-1); 328 } 329 330 ret = rbyte; 331 if (isl_read_byte(dev, REG_DATA2, &rbyte) != 0) { 332 device_printf(dev, "Couldn't read second byte after command %d\n", cmd_mask); 333 return (-1); 334 } 335 ret += rbyte << 8; 336 337 return (ret); 338 } 339 340 DRIVER_MODULE(isl, iicbus, isl_driver, NULL, NULL); 341 MODULE_DEPEND(isl, iicbus, IICBUS_MINVER, IICBUS_PREFVER, IICBUS_MAXVER); 342 MODULE_VERSION(isl, 1); 343