xref: /netbsd-src/sys/dev/i2c/tsllux.c (revision 3aa5a3ae30f092daa39cb540c574af3082fdc595)
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