xref: /minix3/minix/drivers/sensors/tsl2550/tsl2550.c (revision 3f82ac6a4e188419336747098d0d6616cd2f3d3d)
1 /* Driver for the TSL2550 Ambient Light Sensor */
2 
3 #include <minix/ds.h>
4 #include <minix/drivers.h>
5 #include <minix/i2c.h>
6 #include <minix/i2cdriver.h>
7 #include <minix/chardriver.h>
8 #include <minix/log.h>
9 #include <minix/type.h>
10 #include <minix/spin.h>
11 
12 /*
13  * Device Commands
14  */
15 #define CMD_PWR_DOWN 0x00
16 #define CMD_PWR_UP 0x03
17 #define CMD_EXT_RANGE 0x1d
18 #define CMD_NORM_RANGE 0x18
19 #define CMD_READ_ADC0 0x43
20 #define CMD_READ_ADC1 0x83
21 
22 /* When powered up and communicating, the register should have this value */
23 #define EXPECTED_PWR_UP_TEST_VAL 0x03
24 
25 /* Maximum Lux value in Standard Mode */
26 #define MAX_LUX_STD_MODE 1846
27 
28 /* Bit Masks for ADC Data */
29 #define ADC_VALID_MASK (1<<7)
30 #define ADC_CHORD_MASK ((1<<6)|(1<<5)|(1<<4))
31 #define ADC_STEP_MASK ((1<<3)|(1<<2)|(1<<1)|(1<<0))
32 
33 #define ADC_VAL_IS_VALID(x) ((x & ADC_VALID_MASK) == ADC_VALID_MASK)
34 #define ADC_VAL_TO_CHORD_BITS(x) ((x & ADC_CHORD_MASK) >> 4)
35 #define ADC_VAL_TO_STEP_BITS(x) (x & ADC_STEP_MASK)
36 
37 /* logging - use with log_warn(), log_info(), log_debug(), log_trace(), etc */
38 static struct log log = {
39 	.name = "tsl2550",
40 	.log_level = LEVEL_INFO,
41 	.log_func = default_log
42 };
43 
44 /* The slave address is hardwired to 0x39 and cannot be changed. */
45 static i2c_addr_t valid_addrs[2] = {
46 	0x39, 0x00
47 };
48 
49 /* Buffer to store output string returned when reading from device file. */
50 #define BUFFER_LEN 32
51 char buffer[BUFFER_LEN + 1];
52 
53 /* the bus that this device is on (counting starting at 1) */
54 static uint32_t bus;
55 
56 /* slave address of the device */
57 static i2c_addr_t address;
58 
59 /* endpoint for the driver for the bus itself. */
60 static endpoint_t bus_endpoint;
61 
62 /* main driver functions */
63 static int tsl2550_init(void);
64 static int adc_read(int adc, uint8_t * val);
65 static int measure_lux(uint32_t * lux);
66 
67 /* libchardriver callbacks */
68 static ssize_t tsl2550_read(devminor_t minor, u64_t position, endpoint_t endpt,
69     cp_grant_id_t grant, size_t size, int flags, cdev_id_t id);
70 static void tsl2550_other(message * m, int ipc_status);
71 
72 /* Entry points to this driver from libchardriver. */
73 static struct chardriver tsl2550_tab = {
74 	.cdr_read	= tsl2550_read,
75 	.cdr_other	= tsl2550_other
76 };
77 
78 /*
79  * These two lookup tables and the formulas used in measure_lux() are from
80  * 'TAOS INTELLIGENT OPTO SENSOR DESIGNER'S NOTEBOOK' Number 9
81  * 'Simplified TSL2550 Lux Calculation for Embedded and Micro Controllers'.
82  *
83  * The tables and formulas eliminate the need for floating point math and
84  * functions from libm. It also speeds up the calculations.
85  */
86 
87 /* Look up table for converting ADC values to ADC counts */
88 static const uint32_t adc_counts_lut[128] = {
89 	0, 1, 2, 3, 4, 5, 6, 7,
90 	8, 9, 10, 11, 12, 13, 14, 15,
91 	16, 18, 20, 22, 24, 26, 28, 30,
92 	32, 34, 36, 38, 40, 42, 44, 46,
93 	49, 53, 57, 61, 65, 69, 73, 77,
94 	81, 85, 89, 93, 97, 101, 105, 109,
95 	115, 123, 131, 139, 147, 155, 163, 171,
96 	179, 187, 195, 203, 211, 219, 227, 235,
97 	247, 263, 279, 295, 311, 327, 343, 359,
98 	375, 391, 407, 423, 439, 455, 471, 487,
99 	511, 543, 575, 607, 639, 671, 703, 735,
100 	767, 799, 831, 863, 895, 927, 959, 991,
101 	1039, 1103, 1167, 1231, 1295, 1359, 1423, 1487,
102 	1551, 1615, 1679, 1743, 1807, 1871, 1935, 1999,
103 	2095, 2223, 2351, 2479, 2607, 2735, 2863, 2991,
104 	3119, 3247, 3375, 3503, 3631, 3759, 3887, 4015
105 };
106 
107 /* Look up table of scaling factors */
108 static const uint32_t ratio_lut[129] = {
109 	100, 100, 100, 100, 100, 100, 100, 100,
110 	100, 100, 100, 100, 100, 100, 99, 99,
111 	99, 99, 99, 99, 99, 99, 99, 99,
112 	99, 99, 99, 98, 98, 98, 98, 98,
113 	98, 98, 97, 97, 97, 97, 97, 96,
114 	96, 96, 96, 95, 95, 95, 94, 94,
115 	93, 93, 93, 92, 92, 91, 91, 90,
116 	89, 89, 88, 87, 87, 86, 85, 84,
117 	83, 82, 81, 80, 79, 78, 77, 75,
118 	74, 73, 71, 69, 68, 66, 64, 62,
119 	60, 58, 56, 54, 52, 49, 47, 44,
120 	42, 41, 40, 40, 39, 39, 38, 38,
121 	37, 37, 37, 36, 36, 36, 35, 35,
122 	35, 35, 34, 34, 34, 34, 33, 33,
123 	33, 33, 32, 32, 32, 32, 32, 31,
124 	31, 31, 31, 31, 30, 30, 30, 30,
125 	30
126 };
127 
128 static int
measure_lux(uint32_t * lux)129 measure_lux(uint32_t * lux)
130 {
131 	int r;
132 	uint8_t adc0_val, adc1_val;
133 	uint32_t adc0_cnt, adc1_cnt;
134 	uint32_t ratio;
135 
136 	r = adc_read(0, &adc0_val);
137 	if (r != OK) {
138 		return -1;
139 	}
140 
141 	r = adc_read(1, &adc1_val);
142 	if (r != OK) {
143 		return -1;
144 	}
145 
146 	/* Look up the adc count, drop the MSB to put in range 0-127. */
147 	adc0_cnt = adc_counts_lut[adc0_val & ~ADC_VALID_MASK];
148 	adc1_cnt = adc_counts_lut[adc1_val & ~ADC_VALID_MASK];
149 
150 	/* default scaling factor */
151 	ratio = 128;
152 
153 	/* calculate ratio - avoid div by 0, ensure cnt1 <= cnt0 */
154 	if ((adc0_cnt != 0) && (adc1_cnt <= adc0_cnt)) {
155 		ratio = (adc1_cnt * 128 / adc0_cnt);
156 	}
157 
158 	/* ensure ratio isn't outside ratio_lut[] */
159 	if (ratio > 128) {
160 		ratio = 128;
161 	}
162 
163 	/* calculate lux */
164 	*lux = ((adc0_cnt - adc1_cnt) * ratio_lut[ratio]) / 256;
165 
166 	/* range check */
167 	if (*lux > MAX_LUX_STD_MODE) {
168 		*lux = MAX_LUX_STD_MODE;
169 	}
170 
171 	return OK;
172 }
173 
174 static int
adc_read(int adc,uint8_t * val)175 adc_read(int adc, uint8_t * val)
176 {
177 	int r;
178 	spin_t spin;
179 
180 	if (adc != 0 && adc != 1) {
181 		log_warn(&log, "Invalid ADC number %d, expected 0 or 1.\n",
182 		    adc);
183 		return EINVAL;
184 	}
185 
186 	if (val == NULL) {
187 		log_warn(&log, "Read called with a NULL pointer.\n");
188 		return EINVAL;
189 	}
190 
191 	*val = (adc == 0) ? CMD_READ_ADC0 : CMD_READ_ADC1;
192 
193 	/* Select the ADC to read from */
194 	r = i2creg_raw_write8(bus_endpoint, address, *val);
195 	if (r != OK) {
196 		log_warn(&log, "Failed to write ADC read command.\n");
197 		return -1;
198 	}
199 
200 	*val = 0;
201 
202 	/* Repeatedly read until the value is valid (i.e. the conversion
203 	 * finishes). Depending on the timing, the data sheet says this
204 	 * could take up to 400ms.
205 	 */
206 	spin_init(&spin, 400000);
207 	do {
208 		r = i2creg_raw_read8(bus_endpoint, address, val);
209 		if (r != OK) {
210 			log_warn(&log, "Failed to read ADC%d value.\n", adc);
211 			return -1;
212 		}
213 
214 		if (ADC_VAL_IS_VALID(*val)) {
215 			return OK;
216 		}
217 	} while (spin_check(&spin));
218 
219 	/* Final read attempt. If the bus was really busy with other requests
220 	 * and the timing of things happened in the worst possible case,
221 	 * there is a chance that the loop above only did 1 read (slightly
222 	 * before 400 ms) and left the loop. To ensure there is a final read
223 	 * at or after the 400 ms mark, we try one last time here.
224 	 */
225 	r = i2creg_raw_read8(bus_endpoint, address, val);
226 	if (r != OK) {
227 		log_warn(&log, "Failed to read ADC%d value.\n", adc);
228 		return -1;
229 	}
230 
231 	if (ADC_VAL_IS_VALID(*val)) {
232 		return OK;
233 	} else {
234 		log_warn(&log, "ADC%d never returned a valid result.\n", adc);
235 		return EIO;
236 	}
237 }
238 
239 static int
tsl2550_init(void)240 tsl2550_init(void)
241 {
242 	int r;
243 	uint8_t val;
244 
245 	/* Power on the device */
246 	r = i2creg_raw_write8(bus_endpoint, address, CMD_PWR_UP);
247 	if (r != OK) {
248 		log_warn(&log, "Power-up command failed.\n");
249 		return -1;
250 	}
251 
252 	/* Read power on test value */
253 	r = i2creg_raw_read8(bus_endpoint, address, &val);
254 	if (r != OK) {
255 		log_warn(&log, "Failed to read power on test value.\n");
256 		return -1;
257 	}
258 
259 	/* Check power on test value */
260 	if (val != EXPECTED_PWR_UP_TEST_VAL) {
261 		log_warn(&log, "Bad test value. Got 0x%x, expected 0x%x\n",
262 		    val, EXPECTED_PWR_UP_TEST_VAL);
263 		return -1;
264 	}
265 
266 	/* Set range to normal */
267 	r = i2creg_raw_write8(bus_endpoint, address, CMD_NORM_RANGE);
268 	if (r != OK) {
269 		log_warn(&log, "Normal range command failed.\n");
270 		return -1;
271 	}
272 
273 	return OK;
274 }
275 
276 static ssize_t
tsl2550_read(devminor_t UNUSED (minor),u64_t position,endpoint_t endpt,cp_grant_id_t grant,size_t size,int UNUSED (flags),cdev_id_t UNUSED (id))277 tsl2550_read(devminor_t UNUSED(minor), u64_t position, endpoint_t endpt,
278     cp_grant_id_t grant, size_t size, int UNUSED(flags), cdev_id_t UNUSED(id))
279 {
280 	u64_t dev_size;
281 	int bytes, r;
282 	uint32_t lux;
283 
284 	r = measure_lux(&lux);
285 	if (r != OK) {
286 		return EIO;
287 	}
288 
289 	memset(buffer, '\0', BUFFER_LEN + 1);
290 	snprintf(buffer, BUFFER_LEN, "%-16s: %d\n", "ILLUMINANCE", lux);
291 
292 	dev_size = (u64_t)strlen(buffer);
293 	if (position >= dev_size) return 0;
294 	if (position + size > dev_size)
295 		size = (size_t)(dev_size - position);
296 
297 	r = sys_safecopyto(endpt, grant, 0,
298 	    (vir_bytes)(buffer + (size_t)position), size);
299 
300 	return (r != OK) ? r : size;
301 }
302 
303 static void
tsl2550_other(message * m,int ipc_status)304 tsl2550_other(message * m, int ipc_status)
305 {
306 	int r;
307 
308 	if (is_ipc_notify(ipc_status)) {
309 		if (m->m_source == DS_PROC_NR) {
310 			log_debug(&log,
311 			    "bus driver changed state, update endpoint\n");
312 			i2cdriver_handle_bus_update(&bus_endpoint, bus,
313 			    address);
314 		}
315 		return;
316 	}
317 
318 	log_warn(&log, "Invalid message type (0x%x)\n", m->m_type);
319 }
320 
321 static int
sef_cb_lu_state_save(int UNUSED (result),int UNUSED (flags))322 sef_cb_lu_state_save(int UNUSED(result), int UNUSED(flags))
323 {
324 	ds_publish_u32("bus", bus, DSF_OVERWRITE);
325 	ds_publish_u32("address", address, DSF_OVERWRITE);
326 	return OK;
327 }
328 
329 static int
lu_state_restore(void)330 lu_state_restore(void)
331 {
332 	/* Restore the state. */
333 	u32_t value;
334 
335 	ds_retrieve_u32("bus", &value);
336 	ds_delete_u32("bus");
337 	bus = (int) value;
338 
339 	ds_retrieve_u32("address", &value);
340 	ds_delete_u32("address");
341 	address = (int) value;
342 
343 	return OK;
344 }
345 
346 static int
sef_cb_init(int type,sef_init_info_t * UNUSED (info))347 sef_cb_init(int type, sef_init_info_t * UNUSED(info))
348 {
349 	int r;
350 
351 	if (type == SEF_INIT_LU) {
352 		/* Restore the state. */
353 		lu_state_restore();
354 	}
355 
356 	/* look-up the endpoint for the bus driver */
357 	bus_endpoint = i2cdriver_bus_endpoint(bus);
358 	if (bus_endpoint == 0) {
359 		log_warn(&log, "Couldn't find bus driver.\n");
360 		return EXIT_FAILURE;
361 	}
362 
363 	/* claim the device */
364 	r = i2cdriver_reserve_device(bus_endpoint, address);
365 	if (r != OK) {
366 		log_warn(&log, "Couldn't reserve device 0x%x (r=%d)\n",
367 		    address, r);
368 		return EXIT_FAILURE;
369 	}
370 
371 	r = tsl2550_init();
372 	if (r != OK) {
373 		log_warn(&log, "Device Init Failed\n");
374 		return EXIT_FAILURE;
375 	}
376 
377 	if (type != SEF_INIT_LU) {
378 
379 		/* sign up for updates about the i2c bus going down/up */
380 		r = i2cdriver_subscribe_bus_updates(bus);
381 		if (r != OK) {
382 			log_warn(&log, "Couldn't subscribe to bus updates\n");
383 			return EXIT_FAILURE;
384 		}
385 
386 		i2cdriver_announce(bus);
387 		log_debug(&log, "announced\n");
388 	}
389 
390 	return OK;
391 }
392 
393 static void
sef_local_startup(void)394 sef_local_startup(void)
395 {
396 	/*
397 	 * Register init callbacks. Use the same function for all event types
398 	 */
399 	sef_setcb_init_fresh(sef_cb_init);
400 	sef_setcb_init_lu(sef_cb_init);
401 	sef_setcb_init_restart(sef_cb_init);
402 
403 	/*
404 	 * Register live update callbacks.
405 	 */
406 	sef_setcb_lu_state_save(sef_cb_lu_state_save);
407 
408 	/* Let SEF perform startup. */
409 	sef_startup();
410 }
411 
412 int
main(int argc,char * argv[])413 main(int argc, char *argv[])
414 {
415 	int r;
416 
417 	env_setargs(argc, argv);
418 
419 	r = i2cdriver_env_parse(&bus, &address, valid_addrs);
420 	if (r < 0) {
421 		log_warn(&log, "Expecting -args 'bus=X address=0xYY'\n");
422 		log_warn(&log, "Example -args 'bus=1 address=0x39'\n");
423 		return EXIT_FAILURE;
424 	} else if (r > 0) {
425 		log_warn(&log,
426 		    "Invalid slave address for device, expecting 0x39\n");
427 		return EXIT_FAILURE;
428 	}
429 
430 	sef_local_startup();
431 
432 	chardriver_task(&tsl2550_tab);
433 
434 	return 0;
435 }
436