drivers: sensor: Support Hamamatsu Photonics S11059 Color Sensor

DataSheet:
https://datasheetspdf.com/pdf/1323325/Hamamatsu/S11059-02DT/1

Testing Environment:
esp32

Signed-off-by: Hiroki Tada <tada.hiroki@fujitsu.com>
This commit is contained in:
Hiroki Tada 2022-08-09 08:10:07 +00:00 committed by Maureen Helm
parent 36cc74e7e8
commit 943158326c
7 changed files with 363 additions and 0 deletions

View file

@ -130,6 +130,7 @@ add_subdirectory_ifdef(CONFIG_RPI_PICO_TEMP rpi_pico_temp)
add_subdirectory_ifdef(CONFIG_XMC4XXX_TEMP xmc4xxx_temp)
add_subdirectory_ifdef(CONFIG_TMD2620 tmd2620)
add_subdirectory_ifdef(CONFIG_ZEPHYR_NTC_THERMISTOR zephyr_thermistor)
add_subdirectory_ifdef(CONFIG_S11059 s11059)
if(CONFIG_USERSPACE OR CONFIG_SENSOR_SHELL OR CONFIG_SENSOR_SHELL_BATTERY)
# The above if() is needed or else CMake would complain about

View file

@ -299,4 +299,6 @@ source "drivers/sensor/tmd2620/Kconfig"
source "drivers/sensor/zephyr_thermistor/Kconfig"
source "drivers/sensor/s11059/Kconfig"
endif # SENSOR

View file

@ -0,0 +1,6 @@
# Copyright (c) 2022 Hiroki Tada
# SPDX-License-Identifier: Apache-2.0
zephyr_library()
zephyr_library_sources(s11059.c)

View file

@ -0,0 +1,12 @@
# S11059 color sensor
# Copyright (c) 2022 Hiroki Tada
# SPDX-License-Identifier: Apache-2.0
config S11059
bool "S11059 color sensor"
default y
depends on DT_HAS_HAMAMATSU_S11059_ENABLED
select I2C
help
Enable driver for S11059 color sensor

View file

@ -0,0 +1,308 @@
/*
* Copyright (c) 2022, Hiroki Tada
*
* SPDX-License-Identifier: Apache-2.0
*
* Datasheet:
* https://datasheetspdf.com/pdf/1323325/Hamamatsu/S11059-02DT/1
*/
#define DT_DRV_COMPAT hamamatsu_s11059
#include <zephyr/device.h>
#include <zephyr/devicetree.h>
#include <zephyr/drivers/i2c.h>
#include <zephyr/drivers/sensor.h>
#include <zephyr/logging/log.h>
#include <zephyr/sys/byteorder.h>
#include <zephyr/sys/util.h>
LOG_MODULE_REGISTER(S11059, CONFIG_SENSOR_LOG_LEVEL);
/* register address */
#define S11059_REG_ADDR_CONTROL 0x00
#define S11059_REG_ADDR_MANUAL_TIMING 0x01
#define S11059_REG_ADDR_DATA 0x03
/* control bit */
#define S11059_CONTROL_GAIN 3
#define S11059_CONTROL_STANDBY_MONITOR 5
#define S11059_CONTROL_STADBY 6
#define S11059_CONTROL_ADC_RESET 7
/* bit mask for control */
#define S11059_BIT_MASK_INTEGRATION_TIME 0x03
#define S11059_BIT_MASK_CONTROL_STANDBY_MONITOR 0x20
/* factors for converting sensor samples to Lux */
#define S11059_CONVERT_FACTOR_LOW_RED (112)
#define S11059_CONVERT_FACTOR_LOW_GREEN (83)
#define S11059_CONVERT_FACTOR_LOW_BLUE (44)
#define S11059_CONVERT_FACTOR_LOW_IR (3 * 10)
#define S11059_CONVERT_FACTOR_HIGH_RED (117 * 10)
#define S11059_CONVERT_FACTOR_HIGH_GREEN (85 * 10)
#define S11059_CONVERT_FACTOR_HIGH_BLUE (448)
#define S11059_CONVERT_FACTOR_HIGH_IR (30 * 10)
#define S11059_INTEGRATION_TIME_MODE_00 175
#define S11059_INTEGRATION_TIME_MODE_01 2800
#define S11059_INTEGRATION_TIME_MODE_10 44800
#define S11059_INTEGRATION_TIME_MODE_11 358400
#define S11059_WAIT_PER_LOOP 400
#define S11059_INITIAL_CONTROL 0x04
#define S11059_MAX_MANUAL_TIMING UINT16_MAX
#define S11059_CARRY_UP 10000
#define S11059_NUM_GAIN_MODE 2
enum s11059_channel {
RED,
GREEN,
BLUE,
IR,
NUM_OF_COLOR_CHANNELS
};
struct s11059_dev_config {
struct i2c_dt_spec bus;
uint8_t gain;
int64_t integration_time; /* integration period (unit: us) */
};
struct s11059_data {
uint16_t samples[NUM_OF_COLOR_CHANNELS];
};
static const uint16_t convert_factors[S11059_NUM_GAIN_MODE][NUM_OF_COLOR_CHANNELS] = {
{S11059_CONVERT_FACTOR_LOW_RED, S11059_CONVERT_FACTOR_LOW_GREEN,
S11059_CONVERT_FACTOR_LOW_BLUE, S11059_CONVERT_FACTOR_LOW_IR},
{S11059_CONVERT_FACTOR_HIGH_RED, S11059_CONVERT_FACTOR_HIGH_GREEN,
S11059_CONVERT_FACTOR_HIGH_BLUE, S11059_CONVERT_FACTOR_HIGH_IR}};
/* Integration timing in Manual integration mode */
static const uint32_t integ_time_factor[] = {
S11059_INTEGRATION_TIME_MODE_00, S11059_INTEGRATION_TIME_MODE_01,
S11059_INTEGRATION_TIME_MODE_10, S11059_INTEGRATION_TIME_MODE_11};
static int s11059_convert_channel_to_index(enum sensor_channel chan)
{
switch (chan) {
case SENSOR_CHAN_RED:
return RED;
case SENSOR_CHAN_GREEN:
return GREEN;
case SENSOR_CHAN_BLUE:
return BLUE;
default:
return IR;
}
}
static int s11059_samples_read(const struct device *dev, uint8_t addr, uint16_t *val, uint32_t size)
{
const struct s11059_dev_config *cfg = dev->config;
int rc;
if (size < NUM_OF_COLOR_CHANNELS * 2) {
return -EINVAL;
}
rc = i2c_burst_read_dt(&cfg->bus, addr, (uint8_t *)val, size);
if (rc < 0) {
return rc;
}
for (size_t i = 0; i < NUM_OF_COLOR_CHANNELS; i++) {
val[i] = sys_be16_to_cpu(val[i]);
}
return 0;
}
static int s11059_control_write(const struct device *dev, uint8_t control)
{
const struct s11059_dev_config *cfg = dev->config;
const uint8_t opcode[] = {S11059_REG_ADDR_CONTROL, control};
return i2c_write_dt(&cfg->bus, opcode, sizeof(opcode));
}
static int s11059_manual_timing_write(const struct device *dev, uint16_t manual_time)
{
const struct s11059_dev_config *cfg = dev->config;
const uint8_t opcode[] = {S11059_REG_ADDR_MANUAL_TIMING, manual_time >> 8,
manual_time & 0xFF};
return i2c_write_dt(&cfg->bus, opcode, sizeof(opcode));
}
static int s11059_start_measurement(const struct device *dev)
{
const struct s11059_dev_config *cfg = dev->config;
uint8_t control;
int rc;
/* read current control */
rc = i2c_reg_read_byte_dt(&cfg->bus, S11059_REG_ADDR_CONTROL, &control);
if (rc < 0) {
LOG_ERR("%s, Failed to read current control.", dev->name);
return rc;
}
/* reset adc block */
WRITE_BIT(control, S11059_CONTROL_ADC_RESET, 1);
WRITE_BIT(control, S11059_CONTROL_STADBY, 0);
rc = s11059_control_write(dev, control);
if (rc < 0) {
LOG_ERR("%s, Failed to reset adc.", dev->name);
return rc;
}
/* start device */
WRITE_BIT(control, S11059_CONTROL_ADC_RESET, 0);
rc = s11059_control_write(dev, control);
if (rc < 0) {
LOG_ERR("%s, Failed to start device.", dev->name);
return rc;
}
return 0;
}
static int s11059_integ_time_calculate(const struct device *dev, uint16_t *manual_time,
uint8_t *mode)
{
const struct s11059_dev_config *cfg = dev->config;
int64_t tmp;
if (cfg->integration_time < integ_time_factor[0]) {
*mode = 0;
*manual_time = 1;
} else {
*manual_time = S11059_MAX_MANUAL_TIMING;
for (uint8_t i = 0; i < ARRAY_SIZE(integ_time_factor); i++) {
*mode = i;
tmp = cfg->integration_time / integ_time_factor[i];
if (tmp < S11059_MAX_MANUAL_TIMING) {
*manual_time = (uint16_t)tmp;
break;
}
}
}
return 0;
}
static int s11059_sample_fetch(const struct device *dev, enum sensor_channel chan)
{
const struct s11059_dev_config *cfg = dev->config;
struct s11059_data *drv_data = dev->data;
uint16_t values[NUM_OF_COLOR_CHANNELS];
uint8_t control;
int rc;
if (chan != SENSOR_CHAN_ALL) {
LOG_ERR("%s, Unsupported sensor channel", dev->name);
return -ENOTSUP;
}
rc = s11059_start_measurement(dev);
if (rc < 0) {
LOG_ERR("%s, Failed to start measurement.", dev->name);
return rc;
}
do {
rc = i2c_reg_read_byte_dt(&cfg->bus, S11059_REG_ADDR_CONTROL, &control);
if (rc < 0) {
LOG_ERR("%s, Failed to read control.", dev->name);
return rc;
}
k_usleep(S11059_WAIT_PER_LOOP);
} while (!(control & S11059_BIT_MASK_CONTROL_STANDBY_MONITOR));
rc = s11059_samples_read(dev, S11059_REG_ADDR_DATA, values, sizeof(values));
if (rc < 0) {
LOG_ERR("%s, Failed to get sample.", dev->name);
return rc;
}
for (size_t i = 0; i < NUM_OF_COLOR_CHANNELS; i++) {
drv_data->samples[i] = values[i];
}
return 0;
}
static int s11059_channel_get(const struct device *dev, enum sensor_channel chan,
struct sensor_value *val)
{
const struct s11059_dev_config *cfg = dev->config;
struct s11059_data *drv_data = dev->data;
const uint8_t index = s11059_convert_channel_to_index(chan);
const uint16_t factor = convert_factors[cfg->gain][index];
uint32_t meas_value;
meas_value = drv_data->samples[index] * S11059_CARRY_UP / factor;
val->val1 = meas_value / (S11059_CARRY_UP / 10);
val->val2 = meas_value % (S11059_CARRY_UP / 10);
return 0;
}
static int s11059_init(const struct device *dev)
{
const struct s11059_dev_config *cfg = dev->config;
uint8_t control = S11059_INITIAL_CONTROL;
uint16_t manual_time;
uint8_t timing_mode;
int rc;
/* device set */
if (!i2c_is_ready_dt(&cfg->bus)) {
LOG_ERR("%s, device is not ready.", dev->name);
return -ENODEV;
}
rc = s11059_integ_time_calculate(dev, &manual_time, &timing_mode);
if (rc < 0) {
LOG_ERR("%s, Failed to calculate manual timing.", dev->name);
return rc;
}
rc = s11059_manual_timing_write(dev, manual_time);
if (rc < 0) {
LOG_ERR("%s, Failed to set manual timing.", dev->name);
return rc;
}
/* set integration time mode and gain*/
control |= timing_mode & S11059_BIT_MASK_INTEGRATION_TIME;
WRITE_BIT(control, S11059_CONTROL_GAIN, cfg->gain);
rc = s11059_control_write(dev, control);
if (rc < 0) {
LOG_ERR("%s, Failed to set gain and integration time.", dev->name);
return rc;
}
return 0;
}
static const struct sensor_driver_api s11059_driver_api = {
.sample_fetch = s11059_sample_fetch,
.channel_get = s11059_channel_get,
};
#define S11059_INST(inst) \
static struct s11059_data s11059_data_##inst; \
static const struct s11059_dev_config s11059_config_##inst = { \
.bus = I2C_DT_SPEC_INST_GET(inst), \
.gain = DT_INST_PROP(inst, high_gain), \
.integration_time = DT_INST_PROP(inst, integration_time)}; \
SENSOR_DEVICE_DT_INST_DEFINE(inst, s11059_init, NULL, &s11059_data_##inst, \
&s11059_config_##inst, POST_KERNEL, \
CONFIG_SENSOR_INIT_PRIORITY, &s11059_driver_api);
DT_INST_FOREACH_STATUS_OKAY(S11059_INST)

View file

@ -0,0 +1,28 @@
# Copyright (c) 2022, Hiroki Tada
# SPDX-License-Identifier: Apache-2.0
description: |
Hamamatsu Photonics S11059 Color Sensor. See datasheet at
https://datasheetspdf.com/pdf/1323325/Hamamatsu/S11059-02DT/1
compatible: "hamamatsu,s11059"
include: [sensor-device.yaml, i2c-device.yaml]
properties:
high-gain:
type: boolean
description: |
When present, the high gain is enabled.
The gain ratio is 10 times from low to high.
integration-time:
type: int
default: 546000
description: |
Integration time (unit is us).
By setting this value to the desired integration time,
the Integration time setting and Manual timing register
values are set automatically.
The default value is 546ms. See datasheet, bottom of page 3.

View file

@ -694,3 +694,9 @@ test_i2c_wsen_pads: wsen_pads@6A {
drdy-gpios = <&test_gpio 0 0>;
odr = <1>;
};
test_i2c_s11059: s11059@6b {
compatible = "hamamatsu,s11059";
reg = <0x6b>;
integration-time = <546000>;
};