/* * Copyright (c) 2023, Gustavo Silva * * SPDX-License-Identifier: Apache-2.0 */ #define DT_DRV_COMPAT ams_tsl2561 #include #include #include #include #include #include #include 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)