1*3aa5a3aeSriastradh /* $NetBSD: tsllux.c,v 1.4 2022/02/12 03:24:35 riastradh Exp $ */
2614805fdSthorpej
3614805fdSthorpej /*-
4614805fdSthorpej * Copyright (c) 2018 Jason R. Thorpe
5614805fdSthorpej * All rights reserved.
6614805fdSthorpej *
7614805fdSthorpej * Redistribution and use in source and binary forms, with or without
8614805fdSthorpej * modification, are permitted provided that the following conditions
9614805fdSthorpej * are met:
10614805fdSthorpej * 1. Redistributions of source code must retain the above copyright
11614805fdSthorpej * notice, this list of conditions and the following disclaimer.
12614805fdSthorpej * 2. Redistributions in binary form must reproduce the above copyright
13614805fdSthorpej * notice, this list of conditions and the following disclaimer in the
14614805fdSthorpej * documentation and/or other materials provided with the distribution.
15614805fdSthorpej *
16614805fdSthorpej * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
17614805fdSthorpej * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
18614805fdSthorpej * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19614805fdSthorpej * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
20614805fdSthorpej * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
21614805fdSthorpej * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
22614805fdSthorpej * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
23614805fdSthorpej * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
24614805fdSthorpej * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
25614805fdSthorpej * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
26614805fdSthorpej * POSSIBILITY OF SUCH DAMAGE.
27614805fdSthorpej */
28614805fdSthorpej
29614805fdSthorpej #include <sys/cdefs.h>
30*3aa5a3aeSriastradh __KERNEL_RCSID(0, "$NetBSD: tsllux.c,v 1.4 2022/02/12 03:24:35 riastradh Exp $");
31614805fdSthorpej
32614805fdSthorpej #include <sys/param.h>
33614805fdSthorpej #include <sys/systm.h>
34614805fdSthorpej #include <sys/device.h>
35614805fdSthorpej #include <sys/conf.h>
36614805fdSthorpej #include <sys/bus.h>
37614805fdSthorpej #include <sys/kernel.h>
38614805fdSthorpej #include <sys/kmem.h>
39614805fdSthorpej #include <sys/mutex.h>
40614805fdSthorpej #include <sys/proc.h>
41614805fdSthorpej #include <sys/sysctl.h>
42614805fdSthorpej
43614805fdSthorpej #include <dev/i2c/i2cvar.h>
44614805fdSthorpej #include <dev/i2c/tsl256xreg.h>
45614805fdSthorpej
46614805fdSthorpej #include <dev/sysmon/sysmonvar.h>
47614805fdSthorpej
48614805fdSthorpej struct tsllux_softc {
49614805fdSthorpej device_t sc_dev;
50614805fdSthorpej i2c_tag_t sc_i2c;
51614805fdSthorpej i2c_addr_t sc_addr;
52614805fdSthorpej
53614805fdSthorpej uint32_t sc_poweron;
54614805fdSthorpej
55614805fdSthorpej /*
56614805fdSthorpej * Locking order is:
57614805fdSthorpej * tsllux mutex -> i2c bus
58614805fdSthorpej */
59614805fdSthorpej kmutex_t sc_lock;
60614805fdSthorpej
61614805fdSthorpej uint8_t sc_itime;
62614805fdSthorpej uint8_t sc_gain;
63614805fdSthorpej bool sc_cs_package;
64614805fdSthorpej bool sc_auto_gain;
65614805fdSthorpej
66614805fdSthorpej struct sysmon_envsys *sc_sme;
67614805fdSthorpej envsys_data_t sc_sensor;
68614805fdSthorpej
69614805fdSthorpej struct sysctllog *sc_sysctllog;
70614805fdSthorpej };
71614805fdSthorpej
72614805fdSthorpej #define TSLLUX_F_CS_PACKAGE 0x01
73614805fdSthorpej
74614805fdSthorpej static int tsllux_match(device_t, cfdata_t, void *);
75614805fdSthorpej static void tsllux_attach(device_t, device_t, void *);
76614805fdSthorpej
77614805fdSthorpej CFATTACH_DECL_NEW(tsllux, sizeof(struct tsllux_softc),
78614805fdSthorpej tsllux_match, tsllux_attach, NULL, NULL);
79614805fdSthorpej
80614805fdSthorpej static const struct device_compatible_entry tsllux_compat_data[] = {
817246a945Sthorpej { .compat = "amstaos,tsl2560" },
827246a945Sthorpej { .compat = "amstaos,tsl2561" },
8318f3098cSthorpej DEVICE_COMPAT_EOL
84614805fdSthorpej };
85614805fdSthorpej
86614805fdSthorpej static int tsllux_read1(struct tsllux_softc *, uint8_t, uint8_t *);
87614805fdSthorpej static int tsllux_read2(struct tsllux_softc *, uint8_t, uint16_t *);
88614805fdSthorpej static int tsllux_write1(struct tsllux_softc *, uint8_t, uint8_t);
89614805fdSthorpej #if 0
90614805fdSthorpej static int tsllux_write2(struct tsllux_softc *, uint8_t, uint16_t);
91614805fdSthorpej #endif
92614805fdSthorpej
93614805fdSthorpej static void tsllux_sysctl_attach(struct tsllux_softc *);
94614805fdSthorpej
95614805fdSthorpej static int tsllux_poweron(struct tsllux_softc *);
96614805fdSthorpej static int tsllux_poweroff(struct tsllux_softc *);
97614805fdSthorpej
98614805fdSthorpej static int tsllux_set_integration_time(struct tsllux_softc *, uint8_t);
99614805fdSthorpej static int tsllux_set_gain(struct tsllux_softc *, uint8_t);
100614805fdSthorpej static int tsllux_set_autogain(struct tsllux_softc *, bool);
101614805fdSthorpej
102614805fdSthorpej static int tsllux_get_lux(struct tsllux_softc *, uint32_t *,
103614805fdSthorpej uint16_t *, uint16_t *);
104614805fdSthorpej
105614805fdSthorpej static void tsllux_sensors_refresh(struct sysmon_envsys *, envsys_data_t *);
106614805fdSthorpej
107614805fdSthorpej static int
tsllux_match(device_t parent,cfdata_t match,void * aux)108614805fdSthorpej tsllux_match(device_t parent, cfdata_t match, void *aux)
109614805fdSthorpej {
110614805fdSthorpej struct i2c_attach_args *ia = aux;
111614805fdSthorpej uint8_t id_reg;
112614805fdSthorpej int error, match_result;
113614805fdSthorpej
114614805fdSthorpej if (iic_use_direct_match(ia, match, tsllux_compat_data, &match_result))
115614805fdSthorpej return (match_result);
116614805fdSthorpej
117614805fdSthorpej switch (ia->ia_addr) {
118614805fdSthorpej case TSL256x_SLAVEADDR_GND:
119614805fdSthorpej case TSL256x_SLAVEADDR_FLOAT:
120614805fdSthorpej case TSL256x_SLAVEADDR_VDD:
121614805fdSthorpej break;
122614805fdSthorpej
123614805fdSthorpej default:
124614805fdSthorpej return (0);
125614805fdSthorpej }
126614805fdSthorpej
127614805fdSthorpej if (iic_acquire_bus(ia->ia_tag, 0) != 0)
128614805fdSthorpej return (0);
129614805fdSthorpej error = iic_smbus_read_byte(ia->ia_tag, ia->ia_addr,
130614805fdSthorpej TSL256x_REG_ID | COMMAND6x_CMD, &id_reg, 0);
131614805fdSthorpej iic_release_bus(ia->ia_tag, 0);
132614805fdSthorpej
133614805fdSthorpej if (error)
134614805fdSthorpej return (0);
135614805fdSthorpej
136614805fdSthorpej /* XXX This loses if we have a 2560 rev. 0. */
137614805fdSthorpej if (id_reg == 0)
138614805fdSthorpej return (I2C_MATCH_ADDRESS_ONLY);
139614805fdSthorpej
140614805fdSthorpej return (I2C_MATCH_ADDRESS_AND_PROBE);
141614805fdSthorpej }
142614805fdSthorpej
143614805fdSthorpej static void
tsllux_attach(device_t parent,device_t self,void * aux)144614805fdSthorpej tsllux_attach(device_t parent, device_t self, void *aux)
145614805fdSthorpej {
146614805fdSthorpej struct tsllux_softc *sc = device_private(self);
147614805fdSthorpej struct i2c_attach_args *ia = aux;
148614805fdSthorpej bool have_i2c;
149614805fdSthorpej
150614805fdSthorpej /* XXX IPL_NONE changes when we support threshold interrupts. */
151614805fdSthorpej mutex_init(&sc->sc_lock, MUTEX_DEFAULT, IPL_NONE);
152614805fdSthorpej
153614805fdSthorpej sc->sc_dev = self;
154614805fdSthorpej sc->sc_i2c = ia->ia_tag;
155614805fdSthorpej sc->sc_addr = ia->ia_addr;
156614805fdSthorpej
157*3aa5a3aeSriastradh if (device_cfdata(self)->cf_flags & TSLLUX_F_CS_PACKAGE)
158614805fdSthorpej sc->sc_cs_package = true;
159614805fdSthorpej
160614805fdSthorpej if (iic_acquire_bus(ia->ia_tag, 0) != 0) {
161614805fdSthorpej return;
162614805fdSthorpej }
163614805fdSthorpej
164614805fdSthorpej have_i2c = true;
165614805fdSthorpej
166614805fdSthorpej /* Power on the device and clear any pending interrupts. */
167614805fdSthorpej if (tsllux_write1(sc, TSL256x_REG_CONTROL | COMMAND6x_CLEAR,
168614805fdSthorpej CONTROL6x_POWER_ON)) {
169614805fdSthorpej aprint_error_dev(self, ": unable to power on device\n");
170614805fdSthorpej goto out;
171614805fdSthorpej }
172614805fdSthorpej sc->sc_poweron = 1;
173614805fdSthorpej
174614805fdSthorpej /* Make sure interrupts are disabled. */
175614805fdSthorpej if (tsllux_write1(sc, TSL256x_REG_INTERRUPT | COMMAND6x_CLEAR, 0)) {
176614805fdSthorpej aprint_error_dev(self, ": unable to disable interrupts\n");
177614805fdSthorpej goto out;
178614805fdSthorpej }
179614805fdSthorpej
180614805fdSthorpej aprint_naive("\n");
181614805fdSthorpej aprint_normal(": TSL256x Light-to-Digital converter%s\n",
182614805fdSthorpej sc->sc_cs_package ? " (CS package)" : "");
183614805fdSthorpej
184614805fdSthorpej /* Inititalize timing to reasonable defaults. */
185614805fdSthorpej sc->sc_auto_gain = true;
186614805fdSthorpej sc->sc_gain = TIMING6x_GAIN_16X;
187614805fdSthorpej if (tsllux_set_integration_time(sc, TIMING6x_INTEG_101ms)) {
188614805fdSthorpej aprint_error_dev(self, ": unable to set integration time\n");
189614805fdSthorpej goto out;
190614805fdSthorpej }
191614805fdSthorpej
192614805fdSthorpej tsllux_poweroff(sc);
193614805fdSthorpej
194614805fdSthorpej iic_release_bus(ia->ia_tag, 0);
195614805fdSthorpej have_i2c = false;
196614805fdSthorpej
197614805fdSthorpej tsllux_sysctl_attach(sc);
198614805fdSthorpej
199614805fdSthorpej sc->sc_sme = sysmon_envsys_create();
200614805fdSthorpej sc->sc_sme->sme_name = device_xname(self);
201614805fdSthorpej sc->sc_sme->sme_cookie = sc;
202614805fdSthorpej sc->sc_sme->sme_refresh = tsllux_sensors_refresh;
203614805fdSthorpej
204614805fdSthorpej sc->sc_sensor.units = ENVSYS_LUX;
205614805fdSthorpej sc->sc_sensor.state = ENVSYS_SINVALID;
206614805fdSthorpej snprintf(sc->sc_sensor.desc, sizeof(sc->sc_sensor.desc),
207614805fdSthorpej "ambient light");
208614805fdSthorpej sysmon_envsys_sensor_attach(sc->sc_sme, &sc->sc_sensor);
209614805fdSthorpej
210614805fdSthorpej sysmon_envsys_register(sc->sc_sme);
211614805fdSthorpej
212614805fdSthorpej out:
213614805fdSthorpej if (have_i2c) {
214614805fdSthorpej if (sc->sc_poweron)
215614805fdSthorpej tsllux_poweroff(sc);
216614805fdSthorpej iic_release_bus(ia->ia_tag, 0);
217614805fdSthorpej }
218614805fdSthorpej }
219614805fdSthorpej
220614805fdSthorpej static int
tsllux_sysctl_cs_package(SYSCTLFN_ARGS)221614805fdSthorpej tsllux_sysctl_cs_package(SYSCTLFN_ARGS)
222614805fdSthorpej {
223614805fdSthorpej struct tsllux_softc *sc;
224614805fdSthorpej struct sysctlnode node;
225614805fdSthorpej int error;
226614805fdSthorpej u_int val;
227614805fdSthorpej
228614805fdSthorpej node = *rnode;
229614805fdSthorpej sc = node.sysctl_data;
230614805fdSthorpej
231614805fdSthorpej mutex_enter(&sc->sc_lock);
232614805fdSthorpej val = sc->sc_cs_package ? 1 : 0;
233614805fdSthorpej node.sysctl_data = &val;
234614805fdSthorpej error = sysctl_lookup(SYSCTLFN_CALL(&node));
235614805fdSthorpej if (error || newp == NULL) {
236614805fdSthorpej mutex_exit(&sc->sc_lock);
237614805fdSthorpej return (error);
238614805fdSthorpej }
239614805fdSthorpej
240614805fdSthorpej /* CS package indicator is used only in software; no need for I2C. */
241614805fdSthorpej
242614805fdSthorpej sc->sc_cs_package = val ? true : false;
243614805fdSthorpej mutex_exit(&sc->sc_lock);
244614805fdSthorpej
245614805fdSthorpej return (error);
246614805fdSthorpej }
247614805fdSthorpej
248614805fdSthorpej static int
tsllux_sysctl_autogain(SYSCTLFN_ARGS)249614805fdSthorpej tsllux_sysctl_autogain(SYSCTLFN_ARGS)
250614805fdSthorpej {
251614805fdSthorpej struct tsllux_softc *sc;
252614805fdSthorpej struct sysctlnode node;
253614805fdSthorpej int error;
254614805fdSthorpej u_int val;
255614805fdSthorpej
256614805fdSthorpej node = *rnode;
257614805fdSthorpej sc = node.sysctl_data;
258614805fdSthorpej
259614805fdSthorpej mutex_enter(&sc->sc_lock);
260614805fdSthorpej val = sc->sc_auto_gain ? 1 : 0;
261614805fdSthorpej node.sysctl_data = &val;
262614805fdSthorpej error = sysctl_lookup(SYSCTLFN_CALL(&node));
263614805fdSthorpej if (error || newp == NULL) {
264614805fdSthorpej mutex_exit(&sc->sc_lock);
265614805fdSthorpej return (error);
266614805fdSthorpej }
267614805fdSthorpej
268614805fdSthorpej /* Auto-gain is a software feature; no need for I2C. */
269614805fdSthorpej
270614805fdSthorpej error = tsllux_set_autogain(sc, val ? true : false);
271614805fdSthorpej mutex_exit(&sc->sc_lock);
272614805fdSthorpej
273614805fdSthorpej return (error);
274614805fdSthorpej }
275614805fdSthorpej
276614805fdSthorpej static int
tsllux_sysctl_gain(SYSCTLFN_ARGS)277614805fdSthorpej tsllux_sysctl_gain(SYSCTLFN_ARGS)
278614805fdSthorpej {
279614805fdSthorpej struct tsllux_softc *sc;
280614805fdSthorpej struct sysctlnode node;
281614805fdSthorpej int error;
282614805fdSthorpej u_int val;
283614805fdSthorpej uint8_t new_gain;
284614805fdSthorpej
285614805fdSthorpej node = *rnode;
286614805fdSthorpej sc = node.sysctl_data;
287614805fdSthorpej
288614805fdSthorpej mutex_enter(&sc->sc_lock);
289614805fdSthorpej
290614805fdSthorpej switch (sc->sc_gain) {
291614805fdSthorpej case TIMING6x_GAIN_1X:
292614805fdSthorpej val = 1;
293614805fdSthorpej break;
294614805fdSthorpej
295614805fdSthorpej case TIMING6x_GAIN_16X:
296614805fdSthorpej val = 16;
297614805fdSthorpej break;
298614805fdSthorpej
299614805fdSthorpej default:
300614805fdSthorpej val = 1;
301614805fdSthorpej break;
302614805fdSthorpej }
303614805fdSthorpej node.sysctl_data = &val;
304614805fdSthorpej error = sysctl_lookup(SYSCTLFN_CALL(&node));
305614805fdSthorpej if (error || newp == NULL) {
306614805fdSthorpej mutex_exit(&sc->sc_lock);
307614805fdSthorpej return (error);
308614805fdSthorpej }
309614805fdSthorpej
310614805fdSthorpej switch (val) {
311614805fdSthorpej case 1:
312614805fdSthorpej new_gain = TIMING6x_GAIN_1X;
313614805fdSthorpej break;
314614805fdSthorpej
315614805fdSthorpej case 16:
316614805fdSthorpej new_gain = TIMING6x_GAIN_16X;
317614805fdSthorpej break;
318614805fdSthorpej
319614805fdSthorpej default:
320614805fdSthorpej mutex_exit(&sc->sc_lock);
321614805fdSthorpej return (EINVAL);
322614805fdSthorpej }
323614805fdSthorpej
324614805fdSthorpej if ((error = iic_acquire_bus(sc->sc_i2c, 0)) != 0) {
325614805fdSthorpej mutex_exit(&sc->sc_lock);
326614805fdSthorpej return (error);
327614805fdSthorpej }
328614805fdSthorpej
329614805fdSthorpej error = tsllux_set_gain(sc, new_gain);
330614805fdSthorpej iic_release_bus(sc->sc_i2c, 0);
331614805fdSthorpej mutex_exit(&sc->sc_lock);
332614805fdSthorpej
333614805fdSthorpej return (error);
334614805fdSthorpej }
335614805fdSthorpej
336614805fdSthorpej static int
tsllux_sysctl_itime(SYSCTLFN_ARGS)337614805fdSthorpej tsllux_sysctl_itime(SYSCTLFN_ARGS)
338614805fdSthorpej {
339614805fdSthorpej struct tsllux_softc *sc;
340614805fdSthorpej struct sysctlnode node;
341614805fdSthorpej int error;
342614805fdSthorpej u_int val;
343614805fdSthorpej uint8_t new_itime;
344614805fdSthorpej
345614805fdSthorpej node = *rnode;
346614805fdSthorpej sc = node.sysctl_data;
347614805fdSthorpej
348614805fdSthorpej mutex_enter(&sc->sc_lock);
349614805fdSthorpej
350614805fdSthorpej switch (sc->sc_itime) {
351614805fdSthorpej case TIMING6x_INTEG_13_7ms:
352614805fdSthorpej val = 13;
353614805fdSthorpej break;
354614805fdSthorpej
355614805fdSthorpej case TIMING6x_INTEG_101ms:
356614805fdSthorpej val = 101;
357614805fdSthorpej break;
358614805fdSthorpej
359614805fdSthorpej case TIMING6x_INTEG_402ms:
360614805fdSthorpej default:
361614805fdSthorpej val = 402;
362614805fdSthorpej break;
363614805fdSthorpej }
364614805fdSthorpej node.sysctl_data = &val;
365614805fdSthorpej error = sysctl_lookup(SYSCTLFN_CALL(&node));
366614805fdSthorpej if (error || newp == NULL) {
367614805fdSthorpej mutex_exit(&sc->sc_lock);
368614805fdSthorpej return (error);
369614805fdSthorpej }
370614805fdSthorpej
371614805fdSthorpej switch (val) {
372614805fdSthorpej case 13:
373614805fdSthorpej case 14:
374614805fdSthorpej new_itime = TIMING6x_INTEG_13_7ms;
375614805fdSthorpej break;
376614805fdSthorpej
377614805fdSthorpej case 101:
378614805fdSthorpej new_itime = TIMING6x_INTEG_101ms;
379614805fdSthorpej break;
380614805fdSthorpej
381614805fdSthorpej case 402:
382614805fdSthorpej new_itime = TIMING6x_INTEG_402ms;
383614805fdSthorpej break;
384614805fdSthorpej
385614805fdSthorpej default:
386614805fdSthorpej mutex_exit(&sc->sc_lock);
387614805fdSthorpej return (EINVAL);
388614805fdSthorpej }
389614805fdSthorpej
390614805fdSthorpej if ((error = iic_acquire_bus(sc->sc_i2c, 0)) != 0) {
391614805fdSthorpej mutex_exit(&sc->sc_lock);
392614805fdSthorpej return (error);
393614805fdSthorpej }
394614805fdSthorpej
395614805fdSthorpej error = tsllux_set_integration_time(sc, new_itime);
396614805fdSthorpej iic_release_bus(sc->sc_i2c, 0);
397614805fdSthorpej mutex_exit(&sc->sc_lock);
398614805fdSthorpej
399614805fdSthorpej return (error);
400614805fdSthorpej }
401614805fdSthorpej
402614805fdSthorpej static void
tsllux_sysctl_attach(struct tsllux_softc * sc)403614805fdSthorpej tsllux_sysctl_attach(struct tsllux_softc *sc)
404614805fdSthorpej {
405614805fdSthorpej struct sysctllog **log = &sc->sc_sysctllog;
406614805fdSthorpej const struct sysctlnode *rnode, *cnode;
407614805fdSthorpej int error;
408614805fdSthorpej
409614805fdSthorpej error = sysctl_createv(log, 0, NULL, &rnode, CTLFLAG_PERMANENT,
410614805fdSthorpej CTLTYPE_NODE, device_xname(sc->sc_dev),
411614805fdSthorpej SYSCTL_DESCR("tsl256x control"),
412614805fdSthorpej NULL, 0, NULL, 0, CTL_HW, CTL_CREATE, CTL_EOL);
413614805fdSthorpej if (error)
414614805fdSthorpej return;
415614805fdSthorpej
416614805fdSthorpej error = sysctl_createv(log, 0, &rnode, &cnode,
417614805fdSthorpej CTLFLAG_PERMANENT|CTLFLAG_READWRITE, CTLTYPE_INT, "cs_package",
418614805fdSthorpej SYSCTL_DESCR("sensor in Chipscale (CS) package"),
419614805fdSthorpej tsllux_sysctl_cs_package, 0,
420614805fdSthorpej (void *)sc, 0, CTL_CREATE, CTL_EOL);
421614805fdSthorpej if (error)
422614805fdSthorpej return;
423614805fdSthorpej
424614805fdSthorpej error = sysctl_createv(log, 0, &rnode, &cnode,
425614805fdSthorpej CTLFLAG_PERMANENT|CTLFLAG_READWRITE, CTLTYPE_INT, "auto_gain",
426614805fdSthorpej SYSCTL_DESCR("auto-gain algorithm enabled"),
427614805fdSthorpej tsllux_sysctl_autogain, 0,
428614805fdSthorpej (void *)sc, 0, CTL_CREATE, CTL_EOL);
429614805fdSthorpej if (error)
430614805fdSthorpej return;
431614805fdSthorpej
432614805fdSthorpej error = sysctl_createv(log, 0, &rnode, &cnode,
433614805fdSthorpej CTLFLAG_PERMANENT|CTLFLAG_READWRITE, CTLTYPE_INT, "gain",
434614805fdSthorpej SYSCTL_DESCR("sensor gain"), tsllux_sysctl_gain, 0,
435614805fdSthorpej (void *)sc, 0, CTL_CREATE, CTL_EOL);
436614805fdSthorpej if (error)
437614805fdSthorpej return;
438614805fdSthorpej
439614805fdSthorpej error = sysctl_createv(log, 0, &rnode, &cnode,
440614805fdSthorpej CTLFLAG_PERMANENT|CTLFLAG_READWRITE, CTLTYPE_INT,
441614805fdSthorpej "integration_time",
442614805fdSthorpej SYSCTL_DESCR("ADC integration time"), tsllux_sysctl_itime, 0,
443614805fdSthorpej (void *)sc, 0, CTL_CREATE, CTL_EOL);
444614805fdSthorpej if (error)
445614805fdSthorpej return;
446614805fdSthorpej }
447614805fdSthorpej
448614805fdSthorpej static void
tsllux_sensors_refresh(struct sysmon_envsys * sme,envsys_data_t * edata)449614805fdSthorpej tsllux_sensors_refresh(struct sysmon_envsys *sme, envsys_data_t *edata)
450614805fdSthorpej {
451614805fdSthorpej struct tsllux_softc *sc = sme->sme_cookie;
452614805fdSthorpej uint32_t lux;
453614805fdSthorpej int error;
454614805fdSthorpej
455614805fdSthorpej if (edata != &sc->sc_sensor) {
456614805fdSthorpej edata->state = ENVSYS_SINVALID;
457614805fdSthorpej return;
458614805fdSthorpej }
459614805fdSthorpej
460614805fdSthorpej mutex_enter(&sc->sc_lock);
461614805fdSthorpej
462614805fdSthorpej if ((error = iic_acquire_bus(sc->sc_i2c, 0)) == 0) {
463614805fdSthorpej error = tsllux_get_lux(sc, &lux, NULL, NULL);
464614805fdSthorpej iic_release_bus(sc->sc_i2c, 0);
465614805fdSthorpej }
466614805fdSthorpej
467614805fdSthorpej if (error) {
468614805fdSthorpej edata->state = ENVSYS_SINVALID;
469614805fdSthorpej } else {
470614805fdSthorpej edata->value_cur = lux;
471614805fdSthorpej edata->state = ENVSYS_SVALID;
472614805fdSthorpej }
473614805fdSthorpej
474614805fdSthorpej mutex_exit(&sc->sc_lock);
475614805fdSthorpej }
476614805fdSthorpej
477614805fdSthorpej /*
478614805fdSthorpej * Allow pending interrupts to be cleared as part of another operation.
479614805fdSthorpej */
480614805fdSthorpej #define REGMASK6x (COMMAND6x_REGMASK | COMMAND6x_CLEAR)
481614805fdSthorpej
482614805fdSthorpej static int
tsllux_read1(struct tsllux_softc * sc,uint8_t reg,uint8_t * valp)483614805fdSthorpej tsllux_read1(struct tsllux_softc *sc, uint8_t reg, uint8_t *valp)
484614805fdSthorpej {
485614805fdSthorpej reg = (reg & REGMASK6x) | COMMAND6x_CMD;
486614805fdSthorpej return (iic_smbus_read_byte(sc->sc_i2c, sc->sc_addr, reg, valp, 0));
487614805fdSthorpej }
488614805fdSthorpej
489614805fdSthorpej static int
tsllux_read2(struct tsllux_softc * sc,uint8_t reg,uint16_t * valp)490614805fdSthorpej tsllux_read2(struct tsllux_softc *sc, uint8_t reg, uint16_t *valp)
491614805fdSthorpej {
492614805fdSthorpej reg = (reg & REGMASK6x) | COMMAND6x_CMD | COMMAND6x_WORD;
493614805fdSthorpej return (iic_smbus_read_word(sc->sc_i2c, sc->sc_addr, reg, valp, 0));
494614805fdSthorpej }
495614805fdSthorpej
496614805fdSthorpej static int
tsllux_write1(struct tsllux_softc * sc,uint8_t reg,uint8_t val)497614805fdSthorpej tsllux_write1(struct tsllux_softc *sc, uint8_t reg, uint8_t val)
498614805fdSthorpej {
499614805fdSthorpej reg = (reg & REGMASK6x) | COMMAND6x_CMD;
500614805fdSthorpej return (iic_smbus_write_byte(sc->sc_i2c, sc->sc_addr, reg, val, 0));
501614805fdSthorpej }
502614805fdSthorpej
503614805fdSthorpej #if 0
504614805fdSthorpej static int
505614805fdSthorpej tsllux_write2(struct tsllux_softc *sc, uint8_t reg, uint16_t val)
506614805fdSthorpej {
507614805fdSthorpej reg = (reg & REGMASK6x) | COMMAND6x_CMD | COMMAND6x_WORD;
508614805fdSthorpej return (iic_smbus_write_word(sc->sc_i2c, sc->sc_addr, reg, val, 0));
509614805fdSthorpej }
510614805fdSthorpej #endif
511614805fdSthorpej
512614805fdSthorpej #undef REGMASK
513614805fdSthorpej
514614805fdSthorpej static int
tsllux_poweron(struct tsllux_softc * sc)515614805fdSthorpej tsllux_poweron(struct tsllux_softc *sc)
516614805fdSthorpej {
517614805fdSthorpej int error;
518614805fdSthorpej
519614805fdSthorpej if (sc->sc_poweron++ == 0) {
520614805fdSthorpej uint8_t val;
521614805fdSthorpej
522614805fdSthorpej error = tsllux_write1(sc, TSL256x_REG_CONTROL,
523614805fdSthorpej CONTROL6x_POWER_ON);
524614805fdSthorpej if (error)
525614805fdSthorpej return (error);
526614805fdSthorpej
527614805fdSthorpej error = tsllux_read1(sc, TSL256x_REG_CONTROL, &val);
528614805fdSthorpej if (error)
529614805fdSthorpej return (error);
530614805fdSthorpej
531614805fdSthorpej if (val != CONTROL6x_POWER_ON) {
532614805fdSthorpej aprint_error_dev(sc->sc_dev,
533614805fdSthorpej "failed to power on sensor\n");
534614805fdSthorpej return (EIO);
535614805fdSthorpej }
536614805fdSthorpej }
537614805fdSthorpej return (0);
538614805fdSthorpej }
539614805fdSthorpej
540614805fdSthorpej static int
tsllux_poweroff(struct tsllux_softc * sc)541614805fdSthorpej tsllux_poweroff(struct tsllux_softc *sc)
542614805fdSthorpej {
543614805fdSthorpej if (sc->sc_poweron && --sc->sc_poweron == 0)
544614805fdSthorpej return (tsllux_write1(sc, TSL256x_REG_CONTROL,
545614805fdSthorpej CONTROL6x_POWER_OFF));
546614805fdSthorpej return (0);
547614805fdSthorpej }
548614805fdSthorpej
549614805fdSthorpej static int
tsllux_set_integration_time(struct tsllux_softc * sc,uint8_t time)550614805fdSthorpej tsllux_set_integration_time(struct tsllux_softc *sc, uint8_t time)
551614805fdSthorpej {
552614805fdSthorpej int error;
553614805fdSthorpej
554614805fdSthorpej switch (time) {
555614805fdSthorpej case TIMING6x_INTEG_13_7ms:
556614805fdSthorpej case TIMING6x_INTEG_101ms:
557614805fdSthorpej case TIMING6x_INTEG_402ms:
558614805fdSthorpej break;
559614805fdSthorpej
560614805fdSthorpej default:
561614805fdSthorpej return (EINVAL);
562614805fdSthorpej }
563614805fdSthorpej
564614805fdSthorpej if ((error = tsllux_poweron(sc)) != 0)
565614805fdSthorpej return (error);
566614805fdSthorpej
567614805fdSthorpej if ((error = tsllux_write1(sc, TSL256x_REG_TIMING,
568614805fdSthorpej time | sc->sc_gain)) != 0)
569614805fdSthorpej goto out;
570614805fdSthorpej
571614805fdSthorpej sc->sc_itime = time;
572614805fdSthorpej
573614805fdSthorpej out:
574614805fdSthorpej (void) tsllux_poweroff(sc);
575614805fdSthorpej return (error);
576614805fdSthorpej }
577614805fdSthorpej
578614805fdSthorpej static int
tsllux_set_gain0(struct tsllux_softc * sc,uint8_t gain)579614805fdSthorpej tsllux_set_gain0(struct tsllux_softc *sc, uint8_t gain)
580614805fdSthorpej {
581614805fdSthorpej int error;
582614805fdSthorpej
583614805fdSthorpej if ((error = tsllux_write1(sc, TSL256x_REG_TIMING,
584614805fdSthorpej sc->sc_itime | gain)) != 0)
585614805fdSthorpej return (error);
586614805fdSthorpej
587614805fdSthorpej sc->sc_gain = gain;
588614805fdSthorpej return (0);
589614805fdSthorpej }
590614805fdSthorpej
591614805fdSthorpej static int
tsllux_set_gain(struct tsllux_softc * sc,uint8_t gain)592614805fdSthorpej tsllux_set_gain(struct tsllux_softc *sc, uint8_t gain)
593614805fdSthorpej {
594614805fdSthorpej int error;
595614805fdSthorpej
596614805fdSthorpej switch (gain) {
597614805fdSthorpej case TIMING6x_GAIN_1X:
598614805fdSthorpej case TIMING6x_GAIN_16X:
599614805fdSthorpej break;
600614805fdSthorpej
601614805fdSthorpej default:
602614805fdSthorpej return (EINVAL);
603614805fdSthorpej }
604614805fdSthorpej
605614805fdSthorpej if ((error = tsllux_poweron(sc)) != 0)
606614805fdSthorpej return (error);
607614805fdSthorpej
608614805fdSthorpej if ((error = tsllux_set_gain0(sc, gain)) != 0)
609614805fdSthorpej goto out;
610614805fdSthorpej
611614805fdSthorpej sc->sc_auto_gain = false;
612614805fdSthorpej
613614805fdSthorpej out:
614614805fdSthorpej (void) tsllux_poweroff(sc);
615614805fdSthorpej return (error);
616614805fdSthorpej }
617614805fdSthorpej
618614805fdSthorpej static int
tsllux_set_autogain(struct tsllux_softc * sc,bool use_autogain)619614805fdSthorpej tsllux_set_autogain(struct tsllux_softc *sc, bool use_autogain)
620614805fdSthorpej {
621614805fdSthorpej
622614805fdSthorpej sc->sc_auto_gain = use_autogain;
623614805fdSthorpej return (0);
624614805fdSthorpej }
625614805fdSthorpej
626614805fdSthorpej static int
tsllux_wait_for_adcs(struct tsllux_softc * sc)627614805fdSthorpej tsllux_wait_for_adcs(struct tsllux_softc *sc)
628614805fdSthorpej {
629614805fdSthorpej int ms;
630614805fdSthorpej
631614805fdSthorpej switch (sc->sc_itime) {
632614805fdSthorpej case TIMING6x_INTEG_13_7ms:
633614805fdSthorpej /* Wait 15ms for 13.7ms integration */
634614805fdSthorpej ms = 15;
635614805fdSthorpej break;
636614805fdSthorpej
637614805fdSthorpej case TIMING6x_INTEG_101ms:
638614805fdSthorpej /* Wait 120ms for 101ms integration */
639614805fdSthorpej ms = 120;
640614805fdSthorpej break;
641614805fdSthorpej
642614805fdSthorpej case TIMING6x_INTEG_402ms:
643614805fdSthorpej default:
644614805fdSthorpej /* Wait 450ms for 402ms integration */
645614805fdSthorpej ms = 450;
646614805fdSthorpej break;
647614805fdSthorpej }
648614805fdSthorpej
649614805fdSthorpej if (ms < hztoms(1)) {
650614805fdSthorpej /* Just busy-wait if we want to wait for less than 1 tick. */
651614805fdSthorpej delay(ms * 1000);
652614805fdSthorpej } else {
653614805fdSthorpej /* Round up one tick for the case where we sleep. */
654614805fdSthorpej (void) kpause("tslluxwait", false, mstohz(ms) + 1, NULL);
655614805fdSthorpej }
656614805fdSthorpej
657614805fdSthorpej return (0);
658614805fdSthorpej }
659614805fdSthorpej
660614805fdSthorpej static int
tsllux_read_adcs(struct tsllux_softc * sc,uint16_t * adc0valp,uint16_t * adc1valp)661614805fdSthorpej tsllux_read_adcs(struct tsllux_softc *sc, uint16_t *adc0valp,
662614805fdSthorpej uint16_t *adc1valp)
663614805fdSthorpej {
664614805fdSthorpej int error;
665614805fdSthorpej
666614805fdSthorpej if ((error = tsllux_read2(sc, TSL256x_REG_DATA0LOW, adc0valp)) == 0)
667614805fdSthorpej error = tsllux_read2(sc, TSL256x_REG_DATA1LOW, adc1valp);
668614805fdSthorpej
669614805fdSthorpej return (error);
670614805fdSthorpej }
671614805fdSthorpej
672614805fdSthorpej /*
673614805fdSthorpej * The following code is partially derived from Adafruit's TSL2561
674614805fdSthorpej * driver for Arduino (which was in turn derived from the data sheet),
675614805fdSthorpej * which carries this notice:
676614805fdSthorpej *
677614805fdSthorpej * @file Adafruit_TSL2561_U.cpp
678614805fdSthorpej *
679614805fdSthorpej * @mainpage Adafruit TSL2561 Light/Lux sensor driver
680614805fdSthorpej *
681614805fdSthorpej * @section intro_sec Introduction
682614805fdSthorpej *
683614805fdSthorpej * This is the documentation for Adafruit's TSL2561 driver for the
684614805fdSthorpej * Arduino platform. It is designed specifically to work with the
685614805fdSthorpej * Adafruit TSL2561 breakout: http://www.adafruit.com/products/439
686614805fdSthorpej *
687614805fdSthorpej * These sensors use I2C to communicate, 2 pins (SCL+SDA) are required
688614805fdSthorpej * to interface with the breakout.
689614805fdSthorpej *
690614805fdSthorpej * Adafruit invests time and resources providing this open source code,
691614805fdSthorpej * please support Adafruit and open-source hardware by purchasing
692614805fdSthorpej * products from Adafruit!
693614805fdSthorpej *
694614805fdSthorpej * @section dependencies Dependencies
695614805fdSthorpej *
696614805fdSthorpej * This library depends on <a href="https://github.com/adafruit/Adafruit_Sensor">
697614805fdSthorpej * Adafruit_Sensor</a> being present on your system. Please make sure you have
698614805fdSthorpej * installed the latest version before using this library.
699614805fdSthorpej *
700614805fdSthorpej * @section author Author
701614805fdSthorpej *
702614805fdSthorpej * Written by Kevin "KTOWN" Townsend for Adafruit Industries.
703614805fdSthorpej *
704614805fdSthorpej * @section license License
705614805fdSthorpej *
706614805fdSthorpej * BSD license, all text here must be included in any redistribution.
707614805fdSthorpej *
708614805fdSthorpej * @section HISTORY
709614805fdSthorpej *
710614805fdSthorpej * v2.0 - Rewrote driver for Adafruit_Sensor and Auto-Gain support, and
711614805fdSthorpej * added lux clipping check (returns 0 lux on sensor saturation)
712614805fdSthorpej * v1.0 - First release (previously TSL2561)
713614805fdSthorpej */
714614805fdSthorpej
715614805fdSthorpej static int
tsllux_read_sensors(struct tsllux_softc * sc,uint16_t * adc0p,uint16_t * adc1p)716614805fdSthorpej tsllux_read_sensors(struct tsllux_softc *sc, uint16_t *adc0p, uint16_t *adc1p)
717614805fdSthorpej {
718614805fdSthorpej int error;
719614805fdSthorpej
720614805fdSthorpej if ((error = tsllux_poweron(sc)) != 0)
721614805fdSthorpej return (error);
722614805fdSthorpej
723614805fdSthorpej if ((error = tsllux_wait_for_adcs(sc)) != 0)
724614805fdSthorpej goto out;
725614805fdSthorpej
726614805fdSthorpej error = tsllux_read_adcs(sc, adc0p, adc1p);
727614805fdSthorpej
728614805fdSthorpej out:
729614805fdSthorpej (void) tsllux_poweroff(sc);
730614805fdSthorpej return (error);
731614805fdSthorpej }
732614805fdSthorpej
733614805fdSthorpej /*
734614805fdSthorpej * Auto-gain thresholds:
735614805fdSthorpej */
736614805fdSthorpej #define TSL2561_AGC_THI_13MS (4850) /* Max value at Ti 13ms = 5047 */
737614805fdSthorpej #define TSL2561_AGC_TLO_13MS (100) /* Min value at Ti 13ms = 100 */
738614805fdSthorpej #define TSL2561_AGC_THI_101MS (36000) /* Max value at Ti 101ms = 37177 */
739614805fdSthorpej #define TSL2561_AGC_TLO_101MS (200) /* Min value at Ti 101ms = 200 */
740614805fdSthorpej #define TSL2561_AGC_THI_402MS (63000) /* Max value at Ti 402ms = 65535 */
741614805fdSthorpej #define TSL2561_AGC_TLO_402MS (500) /* Min value at Ti 402ms = 500 */
742614805fdSthorpej
743614805fdSthorpej static int
tsllux_get_sensor_data(struct tsllux_softc * sc,uint16_t * broadband,uint16_t * ir)744614805fdSthorpej tsllux_get_sensor_data(struct tsllux_softc *sc, uint16_t *broadband,
745614805fdSthorpej uint16_t *ir)
746614805fdSthorpej {
747614805fdSthorpej int error = 0;
748614805fdSthorpej uint16_t adc0, adc1;
749614805fdSthorpej bool did_adjust_gain, valid;
750614805fdSthorpej uint16_t hi, lo;
751614805fdSthorpej
752614805fdSthorpej if (sc->sc_auto_gain == false) {
753614805fdSthorpej error = tsllux_read_sensors(sc, &adc0, &adc1);
754614805fdSthorpej goto out;
755614805fdSthorpej }
756614805fdSthorpej
757614805fdSthorpej /* Set the hi / lo threshold based on current integration time. */
758614805fdSthorpej switch (sc->sc_itime) {
759614805fdSthorpej case TIMING6x_INTEG_13_7ms:
760614805fdSthorpej hi = TSL2561_AGC_THI_13MS;
761614805fdSthorpej lo = TSL2561_AGC_TLO_13MS;
762614805fdSthorpej break;
763614805fdSthorpej
764614805fdSthorpej case TIMING6x_INTEG_101ms:
765614805fdSthorpej hi = TSL2561_AGC_THI_101MS;
766614805fdSthorpej lo = TSL2561_AGC_TLO_101MS;
767614805fdSthorpej break;
768614805fdSthorpej
769614805fdSthorpej case TIMING6x_INTEG_402ms:
770614805fdSthorpej default:
771614805fdSthorpej hi = TSL2561_AGC_THI_402MS;
772614805fdSthorpej lo = TSL2561_AGC_TLO_402MS;
773614805fdSthorpej }
774614805fdSthorpej
775614805fdSthorpej /* Read data and adjust the gain until we have a valid range. */
776614805fdSthorpej for (valid = false, did_adjust_gain = false; valid == false; ) {
777614805fdSthorpej if ((error = tsllux_read_sensors(sc, &adc0, &adc1)) != 0)
778614805fdSthorpej goto out;
779614805fdSthorpej
780614805fdSthorpej if (did_adjust_gain == false) {
781614805fdSthorpej if (adc0 < lo && sc->sc_gain == TIMING6x_GAIN_1X) {
782614805fdSthorpej /* Increase the gain and try again. */
783614805fdSthorpej if ((error =
784614805fdSthorpej tsllux_set_gain0(sc,
785614805fdSthorpej TIMING6x_GAIN_16X)) != 0)
786614805fdSthorpej goto out;
787614805fdSthorpej did_adjust_gain = true;
788614805fdSthorpej } else if (adc0 > hi &&
789614805fdSthorpej sc->sc_gain == TIMING6x_GAIN_16X) {
790614805fdSthorpej /* Decrease the gain and try again. */
791614805fdSthorpej if ((error =
792614805fdSthorpej tsllux_set_gain0(sc,
793614805fdSthorpej TIMING6x_GAIN_1X)) != 0)
794614805fdSthorpej goto out;
795614805fdSthorpej did_adjust_gain = true;
796614805fdSthorpej } else {
797614805fdSthorpej /*
798614805fdSthorpej * Reading is either valid or we're already
799614805fdSthorpej * at the chip's limits.
800614805fdSthorpej */
801614805fdSthorpej valid = true;
802614805fdSthorpej }
803614805fdSthorpej } else {
804614805fdSthorpej /*
805614805fdSthorpej * If we've already adjust the gain once, just
806614805fdSthorpej * return the new results. This avoids endless
807614805fdSthorpej * loops where a value is at one extre pre-gain
808614805fdSthorpej * and at the other extreme post-gain.
809614805fdSthorpej */
810614805fdSthorpej valid = true;
811614805fdSthorpej }
812614805fdSthorpej }
813614805fdSthorpej
814614805fdSthorpej out:
815614805fdSthorpej if (error == 0) {
816614805fdSthorpej if (broadband != NULL)
817614805fdSthorpej *broadband = adc0;
818614805fdSthorpej if (ir != NULL)
819614805fdSthorpej *ir = adc1;
820614805fdSthorpej }
821614805fdSthorpej return (error);
822614805fdSthorpej }
823614805fdSthorpej
824614805fdSthorpej /*
825614805fdSthorpej * Clipping thresholds:
826614805fdSthorpej */
827614805fdSthorpej #define TSL2561_CLIPPING_13MS (4900)
828614805fdSthorpej #define TSL2561_CLIPPING_101MS (37000)
829614805fdSthorpej #define TSL2561_CLIPPING_402MS (65000)
830614805fdSthorpej
831614805fdSthorpej /*
832614805fdSthorpej * Scaling factors:
833614805fdSthorpej */
834614805fdSthorpej #define TSL2561_LUX_LUXSCALE (14) /* Scale by 2^14 */
835614805fdSthorpej #define TSL2561_LUX_RATIOSCALE (9) /* Scale ratio by 2^9 */
836614805fdSthorpej #define TSL2561_LUX_CHSCALE (10) /* Scale channel values by 2^10 */
837614805fdSthorpej #define TSL2561_LUX_CHSCALE_TINT0 (0x7517) /* 322/11 * 2^TSL2561_LUX_CHSCALE */
838614805fdSthorpej #define TSL2561_LUX_CHSCALE_TINT1 (0x0FE7) /* 322/81 * 2^TSL2561_LUX_CHSCALE */
839614805fdSthorpej
840614805fdSthorpej /*
841614805fdSthorpej * Lux factors (the datasheet explains how these magic constants
842614805fdSthorpej * are used):
843614805fdSthorpej */
844614805fdSthorpej /* T, FN and CL package values */
845614805fdSthorpej #define TSL2561_LUX_K1T (0x0040) /* 0.125 * 2^RATIO_SCALE */
846614805fdSthorpej #define TSL2561_LUX_B1T (0x01f2) /* 0.0304 * 2^LUX_SCALE */
847614805fdSthorpej #define TSL2561_LUX_M1T (0x01be) /* 0.0272 * 2^LUX_SCALE */
848614805fdSthorpej #define TSL2561_LUX_K2T (0x0080) /* 0.250 * 2^RATIO_SCALE */
849614805fdSthorpej #define TSL2561_LUX_B2T (0x0214) /* 0.0325 * 2^LUX_SCALE */
850614805fdSthorpej #define TSL2561_LUX_M2T (0x02d1) /* 0.0440 * 2^LUX_SCALE */
851614805fdSthorpej #define TSL2561_LUX_K3T (0x00c0) /* 0.375 * 2^RATIO_SCALE */
852614805fdSthorpej #define TSL2561_LUX_B3T (0x023f) /* 0.0351 * 2^LUX_SCALE */
853614805fdSthorpej #define TSL2561_LUX_M3T (0x037b) /* 0.0544 * 2^LUX_SCALE */
854614805fdSthorpej #define TSL2561_LUX_K4T (0x0100) /* 0.50 * 2^RATIO_SCALE */
855614805fdSthorpej #define TSL2561_LUX_B4T (0x0270) /* 0.0381 * 2^LUX_SCALE */
856614805fdSthorpej #define TSL2561_LUX_M4T (0x03fe) /* 0.0624 * 2^LUX_SCALE */
857614805fdSthorpej #define TSL2561_LUX_K5T (0x0138) /* 0.61 * 2^RATIO_SCALE */
858614805fdSthorpej #define TSL2561_LUX_B5T (0x016f) /* 0.0224 * 2^LUX_SCALE */
859614805fdSthorpej #define TSL2561_LUX_M5T (0x01fc) /* 0.0310 * 2^LUX_SCALE */
860614805fdSthorpej #define TSL2561_LUX_K6T (0x019a) /* 0.80 * 2^RATIO_SCALE */
861614805fdSthorpej #define TSL2561_LUX_B6T (0x00d2) /* 0.0128 * 2^LUX_SCALE */
862614805fdSthorpej #define TSL2561_LUX_M6T (0x00fb) /* 0.0153 * 2^LUX_SCALE */
863614805fdSthorpej #define TSL2561_LUX_K7T (0x029a) /* 1.3 * 2^RATIO_SCALE */
864614805fdSthorpej #define TSL2561_LUX_B7T (0x0018) /* 0.00146 * 2^LUX_SCALE */
865614805fdSthorpej #define TSL2561_LUX_M7T (0x0012) /* 0.00112 * 2^LUX_SCALE */
866614805fdSthorpej #define TSL2561_LUX_K8T (0x029a) /* 1.3 * 2^RATIO_SCALE */
867614805fdSthorpej #define TSL2561_LUX_B8T (0x0000) /* 0.000 * 2^LUX_SCALE */
868614805fdSthorpej #define TSL2561_LUX_M8T (0x0000) /* 0.000 * 2^LUX_SCALE */
869614805fdSthorpej
870614805fdSthorpej /* CS package values */
871614805fdSthorpej #define TSL2561_LUX_K1C (0x0043) /* 0.130 * 2^RATIO_SCALE */
872614805fdSthorpej #define TSL2561_LUX_B1C (0x0204) /* 0.0315 * 2^LUX_SCALE */
873614805fdSthorpej #define TSL2561_LUX_M1C (0x01ad) /* 0.0262 * 2^LUX_SCALE */
874614805fdSthorpej #define TSL2561_LUX_K2C (0x0085) /* 0.260 * 2^RATIO_SCALE */
875614805fdSthorpej #define TSL2561_LUX_B2C (0x0228) /* 0.0337 * 2^LUX_SCALE */
876614805fdSthorpej #define TSL2561_LUX_M2C (0x02c1) /* 0.0430 * 2^LUX_SCALE */
877614805fdSthorpej #define TSL2561_LUX_K3C (0x00c8) /* 0.390 * 2^RATIO_SCALE */
878614805fdSthorpej #define TSL2561_LUX_B3C (0x0253) /* 0.0363 * 2^LUX_SCALE */
879614805fdSthorpej #define TSL2561_LUX_M3C (0x0363) /* 0.0529 * 2^LUX_SCALE */
880614805fdSthorpej #define TSL2561_LUX_K4C (0x010a) /* 0.520 * 2^RATIO_SCALE */
881614805fdSthorpej #define TSL2561_LUX_B4C (0x0282) /* 0.0392 * 2^LUX_SCALE */
882614805fdSthorpej #define TSL2561_LUX_M4C (0x03df) /* 0.0605 * 2^LUX_SCALE */
883614805fdSthorpej #define TSL2561_LUX_K5C (0x014d) /* 0.65 * 2^RATIO_SCALE */
884614805fdSthorpej #define TSL2561_LUX_B5C (0x0177) /* 0.0229 * 2^LUX_SCALE */
885614805fdSthorpej #define TSL2561_LUX_M5C (0x01dd) /* 0.0291 * 2^LUX_SCALE */
886614805fdSthorpej #define TSL2561_LUX_K6C (0x019a) /* 0.80 * 2^RATIO_SCALE */
887614805fdSthorpej #define TSL2561_LUX_B6C (0x0101) /* 0.0157 * 2^LUX_SCALE */
888614805fdSthorpej #define TSL2561_LUX_M6C (0x0127) /* 0.0180 * 2^LUX_SCALE */
889614805fdSthorpej #define TSL2561_LUX_K7C (0x029a) /* 1.3 * 2^RATIO_SCALE */
890614805fdSthorpej #define TSL2561_LUX_B7C (0x0037) /* 0.00338 * 2^LUX_SCALE */
891614805fdSthorpej #define TSL2561_LUX_M7C (0x002b) /* 0.00260 * 2^LUX_SCALE */
892614805fdSthorpej #define TSL2561_LUX_K8C (0x029a) /* 1.3 * 2^RATIO_SCALE */
893614805fdSthorpej #define TSL2561_LUX_B8C (0x0000) /* 0.000 * 2^LUX_SCALE */
894614805fdSthorpej #define TSL2561_LUX_M8C (0x0000) /* 0.000 * 2^LUX_SCALE */
895614805fdSthorpej
896614805fdSthorpej struct lux_factor_table_entry {
897614805fdSthorpej uint16_t k;
898614805fdSthorpej uint16_t b;
899614805fdSthorpej uint16_t m;
900614805fdSthorpej };
901614805fdSthorpej
902614805fdSthorpej static const struct lux_factor_table_entry lux_factor_table[] = {
903614805fdSthorpej { TSL2561_LUX_K1T, TSL2561_LUX_B1T, TSL2561_LUX_M1T },
904614805fdSthorpej { TSL2561_LUX_K2T, TSL2561_LUX_B2T, TSL2561_LUX_M2T },
905614805fdSthorpej { TSL2561_LUX_K3T, TSL2561_LUX_B3T, TSL2561_LUX_M3T },
906614805fdSthorpej { TSL2561_LUX_K4T, TSL2561_LUX_B4T, TSL2561_LUX_M4T },
907614805fdSthorpej { TSL2561_LUX_K5T, TSL2561_LUX_B5T, TSL2561_LUX_M5T },
908614805fdSthorpej { TSL2561_LUX_K6T, TSL2561_LUX_B6T, TSL2561_LUX_M6T },
909614805fdSthorpej { TSL2561_LUX_K7T, TSL2561_LUX_B7T, TSL2561_LUX_M7T },
910614805fdSthorpej { TSL2561_LUX_K8T, TSL2561_LUX_B8T, TSL2561_LUX_M8T },
911614805fdSthorpej };
912614805fdSthorpej static const int lux_factor_table_last_entry =
913614805fdSthorpej (sizeof(lux_factor_table) / sizeof(lux_factor_table[0])) - 1;
914614805fdSthorpej
915614805fdSthorpej static const struct lux_factor_table_entry lux_factor_table_cs_package[] = {
916614805fdSthorpej { TSL2561_LUX_K1C, TSL2561_LUX_B1C, TSL2561_LUX_M1C },
917614805fdSthorpej { TSL2561_LUX_K2C, TSL2561_LUX_B2C, TSL2561_LUX_M2C },
918614805fdSthorpej { TSL2561_LUX_K3C, TSL2561_LUX_B3C, TSL2561_LUX_M3C },
919614805fdSthorpej { TSL2561_LUX_K4C, TSL2561_LUX_B4C, TSL2561_LUX_M4C },
920614805fdSthorpej { TSL2561_LUX_K5C, TSL2561_LUX_B5C, TSL2561_LUX_M5C },
921614805fdSthorpej { TSL2561_LUX_K6C, TSL2561_LUX_B6C, TSL2561_LUX_M6C },
922614805fdSthorpej { TSL2561_LUX_K7C, TSL2561_LUX_B7C, TSL2561_LUX_M7C },
923614805fdSthorpej { TSL2561_LUX_K8C, TSL2561_LUX_B8C, TSL2561_LUX_M8C },
924614805fdSthorpej };
925614805fdSthorpej static const int lux_factor_table_cs_package_last_entry =
926614805fdSthorpej (sizeof(lux_factor_table_cs_package) /
927614805fdSthorpej sizeof(lux_factor_table_cs_package[0])) - 1;
928614805fdSthorpej
929614805fdSthorpej static int
tsllux_get_lux(struct tsllux_softc * sc,uint32_t * luxp,uint16_t * raw_broadband,uint16_t * raw_ir)930614805fdSthorpej tsllux_get_lux(struct tsllux_softc *sc, uint32_t *luxp,
931614805fdSthorpej uint16_t *raw_broadband, uint16_t *raw_ir)
932614805fdSthorpej {
933614805fdSthorpej uint32_t channel0, channel1, scale, ratio, lux = 0;
934614805fdSthorpej uint16_t broadband, ir;
935614805fdSthorpej uint16_t clip_threshold;
936614805fdSthorpej const struct lux_factor_table_entry *table;
937614805fdSthorpej int idx, last_entry, error;
938614805fdSthorpej int32_t temp;
939614805fdSthorpej
940614805fdSthorpej if ((error = tsllux_get_sensor_data(sc, &broadband, &ir)) != 0)
941614805fdSthorpej return (error);
942614805fdSthorpej
943614805fdSthorpej if (luxp == NULL) {
944614805fdSthorpej /*
945614805fdSthorpej * Caller doesn't want the calculated Lux value, so
946614805fdSthorpej * don't bother calculating it. Maybe they just want
947614805fdSthorpej * the raw sensor data?
948614805fdSthorpej */
949614805fdSthorpej goto out;
950614805fdSthorpej }
951614805fdSthorpej
952614805fdSthorpej /*
953614805fdSthorpej * Check to see if the sensor is saturated. If so,
954614805fdSthorpej * just return a "max brightness" value.
955614805fdSthorpej */
956614805fdSthorpej switch (sc->sc_itime) {
957614805fdSthorpej case TIMING6x_INTEG_13_7ms:
958614805fdSthorpej clip_threshold = TSL2561_CLIPPING_13MS;
959614805fdSthorpej break;
960614805fdSthorpej
961614805fdSthorpej case TIMING6x_INTEG_101ms:
962614805fdSthorpej clip_threshold = TSL2561_CLIPPING_101MS;
963614805fdSthorpej break;
964614805fdSthorpej
965614805fdSthorpej case TIMING6x_INTEG_402ms:
966614805fdSthorpej default:
967614805fdSthorpej clip_threshold = TSL2561_CLIPPING_402MS;
968614805fdSthorpej break;
969614805fdSthorpej }
970614805fdSthorpej
971614805fdSthorpej if (broadband > clip_threshold || ir > clip_threshold) {
972614805fdSthorpej lux = 65536;
973614805fdSthorpej goto out;
974614805fdSthorpej }
975614805fdSthorpej
976614805fdSthorpej /* Get correct scale factor based on integration time. */
977614805fdSthorpej switch (sc->sc_itime) {
978614805fdSthorpej case TIMING6x_INTEG_13_7ms:
979614805fdSthorpej scale = TSL2561_LUX_CHSCALE_TINT0;
980614805fdSthorpej break;
981614805fdSthorpej
982614805fdSthorpej case TIMING6x_INTEG_101ms:
983614805fdSthorpej scale = TSL2561_LUX_CHSCALE_TINT1;
984614805fdSthorpej break;
985614805fdSthorpej
986614805fdSthorpej case TIMING6x_INTEG_402ms:
987614805fdSthorpej default:
988614805fdSthorpej scale = (1 << TSL2561_LUX_CHSCALE);
989614805fdSthorpej }
990614805fdSthorpej
991614805fdSthorpej /* Scale for gain. */
992614805fdSthorpej if (sc->sc_gain == TIMING6x_GAIN_1X)
993614805fdSthorpej scale <<= 4;
994614805fdSthorpej
995614805fdSthorpej /* Scale the channel values. */
996614805fdSthorpej channel0 = ((uint32_t)broadband * scale) >> TSL2561_LUX_CHSCALE;
997614805fdSthorpej channel1 = ((uint32_t)ir * scale) >> TSL2561_LUX_CHSCALE;
998614805fdSthorpej
999614805fdSthorpej /* Find the ratio of the channel values (ir / broadband) */
1000614805fdSthorpej if (channel0 != 0)
1001614805fdSthorpej ratio = (channel1 << (TSL2561_LUX_RATIOSCALE + 1)) / channel0;
1002614805fdSthorpej else
1003614805fdSthorpej ratio = 0;
1004614805fdSthorpej
1005614805fdSthorpej /* Round the ratio value. */
1006614805fdSthorpej ratio = (ratio + 1) >> 1;
1007614805fdSthorpej
1008614805fdSthorpej if (sc->sc_cs_package) {
1009614805fdSthorpej table = lux_factor_table_cs_package;
1010614805fdSthorpej last_entry = lux_factor_table_cs_package_last_entry;
1011614805fdSthorpej } else {
1012614805fdSthorpej table = lux_factor_table;
1013614805fdSthorpej last_entry = lux_factor_table_last_entry;
1014614805fdSthorpej }
1015614805fdSthorpej
1016614805fdSthorpej /*
1017614805fdSthorpej * The table is arranged such that we compare <= against
1018614805fdSthorpej * the key, and if all else fails, we use the last entry.
1019614805fdSthorpej * The pseudo-code in the data sheet shows what's going on.
1020614805fdSthorpej */
1021614805fdSthorpej for (idx = 0; idx < last_entry; idx++) {
1022614805fdSthorpej if (ratio <= table[idx].k)
1023614805fdSthorpej break;
1024614805fdSthorpej }
1025614805fdSthorpej
1026614805fdSthorpej temp = ((channel0 * table[idx].b) - (channel1 * table[idx].m));
1027614805fdSthorpej
1028614805fdSthorpej /* Do not allow negative Lux value. */
1029614805fdSthorpej if (temp < 0)
1030614805fdSthorpej temp = 0;
1031614805fdSthorpej
1032614805fdSthorpej /* Round lsb (2^(LUX_SCALE-1)) */
1033614805fdSthorpej temp += (1 << (TSL2561_LUX_LUXSCALE-1));
1034614805fdSthorpej
1035614805fdSthorpej /* Strip off fractional portion */
1036614805fdSthorpej lux = temp >> TSL2561_LUX_LUXSCALE;
1037614805fdSthorpej
1038614805fdSthorpej out:
1039614805fdSthorpej if (error == 0) {
1040614805fdSthorpej if (luxp != NULL)
1041614805fdSthorpej *luxp = lux;
1042614805fdSthorpej if (raw_broadband != NULL)
1043614805fdSthorpej *raw_broadband = broadband;
1044614805fdSthorpej if (raw_ir != NULL)
1045614805fdSthorpej *raw_ir = ir;
1046614805fdSthorpej }
1047614805fdSthorpej return (error);
1048614805fdSthorpej }
1049