| /* |
| * Copyright (c) 2017 Intel Corporation |
| * Copyright (c) 2018 Phytec Messtechnik GmbH |
| * |
| *SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #define DT_DRV_COMPAT avago_apds9960 |
| |
| /* @file |
| * @brief driver for APDS9960 ALS/RGB/gesture/proximity sensor |
| */ |
| |
| #include <zephyr/device.h> |
| #include <zephyr/drivers/sensor.h> |
| #include <zephyr/drivers/i2c.h> |
| #include <zephyr/pm/device.h> |
| #include <zephyr/sys/__assert.h> |
| #include <zephyr/sys/byteorder.h> |
| #include <zephyr/init.h> |
| #include <zephyr/kernel.h> |
| #include <string.h> |
| #include <zephyr/logging/log.h> |
| |
| #include "apds9960.h" |
| |
| LOG_MODULE_REGISTER(APDS9960, CONFIG_SENSOR_LOG_LEVEL); |
| |
| static void apds9960_handle_cb(struct apds9960_data *drv_data) |
| { |
| apds9960_setup_int(drv_data->dev->config, false); |
| |
| #ifdef CONFIG_APDS9960_TRIGGER |
| k_work_submit(&drv_data->work); |
| #else |
| k_sem_give(&drv_data->data_sem); |
| #endif |
| } |
| |
| static void apds9960_gpio_callback(const struct device *dev, |
| struct gpio_callback *cb, uint32_t pins) |
| { |
| struct apds9960_data *drv_data = |
| CONTAINER_OF(cb, struct apds9960_data, gpio_cb); |
| |
| apds9960_handle_cb(drv_data); |
| } |
| |
| static int apds9960_sample_fetch(const struct device *dev, |
| enum sensor_channel chan) |
| { |
| const struct apds9960_config *config = dev->config; |
| struct apds9960_data *data = dev->data; |
| uint8_t tmp; |
| |
| if (chan != SENSOR_CHAN_ALL) { |
| LOG_ERR("Unsupported sensor channel"); |
| return -ENOTSUP; |
| } |
| |
| #ifndef CONFIG_APDS9960_TRIGGER |
| apds9960_setup_int(config, true); |
| |
| #ifdef CONFIG_APDS9960_ENABLE_ALS |
| tmp = APDS9960_ENABLE_PON | APDS9960_ENABLE_AIEN; |
| #else |
| tmp = APDS9960_ENABLE_PON | APDS9960_ENABLE_PIEN; |
| #endif |
| if (i2c_reg_update_byte_dt(&config->i2c, |
| APDS9960_ENABLE_REG, tmp, tmp)) { |
| LOG_ERR("Power on bit not set."); |
| return -EIO; |
| } |
| |
| k_sem_take(&data->data_sem, K_FOREVER); |
| #endif |
| |
| if (i2c_reg_read_byte_dt(&config->i2c, |
| APDS9960_STATUS_REG, &tmp)) { |
| return -EIO; |
| } |
| |
| LOG_DBG("status: 0x%x", tmp); |
| if (tmp & APDS9960_STATUS_PINT) { |
| if (i2c_reg_read_byte_dt(&config->i2c, |
| APDS9960_PDATA_REG, &data->pdata)) { |
| return -EIO; |
| } |
| } |
| |
| if (tmp & APDS9960_STATUS_AINT) { |
| if (i2c_burst_read_dt(&config->i2c, |
| APDS9960_CDATAL_REG, |
| (uint8_t *)&data->sample_crgb, |
| sizeof(data->sample_crgb))) { |
| return -EIO; |
| } |
| |
| } |
| |
| #ifndef CONFIG_APDS9960_TRIGGER |
| if (i2c_reg_update_byte_dt(&config->i2c, |
| APDS9960_ENABLE_REG, |
| APDS9960_ENABLE_PON, |
| 0)) { |
| return -EIO; |
| } |
| #endif |
| |
| if (i2c_reg_write_byte_dt(&config->i2c, |
| APDS9960_AICLEAR_REG, 0)) { |
| return -EIO; |
| } |
| |
| return 0; |
| } |
| |
| static int apds9960_channel_get(const struct device *dev, |
| enum sensor_channel chan, |
| struct sensor_value *val) |
| { |
| struct apds9960_data *data = dev->data; |
| |
| switch (chan) { |
| #ifdef CONFIG_APDS9960_ENABLE_ALS |
| 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; |
| #endif |
| case SENSOR_CHAN_PROX: |
| val->val1 = data->pdata; |
| val->val2 = 0; |
| break; |
| default: |
| return -ENOTSUP; |
| } |
| |
| return 0; |
| } |
| |
| static int apds9960_proxy_setup(const struct device *dev) |
| { |
| const struct apds9960_config *config = dev->config; |
| |
| if (i2c_reg_write_byte_dt(&config->i2c, |
| APDS9960_POFFSET_UR_REG, |
| APDS9960_DEFAULT_POFFSET_UR)) { |
| LOG_ERR("Default offset UR not set "); |
| return -EIO; |
| } |
| |
| if (i2c_reg_write_byte_dt(&config->i2c, |
| APDS9960_POFFSET_DL_REG, |
| APDS9960_DEFAULT_POFFSET_DL)) { |
| LOG_ERR("Default offset DL not set "); |
| return -EIO; |
| } |
| |
| if (i2c_reg_write_byte_dt(&config->i2c, |
| APDS9960_PPULSE_REG, |
| config->ppcount)) { |
| LOG_ERR("Default pulse count not set "); |
| return -EIO; |
| } |
| |
| if (i2c_reg_update_byte_dt(&config->i2c, |
| APDS9960_CONTROL_REG, |
| APDS9960_CONTROL_LDRIVE, |
| APDS9960_DEFAULT_LDRIVE)) { |
| LOG_ERR("LED Drive Strength not set"); |
| return -EIO; |
| } |
| |
| if (i2c_reg_update_byte_dt(&config->i2c, |
| APDS9960_CONFIG2_REG, |
| APDS9960_PLED_BOOST_300, |
| config->pled_boost)) { |
| LOG_ERR("LED Drive Strength not set"); |
| return -EIO; |
| } |
| |
| if (i2c_reg_update_byte_dt(&config->i2c, |
| APDS9960_CONTROL_REG, APDS9960_CONTROL_PGAIN, |
| (config->pgain & APDS9960_PGAIN_8X))) { |
| LOG_ERR("Gain is not set"); |
| return -EIO; |
| } |
| |
| if (i2c_reg_write_byte_dt(&config->i2c, |
| APDS9960_PILT_REG, APDS9960_DEFAULT_PILT)) { |
| LOG_ERR("Low threshold not set"); |
| return -EIO; |
| } |
| |
| if (i2c_reg_write_byte_dt(&config->i2c, |
| APDS9960_PIHT_REG, APDS9960_DEFAULT_PIHT)) { |
| LOG_ERR("High threshold not set"); |
| return -EIO; |
| } |
| |
| if (i2c_reg_update_byte_dt(&config->i2c, |
| APDS9960_ENABLE_REG, APDS9960_ENABLE_PEN, |
| APDS9960_ENABLE_PEN)) { |
| LOG_ERR("Proximity mode is not enabled"); |
| return -EIO; |
| } |
| |
| return 0; |
| } |
| |
| #ifdef CONFIG_APDS9960_ENABLE_ALS |
| static int apds9960_ambient_setup(const struct device *dev) |
| { |
| const struct apds9960_config *config = dev->config; |
| uint16_t th; |
| |
| /* ADC value */ |
| if (i2c_reg_write_byte_dt(&config->i2c, |
| APDS9960_ATIME_REG, APDS9960_DEFAULT_ATIME)) { |
| LOG_ERR("Default integration time not set for ADC"); |
| return -EIO; |
| } |
| |
| /* ALS Gain */ |
| if (i2c_reg_update_byte_dt(&config->i2c, |
| APDS9960_CONTROL_REG, |
| APDS9960_CONTROL_AGAIN, |
| (config->again & APDS9960_AGAIN_64X))) { |
| LOG_ERR("Ambient Gain is not set"); |
| return -EIO; |
| } |
| |
| th = sys_cpu_to_le16(APDS9960_DEFAULT_AILT); |
| if (i2c_burst_write_dt(&config->i2c, |
| APDS9960_INT_AILTL_REG, |
| (uint8_t *)&th, sizeof(th))) { |
| LOG_ERR("ALS low threshold not set"); |
| return -EIO; |
| } |
| |
| th = sys_cpu_to_le16(APDS9960_DEFAULT_AIHT); |
| if (i2c_burst_write_dt(&config->i2c, |
| APDS9960_INT_AIHTL_REG, |
| (uint8_t *)&th, sizeof(th))) { |
| LOG_ERR("ALS low threshold not set"); |
| return -EIO; |
| } |
| |
| /* Enable ALS */ |
| if (i2c_reg_update_byte_dt(&config->i2c, |
| APDS9960_ENABLE_REG, APDS9960_ENABLE_AEN, |
| APDS9960_ENABLE_AEN)) { |
| LOG_ERR("ALS is not enabled"); |
| return -EIO; |
| } |
| |
| return 0; |
| } |
| #endif |
| |
| static int apds9960_sensor_setup(const struct device *dev) |
| { |
| const struct apds9960_config *config = dev->config; |
| uint8_t chip_id; |
| |
| if (i2c_reg_read_byte_dt(&config->i2c, |
| APDS9960_ID_REG, &chip_id)) { |
| LOG_ERR("Failed reading chip id"); |
| return -EIO; |
| } |
| |
| if (!((chip_id == APDS9960_ID_1) || (chip_id == APDS9960_ID_2))) { |
| LOG_ERR("Invalid chip id 0x%x", chip_id); |
| return -EIO; |
| } |
| |
| /* Disable all functions and interrupts */ |
| if (i2c_reg_write_byte_dt(&config->i2c, |
| APDS9960_ENABLE_REG, 0)) { |
| LOG_ERR("ENABLE register is not cleared"); |
| return -EIO; |
| } |
| |
| if (i2c_reg_write_byte_dt(&config->i2c, |
| APDS9960_AICLEAR_REG, 0)) { |
| return -EIO; |
| } |
| |
| /* Disable gesture interrupt */ |
| if (i2c_reg_write_byte_dt(&config->i2c, |
| APDS9960_GCONFIG4_REG, 0)) { |
| LOG_ERR("GCONFIG4 register is not cleared"); |
| return -EIO; |
| } |
| |
| if (i2c_reg_write_byte_dt(&config->i2c, |
| APDS9960_WTIME_REG, APDS9960_DEFAULT_WTIME)) { |
| LOG_ERR("Default wait time not set"); |
| return -EIO; |
| } |
| |
| if (i2c_reg_write_byte_dt(&config->i2c, |
| APDS9960_CONFIG1_REG, |
| APDS9960_DEFAULT_CONFIG1)) { |
| LOG_ERR("Default WLONG not set"); |
| return -EIO; |
| } |
| |
| if (i2c_reg_write_byte_dt(&config->i2c, |
| APDS9960_CONFIG2_REG, |
| APDS9960_DEFAULT_CONFIG2)) { |
| LOG_ERR("Configuration Register Two not set"); |
| return -EIO; |
| } |
| |
| if (i2c_reg_write_byte_dt(&config->i2c, |
| APDS9960_CONFIG3_REG, |
| APDS9960_DEFAULT_CONFIG3)) { |
| LOG_ERR("Configuration Register Three not set"); |
| return -EIO; |
| } |
| |
| if (i2c_reg_write_byte_dt(&config->i2c, |
| APDS9960_PERS_REG, |
| APDS9960_DEFAULT_PERS)) { |
| LOG_ERR("Interrupt persistence not set"); |
| return -EIO; |
| } |
| |
| if (apds9960_proxy_setup(dev)) { |
| LOG_ERR("Failed to setup proximity functionality"); |
| return -EIO; |
| } |
| |
| #ifdef CONFIG_APDS9960_ENABLE_ALS |
| if (apds9960_ambient_setup(dev)) { |
| LOG_ERR("Failed to setup ambient light functionality"); |
| return -EIO; |
| } |
| #endif |
| |
| return 0; |
| } |
| |
| static int apds9960_init_interrupt(const struct device *dev) |
| { |
| const struct apds9960_config *config = dev->config; |
| struct apds9960_data *drv_data = dev->data; |
| |
| if (!device_is_ready(config->int_gpio.port)) { |
| LOG_ERR("%s: device %s is not ready", dev->name, |
| config->int_gpio.port->name); |
| return -ENODEV; |
| } |
| |
| gpio_pin_configure_dt(&config->int_gpio, GPIO_INPUT | config->int_gpio.dt_flags); |
| |
| gpio_init_callback(&drv_data->gpio_cb, |
| apds9960_gpio_callback, |
| BIT(config->int_gpio.pin)); |
| |
| if (gpio_add_callback(config->int_gpio.port, &drv_data->gpio_cb) < 0) { |
| LOG_DBG("Failed to set gpio callback!"); |
| return -EIO; |
| } |
| |
| drv_data->dev = dev; |
| |
| #ifdef CONFIG_APDS9960_TRIGGER |
| drv_data->work.handler = apds9960_work_cb; |
| if (i2c_reg_update_byte_dt(&config->i2c, |
| APDS9960_ENABLE_REG, |
| APDS9960_ENABLE_PON, |
| APDS9960_ENABLE_PON)) { |
| LOG_ERR("Power on bit not set."); |
| return -EIO; |
| } |
| |
| #else |
| k_sem_init(&drv_data->data_sem, 0, K_SEM_MAX_LIMIT); |
| #endif |
| apds9960_setup_int(config, true); |
| |
| if (gpio_pin_get_dt(&config->int_gpio) > 0) { |
| apds9960_handle_cb(drv_data); |
| } |
| |
| return 0; |
| } |
| |
| #ifdef CONFIG_PM_DEVICE |
| static int apds9960_pm_action(const struct device *dev, |
| enum pm_device_action action) |
| { |
| const struct apds9960_config *config = dev->config; |
| int ret = 0; |
| |
| switch (action) { |
| case PM_DEVICE_ACTION_RESUME: |
| if (i2c_reg_update_byte_dt(&config->i2c, |
| APDS9960_ENABLE_REG, |
| APDS9960_ENABLE_PON, |
| APDS9960_ENABLE_PON)) { |
| ret = -EIO; |
| } |
| break; |
| case PM_DEVICE_ACTION_SUSPEND: |
| if (i2c_reg_update_byte_dt(&config->i2c, |
| APDS9960_ENABLE_REG, |
| APDS9960_ENABLE_PON, 0)) { |
| ret = -EIO; |
| } |
| |
| if (i2c_reg_write_byte_dt(&config->i2c, |
| APDS9960_AICLEAR_REG, 0)) { |
| ret = -EIO; |
| } |
| break; |
| default: |
| return -ENOTSUP; |
| } |
| |
| return ret; |
| } |
| #endif |
| |
| static int apds9960_init(const struct device *dev) |
| { |
| const struct apds9960_config *config = dev->config; |
| struct apds9960_data *data = dev->data; |
| |
| /* Initialize time 5.7ms */ |
| k_sleep(K_MSEC(6)); |
| |
| if (!device_is_ready(config->i2c.bus)) { |
| LOG_ERR("Bus device is not ready"); |
| return -EINVAL; |
| } |
| |
| (void)memset(data->sample_crgb, 0, sizeof(data->sample_crgb)); |
| data->pdata = 0U; |
| |
| if (apds9960_sensor_setup(dev) < 0) { |
| LOG_ERR("Failed to setup device!"); |
| return -EIO; |
| } |
| |
| if (apds9960_init_interrupt(dev) < 0) { |
| LOG_ERR("Failed to initialize interrupt!"); |
| return -EIO; |
| } |
| |
| return 0; |
| } |
| |
| static const struct sensor_driver_api apds9960_driver_api = { |
| .sample_fetch = &apds9960_sample_fetch, |
| .channel_get = &apds9960_channel_get, |
| #ifdef CONFIG_APDS9960_TRIGGER |
| .attr_set = apds9960_attr_set, |
| .trigger_set = apds9960_trigger_set, |
| #endif |
| }; |
| |
| static const struct apds9960_config apds9960_config = { |
| .i2c = I2C_DT_SPEC_INST_GET(0), |
| .int_gpio = GPIO_DT_SPEC_INST_GET(0, int_gpios), |
| #if CONFIG_APDS9960_PGAIN_8X |
| .pgain = APDS9960_PGAIN_8X, |
| #elif CONFIG_APDS9960_PGAIN_4X |
| .pgain = APDS9960_PGAIN_4X, |
| #elif CONFIG_APDS9960_PGAIN_2X |
| .pgain = APDS9960_PGAIN_2X, |
| #else |
| .pgain = APDS9960_PGAIN_1X, |
| #endif |
| #if CONFIG_APDS9960_AGAIN_64X |
| .again = APDS9960_AGAIN_64X, |
| #elif CONFIG_APDS9960_AGAIN_16X |
| .again = APDS9960_AGAIN_16X, |
| #elif CONFIG_APDS9960_AGAIN_4X |
| .again = APDS9960_AGAIN_4X, |
| #else |
| .again = APDS9960_AGAIN_1X, |
| #endif |
| #if CONFIG_APDS9960_PPULSE_LENGTH_32US |
| .ppcount = APDS9960_PPULSE_LENGTH_32US | |
| (CONFIG_APDS9960_PPULSE_COUNT - 1), |
| #elif CONFIG_APDS9960_PPULSE_LENGTH_16US |
| .ppcount = APDS9960_PPULSE_LENGTH_16US | |
| (CONFIG_APDS9960_PPULSE_COUNT - 1), |
| #elif CONFIG_APDS9960_PPULSE_LENGTH_8US |
| .ppcount = APDS9960_PPULSE_LENGTH_8US | |
| (CONFIG_APDS9960_PPULSE_COUNT - 1), |
| #else |
| .ppcount = APDS9960_PPULSE_LENGTH_4US | |
| (CONFIG_APDS9960_PPULSE_COUNT - 1), |
| #endif |
| #if CONFIG_APDS9960_PLED_BOOST_300PCT |
| .pled_boost = APDS9960_PLED_BOOST_300, |
| #elif CONFIG_APDS9960_PLED_BOOST_200PCT |
| .pled_boost = APDS9960_PLED_BOOST_200, |
| #elif CONFIG_APDS9960_PLED_BOOST_150PCT |
| .pled_boost = APDS9960_PLED_BOOST_150, |
| #else |
| .pled_boost = APDS9960_PLED_BOOST_100, |
| #endif |
| }; |
| |
| static struct apds9960_data apds9960_data; |
| |
| PM_DEVICE_DT_INST_DEFINE(0, apds9960_pm_action); |
| |
| SENSOR_DEVICE_DT_INST_DEFINE(0, apds9960_init, |
| PM_DEVICE_DT_INST_GET(0), &apds9960_data, &apds9960_config, |
| POST_KERNEL, CONFIG_SENSOR_INIT_PRIORITY, &apds9960_driver_api); |