/* * Copyright 2023 Google LLC * * SPDX-License-Identifier: Apache-2.0 */ #define DT_DRV_COMPAT ams_tcs3400 #include #include #include #include #include #include #include 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 DEVICE_API(sensor, 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)