/*
* Copyright (c) 2020 Richard Osterloh
*
* SPDX-License-Identifier: Apache-2.0
*/
#define DT_DRV_COMPAT vishay_vcnl4040
#include "vcnl4040.h"
#include <zephyr/pm/device.h>
#include <zephyr/sys/__assert.h>
#include <zephyr/sys/byteorder.h>
#include <zephyr/sys/util.h>
#include <zephyr/logging/log.h>
#include <stdlib.h>
LOG_MODULE_REGISTER(vcnl4040, CONFIG_SENSOR_LOG_LEVEL);
int vcnl4040_read(const struct device *dev, uint8_t reg, uint16_t *out)
{
const struct vcnl4040_config *config = dev->config;
struct vcnl4040_data *data = dev->data;
uint8_t buff[2] = { 0 };
int ret = 0;
ret = i2c_write_read(data->i2c, config->i2c_address,
®, sizeof(reg), buff, sizeof(buff));
if (!ret)
*out = sys_get_le16(buff);
return ret;
}
int vcnl4040_write(const struct device *dev, uint8_t reg, uint16_t value)
{
const struct vcnl4040_config *config = dev->config;
struct vcnl4040_data *data = dev->data;
struct i2c_msg msg;
int ret;
uint8_t buff[3];
sys_put_le16(value, &buff[1]);
buff[0] = reg;
msg.buf = buff;
msg.flags = 0;
msg.len = sizeof(buff);
ret = i2c_transfer(data->i2c, &msg, 1, config->i2c_address);
if (ret < 0) {
LOG_ERR("write block failed");
return ret;
}
return 0;
}
static int vcnl4040_sample_fetch(const struct device *dev,
enum sensor_channel chan)
{
struct vcnl4040_data *data = dev->data;
int ret = 0;
#ifdef CONFIG_VCNL4040_ENABLE_ALS
__ASSERT_NO_MSG(chan == SENSOR_CHAN_ALL ||
chan == SENSOR_CHAN_PROX ||
chan == SENSOR_CHAN_LIGHT);
#else
__ASSERT_NO_MSG(chan == SENSOR_CHAN_ALL || chan == SENSOR_CHAN_PROX);
#endif
k_sem_take(&data->sem, K_FOREVER);
if (chan == SENSOR_CHAN_ALL || chan == SENSOR_CHAN_PROX) {
ret = vcnl4040_read(dev, VCNL4040_REG_PS_DATA,
&data->proximity);
if (ret < 0) {
LOG_ERR("Could not fetch proximity");
goto exit;
}
}
#ifdef CONFIG_VCNL4040_ENABLE_ALS
if (chan == SENSOR_CHAN_ALL || chan == SENSOR_CHAN_LIGHT) {
ret = vcnl4040_read(dev, VCNL4040_REG_ALS_DATA,
&data->light);
if (ret < 0) {
LOG_ERR("Could not fetch ambient light");
}
}
#endif
exit:
k_sem_give(&data->sem);
return ret;
}
static int vcnl4040_channel_get(const struct device *dev,
enum sensor_channel chan,
struct sensor_value *val)
{
struct vcnl4040_data *data = dev->data;
int ret = 0;
k_sem_take(&data->sem, K_FOREVER);
switch (chan) {
case SENSOR_CHAN_PROX:
val->val1 = data->proximity;
val->val2 = 0;
break;
#ifdef CONFIG_VCNL4040_ENABLE_ALS
case SENSOR_CHAN_LIGHT:
val->val1 = data->light * data->sensitivity;
val->val2 = 0;
break;
#endif
default:
ret = -ENOTSUP;
}
k_sem_give(&data->sem);
return ret;
}
static int vcnl4040_proxy_setup(const struct device *dev)
{
const struct vcnl4040_config *config = dev->config;
uint16_t conf = 0;
if (vcnl4040_read(dev, VCNL4040_REG_PS_MS, &conf)) {
LOG_ERR("Could not read proximity config");
return -EIO;
}
/* Set LED current */
conf |= config->led_i << VCNL4040_LED_I_POS;
if (vcnl4040_write(dev, VCNL4040_REG_PS_MS, conf)) {
LOG_ERR("Could not write proximity config");
return -EIO;
}
if (vcnl4040_read(dev, VCNL4040_REG_PS_CONF, &conf)) {
LOG_ERR("Could not read proximity config");
return -EIO;
}
/* Set PS_HD */
conf |= VCNL4040_PS_HD_MASK;
/* Set duty cycle */
conf |= config->led_dc << VCNL4040_PS_DUTY_POS;
/* Set integration time */
conf |= config->proxy_it << VCNL4040_PS_IT_POS;
/* Clear proximity shutdown */
conf &= ~VCNL4040_PS_SD_MASK;
if (vcnl4040_write(dev, VCNL4040_REG_PS_CONF, conf)) {
LOG_ERR("Could not write proximity config");
return -EIO;
}
return 0;
}
#ifdef CONFIG_VCNL4040_ENABLE_ALS
static int vcnl4040_ambient_setup(const struct device *dev)
{
const struct vcnl4040_config *config = dev->config;
struct vcnl4040_data *data = dev->data;
uint16_t conf = 0;
if (vcnl4040_read(dev, VCNL4040_REG_ALS_CONF, &conf)) {
LOG_ERR("Could not read proximity config");
return -EIO;
}
/* Set ALS integration time */
conf |= config->als_it << VCNL4040_ALS_IT_POS;
/* Clear ALS shutdown */
conf &= ~VCNL4040_ALS_SD_MASK;
if (vcnl4040_write(dev, VCNL4040_REG_ALS_CONF, conf)) {
LOG_ERR("Could not write proximity config");
return -EIO;
}
/*
* scale the lux depending on the value of the integration time
* see page 8 of the VCNL4040 application note:
* https://www.vishay.com/docs/84307/designingvcnl4040.pdf
*/
switch (config->als_it) {
case VCNL4040_AMBIENT_INTEGRATION_TIME_80MS:
data->sensitivity = 0.12;
break;
case VCNL4040_AMBIENT_INTEGRATION_TIME_160MS:
data->sensitivity = 0.06;
break;
case VCNL4040_AMBIENT_INTEGRATION_TIME_320MS:
data->sensitivity = 0.03;
break;
case VCNL4040_AMBIENT_INTEGRATION_TIME_640MS:
data->sensitivity = 0.015;
break;
default:
data->sensitivity = 1.0;
LOG_WRN("Cannot set ALS sensitivity from ALS_IT=%d",
config->als_it);
break;
}
return 0;
}
#endif
#ifdef CONFIG_PM_DEVICE
static int vcnl4040_pm_action(const struct device *dev,
enum pm_device_action action)
{
int ret = 0;
uint16_t ps_conf;
ret = vcnl4040_read(dev, VCNL4040_REG_PS_CONF, &ps_conf);
if (ret < 0)
return ret;
#ifdef CONFIG_VCNL4040_ENABLE_ALS
uint16_t als_conf;
ret = vcnl4040_read(dev, VCNL4040_REG_ALS_CONF, &als_conf);
if (ret < 0)
return ret;
#endif
switch (action) {
case PM_DEVICE_ACTION_RESUME:
/* Clear proximity shutdown */
ps_conf &= ~VCNL4040_PS_SD_MASK;
ret = vcnl4040_write(dev, VCNL4040_REG_PS_CONF,
ps_conf);
if (ret < 0)
return ret;
#ifdef CONFIG_VCNL4040_ENABLE_ALS
/* Clear als shutdown */
als_conf &= ~VCNL4040_ALS_SD_MASK;
ret = vcnl4040_write(dev, VCNL4040_REG_ALS_CONF,
als_conf);
if (ret < 0)
return ret;
#endif
break;
case PM_DEVICE_ACTION_SUSPEND:
/* Set proximity shutdown bit 0 */
ps_conf |= VCNL4040_PS_SD_MASK;
ret = vcnl4040_write(dev, VCNL4040_REG_PS_CONF,
ps_conf);
if (ret < 0)
return ret;
#ifdef CONFIG_VCNL4040_ENABLE_ALS
/* Clear als shutdown bit 0 */
als_conf |= VCNL4040_ALS_SD_MASK;
ret = vcnl4040_write(dev, VCNL4040_REG_ALS_CONF,
als_conf);
if (ret < 0)
return ret;
#endif
break;
default:
return -ENOTSUP;
}
return ret;
}
#endif
static int vcnl4040_init(const struct device *dev)
{
const struct vcnl4040_config *config = dev->config;
struct vcnl4040_data *data = dev->data;
uint16_t id;
/* Get the I2C device */
data->i2c = device_get_binding(config->i2c_name);
if (data->i2c == NULL) {
LOG_ERR("Could not find I2C device");
return -EINVAL;
}
/* Check device id */
if (vcnl4040_read(dev, VCNL4040_REG_DEVICE_ID, &id)) {
LOG_ERR("Could not read device id");
return -EIO;
}
if (id != VCNL4040_DEFAULT_ID) {
LOG_ERR("Incorrect device id (%d)", id);
return -EIO;
}
if (vcnl4040_proxy_setup(dev)) {
LOG_ERR("Failed to setup proximity functionality");
return -EIO;
}
#ifdef CONFIG_VCNL4040_ENABLE_ALS
if (vcnl4040_ambient_setup(dev)) {
LOG_ERR("Failed to setup ambient light functionality");
return -EIO;
}
#endif
k_sem_init(&data->sem, 0, K_SEM_MAX_LIMIT);
#if CONFIG_VCNL4040_TRIGGER
if (vcnl4040_trigger_init(dev)) {
LOG_ERR("Could not initialise interrupts");
return -EIO;
}
#endif
k_sem_give(&data->sem);
LOG_DBG("Init complete");
return 0;
}
static const struct sensor_driver_api vcnl4040_driver_api = {
.sample_fetch = vcnl4040_sample_fetch,
.channel_get = vcnl4040_channel_get,
#ifdef CONFIG_VCNL4040_TRIGGER
.attr_set = vcnl4040_attr_set,
.trigger_set = vcnl4040_trigger_set,
#endif
};
static const struct vcnl4040_config vcnl4040_config = {
.i2c_name = DT_INST_BUS_LABEL(0),
.i2c_address = DT_INST_REG_ADDR(0),
#ifdef CONFIG_VCNL4040_TRIGGER
#if DT_INST_NODE_HAS_PROP(0, int_gpios)
.gpio_name = DT_INST_GPIO_LABEL(0, int_gpios),
.gpio_pin = DT_INST_GPIO_PIN(0, int_gpios),
.gpio_flags = DT_INST_GPIO_FLAGS(0, int_gpios),
#else
.gpio_name = NULL,
.gpio_pin = 0,
.gpio_flags = 0,
#endif
#endif
.led_i = DT_INST_ENUM_IDX(0, led_current),
.led_dc = DT_INST_ENUM_IDX(0, led_duty_cycle),
.als_it = DT_INST_ENUM_IDX(0, als_it),
.proxy_it = DT_INST_ENUM_IDX(0, proximity_it),
.proxy_type = DT_INST_ENUM_IDX(0, proximity_trigger),
};
static struct vcnl4040_data vcnl4040_data;
PM_DEVICE_DT_INST_DEFINE(0, vcnl4040_pm_action);
DEVICE_DT_INST_DEFINE(0, vcnl4040_init,
PM_DEVICE_DT_INST_GET(0), &vcnl4040_data, &vcnl4040_config,
POST_KERNEL, CONFIG_SENSOR_INIT_PRIORITY, &vcnl4040_driver_api);