| /* |
| * Copyright (c) 2023, Gustavo Silva |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #define DT_DRV_COMPAT ams_tsl2561 |
| |
| #include <zephyr/drivers/sensor.h> |
| #include <zephyr/drivers/i2c.h> |
| #include <zephyr/logging/log.h> |
| #include <zephyr/kernel.h> |
| #include <zephyr/devicetree.h> |
| #include <zephyr/sys/byteorder.h> |
| #include <zephyr/sys/__assert.h> |
| |
| LOG_MODULE_REGISTER(TSL2561, CONFIG_SENSOR_LOG_LEVEL); |
| |
| #define TSL2561_CHIP_ID 0x05 |
| |
| #define TSL2561_GAIN_1X 0x00 |
| #define TSL2561_GAIN_16X 0x01 |
| |
| #define TSL2561_INTEGRATION_13MS 0x00 |
| #define TSL2561_INTEGRATION_101MS 0x01 |
| #define TSL2561_INTEGRATION_402MS 0x02 |
| |
| /* Register set */ |
| #define TSL2561_REG_CONTROL 0x00 |
| #define TSL2561_REG_TIMING 0x01 |
| #define TSL2561_REG_THRESHLOWLOW 0x02 |
| #define TSL2561_REG_THRESHLOWHIGH 0x03 |
| #define TSL2561_REG_THRESHHIGHLOW 0x04 |
| #define TSL2561_REG_THRESHHIGHHIGH 0x05 |
| #define TSL2561_REG_INTERRUPT 0x06 |
| #define TSL2561_REG_ID 0x0A |
| #define TSL2561_REG_DATA0LOW 0x0C |
| #define TSL2561_REG_DATA0HIGH 0x0D |
| #define TSL2561_REG_DATA1LOW 0x0E |
| #define TSL2561_REG_DATA1HIGH 0x0F |
| |
| /* Command register fields */ |
| #define TSL2561_COMMAND_CMD BIT(7) |
| #define TSL2561_COMMAND_WORD BIT(5) |
| |
| /* Control register fields */ |
| #define TSL2561_CONTROL_POWER_UP 0x03 |
| #define TSL2561_CONTROL_POWER_DOWN 0x00 |
| |
| /* Timing register fields */ |
| #define TSL2561_TIMING_GAIN BIT(4) |
| #define TSL2561_TIMING_INTEG GENMASK(1, 0) |
| |
| /* ID register part number mask */ |
| #define TSL2561_ID_PARTNO GENMASK(7, 4) |
| |
| /* Lux calculation constants */ |
| #define TSL2561_LUX_SCALE 14U |
| #define TSL2561_RATIO_SCALE 9U |
| #define TSL2561_CH_SCALE 10U |
| #define TSL2561_CHSCALE_TINT0 0x7517 |
| #define TSL2561_CHSCALE_TINT1 0x0FE7 |
| |
| #define TSL2561_LUX_K1T 0X0040 /* 0.125 * 2^RATIO_SCALE */ |
| #define TSL2561_LUX_B1T 0X01F2 /* 0.0304 * 2^LUX_SCALE */ |
| #define TSL2561_LUX_M1T 0X01BE /* 0.0272 * 2^LUX_SCALE */ |
| #define TSL2561_LUX_K2T 0X0080 /* 0.250 * 2^RATIO_SCALE */ |
| #define TSL2561_LUX_B2T 0X0214 /* 0.0325 * 2^LUX_SCALE */ |
| #define TSL2561_LUX_M2T 0X02D1 /* 0.0440 * 2^LUX_SCALE */ |
| #define TSL2561_LUX_K3T 0X00C0 /* 0.375 * 2^RATIO_SCALE */ |
| #define TSL2561_LUX_B3T 0X023F /* 0.0351 * 2^LUX_SCALE */ |
| #define TSL2561_LUX_M3T 0X037B /* 0.0544 * 2^LUX_SCALE */ |
| #define TSL2561_LUX_K4T 0X0100 /* 0.50 * 2^RATIO_SCALE */ |
| #define TSL2561_LUX_B4T 0X0270 /* 0.0381 * 2^LUX_SCALE */ |
| #define TSL2561_LUX_M4T 0X03FE /* 0.0624 * 2^LUX_SCALE */ |
| #define TSL2561_LUX_K5T 0X0138 /* 0.61 * 2^RATIO_SCALE */ |
| #define TSL2561_LUX_B5T 0X016F /* 0.0224 * 2^LUX_SCALE */ |
| #define TSL2561_LUX_M5T 0X01FC /* 0.0310 * 2^LUX_SCALE */ |
| #define TSL2561_LUX_K6T 0X019A /* 0.80 * 2^RATIO_SCALE */ |
| #define TSL2561_LUX_B6T 0X00D2 /* 0.0128 * 2^LUX_SCALE */ |
| #define TSL2561_LUX_M6T 0X00FB /* 0.0153 * 2^LUX_SCALE */ |
| #define TSL2561_LUX_K7T 0X029A /* 1.3 * 2^RATIO_SCALE */ |
| #define TSL2561_LUX_B7T 0X0018 /* 0.00146 * 2^LUX_SCALE */ |
| #define TSL2561_LUX_M7T 0X0012 /* 0.00112 * 2^LUX_SCALE */ |
| #define TSL2561_LUX_K8T 0X029A /* 1.3 * 2^RATIO_SCALE */ |
| #define TSL2561_LUX_B8T 0X0000 /* 0.000 * 2^LUX_SCALE */ |
| #define TSL2561_LUX_M8T 0X0000 /* 0.000 * 2^LUX_SCALE */ |
| |
| struct tsl2561_config { |
| struct i2c_dt_spec i2c; |
| uint16_t integration_time; |
| uint8_t gain; |
| }; |
| |
| struct tsl2561_data { |
| uint16_t ch0; |
| uint16_t ch1; |
| uint32_t ch_scale; |
| }; |
| |
| static int tsl2561_reg_read(const struct device *dev, uint8_t reg, uint8_t *buf, uint8_t size) |
| { |
| int ret; |
| const struct tsl2561_config *config = dev->config; |
| uint8_t cmd = (TSL2561_COMMAND_CMD | TSL2561_COMMAND_WORD | reg); |
| |
| ret = i2c_write_read_dt(&config->i2c, &cmd, 1U, buf, size); |
| if (ret < 0) { |
| LOG_ERR("Failed reading register 0x%02x", reg); |
| return ret; |
| } |
| |
| return 0; |
| } |
| |
| static int tsl2561_reg_write(const struct device *dev, uint8_t reg, uint8_t val) |
| { |
| int ret; |
| const struct tsl2561_config *config = dev->config; |
| uint8_t buf[2]; |
| |
| buf[0] = (TSL2561_COMMAND_CMD | TSL2561_COMMAND_WORD | reg); |
| buf[1] = val; |
| |
| ret = i2c_write_dt(&config->i2c, buf, 2U); |
| if (ret < 0) { |
| LOG_ERR("Failed writing register 0x%02x", reg); |
| return ret; |
| } |
| |
| return 0; |
| } |
| |
| static int tsl2561_sample_fetch(const struct device *dev, enum sensor_channel chan) |
| { |
| const struct tsl2561_config *config = dev->config; |
| struct tsl2561_data *data = dev->data; |
| uint8_t bytes[2]; |
| int ret; |
| |
| if (chan != SENSOR_CHAN_ALL && chan != SENSOR_CHAN_LIGHT) { |
| LOG_ERR("Unsupported sensor channel"); |
| return -ENOTSUP; |
| } |
| |
| ret = tsl2561_reg_write(dev, TSL2561_REG_CONTROL, TSL2561_CONTROL_POWER_UP); |
| if (ret < 0) { |
| LOG_ERR("Failed to power up device"); |
| return ret; |
| } |
| |
| /* Short sleep after power up. Not in the datasheet, but found by trial and error */ |
| k_msleep(5); |
| |
| k_msleep(config->integration_time); |
| |
| /* Read data register's lower and upper bytes consecutively */ |
| ret = tsl2561_reg_read(dev, TSL2561_REG_DATA0LOW, bytes, 2U); |
| if (ret < 0) { |
| LOG_ERR("Failed reading channel0 data"); |
| return ret; |
| } |
| data->ch0 = bytes[1] << 8 | bytes[0]; |
| |
| ret = tsl2561_reg_read(dev, TSL2561_REG_DATA1LOW, bytes, 2U); |
| if (ret < 0) { |
| LOG_ERR("Failed reading channel1 data"); |
| return ret; |
| } |
| data->ch1 = bytes[1] << 8 | bytes[0]; |
| |
| ret = tsl2561_reg_write(dev, TSL2561_REG_CONTROL, TSL2561_CONTROL_POWER_DOWN); |
| if (ret < 0) { |
| LOG_ERR("Failed to power down device"); |
| return ret; |
| } |
| |
| LOG_DBG("channel0: 0x%x; channel1: 0x%x", data->ch0, data->ch1); |
| |
| return 0; |
| } |
| |
| static int tsl2561_channel_get(const struct device *dev, enum sensor_channel chan, |
| struct sensor_value *val) |
| { |
| struct tsl2561_data *data = dev->data; |
| uint32_t channel0; |
| uint32_t channel1; |
| |
| if (chan != SENSOR_CHAN_ALL && chan != SENSOR_CHAN_LIGHT) { |
| return -ENOTSUP; |
| } |
| |
| channel0 = (data->ch0 * data->ch_scale) >> TSL2561_CH_SCALE; |
| channel1 = (data->ch1 * data->ch_scale) >> TSL2561_CH_SCALE; |
| |
| uint32_t ratio1 = 0; |
| |
| if (channel0 != 0) { |
| ratio1 = (channel1 << (TSL2561_RATIO_SCALE + 1)) / channel0; |
| } |
| |
| /* Round the ratio value */ |
| uint32_t ratio = (ratio1 + 1) >> 1; |
| |
| uint32_t b = 0; |
| uint32_t m = 0; |
| |
| if (ratio <= TSL2561_LUX_K1T) { |
| b = TSL2561_LUX_B1T; |
| m = TSL2561_LUX_M1T; |
| } else if (ratio <= TSL2561_LUX_K2T) { |
| b = TSL2561_LUX_B2T; |
| m = TSL2561_LUX_M2T; |
| } else if (ratio <= TSL2561_LUX_K3T) { |
| b = TSL2561_LUX_B3T; |
| m = TSL2561_LUX_M3T; |
| } else if (ratio <= TSL2561_LUX_K4T) { |
| b = TSL2561_LUX_B4T; |
| m = TSL2561_LUX_M4T; |
| } else if (ratio <= TSL2561_LUX_K5T) { |
| b = TSL2561_LUX_B5T; |
| m = TSL2561_LUX_M5T; |
| } else if (ratio <= TSL2561_LUX_K6T) { |
| b = TSL2561_LUX_B6T; |
| m = TSL2561_LUX_M6T; |
| } else if (ratio <= TSL2561_LUX_K7T) { |
| b = TSL2561_LUX_B7T; |
| m = TSL2561_LUX_M7T; |
| } else if (ratio > TSL2561_LUX_K8T) { |
| b = TSL2561_LUX_B8T; |
| m = TSL2561_LUX_M8T; |
| } |
| |
| int32_t tmp = ((channel0 * b) - (channel1 * m)); |
| |
| /* Round LSB (2^(LUX_SCALE−1)) */ |
| tmp += (1 << (TSL2561_LUX_SCALE - 1)); |
| |
| /* Strip off fractional portion */ |
| val->val1 = tmp >> TSL2561_LUX_SCALE; |
| |
| val->val2 = 0; |
| |
| return 0; |
| } |
| |
| static const struct sensor_driver_api tsl2561_driver_api = { |
| .sample_fetch = tsl2561_sample_fetch, |
| .channel_get = tsl2561_channel_get |
| }; |
| |
| static int tsl2561_sensor_setup(const struct device *dev) |
| { |
| const struct tsl2561_config *config = dev->config; |
| struct tsl2561_data *data = dev->data; |
| uint8_t timing_reg; |
| uint8_t chip_id; |
| uint8_t tmp; |
| int ret; |
| |
| ret = tsl2561_reg_read(dev, TSL2561_REG_ID, &chip_id, 1U); |
| if (ret < 0) { |
| LOG_ERR("Failed reading chip ID"); |
| return ret; |
| } |
| |
| if (FIELD_GET(TSL2561_ID_PARTNO, chip_id) != TSL2561_CHIP_ID) { |
| LOG_ERR("Chip ID is invalid! Device @%02x is not TSL2561!", config->i2c.addr); |
| return -EIO; |
| } |
| |
| switch (config->integration_time) { |
| case 13: |
| tmp = TSL2561_INTEGRATION_13MS; |
| data->ch_scale = TSL2561_CHSCALE_TINT0; |
| break; |
| case 101: |
| tmp = TSL2561_INTEGRATION_101MS; |
| data->ch_scale = TSL2561_CHSCALE_TINT1; |
| break; |
| case 402: |
| tmp = TSL2561_INTEGRATION_402MS; |
| data->ch_scale = (1 << TSL2561_CH_SCALE); |
| break; |
| default: |
| LOG_ERR("Invalid integration time"); |
| return -EINVAL; |
| } |
| |
| timing_reg = TSL2561_TIMING_INTEG & tmp; |
| |
| switch (config->gain) { |
| case 1: |
| tmp = TSL2561_GAIN_1X; |
| data->ch_scale = data->ch_scale << 4; |
| break; |
| case 16: |
| tmp = TSL2561_GAIN_16X; |
| break; |
| default: |
| LOG_ERR("Invalid ADC gain"); |
| return -EINVAL; |
| } |
| |
| timing_reg |= FIELD_PREP(TSL2561_TIMING_GAIN, tmp); |
| |
| ret = tsl2561_reg_write(dev, TSL2561_REG_TIMING, timing_reg); |
| if (ret < 0) { |
| LOG_ERR("Failed setting timing register"); |
| return ret; |
| } |
| |
| return 0; |
| } |
| |
| static int tsl2561_init(const struct device *dev) |
| { |
| const struct tsl2561_config *config = dev->config; |
| int ret; |
| |
| if (!i2c_is_ready_dt(&config->i2c)) { |
| LOG_ERR("I2C dev %s not ready", config->i2c.bus->name); |
| return -ENODEV; |
| } |
| |
| ret = tsl2561_sensor_setup(dev); |
| if (ret < 0) { |
| LOG_ERR("Failed to configure device"); |
| return ret; |
| } |
| |
| return 0; |
| } |
| |
| #define TSL2561_INIT_INST(n) \ |
| static struct tsl2561_data tsl2561_data_##n; \ |
| static const struct tsl2561_config tsl2561_config_##n = { \ |
| .i2c = I2C_DT_SPEC_INST_GET(n), \ |
| .integration_time = DT_INST_PROP(n, integration_time), \ |
| .gain = DT_INST_PROP(n, gain)}; \ |
| SENSOR_DEVICE_DT_INST_DEFINE(n, tsl2561_init, NULL, &tsl2561_data_##n, \ |
| &tsl2561_config_##n, POST_KERNEL, \ |
| CONFIG_SENSOR_INIT_PRIORITY, &tsl2561_driver_api); |
| |
| DT_INST_FOREACH_STATUS_OKAY(TSL2561_INIT_INST) |