From 039262484ab349143474d62f4a163f51c5227e50 Mon Sep 17 00:00:00 2001 From: Robert-Ionut Alexa Date: Sat, 8 Oct 2022 11:31:29 +0300 Subject: [PATCH] drivers/sensors: add LTR308 ambient light sensor Add support for the LTR308 ambient light sensor through I2C. Currently, the interrupt-driven mode of operation is not supported. Signed-off-by: Robert-Ionut Alexa --- drivers/sensors/Kconfig | 32 ++ drivers/sensors/Make.defs | 4 + drivers/sensors/ltr308.c | 655 +++++++++++++++++++++++++++++++++ include/nuttx/sensors/ltr308.h | 91 +++++ 4 files changed, 782 insertions(+) create mode 100644 drivers/sensors/ltr308.c create mode 100644 include/nuttx/sensors/ltr308.h diff --git a/drivers/sensors/Kconfig b/drivers/sensors/Kconfig index 2b229964fc..057fca9786 100644 --- a/drivers/sensors/Kconfig +++ b/drivers/sensors/Kconfig @@ -1144,4 +1144,36 @@ config HDC1008_DEBUG Enables debug features for the HDC1008 endif # SENSORS_HDC1008 + +config SENSORS_LTR308 + bool "LTR308 Lite-On ambient light sensor" + default n + select I2C + ---help--- + Enable driver support for the LTR308 Lite-on ambient light sensor. + +if SENSORS_LTR308 + +config SENSORS_LTR308_I2C_FREQUENCY + int "LTR308 I2C frequency" + default 400000 + range 1 400000 + +config SENSORS_LTR308_POLL_INTERVAL + int "Polling interval in microseconds, default 1 sec" + depends on SENSORS_LTR308 + default 1000000 + range 0 4294967295 + ---help--- + The interval until a new sensor measurement will be triggered. + +config SENSORS_LTR308_THREAD_STACKSIZE + int "Worker thread stack size" + depends on SENSORS_LTR308 + default 1024 + ---help--- + The stack size for the worker thread. + +endif # SENSORS_LTR308 + endif # SENSORS diff --git a/drivers/sensors/Make.defs b/drivers/sensors/Make.defs index 4fb4c3ec45..593f32f783 100644 --- a/drivers/sensors/Make.defs +++ b/drivers/sensors/Make.defs @@ -228,6 +228,10 @@ ifeq ($(CONFIG_SENSORS_T67XX),y) CSRCS += t67xx.c endif +ifeq ($(CONFIG_SENSORS_LTR308),y) + CSRCS += ltr308.c +endif + endif # CONFIG_I2C # These drivers depend on SPI support diff --git a/drivers/sensors/ltr308.c b/drivers/sensors/ltr308.c new file mode 100644 index 0000000000..2e203a5976 --- /dev/null +++ b/drivers/sensors/ltr308.c @@ -0,0 +1,655 @@ +/**************************************************************************** + * drivers/sensors/ltr308.c + * Character driver for the LTR-308ALS-01 Lite-On ambient light sensor. + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#if defined(CONFIG_I2C) && defined(CONFIG_SENSORS_LTR308) + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +#define LTR308_ADDR 0x53 +#define DEVID 0xB1 + +#define LTR308_CTRL 0x00 +#define LTR308_MEAS_RATE 0x04 +#define LTR308_ALS_GAIN 0x05 +#define LTR308_PART_ID 0x06 +#define LTR308_STATUS 0x07 +#define LTR308_DATA_0 0x0D + +/**************************************************************************** + * Private Type Definitions + ****************************************************************************/ + +struct ltr308_sensor_s +{ + struct sensor_lowerhalf_s lower; /* Common lower interface */ + uint8_t integration_time; /* The interval between 2 ALS cycles */ + uint8_t measurement_rate; /* The interval between 2 data updates */ + uint8_t gain; /* Sensor gain */ +}; + +struct ltr308_dev_s +{ + struct ltr308_sensor_s dev; /* Sensor private data */ + FAR struct i2c_master_s *i2c; /* I2C interface */ + mutex_t dev_lock; /* Manages exclusive access to the device */ + sem_t run; /* Locks sensor thread */ + bool enabled; /* Enable/Disable LTR308 */ +}; + +/**************************************************************************** + * Private Function Prototypes + ****************************************************************************/ + +static int ltr308_activate(FAR struct sensor_lowerhalf_s *lower, + FAR struct file *filep, bool enabled); +static int ltr308_calibrate(FAR struct sensor_lowerhalf_s *lower, + FAR struct file *filep, + unsigned long arg); + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +static const struct sensor_ops_s g_sensor_ops = +{ + NULL, /* open */ + NULL, /* close */ + ltr308_activate, /* activate */ + NULL, /* set_interval */ + NULL, /* batch */ + NULL, /* fetch */ + NULL, /* selftest */ + NULL, /* set_calibvalue */ + ltr308_calibrate, /* calibrate */ + NULL /* control */ +}; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: ltr308_set_reg8 + * + * Description: + * Write to an 8-bit LTR308 register + * + * Return value: + * Zero (OK) on success; a negated errno value on failure + ****************************************************************************/ + +static int ltr308_set_reg8(FAR struct ltr308_dev_s *priv, uint8_t regaddr, + uint8_t regval) +{ + struct i2c_msg_s msg; + uint8_t txbuffer[2]; + int ret; + + txbuffer[0] = regaddr; + txbuffer[1] = regval; + + msg.frequency = CONFIG_SENSORS_LTR308_I2C_FREQUENCY; + msg.addr = LTR308_ADDR; + msg.flags = 0; + msg.buffer = txbuffer; + msg.length = 2; + + ret = I2C_TRANSFER(priv->i2c, &msg, 1); + if (ret < 0) + { + snerr("I2C_TRANSFER failed (err = %d)\n", ret); + } + + return ret; +} + +/**************************************************************************** + * Name: ltr308_get_reg8 + * + * Description: + * Read from an 8-bit LTR308 register + * + * Return value: + * Zero (OK) on success; a negated errno value on failure + ****************************************************************************/ + +static int ltr308_get_reg8(FAR struct ltr308_dev_s *priv, uint8_t regaddr, + FAR uint8_t *regval) +{ + struct i2c_msg_s msg[2]; + int ret; + + msg[0].frequency = CONFIG_SENSORS_LTR308_I2C_FREQUENCY; + msg[0].addr = LTR308_ADDR; + msg[0].flags = 0; + msg[0].buffer = ®addr; + msg[0].length = 1; + + msg[1].frequency = CONFIG_SENSORS_LTR308_I2C_FREQUENCY; + msg[1].addr = LTR308_ADDR; + msg[1].flags = I2C_M_READ; + msg[1].buffer = regval; + msg[1].length = 1; + + ret = I2C_TRANSFER(priv->i2c, msg, 2); + if (ret < 0) + { + snerr("ERROR: I2C_TRANSFER failed (err = %d)\n", ret); + } + + return ret; +} + +/**************************************************************************** + * Name: ltr308_get_reg24 + * + * Description: + * Read from 3 8-bit LTR308 registers + * + * Return value: + * Zero (OK) on success; a negated errno value on failure + ****************************************************************************/ + +static int ltr308_get_reg24(FAR struct ltr308_dev_s *priv, uint8_t regaddr, + FAR uint32_t *val) +{ + int ret; + int i; + + *val = 0; + for (i = 0; i < 3; i++, regaddr++) + { + ret = ltr308_get_reg8(priv, regaddr, ((uint8_t *)val) + i); + if (ret < 0) + { + return ret; + } + } + + return ret; +} + +/**************************************************************************** + * Name: ltr308_checkid + * + * Description: + * Read and verify the LTR308 chip ID + * + * Return value: + * Zero (OK) on success; a negated errno value on failure + ****************************************************************************/ + +static int ltr308_checkid(FAR struct ltr308_dev_s *priv) +{ + uint8_t devid; + int ret; + + /* Read device ID */ + + ret = ltr308_get_reg8(priv, LTR308_PART_ID, &devid); + if (ret < 0) + { + return ret; + } + + sninfo("devid: 0x%02x\n", devid); + + return (devid != DEVID) ? -ENODEV : OK; +} + +/**************************************************************************** + * Name: ltr308_get_status + * + * Description: + * Get the status information of LTR308 + * + * Return value: + * Zero (OK) on success; a negated errno value on failure + ****************************************************************************/ + +static int ltr308_get_status(FAR struct ltr308_dev_s *priv, + FAR bool *power_on, + FAR bool *interrupt_pending, + FAR bool *data_pending) +{ + uint8_t status; + uint8_t ret; + + ret = ltr308_get_reg8(priv, LTR308_STATUS, &status); + if (ret < 0) + { + return ret; + } + + if (power_on != NULL) + { + *power_on = status & 0x20; + } + + if (interrupt_pending != NULL) + { + *interrupt_pending = status & 0x10; + } + + if (data_pending != NULL) + { + *data_pending = status & 0x08; + } + + return ret; +} + +/**************************************************************************** + * Name: ltr308_get_lux + * + * Description: + * Convert raw data to lux + * @gain: see ltr308_calibrate() + * @integration_time: see ltr308_calibrate() + * @ch: result from ltr308_get_data() + * + * Return value: + * Zero (OK) on success; a negated errno value on failure + ****************************************************************************/ + +static int ltr308_get_lux(FAR struct ltr308_dev_s *priv, uint8_t gain, + uint8_t integration_time, uint32_t data, + FAR float *lux) +{ + float d0; + + /* Determine if sensor is saturated. If so, abandon ship (calculation will + * not be accurate) + */ + + if (data == 0x000fffff) + { + *lux = 0.0; + return -ENODATA; + } + + d0 = ((float)data * 0.6); + switch (gain) + { + case 0: + break; + + case 1: + d0 /= 3; + break; + + case 2: + d0 /= 6; + break; + + case 3: + d0 /= 9; + break; + + case 4: + d0 /= 18; + break; + + default: + d0 = 0.0; + break; + } + + switch (integration_time) + { + case 0: + *lux = d0 / 4; + break; + + case 1: + *lux = d0 / 2; + break; + + case 2: + *lux = d0; + break; + + case 3: + *lux = d0 * 2; + break; + + case 4: + *lux = d0 * 4; + break; + + default: + *lux = 0.0; + break; + } + + return 0; +} + +/**************************************************************************** + * Name: ltr308_activate + * + * Description: + * Enable/Disable LTR308 sensor + * + * Return value: + * Zero (OK) on success; a negated errno value on failure + ****************************************************************************/ + +static int ltr308_activate(FAR struct sensor_lowerhalf_s *lower, + FAR struct file *filep, bool enabled) +{ + FAR struct ltr308_sensor_s *dev = container_of(lower, + FAR struct ltr308_sensor_s, + lower); + FAR struct ltr308_dev_s *priv = container_of(dev, + FAR struct ltr308_dev_s, + dev); + int ret; + + ret = ltr308_set_reg8(priv, LTR308_CTRL, enabled << 1); + if (ret < 0) + { + snerr("ERROR: Could not enable sensor\n"); + return ret; + } + + priv->enabled = enabled; + if (enabled == true) + { + /* Wake up the polling thread */ + + nxsem_post(&priv->run); + } + + return OK; +} + +/**************************************************************************** + * Name: ltr308_calibrate + * + * Description: + * Set the integration time, measurement rate and gain of LTR308 + * @integration_time - the measurement time for each ALS cycle + * @measurement_rate - the interval between DATA_REGISTERS update. Must be + * set to be equal or greater than integration time + * @gain - the sensor gain + * + * Return value: + * Zero (OK) on success; a negated errno value on failure + ****************************************************************************/ + +static int ltr308_calibrate(FAR struct sensor_lowerhalf_s *lower, + FAR struct file *filep, + unsigned long arg) +{ + FAR struct ltr308_sensor_s *dev = container_of(lower, + FAR struct ltr308_sensor_s, + lower); + FAR struct ltr308_dev_s *priv = container_of(dev, + FAR struct ltr308_dev_s, + dev); + FAR struct ltr308_calibval *calibval = + (FAR struct ltr308_calibval *) arg; + uint8_t measurement = 0; + int ret; + + /* Sanity checks */ + + if (calibval->integration_time >= 5) + { + calibval->integration_time = 0; + } + + if (calibval->measurement_rate >= 6 || calibval->measurement_rate == 4) + { + calibval->measurement_rate = 0; + } + + if (calibval->gain >= 5) + { + calibval->gain = 0; + } + + /* Do not overwrite if the values have not been modified */ + + nxmutex_lock(&priv->dev_lock); + + if (dev->integration_time != calibval->integration_time || + dev->measurement_rate != calibval->measurement_rate) + { + dev->integration_time = calibval->integration_time; + dev->measurement_rate = calibval->measurement_rate; + + measurement |= calibval->integration_time << 4; + measurement |= calibval->measurement_rate; + + ret = ltr308_set_reg8(priv, LTR308_MEAS_RATE, measurement); + if (ret < 0) + { + goto err_out; + } + } + + if (dev->gain != calibval->gain) + { + dev->gain = calibval->gain; + + ret = ltr308_set_reg8(priv, LTR308_ALS_GAIN, calibval->gain); + if (ret < 0) + { + goto err_out; + } + } + + nxmutex_unlock(&priv->dev_lock); + return OK; + +err_out: + nxmutex_unlock(&priv->dev_lock); + snerr("ERROR: Failed to calibrate sensor\n"); + return ret; +} + +/**************************************************************************** + * Name: ltr308_thread + * + * Description: + * Thread for performing data readings at fixed intervals, defined by + * CONFIG_SENSORS_LTR308_POLL_INTERVAL + * @argc - Number of arguments + * @argv - Pointer to argument list + * + * Return value: + * Zero (OK) on success; a negated errno value on failure + ****************************************************************************/ + +static int ltr308_thread(int argc, char** argv) +{ + FAR struct ltr308_dev_s *priv = (FAR struct ltr308_dev_s *) + ((uintptr_t)strtoul(argv[1], NULL, 0)); + struct sensor_light light; + uint32_t data = 0; + bool data_pending; + float lux; + int ret; + + while (true) + { + if (priv->enabled == false) + { + /* Wait for the sensor to be enabled */ + + nxsem_wait(&priv->run); + } + + /* Read the data registers and sleep if no change is detected + * or the sensor is saturated + */ + + ret = ltr308_get_status(priv, NULL, NULL, &data_pending); + if (ret < 0) + { + snerr("ERROR: Failed to read sensor's status\n"); + return ret; + } + + if (data_pending == false) + { + goto thread_sleep; + } + + ret = ltr308_get_reg24(priv, LTR308_DATA_0, &data); + if (ret < 0) + { + snerr("ERROR: Failed to read data from sensor\n"); + return ret; + } + + ret = ltr308_get_lux(priv, priv->dev.gain, priv->dev.integration_time, + data, &lux); + if (ret < 0) + { + goto thread_sleep; + } + + light.timestamp = sensor_get_timestamp(); + light.light = lux; + priv->dev.lower.push_event(priv->dev.lower.priv, &light, + sizeof(struct sensor_light)); + +thread_sleep: + nxsig_usleep(CONFIG_SENSORS_LTR308_POLL_INTERVAL); + } + + return OK; +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: ltr308_register + * + * Description: + * Register the LTR308 character device + * @devno - The user-specified device number, starting from 0 + * @i2c - An instance of the I2C interface to use to communicate with + * LTR308 + * + * Return value: + * Zero (OK) on success; a negated errno value on failure + ****************************************************************************/ + +int ltr308_register(int devno, FAR struct i2c_master_s *i2c) +{ + FAR struct sensor_lowerhalf_s *lower; + FAR struct ltr308_dev_s *priv; + FAR char *argv[2]; + char arg1[32]; + int ret = OK; + + DEBUGASSERT(i2c != NULL); + + /* Initialize the LTR308 device structure */ + + priv = (FAR struct ltr308_dev_s *)kmm_zalloc(sizeof(struct ltr308_dev_s)); + if (priv == NULL) + { + snerr("ERROR: Failed to allocate instance (err = %d)\n", ret); + return -ENOMEM; + } + + priv->i2c = i2c; + priv->enabled = false; + nxmutex_init(&priv->dev_lock); + nxsem_init(&priv->run, 0, 0); + + /* Check Device ID */ + + ret = ltr308_checkid(priv); + if (ret < 0) + { + snerr("ERROR: Wrong device ID!\n"); + goto err_init; + } + + /* Register the character driver */ + + lower = &priv->dev.lower; + lower->ops = &g_sensor_ops; + lower->type = SENSOR_TYPE_LIGHT; + + ret = sensor_register(lower, 0); + if (ret < 0) + { + snerr("ERROR: Failed to register driver (err = %d)\n", ret); + goto err_init; + } + + /* Create thread for polling sensor data */ + + snprintf(arg1, 16, "0x%" PRIxPTR, (uintptr_t)priv); + argv[0] = arg1; + argv[1] = NULL; + ret = kthread_create("ltr308_thread", SCHED_PRIORITY_DEFAULT, + CONFIG_SENSORS_LTR308_THREAD_STACKSIZE, + ltr308_thread, argv); + if (ret < 0) + { + snerr("ERROR: Failed to create poll thread (err = %d)\n", ret); + goto err_register; + } + + sninfo("LTR308 driver loaded successfully!\n"); + return OK; + +err_register: + sensor_unregister(lower, 0); +err_init: + nxsem_destroy(&priv->run); + nxmutex_destroy(&priv->dev_lock); + kmm_free(priv); + return ret; +} + +#endif /* CONFIG_I2C && CONFIG_SENSORS_LTR308 */ diff --git a/include/nuttx/sensors/ltr308.h b/include/nuttx/sensors/ltr308.h new file mode 100644 index 0000000000..d02b028123 --- /dev/null +++ b/include/nuttx/sensors/ltr308.h @@ -0,0 +1,91 @@ +/**************************************************************************** + * include/nuttx/sensors/ltr308.h + * Character driver for the LTR-308ALS-01 Lite-On ambient light sensor. + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +#ifndef __INCLUDE_NUTTX_SENSORS_LTR308_H +#define __INCLUDE_NUTTX_SENSORS_LTR308_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include + +/* Configuration ************************************************************ + * Prerequisites: + * + * CONFIG_I2C + * Enables support for I2C drivers + * CONFIG_SENSORS_LTR308 + * Enables support for the LTR308 driver + */ + +#if defined(CONFIG_I2C) && defined(CONFIG_SENSORS_LTR308) + +/**************************************************************************** + * Public Types + ****************************************************************************/ + +struct i2c_master_s; + +struct ltr308_calibval +{ + uint8_t integration_time; + uint8_t measurement_rate; + uint8_t gain; +}; + +/**************************************************************************** + * Public Function Prototypes + ****************************************************************************/ + +#ifdef __cplusplus +#define EXTERN extern "C" +extern "C" +{ +#else +#define EXTERN extern +#endif + +/**************************************************************************** + * Name: ltr308_register + * + * Description: + * Register the LTR308 character device as 'devpath'. + * + * Input Parameters: + * devno - Number of device (i.e. ltr0, ltr1, ...) + * i2c - An I2C driver instance. + * + * Returned Value: + * Zero (OK) on success; a negated errno value on failure. + * + ****************************************************************************/ + +int ltr308_register(int devno, FAR struct i2c_master_s *i2c); + +#undef EXTERN +#ifdef __cplusplus +} +#endif + +#endif /* CONFIG_I2C && CONFIG_SENSORS_LTR308 */ +#endif /* __INCLUDE_NUTTX_SENSORS_LTR308_H */