Annotation of src/sys/dev/i2c/tsllux.c, Revision 1.2
1.2 ! thorpej 1: /* $NetBSD: tsllux.c,v 1.1 2021/01/04 22:09:35 thorpej Exp $ */
1.1 thorpej 2:
3: /*-
4: * Copyright (c) 2018 Jason R. Thorpe
5: * All rights reserved.
6: *
7: * Redistribution and use in source and binary forms, with or without
8: * modification, are permitted provided that the following conditions
9: * are met:
10: * 1. Redistributions of source code must retain the above copyright
11: * notice, this list of conditions and the following disclaimer.
12: * 2. Redistributions in binary form must reproduce the above copyright
13: * notice, this list of conditions and the following disclaimer in the
14: * documentation and/or other materials provided with the distribution.
15: *
16: * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
17: * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
18: * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19: * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
20: * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
21: * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
22: * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
23: * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
24: * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
25: * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
26: * POSSIBILITY OF SUCH DAMAGE.
27: */
28:
29: #include <sys/cdefs.h>
1.2 ! thorpej 30: __KERNEL_RCSID(0, "$NetBSD: tsllux.c,v 1.1 2021/01/04 22:09:35 thorpej Exp $");
1.1 thorpej 31:
32: #include <sys/param.h>
33: #include <sys/systm.h>
34: #include <sys/device.h>
35: #include <sys/conf.h>
36: #include <sys/bus.h>
37: #include <sys/kernel.h>
38: #include <sys/kmem.h>
39: #include <sys/mutex.h>
40: #include <sys/proc.h>
41: #include <sys/sysctl.h>
42:
43: #include <dev/i2c/i2cvar.h>
44: #include <dev/i2c/tsl256xreg.h>
45:
46: #include <dev/sysmon/sysmonvar.h>
47:
48: struct tsllux_softc {
49: device_t sc_dev;
50: i2c_tag_t sc_i2c;
51: i2c_addr_t sc_addr;
52:
53: uint32_t sc_poweron;
54:
55: /*
56: * Locking order is:
57: * tsllux mutex -> i2c bus
58: */
59: kmutex_t sc_lock;
60:
61: uint8_t sc_itime;
62: uint8_t sc_gain;
63: bool sc_cs_package;
64: bool sc_auto_gain;
65:
66: struct sysmon_envsys *sc_sme;
67: envsys_data_t sc_sensor;
68:
69: struct sysctllog *sc_sysctllog;
70: };
71:
72: #define TSLLUX_F_CS_PACKAGE 0x01
73:
74: static int tsllux_match(device_t, cfdata_t, void *);
75: static void tsllux_attach(device_t, device_t, void *);
76:
77: CFATTACH_DECL_NEW(tsllux, sizeof(struct tsllux_softc),
78: tsllux_match, tsllux_attach, NULL, NULL);
79:
80: static const struct device_compatible_entry tsllux_compat_data[] = {
1.2 ! thorpej 81: { .compat = "amstaos,tsl2560" },
! 82: { .compat = "amstaos,tsl2561" },
! 83:
! 84: { 0 }
1.1 thorpej 85: };
86:
87: static int tsllux_read1(struct tsllux_softc *, uint8_t, uint8_t *);
88: static int tsllux_read2(struct tsllux_softc *, uint8_t, uint16_t *);
89: static int tsllux_write1(struct tsllux_softc *, uint8_t, uint8_t);
90: #if 0
91: static int tsllux_write2(struct tsllux_softc *, uint8_t, uint16_t);
92: #endif
93:
94: static void tsllux_sysctl_attach(struct tsllux_softc *);
95:
96: static int tsllux_poweron(struct tsllux_softc *);
97: static int tsllux_poweroff(struct tsllux_softc *);
98:
99: static int tsllux_set_integration_time(struct tsllux_softc *, uint8_t);
100: static int tsllux_set_gain(struct tsllux_softc *, uint8_t);
101: static int tsllux_set_autogain(struct tsllux_softc *, bool);
102:
103: static int tsllux_get_lux(struct tsllux_softc *, uint32_t *,
104: uint16_t *, uint16_t *);
105:
106: static void tsllux_sensors_refresh(struct sysmon_envsys *, envsys_data_t *);
107:
108: static int
109: tsllux_match(device_t parent, cfdata_t match, void *aux)
110: {
111: struct i2c_attach_args *ia = aux;
112: uint8_t id_reg;
113: int error, match_result;
114:
115: if (iic_use_direct_match(ia, match, tsllux_compat_data, &match_result))
116: return (match_result);
117:
118: switch (ia->ia_addr) {
119: case TSL256x_SLAVEADDR_GND:
120: case TSL256x_SLAVEADDR_FLOAT:
121: case TSL256x_SLAVEADDR_VDD:
122: break;
123:
124: default:
125: return (0);
126: }
127:
128: if (iic_acquire_bus(ia->ia_tag, 0) != 0)
129: return (0);
130: error = iic_smbus_read_byte(ia->ia_tag, ia->ia_addr,
131: TSL256x_REG_ID | COMMAND6x_CMD, &id_reg, 0);
132: iic_release_bus(ia->ia_tag, 0);
133:
134: if (error)
135: return (0);
136:
137: /* XXX This loses if we have a 2560 rev. 0. */
138: if (id_reg == 0)
139: return (I2C_MATCH_ADDRESS_ONLY);
140:
141: return (I2C_MATCH_ADDRESS_AND_PROBE);
142: }
143:
144: static void
145: tsllux_attach(device_t parent, device_t self, void *aux)
146: {
147: struct tsllux_softc *sc = device_private(self);
148: struct i2c_attach_args *ia = aux;
149: bool have_i2c;
150:
151: /* XXX IPL_NONE changes when we support threshold interrupts. */
152: mutex_init(&sc->sc_lock, MUTEX_DEFAULT, IPL_NONE);
153:
154: sc->sc_dev = self;
155: sc->sc_i2c = ia->ia_tag;
156: sc->sc_addr = ia->ia_addr;
157:
158: if (self->dv_cfdata != NULL &&
159: self->dv_cfdata->cf_flags & TSLLUX_F_CS_PACKAGE)
160: sc->sc_cs_package = true;
161:
162: if (iic_acquire_bus(ia->ia_tag, 0) != 0) {
163: return;
164: }
165:
166: have_i2c = true;
167:
168: /* Power on the device and clear any pending interrupts. */
169: if (tsllux_write1(sc, TSL256x_REG_CONTROL | COMMAND6x_CLEAR,
170: CONTROL6x_POWER_ON)) {
171: aprint_error_dev(self, ": unable to power on device\n");
172: goto out;
173: }
174: sc->sc_poweron = 1;
175:
176: /* Make sure interrupts are disabled. */
177: if (tsllux_write1(sc, TSL256x_REG_INTERRUPT | COMMAND6x_CLEAR, 0)) {
178: aprint_error_dev(self, ": unable to disable interrupts\n");
179: goto out;
180: }
181:
182: aprint_naive("\n");
183: aprint_normal(": TSL256x Light-to-Digital converter%s\n",
184: sc->sc_cs_package ? " (CS package)" : "");
185:
186: /* Inititalize timing to reasonable defaults. */
187: sc->sc_auto_gain = true;
188: sc->sc_gain = TIMING6x_GAIN_16X;
189: if (tsllux_set_integration_time(sc, TIMING6x_INTEG_101ms)) {
190: aprint_error_dev(self, ": unable to set integration time\n");
191: goto out;
192: }
193:
194: tsllux_poweroff(sc);
195:
196: iic_release_bus(ia->ia_tag, 0);
197: have_i2c = false;
198:
199: tsllux_sysctl_attach(sc);
200:
201: sc->sc_sme = sysmon_envsys_create();
202: sc->sc_sme->sme_name = device_xname(self);
203: sc->sc_sme->sme_cookie = sc;
204: sc->sc_sme->sme_refresh = tsllux_sensors_refresh;
205:
206: sc->sc_sensor.units = ENVSYS_LUX;
207: sc->sc_sensor.state = ENVSYS_SINVALID;
208: snprintf(sc->sc_sensor.desc, sizeof(sc->sc_sensor.desc),
209: "ambient light");
210: sysmon_envsys_sensor_attach(sc->sc_sme, &sc->sc_sensor);
211:
212: sysmon_envsys_register(sc->sc_sme);
213:
214: out:
215: if (have_i2c) {
216: if (sc->sc_poweron)
217: tsllux_poweroff(sc);
218: iic_release_bus(ia->ia_tag, 0);
219: }
220: }
221:
222: static int
223: tsllux_sysctl_cs_package(SYSCTLFN_ARGS)
224: {
225: struct tsllux_softc *sc;
226: struct sysctlnode node;
227: int error;
228: u_int val;
229:
230: node = *rnode;
231: sc = node.sysctl_data;
232:
233: mutex_enter(&sc->sc_lock);
234: val = sc->sc_cs_package ? 1 : 0;
235: node.sysctl_data = &val;
236: error = sysctl_lookup(SYSCTLFN_CALL(&node));
237: if (error || newp == NULL) {
238: mutex_exit(&sc->sc_lock);
239: return (error);
240: }
241:
242: /* CS package indicator is used only in software; no need for I2C. */
243:
244: sc->sc_cs_package = val ? true : false;
245: mutex_exit(&sc->sc_lock);
246:
247: return (error);
248: }
249:
250: static int
251: tsllux_sysctl_autogain(SYSCTLFN_ARGS)
252: {
253: struct tsllux_softc *sc;
254: struct sysctlnode node;
255: int error;
256: u_int val;
257:
258: node = *rnode;
259: sc = node.sysctl_data;
260:
261: mutex_enter(&sc->sc_lock);
262: val = sc->sc_auto_gain ? 1 : 0;
263: node.sysctl_data = &val;
264: error = sysctl_lookup(SYSCTLFN_CALL(&node));
265: if (error || newp == NULL) {
266: mutex_exit(&sc->sc_lock);
267: return (error);
268: }
269:
270: /* Auto-gain is a software feature; no need for I2C. */
271:
272: error = tsllux_set_autogain(sc, val ? true : false);
273: mutex_exit(&sc->sc_lock);
274:
275: return (error);
276: }
277:
278: static int
279: tsllux_sysctl_gain(SYSCTLFN_ARGS)
280: {
281: struct tsllux_softc *sc;
282: struct sysctlnode node;
283: int error;
284: u_int val;
285: uint8_t new_gain;
286:
287: node = *rnode;
288: sc = node.sysctl_data;
289:
290: mutex_enter(&sc->sc_lock);
291:
292: switch (sc->sc_gain) {
293: case TIMING6x_GAIN_1X:
294: val = 1;
295: break;
296:
297: case TIMING6x_GAIN_16X:
298: val = 16;
299: break;
300:
301: default:
302: val = 1;
303: break;
304: }
305: node.sysctl_data = &val;
306: error = sysctl_lookup(SYSCTLFN_CALL(&node));
307: if (error || newp == NULL) {
308: mutex_exit(&sc->sc_lock);
309: return (error);
310: }
311:
312: switch (val) {
313: case 1:
314: new_gain = TIMING6x_GAIN_1X;
315: break;
316:
317: case 16:
318: new_gain = TIMING6x_GAIN_16X;
319: break;
320:
321: default:
322: mutex_exit(&sc->sc_lock);
323: return (EINVAL);
324: }
325:
326: if ((error = iic_acquire_bus(sc->sc_i2c, 0)) != 0) {
327: mutex_exit(&sc->sc_lock);
328: return (error);
329: }
330:
331: error = tsllux_set_gain(sc, new_gain);
332: iic_release_bus(sc->sc_i2c, 0);
333: mutex_exit(&sc->sc_lock);
334:
335: return (error);
336: }
337:
338: static int
339: tsllux_sysctl_itime(SYSCTLFN_ARGS)
340: {
341: struct tsllux_softc *sc;
342: struct sysctlnode node;
343: int error;
344: u_int val;
345: uint8_t new_itime;
346:
347: node = *rnode;
348: sc = node.sysctl_data;
349:
350: mutex_enter(&sc->sc_lock);
351:
352: switch (sc->sc_itime) {
353: case TIMING6x_INTEG_13_7ms:
354: val = 13;
355: break;
356:
357: case TIMING6x_INTEG_101ms:
358: val = 101;
359: break;
360:
361: case TIMING6x_INTEG_402ms:
362: default:
363: val = 402;
364: break;
365: }
366: node.sysctl_data = &val;
367: error = sysctl_lookup(SYSCTLFN_CALL(&node));
368: if (error || newp == NULL) {
369: mutex_exit(&sc->sc_lock);
370: return (error);
371: }
372:
373: switch (val) {
374: case 13:
375: case 14:
376: new_itime = TIMING6x_INTEG_13_7ms;
377: break;
378:
379: case 101:
380: new_itime = TIMING6x_INTEG_101ms;
381: break;
382:
383: case 402:
384: new_itime = TIMING6x_INTEG_402ms;
385: break;
386:
387: default:
388: mutex_exit(&sc->sc_lock);
389: return (EINVAL);
390: }
391:
392: if ((error = iic_acquire_bus(sc->sc_i2c, 0)) != 0) {
393: mutex_exit(&sc->sc_lock);
394: return (error);
395: }
396:
397: error = tsllux_set_integration_time(sc, new_itime);
398: iic_release_bus(sc->sc_i2c, 0);
399: mutex_exit(&sc->sc_lock);
400:
401: return (error);
402: }
403:
404: static void
405: tsllux_sysctl_attach(struct tsllux_softc *sc)
406: {
407: struct sysctllog **log = &sc->sc_sysctllog;
408: const struct sysctlnode *rnode, *cnode;
409: int error;
410:
411: error = sysctl_createv(log, 0, NULL, &rnode, CTLFLAG_PERMANENT,
412: CTLTYPE_NODE, device_xname(sc->sc_dev),
413: SYSCTL_DESCR("tsl256x control"),
414: NULL, 0, NULL, 0, CTL_HW, CTL_CREATE, CTL_EOL);
415: if (error)
416: return;
417:
418: error = sysctl_createv(log, 0, &rnode, &cnode,
419: CTLFLAG_PERMANENT|CTLFLAG_READWRITE, CTLTYPE_INT, "cs_package",
420: SYSCTL_DESCR("sensor in Chipscale (CS) package"),
421: tsllux_sysctl_cs_package, 0,
422: (void *)sc, 0, CTL_CREATE, CTL_EOL);
423: if (error)
424: return;
425:
426: error = sysctl_createv(log, 0, &rnode, &cnode,
427: CTLFLAG_PERMANENT|CTLFLAG_READWRITE, CTLTYPE_INT, "auto_gain",
428: SYSCTL_DESCR("auto-gain algorithm enabled"),
429: tsllux_sysctl_autogain, 0,
430: (void *)sc, 0, CTL_CREATE, CTL_EOL);
431: if (error)
432: return;
433:
434: error = sysctl_createv(log, 0, &rnode, &cnode,
435: CTLFLAG_PERMANENT|CTLFLAG_READWRITE, CTLTYPE_INT, "gain",
436: SYSCTL_DESCR("sensor gain"), tsllux_sysctl_gain, 0,
437: (void *)sc, 0, CTL_CREATE, CTL_EOL);
438: if (error)
439: return;
440:
441: error = sysctl_createv(log, 0, &rnode, &cnode,
442: CTLFLAG_PERMANENT|CTLFLAG_READWRITE, CTLTYPE_INT,
443: "integration_time",
444: SYSCTL_DESCR("ADC integration time"), tsllux_sysctl_itime, 0,
445: (void *)sc, 0, CTL_CREATE, CTL_EOL);
446: if (error)
447: return;
448: }
449:
450: static void
451: tsllux_sensors_refresh(struct sysmon_envsys *sme, envsys_data_t *edata)
452: {
453: struct tsllux_softc *sc = sme->sme_cookie;
454: uint32_t lux;
455: int error;
456:
457: if (edata != &sc->sc_sensor) {
458: edata->state = ENVSYS_SINVALID;
459: return;
460: }
461:
462: mutex_enter(&sc->sc_lock);
463:
464: if ((error = iic_acquire_bus(sc->sc_i2c, 0)) == 0) {
465: error = tsllux_get_lux(sc, &lux, NULL, NULL);
466: iic_release_bus(sc->sc_i2c, 0);
467: }
468:
469: if (error) {
470: edata->state = ENVSYS_SINVALID;
471: } else {
472: edata->value_cur = lux;
473: edata->state = ENVSYS_SVALID;
474: }
475:
476: mutex_exit(&sc->sc_lock);
477: }
478:
479: /*
480: * Allow pending interrupts to be cleared as part of another operation.
481: */
482: #define REGMASK6x (COMMAND6x_REGMASK | COMMAND6x_CLEAR)
483:
484: static int
485: tsllux_read1(struct tsllux_softc *sc, uint8_t reg, uint8_t *valp)
486: {
487: reg = (reg & REGMASK6x) | COMMAND6x_CMD;
488: return (iic_smbus_read_byte(sc->sc_i2c, sc->sc_addr, reg, valp, 0));
489: }
490:
491: static int
492: tsllux_read2(struct tsllux_softc *sc, uint8_t reg, uint16_t *valp)
493: {
494: reg = (reg & REGMASK6x) | COMMAND6x_CMD | COMMAND6x_WORD;
495: return (iic_smbus_read_word(sc->sc_i2c, sc->sc_addr, reg, valp, 0));
496: }
497:
498: static int
499: tsllux_write1(struct tsllux_softc *sc, uint8_t reg, uint8_t val)
500: {
501: reg = (reg & REGMASK6x) | COMMAND6x_CMD;
502: return (iic_smbus_write_byte(sc->sc_i2c, sc->sc_addr, reg, val, 0));
503: }
504:
505: #if 0
506: static int
507: tsllux_write2(struct tsllux_softc *sc, uint8_t reg, uint16_t val)
508: {
509: reg = (reg & REGMASK6x) | COMMAND6x_CMD | COMMAND6x_WORD;
510: return (iic_smbus_write_word(sc->sc_i2c, sc->sc_addr, reg, val, 0));
511: }
512: #endif
513:
514: #undef REGMASK
515:
516: static int
517: tsllux_poweron(struct tsllux_softc *sc)
518: {
519: int error;
520:
521: if (sc->sc_poweron++ == 0) {
522: uint8_t val;
523:
524: error = tsllux_write1(sc, TSL256x_REG_CONTROL,
525: CONTROL6x_POWER_ON);
526: if (error)
527: return (error);
528:
529: error = tsllux_read1(sc, TSL256x_REG_CONTROL, &val);
530: if (error)
531: return (error);
532:
533: if (val != CONTROL6x_POWER_ON) {
534: aprint_error_dev(sc->sc_dev,
535: "failed to power on sensor\n");
536: return (EIO);
537: }
538: }
539: return (0);
540: }
541:
542: static int
543: tsllux_poweroff(struct tsllux_softc *sc)
544: {
545: if (sc->sc_poweron && --sc->sc_poweron == 0)
546: return (tsllux_write1(sc, TSL256x_REG_CONTROL,
547: CONTROL6x_POWER_OFF));
548: return (0);
549: }
550:
551: static int
552: tsllux_set_integration_time(struct tsllux_softc *sc, uint8_t time)
553: {
554: int error;
555:
556: switch (time) {
557: case TIMING6x_INTEG_13_7ms:
558: case TIMING6x_INTEG_101ms:
559: case TIMING6x_INTEG_402ms:
560: break;
561:
562: default:
563: return (EINVAL);
564: }
565:
566: if ((error = tsllux_poweron(sc)) != 0)
567: return (error);
568:
569: if ((error = tsllux_write1(sc, TSL256x_REG_TIMING,
570: time | sc->sc_gain)) != 0)
571: goto out;
572:
573: sc->sc_itime = time;
574:
575: out:
576: (void) tsllux_poweroff(sc);
577: return (error);
578: }
579:
580: static int
581: tsllux_set_gain0(struct tsllux_softc *sc, uint8_t gain)
582: {
583: int error;
584:
585: if ((error = tsllux_write1(sc, TSL256x_REG_TIMING,
586: sc->sc_itime | gain)) != 0)
587: return (error);
588:
589: sc->sc_gain = gain;
590: return (0);
591: }
592:
593: static int
594: tsllux_set_gain(struct tsllux_softc *sc, uint8_t gain)
595: {
596: int error;
597:
598: switch (gain) {
599: case TIMING6x_GAIN_1X:
600: case TIMING6x_GAIN_16X:
601: break;
602:
603: default:
604: return (EINVAL);
605: }
606:
607: if ((error = tsllux_poweron(sc)) != 0)
608: return (error);
609:
610: if ((error = tsllux_set_gain0(sc, gain)) != 0)
611: goto out;
612:
613: sc->sc_auto_gain = false;
614:
615: out:
616: (void) tsllux_poweroff(sc);
617: return (error);
618: }
619:
620: static int
621: tsllux_set_autogain(struct tsllux_softc *sc, bool use_autogain)
622: {
623:
624: sc->sc_auto_gain = use_autogain;
625: return (0);
626: }
627:
628: static int
629: tsllux_wait_for_adcs(struct tsllux_softc *sc)
630: {
631: int ms;
632:
633: switch (sc->sc_itime) {
634: case TIMING6x_INTEG_13_7ms:
635: /* Wait 15ms for 13.7ms integration */
636: ms = 15;
637: break;
638:
639: case TIMING6x_INTEG_101ms:
640: /* Wait 120ms for 101ms integration */
641: ms = 120;
642: break;
643:
644: case TIMING6x_INTEG_402ms:
645: default:
646: /* Wait 450ms for 402ms integration */
647: ms = 450;
648: break;
649: }
650:
651: if (ms < hztoms(1)) {
652: /* Just busy-wait if we want to wait for less than 1 tick. */
653: delay(ms * 1000);
654: } else {
655: /* Round up one tick for the case where we sleep. */
656: (void) kpause("tslluxwait", false, mstohz(ms) + 1, NULL);
657: }
658:
659: return (0);
660: }
661:
662: static int
663: tsllux_read_adcs(struct tsllux_softc *sc, uint16_t *adc0valp,
664: uint16_t *adc1valp)
665: {
666: int error;
667:
668: if ((error = tsllux_read2(sc, TSL256x_REG_DATA0LOW, adc0valp)) == 0)
669: error = tsllux_read2(sc, TSL256x_REG_DATA1LOW, adc1valp);
670:
671: return (error);
672: }
673:
674: /*
675: * The following code is partially derived from Adafruit's TSL2561
676: * driver for Arduino (which was in turn derived from the data sheet),
677: * which carries this notice:
678: *
679: * @file Adafruit_TSL2561_U.cpp
680: *
681: * @mainpage Adafruit TSL2561 Light/Lux sensor driver
682: *
683: * @section intro_sec Introduction
684: *
685: * This is the documentation for Adafruit's TSL2561 driver for the
686: * Arduino platform. It is designed specifically to work with the
687: * Adafruit TSL2561 breakout: http://www.adafruit.com/products/439
688: *
689: * These sensors use I2C to communicate, 2 pins (SCL+SDA) are required
690: * to interface with the breakout.
691: *
692: * Adafruit invests time and resources providing this open source code,
693: * please support Adafruit and open-source hardware by purchasing
694: * products from Adafruit!
695: *
696: * @section dependencies Dependencies
697: *
698: * This library depends on <a href="https://github.com/adafruit/Adafruit_Sensor">
699: * Adafruit_Sensor</a> being present on your system. Please make sure you have
700: * installed the latest version before using this library.
701: *
702: * @section author Author
703: *
704: * Written by Kevin "KTOWN" Townsend for Adafruit Industries.
705: *
706: * @section license License
707: *
708: * BSD license, all text here must be included in any redistribution.
709: *
710: * @section HISTORY
711: *
712: * v2.0 - Rewrote driver for Adafruit_Sensor and Auto-Gain support, and
713: * added lux clipping check (returns 0 lux on sensor saturation)
714: * v1.0 - First release (previously TSL2561)
715: */
716:
717: static int
718: tsllux_read_sensors(struct tsllux_softc *sc, uint16_t *adc0p, uint16_t *adc1p)
719: {
720: int error;
721:
722: if ((error = tsllux_poweron(sc)) != 0)
723: return (error);
724:
725: if ((error = tsllux_wait_for_adcs(sc)) != 0)
726: goto out;
727:
728: error = tsllux_read_adcs(sc, adc0p, adc1p);
729:
730: out:
731: (void) tsllux_poweroff(sc);
732: return (error);
733: }
734:
735: /*
736: * Auto-gain thresholds:
737: */
738: #define TSL2561_AGC_THI_13MS (4850) /* Max value at Ti 13ms = 5047 */
739: #define TSL2561_AGC_TLO_13MS (100) /* Min value at Ti 13ms = 100 */
740: #define TSL2561_AGC_THI_101MS (36000) /* Max value at Ti 101ms = 37177 */
741: #define TSL2561_AGC_TLO_101MS (200) /* Min value at Ti 101ms = 200 */
742: #define TSL2561_AGC_THI_402MS (63000) /* Max value at Ti 402ms = 65535 */
743: #define TSL2561_AGC_TLO_402MS (500) /* Min value at Ti 402ms = 500 */
744:
745: static int
746: tsllux_get_sensor_data(struct tsllux_softc *sc, uint16_t *broadband,
747: uint16_t *ir)
748: {
749: int error = 0;
750: uint16_t adc0, adc1;
751: bool did_adjust_gain, valid;
752: uint16_t hi, lo;
753:
754: if (sc->sc_auto_gain == false) {
755: error = tsllux_read_sensors(sc, &adc0, &adc1);
756: goto out;
757: }
758:
759: /* Set the hi / lo threshold based on current integration time. */
760: switch (sc->sc_itime) {
761: case TIMING6x_INTEG_13_7ms:
762: hi = TSL2561_AGC_THI_13MS;
763: lo = TSL2561_AGC_TLO_13MS;
764: break;
765:
766: case TIMING6x_INTEG_101ms:
767: hi = TSL2561_AGC_THI_101MS;
768: lo = TSL2561_AGC_TLO_101MS;
769: break;
770:
771: case TIMING6x_INTEG_402ms:
772: default:
773: hi = TSL2561_AGC_THI_402MS;
774: lo = TSL2561_AGC_TLO_402MS;
775: }
776:
777: /* Read data and adjust the gain until we have a valid range. */
778: for (valid = false, did_adjust_gain = false; valid == false; ) {
779: if ((error = tsllux_read_sensors(sc, &adc0, &adc1)) != 0)
780: goto out;
781:
782: if (did_adjust_gain == false) {
783: if (adc0 < lo && sc->sc_gain == TIMING6x_GAIN_1X) {
784: /* Increase the gain and try again. */
785: if ((error =
786: tsllux_set_gain0(sc,
787: TIMING6x_GAIN_16X)) != 0)
788: goto out;
789: did_adjust_gain = true;
790: } else if (adc0 > hi &&
791: sc->sc_gain == TIMING6x_GAIN_16X) {
792: /* Decrease the gain and try again. */
793: if ((error =
794: tsllux_set_gain0(sc,
795: TIMING6x_GAIN_1X)) != 0)
796: goto out;
797: did_adjust_gain = true;
798: } else {
799: /*
800: * Reading is either valid or we're already
801: * at the chip's limits.
802: */
803: valid = true;
804: }
805: } else {
806: /*
807: * If we've already adjust the gain once, just
808: * return the new results. This avoids endless
809: * loops where a value is at one extre pre-gain
810: * and at the other extreme post-gain.
811: */
812: valid = true;
813: }
814: }
815:
816: out:
817: if (error == 0) {
818: if (broadband != NULL)
819: *broadband = adc0;
820: if (ir != NULL)
821: *ir = adc1;
822: }
823: return (error);
824: }
825:
826: /*
827: * Clipping thresholds:
828: */
829: #define TSL2561_CLIPPING_13MS (4900)
830: #define TSL2561_CLIPPING_101MS (37000)
831: #define TSL2561_CLIPPING_402MS (65000)
832:
833: /*
834: * Scaling factors:
835: */
836: #define TSL2561_LUX_LUXSCALE (14) /* Scale by 2^14 */
837: #define TSL2561_LUX_RATIOSCALE (9) /* Scale ratio by 2^9 */
838: #define TSL2561_LUX_CHSCALE (10) /* Scale channel values by 2^10 */
839: #define TSL2561_LUX_CHSCALE_TINT0 (0x7517) /* 322/11 * 2^TSL2561_LUX_CHSCALE */
840: #define TSL2561_LUX_CHSCALE_TINT1 (0x0FE7) /* 322/81 * 2^TSL2561_LUX_CHSCALE */
841:
842: /*
843: * Lux factors (the datasheet explains how these magic constants
844: * are used):
845: */
846: /* T, FN and CL package values */
847: #define TSL2561_LUX_K1T (0x0040) /* 0.125 * 2^RATIO_SCALE */
848: #define TSL2561_LUX_B1T (0x01f2) /* 0.0304 * 2^LUX_SCALE */
849: #define TSL2561_LUX_M1T (0x01be) /* 0.0272 * 2^LUX_SCALE */
850: #define TSL2561_LUX_K2T (0x0080) /* 0.250 * 2^RATIO_SCALE */
851: #define TSL2561_LUX_B2T (0x0214) /* 0.0325 * 2^LUX_SCALE */
852: #define TSL2561_LUX_M2T (0x02d1) /* 0.0440 * 2^LUX_SCALE */
853: #define TSL2561_LUX_K3T (0x00c0) /* 0.375 * 2^RATIO_SCALE */
854: #define TSL2561_LUX_B3T (0x023f) /* 0.0351 * 2^LUX_SCALE */
855: #define TSL2561_LUX_M3T (0x037b) /* 0.0544 * 2^LUX_SCALE */
856: #define TSL2561_LUX_K4T (0x0100) /* 0.50 * 2^RATIO_SCALE */
857: #define TSL2561_LUX_B4T (0x0270) /* 0.0381 * 2^LUX_SCALE */
858: #define TSL2561_LUX_M4T (0x03fe) /* 0.0624 * 2^LUX_SCALE */
859: #define TSL2561_LUX_K5T (0x0138) /* 0.61 * 2^RATIO_SCALE */
860: #define TSL2561_LUX_B5T (0x016f) /* 0.0224 * 2^LUX_SCALE */
861: #define TSL2561_LUX_M5T (0x01fc) /* 0.0310 * 2^LUX_SCALE */
862: #define TSL2561_LUX_K6T (0x019a) /* 0.80 * 2^RATIO_SCALE */
863: #define TSL2561_LUX_B6T (0x00d2) /* 0.0128 * 2^LUX_SCALE */
864: #define TSL2561_LUX_M6T (0x00fb) /* 0.0153 * 2^LUX_SCALE */
865: #define TSL2561_LUX_K7T (0x029a) /* 1.3 * 2^RATIO_SCALE */
866: #define TSL2561_LUX_B7T (0x0018) /* 0.00146 * 2^LUX_SCALE */
867: #define TSL2561_LUX_M7T (0x0012) /* 0.00112 * 2^LUX_SCALE */
868: #define TSL2561_LUX_K8T (0x029a) /* 1.3 * 2^RATIO_SCALE */
869: #define TSL2561_LUX_B8T (0x0000) /* 0.000 * 2^LUX_SCALE */
870: #define TSL2561_LUX_M8T (0x0000) /* 0.000 * 2^LUX_SCALE */
871:
872: /* CS package values */
873: #define TSL2561_LUX_K1C (0x0043) /* 0.130 * 2^RATIO_SCALE */
874: #define TSL2561_LUX_B1C (0x0204) /* 0.0315 * 2^LUX_SCALE */
875: #define TSL2561_LUX_M1C (0x01ad) /* 0.0262 * 2^LUX_SCALE */
876: #define TSL2561_LUX_K2C (0x0085) /* 0.260 * 2^RATIO_SCALE */
877: #define TSL2561_LUX_B2C (0x0228) /* 0.0337 * 2^LUX_SCALE */
878: #define TSL2561_LUX_M2C (0x02c1) /* 0.0430 * 2^LUX_SCALE */
879: #define TSL2561_LUX_K3C (0x00c8) /* 0.390 * 2^RATIO_SCALE */
880: #define TSL2561_LUX_B3C (0x0253) /* 0.0363 * 2^LUX_SCALE */
881: #define TSL2561_LUX_M3C (0x0363) /* 0.0529 * 2^LUX_SCALE */
882: #define TSL2561_LUX_K4C (0x010a) /* 0.520 * 2^RATIO_SCALE */
883: #define TSL2561_LUX_B4C (0x0282) /* 0.0392 * 2^LUX_SCALE */
884: #define TSL2561_LUX_M4C (0x03df) /* 0.0605 * 2^LUX_SCALE */
885: #define TSL2561_LUX_K5C (0x014d) /* 0.65 * 2^RATIO_SCALE */
886: #define TSL2561_LUX_B5C (0x0177) /* 0.0229 * 2^LUX_SCALE */
887: #define TSL2561_LUX_M5C (0x01dd) /* 0.0291 * 2^LUX_SCALE */
888: #define TSL2561_LUX_K6C (0x019a) /* 0.80 * 2^RATIO_SCALE */
889: #define TSL2561_LUX_B6C (0x0101) /* 0.0157 * 2^LUX_SCALE */
890: #define TSL2561_LUX_M6C (0x0127) /* 0.0180 * 2^LUX_SCALE */
891: #define TSL2561_LUX_K7C (0x029a) /* 1.3 * 2^RATIO_SCALE */
892: #define TSL2561_LUX_B7C (0x0037) /* 0.00338 * 2^LUX_SCALE */
893: #define TSL2561_LUX_M7C (0x002b) /* 0.00260 * 2^LUX_SCALE */
894: #define TSL2561_LUX_K8C (0x029a) /* 1.3 * 2^RATIO_SCALE */
895: #define TSL2561_LUX_B8C (0x0000) /* 0.000 * 2^LUX_SCALE */
896: #define TSL2561_LUX_M8C (0x0000) /* 0.000 * 2^LUX_SCALE */
897:
898: struct lux_factor_table_entry {
899: uint16_t k;
900: uint16_t b;
901: uint16_t m;
902: };
903:
904: static const struct lux_factor_table_entry lux_factor_table[] = {
905: { TSL2561_LUX_K1T, TSL2561_LUX_B1T, TSL2561_LUX_M1T },
906: { TSL2561_LUX_K2T, TSL2561_LUX_B2T, TSL2561_LUX_M2T },
907: { TSL2561_LUX_K3T, TSL2561_LUX_B3T, TSL2561_LUX_M3T },
908: { TSL2561_LUX_K4T, TSL2561_LUX_B4T, TSL2561_LUX_M4T },
909: { TSL2561_LUX_K5T, TSL2561_LUX_B5T, TSL2561_LUX_M5T },
910: { TSL2561_LUX_K6T, TSL2561_LUX_B6T, TSL2561_LUX_M6T },
911: { TSL2561_LUX_K7T, TSL2561_LUX_B7T, TSL2561_LUX_M7T },
912: { TSL2561_LUX_K8T, TSL2561_LUX_B8T, TSL2561_LUX_M8T },
913: };
914: static const int lux_factor_table_last_entry =
915: (sizeof(lux_factor_table) / sizeof(lux_factor_table[0])) - 1;
916:
917: static const struct lux_factor_table_entry lux_factor_table_cs_package[] = {
918: { TSL2561_LUX_K1C, TSL2561_LUX_B1C, TSL2561_LUX_M1C },
919: { TSL2561_LUX_K2C, TSL2561_LUX_B2C, TSL2561_LUX_M2C },
920: { TSL2561_LUX_K3C, TSL2561_LUX_B3C, TSL2561_LUX_M3C },
921: { TSL2561_LUX_K4C, TSL2561_LUX_B4C, TSL2561_LUX_M4C },
922: { TSL2561_LUX_K5C, TSL2561_LUX_B5C, TSL2561_LUX_M5C },
923: { TSL2561_LUX_K6C, TSL2561_LUX_B6C, TSL2561_LUX_M6C },
924: { TSL2561_LUX_K7C, TSL2561_LUX_B7C, TSL2561_LUX_M7C },
925: { TSL2561_LUX_K8C, TSL2561_LUX_B8C, TSL2561_LUX_M8C },
926: };
927: static const int lux_factor_table_cs_package_last_entry =
928: (sizeof(lux_factor_table_cs_package) /
929: sizeof(lux_factor_table_cs_package[0])) - 1;
930:
931: static int
932: tsllux_get_lux(struct tsllux_softc *sc, uint32_t *luxp,
933: uint16_t *raw_broadband, uint16_t *raw_ir)
934: {
935: uint32_t channel0, channel1, scale, ratio, lux = 0;
936: uint16_t broadband, ir;
937: uint16_t clip_threshold;
938: const struct lux_factor_table_entry *table;
939: int idx, last_entry, error;
940: int32_t temp;
941:
942: if ((error = tsllux_get_sensor_data(sc, &broadband, &ir)) != 0)
943: return (error);
944:
945: if (luxp == NULL) {
946: /*
947: * Caller doesn't want the calculated Lux value, so
948: * don't bother calculating it. Maybe they just want
949: * the raw sensor data?
950: */
951: goto out;
952: }
953:
954: /*
955: * Check to see if the sensor is saturated. If so,
956: * just return a "max brightness" value.
957: */
958: switch (sc->sc_itime) {
959: case TIMING6x_INTEG_13_7ms:
960: clip_threshold = TSL2561_CLIPPING_13MS;
961: break;
962:
963: case TIMING6x_INTEG_101ms:
964: clip_threshold = TSL2561_CLIPPING_101MS;
965: break;
966:
967: case TIMING6x_INTEG_402ms:
968: default:
969: clip_threshold = TSL2561_CLIPPING_402MS;
970: break;
971: }
972:
973: if (broadband > clip_threshold || ir > clip_threshold) {
974: lux = 65536;
975: goto out;
976: }
977:
978: /* Get correct scale factor based on integration time. */
979: switch (sc->sc_itime) {
980: case TIMING6x_INTEG_13_7ms:
981: scale = TSL2561_LUX_CHSCALE_TINT0;
982: break;
983:
984: case TIMING6x_INTEG_101ms:
985: scale = TSL2561_LUX_CHSCALE_TINT1;
986: break;
987:
988: case TIMING6x_INTEG_402ms:
989: default:
990: scale = (1 << TSL2561_LUX_CHSCALE);
991: }
992:
993: /* Scale for gain. */
994: if (sc->sc_gain == TIMING6x_GAIN_1X)
995: scale <<= 4;
996:
997: /* Scale the channel values. */
998: channel0 = ((uint32_t)broadband * scale) >> TSL2561_LUX_CHSCALE;
999: channel1 = ((uint32_t)ir * scale) >> TSL2561_LUX_CHSCALE;
1000:
1001: /* Find the ratio of the channel values (ir / broadband) */
1002: if (channel0 != 0)
1003: ratio = (channel1 << (TSL2561_LUX_RATIOSCALE + 1)) / channel0;
1004: else
1005: ratio = 0;
1006:
1007: /* Round the ratio value. */
1008: ratio = (ratio + 1) >> 1;
1009:
1010: if (sc->sc_cs_package) {
1011: table = lux_factor_table_cs_package;
1012: last_entry = lux_factor_table_cs_package_last_entry;
1013: } else {
1014: table = lux_factor_table;
1015: last_entry = lux_factor_table_last_entry;
1016: }
1017:
1018: /*
1019: * The table is arranged such that we compare <= against
1020: * the key, and if all else fails, we use the last entry.
1021: * The pseudo-code in the data sheet shows what's going on.
1022: */
1023: for (idx = 0; idx < last_entry; idx++) {
1024: if (ratio <= table[idx].k)
1025: break;
1026: }
1027:
1028: temp = ((channel0 * table[idx].b) - (channel1 * table[idx].m));
1029:
1030: /* Do not allow negative Lux value. */
1031: if (temp < 0)
1032: temp = 0;
1033:
1034: /* Round lsb (2^(LUX_SCALE-1)) */
1035: temp += (1 << (TSL2561_LUX_LUXSCALE-1));
1036:
1037: /* Strip off fractional portion */
1038: lux = temp >> TSL2561_LUX_LUXSCALE;
1039:
1040: out:
1041: if (error == 0) {
1042: if (luxp != NULL)
1043: *luxp = lux;
1044: if (raw_broadband != NULL)
1045: *raw_broadband = broadband;
1046: if (raw_ir != NULL)
1047: *raw_ir = ir;
1048: }
1049: return (error);
1050: }
CVSweb <webmaster@jp.NetBSD.org>