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