drivers/led: add IS31FL3194 LED driver
Add support for the IS31FL3194 3-channel LED driver. This driver can be configured to handle its outputs as either a single RGB LED or (up to) three independent LEDs. Signed-off-by: Luca Burelli <l.burelli@arduino.cc>
This commit is contained in:
parent
8aa6ae43ce
commit
912a581a95
5 changed files with 446 additions and 0 deletions
|
@ -18,6 +18,7 @@ zephyr_library_sources_ifdef(CONFIG_NCP5623 ncp5623.c)
|
|||
zephyr_library_sources_ifdef(CONFIG_PCA9633 pca9633.c)
|
||||
zephyr_library_sources_ifdef(CONFIG_TLC59108 tlc59108.c)
|
||||
zephyr_library_sources_ifdef(CONFIG_IS31FL3733 is31fl3733.c)
|
||||
zephyr_library_sources_ifdef(CONFIG_IS31FL3194 is31fl3194.c)
|
||||
|
||||
zephyr_library_sources_ifdef(CONFIG_LED_SHELL led_shell.c)
|
||||
|
||||
|
|
|
@ -40,5 +40,6 @@ source "drivers/led/Kconfig.pwm"
|
|||
source "drivers/led/Kconfig.tlc59108"
|
||||
source "drivers/led/Kconfig.xec"
|
||||
source "drivers/led/Kconfig.is31fl3733"
|
||||
source "drivers/led/Kconfig.is31fl3194"
|
||||
|
||||
endif # LED
|
||||
|
|
11
drivers/led/Kconfig.is31fl3194
Normal file
11
drivers/led/Kconfig.is31fl3194
Normal file
|
@ -0,0 +1,11 @@
|
|||
# Copyright (c) 2024 Arduino SA
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
config IS31FL3194
|
||||
bool "IS31FL3194 LED driver"
|
||||
default y
|
||||
depends on DT_HAS_ISSI_IS31FL3194_ENABLED
|
||||
select I2C
|
||||
help
|
||||
Enable LED driver for Lumissil Microsystems (a division of ISSI)
|
||||
IS31FL3194. This chip supports one RGB LED or 3 independent LEDs.
|
359
drivers/led/is31fl3194.c
Normal file
359
drivers/led/is31fl3194.c
Normal file
|
@ -0,0 +1,359 @@
|
|||
/*
|
||||
* 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)
|
74
dts/bindings/led/issi,is31fl3194.yaml
Normal file
74
dts/bindings/led/issi,is31fl3194.yaml
Normal file
|
@ -0,0 +1,74 @@
|
|||
# Copyright (c) 2024 Arduino SA
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
description: |
|
||||
IS31FL3194 3-channel LED driver with programmable pattern sequencing
|
||||
|
||||
This driver supports single-channel and RGB LEDs. For single channel LEDs,
|
||||
the led_set_brightness() API can be used to set the brightness of each LED.
|
||||
For RGB LEDs, the led_set_color() API can be used to set the red, green and
|
||||
blue components; the driver takes care of routing to the outputs described
|
||||
by the color-mapping property.
|
||||
|
||||
The LED_SHELL application can be used for testing.
|
||||
|
||||
The following defines a single RGB LED in the is31fl3194 DT node:
|
||||
|
||||
is31fl3194@53 {
|
||||
compatible = "issi,is31fl3194";
|
||||
reg = <0x53>;
|
||||
|
||||
led_0 {
|
||||
label = "RGB LED";
|
||||
color-mapping =
|
||||
<LED_COLOR_ID_RED>,
|
||||
<LED_COLOR_ID_GREEN>,
|
||||
<LED_COLOR_ID_BLUE>;
|
||||
};
|
||||
};
|
||||
|
||||
The following example defines three single-channel LEDs in the is31fl3194 DT node:
|
||||
|
||||
is31fl3194@53 {
|
||||
compatible = "issi,is31fl3194";
|
||||
reg = <0x53>;
|
||||
|
||||
led_0 {
|
||||
label = "RED LED";
|
||||
color-mapping = <LED_COLOR_ID_RED>;
|
||||
};
|
||||
|
||||
led_1 {
|
||||
label = "GREEN LED";
|
||||
color-mapping = <LED_COLOR_ID_GREEN>;
|
||||
};
|
||||
|
||||
led_2 {
|
||||
label = "BLUE LED";
|
||||
color-mapping = <LED_COLOR_ID_BLUE>;
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
compatible: "issi,is31fl3194"
|
||||
|
||||
include: ["i2c-device.yaml", "led-controller.yaml"]
|
||||
|
||||
child-binding:
|
||||
properties:
|
||||
label:
|
||||
required: true
|
||||
|
||||
color-mapping:
|
||||
required: true
|
||||
|
||||
current-limit:
|
||||
type: int
|
||||
enum:
|
||||
- 10
|
||||
- 20
|
||||
- 30
|
||||
- 40
|
||||
required: true
|
||||
description: |
|
||||
The current limit for the LED in mA.
|
Loading…
Add table
Add a link
Reference in a new issue