diff --git a/drivers/sensor/CMakeLists.txt b/drivers/sensor/CMakeLists.txt index e9ccd669faa..d90be761d2f 100644 --- a/drivers/sensor/CMakeLists.txt +++ b/drivers/sensor/CMakeLists.txt @@ -158,6 +158,7 @@ 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_TSL2591 tsl2591) add_subdirectory_ifdef(CONFIG_VCMP_IT8XXX2 ite_vcmp_it8xxx2) add_subdirectory_ifdef(CONFIG_VCNL4040 vcnl4040) add_subdirectory_ifdef(CONFIG_VCNL36825T vcnl36825t) diff --git a/drivers/sensor/Kconfig b/drivers/sensor/Kconfig index a85d949678c..5f5860543a3 100644 --- a/drivers/sensor/Kconfig +++ b/drivers/sensor/Kconfig @@ -238,6 +238,7 @@ source "drivers/sensor/tmp112/Kconfig" source "drivers/sensor/tmp116/Kconfig" source "drivers/sensor/tsl2540/Kconfig" source "drivers/sensor/tsl2561/Kconfig" +source "drivers/sensor/tsl2591/Kconfig" source "drivers/sensor/vcnl4040/Kconfig" source "drivers/sensor/vcnl36825t/Kconfig" source "drivers/sensor/veml7700/Kconfig" diff --git a/drivers/sensor/tsl2591/CMakeLists.txt b/drivers/sensor/tsl2591/CMakeLists.txt new file mode 100644 index 00000000000..deccc02f8e5 --- /dev/null +++ b/drivers/sensor/tsl2591/CMakeLists.txt @@ -0,0 +1,6 @@ +# SPDX-License-Identifier: Apache-2.0 + +zephyr_library() + +zephyr_library_sources(tsl2591.c) +zephyr_library_sources_ifdef(CONFIG_TSL2591_TRIGGER tsl2591_trigger.c) diff --git a/drivers/sensor/tsl2591/Kconfig b/drivers/sensor/tsl2591/Kconfig new file mode 100644 index 00000000000..4718f577e12 --- /dev/null +++ b/drivers/sensor/tsl2591/Kconfig @@ -0,0 +1,63 @@ +# Copyright (c) 2023 Kurtis Dinelle +# SPDX-License-Identifier: Apache-2.0 + +menuconfig TSL2591 + bool "OSRAM-AMS TSL2591 light sensor" + default y + depends on DT_HAS_AMS_TSL2591_ENABLED + select I2C + help + Enable driver for TSL2591 sensor. + +if TSL2591 +config TSL2591_FETCH_WAIT + bool "Wait for valid sensor reading before fetch" + help + If set, the driver will automatically wait for the duration of an integration cycle + during a fetch call if necessary. + +config TSL2591_WARN_SATURATED + bool "Warn if sensor is potentially saturated" + help + If set, the driver will warn if the sensor ADC is + potentially saturated after a data fetch. + +config TSL2591_TRIGGER + bool + +choice + prompt "Trigger mode" + default TSL2591_TRIGGER_NONE + help + Specify the type of triggering to be used by the driver. + +config TSL2591_TRIGGER_NONE + bool "No trigger" + +config TSL2591_TRIGGER_GLOBAL_THREAD + bool "Use global thread" + depends on GPIO + select TSL2591_TRIGGER + +config TSL2591_TRIGGER_OWN_THREAD + bool "Use own thread" + depends on GPIO + select TSL2591_TRIGGER + +endchoice + +config TSL2591_THREAD_PRIORITY + int "Thread priority" + depends on TSL2591_TRIGGER_OWN_THREAD + default 10 + help + Priority of thread used by the driver to handle interrupts. + +config TSL2591_THREAD_STACK_SIZE + int "Thread stack size" + depends on TSL2591_TRIGGER_OWN_THREAD + default 1024 + help + Stack size of thread used by the driver to handle interrupts. + +endif # TSL2591 diff --git a/drivers/sensor/tsl2591/tsl2591.c b/drivers/sensor/tsl2591/tsl2591.c new file mode 100644 index 00000000000..ee2c0b56cb4 --- /dev/null +++ b/drivers/sensor/tsl2591/tsl2591.c @@ -0,0 +1,535 @@ +/* + * Copyright (c) 2023 Kurtis Dinelle + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#define DT_DRV_COMPAT ams_tsl2591 + +#include +#include +#include +#include "tsl2591.h" + +LOG_MODULE_REGISTER(TSL2591, CONFIG_SENSOR_LOG_LEVEL); + +static int tsl2591_reg_read(const struct device *dev, uint8_t reg, uint8_t *buf, uint8_t size) +{ + const struct tsl2591_config *config = dev->config; + uint8_t cmd = TSL2591_NORMAL_CMD | reg; + + return i2c_write_read_dt(&config->i2c, &cmd, 1U, buf, size); +} + +static int tsl2591_reg_write(const struct device *dev, uint8_t reg, uint8_t val) +{ + const struct tsl2591_config *config = dev->config; + uint8_t cmd[2] = {TSL2591_NORMAL_CMD | reg, val}; + + return i2c_write_dt(&config->i2c, cmd, 2U); +} + +int tsl2591_reg_update(const struct device *dev, uint8_t reg, uint8_t mask, uint8_t val) +{ + uint8_t old_value, new_value; + int ret; + + ret = tsl2591_reg_read(dev, reg, &old_value, 1U); + if (ret < 0) { + return ret; + } + + new_value = (old_value & ~mask) | (val & mask); + if (new_value == old_value) { + return 0; + } + + return tsl2591_reg_write(dev, reg, new_value); +} + +static int tsl2591_sample_fetch(const struct device *dev, enum sensor_channel chan) +{ + struct tsl2591_data *data = dev->data; + uint8_t als_data[4]; + int ret; + +#ifdef CONFIG_TSL2591_FETCH_WAIT + uint8_t status; + + ret = tsl2591_reg_read(dev, TSL2591_REG_STATUS, &status, 1U); + if (ret < 0) { + LOG_ERR("Failed to read status register"); + return ret; + } + + /* Check if ALS has completed an integration cycle since AEN asserted. + * If not, sleep for the duration of an integration cycle to ensure valid reading. + */ + if (!(status & TSL2591_AVALID_MASK)) { + k_msleep((data->atime / 100) * TSL2591_MAX_TIME_STEP); + } + + /* Reassert AEN to determine if next reading is valid */ + ret = tsl2591_reg_update(dev, TSL2591_REG_ENABLE, TSL2591_AEN_MASK, TSL2591_AEN_OFF); + if (ret < 0) { + LOG_ERR("Failed to disable ALS"); + return ret; + } + + ret = tsl2591_reg_update(dev, TSL2591_REG_ENABLE, TSL2591_AEN_MASK, TSL2591_AEN_ON); + if (ret < 0) { + LOG_ERR("Failed to re-enable ALS"); + return ret; + } +#endif + + switch (chan) { + case SENSOR_CHAN_ALL: + ret = tsl2591_reg_read(dev, TSL2591_REG_C0DATAL, als_data, 4U); + if (ret < 0) { + LOG_ERR("Failed to read ALS data"); + return ret; + } + + data->vis_count = sys_get_le16(als_data); + data->ir_count = sys_get_le16(als_data + 2); + break; + case SENSOR_CHAN_LIGHT: + ret = tsl2591_reg_read(dev, TSL2591_REG_C0DATAL, als_data, 2U); + if (ret < 0) { + LOG_ERR("Failed to read ALS visible light data"); + return ret; + } + + data->vis_count = sys_get_le16(als_data); + break; + case SENSOR_CHAN_IR: + ret = tsl2591_reg_read(dev, TSL2591_REG_C1DATAL, als_data, 2U); + if (ret < 0) { + LOG_ERR("Failed to read ALS infrared data"); + return ret; + } + + data->ir_count = sys_get_le16(als_data); + break; + default: + LOG_ERR("Unsupported sensor channel"); + return -ENOTSUP; + } + +#ifdef CONFIG_TSL2591_WARN_SATURATED + uint16_t max_count = data->atime == 100 ? TSL2591_MAX_ADC_100 : TSL2591_MAX_ADC; + bool vis_saturated = (chan == SENSOR_CHAN_ALL || chan == SENSOR_CHAN_LIGHT) && + (data->vis_count >= max_count); + bool ir_saturated = (chan == SENSOR_CHAN_ALL || chan == SENSOR_CHAN_IR) && + (data->ir_count >= max_count); + if (vis_saturated || ir_saturated) { + LOG_WRN("Sensor ADC potentially saturated, reading may be invalid"); + return -EOVERFLOW; + } +#endif + + return 0; +} + +static int tsl2591_channel_get(const struct device *dev, enum sensor_channel chan, + struct sensor_value *val) +{ + const struct tsl2591_data *data = dev->data; + int64_t cpl = data->atime * data->again; + int64_t strength; + + /* Unfortunately, datasheet does not provide a lux conversion formula for this particular + * device. There is still ongoing discussion about the proper formula, though this + * implementation uses a slightly modified version of the Adafruit library formula: + * https://github.com/adafruit/Adafruit_TSL2591_Library/ + * + * Since the device relies on both visible and IR readings to calculate lux, + * read SENSOR_CHAN_ALL to get a closer approximation of lux. Reading SENSOR_CHAN_LIGHT or + * SENSOR_CHAN_IR individually can be more closely thought of as relative strength + * as opposed to true lux. + */ + switch (chan) { + case SENSOR_CHAN_ALL: + if (data->vis_count > 0) { + cpl *= 1000000; + strength = + (data->vis_count - data->ir_count) * + (1000000 - (((int64_t)data->ir_count * 1000000) / data->vis_count)); + } else { + strength = 0; + } + break; + case SENSOR_CHAN_LIGHT: + strength = data->vis_count; + break; + case SENSOR_CHAN_IR: + strength = data->ir_count; + break; + default: + LOG_ERR("Unsupported sensor channel"); + return -ENOTSUP; + } + + strength *= TSL2591_LUX_DF; + val->val1 = strength / cpl; + val->val2 = ((strength % cpl) * 1000000) / cpl; + + return 0; +} + +#ifdef CONFIG_TSL2591_TRIGGER +static int tsl2591_set_threshold(const struct device *dev, enum sensor_attribute attr, + const struct sensor_value *val) +{ + const struct tsl2591_data *data = dev->data; + const struct tsl2591_config *config = dev->config; + uint64_t cpl; + uint32_t raw; + uint16_t thld; + uint8_t thld_reg; + uint8_t cmd[3]; + int ret; + + /* Convert from relative strength of visible light to raw value */ + cpl = data->atime * data->again; + raw = ((val->val1 * cpl) / TSL2591_LUX_DF) + + ((val->val2 * cpl) / (1000000U * TSL2591_LUX_DF)); + + if (raw > TSL2591_MAX_ADC) { + LOG_ERR("Given value would overflow threshold register"); + return -EOVERFLOW; + } + + thld = sys_cpu_to_le16(raw); + thld_reg = attr == SENSOR_ATTR_LOWER_THRESH ? TSL2591_REG_AILTL : TSL2591_REG_AIHTL; + + cmd[0] = TSL2591_NORMAL_CMD | thld_reg; + bytecpy(cmd + 1, &thld, 2U); + + ret = i2c_write_dt(&config->i2c, cmd, 3U); + if (ret < 0) { + LOG_ERR("Failed to set interrupt threshold"); + } + + return ret; +} + +static int tsl2591_set_persist(const struct device *dev, int32_t persist_filter) +{ + uint8_t persist_mode; + int ret; + + switch (persist_filter) { + case 0: + persist_mode = TSL2591_PERSIST_EVERY; + break; + case 1: + persist_mode = TSL2591_PERSIST_1; + break; + case 2: + persist_mode = TSL2591_PERSIST_2; + break; + case 3: + persist_mode = TSL2591_PERSIST_3; + break; + case 5: + persist_mode = TSL2591_PERSIST_5; + break; + case 10: + persist_mode = TSL2591_PERSIST_10; + break; + case 15: + persist_mode = TSL2591_PERSIST_15; + break; + case 20: + persist_mode = TSL2591_PERSIST_20; + break; + case 25: + persist_mode = TSL2591_PERSIST_25; + break; + case 30: + persist_mode = TSL2591_PERSIST_30; + break; + case 35: + persist_mode = TSL2591_PERSIST_35; + break; + case 40: + persist_mode = TSL2591_PERSIST_40; + break; + case 45: + persist_mode = TSL2591_PERSIST_45; + break; + case 50: + persist_mode = TSL2591_PERSIST_50; + break; + case 55: + persist_mode = TSL2591_PERSIST_55; + break; + case 60: + persist_mode = TSL2591_PERSIST_60; + break; + default: + LOG_ERR("Invalid persist filter"); + return -EINVAL; + } + + ret = tsl2591_reg_write(dev, TSL2591_REG_PERSIST, persist_mode); + if (ret < 0) { + LOG_ERR("Failed to set persist filter"); + } + + return ret; +} +#endif + +static int tsl2591_set_gain(const struct device *dev, enum sensor_gain_tsl2591 gain) +{ + struct tsl2591_data *data = dev->data; + uint8_t gain_mode; + int ret; + + switch (gain) { + case TSL2591_SENSOR_GAIN_LOW: + data->again = TSL2591_GAIN_SCALE_LOW; + gain_mode = TSL2591_GAIN_MODE_LOW; + break; + case TSL2591_SENSOR_GAIN_MED: + data->again = TSL2591_GAIN_SCALE_MED; + gain_mode = TSL2591_GAIN_MODE_MED; + break; + case TSL2591_SENSOR_GAIN_HIGH: + data->again = TSL2591_GAIN_SCALE_HIGH; + gain_mode = TSL2591_GAIN_MODE_HIGH; + break; + case TSL2591_SENSOR_GAIN_MAX: + data->again = TSL2591_GAIN_SCALE_MAX; + gain_mode = TSL2591_GAIN_MODE_MAX; + break; + default: + LOG_ERR("Invalid gain mode"); + return -EINVAL; + } + + ret = tsl2591_reg_update(dev, TSL2591_REG_CONFIG, TSL2591_AGAIN_MASK, gain_mode); + if (ret < 0) { + LOG_ERR("Failed to set gain mode"); + } + + return ret; +} + +static int tsl2591_set_integration(const struct device *dev, int32_t integration_time) +{ + struct tsl2591_data *data = dev->data; + uint8_t atime_mode; + int ret; + + switch (integration_time) { + case 100: + atime_mode = TSL2591_INTEGRATION_100MS; + break; + case 200: + atime_mode = TSL2591_INTEGRATION_200MS; + break; + case 300: + atime_mode = TSL2591_INTEGRATION_300MS; + break; + case 400: + atime_mode = TSL2591_INTEGRATION_400MS; + break; + case 500: + atime_mode = TSL2591_INTEGRATION_500MS; + break; + case 600: + atime_mode = TSL2591_INTEGRATION_600MS; + break; + default: + LOG_ERR("Invalid integration time"); + return -EINVAL; + } + + ret = tsl2591_reg_update(dev, TSL2591_REG_CONFIG, TSL2591_ATIME_MASK, atime_mode); + if (ret < 0) { + LOG_ERR("Failed to set integration time"); + return ret; + } + + data->atime = integration_time; + + return 0; +} + +static int tsl2591_attr_set(const struct device *dev, enum sensor_channel chan, + enum sensor_attribute attr, const struct sensor_value *val) +{ + const struct tsl2591_data *data = dev->data; + int ret; + + ret = tsl2591_reg_update(dev, TSL2591_REG_ENABLE, TSL2591_POWER_MASK, TSL2591_POWER_OFF); + if (ret < 0) { + LOG_ERR("Unable to power down device"); + return ret; + } + +#ifdef CONFIG_TSL2591_TRIGGER + if (attr == SENSOR_ATTR_UPPER_THRESH || attr == SENSOR_ATTR_LOWER_THRESH) { + if (chan == SENSOR_CHAN_LIGHT) { + ret = tsl2591_set_threshold(dev, attr, val); + } else { + LOG_ERR("Attribute not supported for channel"); + ret = -ENOTSUP; + } + goto exit; + } +#endif + + switch ((enum sensor_attribute_tsl2591)attr) { + case SENSOR_ATTR_GAIN_MODE: + ret = tsl2591_set_gain(dev, (enum sensor_gain_tsl2591)val->val1); + break; + case SENSOR_ATTR_INTEGRATION_TIME: + ret = tsl2591_set_integration(dev, val->val1); + break; + +#ifdef CONFIG_TSL2591_TRIGGER + case SENSOR_ATTR_INT_PERSIST: + ret = tsl2591_set_persist(dev, val->val1); + break; +#endif + default: + LOG_ERR("Invalid sensor attribute"); + ret = -EINVAL; + goto exit; /* So the compiler doesn't warn if triggers not enabled */ + } + +exit: + if (data->powered_on) { + ret = tsl2591_reg_update(dev, TSL2591_REG_ENABLE, TSL2591_POWER_MASK, + TSL2591_POWER_ON); + } + + return ret; +} + +static int tsl2591_setup(const struct device *dev) +{ + struct tsl2591_data *data = dev->data; + uint8_t device_id; + int ret; + + ret = tsl2591_reg_write(dev, TSL2591_REG_CONFIG, TSL2591_SRESET); + if (ret < 0) { + LOG_ERR("Failed to reset device"); + return ret; + } + + ret = tsl2591_reg_read(dev, TSL2591_REG_ID, &device_id, 1U); + if (ret < 0) { + LOG_ERR("Failed to read device ID"); + return ret; + } + + if (device_id != TSL2591_DEV_ID) { + LOG_ERR("Device with ID 0x%02x is not supported", device_id); + return -ENOTSUP; + } + + /* Set initial values to match sensor values on reset */ + data->again = TSL2591_GAIN_SCALE_LOW; + data->atime = 100U; + + ret = tsl2591_reg_write(dev, TSL2591_REG_ENABLE, TSL2591_POWER_ON); + if (ret < 0) { + LOG_ERR("Failed to perform initial power up of device"); + return ret; + } + + data->powered_on = true; + + return 0; +} + +static int tsl2591_init(const struct device *dev) +{ + const struct tsl2591_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 = tsl2591_setup(dev); + if (ret < 0) { + LOG_ERR("Failed to setup device"); + return ret; + } + +#ifdef CONFIG_TSL2591_TRIGGER + ret = tsl2591_initialize_int(dev); + if (ret < 0) { + LOG_ERR("Failed to initialize interrupt!"); + return ret; + } +#endif + + return 0; +} + +static const struct sensor_driver_api tsl2591_driver_api = { +#ifdef CONFIG_TSL2591_TRIGGER + .trigger_set = tsl2591_trigger_set, +#endif + .attr_set = tsl2591_attr_set, + .sample_fetch = tsl2591_sample_fetch, + .channel_get = tsl2591_channel_get}; + +#ifdef CONFIG_PM_DEVICE +static int tsl2591_pm_action(const struct device *dev, enum pm_device_action action) +{ + struct tsl2591_data *data = dev->data; + int ret; + + switch (action) { + case PM_DEVICE_ACTION_RESUME: + ret = tsl2591_reg_update(dev, TSL2591_REG_ENABLE, TSL2591_POWER_MASK, + TSL2591_POWER_ON); + if (ret < 0) { + LOG_ERR("Failed to power on device"); + return ret; + } + + data->powered_on = true; + break; + case PM_DEVICE_ACTION_SUSPEND: + ret = tsl2591_reg_update(dev, TSL2591_REG_ENABLE, TSL2591_POWER_MASK, + TSL2591_POWER_OFF); + if (ret < 0) { + LOG_ERR("Failed to power off device"); + return ret; + } + + data->powered_on = false; + break; + default: + LOG_ERR("Unsupported PM action"); + return -ENOTSUP; + } + + return 0; +} +#endif + +#define TSL2591_INIT_INST(n) \ + static struct tsl2591_data tsl2591_data_##n; \ + static const struct tsl2591_config tsl2591_config_##n = { \ + .i2c = I2C_DT_SPEC_INST_GET(n), \ + IF_ENABLED(CONFIG_TSL2591_TRIGGER, \ + (.int_gpio = GPIO_DT_SPEC_INST_GET_OR(n, int_gpios, {0}),))}; \ + PM_DEVICE_DT_INST_DEFINE(n, tsl2591_pm_action); \ + SENSOR_DEVICE_DT_INST_DEFINE(n, tsl2591_init, PM_DEVICE_DT_INST_GET(n), &tsl2591_data_##n, \ + &tsl2591_config_##n, POST_KERNEL, \ + CONFIG_SENSOR_INIT_PRIORITY, &tsl2591_driver_api); + +DT_INST_FOREACH_STATUS_OKAY(TSL2591_INIT_INST) diff --git a/drivers/sensor/tsl2591/tsl2591.h b/drivers/sensor/tsl2591/tsl2591.h new file mode 100644 index 00000000000..ef2fb5d797b --- /dev/null +++ b/drivers/sensor/tsl2591/tsl2591.h @@ -0,0 +1,151 @@ +/* + * Copyright (c) 2023 Kurtis Dinelle + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_DRIVERS_SENSOR_TSL2591_TSL2591_H_ +#define ZEPHYR_DRIVERS_SENSOR_TSL2591_TSL2591_H_ + +#include +#include +#include +#include + +/* Device Identification */ +#define TSL2591_DEV_ID 0x50 + +/* Command: CMD:7 | TRANSACTION:6:5 | ADDR/SF:4:0 */ +#define TSL2591_NORMAL_CMD (BIT(7) | BIT(5)) +#define TSL2591_SPECIAL_CMD (BIT(7) | BIT(6) | BIT(5)) +#define TSL2591_CLEAR_INT_CMD (TSL2591_SPECIAL_CMD | 0x7) + +/* Enable: (0x00): NPIEN:7 | SAI:6 | Reserved:5 | AIEN:4 | Reserved:3:2 | AEN:1 | PON:0 */ +#define TSL2591_POWER_MASK (BIT(1) | BIT(0)) +#define TSL2591_POWER_ON (BIT(1) | BIT(0)) +#define TSL2591_POWER_OFF (0) +#define TSL2591_AEN_MASK (BIT(1)) +#define TSL2591_AEN_ON (BIT(1)) +#define TSL2591_AEN_OFF (0) +#define TSL2591_AIEN_MASK (BIT(4)) +#define TSL2591_AIEN_ON (BIT(4)) +#define TSL2591_AIEN_OFF (0) + +/* Config/Control: (0x01): SRESET:7 | Reserved:6 | AGAIN:5:4 | Reserved:3 | ATIME:2:0 */ +#define TSL2591_SRESET (BIT(7)) +#define TSL2591_AGAIN_MASK (BIT(5) | BIT(4)) +#define TSL2591_ATIME_MASK (BIT(2) | BIT(1) | BIT(0)) + +/* Status: (0x13): Reserved:7:6 | NPINTR:5 | AINT:4 | Reserved:3:1 | AVALID:0 */ +#define TSL2591_AVALID_MASK (BIT(0)) + +/* Register Addresses */ +#define TSL2591_REG_ENABLE 0x00 +#define TSL2591_REG_CONFIG 0x01 +#define TSL2591_REG_AILTL 0x04 +#define TSL2591_REG_AILTH 0x05 +#define TSL2591_REG_AIHTL 0x06 +#define TSL2591_REG_AIHTH 0x07 +#define TSL2591_REG_NPAILTL 0x08 +#define TSL2591_REG_NPAILTH 0x09 +#define TSL2591_REG_NPAIHTL 0x0A +#define TSL2591_REG_NPAIHTH 0x0B +#define TSL2591_REG_PERSIST 0x0C +#define TSL2591_REG_PID 0x11 +#define TSL2591_REG_ID 0x12 +#define TSL2591_REG_STATUS 0x13 +#define TSL2591_REG_C0DATAL 0x14 +#define TSL2591_REG_C0DATAH 0x15 +#define TSL2591_REG_C1DATAL 0x16 +#define TSL2591_REG_C1DATAH 0x17 + +/* Integration Time Modes */ +#define TSL2591_INTEGRATION_100MS 0x00 +#define TSL2591_INTEGRATION_200MS 0x01 +#define TSL2591_INTEGRATION_300MS 0x02 +#define TSL2591_INTEGRATION_400MS 0x03 +#define TSL2591_INTEGRATION_500MS 0x04 +#define TSL2591_INTEGRATION_600MS 0x05 + +/* Gain Modes */ +#define TSL2591_GAIN_MODE_LOW 0x00 +#define TSL2591_GAIN_MODE_MED 0x10 +#define TSL2591_GAIN_MODE_HIGH 0x20 +#define TSL2591_GAIN_MODE_MAX 0x30 + +/* Gain Scales (Typical Values) + * See datasheet, used only for lux calculation. + */ +#define TSL2591_GAIN_SCALE_LOW 1U +#define TSL2591_GAIN_SCALE_MED 25U +#define TSL2591_GAIN_SCALE_HIGH 400U +#define TSL2591_GAIN_SCALE_MAX 9200U + +/* Persistence Filters */ +#define TSL2591_PERSIST_EVERY 0x00 +#define TSL2591_PERSIST_1 0x01 +#define TSL2591_PERSIST_2 0x02 +#define TSL2591_PERSIST_3 0x03 +#define TSL2591_PERSIST_5 0x04 +#define TSL2591_PERSIST_10 0x05 +#define TSL2591_PERSIST_15 0x06 +#define TSL2591_PERSIST_20 0x07 +#define TSL2591_PERSIST_25 0x08 +#define TSL2591_PERSIST_30 0x09 +#define TSL2591_PERSIST_35 0x0A +#define TSL2591_PERSIST_40 0x0B +#define TSL2591_PERSIST_45 0x0C +#define TSL2591_PERSIST_50 0x0D +#define TSL2591_PERSIST_55 0x0E +#define TSL2591_PERSIST_60 0x0F + +/* Device factor coefficient for lux calculations */ +#define TSL2591_LUX_DF 408 + +/* Max integration time (in ms) for single step */ +#define TSL2591_MAX_TIME_STEP 105 + +/* Max ADC Counts */ +#define TSL2591_MAX_ADC 65535 +#define TSL2591_MAX_ADC_100 36863 + +struct tsl2591_config { + const struct i2c_dt_spec i2c; +#ifdef CONFIG_TSL2591_TRIGGER + const struct gpio_dt_spec int_gpio; +#endif +}; + +struct tsl2591_data { + uint16_t vis_count; + uint16_t ir_count; + uint16_t again; + uint16_t atime; + bool powered_on; + +#ifdef CONFIG_TSL2591_TRIGGER + const struct device *dev; + struct gpio_callback gpio_cb; + sensor_trigger_handler_t th_handler; + const struct sensor_trigger *th_trigger; + +#if defined(CONFIG_TSL2591_TRIGGER_OWN_THREAD) + K_KERNEL_STACK_MEMBER(thread_stack, CONFIG_TSL2591_THREAD_STACK_SIZE); + struct k_sem trig_sem; + struct k_thread thread; +#elif defined(CONFIG_TSL2591_TRIGGER_GLOBAL_THREAD) + struct k_work work; +#endif + +#endif +}; + +int tsl2591_reg_update(const struct device *dev, uint8_t reg, uint8_t mask, uint8_t val); + +#ifdef CONFIG_TSL2591_TRIGGER +int tsl2591_trigger_set(const struct device *dev, const struct sensor_trigger *trig, + sensor_trigger_handler_t handler); +int tsl2591_initialize_int(const struct device *dev); +#endif + +#endif /* ZEPHYR_DRIVERS_SENSOR_TSL2591_TSL2591_H_ */ diff --git a/drivers/sensor/tsl2591/tsl2591_trigger.c b/drivers/sensor/tsl2591/tsl2591_trigger.c new file mode 100644 index 00000000000..ea0e34d3f81 --- /dev/null +++ b/drivers/sensor/tsl2591/tsl2591_trigger.c @@ -0,0 +1,156 @@ +/* + * Copyright (c) 2023 Kurtis Dinelle + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "tsl2591.h" + +LOG_MODULE_DECLARE(TSL2591, CONFIG_SENSOR_LOG_LEVEL); + +static inline void tsl2591_setup_int(const struct device *dev, bool enable) +{ + const struct tsl2591_config *config = dev->config; + gpio_flags_t flags = enable ? GPIO_INT_EDGE_TO_ACTIVE : GPIO_INT_DISABLE; + + gpio_pin_interrupt_configure_dt(&config->int_gpio, flags); +} + +static void tsl2591_gpio_callback(const struct device *dev, struct gpio_callback *cb, uint32_t pins) +{ + ARG_UNUSED(dev); + ARG_UNUSED(pins); + + struct tsl2591_data *data = CONTAINER_OF(cb, struct tsl2591_data, gpio_cb); + + tsl2591_setup_int(data->dev, false); + +#if defined(CONFIG_TSL2591_TRIGGER_OWN_THREAD) + k_sem_give(&data->trig_sem); +#elif defined(CONFIG_TSL2591_TRIGGER_GLOBAL_THREAD) + k_work_submit(&data->work); +#endif +} + +static void tsl2591_handle_int(const struct device *dev) +{ + struct tsl2591_data *data = dev->data; + const struct tsl2591_config *config = dev->config; + uint8_t clear_cmd; + int ret; + + /* Interrupt must be cleared manually */ + clear_cmd = TSL2591_CLEAR_INT_CMD; + ret = i2c_write_dt(&config->i2c, &clear_cmd, 1U); + if (ret < 0) { + LOG_ERR("Failed to clear interrupt"); + return; + } + + if (data->th_handler != NULL) { + data->th_handler(dev, data->th_trigger); + } + + tsl2591_setup_int(dev, true); +} + +#ifdef CONFIG_TSL2591_TRIGGER_OWN_THREAD +static void tsl2591_thread(void *p1, void *p2, void *p3) +{ + ARG_UNUSED(p2); + ARG_UNUSED(p3); + + struct tsl2591_data *data = p1; + + while (1) { + k_sem_take(&data->trig_sem, K_FOREVER); + tsl2591_handle_int(data->dev); + } +} +#endif + +#ifdef CONFIG_TSL2591_TRIGGER_GLOBAL_THREAD +static void tsl2591_work_handler(struct k_work *work) +{ + struct tsl2591_data *data = CONTAINER_OF(work, struct tsl2591_data, work); + + tsl2591_handle_int(data->dev); +} +#endif + +int tsl2591_trigger_set(const struct device *dev, const struct sensor_trigger *trig, + sensor_trigger_handler_t handler) +{ + struct tsl2591_data *data = dev->data; + const struct tsl2591_config *config = dev->config; + int ret; + + if (!config->int_gpio.port) { + return -ENOTSUP; + } + + if (trig->chan != SENSOR_CHAN_LIGHT) { + LOG_ERR("Unsupported sensor trigger channel"); + return -ENOTSUP; + } + + if (trig->type != SENSOR_TRIG_THRESHOLD) { + LOG_ERR("Unsupported sensor trigger type"); + return -ENOTSUP; + } + + data->th_handler = handler; + data->th_trigger = trig; + tsl2591_setup_int(dev, true); + + ret = tsl2591_reg_update(dev, TSL2591_REG_ENABLE, TSL2591_AIEN_MASK, TSL2591_AIEN_ON); + if (ret < 0) { + LOG_ERR("Failed to enable interrupt on sensor"); + } + + return ret; +} + +int tsl2591_initialize_int(const struct device *dev) +{ + struct tsl2591_data *data = dev->data; + const struct tsl2591_config *config = dev->config; + int ret; + + if (!gpio_is_ready_dt(&config->int_gpio)) { + LOG_ERR("%s: gpio controller %s not ready", dev->name, config->int_gpio.port->name); + return -ENODEV; + } + + ret = gpio_pin_configure_dt(&config->int_gpio, GPIO_INPUT | config->int_gpio.dt_flags); + if (ret < 0) { + LOG_ERR("Failed to configure gpio pin for input"); + return ret; + } + + gpio_init_callback(&data->gpio_cb, tsl2591_gpio_callback, BIT(config->int_gpio.pin)); + + ret = gpio_add_callback(config->int_gpio.port, &data->gpio_cb); + if (ret < 0) { + LOG_DBG("Failed to set gpio callback"); + return ret; + } + + data->dev = dev; + +#if defined(CONFIG_TSL2591_TRIGGER_OWN_THREAD) + ret = k_sem_init(&data->trig_sem, 0, K_SEM_MAX_LIMIT); + if (ret < 0) { + LOG_ERR("Failed to initialize trigger semaphore"); + return ret; + } + + k_thread_create(&data->thread, data->thread_stack, CONFIG_TSL2591_THREAD_STACK_SIZE, + tsl2591_thread, data, NULL, NULL, + K_PRIO_COOP(CONFIG_TSL2591_THREAD_PRIORITY), 0, K_NO_WAIT); +#elif defined(CONFIG_TSL2591_TRIGGER_GLOBAL_THREAD) + data->work.handler = tsl2591_work_handler; +#endif + + return 0; +} diff --git a/dts/bindings/sensor/ams,tsl2591.yaml b/dts/bindings/sensor/ams,tsl2591.yaml new file mode 100644 index 00000000000..6d4f71d881e --- /dev/null +++ b/dts/bindings/sensor/ams,tsl2591.yaml @@ -0,0 +1,17 @@ +# Copyright (c) 2023 Kurtis Dinelle +# SPDX-License-Identifier: Apache-2.0 + +description: | + OSRAM ams TSL2591 ambient light sensor. + +compatible: "ams,tsl2591" + +include: [sensor-device.yaml, i2c-device.yaml] + +properties: + int-gpios: + type: phandle-array + description: | + The interrupt pin of the TSL2591 is open-drain, active low. + If connected directly, the MCU pin should be configured + as pull-up, active low. diff --git a/include/zephyr/drivers/sensor/tsl2591.h b/include/zephyr/drivers/sensor/tsl2591.h new file mode 100644 index 00000000000..b8685ff464d --- /dev/null +++ b/include/zephyr/drivers/sensor/tsl2591.h @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2023 Kurtis Dinelle + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file + * @brief Extended public API for AMS's TSL2591 ambient light sensor + * + * This exposes attributes for the TSL2591 which can be used for + * setting the on-chip gain, integration time, and persist filter parameters. + */ + +#ifndef ZEPHYR_INCLUDE_DRIVERS_SENSOR_TSL2591_H_ +#define ZEPHYR_INCLUDE_DRIVERS_SENSOR_TSL2591_H_ + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +enum sensor_attribute_tsl2591 { + /* Sensor ADC Gain Mode + * Rather than set this value directly, can only be set to operate in one of four modes: + * + * TSL2591_SENSOR_GAIN_LOW + * TSL2591_SENSOR_GAIN_MED + * TSL2591_SENSOR_GAIN_HIGH + * TSL2591_SENSOR_GAIN_MAX + * + * See datasheet for actual typical gain scales these modes correspond to. + */ + SENSOR_ATTR_GAIN_MODE = SENSOR_ATTR_PRIV_START + 1, + + /* Sensor ADC Integration Time (in ms) + * Can only be set to one of six values: + * + * 100, 200, 300, 400, 500, or 600 + */ + SENSOR_ATTR_INTEGRATION_TIME, + + /* Sensor ALS Interrupt Persist Filter + * Represents the number of consecutive sensor readings outside of a set threshold + * before triggering an interrupt. Can only be set to one of sixteen values: + * + * 0, 1, 2, 3, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, or 60 + * + * Setting this to 0 causes an interrupt to generate every ALS cycle, + * regardless of threshold. + * Setting this to 1 is equivalent to the no-persist interrupt mode. + */ + SENSOR_ATTR_INT_PERSIST +}; + +enum sensor_gain_tsl2591 { + TSL2591_SENSOR_GAIN_LOW, + TSL2591_SENSOR_GAIN_MED, + TSL2591_SENSOR_GAIN_HIGH, + TSL2591_SENSOR_GAIN_MAX +}; + +#ifdef __cplusplus +} +#endif + +#endif /* ZEPHYR_INCLUDE_DRIVERS_SENSOR_TSL2591_H_ */ diff --git a/tests/drivers/build_all/sensor/i2c.dtsi b/tests/drivers/build_all/sensor/i2c.dtsi index 88826c9d0a1..a949cc1b289 100644 --- a/tests/drivers/build_all/sensor/i2c.dtsi +++ b/tests/drivers/build_all/sensor/i2c.dtsi @@ -932,3 +932,9 @@ test_i2c_ens160: ens160@83 { reg = <0x83>; int-gpios = <&test_gpio 0 0>; }; + +test_i2c_tsl2591: tsl2591@84 { + compatible = "ams,tsl2591"; + reg = <0x84>; + int-gpios = <&test_gpio 0 0>; +}; diff --git a/tests/drivers/build_all/sensor/sensors_trigger_global.conf b/tests/drivers/build_all/sensor/sensors_trigger_global.conf index b888b0541fc..75d24e13857 100644 --- a/tests/drivers/build_all/sensor/sensors_trigger_global.conf +++ b/tests/drivers/build_all/sensor/sensors_trigger_global.conf @@ -57,6 +57,7 @@ CONFIG_TMAG5170_TRIGGER_GLOBAL_THREAD=y CONFIG_TMD2620_TRIGGER_GLOBAL_THREAD=y CONFIG_TMP007_TRIGGER_GLOBAL_THREAD=y CONFIG_TSL2540_TRIGGER_GLOBAL_THREAD=y +CONFIG_TSL2591_TRIGGER_GLOBAL_THREAD=y CONFIG_VCNL4040_TRIGGER_GLOBAL_THREAD=y CONFIG_WSEN_HIDS_TRIGGER_GLOBAL_THREAD=y CONFIG_WSEN_TIDS_TRIGGER_GLOBAL_THREAD=y diff --git a/tests/drivers/build_all/sensor/sensors_trigger_none.conf b/tests/drivers/build_all/sensor/sensors_trigger_none.conf index a1e0692a8a1..665975a0d62 100644 --- a/tests/drivers/build_all/sensor/sensors_trigger_none.conf +++ b/tests/drivers/build_all/sensor/sensors_trigger_none.conf @@ -57,6 +57,7 @@ CONFIG_TMAG5170_TRIGGER_NONE=y CONFIG_TMD2620_TRIGGER_NONE=y CONFIG_TMP007_TRIGGER_NONE=y CONFIG_TSL2540_TRIGGER_NONE=y +CONFIG_TSL2591_TRIGGER_NONE=y CONFIG_VCNL4040_TRIGGER_NONE=y CONFIG_WSEN_HIDS_TRIGGER_NONE=y CONFIG_WSEN_TIDS_TRIGGER_NONE=y diff --git a/tests/drivers/build_all/sensor/sensors_trigger_own.conf b/tests/drivers/build_all/sensor/sensors_trigger_own.conf index 9d965e00cbc..71aed7dfaab 100644 --- a/tests/drivers/build_all/sensor/sensors_trigger_own.conf +++ b/tests/drivers/build_all/sensor/sensors_trigger_own.conf @@ -55,6 +55,7 @@ CONFIG_TCN75A_TRIGGER_OWN_THREAD=y CONFIG_TMAG5170_TRIGGER_OWN_THREAD=y CONFIG_TMP007_TRIGGER_OWN_THREAD=y CONFIG_TSL2540_TRIGGER_OWN_THREAD=y +CONFIG_TSL2591_TRIGGER_OWN_THREAD=y CONFIG_VCNL4040_TRIGGER_OWN_THREAD=y CONFIG_WSEN_HIDS_TRIGGER_OWN_THREAD=y CONFIG_WSEN_TIDS_TRIGGER_OWN_THREAD=y