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