zephyr/drivers/led/is31fl3194.c

360 lines
8.4 KiB
C

/*
* Copyright (c) 2024 Arduino SA
*
* SPDX-License-Identifier: Apache-2.0
*/
#define DT_DRV_COMPAT issi_is31fl3194
/**
* @file
* @brief IS31FL3194 LED driver
*
* The IS31FL3194 is a 3-channel LED driver that communicates over I2C.
*/
#include <zephyr/device.h>
#include <zephyr/drivers/i2c.h>
#include <zephyr/drivers/led.h>
#include <zephyr/kernel.h>
#include <zephyr/logging/log.h>
#include <zephyr/dt-bindings/led/led.h>
LOG_MODULE_REGISTER(is31fl3194, CONFIG_LED_LOG_LEVEL);
#define IS31FL3194_PROD_ID_REG 0x00
#define IS31FL3194_CONF_REG 0x01
#define IS31FL3194_CURRENT_REG 0x03
#define IS31FL3194_OUT1_REG 0x10
#define IS31FL3194_OUT2_REG 0x21
#define IS31FL3194_OUT3_REG 0x32
#define IS31FL3194_UPDATE_REG 0x40
#define IS31FL3194_PROD_ID_VAL 0xce
#define IS31FL3194_CONF_ENABLE 0x01
#define IS31FL3194_UPDATE_VAL 0xc5
#define IS31FL3194_CHANNEL_COUNT 3
static const uint8_t led_channels[] = {
IS31FL3194_OUT1_REG,
IS31FL3194_OUT2_REG,
IS31FL3194_OUT3_REG
};
struct is31fl3194_config {
struct i2c_dt_spec bus;
uint8_t num_leds;
const struct led_info *led_infos;
const uint8_t *current_limits;
};
static const struct led_info *is31fl3194_led_to_info(const struct is31fl3194_config *config,
uint32_t led)
{
if (led < config->num_leds) {
return &config->led_infos[led];
}
return NULL;
}
static int is31fl3194_get_info(const struct device *dev,
uint32_t led,
const struct led_info **info_out)
{
const struct is31fl3194_config *config = dev->config;
const struct led_info *info = is31fl3194_led_to_info(config, led);
if (info == NULL) {
return -EINVAL;
}
*info_out = info;
return 0;
}
static int is31fl3194_set_color(const struct device *dev, uint32_t led, uint8_t num_colors,
const uint8_t *color)
{
const struct is31fl3194_config *config = dev->config;
const struct led_info *info = is31fl3194_led_to_info(config, led);
int ret;
if (info == NULL) {
return -ENODEV;
}
if (info->num_colors != 3) {
return -ENOTSUP;
}
if (num_colors != 3) {
return -EINVAL;
}
for (int i = 0; i < 3; i++) {
uint8_t value;
switch (info->color_mapping[i]) {
case LED_COLOR_ID_RED:
value = color[0];
break;
case LED_COLOR_ID_GREEN:
value = color[1];
break;
case LED_COLOR_ID_BLUE:
value = color[2];
break;
default:
/* unreachable: mapping already tested in is31fl3194_check_config */
continue;
}
ret = i2c_reg_write_byte_dt(&config->bus, led_channels[i], value);
if (ret != 0) {
break;
}
}
if (ret == 0) {
ret = i2c_reg_write_byte_dt(&config->bus,
IS31FL3194_UPDATE_REG,
IS31FL3194_UPDATE_VAL);
}
if (ret != 0) {
LOG_ERR("%s: LED write failed: %d", dev->name, ret);
}
return ret;
}
static int is31fl3194_set_brightness(const struct device *dev, uint32_t led, uint8_t value)
{
const struct is31fl3194_config *config = dev->config;
const struct led_info *info = is31fl3194_led_to_info(config, led);
int ret = 0;
if (info == NULL) {
return -ENODEV;
}
if (info->num_colors != 1) {
return -ENOTSUP;
}
if (value > 100) {
return -EINVAL;
}
/* Rescale 0..100 to 0..255 */
value = value * 255 / 100;
ret = i2c_reg_write_byte_dt(&config->bus, led_channels[led], value);
if (ret == 0) {
ret = i2c_reg_write_byte_dt(&config->bus,
IS31FL3194_UPDATE_REG,
IS31FL3194_UPDATE_VAL);
}
if (ret != 0) {
LOG_ERR("%s: LED write failed", dev->name);
}
return ret;
}
static inline int is31fl3194_led_on(const struct device *dev, uint32_t led)
{
return is31fl3194_set_brightness(dev, led, 100);
}
static inline int is31fl3194_led_off(const struct device *dev, uint32_t led)
{
return is31fl3194_set_brightness(dev, led, 0);
}
/*
* Counts red, green, blue channels; returns true if color_id is valid
* and no more than one channel maps to the same color
*/
static bool is31fl3194_count_colors(const struct device *dev,
uint8_t color_id, uint8_t *rgb_counts)
{
bool ret = false;
switch (color_id) {
case LED_COLOR_ID_RED:
ret = (++rgb_counts[0] == 1);
break;
case LED_COLOR_ID_GREEN:
ret = (++rgb_counts[1] == 1);
break;
case LED_COLOR_ID_BLUE:
ret = (++rgb_counts[2] == 1);
break;
}
if (!ret) {
LOG_ERR("%s: invalid color %d (duplicate or not RGB)",
dev->name, color_id);
}
return ret;
}
static int is31fl3194_check_config(const struct device *dev)
{
const struct is31fl3194_config *config = dev->config;
const struct led_info *info;
uint8_t rgb_counts[3] = { 0 };
uint8_t i;
switch (config->num_leds) {
case 1:
/* check that it is a three-channel LED */
info = &config->led_infos[0];
if (info->num_colors != 3) {
LOG_ERR("%s: invalid number of colors %d "
"(must be 3 for RGB LED)",
dev->name, info->num_colors);
return -EINVAL;
}
for (i = 0; i < 3; i++) {
if (!is31fl3194_count_colors(dev, info->color_mapping[i], rgb_counts)) {
return -EINVAL;
}
}
break;
case 3:
/* check that each LED is single-color */
for (i = 0; i < 3; i++) {
info = &config->led_infos[i];
if (info->num_colors != 1) {
LOG_ERR("%s: invalid number of colors %d "
"(must be 1 when defining multiple LEDs)",
dev->name, info->num_colors);
return -EINVAL;
}
if (!is31fl3194_count_colors(dev, info->color_mapping[0], rgb_counts)) {
return -EINVAL;
}
}
break;
default:
LOG_ERR("%s: invalid number of LEDs %d (must be 1 or 3)",
dev->name, config->num_leds);
return -EINVAL;
}
return 0;
}
static int is31fl3194_init(const struct device *dev)
{
const struct is31fl3194_config *config = dev->config;
const struct led_info *info = NULL;
int i, ret;
uint8_t prod_id, band;
uint8_t current_reg = 0;
ret = is31fl3194_check_config(dev);
if (ret != 0) {
return ret;
}
if (!i2c_is_ready_dt(&config->bus)) {
LOG_ERR("%s: I2C device not ready", dev->name);
return -ENODEV;
}
ret = i2c_reg_read_byte_dt(&config->bus, IS31FL3194_PROD_ID_REG, &prod_id);
if (ret != 0) {
LOG_ERR("%s: failed to read product ID", dev->name);
return ret;
}
if (prod_id != IS31FL3194_PROD_ID_VAL) {
LOG_ERR("%s: invalid product ID 0x%02x (expected 0x%02x)", dev->name, prod_id,
IS31FL3194_PROD_ID_VAL);
return -ENODEV;
}
/* calc current limit register value */
info = &config->led_infos[0];
if (info->num_colors == IS31FL3194_CHANNEL_COUNT) {
/* one RGB LED: set all channels to the same current limit */
band = (config->current_limits[0] / 10) - 1;
for (i = 0; i < IS31FL3194_CHANNEL_COUNT; i++) {
current_reg |= band << (2 * i);
}
} else {
/* single-channel LEDs: independent limits */
for (i = 0; i < config->num_leds; i++) {
band = (config->current_limits[i] / 10) - 1;
current_reg |= band << (2 * i);
}
}
ret = i2c_reg_write_byte_dt(&config->bus, IS31FL3194_CURRENT_REG, current_reg);
if (ret != 0) {
LOG_ERR("%s: failed to set current limit", dev->name);
return ret;
}
/* enable device */
return i2c_reg_write_byte_dt(&config->bus, IS31FL3194_CONF_REG, IS31FL3194_CONF_ENABLE);
}
static const struct led_driver_api is31fl3194_led_api = {
.set_brightness = is31fl3194_set_brightness,
.on = is31fl3194_led_on,
.off = is31fl3194_led_off,
.get_info = is31fl3194_get_info,
.set_color = is31fl3194_set_color,
};
#define COLOR_MAPPING(led_node_id) \
static const uint8_t color_mapping_##led_node_id[] = \
DT_PROP(led_node_id, color_mapping);
#define LED_INFO(led_node_id) \
{ \
.label = DT_PROP(led_node_id, label), \
.num_colors = DT_PROP_LEN(led_node_id, color_mapping), \
.color_mapping = color_mapping_##led_node_id, \
},
#define LED_CURRENT(led_node_id) \
DT_PROP(led_node_id, current_limit),
#define IS31FL3194_DEFINE(id) \
\
DT_INST_FOREACH_CHILD(id, COLOR_MAPPING) \
\
static const struct led_info is31fl3194_leds_##id[] = \
{ DT_INST_FOREACH_CHILD(id, LED_INFO) }; \
static const uint8_t is31fl3194_currents_##id[] = \
{ DT_INST_FOREACH_CHILD(id, LED_CURRENT) }; \
BUILD_ASSERT(ARRAY_SIZE(is31fl3194_leds_##id) > 0, \
"No LEDs defined for " #id); \
\
static const struct is31fl3194_config is31fl3194_config_##id = { \
.bus = I2C_DT_SPEC_INST_GET(id), \
.num_leds = ARRAY_SIZE(is31fl3194_leds_##id), \
.led_infos = is31fl3194_leds_##id, \
.current_limits = is31fl3194_currents_##id, \
}; \
DEVICE_DT_INST_DEFINE(id, &is31fl3194_init, NULL, NULL, \
&is31fl3194_config_##id, POST_KERNEL, \
CONFIG_LED_INIT_PRIORITY, &is31fl3194_led_api);
DT_INST_FOREACH_STATUS_OKAY(IS31FL3194_DEFINE)