drivers: sensors: add a sensor driver for TCS3400
Add a sensor driver for the TCS3400 color light-to-digital converter.
Signed-off-by: Fabio Baltieri <fabiobaltieri@google.com>
diff --git a/drivers/sensor/CMakeLists.txt b/drivers/sensor/CMakeLists.txt
index a9294cc..bfb08e4 100644
--- a/drivers/sensor/CMakeLists.txt
+++ b/drivers/sensor/CMakeLists.txt
@@ -140,6 +140,7 @@
add_subdirectory_ifdef(CONFIG_TMD2620 tmd2620)
add_subdirectory_ifdef(CONFIG_NTC_THERMISTOR ntc_thermistor)
add_subdirectory_ifdef(CONFIG_S11059 s11059)
+add_subdirectory_ifdef(CONFIG_TCS3400 tcs3400)
if(CONFIG_USERSPACE OR CONFIG_SENSOR_SHELL OR CONFIG_SENSOR_SHELL_BATTERY)
# The above if() is needed or else CMake would complain about
diff --git a/drivers/sensor/Kconfig b/drivers/sensor/Kconfig
index 37571d2..8ae89ca 100644
--- a/drivers/sensor/Kconfig
+++ b/drivers/sensor/Kconfig
@@ -319,4 +319,6 @@
source "drivers/sensor/s11059/Kconfig"
+source "drivers/sensor/tcs3400/Kconfig"
+
endif # SENSOR
diff --git a/drivers/sensor/tcs3400/CMakeLists.txt b/drivers/sensor/tcs3400/CMakeLists.txt
new file mode 100644
index 0000000..f34ab54
--- /dev/null
+++ b/drivers/sensor/tcs3400/CMakeLists.txt
@@ -0,0 +1,5 @@
+# SPDX-License-Identifier: Apache-2.0
+
+zephyr_library()
+
+zephyr_library_sources(tcs3400.c)
diff --git a/drivers/sensor/tcs3400/Kconfig b/drivers/sensor/tcs3400/Kconfig
new file mode 100644
index 0000000..25f421d
--- /dev/null
+++ b/drivers/sensor/tcs3400/Kconfig
@@ -0,0 +1,10 @@
+# Copyright 2023 Google LLC
+# SPDX-License-Identifier: Apache-2.0
+
+config TCS3400
+ bool "TCS3400 Sensor"
+ default y
+ depends on DT_HAS_AMS_TCS3400_ENABLED
+ select I2C
+ help
+ Enable driver for TCS3400 sensors.
diff --git a/drivers/sensor/tcs3400/tcs3400.c b/drivers/sensor/tcs3400/tcs3400.c
new file mode 100644
index 0000000..3673590
--- /dev/null
+++ b/drivers/sensor/tcs3400/tcs3400.c
@@ -0,0 +1,304 @@
+/*
+ * Copyright 2023 Google LLC
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+#define DT_DRV_COMPAT ams_tcs3400
+
+#include <zephyr/drivers/gpio.h>
+#include <zephyr/drivers/i2c.h>
+#include <zephyr/drivers/sensor.h>
+#include <zephyr/drivers/sensor/tcs3400.h>
+#include <zephyr/sys/byteorder.h>
+#include <zephyr/sys/util.h>
+
+#include <zephyr/logging/log.h>
+LOG_MODULE_REGISTER(tcs3400, CONFIG_SENSOR_LOG_LEVEL);
+
+#define TCS3400_ENABLE_REG 0x80
+#define TCS3400_ENABLE_AIEN BIT(4)
+#define TCS3400_ENABLE_AEN BIT(1)
+#define TCS3400_ENABLE_PON BIT(0)
+
+#define TCS3400_ATIME_REG 0x81
+
+#define TCS3400_PERS_REG 0x8c
+
+#define TCS3400_CONFIG_REG 0x8d
+
+#define TCS3400_CONTROL_REG 0x8f
+
+#define TCS3400_ID_REG 0x92
+#define TCS3400_ID_1 0x90
+#define TCS3400_ID_2 0x93
+
+#define TCS3400_STATUS_REG 0x93
+#define TCS3400_STATUS_AVALID BIT(0)
+
+#define TCS3400_CDATAL_REG 0x94
+#define TCS3400_CDATAH_REG 0x95
+#define TCS3400_RDATAL_REG 0x96
+#define TCS3400_RDATAH_REG 0x97
+#define TCS3400_GDATAL_REG 0x98
+#define TCS3400_GDATAH_REG 0x99
+#define TCS3400_BDATAL_REG 0x9A
+#define TCS3400_BDATAH_REG 0x9B
+
+#define TCS3400_AICLEAR_REG 0xe7
+
+/* Default values */
+#define TCS3400_DEFAULT_ENABLE 0x00
+#define TCS3400_DEFAULT_ATIME 0xff
+#define TCS3400_DEFAULT_PERS 0x00
+#define TCS3400_DEFAULT_CONFIG 0x00
+#define TCS3400_DEFAULT_CONTROL 0x00
+#define TCS3400_AICLEAR_RESET 0x00
+
+struct tcs3400_config {
+ struct i2c_dt_spec i2c;
+ struct gpio_dt_spec int_gpio;
+};
+
+struct tcs3400_data {
+ struct gpio_callback gpio_cb;
+ const struct device *dev;
+
+ uint16_t sample_crgb[4];
+
+ struct k_sem data_sem;
+};
+
+static void tcs3400_setup_int(const struct tcs3400_config *config, bool enable)
+{
+ unsigned int flags = enable ? GPIO_INT_EDGE_TO_ACTIVE : GPIO_INT_DISABLE;
+
+ gpio_pin_interrupt_configure_dt(&config->int_gpio, flags);
+}
+
+static void tcs3400_gpio_callback(const struct device *dev,
+ struct gpio_callback *cb, uint32_t pins)
+{
+ struct tcs3400_data *data = CONTAINER_OF(cb, struct tcs3400_data,
+ gpio_cb);
+
+ tcs3400_setup_int(data->dev->config, false);
+
+ k_sem_give(&data->data_sem);
+}
+
+static int tcs3400_sample_fetch(const struct device *dev,
+ enum sensor_channel chan)
+{
+ const struct tcs3400_config *cfg = dev->config;
+ struct tcs3400_data *data = dev->data;
+ int ret;
+ uint8_t status;
+
+ if (chan != SENSOR_CHAN_ALL) {
+ LOG_ERR("Unsupported sensor channel");
+ return -ENOTSUP;
+ }
+
+ tcs3400_setup_int(cfg, true);
+
+ ret = i2c_reg_write_byte_dt(&cfg->i2c, TCS3400_ENABLE_REG,
+ TCS3400_ENABLE_AIEN | TCS3400_ENABLE_AEN |
+ TCS3400_ENABLE_PON);
+ if (ret) {
+ return ret;
+ }
+
+ k_sem_take(&data->data_sem, K_FOREVER);
+
+ ret = i2c_reg_read_byte_dt(&cfg->i2c, TCS3400_STATUS_REG, &status);
+ if (ret) {
+ return ret;
+ }
+
+ if (status & TCS3400_STATUS_AVALID) {
+ ret = i2c_burst_read_dt(&cfg->i2c, TCS3400_CDATAL_REG,
+ (uint8_t *)&data->sample_crgb,
+ sizeof(data->sample_crgb));
+ if (ret) {
+ return ret;
+ }
+ } else {
+ LOG_ERR("Unexpected status: %02x", status);
+ }
+
+ ret = i2c_reg_write_byte_dt(&cfg->i2c, TCS3400_ENABLE_REG, 0);
+ if (ret) {
+ return ret;
+ }
+
+ ret = i2c_reg_write_byte_dt(&cfg->i2c, TCS3400_AICLEAR_REG, 0);
+ if (ret) {
+ return ret;
+ }
+
+ return 0;
+}
+
+static int tcs3400_channel_get(const struct device *dev,
+ enum sensor_channel chan,
+ struct sensor_value *val)
+{
+ struct tcs3400_data *data = dev->data;
+
+ switch (chan) {
+ case SENSOR_CHAN_LIGHT:
+ val->val1 = sys_le16_to_cpu(data->sample_crgb[0]);
+ val->val2 = 0;
+ break;
+ case SENSOR_CHAN_RED:
+ val->val1 = sys_le16_to_cpu(data->sample_crgb[1]);
+ val->val2 = 0;
+ break;
+ case SENSOR_CHAN_GREEN:
+ val->val1 = sys_le16_to_cpu(data->sample_crgb[2]);
+ val->val2 = 0;
+ break;
+ case SENSOR_CHAN_BLUE:
+ val->val1 = sys_le16_to_cpu(data->sample_crgb[3]);
+ val->val2 = 0;
+ break;
+ default:
+ return -ENOTSUP;
+ }
+
+ return 0;
+}
+
+static int tcs3400_attr_set(const struct device *dev,
+ enum sensor_channel chan,
+ enum sensor_attribute attr,
+ const struct sensor_value *val)
+{
+ const struct tcs3400_config *cfg = dev->config;
+ int ret;
+ uint8_t reg_val;
+
+ switch (attr) {
+ case SENSOR_ATTR_TCS3400_INTEGRATION_CYCLES:
+ if (!IN_RANGE(val->val1, 1, 256)) {
+ return -EINVAL;
+ }
+ reg_val = UINT8_MAX - val->val1 + 1;
+ ret = i2c_reg_write_byte_dt(&cfg->i2c,
+ TCS3400_ATIME_REG, reg_val);
+ if (ret) {
+ return ret;
+ }
+ break;
+ default:
+ return -ENOTSUP;
+ }
+
+ return 0;
+}
+
+static int tcs3400_sensor_setup(const struct device *dev)
+{
+ const struct tcs3400_config *cfg = dev->config;
+ uint8_t chip_id;
+ int ret;
+ struct {
+ uint8_t reg_addr;
+ uint8_t value;
+ } reset_regs[] = {
+ {TCS3400_ENABLE_REG, TCS3400_DEFAULT_ENABLE},
+ {TCS3400_AICLEAR_REG, TCS3400_AICLEAR_RESET},
+ {TCS3400_ATIME_REG, TCS3400_DEFAULT_ATIME},
+ {TCS3400_PERS_REG, TCS3400_DEFAULT_PERS},
+ {TCS3400_CONFIG_REG, TCS3400_DEFAULT_CONFIG},
+ {TCS3400_CONTROL_REG, TCS3400_DEFAULT_CONTROL},
+ };
+
+ ret = i2c_reg_read_byte_dt(&cfg->i2c, TCS3400_ID_REG, &chip_id);
+ if (ret) {
+ LOG_DBG("Failed to read chip id: %d", ret);
+ return ret;
+ }
+
+ if (!((chip_id == TCS3400_ID_1) || (chip_id == TCS3400_ID_2))) {
+ LOG_DBG("Invalid chip id: %02x", chip_id);
+ return -EIO;
+ }
+
+ LOG_INF("chip id: 0x%x", chip_id);
+
+ for (size_t i = 0; i < ARRAY_SIZE(reset_regs); i++) {
+ ret = i2c_reg_write_byte_dt(&cfg->i2c, reset_regs[i].reg_addr,
+ reset_regs[i].value);
+ if (ret) {
+ LOG_ERR("Failed to set default register: %02x",
+ reset_regs[i].reg_addr);
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
+static const struct sensor_driver_api tcs3400_api = {
+ .sample_fetch = tcs3400_sample_fetch,
+ .channel_get = tcs3400_channel_get,
+ .attr_set = tcs3400_attr_set,
+};
+
+static int tcs3400_init(const struct device *dev)
+{
+ const struct tcs3400_config *cfg = dev->config;
+ struct tcs3400_data *data = dev->data;
+ int ret;
+
+ k_sem_init(&data->data_sem, 0, K_SEM_MAX_LIMIT);
+ data->dev = dev;
+
+ if (!i2c_is_ready_dt(&cfg->i2c)) {
+ LOG_ERR("I2C bus is not ready");
+ return -ENODEV;
+ }
+
+ ret = tcs3400_sensor_setup(dev);
+ if (ret < 0) {
+ LOG_ERR("Failed to setup device");
+ return ret;
+ }
+
+ if (!gpio_is_ready_dt(&cfg->int_gpio)) {
+ LOG_ERR("Interrupt GPIO device not ready");
+ return -ENODEV;
+ }
+
+ ret = gpio_pin_configure_dt(&cfg->int_gpio, GPIO_INPUT);
+ if (ret < 0) {
+ LOG_ERR("Failed to configure interrupt pin");
+ return ret;
+ }
+
+ gpio_init_callback(&data->gpio_cb, tcs3400_gpio_callback,
+ BIT(cfg->int_gpio.pin));
+
+ ret = gpio_add_callback(cfg->int_gpio.port, &data->gpio_cb);
+ if (ret < 0) {
+ LOG_ERR("Failed to set GPIO callback");
+ return ret;
+ }
+
+ return 0;
+}
+
+#define TCS3400_INIT(n) \
+ static struct tcs3400_data tcs3400_data_##n; \
+ static const struct tcs3400_config tcs3400_config_##n = { \
+ .i2c = I2C_DT_SPEC_INST_GET(n), \
+ .int_gpio = GPIO_DT_SPEC_INST_GET(n, int_gpios), \
+ }; \
+ SENSOR_DEVICE_DT_INST_DEFINE(n, &tcs3400_init, NULL, \
+ &tcs3400_data_##n, &tcs3400_config_##n, \
+ POST_KERNEL, CONFIG_SENSOR_INIT_PRIORITY, \
+ &tcs3400_api);
+
+DT_INST_FOREACH_STATUS_OKAY(TCS3400_INIT)
diff --git a/dts/bindings/sensor/ams,tcs3400.yaml b/dts/bindings/sensor/ams,tcs3400.yaml
new file mode 100644
index 0000000..609efe8
--- /dev/null
+++ b/dts/bindings/sensor/ams,tcs3400.yaml
@@ -0,0 +1,15 @@
+# Copyright 2023 Google LLC
+# SPDX-License-Identifier: Apache-2.0
+
+description: AMS TCS3400 Color Light-to-Digital Converter
+
+compatible: "ams,tcs3400"
+
+include: [sensor-device.yaml, i2c-device.yaml]
+
+properties:
+ int-gpios:
+ type: phandle-array
+ required: true
+ description: |
+ INT pin GPIO identifier, open-drain, active low.
diff --git a/include/zephyr/drivers/sensor/tcs3400.h b/include/zephyr/drivers/sensor/tcs3400.h
new file mode 100644
index 0000000..1c92cf1
--- /dev/null
+++ b/include/zephyr/drivers/sensor/tcs3400.h
@@ -0,0 +1,17 @@
+/*
+ * Copyright 2023 Google LLC
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+#ifndef ZEPHYR_INCLUDE_DRIVERS_SENSOR_TCS3400_H_
+#define ZEPHYR_INCLUDE_DRIVERS_SENSOR_TCS3400_H_
+
+#include <zephyr/drivers/sensor.h>
+
+enum sensor_attribute_tcs3400 {
+ /** RGBC Integration Cycles */
+ SENSOR_ATTR_TCS3400_INTEGRATION_CYCLES = SENSOR_ATTR_PRIV_START,
+};
+
+#endif /* ZEPHYR_INCLUDE_DRIVERS_SENSOR_TCS3400_H_ */
diff --git a/tests/drivers/build_all/sensor/i2c.dtsi b/tests/drivers/build_all/sensor/i2c.dtsi
index 8ac0e38..4023fc8 100644
--- a/tests/drivers/build_all/sensor/i2c.dtsi
+++ b/tests/drivers/build_all/sensor/i2c.dtsi
@@ -739,3 +739,9 @@
compatible = "microchip,mcp9600";
reg = <0x71>;
};
+
+test_i2c_tcs3400: tcs3400@72 {
+ compatible = "ams,tcs3400";
+ reg = <0x72>;
+ int-gpios = <&test_gpio 0 0>;
+};