530 lines
12 KiB
C
530 lines
12 KiB
C
/*
|
|
* 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);
|
|
|
|
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);
|