zephyr/drivers/sensor/tsl2540/tsl2540.c

374 lines
9.8 KiB
C

/*
* Copyright (c) 2022 T-Mobile USA, Inc.
*
* SPDX-License-Identifier: Apache-2.0
*/
#define DT_DRV_COMPAT ams_tsl2540
#include "tsl2540.h"
#include <stdlib.h>
#include <zephyr/logging/log.h>
#include <zephyr/drivers/sensor.h>
#include <zephyr/pm/device.h>
#include <zephyr/sys/__assert.h>
#include <zephyr/sys/byteorder.h>
#include <zephyr/sys/util.h>
#define TSL2540_INTEGRATION_TIME_MS (2.81)
#define TSL2540_DEVICE_FACTOR (53.0)
#define FIXED_ATTENUATION_TO_DBL(x) (x * 0.00001)
LOG_MODULE_REGISTER(tsl2540, CONFIG_SENSOR_LOG_LEVEL);
static int tsl2540_sample_fetch(const struct device *dev, enum sensor_channel chan)
{
const struct tsl2540_config *cfg = dev->config;
struct tsl2540_data *data = dev->data;
int ret = 0;
__ASSERT_NO_MSG(chan == SENSOR_CHAN_ALL || chan == SENSOR_CHAN_LIGHT ||
chan == SENSOR_CHAN_IR);
k_sem_take(&data->sem, K_FOREVER);
if (chan == SENSOR_CHAN_ALL || chan == SENSOR_CHAN_LIGHT) {
uint16_t le16_buffer;
ret = i2c_burst_read_dt(&cfg->i2c_spec, TSL2540_REG_VIS_LOW,
(uint8_t *)&le16_buffer, sizeof(le16_buffer));
if (ret) {
LOG_ERR("Could not fetch ambient light (visible)");
k_sem_give(&data->sem);
return -EIO;
}
data->count_vis = sys_le16_to_cpu(le16_buffer);
}
if (chan == SENSOR_CHAN_ALL || chan == SENSOR_CHAN_IR) {
uint16_t le16_buffer;
ret = i2c_burst_read_dt(&cfg->i2c_spec, TSL2540_REG_IR_LOW, (uint8_t *)&le16_buffer,
sizeof(le16_buffer));
if (ret) {
LOG_ERR("Could not fetch ambient light (IR)");
k_sem_give(&data->sem);
return -EIO;
}
data->count_ir = sys_le16_to_cpu(le16_buffer);
}
k_sem_give(&data->sem);
return ret;
}
static int tsl2540_channel_get(const struct device *dev, enum sensor_channel chan,
struct sensor_value *val)
{
const struct tsl2540_config *cfg = dev->config;
struct tsl2540_data *data = dev->data;
int ret = 0;
double cpl;
double glass_attenuation = FIXED_ATTENUATION_TO_DBL(cfg->glass_attenuation);
double glass_ir_attenuation = FIXED_ATTENUATION_TO_DBL(cfg->glass_ir_attenuation);
k_sem_take(&data->sem, K_FOREVER);
cpl = (data->integration_time + 1) * TSL2540_INTEGRATION_TIME_MS;
cpl *= data->again;
switch (chan) {
case SENSOR_CHAN_LIGHT:
sensor_value_from_double(val, data->count_vis / cpl *
TSL2540_DEVICE_FACTOR * glass_attenuation);
break;
case SENSOR_CHAN_IR:
sensor_value_from_double(val, data->count_ir / cpl *
TSL2540_DEVICE_FACTOR * glass_ir_attenuation);
break;
default:
ret = -ENOTSUP;
}
k_sem_give(&data->sem);
return ret;
}
static int tsl2540_attr_set_gain(const struct device *dev, enum sensor_gain_tsl2540 gain)
{
const struct tsl2540_config *cfg = dev->config;
struct tsl2540_data *data = dev->data;
uint8_t value = 0;
double again = 0.0;
switch (gain) {
case TSL2540_SENSOR_GAIN_1_2:
value = TSL2540_CFG1_G1_2;
again = TSL2540_AGAIN_S1_2;
break;
case TSL2540_SENSOR_GAIN_1:
value = TSL2540_CFG1_G1;
again = TSL2540_AGAIN_S1;
break;
case TSL2540_SENSOR_GAIN_4:
value = TSL2540_CFG1_G4;
again = TSL2540_AGAIN_S4;
break;
case TSL2540_SENSOR_GAIN_16:
value = TSL2540_CFG1_G16;
again = TSL2540_AGAIN_S16;
break;
case TSL2540_SENSOR_GAIN_64:
value = TSL2540_CFG1_G64;
again = TSL2540_AGAIN_S64;
break;
case TSL2540_SENSOR_GAIN_128:
value = TSL2540_CFG1_G128;
again = TSL2540_CFG2_G128;
break;
}
if (i2c_reg_write_byte_dt(&cfg->i2c_spec, TSL2540_REG_CFG_1, value) < 0) {
return -EIO;
}
if (i2c_reg_write_byte_dt(&cfg->i2c_spec, TSL2540_REG_CFG_2, value) < 0) {
return -EIO;
}
data->again = again;
return 0;
}
static int tsl2540_attr_set(const struct device *dev, enum sensor_channel chan,
enum sensor_attribute attr, const struct sensor_value *val)
{
const struct tsl2540_config *cfg = dev->config;
struct tsl2540_data *data = dev->data;
int ret = 0;
uint8_t temp;
double it;
if ((chan != SENSOR_CHAN_IR) & (chan != SENSOR_CHAN_LIGHT)) {
return -ENOTSUP;
}
k_sem_take(&data->sem, K_FOREVER);
i2c_reg_write_byte_dt(&cfg->i2c_spec, TSL2540_ENABLE_ADDR, TSL2540_ENABLE_MASK &
~TSL2540_ENABLE_CONF);
#if CONFIG_TSL2540_TRIGGER
if (chan == SENSOR_CHAN_LIGHT) {
if (attr == SENSOR_ATTR_UPPER_THRESH) {
double cpl;
uint16_t thld, le16_buffer;
double glass_attenuation = FIXED_ATTENUATION_TO_DBL(cfg->glass_attenuation);
cpl = ((data->integration_time + 1) * TSL2540_INTEGRATION_TIME_MS);
cpl *= data->again;
cpl /= (TSL2540_DEVICE_FACTOR * glass_attenuation);
thld = sensor_value_to_double(val) * cpl;
LOG_DBG("attr: %d, cpl: %g, thld: %x\n", attr, cpl, thld);
le16_buffer = sys_cpu_to_le16(thld);
ret = i2c_burst_write_dt(
&((const struct tsl2540_config *)dev->config)->i2c_spec,
TSL2540_REG_AIHT_LOW, (uint8_t *)&le16_buffer, sizeof(le16_buffer));
goto exit;
}
if (attr == SENSOR_ATTR_LOWER_THRESH) {
double cpl;
uint16_t thld, le16_buffer;
double glass_attenuation = FIXED_ATTENUATION_TO_DBL(cfg->glass_attenuation);
cpl = ((data->integration_time + 1) * TSL2540_INTEGRATION_TIME_MS);
cpl *= data->again;
cpl /= (TSL2540_DEVICE_FACTOR * glass_attenuation);
thld = sensor_value_to_double(val) * cpl;
LOG_DBG("attr: %d, cpl: %g, thld: %x\n", attr, cpl, thld);
le16_buffer = sys_cpu_to_le16(sys_cpu_to_le16(thld));
ret = i2c_burst_write_dt(
&((const struct tsl2540_config *)dev->config)->i2c_spec,
TSL2540_REG_AILT_LOW, (uint8_t *)&le16_buffer, sizeof(le16_buffer));
goto exit;
}
}
#endif /* CONFIG_TSL2540_TRIGGER */
switch ((enum sensor_attribute_tsl2540)attr) {
case SENSOR_ATTR_GAIN:
tsl2540_attr_set_gain(dev, (enum sensor_gain_tsl2540)val->val1);
break;
case SENSOR_ATTR_INT_APERS:
temp = (uint8_t)val->val1;
if (temp > 15) {
ret = -EINVAL;
goto exit;
}
if (i2c_reg_write_byte_dt(&cfg->i2c_spec, TSL2540_REG_PERS, temp)) {
ret = -EIO;
goto exit;
}
break;
case SENSOR_ATTR_INTEGRATION_TIME:
it = sensor_value_to_double(val);
it /= TSL2540_INTEGRATION_TIME_MS;
if (it < 1 || it > 256) {
ret = -EINVAL;
goto exit;
}
it -= 1;
temp = (uint8_t)it;
if (i2c_reg_write_byte_dt(&cfg->i2c_spec, TSL2540_REG_ATIME, temp)) {
ret = -EIO;
goto exit;
}
data->integration_time = temp;
ret = 0;
break;
case SENSOR_ATTR_TSL2540_SHUTDOWN_MODE:
data->enable_mode = TSL2540_ENABLE_DISABLE;
ret = i2c_reg_update_byte_dt(&cfg->i2c_spec, TSL2540_CFG3_ADDR, TSL2540_CFG3_MASK,
TSL2540_CFG3_CONF);
break;
case SENSOR_ATTR_TSL2540_CONTINUOUS_MODE:
data->enable_mode = TSL2540_ENABLE_CONF;
ret = i2c_reg_update_byte_dt(&cfg->i2c_spec, TSL2540_CFG3_ADDR, TSL2540_CFG3_MASK,
TSL2540_CFG3_CONF);
break;
case SENSOR_ATTR_TSL2540_CONTINUOUS_NO_WAIT_MODE:
data->enable_mode = TSL2540_ENABLE_AEN_PON;
ret = i2c_reg_update_byte_dt(&cfg->i2c_spec, TSL2540_CFG3_ADDR, TSL2540_CFG3_MASK,
TSL2540_CFG3_DFLT);
break;
}
exit:
i2c_reg_update_byte_dt(&cfg->i2c_spec, TSL2540_ENABLE_ADDR, TSL2540_ENABLE_MASK,
data->enable_mode);
k_sem_give(&data->sem);
return ret;
}
static int tsl2540_setup(const struct device *dev)
{
struct sensor_value integration_time;
/* Set ALS integration time */
tsl2540_attr_set(dev, (enum sensor_channel)SENSOR_CHAN_LIGHT,
(enum sensor_attribute)SENSOR_ATTR_GAIN,
&(struct sensor_value){.val1 = TSL2540_SENSOR_GAIN_1_2, .val2 = 0});
sensor_value_from_double(&integration_time, 500.0);
tsl2540_attr_set(dev, (enum sensor_channel)SENSOR_CHAN_LIGHT,
(enum sensor_attribute)SENSOR_ATTR_INTEGRATION_TIME, &integration_time);
return 0;
}
static int tsl2540_init(const struct device *dev)
{
const struct tsl2540_config *cfg = dev->config;
struct tsl2540_data *data = dev->data;
data->enable_mode = TSL2540_ENABLE_DISABLE;
k_sem_init(&data->sem, 1, K_SEM_MAX_LIMIT);
if (!i2c_is_ready_dt(&cfg->i2c_spec)) {
LOG_ERR("I2C dev %s not ready", cfg->i2c_spec.bus->name);
return -ENODEV;
}
i2c_reg_write_byte_dt(&cfg->i2c_spec, TSL2540_REG_PERS, 1);
i2c_reg_update_byte_dt(&cfg->i2c_spec, TSL2540_CFG3_ADDR, TSL2540_CFG3_MASK,
TSL2540_CFG3_DFLT);
if (tsl2540_setup(dev)) {
LOG_ERR("Failed to setup ambient light functionality");
return -EIO;
}
#if CONFIG_TSL2540_TRIGGER
if (tsl2540_trigger_init(dev)) {
LOG_ERR("Could not initialize interrupts");
return -EIO;
}
#endif
LOG_DBG("Init complete");
return 0;
}
static const struct sensor_driver_api tsl2540_driver_api = {
.sample_fetch = tsl2540_sample_fetch,
.channel_get = tsl2540_channel_get,
.attr_set = tsl2540_attr_set,
#ifdef CONFIG_TSL2540_TRIGGER
.trigger_set = tsl2540_trigger_set,
#endif
};
#ifdef CONFIG_PM_DEVICE
static int tsl2540_pm_action(const struct device *dev, enum pm_device_action action)
{
const struct tsl2540_config *cfg = dev->config;
struct tsl2540_data *data = dev->data;
int ret = 0;
switch (action) {
case PM_DEVICE_ACTION_RESUME:
ret = i2c_reg_update_byte_dt(&cfg->i2c_spec, TSL2540_ENABLE_ADDR,
TSL2540_ENABLE_MASK, data->enable_mode);
break;
case PM_DEVICE_ACTION_SUSPEND:
ret = i2c_reg_update_byte_dt(&cfg->i2c_spec, TSL2540_ENABLE_ADDR,
TSL2540_ENABLE_MASK, TSL2540_ENABLE_DISABLE);
break;
default:
return -ENOTSUP;
}
return ret;
}
#endif
#define TSL2540_GLASS_ATTEN(inst) \
.glass_attenuation = DT_INST_PROP(inst, glass_attenuation), \
.glass_ir_attenuation = DT_INST_PROP(inst, glass_ir_attenuation), \
#define TSL2540_DEFINE(inst) \
static struct tsl2540_data tsl2540_prv_data_##inst; \
static const struct tsl2540_config tsl2540_config_##inst = { \
.i2c_spec = I2C_DT_SPEC_INST_GET(inst), \
IF_ENABLED(CONFIG_TSL2540_TRIGGER, \
(.int_gpio = GPIO_DT_SPEC_INST_GET(inst, int_gpios),)) \
TSL2540_GLASS_ATTEN(inst) \
}; \
PM_DEVICE_DT_INST_DEFINE(inst, tsl2540_pm_action); \
SENSOR_DEVICE_DT_INST_DEFINE(inst, &tsl2540_init, PM_DEVICE_DT_INST_GET(inst), \
&tsl2540_prv_data_##inst, &tsl2540_config_##inst, POST_KERNEL, \
CONFIG_SENSOR_INIT_PRIORITY, &tsl2540_driver_api);
DT_INST_FOREACH_STATUS_OKAY(TSL2540_DEFINE)