blob: 0e5c0209b6b194fd238af7fa984d2dfea1a8a287 [file] [log] [blame]
/*
* 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)