diff --git a/drivers/sensor/CMakeLists.txt b/drivers/sensor/CMakeLists.txt index f5808b7cd5d..53f12d5eeb6 100644 --- a/drivers/sensor/CMakeLists.txt +++ b/drivers/sensor/CMakeLists.txt @@ -35,6 +35,7 @@ add_subdirectory_ifdef(CONFIG_AMD_SB_TSI amd_sb_tsi) add_subdirectory_ifdef(CONFIG_AMG88XX amg88xx) add_subdirectory_ifdef(CONFIG_APDS9253 apds9253) add_subdirectory_ifdef(CONFIG_APDS9960 apds9960) +add_subdirectory_ifdef(CONFIG_APDS9306 apds9306) add_subdirectory_ifdef(CONFIG_CURRENT_AMP current_amp) add_subdirectory_ifdef(CONFIG_ENS160 ens160) add_subdirectory_ifdef(CONFIG_EXPLORIR_M explorir_m) diff --git a/drivers/sensor/Kconfig b/drivers/sensor/Kconfig index 0fc8252408d..329a6a85869 100644 --- a/drivers/sensor/Kconfig +++ b/drivers/sensor/Kconfig @@ -119,6 +119,7 @@ source "drivers/sensor/amd_sb_tsi/Kconfig" source "drivers/sensor/amg88xx/Kconfig" source "drivers/sensor/apds9253/Kconfig" source "drivers/sensor/apds9960/Kconfig" +source "drivers/sensor/apds9306/Kconfig" source "drivers/sensor/current_amp/Kconfig" source "drivers/sensor/ens160/Kconfig" source "drivers/sensor/explorir_m/Kconfig" diff --git a/drivers/sensor/apds9306/CMakeLists.txt b/drivers/sensor/apds9306/CMakeLists.txt new file mode 100644 index 00000000000..111573c4bfc --- /dev/null +++ b/drivers/sensor/apds9306/CMakeLists.txt @@ -0,0 +1,6 @@ +# SPDX-License-Identifier: Apache-2.0 +# Copyright (c) 2024 Daniel Kampert +# Author: Daniel Kampert + +zephyr_library() +zephyr_library_sources(apds9306.c) diff --git a/drivers/sensor/apds9306/Kconfig b/drivers/sensor/apds9306/Kconfig new file mode 100644 index 00000000000..0f4a0b0377f --- /dev/null +++ b/drivers/sensor/apds9306/Kconfig @@ -0,0 +1,11 @@ +# SPDX-License-Identifier: Apache-2.0 +# Copyright (c) 2024 Daniel Kampert +# Author: Daniel Kampert + +config APDS9306 + bool "APDS9306 Sensor" + default y + depends on DT_HAS_AVAGO_APDS9306_ENABLED + select I2C + help + Enable the driver for the APDS9306 digital light sensor. diff --git a/drivers/sensor/apds9306/apds9306.c b/drivers/sensor/apds9306/apds9306.c new file mode 100644 index 00000000000..2c3ccc25d73 --- /dev/null +++ b/drivers/sensor/apds9306/apds9306.c @@ -0,0 +1,371 @@ +/* Copyright (c) 2024 Daniel Kampert + * Author: Daniel Kampert + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#define APDS9306_REGISTER_MAIN_CTRL 0x00 +#define APDS9306_REGISTER_ALS_MEAS_RATE 0x04 +#define APDS9306_REGISTER_ALS_GAIN 0x05 +#define APDS9306_REGISTER_PART_ID 0x06 +#define APDS9306_REGISTER_MAIN_STATUS 0x07 +#define APDS9306_REGISTER_CLEAR_DATA_0 0x0A +#define APDS9306_REGISTER_CLEAR_DATA_1 0x0B +#define APDS9306_REGISTER_CLEAR_DATA_2 0x0C +#define APDS9306_REGISTER_ALS_DATA_0 0x0D +#define APDS9306_REGISTER_ALS_DATA_1 0x0E +#define APDS9306_REGISTER_ALS_DATA_2 0x0F +#define APDS9306_REGISTER_INT_CFG 0x19 +#define APDS9306_REGISTER_INT_PERSISTENCE 0x1A +#define APDS9306_REGISTER_ALS_THRES_UP_0 0x21 +#define APDS9306_REGISTER_ALS_THRES_UP_1 0x22 +#define APDS9306_REGISTER_ALS_THRES_UP_2 0x23 +#define APDS9306_REGISTER_ALS_THRES_LOW_0 0x24 +#define APDS9306_REGISTER_ALS_THRES_LOW_1 0x25 +#define APDS9306_REGISTER_ALS_THRES_LOW_2 0x26 +#define APDS9306_REGISTER_ALS_THRES_VAR 0x27 + +#define ADPS9306_BIT_ALS_EN BIT(0x01) +#define ADPS9306_BIT_ALS_DATA_STATUS BIT(0x03) +#define APDS9306_BIT_SW_RESET BIT(0x04) +#define ADPS9306_BIT_ALS_INTERRUPT_STATUS BIT(0x03) +#define APDS9306_BIT_POWER_ON_STATUS BIT(0x05) + +#define APDS_9306_065_CHIP_ID 0xB3 +#define APDS_9306_CHIP_ID 0xB1 + +#define DT_DRV_COMPAT avago_apds9306 + +LOG_MODULE_REGISTER(avago_apds9306, CONFIG_SENSOR_LOG_LEVEL); + +struct apds9306_data { + uint32_t light; +}; + +struct apds9306_config { + struct i2c_dt_spec i2c; + uint8_t resolution; + uint16_t frequency; + uint8_t gain; +}; + +struct apds9306_worker_item_t { + struct k_work_delayable dwork; + const struct device *dev; +} apds9306_worker_item; + +static uint32_t apds9306_get_time_for_resolution(uint8_t value) +{ + switch (value) { + case 0: + return 400; + case 1: + return 200; + case 2: + return 100; + case 3: + return 50; + case 4: + return 25; + case 5: + return 4; + default: + return 100; + } +} + +static int apds9306_enable(const struct device *dev) +{ + const struct apds9306_config *config = dev->config; + + return i2c_reg_update_byte_dt(&config->i2c, APDS9306_REGISTER_MAIN_CTRL, + ADPS9306_BIT_ALS_EN, ADPS9306_BIT_ALS_EN); +} + +static int apds9306_standby(const struct device *dev) +{ + const struct apds9306_config *config = dev->config; + + return i2c_reg_update_byte_dt(&config->i2c, APDS9306_REGISTER_MAIN_CTRL, + ADPS9306_BIT_ALS_EN, 0x00); +} + +static void apds9306_worker(struct k_work *p_work) +{ + uint8_t buffer[3]; + uint8_t reg; + struct k_work_delayable *dwork = k_work_delayable_from_work(p_work); + struct apds9306_worker_item_t *item = + CONTAINER_OF(dwork, struct apds9306_worker_item_t, dwork); + struct apds9306_data *data = item->dev->data; + const struct apds9306_config *config = item->dev->config; + + if (i2c_reg_read_byte_dt(&config->i2c, APDS9306_REGISTER_MAIN_STATUS, &buffer[0])) { + LOG_ERR("Failed to read ALS status!"); + return; + } + + if (!(buffer[0] & ADPS9306_BIT_ALS_DATA_STATUS)) { + LOG_DBG("No data ready!"); + return; + } + + if (apds9306_standby(item->dev) != 0) { + LOG_ERR("Can not disable ALS!"); + return; + } + + reg = APDS9306_REGISTER_ALS_DATA_0; + if (i2c_write_read_dt(&config->i2c, ®, sizeof(reg), &buffer, sizeof(buffer)) < 0) { + return; + } + + data->light = sys_get_le24(buffer); + + LOG_DBG("Last measurement: %u", data->light); +} + +static int apds9306_attr_set(const struct device *dev, enum sensor_channel channel, + enum sensor_attribute attribute, const struct sensor_value *value) +{ + uint8_t reg; + uint8_t mask; + uint8_t temp; + const struct apds9306_config *config = dev->config; + + if (channel != SENSOR_CHAN_LIGHT) { + return -ENOTSUP; + } + + if (attribute == SENSOR_ATTR_SAMPLING_FREQUENCY) { + reg = APDS9306_REGISTER_ALS_MEAS_RATE; + mask = GENMASK(2, 0); + temp = FIELD_PREP(0x07, value->val1); + } else if (attribute == SENSOR_ATTR_GAIN) { + reg = APDS9306_REGISTER_ALS_GAIN; + mask = GENMASK(2, 0); + temp = FIELD_PREP(0x07, value->val1); + } else if (attribute == SENSOR_ATTR_RESOLUTION) { + reg = APDS9306_REGISTER_ALS_MEAS_RATE; + mask = GENMASK(7, 4); + temp = FIELD_PREP(0x07, value->val1) << 0x04; + } else { + return -ENOTSUP; + } + + if (i2c_reg_update_byte_dt(&config->i2c, reg, mask, temp)) { + LOG_ERR("Failed to set sensor attribute!"); + return -EFAULT; + } + + return 0; +} + +static int apds9306_attr_get(const struct device *dev, enum sensor_channel channel, + enum sensor_attribute attribute, struct sensor_value *value) +{ + uint8_t mask; + uint8_t temp; + uint8_t reg; + const struct apds9306_config *config = dev->config; + + if (channel != SENSOR_CHAN_LIGHT) { + return -ENOTSUP; + } + + if (attribute == SENSOR_ATTR_SAMPLING_FREQUENCY) { + reg = APDS9306_REGISTER_ALS_MEAS_RATE; + mask = 0x00; + } else if (attribute == SENSOR_ATTR_GAIN) { + reg = APDS9306_REGISTER_ALS_GAIN; + mask = 0x00; + } else if (attribute == SENSOR_ATTR_RESOLUTION) { + reg = APDS9306_REGISTER_ALS_MEAS_RATE; + mask = 0x04; + } else { + return -ENOTSUP; + } + + if (i2c_reg_read_byte_dt(&config->i2c, reg, &temp)) { + LOG_ERR("Failed to read sensor attribute!"); + return -EFAULT; + } + + value->val1 = (temp >> mask) & 0x07; + value->val2 = 0; + + return 0; +} + +static int apds9306_sample_fetch(const struct device *dev, enum sensor_channel channel) +{ + uint8_t buffer; + uint8_t resolution; + uint16_t delay; + const struct apds9306_config *config = dev->config; + + if ((channel != SENSOR_CHAN_ALL) && (channel != SENSOR_CHAN_LIGHT)) { + return -ENOTSUP; + } + + LOG_DBG("Start a new measurement..."); + if (apds9306_enable(dev) != 0) { + LOG_ERR("Can not enable ALS!"); + return -EFAULT; + } + + /* Get the measurement resolution. */ + if (i2c_reg_read_byte_dt(&config->i2c, APDS9306_REGISTER_ALS_MEAS_RATE, &buffer)) { + LOG_ERR("Failed reading resolution"); + return -EFAULT; + } + + /* Convert the resolution into a delay time and wait for the result. */ + resolution = (buffer >> 4) & 0x07; + delay = apds9306_get_time_for_resolution(resolution); + LOG_DBG("Measurement resolution: %u", resolution); + LOG_DBG("Wait for %u ms", delay); + + /* We add a bit more delay to cover the startup time etc. */ + if (!k_work_delayable_is_pending(&apds9306_worker_item.dwork)) { + LOG_DBG("Schedule new work"); + + apds9306_worker_item.dev = dev; + k_work_init_delayable(&apds9306_worker_item.dwork, apds9306_worker); + k_work_schedule(&apds9306_worker_item.dwork, K_MSEC(delay + 100)); + } else { + LOG_DBG("Work pending. Wait for completion."); + } + + return 0; +} + +static int apds9306_channel_get(const struct device *dev, enum sensor_channel channel, + struct sensor_value *value) +{ + struct apds9306_data *data = dev->data; + + if (channel != SENSOR_CHAN_LIGHT) { + return -ENOTSUP; + } + + value->val1 = data->light; + value->val2 = 0; + + return 0; +} + +static int apds9306_sensor_setup(const struct device *dev) +{ + uint32_t now; + uint8_t temp; + const struct apds9306_config *config = dev->config; + + /* Wait for the device to become ready after a possible power cycle. */ + now = k_uptime_get_32(); + do { + i2c_reg_read_byte_dt(&config->i2c, APDS9306_REGISTER_MAIN_STATUS, &temp); + + /* We wait 100 ms maximum for the device to become ready. */ + if ((k_uptime_get_32() - now) > 100) { + LOG_ERR("Sensor timeout!"); + return -EFAULT; + } + + k_msleep(10); + } while (temp & APDS9306_BIT_POWER_ON_STATUS); + + if (i2c_reg_read_byte_dt(&config->i2c, APDS9306_REGISTER_PART_ID, &temp)) { + LOG_ERR("Failed reading chip id!"); + return -EFAULT; + } + + if ((temp != APDS_9306_CHIP_ID) && (temp != APDS_9306_065_CHIP_ID)) { + LOG_ERR("Invalid chip id! Found 0x%X!", temp); + return -EFAULT; + } + + if (temp == APDS_9306_CHIP_ID) { + LOG_DBG("APDS-9306 found!"); + } else if (temp == APDS_9306_065_CHIP_ID) { + LOG_DBG("APDS-9306-065 found!"); + } + + /* Reset the sensor. */ + if (i2c_reg_write_byte_dt(&config->i2c, APDS9306_REGISTER_MAIN_CTRL, + APDS9306_BIT_SW_RESET)) { + LOG_ERR("Can not reset the sensor!"); + return -EFAULT; + } + k_msleep(10); + + /* Perform a dummy read to avoid bus errors after the reset. See */ + /* https://lore.kernel.org/lkml/ab1d9746-4d23-efcc-0ee1-d2b8c634becd@tweaklogic.com/ */ + i2c_reg_read_byte_dt(&config->i2c, APDS9306_REGISTER_PART_ID, &temp); + + return 0; +} + +static int apds9306_init(const struct device *dev) +{ + uint8_t value; + const struct apds9306_config *config = dev->config; + + LOG_DBG("Start to initialize APDS9306..."); + + if (!i2c_is_ready_dt(&config->i2c)) { + LOG_ERR("Bus device is not ready!"); + return -EINVAL; + } + + if (apds9306_sensor_setup(dev)) { + LOG_ERR("Failed to setup device!"); + return -EFAULT; + } + + value = ((config->resolution & 0x07) << 4) | (config->frequency & 0x0F); + LOG_DBG("Write configuration 0x%x to register 0x%x", value, + APDS9306_REGISTER_ALS_MEAS_RATE); + if (i2c_reg_write_byte_dt(&config->i2c, APDS9306_REGISTER_ALS_MEAS_RATE, value)) { + return -EFAULT; + } + + value = config->gain; + LOG_DBG("Write configuration 0x%x to register 0x%x", value, APDS9306_REGISTER_ALS_GAIN); + if (i2c_reg_write_byte_dt(&config->i2c, APDS9306_REGISTER_ALS_GAIN, value)) { + return -EFAULT; + } + + LOG_DBG("APDS9306 initialization successful!"); + + return 0; +} + +static const struct sensor_driver_api apds9306_driver_api = { + .attr_set = apds9306_attr_set, + .attr_get = apds9306_attr_get, + .sample_fetch = apds9306_sample_fetch, + .channel_get = apds9306_channel_get, +}; + +#define APDS9306(inst) \ + static struct apds9306_data apds9306_data_##inst; \ + static const struct apds9306_config apds9306_config_##inst = { \ + .i2c = I2C_DT_SPEC_INST_GET(inst), \ + .resolution = DT_INST_PROP(inst, resolution), \ + .gain = DT_INST_PROP(inst, gain), \ + .frequency = DT_INST_PROP(inst, frequency), \ + }; \ + \ + SENSOR_DEVICE_DT_INST_DEFINE(inst, apds9306_init, NULL, &apds9306_data_##inst, \ + &apds9306_config_##inst, POST_KERNEL, \ + CONFIG_SENSOR_INIT_PRIORITY, &apds9306_driver_api); + +DT_INST_FOREACH_STATUS_OKAY(APDS9306) diff --git a/dts/bindings/sensor/avago,apds9306.yaml b/dts/bindings/sensor/avago,apds9306.yaml new file mode 100644 index 00000000000..1458611dbf0 --- /dev/null +++ b/dts/bindings/sensor/avago,apds9306.yaml @@ -0,0 +1,54 @@ +# SPDX-License-Identifier: Apache-2.0 +# Copyright (c) 2024 Daniel Kampert +# Author: Daniel Kampert + +description: APDS9306 miniature Surface-Mount Digital Ambient Light Sensor. + +compatible: "avago,apds9306" + +include: + - sensor-device.yaml + - i2c-device.yaml + +properties: + gain: + type: int + default: 1 + enum: + - 18 + - 9 + - 6 + - 3 + - 1 + description: + ALS Gain range. + The default corresponds to the reset value of the register field. + + resolution: + type: int + default: 18 + enum: + - 13 + - 16 + - 17 + - 18 + - 19 + - 20 + description: + ALS Resolution / Bit width. + The default corresponds to the reset value of the register field. + + frequency: + type: int + default: 100 + enum: + - 2000 + - 1000 + - 500 + - 200 + - 100 + - 50 + - 25 + description: + ALS Measurement Rate in milliseconds. + The default corresponds to the reset value of the register field. diff --git a/tests/drivers/build_all/sensor/i2c.dtsi b/tests/drivers/build_all/sensor/i2c.dtsi index 4d222494da2..dde57ebfa21 100644 --- a/tests/drivers/build_all/sensor/i2c.dtsi +++ b/tests/drivers/build_all/sensor/i2c.dtsi @@ -1087,3 +1087,12 @@ test_i2c_tmp1075: tmp1075@98 { consecutive-fault-measurements = <4>; interrupt-mode; }; + +apds_9306: apds9306@92 { + compatible = "avago,apds9306"; + reg = <0x92>; + status = "okay"; + gain = <1>; + resolution = <13>; + frequency = <2000>; +};