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