You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
343 lines
8.1 KiB
343 lines
8.1 KiB
/* |
|
* 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 */ |
|
return -EINVAL; |
|
} |
|
|
|
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; |
|
} |
|
|
|
/* Rescale 0..100 to 0..255 */ |
|
value = value * 255 / LED_BRIGHTNESS_MAX; |
|
|
|
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; |
|
} |
|
|
|
/* |
|
* 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 DEVICE_API(led, is31fl3194_led_api) = { |
|
.set_brightness = is31fl3194_set_brightness, |
|
.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)
|
|
|