drivers: sensor: add tsl2561 basic support

Add basic support for ams TSL2561 light sensor. Triggers, attributes
and manual integration time are currently not supported.

Signed-off-by: Gustavo Silva <gustavograzs@gmail.com>
This commit is contained in:
Gustavo Silva 2023-10-04 13:57:24 -03:00 committed by Fabio Baltieri
commit b4625d6f13
7 changed files with 396 additions and 0 deletions

View file

@ -146,6 +146,7 @@ add_subdirectory_ifdef(CONFIG_TMP108 tmp108)
add_subdirectory_ifdef(CONFIG_TMP112 tmp112)
add_subdirectory_ifdef(CONFIG_TMP116 tmp116)
add_subdirectory_ifdef(CONFIG_TSL2540 tsl2540)
add_subdirectory_ifdef(CONFIG_TSL2561 tsl2561)
add_subdirectory_ifdef(CONFIG_VCMP_IT8XXX2 ite_vcmp_it8xxx2)
add_subdirectory_ifdef(CONFIG_VCNL4040 vcnl4040)
add_subdirectory_ifdef(CONFIG_VEML7700 veml7700)

View file

@ -202,6 +202,7 @@ source "drivers/sensor/tmp108/Kconfig"
source "drivers/sensor/tmp112/Kconfig"
source "drivers/sensor/tmp116/Kconfig"
source "drivers/sensor/tsl2540/Kconfig"
source "drivers/sensor/tsl2561/Kconfig"
source "drivers/sensor/vcnl4040/Kconfig"
source "drivers/sensor/veml7700/Kconfig"
source "drivers/sensor/vl53l0x/Kconfig"

View file

@ -0,0 +1,5 @@
# SPDX-License-Identifier: Apache-2.0
zephyr_library()
zephyr_library_sources(tsl2561.c)

View file

@ -0,0 +1,10 @@
# Copyright (c) 2023 Gustavo Silva
# SPDX-License-Identifier: Apache-2.0
config TSL2561
bool "OSRAM-AMS TSL2561 light sensor"
default y
depends on DT_HAS_AMS_TSL2561_ENABLED
select I2C
help
Enable driver for TSL2561 sensor.

View file

@ -0,0 +1,346 @@
/*
* 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];
uint8_t 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_SCALE1)) */
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)

View file

@ -0,0 +1,28 @@
# Copyright (c) 2023, Gustavo Silva
# SPDX-License-Identifier: Apache-2.0
description: |
OSRAM ams TSL2561 light sensor.
compatible: "ams,tsl2561"
include: [sensor-device.yaml, i2c-device.yaml]
properties:
integration-time:
type: int
default: 402
description: |
ADC integration time in ms. The default value matches Timing Register's value at power on.
enum:
- 13
- 101
- 402
gain:
type: int
default: 16
description: |
ADC gain factor. The default value matches Timing Register's value at power on.
enum:
- 1
- 16

View file

@ -794,3 +794,8 @@ test_i2c_adxl367: adxl367@77 {
reg = <0x77>;
odr = <4>;
};
test_i2c_tsl2561: tsl2561@78 {
compatible = "ams,tsl2561";
reg = <0x78>;
};