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