| /* |
| * 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) |