Remove building with NOCRYPTO option
[minix3.git] / minix / drivers / sensors / tsl2550 / tsl2550.c
blob2819e144f3614202e34ae2f563d8849eed0c9097
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 /* Entry points to this driver from libchardriver. */
73 static struct chardriver tsl2550_tab = {
74 .cdr_read = tsl2550_read,
75 .cdr_other = tsl2550_other
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'.
83 * The tables and formulas eliminate the need for floating point math and
84 * functions from libm. It also speeds up the calculations.
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
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,
128 static int
129 measure_lux(uint32_t * lux)
131 int r;
132 uint8_t adc0_val, adc1_val;
133 uint32_t adc0_cnt, adc1_cnt;
134 uint32_t ratio;
136 r = adc_read(0, &adc0_val);
137 if (r != OK) {
138 return -1;
141 r = adc_read(1, &adc1_val);
142 if (r != OK) {
143 return -1;
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];
150 /* default scaling factor */
151 ratio = 128;
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);
158 /* ensure ratio isn't outside ratio_lut[] */
159 if (ratio > 128) {
160 ratio = 128;
163 /* calculate lux */
164 *lux = ((adc0_cnt - adc1_cnt) * ratio_lut[ratio]) / 256;
166 /* range check */
167 if (*lux > MAX_LUX_STD_MODE) {
168 *lux = MAX_LUX_STD_MODE;
171 return OK;
174 static int
175 adc_read(int adc, uint8_t * val)
177 int r;
178 spin_t spin;
180 if (adc != 0 && adc != 1) {
181 log_warn(&log, "Invalid ADC number %d, expected 0 or 1.\n",
182 adc);
183 return EINVAL;
186 if (val == NULL) {
187 log_warn(&log, "Read called with a NULL pointer.\n");
188 return EINVAL;
191 *val = (adc == 0) ? CMD_READ_ADC0 : CMD_READ_ADC1;
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;
200 *val = 0;
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.
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;
214 if (ADC_VAL_IS_VALID(*val)) {
215 return OK;
217 } while (spin_check(&spin));
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.
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;
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;
239 static int
240 tsl2550_init(void)
242 int r;
243 uint8_t val;
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;
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;
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;
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;
273 return OK;
276 static ssize_t
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))
280 u64_t dev_size;
281 int bytes, r;
282 uint32_t lux;
284 r = measure_lux(&lux);
285 if (r != OK) {
286 return EIO;
289 memset(buffer, '\0', BUFFER_LEN + 1);
290 snprintf(buffer, BUFFER_LEN, "%-16s: %d\n", "ILLUMINANCE", lux);
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);
297 r = sys_safecopyto(endpt, grant, 0,
298 (vir_bytes)(buffer + (size_t)position), size);
300 return (r != OK) ? r : size;
303 static void
304 tsl2550_other(message * m, int ipc_status)
306 int r;
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);
315 return;
318 log_warn(&log, "Invalid message type (0x%x)\n", m->m_type);
321 static int
322 sef_cb_lu_state_save(int UNUSED(result), int UNUSED(flags))
324 ds_publish_u32("bus", bus, DSF_OVERWRITE);
325 ds_publish_u32("address", address, DSF_OVERWRITE);
326 return OK;
329 static int
330 lu_state_restore(void)
332 /* Restore the state. */
333 u32_t value;
335 ds_retrieve_u32("bus", &value);
336 ds_delete_u32("bus");
337 bus = (int) value;
339 ds_retrieve_u32("address", &value);
340 ds_delete_u32("address");
341 address = (int) value;
343 return OK;
346 static int
347 sef_cb_init(int type, sef_init_info_t * UNUSED(info))
349 int r;
351 if (type == SEF_INIT_LU) {
352 /* Restore the state. */
353 lu_state_restore();
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;
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;
371 r = tsl2550_init();
372 if (r != OK) {
373 log_warn(&log, "Device Init Failed\n");
374 return EXIT_FAILURE;
377 if (type != SEF_INIT_LU) {
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;
386 i2cdriver_announce(bus);
387 log_debug(&log, "announced\n");
390 return OK;
393 static void
394 sef_local_startup(void)
397 * Register init callbacks. Use the same function for all event types
399 sef_setcb_init_fresh(sef_cb_init);
400 sef_setcb_init_lu(sef_cb_init);
401 sef_setcb_init_restart(sef_cb_init);
404 * Register live update callbacks.
406 sef_setcb_lu_state_save(sef_cb_lu_state_save);
408 /* Let SEF perform startup. */
409 sef_startup();
413 main(int argc, char *argv[])
415 int r;
417 env_setargs(argc, argv);
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;
430 sef_local_startup();
432 chardriver_task(&tsl2550_tab);
434 return 0;