/* * Copyright (c) 2020 Seagate Technology LLC * Copyright (c) 2022 Grinn * * SPDX-License-Identifier: Apache-2.0 */ /** * @file * @brief LP50xx LED controller */ #include #include #include #include #include #include #include #include #include #include #include LOG_MODULE_REGISTER(lp50xx, CONFIG_LED_LOG_LEVEL); #define LP50XX_MAX_BRIGHTNESS 100U /* * Number of supported RGB led modules per chipset. * * For each module, there are 4 associated registers: * - 1 brightness register * - 3 color registers (RGB) * * A chipset can have more modules than leds. In this case, the * associated registers will simply be inactive. */ #define LP5012_NUM_MODULES 4 #define LP5024_NUM_MODULES 8 #define LP5036_NUM_MODULES 12 /* Maximum number of channels */ #define LP50XX_MAX_CHANNELS(nmodules) \ ((LP50XX_COLORS_PER_LED + 1) * ((nmodules) + 1)) #define LP50XX_DISABLE_DELAY_US 3 #define LP50XX_ENABLE_DELAY_US 500 /* Base registers */ #define LP50XX_DEVICE_CONFIG0 0x00 #define LP50XX_DEVICE_CONFIG1 0x01 #define LP50XX_LED_CONFIG0 0x02 #define LP50XX_BANK_BASE(nmodules) \ (0x03 + (((nmodules) - 1) / 8)) #define LP50XX_LED0_BRIGHTNESS(nmodules) \ ((LP50XX_BANK_BASE(nmodules)) + 4) #define LP50XX_OUT0_COLOR(nmodules) \ (LP50XX_LED0_BRIGHTNESS(nmodules) + (nmodules)) #define LP50XX_RESET(nmodules) \ (LP50XX_OUT0_COLOR(nmodules) + LP50XX_COLORS_PER_LED * (nmodules)) /* Register values */ #define CONFIG0_CHIP_EN BIT(6) #define CONFIG1_LED_GLOBAL_OFF BIT(0) #define CONFIG1_MAX_CURRENT_OPT BIT(1) #define CONFIG1_PWM_DITHERING_EN BIT(2) #define CONFIG1_AUTO_INCR_EN BIT(3) #define CONFIG1_POWER_SAVE_EN BIT(4) #define CONFIG1_LOG_SCALE_EN BIT(5) #define RESET_SW 0xFF struct lp50xx_config { struct i2c_dt_spec bus; const struct gpio_dt_spec gpio_enable; uint8_t num_modules; uint8_t max_leds; uint8_t num_leds; bool log_scale_en; bool max_curr_opt; const struct led_info *leds_info; }; struct lp50xx_data { uint8_t *chan_buf; }; static const struct led_info *lp50xx_led_to_info( const struct lp50xx_config *config, uint32_t led) { if (led < config->num_leds) { return &config->leds_info[led]; } return NULL; } static int lp50xx_get_info(const struct device *dev, uint32_t led, const struct led_info **info) { const struct lp50xx_config *config = dev->config; const struct led_info *led_info = lp50xx_led_to_info(config, led); if (!led_info) { return -EINVAL; } *info = led_info; return 0; } static int lp50xx_set_brightness(const struct device *dev, uint32_t led, uint8_t value) { const struct lp50xx_config *config = dev->config; const struct led_info *led_info = lp50xx_led_to_info(config, led); uint8_t buf[2]; if (!led_info) { return -ENODEV; } if (value > LP50XX_MAX_BRIGHTNESS) { LOG_ERR("%s: brightness value out of bounds: val=%d, max=%d", dev->name, value, LP50XX_MAX_BRIGHTNESS); return -EINVAL; } buf[0] = LP50XX_LED0_BRIGHTNESS(config->num_modules) + led_info->index; buf[1] = (value * 0xff) / 100; return i2c_write_dt(&config->bus, buf, sizeof(buf)); } static int lp50xx_on(const struct device *dev, uint32_t led) { return lp50xx_set_brightness(dev, led, 100); } static int lp50xx_off(const struct device *dev, uint32_t led) { return lp50xx_set_brightness(dev, led, 0); } static int lp50xx_set_color(const struct device *dev, uint32_t led, uint8_t num_colors, const uint8_t *color) { const struct lp50xx_config *config = dev->config; const struct led_info *led_info = lp50xx_led_to_info(config, led); uint8_t buf[4]; if (!led_info) { return -ENODEV; } if (num_colors != led_info->num_colors) { LOG_ERR("%s: invalid number of colors: got=%d, expected=%d", dev->name, num_colors, led_info->num_colors); return -EINVAL; } buf[0] = LP50XX_OUT0_COLOR(config->num_modules); buf[0] += LP50XX_COLORS_PER_LED * led_info->index; buf[1] = color[0]; buf[2] = color[1]; buf[3] = color[2]; return i2c_write_dt(&config->bus, buf, sizeof(buf)); } static int lp50xx_write_channels(const struct device *dev, uint32_t start_channel, uint32_t num_channels, const uint8_t *buf) { const struct lp50xx_config *config = dev->config; struct lp50xx_data *data = dev->data; uint8_t base_channel, end_channel, max_channels; base_channel = LP50XX_BANK_BASE(config->num_modules); end_channel = base_channel + start_channel + num_channels; max_channels = base_channel + LP50XX_MAX_CHANNELS(config->num_modules); if (end_channel > max_channels) { return -EINVAL; } /* * Unfortunately this controller doesn't support commands split into * two I2C messages. */ data->chan_buf[0] = base_channel + start_channel; memcpy(data->chan_buf + 1, buf, num_channels); return i2c_write_dt(&config->bus, data->chan_buf, num_channels + 1); } static int lp50xx_reset(const struct device *dev) { const struct lp50xx_config *config = dev->config; uint8_t buf[2]; int err; /* Software reset */ buf[0] = LP50XX_RESET(config->num_modules); buf[1] = RESET_SW; err = i2c_write_dt(&config->bus, buf, 2); if (err < 0) { return err; } /* After reset, apply configuration since all registers are reset. */ buf[0] = LP50XX_DEVICE_CONFIG1; buf[1] = CONFIG1_PWM_DITHERING_EN | CONFIG1_AUTO_INCR_EN | CONFIG1_POWER_SAVE_EN; if (config->max_curr_opt) { buf[1] |= CONFIG1_MAX_CURRENT_OPT; } if (config->log_scale_en) { buf[1] |= CONFIG1_LOG_SCALE_EN; } return i2c_write_dt(&config->bus, buf, 2); } static int lp50xx_hw_enable(const struct device *dev, bool enable) { const struct lp50xx_config *config = dev->config; int err; if (config->gpio_enable.port == NULL) { /* Nothing to do */ return 0; } err = gpio_pin_set_dt(&config->gpio_enable, enable); if (err < 0) { LOG_ERR("%s: failed to set enable gpio", dev->name); return err; } k_usleep(enable ? LP50XX_ENABLE_DELAY_US : LP50XX_DISABLE_DELAY_US); return 0; } static int lp50xx_enable(const struct device *dev, bool enable) { const struct lp50xx_config *config = dev->config; uint8_t value = enable ? CONFIG0_CHIP_EN : 0; return i2c_reg_update_byte_dt(&config->bus, LP50XX_DEVICE_CONFIG0, CONFIG0_CHIP_EN, value); } static int lp50xx_init(const struct device *dev) { const struct lp50xx_config *config = dev->config; int err; if (!i2c_is_ready_dt(&config->bus)) { LOG_ERR("%s: I2C device not ready", dev->name); return -ENODEV; } if (config->num_leds > config->max_leds) { LOG_ERR("%s: invalid number of LEDs %d (max %d)", dev->name, config->num_leds, config->max_leds); return -EINVAL; } /* Configure GPIO if present */ if (config->gpio_enable.port != NULL) { if (!gpio_is_ready_dt(&config->gpio_enable)) { LOG_ERR("%s: enable gpio is not ready", dev->name); return -ENODEV; } err = gpio_pin_configure_dt(&config->gpio_enable, GPIO_OUTPUT_INACTIVE); if (err < 0) { LOG_ERR("%s: failed to initialize enable gpio", dev->name); return err; } } /* Enable hardware */ err = lp50xx_hw_enable(dev, true); if (err < 0) { LOG_ERR("%s: failed to enable hardware", dev->name); return err; } /* Reset device */ err = lp50xx_reset(dev); if (err < 0) { LOG_ERR("%s: failed to reset", dev->name); return err; } /* Enable device */ err = lp50xx_enable(dev, true); if (err < 0) { LOG_ERR("%s: failed to enable", dev->name); return err; } return 0; } #ifdef CONFIG_PM_DEVICE static int lp50xx_pm_action(const struct device *dev, enum pm_device_action action) { switch (action) { case PM_DEVICE_ACTION_SUSPEND: return lp50xx_enable(dev, false); case PM_DEVICE_ACTION_RESUME: return lp50xx_enable(dev, true); default: return -ENOTSUP; } return 0; } #endif /* CONFIG_PM_DEVICE */ static const struct led_driver_api lp50xx_led_api = { .on = lp50xx_on, .off = lp50xx_off, .get_info = lp50xx_get_info, .set_brightness = lp50xx_set_brightness, .set_color = lp50xx_set_color, .write_channels = lp50xx_write_channels, }; #define COLOR_MAPPING(led_node_id) \ 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), \ .index = DT_PROP(led_node_id, index), \ .num_colors = \ DT_PROP_LEN(led_node_id, color_mapping), \ .color_mapping = color_mapping_##led_node_id, \ }, #define LP50XX_DEVICE(n, id, nmodules) \ DT_INST_FOREACH_CHILD(n, COLOR_MAPPING) \ \ static const struct led_info lp##id##_leds_##n[] = { \ DT_INST_FOREACH_CHILD(n, LED_INFO) \ }; \ \ static const struct lp50xx_config lp##id##_config_##n = { \ .bus = I2C_DT_SPEC_INST_GET(n), \ .gpio_enable = \ GPIO_DT_SPEC_INST_GET_OR(n, enable_gpios, {0}), \ .num_modules = nmodules, \ .max_leds = LP##id##_MAX_LEDS, \ .num_leds = ARRAY_SIZE(lp##id##_leds_##n), \ .log_scale_en = DT_INST_PROP(n, log_scale_en), \ .max_curr_opt = DT_INST_PROP(n, max_curr_opt), \ .leds_info = lp##id##_leds_##n, \ }; \ \ static uint8_t lp##id##_chan_buf_##n[LP50XX_MAX_CHANNELS(nmodules) + 1];\ \ static struct lp50xx_data lp##id##_data_##n = { \ .chan_buf = lp##id##_chan_buf_##n, \ }; \ \ PM_DEVICE_DT_INST_DEFINE(n, lp50xx_pm_action); \ \ DEVICE_DT_INST_DEFINE(n, \ lp50xx_init, \ PM_DEVICE_DT_INST_GET(n), \ &lp##id##_data_##n, \ &lp##id##_config_##n, \ POST_KERNEL, CONFIG_LED_INIT_PRIORITY, \ &lp50xx_led_api); #undef DT_DRV_COMPAT #define DT_DRV_COMPAT ti_lp5009 DT_INST_FOREACH_STATUS_OKAY_VARGS(LP50XX_DEVICE, 5009, LP5012_NUM_MODULES) #undef DT_DRV_COMPAT #define DT_DRV_COMPAT ti_lp5012 DT_INST_FOREACH_STATUS_OKAY_VARGS(LP50XX_DEVICE, 5012, LP5012_NUM_MODULES) #undef DT_DRV_COMPAT #define DT_DRV_COMPAT ti_lp5018 DT_INST_FOREACH_STATUS_OKAY_VARGS(LP50XX_DEVICE, 5018, LP5024_NUM_MODULES) #undef DT_DRV_COMPAT #define DT_DRV_COMPAT ti_lp5024 DT_INST_FOREACH_STATUS_OKAY_VARGS(LP50XX_DEVICE, 5024, LP5024_NUM_MODULES) #undef DT_DRV_COMPAT #define DT_DRV_COMPAT ti_lp5030 DT_INST_FOREACH_STATUS_OKAY_VARGS(LP50XX_DEVICE, 5030, LP5036_NUM_MODULES) #undef DT_DRV_COMPAT #define DT_DRV_COMPAT ti_lp5036 DT_INST_FOREACH_STATUS_OKAY_VARGS(LP50XX_DEVICE, 5036, LP5036_NUM_MODULES)