374 lines
9.8 KiB
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)
|