/* Driver for the TSL2550 Ambient Light Sensor */ #include #include #include #include #include #include #include #include /* * Device Commands */ #define CMD_PWR_DOWN 0x00 #define CMD_PWR_UP 0x03 #define CMD_EXT_RANGE 0x1d #define CMD_NORM_RANGE 0x18 #define CMD_READ_ADC0 0x43 #define CMD_READ_ADC1 0x83 /* When powered up and communicating, the register should have this value */ #define EXPECTED_PWR_UP_TEST_VAL 0x03 /* Maximum Lux value in Standard Mode */ #define MAX_LUX_STD_MODE 1846 /* Bit Masks for ADC Data */ #define ADC_VALID_MASK (1<<7) #define ADC_CHORD_MASK ((1<<6)|(1<<5)|(1<<4)) #define ADC_STEP_MASK ((1<<3)|(1<<2)|(1<<1)|(1<<0)) #define ADC_VAL_IS_VALID(x) ((x & ADC_VALID_MASK) == ADC_VALID_MASK) #define ADC_VAL_TO_CHORD_BITS(x) ((x & ADC_CHORD_MASK) >> 4) #define ADC_VAL_TO_STEP_BITS(x) (x & ADC_STEP_MASK) /* logging - use with log_warn(), log_info(), log_debug(), log_trace(), etc */ static struct log log = { .name = "tsl2550", .log_level = LEVEL_INFO, .log_func = default_log }; /* The slave address is hardwired to 0x39 and cannot be changed. */ static i2c_addr_t valid_addrs[2] = { 0x39, 0x00 }; /* Buffer to store output string returned when reading from device file. */ #define BUFFER_LEN 32 char buffer[BUFFER_LEN + 1]; /* the bus that this device is on (counting starting at 1) */ static uint32_t bus; /* slave address of the device */ static i2c_addr_t address; /* endpoint for the driver for the bus itself. */ static endpoint_t bus_endpoint; /* register access functions */ static int reg_read(uint8_t * val); static int reg_write(uint8_t val); /* main driver functions */ static int tsl2550_init(void); static int adc_read(int adc, uint8_t * val); static int measure_lux(uint32_t * lux); /* libchardriver callbacks */ static struct device *tsl2550_prepare(dev_t UNUSED(dev)); static int tsl2550_transfer(endpoint_t endpt, int opcode, u64_t position, iovec_t * iov, unsigned nr_req, endpoint_t UNUSED(user_endpt), unsigned int UNUSED(flags)); static int tsl2550_other(message * m); /* SEF functions */ static int sef_cb_lu_state_save(int); static int lu_state_restore(void); static int sef_cb_init(int type, sef_init_info_t * info); static void sef_local_startup(void); /* Entry points to this driver from libchardriver. */ static struct chardriver tsl2550_tab = { .cdr_open = do_nop, .cdr_close = do_nop, .cdr_ioctl = nop_ioctl, .cdr_prepare = tsl2550_prepare, .cdr_transfer = tsl2550_transfer, .cdr_cleanup = nop_cleanup, .cdr_alarm = nop_alarm, .cdr_cancel = nop_cancel, .cdr_select = nop_select, .cdr_other = tsl2550_other }; static struct device tsl2550_device = { .dv_base = 0, .dv_size = 0 }; /* * These two lookup tables and the formulas used in measure_lux() are from * 'TAOS INTELLIGENT OPTO SENSOR DESIGNER'S NOTEBOOK' Number 9 * 'Simplified TSL2550 Lux Calculation for Embedded and Micro Controllers'. * * The tables and formulas eliminate the need for floating point math and * functions from libm. It also speeds up the calculations. */ /* Look up table for converting ADC values to ADC counts */ static const uint32_t adc_counts_lut[128] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44, 46, 49, 53, 57, 61, 65, 69, 73, 77, 81, 85, 89, 93, 97, 101, 105, 109, 115, 123, 131, 139, 147, 155, 163, 171, 179, 187, 195, 203, 211, 219, 227, 235, 247, 263, 279, 295, 311, 327, 343, 359, 375, 391, 407, 423, 439, 455, 471, 487, 511, 543, 575, 607, 639, 671, 703, 735, 767, 799, 831, 863, 895, 927, 959, 991, 1039, 1103, 1167, 1231, 1295, 1359, 1423, 1487, 1551, 1615, 1679, 1743, 1807, 1871, 1935, 1999, 2095, 2223, 2351, 2479, 2607, 2735, 2863, 2991, 3119, 3247, 3375, 3503, 3631, 3759, 3887, 4015 }; /* Look up table of scaling factors */ static const uint32_t ratio_lut[129] = { 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 98, 98, 98, 98, 98, 98, 98, 97, 97, 97, 97, 97, 96, 96, 96, 96, 95, 95, 95, 94, 94, 93, 93, 93, 92, 92, 91, 91, 90, 89, 89, 88, 87, 87, 86, 85, 84, 83, 82, 81, 80, 79, 78, 77, 75, 74, 73, 71, 69, 68, 66, 64, 62, 60, 58, 56, 54, 52, 49, 47, 44, 42, 41, 40, 40, 39, 39, 38, 38, 37, 37, 37, 36, 36, 36, 35, 35, 35, 35, 34, 34, 34, 34, 33, 33, 33, 33, 32, 32, 32, 32, 32, 31, 31, 31, 31, 31, 30, 30, 30, 30, 30 }; static int measure_lux(uint32_t * lux) { int r; uint8_t adc0_val, adc1_val; uint32_t adc0_cnt, adc1_cnt; uint32_t ratio; r = adc_read(0, &adc0_val); if (r != OK) { return -1; } r = adc_read(1, &adc1_val); if (r != OK) { return -1; } /* Look up the adc count, drop the MSB to put in range 0-127. */ adc0_cnt = adc_counts_lut[adc0_val & ~ADC_VALID_MASK]; adc1_cnt = adc_counts_lut[adc1_val & ~ADC_VALID_MASK]; /* default scaling factor */ ratio = 128; /* calculate ratio - avoid div by 0, ensure cnt1 <= cnt0 */ if ((adc0_cnt != 0) && (adc1_cnt <= adc0_cnt)) { ratio = (adc1_cnt * 128 / adc0_cnt); } /* ensure ratio isn't outside ratio_lut[] */ if (ratio > 128) { ratio = 128; } /* calculate lux */ *lux = ((adc0_cnt - adc1_cnt) * ratio_lut[ratio]) / 256; /* range check */ if (*lux > MAX_LUX_STD_MODE) { *lux = MAX_LUX_STD_MODE; } return OK; } static int adc_read(int adc, uint8_t * val) { int r; spin_t spin; if (adc != 0 && adc != 1) { log_warn(&log, "Invalid ADC number %d, expected 0 or 1.\n", adc); return EINVAL; } if (val == NULL) { log_warn(&log, "Read called with a NULL pointer.\n"); return EINVAL; } *val = (adc == 0) ? CMD_READ_ADC0 : CMD_READ_ADC1; /* Select the ADC to read from */ r = reg_write(*val); if (r != OK) { log_warn(&log, "Failed to write ADC read command.\n"); return -1; } *val = 0; /* Repeatedly read until the value is valid (i.e. the conversion * finishes). Depending on the timing, the data sheet says this * could take up to 400ms. */ spin_init(&spin, 400000); do { r = reg_read(val); if (r != OK) { log_warn(&log, "Failed to read ADC%d value.\n", adc); return -1; } if (ADC_VAL_IS_VALID(*val)) { return OK; } } while (spin_check(&spin)); /* Final read attempt. If the bus was really busy with other requests * and the timing of things happened in the worst possible case, * there is a chance that the loop above only did 1 read (slightly * before 400 ms) and left the loop. To ensure there is a final read * at or after the 400 ms mark, we try one last time here. */ r = reg_read(val); if (r != OK) { log_warn(&log, "Failed to read ADC%d value.\n", adc); return -1; } if (ADC_VAL_IS_VALID(*val)) { return OK; } else { log_warn(&log, "ADC%d never returned a valid result.\n", adc); return EIO; } } static int reg_read(uint8_t * val) { int r; minix_i2c_ioctl_exec_t ioctl_exec; if (val == NULL) { log_warn(&log, "Read called with a NULL pointer.\n"); return EINVAL; } memset(&ioctl_exec, '\0', sizeof(minix_i2c_ioctl_exec_t)); /* Read from chip */ ioctl_exec.iie_op = I2C_OP_READ_WITH_STOP; ioctl_exec.iie_addr = address; /* No register address to write */ ioctl_exec.iie_cmdlen = 0; /* Read one byte */ ioctl_exec.iie_buflen = 1; r = i2cdriver_exec(bus_endpoint, &ioctl_exec); if (r != OK) { log_warn(&log, "reg_read() failed (r=%d)\n", r); return -1; } *val = ioctl_exec.iie_buf[0]; log_trace(&log, "Read 0x%x from reg\n", *val); return OK; } static int reg_write(uint8_t val) { int r; minix_i2c_ioctl_exec_t ioctl_exec; switch (val) { case CMD_PWR_DOWN: case CMD_PWR_UP: case CMD_EXT_RANGE: case CMD_NORM_RANGE: case CMD_READ_ADC0: case CMD_READ_ADC1: /* Command is valid */ break; default: log_warn(&log, "reg_write() called with invalid command 0x%x\n", val); return EINVAL; } memset(&ioctl_exec, '\0', sizeof(minix_i2c_ioctl_exec_t)); /* Write to chip */ ioctl_exec.iie_op = I2C_OP_WRITE_WITH_STOP; ioctl_exec.iie_addr = address; /* No command bytes for writing to this chip */ ioctl_exec.iie_cmdlen = 0; /* Set the byte to write */ ioctl_exec.iie_buf[0] = val; ioctl_exec.iie_buflen = 1; r = i2cdriver_exec(bus_endpoint, &ioctl_exec); if (r != OK) { log_warn(&log, "reg_write() failed (r=%d)\n", r); return -1; } log_trace(&log, "Wrote 0x%x to reg\n", val); return OK; } static int tsl2550_init(void) { int r; uint8_t val; /* Power on the device */ r = reg_write(CMD_PWR_UP); if (r != OK) { log_warn(&log, "Power-up command failed.\n"); return -1; } /* Read power on test value */ r = reg_read(&val); if (r != OK) { log_warn(&log, "Failed to read power on test value.\n"); return -1; } /* Check power on test value */ if (val != EXPECTED_PWR_UP_TEST_VAL) { log_warn(&log, "Bad test value. Got 0x%x, expected 0x%x\n", val, EXPECTED_PWR_UP_TEST_VAL); return -1; } /* Set range to normal */ r = reg_write(CMD_NORM_RANGE); if (r != OK) { log_warn(&log, "Normal range command failed.\n"); return -1; } return OK; } static struct device * tsl2550_prepare(dev_t UNUSED(dev)) { return &tsl2550_device; } static int tsl2550_transfer(endpoint_t endpt, int opcode, u64_t position, iovec_t * iov, unsigned nr_req, endpoint_t UNUSED(user_endpt), unsigned int UNUSED(flags)) { int bytes, r; uint32_t lux; r = measure_lux(&lux); if (r != OK) { return EIO; } memset(buffer, '\0', BUFFER_LEN + 1); snprintf(buffer, BUFFER_LEN, "%-16s: %d\n", "ILLUMINANCE", lux); bytes = strlen(buffer) - position < iov->iov_size ? strlen(buffer) - position : iov->iov_size; if (bytes <= 0) { return OK; } switch (opcode) { case DEV_GATHER_S: r = sys_safecopyto(endpt, (cp_grant_id_t) iov->iov_addr, 0, (vir_bytes) (buffer + position), bytes); iov->iov_size -= bytes; break; default: return EINVAL; } return r; } static int tsl2550_other(message * m) { int r; switch (m->m_type) { case NOTIFY_MESSAGE: if (m->m_source == DS_PROC_NR) { log_debug(&log, "bus driver changed state, update endpoint\n"); i2cdriver_handle_bus_update(&bus_endpoint, bus, address); } r = OK; break; default: log_warn(&log, "Invalid message type (0x%x)\n", m->m_type); r = EINVAL; break; } return r; } static int sef_cb_lu_state_save(int UNUSED(state)) { ds_publish_u32("bus", bus, DSF_OVERWRITE); ds_publish_u32("address", address, DSF_OVERWRITE); return OK; } static int lu_state_restore(void) { /* Restore the state. */ u32_t value; ds_retrieve_u32("bus", &value); ds_delete_u32("bus"); bus = (int) value; ds_retrieve_u32("address", &value); ds_delete_u32("address"); address = (int) value; return OK; } static int sef_cb_init(int type, sef_init_info_t * UNUSED(info)) { int r; if (type == SEF_INIT_LU) { /* Restore the state. */ lu_state_restore(); } /* look-up the endpoint for the bus driver */ bus_endpoint = i2cdriver_bus_endpoint(bus); if (bus_endpoint == 0) { log_warn(&log, "Couldn't find bus driver.\n"); return EXIT_FAILURE; } /* claim the device */ r = i2cdriver_reserve_device(bus_endpoint, address); if (r != OK) { log_warn(&log, "Couldn't reserve device 0x%x (r=%d)\n", address, r); return EXIT_FAILURE; } r = tsl2550_init(); if (r != OK) { log_warn(&log, "Device Init Failed\n"); return EXIT_FAILURE; } if (type != SEF_INIT_LU) { /* sign up for updates about the i2c bus going down/up */ r = i2cdriver_subscribe_bus_updates(bus); if (r != OK) { log_warn(&log, "Couldn't subscribe to bus updates\n"); return EXIT_FAILURE; } i2cdriver_announce(bus); log_debug(&log, "announced\n"); } return OK; } static void sef_local_startup(void) { /* * Register init callbacks. Use the same function for all event types */ sef_setcb_init_fresh(sef_cb_init); sef_setcb_init_lu(sef_cb_init); sef_setcb_init_restart(sef_cb_init); /* * Register live update callbacks. */ /* Agree to update immediately when LU is requested in a valid state. */ sef_setcb_lu_prepare(sef_cb_lu_prepare_always_ready); /* Support live update starting from any standard state. */ sef_setcb_lu_state_isvalid(sef_cb_lu_state_isvalid_standard); /* Register a custom routine to save the state. */ sef_setcb_lu_state_save(sef_cb_lu_state_save); /* Let SEF perform startup. */ sef_startup(); } int main(int argc, char *argv[]) { int r; env_setargs(argc, argv); r = i2cdriver_env_parse(&bus, &address, valid_addrs); if (r < 0) { log_warn(&log, "Expecting -args 'bus=X address=0xYY'\n"); log_warn(&log, "Example -args 'bus=1 address=0x39'\n"); return EXIT_FAILURE; } else if (r > 0) { log_warn(&log, "Invalid slave address for device, expecting 0x39\n"); return EXIT_FAILURE; } sef_local_startup(); chardriver_task(&tsl2550_tab, CHARDRIVER_SYNC); return 0; }