drivers: sensor: Add support for Broadcom APDS-9306
- Add Broadcom / Avago APDS-9306 ambient light sensor driver Signed-off-by: Daniel Kampert <DanielKampert@kampis-elektroecke.de>
This commit is contained in:
parent
5d3d78e9cb
commit
9d0486e3ee
7 changed files with 453 additions and 0 deletions
|
|
@ -35,6 +35,7 @@ add_subdirectory_ifdef(CONFIG_AMD_SB_TSI amd_sb_tsi)
|
|||
add_subdirectory_ifdef(CONFIG_AMG88XX amg88xx)
|
||||
add_subdirectory_ifdef(CONFIG_APDS9253 apds9253)
|
||||
add_subdirectory_ifdef(CONFIG_APDS9960 apds9960)
|
||||
add_subdirectory_ifdef(CONFIG_APDS9306 apds9306)
|
||||
add_subdirectory_ifdef(CONFIG_CURRENT_AMP current_amp)
|
||||
add_subdirectory_ifdef(CONFIG_ENS160 ens160)
|
||||
add_subdirectory_ifdef(CONFIG_EXPLORIR_M explorir_m)
|
||||
|
|
|
|||
|
|
@ -119,6 +119,7 @@ source "drivers/sensor/amd_sb_tsi/Kconfig"
|
|||
source "drivers/sensor/amg88xx/Kconfig"
|
||||
source "drivers/sensor/apds9253/Kconfig"
|
||||
source "drivers/sensor/apds9960/Kconfig"
|
||||
source "drivers/sensor/apds9306/Kconfig"
|
||||
source "drivers/sensor/current_amp/Kconfig"
|
||||
source "drivers/sensor/ens160/Kconfig"
|
||||
source "drivers/sensor/explorir_m/Kconfig"
|
||||
|
|
|
|||
6
drivers/sensor/apds9306/CMakeLists.txt
Normal file
6
drivers/sensor/apds9306/CMakeLists.txt
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
# SPDX-License-Identifier: Apache-2.0
|
||||
# Copyright (c) 2024 Daniel Kampert
|
||||
# Author: Daniel Kampert <DanielKampert@Kampis-Elektroecke.de>
|
||||
|
||||
zephyr_library()
|
||||
zephyr_library_sources(apds9306.c)
|
||||
11
drivers/sensor/apds9306/Kconfig
Normal file
11
drivers/sensor/apds9306/Kconfig
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
# SPDX-License-Identifier: Apache-2.0
|
||||
# Copyright (c) 2024 Daniel Kampert
|
||||
# Author: Daniel Kampert <DanielKampert@Kampis-Elektroecke.de>
|
||||
|
||||
config APDS9306
|
||||
bool "APDS9306 Sensor"
|
||||
default y
|
||||
depends on DT_HAS_AVAGO_APDS9306_ENABLED
|
||||
select I2C
|
||||
help
|
||||
Enable the driver for the APDS9306 digital light sensor.
|
||||
371
drivers/sensor/apds9306/apds9306.c
Normal file
371
drivers/sensor/apds9306/apds9306.c
Normal file
|
|
@ -0,0 +1,371 @@
|
|||
/* Copyright (c) 2024 Daniel Kampert
|
||||
* Author: Daniel Kampert <DanielKampert@kampis-Elektroecke.de>
|
||||
*/
|
||||
|
||||
#include <zephyr/device.h>
|
||||
#include <zephyr/devicetree.h>
|
||||
#include <zephyr/drivers/i2c.h>
|
||||
#include <zephyr/drivers/sensor.h>
|
||||
#include <zephyr/pm/device_runtime.h>
|
||||
#include <zephyr/logging/log.h>
|
||||
#include <zephyr/sys/byteorder.h>
|
||||
#include <zephyr/sys/util.h>
|
||||
|
||||
#define APDS9306_REGISTER_MAIN_CTRL 0x00
|
||||
#define APDS9306_REGISTER_ALS_MEAS_RATE 0x04
|
||||
#define APDS9306_REGISTER_ALS_GAIN 0x05
|
||||
#define APDS9306_REGISTER_PART_ID 0x06
|
||||
#define APDS9306_REGISTER_MAIN_STATUS 0x07
|
||||
#define APDS9306_REGISTER_CLEAR_DATA_0 0x0A
|
||||
#define APDS9306_REGISTER_CLEAR_DATA_1 0x0B
|
||||
#define APDS9306_REGISTER_CLEAR_DATA_2 0x0C
|
||||
#define APDS9306_REGISTER_ALS_DATA_0 0x0D
|
||||
#define APDS9306_REGISTER_ALS_DATA_1 0x0E
|
||||
#define APDS9306_REGISTER_ALS_DATA_2 0x0F
|
||||
#define APDS9306_REGISTER_INT_CFG 0x19
|
||||
#define APDS9306_REGISTER_INT_PERSISTENCE 0x1A
|
||||
#define APDS9306_REGISTER_ALS_THRES_UP_0 0x21
|
||||
#define APDS9306_REGISTER_ALS_THRES_UP_1 0x22
|
||||
#define APDS9306_REGISTER_ALS_THRES_UP_2 0x23
|
||||
#define APDS9306_REGISTER_ALS_THRES_LOW_0 0x24
|
||||
#define APDS9306_REGISTER_ALS_THRES_LOW_1 0x25
|
||||
#define APDS9306_REGISTER_ALS_THRES_LOW_2 0x26
|
||||
#define APDS9306_REGISTER_ALS_THRES_VAR 0x27
|
||||
|
||||
#define ADPS9306_BIT_ALS_EN BIT(0x01)
|
||||
#define ADPS9306_BIT_ALS_DATA_STATUS BIT(0x03)
|
||||
#define APDS9306_BIT_SW_RESET BIT(0x04)
|
||||
#define ADPS9306_BIT_ALS_INTERRUPT_STATUS BIT(0x03)
|
||||
#define APDS9306_BIT_POWER_ON_STATUS BIT(0x05)
|
||||
|
||||
#define APDS_9306_065_CHIP_ID 0xB3
|
||||
#define APDS_9306_CHIP_ID 0xB1
|
||||
|
||||
#define DT_DRV_COMPAT avago_apds9306
|
||||
|
||||
LOG_MODULE_REGISTER(avago_apds9306, CONFIG_SENSOR_LOG_LEVEL);
|
||||
|
||||
struct apds9306_data {
|
||||
uint32_t light;
|
||||
};
|
||||
|
||||
struct apds9306_config {
|
||||
struct i2c_dt_spec i2c;
|
||||
uint8_t resolution;
|
||||
uint16_t frequency;
|
||||
uint8_t gain;
|
||||
};
|
||||
|
||||
struct apds9306_worker_item_t {
|
||||
struct k_work_delayable dwork;
|
||||
const struct device *dev;
|
||||
} apds9306_worker_item;
|
||||
|
||||
static uint32_t apds9306_get_time_for_resolution(uint8_t value)
|
||||
{
|
||||
switch (value) {
|
||||
case 0:
|
||||
return 400;
|
||||
case 1:
|
||||
return 200;
|
||||
case 2:
|
||||
return 100;
|
||||
case 3:
|
||||
return 50;
|
||||
case 4:
|
||||
return 25;
|
||||
case 5:
|
||||
return 4;
|
||||
default:
|
||||
return 100;
|
||||
}
|
||||
}
|
||||
|
||||
static int apds9306_enable(const struct device *dev)
|
||||
{
|
||||
const struct apds9306_config *config = dev->config;
|
||||
|
||||
return i2c_reg_update_byte_dt(&config->i2c, APDS9306_REGISTER_MAIN_CTRL,
|
||||
ADPS9306_BIT_ALS_EN, ADPS9306_BIT_ALS_EN);
|
||||
}
|
||||
|
||||
static int apds9306_standby(const struct device *dev)
|
||||
{
|
||||
const struct apds9306_config *config = dev->config;
|
||||
|
||||
return i2c_reg_update_byte_dt(&config->i2c, APDS9306_REGISTER_MAIN_CTRL,
|
||||
ADPS9306_BIT_ALS_EN, 0x00);
|
||||
}
|
||||
|
||||
static void apds9306_worker(struct k_work *p_work)
|
||||
{
|
||||
uint8_t buffer[3];
|
||||
uint8_t reg;
|
||||
struct k_work_delayable *dwork = k_work_delayable_from_work(p_work);
|
||||
struct apds9306_worker_item_t *item =
|
||||
CONTAINER_OF(dwork, struct apds9306_worker_item_t, dwork);
|
||||
struct apds9306_data *data = item->dev->data;
|
||||
const struct apds9306_config *config = item->dev->config;
|
||||
|
||||
if (i2c_reg_read_byte_dt(&config->i2c, APDS9306_REGISTER_MAIN_STATUS, &buffer[0])) {
|
||||
LOG_ERR("Failed to read ALS status!");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!(buffer[0] & ADPS9306_BIT_ALS_DATA_STATUS)) {
|
||||
LOG_DBG("No data ready!");
|
||||
return;
|
||||
}
|
||||
|
||||
if (apds9306_standby(item->dev) != 0) {
|
||||
LOG_ERR("Can not disable ALS!");
|
||||
return;
|
||||
}
|
||||
|
||||
reg = APDS9306_REGISTER_ALS_DATA_0;
|
||||
if (i2c_write_read_dt(&config->i2c, ®, sizeof(reg), &buffer, sizeof(buffer)) < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
data->light = sys_get_le24(buffer);
|
||||
|
||||
LOG_DBG("Last measurement: %u", data->light);
|
||||
}
|
||||
|
||||
static int apds9306_attr_set(const struct device *dev, enum sensor_channel channel,
|
||||
enum sensor_attribute attribute, const struct sensor_value *value)
|
||||
{
|
||||
uint8_t reg;
|
||||
uint8_t mask;
|
||||
uint8_t temp;
|
||||
const struct apds9306_config *config = dev->config;
|
||||
|
||||
if (channel != SENSOR_CHAN_LIGHT) {
|
||||
return -ENOTSUP;
|
||||
}
|
||||
|
||||
if (attribute == SENSOR_ATTR_SAMPLING_FREQUENCY) {
|
||||
reg = APDS9306_REGISTER_ALS_MEAS_RATE;
|
||||
mask = GENMASK(2, 0);
|
||||
temp = FIELD_PREP(0x07, value->val1);
|
||||
} else if (attribute == SENSOR_ATTR_GAIN) {
|
||||
reg = APDS9306_REGISTER_ALS_GAIN;
|
||||
mask = GENMASK(2, 0);
|
||||
temp = FIELD_PREP(0x07, value->val1);
|
||||
} else if (attribute == SENSOR_ATTR_RESOLUTION) {
|
||||
reg = APDS9306_REGISTER_ALS_MEAS_RATE;
|
||||
mask = GENMASK(7, 4);
|
||||
temp = FIELD_PREP(0x07, value->val1) << 0x04;
|
||||
} else {
|
||||
return -ENOTSUP;
|
||||
}
|
||||
|
||||
if (i2c_reg_update_byte_dt(&config->i2c, reg, mask, temp)) {
|
||||
LOG_ERR("Failed to set sensor attribute!");
|
||||
return -EFAULT;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int apds9306_attr_get(const struct device *dev, enum sensor_channel channel,
|
||||
enum sensor_attribute attribute, struct sensor_value *value)
|
||||
{
|
||||
uint8_t mask;
|
||||
uint8_t temp;
|
||||
uint8_t reg;
|
||||
const struct apds9306_config *config = dev->config;
|
||||
|
||||
if (channel != SENSOR_CHAN_LIGHT) {
|
||||
return -ENOTSUP;
|
||||
}
|
||||
|
||||
if (attribute == SENSOR_ATTR_SAMPLING_FREQUENCY) {
|
||||
reg = APDS9306_REGISTER_ALS_MEAS_RATE;
|
||||
mask = 0x00;
|
||||
} else if (attribute == SENSOR_ATTR_GAIN) {
|
||||
reg = APDS9306_REGISTER_ALS_GAIN;
|
||||
mask = 0x00;
|
||||
} else if (attribute == SENSOR_ATTR_RESOLUTION) {
|
||||
reg = APDS9306_REGISTER_ALS_MEAS_RATE;
|
||||
mask = 0x04;
|
||||
} else {
|
||||
return -ENOTSUP;
|
||||
}
|
||||
|
||||
if (i2c_reg_read_byte_dt(&config->i2c, reg, &temp)) {
|
||||
LOG_ERR("Failed to read sensor attribute!");
|
||||
return -EFAULT;
|
||||
}
|
||||
|
||||
value->val1 = (temp >> mask) & 0x07;
|
||||
value->val2 = 0;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int apds9306_sample_fetch(const struct device *dev, enum sensor_channel channel)
|
||||
{
|
||||
uint8_t buffer;
|
||||
uint8_t resolution;
|
||||
uint16_t delay;
|
||||
const struct apds9306_config *config = dev->config;
|
||||
|
||||
if ((channel != SENSOR_CHAN_ALL) && (channel != SENSOR_CHAN_LIGHT)) {
|
||||
return -ENOTSUP;
|
||||
}
|
||||
|
||||
LOG_DBG("Start a new measurement...");
|
||||
if (apds9306_enable(dev) != 0) {
|
||||
LOG_ERR("Can not enable ALS!");
|
||||
return -EFAULT;
|
||||
}
|
||||
|
||||
/* Get the measurement resolution. */
|
||||
if (i2c_reg_read_byte_dt(&config->i2c, APDS9306_REGISTER_ALS_MEAS_RATE, &buffer)) {
|
||||
LOG_ERR("Failed reading resolution");
|
||||
return -EFAULT;
|
||||
}
|
||||
|
||||
/* Convert the resolution into a delay time and wait for the result. */
|
||||
resolution = (buffer >> 4) & 0x07;
|
||||
delay = apds9306_get_time_for_resolution(resolution);
|
||||
LOG_DBG("Measurement resolution: %u", resolution);
|
||||
LOG_DBG("Wait for %u ms", delay);
|
||||
|
||||
/* We add a bit more delay to cover the startup time etc. */
|
||||
if (!k_work_delayable_is_pending(&apds9306_worker_item.dwork)) {
|
||||
LOG_DBG("Schedule new work");
|
||||
|
||||
apds9306_worker_item.dev = dev;
|
||||
k_work_init_delayable(&apds9306_worker_item.dwork, apds9306_worker);
|
||||
k_work_schedule(&apds9306_worker_item.dwork, K_MSEC(delay + 100));
|
||||
} else {
|
||||
LOG_DBG("Work pending. Wait for completion.");
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int apds9306_channel_get(const struct device *dev, enum sensor_channel channel,
|
||||
struct sensor_value *value)
|
||||
{
|
||||
struct apds9306_data *data = dev->data;
|
||||
|
||||
if (channel != SENSOR_CHAN_LIGHT) {
|
||||
return -ENOTSUP;
|
||||
}
|
||||
|
||||
value->val1 = data->light;
|
||||
value->val2 = 0;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int apds9306_sensor_setup(const struct device *dev)
|
||||
{
|
||||
uint32_t now;
|
||||
uint8_t temp;
|
||||
const struct apds9306_config *config = dev->config;
|
||||
|
||||
/* Wait for the device to become ready after a possible power cycle. */
|
||||
now = k_uptime_get_32();
|
||||
do {
|
||||
i2c_reg_read_byte_dt(&config->i2c, APDS9306_REGISTER_MAIN_STATUS, &temp);
|
||||
|
||||
/* We wait 100 ms maximum for the device to become ready. */
|
||||
if ((k_uptime_get_32() - now) > 100) {
|
||||
LOG_ERR("Sensor timeout!");
|
||||
return -EFAULT;
|
||||
}
|
||||
|
||||
k_msleep(10);
|
||||
} while (temp & APDS9306_BIT_POWER_ON_STATUS);
|
||||
|
||||
if (i2c_reg_read_byte_dt(&config->i2c, APDS9306_REGISTER_PART_ID, &temp)) {
|
||||
LOG_ERR("Failed reading chip id!");
|
||||
return -EFAULT;
|
||||
}
|
||||
|
||||
if ((temp != APDS_9306_CHIP_ID) && (temp != APDS_9306_065_CHIP_ID)) {
|
||||
LOG_ERR("Invalid chip id! Found 0x%X!", temp);
|
||||
return -EFAULT;
|
||||
}
|
||||
|
||||
if (temp == APDS_9306_CHIP_ID) {
|
||||
LOG_DBG("APDS-9306 found!");
|
||||
} else if (temp == APDS_9306_065_CHIP_ID) {
|
||||
LOG_DBG("APDS-9306-065 found!");
|
||||
}
|
||||
|
||||
/* Reset the sensor. */
|
||||
if (i2c_reg_write_byte_dt(&config->i2c, APDS9306_REGISTER_MAIN_CTRL,
|
||||
APDS9306_BIT_SW_RESET)) {
|
||||
LOG_ERR("Can not reset the sensor!");
|
||||
return -EFAULT;
|
||||
}
|
||||
k_msleep(10);
|
||||
|
||||
/* Perform a dummy read to avoid bus errors after the reset. See */
|
||||
/* https://lore.kernel.org/lkml/ab1d9746-4d23-efcc-0ee1-d2b8c634becd@tweaklogic.com/ */
|
||||
i2c_reg_read_byte_dt(&config->i2c, APDS9306_REGISTER_PART_ID, &temp);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int apds9306_init(const struct device *dev)
|
||||
{
|
||||
uint8_t value;
|
||||
const struct apds9306_config *config = dev->config;
|
||||
|
||||
LOG_DBG("Start to initialize APDS9306...");
|
||||
|
||||
if (!i2c_is_ready_dt(&config->i2c)) {
|
||||
LOG_ERR("Bus device is not ready!");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (apds9306_sensor_setup(dev)) {
|
||||
LOG_ERR("Failed to setup device!");
|
||||
return -EFAULT;
|
||||
}
|
||||
|
||||
value = ((config->resolution & 0x07) << 4) | (config->frequency & 0x0F);
|
||||
LOG_DBG("Write configuration 0x%x to register 0x%x", value,
|
||||
APDS9306_REGISTER_ALS_MEAS_RATE);
|
||||
if (i2c_reg_write_byte_dt(&config->i2c, APDS9306_REGISTER_ALS_MEAS_RATE, value)) {
|
||||
return -EFAULT;
|
||||
}
|
||||
|
||||
value = config->gain;
|
||||
LOG_DBG("Write configuration 0x%x to register 0x%x", value, APDS9306_REGISTER_ALS_GAIN);
|
||||
if (i2c_reg_write_byte_dt(&config->i2c, APDS9306_REGISTER_ALS_GAIN, value)) {
|
||||
return -EFAULT;
|
||||
}
|
||||
|
||||
LOG_DBG("APDS9306 initialization successful!");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct sensor_driver_api apds9306_driver_api = {
|
||||
.attr_set = apds9306_attr_set,
|
||||
.attr_get = apds9306_attr_get,
|
||||
.sample_fetch = apds9306_sample_fetch,
|
||||
.channel_get = apds9306_channel_get,
|
||||
};
|
||||
|
||||
#define APDS9306(inst) \
|
||||
static struct apds9306_data apds9306_data_##inst; \
|
||||
static const struct apds9306_config apds9306_config_##inst = { \
|
||||
.i2c = I2C_DT_SPEC_INST_GET(inst), \
|
||||
.resolution = DT_INST_PROP(inst, resolution), \
|
||||
.gain = DT_INST_PROP(inst, gain), \
|
||||
.frequency = DT_INST_PROP(inst, frequency), \
|
||||
}; \
|
||||
\
|
||||
SENSOR_DEVICE_DT_INST_DEFINE(inst, apds9306_init, NULL, &apds9306_data_##inst, \
|
||||
&apds9306_config_##inst, POST_KERNEL, \
|
||||
CONFIG_SENSOR_INIT_PRIORITY, &apds9306_driver_api);
|
||||
|
||||
DT_INST_FOREACH_STATUS_OKAY(APDS9306)
|
||||
54
dts/bindings/sensor/avago,apds9306.yaml
Normal file
54
dts/bindings/sensor/avago,apds9306.yaml
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
# SPDX-License-Identifier: Apache-2.0
|
||||
# Copyright (c) 2024 Daniel Kampert
|
||||
# Author: Daniel Kampert <DanielKampert@Kampis-Elektroecke.de>
|
||||
|
||||
description: APDS9306 miniature Surface-Mount Digital Ambient Light Sensor.
|
||||
|
||||
compatible: "avago,apds9306"
|
||||
|
||||
include:
|
||||
- sensor-device.yaml
|
||||
- i2c-device.yaml
|
||||
|
||||
properties:
|
||||
gain:
|
||||
type: int
|
||||
default: 1
|
||||
enum:
|
||||
- 18
|
||||
- 9
|
||||
- 6
|
||||
- 3
|
||||
- 1
|
||||
description:
|
||||
ALS Gain range.
|
||||
The default corresponds to the reset value of the register field.
|
||||
|
||||
resolution:
|
||||
type: int
|
||||
default: 18
|
||||
enum:
|
||||
- 13
|
||||
- 16
|
||||
- 17
|
||||
- 18
|
||||
- 19
|
||||
- 20
|
||||
description:
|
||||
ALS Resolution / Bit width.
|
||||
The default corresponds to the reset value of the register field.
|
||||
|
||||
frequency:
|
||||
type: int
|
||||
default: 100
|
||||
enum:
|
||||
- 2000
|
||||
- 1000
|
||||
- 500
|
||||
- 200
|
||||
- 100
|
||||
- 50
|
||||
- 25
|
||||
description:
|
||||
ALS Measurement Rate in milliseconds.
|
||||
The default corresponds to the reset value of the register field.
|
||||
|
|
@ -1087,3 +1087,12 @@ test_i2c_tmp1075: tmp1075@98 {
|
|||
consecutive-fault-measurements = <4>;
|
||||
interrupt-mode;
|
||||
};
|
||||
|
||||
apds_9306: apds9306@92 {
|
||||
compatible = "avago,apds9306";
|
||||
reg = <0x92>;
|
||||
status = "okay";
|
||||
gain = <1>;
|
||||
resolution = <13>;
|
||||
frequency = <2000>;
|
||||
};
|
||||
|
|
|
|||
Loading…
Reference in a new issue