From 8fdc6c1f1b8237c7320963cf27663a93abb038d4 Mon Sep 17 00:00:00 2001 From: Ayush Kothari Date: Sat, 15 Jun 2024 18:28:46 +0530 Subject: [PATCH 1/2] driver: led: Add common LP55XX driver - Add common driver for LP5521 and LP5562 - Remove LP5562 driver source and KConfig files - Add sample application for LP5521 Signed-off-by: Ayush Kothari --- drivers/led/CMakeLists.txt | 2 +- drivers/led/Kconfig | 2 +- drivers/led/Kconfig.lp5562 | 13 - drivers/led/Kconfig.lp55xx | 11 + drivers/led/lp5562.c | 1005 -------------------- drivers/led/lp55xx.c | 1038 +++++++++++++++++++++ drivers/led/lp55xx.h | 151 +++ dts/bindings/led/ti,lp5521.yaml | 8 + dts/bindings/led/ti,lp5562.yaml | 24 +- dts/bindings/led/ti,lp55xx.yaml | 35 + samples/drivers/led_lp5521/CMakeLists.txt | 9 + samples/drivers/led_lp5521/README.rst | 31 + samples/drivers/led_lp5521/prj.conf | 2 + samples/drivers/led_lp5521/sample.yaml | 9 + samples/drivers/led_lp5521/src/main.c | 81 ++ 15 files changed, 1378 insertions(+), 1043 deletions(-) delete mode 100644 drivers/led/Kconfig.lp5562 create mode 100644 drivers/led/Kconfig.lp55xx delete mode 100644 drivers/led/lp5562.c create mode 100644 drivers/led/lp55xx.c create mode 100644 drivers/led/lp55xx.h create mode 100644 dts/bindings/led/ti,lp5521.yaml create mode 100644 dts/bindings/led/ti,lp55xx.yaml create mode 100644 samples/drivers/led_lp5521/CMakeLists.txt create mode 100644 samples/drivers/led_lp5521/README.rst create mode 100644 samples/drivers/led_lp5521/prj.conf create mode 100644 samples/drivers/led_lp5521/sample.yaml create mode 100644 samples/drivers/led_lp5521/src/main.c diff --git a/drivers/led/CMakeLists.txt b/drivers/led/CMakeLists.txt index 09ea5e202fdf5..a2edbe540751a 100644 --- a/drivers/led/CMakeLists.txt +++ b/drivers/led/CMakeLists.txt @@ -12,8 +12,8 @@ zephyr_library_sources_ifdef(CONFIG_LED_PWM led_pwm.c) zephyr_library_sources_ifdef(CONFIG_LED_XEC led_mchp_xec.c) zephyr_library_sources_ifdef(CONFIG_LP3943 lp3943.c) zephyr_library_sources_ifdef(CONFIG_LP50XX lp50xx.c) -zephyr_library_sources_ifdef(CONFIG_LP5562 lp5562.c) zephyr_library_sources_ifdef(CONFIG_LP5569 lp5569.c) +zephyr_library_sources_ifdef(CONFIG_LP55XX lp55xx.c) zephyr_library_sources_ifdef(CONFIG_NCP5623 ncp5623.c) zephyr_library_sources_ifdef(CONFIG_PCA9633 pca9633.c) zephyr_library_sources_ifdef(CONFIG_TLC59108 tlc59108.c) diff --git a/drivers/led/Kconfig b/drivers/led/Kconfig index 26bf49cd5e2ae..2974677cca85e 100644 --- a/drivers/led/Kconfig +++ b/drivers/led/Kconfig @@ -31,7 +31,6 @@ source "drivers/led/Kconfig.ht16k33" source "drivers/led/Kconfig.is31fl3216a" source "drivers/led/Kconfig.lp3943" source "drivers/led/Kconfig.lp50xx" -source "drivers/led/Kconfig.lp5562" source "drivers/led/Kconfig.lp5569" source "drivers/led/Kconfig.ncp5623" source "drivers/led/Kconfig.npm1300" @@ -41,5 +40,6 @@ source "drivers/led/Kconfig.tlc59108" source "drivers/led/Kconfig.xec" source "drivers/led/Kconfig.is31fl3733" source "drivers/led/Kconfig.is31fl3194" +source "drivers/led/Kconfig.lp55xx" endif # LED diff --git a/drivers/led/Kconfig.lp5562 b/drivers/led/Kconfig.lp5562 deleted file mode 100644 index ee11f84dbd446..0000000000000 --- a/drivers/led/Kconfig.lp5562 +++ /dev/null @@ -1,13 +0,0 @@ -# Copyright (c) 2018 Workaround GmbH -# SPDX-License-Identifier: Apache-2.0 - -config LP5562 - bool "LP5562 LED driver" - default y - depends on DT_HAS_TI_LP5562_ENABLED - select I2C - help - Enable LED driver for LP5562. - - LP5562 LED driver has 4 channels (RGBW). Each channel can drive up to - 25.5 mA per LED. diff --git a/drivers/led/Kconfig.lp55xx b/drivers/led/Kconfig.lp55xx new file mode 100644 index 0000000000000..ac2e9cf6079e7 --- /dev/null +++ b/drivers/led/Kconfig.lp55xx @@ -0,0 +1,11 @@ +# Copyright (c) 2024 Croxel, Inc. +# SPDX-License-Identifier: Apache-2.0 + +config LP55XX + bool "LP55XX LED controller" + default y + depends on DT_HAS_TI_LP5562_ENABLED || DT_HAS_TI_LP5521_ENABLED + select I2C + help + Enable driver for the Texas Instruments LP55XX I2C LED + controllers. It supports: LP5562 and LP5529. diff --git a/drivers/led/lp5562.c b/drivers/led/lp5562.c deleted file mode 100644 index d1a1f4031fb13..0000000000000 --- a/drivers/led/lp5562.c +++ /dev/null @@ -1,1005 +0,0 @@ -/* - * Copyright (c) 2018 Workaround GmbH - * - * SPDX-License-Identifier: Apache-2.0 - */ - -#define DT_DRV_COMPAT ti_lp5562 - -/** - * @file - * @brief LP5562 LED driver - * - * The LP5562 is a 4-channel LED driver that communicates over I2C. The four - * channels are expected to be connected to a red, green, blue and white LED. - * Each LED can be driven by two different sources. - * - * 1. The brightness of each LED can be configured directly by setting a - * register that drives the PWM of the connected LED. - * - * 2. A program can be transferred to the driver and run by one of the three - * available execution engines. Up to 16 commands can be defined in each - * program. Possible commands are: - * - Set the brightness. - * - Fade the brightness over time. - * - Loop parts of the program or the whole program. - * - Add delays. - * - Synchronize between the engines. - * - * After the program has been transferred, it can run infinitely without - * communication between the host MCU and the driver. - */ - -#include -#include -#include -#include - -#define LOG_LEVEL CONFIG_LED_LOG_LEVEL -#include -LOG_MODULE_REGISTER(lp5562); - -#include "led_context.h" - -/* Registers */ -#define LP5562_ENABLE 0x00 -#define LP5562_OP_MODE 0x01 -#define LP5562_B_PWM 0x02 -#define LP5562_G_PWM 0x03 -#define LP5562_R_PWM 0x04 -#define LP5562_B_CURRENT 0x05 -#define LP5562_G_CURRENT 0x06 -#define LP5562_R_CURRENT 0x07 -#define LP5562_CONFIG 0x08 -#define LP5562_ENG1_PC 0x09 -#define LP5562_ENG2_PC 0x0A -#define LP5562_ENG3_PC 0x0B -#define LP5562_STATUS 0x0C -#define LP5562_RESET 0x0D -#define LP5562_W_PWM 0x0E -#define LP5562_W_CURRENT 0x0F -#define LP5562_PROG_MEM_ENG1_BASE 0x10 -#define LP5562_PROG_MEM_ENG2_BASE 0x30 -#define LP5562_PROG_MEM_ENG3_BASE 0x50 -#define LP5562_LED_MAP 0x70 - -/* - * The wait command has six bits for the number of steps (max 63) with up to - * 15.6ms per step if the prescaler is set to 1. We round the step length - * however to 16ms for easier handling, so the maximum blinking period is - * therefore (16 * 63) = 1008ms. We round it down to 1000ms to be on the safe - * side. - */ -#define LP5562_MAX_BLINK_PERIOD 1000 -/* - * The minimum waiting period is 0.49ms with the prescaler set to 0 and one - * step. We round up to a full millisecond. - */ -#define LP5562_MIN_BLINK_PERIOD 1 - -/* Brightness limits in percent */ -#define LP5562_MIN_BRIGHTNESS 0 -#define LP5562_MAX_BRIGHTNESS 100 - -/* Output current limits in 0.1 mA */ -#define LP5562_MIN_CURRENT_SETTING 0 -#define LP5562_MAX_CURRENT_SETTING 255 - -/* Values for ENABLE register. */ -#define LP5562_ENABLE_CHIP_EN (1 << 6) -#define LP5562_ENABLE_LOG_EN (1 << 7) - -/* Values for CONFIG register. */ -#define LP5562_CONFIG_EXTERNAL_CLOCK 0x00 -#define LP5562_CONFIG_INTERNAL_CLOCK 0x01 -#define LP5562_CONFIG_CLOCK_AUTOMATIC_SELECT 0x02 -#define LP5562_CONFIG_PWRSAVE_EN (1 << 5) -/* Enable 558 Hz frequency for PWM. Default is 256. */ -#define LP5562_CONFIG_PWM_HW_FREQ_558 (1 << 6) - -/* Values for execution engine programs. */ -#define LP5562_PROG_COMMAND_SET_PWM (1 << 6) -#define LP5562_PROG_COMMAND_RAMP_TIME(prescale, step_time) \ - (((prescale) << 6) | (step_time)) -#define LP5562_PROG_COMMAND_STEP_COUNT(fade_direction, count) \ - (((fade_direction) << 7) | (count)) - -/* Helper definitions. */ -#define LP5562_PROG_MAX_COMMANDS 16 -#define LP5562_MASK 0x03 -#define LP5562_CHANNEL_MASK(channel) ((LP5562_MASK) << (channel << 1)) - -/* - * Available channels. There are four LED channels usable with the LP5562. While - * they can be mapped to LEDs of any color, the driver's typical application is - * with a red, a green, a blue and a white LED. Since the data sheet's - * nomenclature uses RGBW, we keep it that way. - */ -enum lp5562_led_channels { - LP5562_CHANNEL_B, - LP5562_CHANNEL_G, - LP5562_CHANNEL_R, - LP5562_CHANNEL_W, - - LP5562_CHANNEL_COUNT, -}; - -/* - * Each channel can be driven by directly assigning a value between 0 and 255 to - * it to drive the PWM or by one of the three execution engines that can be - * programmed for custom lighting patterns in order to reduce the I2C traffic - * for repetitive patterns. - */ -enum lp5562_led_sources { - LP5562_SOURCE_PWM, - LP5562_SOURCE_ENGINE_1, - LP5562_SOURCE_ENGINE_2, - LP5562_SOURCE_ENGINE_3, - - LP5562_SOURCE_COUNT, -}; - -/* Operational modes of the execution engines. */ -enum lp5562_engine_op_modes { - LP5562_OP_MODE_DISABLED = 0x00, - LP5562_OP_MODE_LOAD = 0x01, - LP5562_OP_MODE_RUN = 0x02, - LP5562_OP_MODE_DIRECT_CTRL = 0x03, -}; - -/* Execution state of the engines. */ -enum lp5562_engine_exec_states { - LP5562_ENGINE_MODE_HOLD = 0x00, - LP5562_ENGINE_MODE_STEP = 0x01, - LP5562_ENGINE_MODE_RUN = 0x02, - LP5562_ENGINE_MODE_EXEC = 0x03, -}; - -/* Fading directions for programs executed by the engines. */ -enum lp5562_engine_fade_dirs { - LP5562_FADE_UP = 0x00, - LP5562_FADE_DOWN = 0x01, -}; - -struct lp5562_config { - struct i2c_dt_spec bus; - uint8_t r_current; - uint8_t g_current; - uint8_t b_current; - uint8_t w_current; -}; - -struct lp5562_data { - struct led_data dev_data; -}; - -/* - * @brief Get the register for the given LED channel used to directly write a - * brightness value instead of using the execution engines. - * - * @param channel LED channel. - * @param reg Pointer to the register address. - * - * @retval 0 On success. - * @retval -EINVAL If an invalid channel is given. - */ -static int lp5562_get_pwm_reg(enum lp5562_led_channels channel, uint8_t *reg) -{ - switch (channel) { - case LP5562_CHANNEL_W: - *reg = LP5562_W_PWM; - break; - case LP5562_CHANNEL_R: - *reg = LP5562_R_PWM; - break; - case LP5562_CHANNEL_G: - *reg = LP5562_G_PWM; - break; - case LP5562_CHANNEL_B: - *reg = LP5562_B_PWM; - break; - default: - LOG_ERR("Invalid channel given."); - return -EINVAL; - } - - return 0; -} - -/* - * @brief Get the base address for programs of the given execution engine. - * - * @param engine Engine the base address is requested for. - * @param base_addr Pointer to the base address. - * - * @retval 0 On success. - * @retval -EINVAL If a source is given that is not a valid engine. - */ -static int lp5562_get_engine_ram_base_addr(enum lp5562_led_sources engine, - uint8_t *base_addr) -{ - switch (engine) { - case LP5562_SOURCE_ENGINE_1: - *base_addr = LP5562_PROG_MEM_ENG1_BASE; - break; - case LP5562_SOURCE_ENGINE_2: - *base_addr = LP5562_PROG_MEM_ENG2_BASE; - break; - case LP5562_SOURCE_ENGINE_3: - *base_addr = LP5562_PROG_MEM_ENG3_BASE; - break; - default: - return -EINVAL; - } - - return 0; -} - -/* - * @brief Helper to get the register bit shift for the execution engines. - * - * The engine with the highest index is placed on the lowest two bits in the - * OP_MODE and ENABLE registers. - * - * @param engine Engine the shift is requested for. - * @param shift Pointer to the shift value. - * - * @retval 0 On success. - * @retval -EINVAL If a source is given that is not a valid engine. - */ -static int lp5562_get_engine_reg_shift(enum lp5562_led_sources engine, - uint8_t *shift) -{ - switch (engine) { - case LP5562_SOURCE_ENGINE_1: - *shift = 4U; - break; - case LP5562_SOURCE_ENGINE_2: - *shift = 2U; - break; - case LP5562_SOURCE_ENGINE_3: - *shift = 0U; - break; - default: - return -EINVAL; - } - - return 0; -} - -/* - * @brief Convert a time in milliseconds to a combination of prescale and - * step_time for the execution engine programs. - * - * This function expects the given time in milliseconds to be in the allowed - * range the device can handle (0ms to 1000ms). - * - * @param data Capabilities of the driver. - * @param ms Time to be converted in milliseconds [0..1000]. - * @param prescale Pointer to the prescale value. - * @param step_time Pointer to the step_time value. - */ -static void lp5562_ms_to_prescale_and_step(struct led_data *data, uint32_t ms, - uint8_t *prescale, uint8_t *step_time) -{ - /* - * One step with the prescaler set to 0 takes 0.49ms. The max value for - * step_time is 63, so we just double the millisecond value. That way - * the step_time value never goes above the allowed 63. - */ - if (ms < 31) { - *prescale = 0U; - *step_time = ms << 1; - - return; - } - - /* - * With a prescaler value set to 1 one step takes 15.6ms. So by dividing - * through 16 we get a decent enough result with low effort. - */ - *prescale = 1U; - *step_time = ms >> 4; - - return; -} - -/* - * @brief Assign a source to the given LED channel. - * - * @param dev LP5562 device. - * @param channel LED channel the source is assigned to. - * @param source Source for the channel. - * - * @retval 0 On success. - * @retval -EIO If the underlying I2C call fails. - */ -static int lp5562_set_led_source(const struct device *dev, - enum lp5562_led_channels channel, - enum lp5562_led_sources source) -{ - const struct lp5562_config *config = dev->config; - - if (i2c_reg_update_byte_dt(&config->bus, LP5562_LED_MAP, - LP5562_CHANNEL_MASK(channel), - source << (channel << 1))) { - LOG_ERR("LED reg update failed."); - return -EIO; - } - - return 0; -} - -/* - * @brief Get the assigned source of the given LED channel. - * - * @param dev LP5562 device. - * @param channel Requested LED channel. - * @param source Pointer to the source of the channel. - * - * @retval 0 On success. - * @retval -EIO If the underlying I2C call fails. - */ -static int lp5562_get_led_source(const struct device *dev, - enum lp5562_led_channels channel, - enum lp5562_led_sources *source) -{ - const struct lp5562_config *config = dev->config; - uint8_t led_map; - - if (i2c_reg_read_byte_dt(&config->bus, LP5562_LED_MAP, &led_map)) { - return -EIO; - } - - *source = (led_map >> (channel << 1)) & LP5562_MASK; - - return 0; -} - -/* - * @brief Request whether an engine is currently running. - * - * @param dev LP5562 device. - * @param engine Engine to check. - * - * @return Indication of the engine execution state. - * - * @retval true If the engine is currently running. - * @retval false If the engine is not running or an error occurred. - */ -static bool lp5562_is_engine_executing(const struct device *dev, - enum lp5562_led_sources engine) -{ - const struct lp5562_config *config = dev->config; - uint8_t enabled, shift; - int ret; - - ret = lp5562_get_engine_reg_shift(engine, &shift); - if (ret) { - return false; - } - - if (i2c_reg_read_byte_dt(&config->bus, LP5562_ENABLE, &enabled)) { - LOG_ERR("Failed to read ENABLE register."); - return false; - } - - enabled = (enabled >> shift) & LP5562_MASK; - - if (enabled == LP5562_ENGINE_MODE_RUN) { - return true; - } - - return false; -} - -/* - * @brief Get an available execution engine that is currently unused. - * - * @param dev LP5562 device. - * @param engine Pointer to the engine ID. - * - * @retval 0 On success. - * @retval -ENODEV If all engines are busy. - */ -static int lp5562_get_available_engine(const struct device *dev, - enum lp5562_led_sources *engine) -{ - enum lp5562_led_sources src; - - for (src = LP5562_SOURCE_ENGINE_1; src < LP5562_SOURCE_COUNT; src++) { - if (!lp5562_is_engine_executing(dev, src)) { - LOG_DBG("Available engine: %d", src); - *engine = src; - return 0; - } - } - - LOG_ERR("No unused engine available"); - - return -ENODEV; -} - -/* - * @brief Set an register shifted for the given execution engine. - * - * @param dev LP5562 device. - * @param engine Engine the value is shifted for. - * @param reg Register address to set. - * @param val Value to set. - * - * @retval 0 On success. - * @retval -EIO If the underlying I2C call fails. - */ -static int lp5562_set_engine_reg(const struct device *dev, - enum lp5562_led_sources engine, - uint8_t reg, uint8_t val) -{ - const struct lp5562_config *config = dev->config; - uint8_t shift; - int ret; - - ret = lp5562_get_engine_reg_shift(engine, &shift); - if (ret) { - return ret; - } - - if (i2c_reg_update_byte_dt(&config->bus, reg, LP5562_MASK << shift, - val << shift)) { - return -EIO; - } - - return 0; -} - -/* - * @brief Set the operational mode of the given engine. - * - * @param dev LP5562 device. - * @param engine Engine the operational mode is changed for. - * @param mode Mode to set. - * - * @retval 0 On success. - * @retval -EIO If the underlying I2C call fails. - */ -static inline int lp5562_set_engine_op_mode(const struct device *dev, - enum lp5562_led_sources engine, - enum lp5562_engine_op_modes mode) -{ - return lp5562_set_engine_reg(dev, engine, LP5562_OP_MODE, mode); -} - -/* - * @brief Set the execution state of the given engine. - * - * @param dev LP5562 device. - * @param engine Engine the execution state is changed for. - * @param state State to set. - * - * @retval 0 On success. - * @retval -EIO If the underlying I2C call fails. - */ -static inline int lp5562_set_engine_exec_state(const struct device *dev, - enum lp5562_led_sources engine, - enum lp5562_engine_exec_states state) -{ - int ret; - - ret = lp5562_set_engine_reg(dev, engine, LP5562_ENABLE, state); - - /* - * Delay between consecutive I2C writes to - * ENABLE register (00h) need to be longer than 488μs (typ.). - */ - k_sleep(K_MSEC(1)); - - return ret; -} - -/* - * @brief Start the execution of the program of the given engine. - * - * @param dev LP5562 device. - * @param engine Engine that is started. - * - * @retval 0 On success. - * @retval -EIO If the underlying I2C call fails. - */ -static inline int lp5562_start_program_exec(const struct device *dev, - enum lp5562_led_sources engine) -{ - if (lp5562_set_engine_op_mode(dev, engine, LP5562_OP_MODE_RUN)) { - return -EIO; - } - - return lp5562_set_engine_exec_state(dev, engine, - LP5562_ENGINE_MODE_RUN); -} - -/* - * @brief Stop the execution of the program of the given engine. - * - * @param dev LP5562 device. - * @param engine Engine that is stopped. - * - * @retval 0 On success. - * @retval -EIO If the underlying I2C call fails. - */ -static inline int lp5562_stop_program_exec(const struct device *dev, - enum lp5562_led_sources engine) -{ - if (lp5562_set_engine_op_mode(dev, engine, LP5562_OP_MODE_DISABLED)) { - return -EIO; - } - - return lp5562_set_engine_exec_state(dev, engine, - LP5562_ENGINE_MODE_HOLD); -} - -/* - * @brief Program a command to the memory of the given execution engine. - * - * @param dev LP5562 device. - * @param engine Engine that is programmed. - * @param command_index Index of the command that is programmed. - * @param command_msb Most significant byte of the command. - * @param command_lsb Least significant byte of the command. - * - * @retval 0 On success. - * @retval -EINVAL If the given command index is out of range or an invalid - * engine is passed. - * @retval -EIO If the underlying I2C call fails. - */ -static int lp5562_program_command(const struct device *dev, - enum lp5562_led_sources engine, - uint8_t command_index, - uint8_t command_msb, - uint8_t command_lsb) -{ - const struct lp5562_config *config = dev->config; - uint8_t prog_base_addr; - int ret; - - if (command_index >= LP5562_PROG_MAX_COMMANDS) { - return -EINVAL; - } - - ret = lp5562_get_engine_ram_base_addr(engine, &prog_base_addr); - if (ret) { - LOG_ERR("Failed to get base RAM address."); - return ret; - } - - if (i2c_reg_write_byte_dt(&config->bus, - prog_base_addr + (command_index << 1), - command_msb)) { - LOG_ERR("Failed to update LED."); - return -EIO; - } - - if (i2c_reg_write_byte_dt(&config->bus, - prog_base_addr + (command_index << 1) + 1, - command_lsb)) { - LOG_ERR("Failed to update LED."); - return -EIO; - } - - return 0; -} - -/* - * @brief Program a command to set a fixed brightness to the given engine. - * - * @param dev LP5562 device. - * @param engine Engine to be programmed. - * @param command_index Index of the command in the program sequence. - * @param brightness Brightness to be set for the LED in percent. - * - * @retval 0 On success. - * @retval -EINVAL If the passed arguments are invalid or out of range. - * @retval -EIO If the underlying I2C call fails. - */ -static int lp5562_program_set_brightness(const struct device *dev, - enum lp5562_led_sources engine, - uint8_t command_index, - uint8_t brightness) -{ - struct lp5562_data *data = dev->data; - struct led_data *dev_data = &data->dev_data; - uint8_t val; - - if ((brightness < dev_data->min_brightness) || - (brightness > dev_data->max_brightness)) { - return -EINVAL; - } - - val = (brightness * 0xFF) / dev_data->max_brightness; - - return lp5562_program_command(dev, engine, command_index, - LP5562_PROG_COMMAND_SET_PWM, val); -} - -/* - * @brief Program a command to ramp the brightness over time. - * - * In each step the PWM value is increased or decreased by 1/255th until the - * maximum or minimum value is reached or step_count steps have been done. - * - * @param dev LP5562 device. - * @param engine Engine to be programmed. - * @param command_index Index of the command in the program sequence. - * @param time_per_step Time each step takes in milliseconds. - * @param step_count Number of steps to perform. - * @param fade_dir Direction of the ramp (in-/decrease brightness). - * - * @retval 0 On success. - * @retval -EINVAL If the passed arguments are invalid or out of range. - * @retval -EIO If the underlying I2C call fails. - */ -static int lp5562_program_ramp(const struct device *dev, - enum lp5562_led_sources engine, - uint8_t command_index, - uint32_t time_per_step, - uint8_t step_count, - enum lp5562_engine_fade_dirs fade_dir) -{ - struct lp5562_data *data = dev->data; - struct led_data *dev_data = &data->dev_data; - uint8_t prescale, step_time; - - if ((time_per_step < dev_data->min_period) || - (time_per_step > dev_data->max_period)) { - return -EINVAL; - } - - lp5562_ms_to_prescale_and_step(dev_data, time_per_step, - &prescale, &step_time); - - return lp5562_program_command(dev, engine, command_index, - LP5562_PROG_COMMAND_RAMP_TIME(prescale, step_time), - LP5562_PROG_COMMAND_STEP_COUNT(fade_dir, step_count)); -} - -/* - * @brief Program a command to do nothing for the given time. - * - * @param dev LP5562 device. - * @param engine Engine to be programmed. - * @param command_index Index of the command in the program sequence. - * @param time Time to do nothing in milliseconds. - * - * @retval 0 On success. - * @retval -EINVAL If the passed arguments are invalid or out of range. - * @retval -EIO If the underlying I2C call fails. - */ -static inline int lp5562_program_wait(const struct device *dev, - enum lp5562_led_sources engine, - uint8_t command_index, - uint32_t time) -{ - /* - * A wait command is a ramp with the step_count set to 0. The fading - * direction does not matter in this case. - */ - return lp5562_program_ramp(dev, engine, command_index, - time, 0, LP5562_FADE_UP); -} - -/* - * @brief Program a command to go back to the beginning of the program. - * - * Can be used at the end of a program to loop it infinitely. - * - * @param dev LP5562 device. - * @param engine Engine to be programmed. - * @param command_index Index of the command in the program sequence. - * - * @retval 0 On success. - * @retval -EINVAL If the given command index is out of range or an invalid - * engine is passed. - * @retval -EIO If the underlying I2C call fails. - */ -static inline int lp5562_program_go_to_start(const struct device *dev, - enum lp5562_led_sources engine, - uint8_t command_index) -{ - return lp5562_program_command(dev, engine, command_index, 0x00, 0x00); -} - -/* - * @brief Change the brightness of a running blink program. - * - * We know that the current program executes a blinking pattern - * consisting of following commands: - * - * - set_brightness high - * - wait on_delay - * - set_brightness low - * - wait off_delay - * - return to start - * - * In order to change the brightness during blinking, we overwrite only - * the first command and start execution again. - * - * @param dev LP5562 device. - * @param engine Engine running the blinking program. - * @param brightness_on New brightness value. - * - * @retval 0 On Success. - * @retval -EINVAL If the engine ID or brightness is out of range. - * @retval -EIO If the underlying I2C call fails. - */ -static int lp5562_update_blinking_brightness(const struct device *dev, - enum lp5562_led_sources engine, - uint8_t brightness_on) -{ - int ret; - - ret = lp5562_stop_program_exec(dev, engine); - if (ret) { - return ret; - } - - ret = lp5562_set_engine_op_mode(dev, engine, LP5562_OP_MODE_LOAD); - if (ret) { - return ret; - } - - - ret = lp5562_program_set_brightness(dev, engine, 0, brightness_on); - if (ret) { - return ret; - } - - ret = lp5562_start_program_exec(dev, engine); - if (ret) { - LOG_ERR("Failed to execute program."); - return ret; - } - - return 0; -} - -static int lp5562_led_blink(const struct device *dev, uint32_t led, - uint32_t delay_on, uint32_t delay_off) -{ - struct lp5562_data *data = dev->data; - struct led_data *dev_data = &data->dev_data; - int ret; - enum lp5562_led_sources engine; - uint8_t command_index = 0U; - - ret = lp5562_get_available_engine(dev, &engine); - if (ret) { - return ret; - } - - ret = lp5562_set_led_source(dev, led, engine); - if (ret) { - LOG_ERR("Failed to set LED source."); - return ret; - } - - ret = lp5562_set_engine_op_mode(dev, engine, LP5562_OP_MODE_LOAD); - if (ret) { - return ret; - } - - ret = lp5562_program_set_brightness(dev, engine, command_index, - dev_data->max_brightness); - if (ret) { - return ret; - } - - ret = lp5562_program_wait(dev, engine, ++command_index, delay_on); - if (ret) { - return ret; - } - - ret = lp5562_program_set_brightness(dev, engine, ++command_index, - dev_data->min_brightness); - if (ret) { - return ret; - } - - ret = lp5562_program_wait(dev, engine, ++command_index, delay_off); - if (ret) { - return ret; - } - - ret = lp5562_program_go_to_start(dev, engine, ++command_index); - if (ret) { - return ret; - } - - ret = lp5562_start_program_exec(dev, engine); - if (ret) { - LOG_ERR("Failed to execute program."); - return ret; - } - - return 0; -} - -static int lp5562_led_set_brightness(const struct device *dev, uint32_t led, - uint8_t value) -{ - const struct lp5562_config *config = dev->config; - struct lp5562_data *data = dev->data; - struct led_data *dev_data = &data->dev_data; - int ret; - uint8_t val, reg; - enum lp5562_led_sources current_source; - - if ((value < dev_data->min_brightness) || - (value > dev_data->max_brightness)) { - return -EINVAL; - } - - ret = lp5562_get_led_source(dev, led, ¤t_source); - if (ret) { - return ret; - } - - if (current_source != LP5562_SOURCE_PWM) { - if (lp5562_is_engine_executing(dev, current_source)) { - /* - * LED is blinking currently. Restart the blinking with - * the passed brightness. - */ - return lp5562_update_blinking_brightness(dev, - current_source, value); - } - - ret = lp5562_set_led_source(dev, led, LP5562_SOURCE_PWM); - if (ret) { - return ret; - } - } - - val = (value * 0xFF) / dev_data->max_brightness; - - ret = lp5562_get_pwm_reg(led, ®); - if (ret) { - return ret; - } - - if (i2c_reg_write_byte_dt(&config->bus, reg, val)) { - LOG_ERR("LED write failed"); - return -EIO; - } - - return 0; -} - -static inline int lp5562_led_on(const struct device *dev, uint32_t led) -{ - struct lp5562_data *data = dev->data; - struct led_data *dev_data = &data->dev_data; - - return lp5562_led_set_brightness(dev, led, dev_data->max_brightness); -} - -static inline int lp5562_led_off(const struct device *dev, uint32_t led) -{ - struct lp5562_data *data = dev->data; - struct led_data *dev_data = &data->dev_data; - - int ret; - enum lp5562_led_sources current_source; - - ret = lp5562_get_led_source(dev, led, ¤t_source); - if (ret) { - return ret; - } - - if (current_source != LP5562_SOURCE_PWM) { - ret = lp5562_stop_program_exec(dev, current_source); - if (ret) { - return ret; - } - } - - return lp5562_led_set_brightness(dev, led, dev_data->min_brightness); -} - -static int lp5562_led_update_current(const struct device *dev) -{ - const struct lp5562_config *config = dev->config; - int ret; - uint8_t tx_buf[4] = { - LP5562_B_CURRENT, - config->b_current, - config->g_current, - config->r_current }; - - ret = i2c_write_dt(&config->bus, tx_buf, sizeof(tx_buf)); - if (ret == 0) { - ret = i2c_reg_write_byte_dt(&config->bus, LP5562_W_CURRENT, config->w_current); - } - - return ret; -} - -static int lp5562_led_init(const struct device *dev) -{ - const struct lp5562_config *config = dev->config; - struct lp5562_data *data = dev->data; - struct led_data *dev_data = &data->dev_data; - int ret; - - if (!device_is_ready(config->bus.bus)) { - LOG_ERR("I2C device not ready"); - return -ENODEV; - } - - /* Hardware specific limits */ - dev_data->min_period = LP5562_MIN_BLINK_PERIOD; - dev_data->max_period = LP5562_MAX_BLINK_PERIOD; - dev_data->min_brightness = LP5562_MIN_BRIGHTNESS; - dev_data->max_brightness = LP5562_MAX_BRIGHTNESS; - - ret = lp5562_led_update_current(dev); - if (ret) { - LOG_ERR("Setting current setting LP5562 LED chip failed."); - return ret; - } - - if (i2c_reg_write_byte_dt(&config->bus, LP5562_ENABLE, - LP5562_ENABLE_CHIP_EN)) { - LOG_ERR("Enabling LP5562 LED chip failed."); - return -EIO; - } - - if (i2c_reg_write_byte_dt(&config->bus, LP5562_CONFIG, - (LP5562_CONFIG_INTERNAL_CLOCK | - LP5562_CONFIG_PWRSAVE_EN))) { - LOG_ERR("Configuring LP5562 LED chip failed."); - return -EIO; - } - - if (i2c_reg_write_byte_dt(&config->bus, LP5562_OP_MODE, 0x00)) { - LOG_ERR("Disabling all engines failed."); - return -EIO; - } - - if (i2c_reg_write_byte_dt(&config->bus, LP5562_LED_MAP, 0x00)) { - LOG_ERR("Setting all LEDs to manual control failed."); - return -EIO; - } - - return 0; -} - -static const struct led_driver_api lp5562_led_api = { - .blink = lp5562_led_blink, - .set_brightness = lp5562_led_set_brightness, - .on = lp5562_led_on, - .off = lp5562_led_off, -}; - -#define LP5562_DEFINE(id) \ - BUILD_ASSERT(DT_INST_PROP(id, red_output_current) <= LP5562_MAX_CURRENT_SETTING,\ - "Red channel current must be between 0 and 25.5 mA."); \ - BUILD_ASSERT(DT_INST_PROP(id, green_output_current) <= LP5562_MAX_CURRENT_SETTING,\ - "Green channel current must be between 0 and 25.5 mA."); \ - BUILD_ASSERT(DT_INST_PROP(id, blue_output_current) <= LP5562_MAX_CURRENT_SETTING,\ - "Blue channel current must be between 0 and 25.5 mA."); \ - BUILD_ASSERT(DT_INST_PROP(id, white_output_current) <= LP5562_MAX_CURRENT_SETTING,\ - "White channel current must be between 0 and 25.5 mA."); \ - static const struct lp5562_config lp5562_config_##id = { \ - .bus = I2C_DT_SPEC_INST_GET(id), \ - .r_current = DT_INST_PROP(id, red_output_current), \ - .g_current = DT_INST_PROP(id, green_output_current), \ - .b_current = DT_INST_PROP(id, blue_output_current), \ - .w_current = DT_INST_PROP(id, white_output_current), \ - }; \ - \ - struct lp5562_data lp5562_data_##id; \ - DEVICE_DT_INST_DEFINE(id, &lp5562_led_init, NULL, \ - &lp5562_data_##id, \ - &lp5562_config_##id, POST_KERNEL, \ - CONFIG_LED_INIT_PRIORITY, \ - &lp5562_led_api); \ - -DT_INST_FOREACH_STATUS_OKAY(LP5562_DEFINE) diff --git a/drivers/led/lp55xx.c b/drivers/led/lp55xx.c new file mode 100644 index 0000000000000..fccfcb9e7c5c4 --- /dev/null +++ b/drivers/led/lp55xx.c @@ -0,0 +1,1038 @@ +/* + * Copyright (c) 2024 Croxel, Inc. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file + * @brief LP55XX LED driver + * + * This LP55XX driver currently supports LP5521 and LP5562 which is a 3 and 4 channel LED driver + * respectively that communicates over I2C. + */ + +#include +#include +#include +#include +#include +#include "lp55xx.h" + +#define LOG_LEVEL CONFIG_LED_LOG_LEVEL +#include +LOG_MODULE_REGISTER(lp55XX); + +#include "led_context.h" + +struct lp55xx_registers { + uint8_t enable_reg; + uint8_t op_mode_reg; + uint8_t blue_pwm_reg; + uint8_t green_pwm_reg; + uint8_t red_pwm_reg; + uint8_t white_pwm_reg; + uint8_t blue_curr_reg; + uint8_t green_curr_reg; + uint8_t red_curr_reg; + uint8_t white_curr_reg; + uint8_t config_reg; + uint8_t blue_engine_pc_reg; + uint8_t green_engine_pc_reg; + uint8_t red_engine_pc_reg; + uint8_t status_reg; + uint8_t reset_reg; + uint8_t blue_prog_mem_base_reg; + uint8_t green_prog_mem_base_reg; + uint8_t red_prog_mem_base_reg; + uint8_t led_map_reg; +}; + +#ifdef CONFIG_DT_HAS_TI_LP5562_ENABLED +static const struct lp55xx_registers lp5562_reg = { + .enable_reg = LP5562_ENABLE, + .op_mode_reg = LP5562_OP_MODE, + .blue_pwm_reg = LP5562_B_PWM, + .green_pwm_reg = LP5562_G_PWM, + .red_pwm_reg = LP5562_R_PWM, + .white_pwm_reg = LP5562_W_PWM, + .blue_curr_reg = LP5562_B_CURRENT, + .green_curr_reg = LP5562_G_CURRENT, + .red_curr_reg = LP5562_R_CURRENT, + .white_curr_reg = LP5562_W_CURRENT, + .config_reg = LP5562_CONFIG, + .blue_engine_pc_reg = LP5562_ENG1_PC, + .green_engine_pc_reg = LP5562_ENG2_PC, + .red_engine_pc_reg = LP5562_ENG3_PC, + .status_reg = LP5562_STATUS, + .reset_reg = LP5562_RESET, + .blue_prog_mem_base_reg = LP5562_PROG_MEM_ENG1_BASE, + .green_prog_mem_base_reg = LP5562_PROG_MEM_ENG2_BASE, + .red_prog_mem_base_reg = LP5562_PROG_MEM_ENG3_BASE, + .led_map_reg = LP5562_LED_MAP, +}; +#endif + +#ifdef CONFIG_DT_HAS_TI_LP5521_ENABLED +static const struct lp55xx_registers lp5521_reg = { + .enable_reg = LP5521_ENABLE, + .op_mode_reg = LP5521_OP_MODE, + .red_pwm_reg = LP5521_R_PWM, + .green_pwm_reg = LP5521_G_PWM, + .blue_pwm_reg = LP5521_B_PWM, + .white_pwm_reg = 0xFF, /* Not applicable for LP5521 */ + .red_curr_reg = LP5521_R_CURRENT, + .green_curr_reg = LP5521_G_CURRENT, + .blue_curr_reg = LP5521_B_CURRENT, + .white_curr_reg = 0xFF, /* Not applicable for LP5521 */ + .config_reg = LP5521_CONFIG, + .red_engine_pc_reg = LP5521_R_PC, + .green_engine_pc_reg = LP5521_G_PC, + .blue_engine_pc_reg = LP5521_B_PC, + .status_reg = LP5521_STATUS, + .reset_reg = LP5521_RESET, + .blue_prog_mem_base_reg = LP5521_PROG_MEM_BLUE_ENG_BASE, + .green_prog_mem_base_reg = LP5521_PROG_MEM_GREEN_ENG_BASE, + .red_prog_mem_base_reg = LP5521_PROG_MEM_RED_ENG_BASE, + .led_map_reg = 0xFF, /* Not applicable for LP5521 */ +}; +#endif + +struct lp55xx_config { + uint8_t r_current; + uint8_t g_current; + uint8_t b_current; + uint8_t w_current; + uint8_t total_leds; + uint16_t id; + struct i2c_dt_spec bus; + const struct lp55xx_registers *registers; + const struct gpio_dt_spec en_pin; +}; + +struct lp55xx_data { + struct led_data dev_data; +}; + +/* + * @brief Get the register for the given LED channel used to directly write a + * brightness value instead of using the execution engines. + * + * @param channel LED channel. + * @param reg Pointer to the register address. + * + * @retval 0 On success. + * @retval -EINVAL If an invalid channel is given. + */ +static int lp55xx_get_pwm_reg(const struct lp55xx_config *config, enum lp55xx_led_channels channel, + uint8_t *reg) +{ + switch (channel) { + case LP55XX_CHANNEL_W: + *reg = config->registers->white_pwm_reg; + break; + case LP55XX_CHANNEL_R: + *reg = config->registers->red_pwm_reg; + break; + case LP55XX_CHANNEL_G: + *reg = config->registers->green_pwm_reg; + break; + case LP55XX_CHANNEL_B: + *reg = config->registers->blue_pwm_reg; + break; + default: + LOG_ERR("Invalid channel given."); + return -EINVAL; + } + + return 0; +} + +/* + * @brief Get the base address for programs of the given execution engine. + * + * @param engine Engine the base address is requested for. + * @param base_addr Pointer to the base address. + * + * @retval 0 On success. + * @retval -EINVAL If a source is given that is not a valid engine. + */ +static int lp55xx_get_engine_ram_base_addr(const struct lp55xx_config *config, + enum lp55xx_led_sources engine, uint8_t *base_addr) +{ + if (config->id == LP5562_ID) { + switch (engine) { + case LP5562_SOURCE_ENGINE_1: + *base_addr = config->registers->blue_prog_mem_base_reg; + break; + case LP5562_SOURCE_ENGINE_2: + *base_addr = config->registers->green_prog_mem_base_reg; + ; + break; + case LP5562_SOURCE_ENGINE_3: + *base_addr = config->registers->red_prog_mem_base_reg; + break; + default: + return -EINVAL; + } + } else if (config->id == LP5521_ID) { + switch (engine) { + case LP5562_SOURCE_ENGINE_1: + *base_addr = config->registers->red_prog_mem_base_reg; + break; + case LP5562_SOURCE_ENGINE_2: + *base_addr = config->registers->green_prog_mem_base_reg; + ; + break; + case LP5562_SOURCE_ENGINE_3: + *base_addr = config->registers->blue_prog_mem_base_reg; + ; + break; + default: + return -EINVAL; + } + } + return 0; +} + +/* + * @brief Helper to get the register bit shift for the execution engines. + * + * The engine with the highest index is placed on the lowest two bits in the + * OP_MODE and ENABLE registers. + * + * @param engine Engine the shift is requested for. + * @param shift Pointer to the shift value. + * + * @retval 0 On success. + * @retval -EINVAL If a source is given that is not a valid engine. + */ +static int lp55xx_get_engine_reg_shift(enum lp55xx_led_sources engine, uint8_t *shift) +{ + switch (engine) { + case LP5562_SOURCE_ENGINE_1: + *shift = 4U; + break; + case LP5562_SOURCE_ENGINE_2: + *shift = 2U; + break; + case LP5562_SOURCE_ENGINE_3: + *shift = 0U; + break; + default: + return -EINVAL; + } + + return 0; +} + +/* + * @brief Convert a time in milliseconds to a combination of prescale and + * step_time for the execution engine programs. + * + * This function expects the given time in milliseconds to be in the allowed + * range the device can handle (0ms to 1000ms). + * + * @param data Capabilities of the driver. + * @param ms Time to be converted in milliseconds [0..1000]. + * @param prescale Pointer to the prescale value. + * @param step_time Pointer to the step_time value. + */ +static void lp55xx_ms_to_prescale_and_step(struct led_data *data, uint32_t ms, uint8_t *prescale, + uint8_t *step_time) +{ + /* + * One step with the prescaler set to 0 takes 0.49ms. The max value for + * step_time is 63, so we just double the millisecond value. That way + * the step_time value never goes above the allowed 63. + */ + if (ms < 31) { + *prescale = 0U; + *step_time = ms << 1; + + return; + } + + /* + * With a prescaler value set to 1 one step takes 15.6ms. So by dividing + * through 16 we get a decent enough result with low effort. + */ + *prescale = 1U; + *step_time = ms >> 4; +} + +/* + * @brief Assign a source to the given LED channel. + * + * @param dev LP55xx device. + * @param channel LED channel the source is assigned to. + * @param source Source for the channel. + * + * @retval 0 On success. + * @retval -EIO If the underlying I2C call fails. + */ +static int lp55xx_set_led_source(const struct device *dev, enum lp55xx_led_channels channel, + enum lp55xx_led_sources source) +{ + const struct lp55xx_config *config = dev->config; + uint8_t reg_addrs = 0; + + if (config->id == LP5562_ID) { + reg_addrs = config->registers->led_map_reg; + } else if (config->id == LP5521_ID) { + reg_addrs = config->registers->op_mode_reg; + } + + if (i2c_reg_update_byte_dt(&config->bus, reg_addrs, LP55XX_CHANNEL_MASK(channel), + source << (channel << 1))) { + LOG_ERR("LED reg update failed."); + return -EIO; + } + k_msleep(1); /* Must wait for next i2c write on OP_MODE reg */ + return 0; +} + +/* + * @brief Get the assigned source of the given LED channel. + * + * @param dev LP55xx device. + * @param channel Requested LED channel. + * @param source Pointer to the source of the channel. + * + * @retval 0 On success. + * @retval -EIO If the underlying I2C call fails. + */ +static int lp55xx_get_led_source(const struct device *dev, enum lp55xx_led_channels channel, + enum lp55xx_led_sources *source) +{ + const struct lp55xx_config *config = dev->config; + uint8_t led_map; + + if (config->id == LP5562_ID) { + if (i2c_reg_read_byte_dt(&config->bus, config->registers->led_map_reg, &led_map)) { + return -EIO; + } + *source = (led_map >> (channel << 1)) & LP55XX_MASK; + } else if (config->id == LP5521_ID) { + /* Source is fixed in LP5521 */ + switch (channel) { + case LP55XX_CHANNEL_B: + *source = LP5562_SOURCE_ENGINE_3; + break; + case LP55XX_CHANNEL_R: + *source = LP5562_SOURCE_ENGINE_1; + break; + case LP55XX_CHANNEL_G: + *source = LP5562_SOURCE_ENGINE_2; + break; + default: + return -EINVAL; + } + } + + return 0; +} + +/* + * @brief Request whether an engine is currently running. + * + * @param dev LP55xx device. + * @param engine Engine to check. + * + * @return Indication of the engine execution state. + * + * @retval true If the engine is currently running. + * @retval false If the engine is not running or an error occurred. + */ +static bool lp55xx_is_engine_executing(const struct device *dev, enum lp55xx_led_sources engine) +{ + const struct lp55xx_config *config = dev->config; + uint8_t enabled, shift; + int ret; + + ret = lp55xx_get_engine_reg_shift(engine, &shift); + if (ret) { + return false; + } + + if (i2c_reg_read_byte_dt(&config->bus, config->registers->enable_reg, &enabled)) { + LOG_ERR("Failed to read ENABLE register."); + return false; + } + + enabled = (enabled >> shift) & LP55XX_MASK; + + if (enabled == LP55XX_ENGINE_MODE_RUN) { + return true; + } + + return false; +} + +/* + * @brief Get an available execution engine that is currently unused. + * + * @param dev LP55xx device. + * @param engine Pointer to the engine ID. + * + * @retval 0 On success. + * @retval -ENODEV If all engines are busy. + */ +static int lp55xx_get_available_engine(const struct device *dev, enum lp55xx_led_sources *engine, + uint32_t led) +{ + enum lp55xx_led_sources src; + const struct lp55xx_config *config = dev->config; + + if (config->id == LP5521_ID) { + /* This IC has fixed or non-mappable engines */ + switch (led) { + case LP55XX_CHANNEL_B: + *engine = LP5562_SOURCE_ENGINE_3; + break; + case LP55XX_CHANNEL_G: + *engine = LP5562_SOURCE_ENGINE_2; + break; + case LP55XX_CHANNEL_R: + *engine = LP5562_SOURCE_ENGINE_1; + break; + default: + return -ENODEV; + } + return 0; + } + + for (src = LP5562_SOURCE_ENGINE_1; src < LP5562_SOURCE_COUNT; src++) { + if (!lp55xx_is_engine_executing(dev, src)) { + LOG_DBG("Available engine: %d", src); + *engine = src; + return 0; + } + } + + LOG_ERR("No unused engine available"); + + return -ENODEV; +} + +/* + * @brief Set an register shifted for the given execution engine. + * + * @param dev LP55xx device. + * @param engine Engine the value is shifted for. + * @param reg Register address to set. + * @param val Value to set. + * + * @retval 0 On success. + * @retval -EIO If the underlying I2C call fails. + */ +static int lp55xx_set_engine_reg(const struct device *dev, enum lp55xx_led_sources engine, + uint8_t reg, uint8_t val) +{ + const struct lp55xx_config *config = dev->config; + uint8_t shift; + int ret; + + ret = lp55xx_get_engine_reg_shift(engine, &shift); + if (ret) { + return ret; + } + + if (i2c_reg_update_byte_dt(&config->bus, reg, LP55XX_MASK << shift, val << shift)) { + return -EIO; + } + + return 0; +} + +/* + * @brief Set the operational mode of the given engine. + * + * @param dev LP55xx device. + * @param engine Engine the operational mode is changed for. + * @param mode Mode to set. + * + * @retval 0 On success. + * @retval -EIO If the underlying I2C call fails. + */ +static inline int lp55xx_set_engine_op_mode(const struct device *dev, + enum lp55xx_led_sources engine, + enum lp55xx_engine_op_modes mode) +{ + const struct lp55xx_config *config = dev->config; + + return lp55xx_set_engine_reg(dev, engine, config->registers->op_mode_reg, mode); +} + +/* + * @brief Set the execution state of the given engine. + * + * @param dev LP55xx device. + * @param engine Engine the execution state is changed for. + * @param state State to set. + * + * @retval 0 On success. + * @retval -EIO If the underlying I2C call fails. + */ +static inline int lp55xx_set_engine_exec_state(const struct device *dev, + enum lp55xx_led_sources engine, + enum lp55xx_engine_exec_states state) +{ + int ret; + const struct lp55xx_config *config = dev->config; + + ret = lp55xx_set_engine_reg(dev, engine, config->registers->enable_reg, state); + + /* + * Delay between consecutive I2C writes to + * ENABLE register (00h) need to be longer than 488μs (typ.). + */ + k_sleep(K_MSEC(1)); + + return ret; +} + +/* + * @brief Start the execution of the program of the given engine. + * + * @param dev LP55xx device. + * @param engine Engine that is started. + * + * @retval 0 On success. + * @retval -EIO If the underlying I2C call fails. + */ +static inline int lp55xx_start_program_exec(const struct device *dev, + enum lp55xx_led_sources engine) +{ + if (lp55xx_set_engine_op_mode(dev, engine, LP55XX_OP_MODE_RUN)) { + return -EIO; + } + + return lp55xx_set_engine_exec_state(dev, engine, LP55XX_ENGINE_MODE_RUN); +} + +/* + * @brief Stop the execution of the program of the given engine. + * + * @param dev LP55xx device. + * @param engine Engine that is stopped. + * + * @retval 0 On success. + * @retval -EIO If the underlying I2C call fails. + */ +static inline int lp55xx_stop_program_exec(const struct device *dev, enum lp55xx_led_sources engine) +{ + if (lp55xx_set_engine_op_mode(dev, engine, LP55XX_OP_MODE_DISABLED)) { + return -EIO; + } + + return lp55xx_set_engine_exec_state(dev, engine, LP55XX_ENGINE_MODE_HOLD); +} + +/* + * @brief Program a command to the memory of the given execution engine. + * + * @param dev LP55xx device. + * @param engine Engine that is programmed. + * @param command_index Index of the command that is programmed. + * @param command_msb Most significant byte of the command. + * @param command_lsb Least significant byte of the command. + * + * @retval 0 On success. + * @retval -EINVAL If the given command index is out of range or an invalid + * engine is passed. + * @retval -EIO If the underlying I2C call fails. + */ +static int lp55xx_program_command(const struct device *dev, enum lp55xx_led_sources engine, + uint8_t command_index, uint8_t command_msb, uint8_t command_lsb) +{ + const struct lp55xx_config *config = dev->config; + uint8_t prog_base_addr = 0; + int ret; + + if (command_index >= LP5562_PROG_MAX_COMMANDS) { + return -EINVAL; + } + + ret = lp55xx_get_engine_ram_base_addr(config, engine, &prog_base_addr); + if (ret) { + LOG_ERR("Failed to get base RAM address."); + return ret; + } + + if (i2c_reg_write_byte_dt(&config->bus, prog_base_addr + (command_index << 1), + command_msb)) { + LOG_ERR("Failed to update LED."); + return -EIO; + } + + if (i2c_reg_write_byte_dt(&config->bus, prog_base_addr + (command_index << 1) + 1, + command_lsb)) { + LOG_ERR("Failed to update LED."); + return -EIO; + } + + return 0; +} + +/* + * @brief Program a command to set a fixed brightness to the given engine. + * + * @param dev LP55xx device. + * @param engine Engine to be programmed. + * @param command_index Index of the command in the program sequence. + * @param brightness Brightness to be set for the LED in percent. + * + * @retval 0 On success. + * @retval -EINVAL If the passed arguments are invalid or out of range. + * @retval -EIO If the underlying I2C call fails. + */ +static int lp55xx_program_set_brightness(const struct device *dev, enum lp55xx_led_sources engine, + uint8_t command_index, uint8_t brightness) +{ + struct lp55xx_data *data = dev->data; + struct led_data *dev_data = &data->dev_data; + uint8_t val; + + if ((brightness < dev_data->min_brightness) || (brightness > dev_data->max_brightness)) { + return -EINVAL; + } + + val = (brightness * 0xFF) / dev_data->max_brightness; + + return lp55xx_program_command(dev, engine, command_index, LP5562_PROG_COMMAND_SET_PWM, val); +} + +/* + * @brief Program a command to ramp the brightness over time. + * + * In each step the PWM value is increased or decreased by 1/255th until the + * maximum or minimum value is reached or step_count steps have been done. + * + * @param dev LP55xx device. + * @param engine Engine to be programmed. + * @param command_index Index of the command in the program sequence. + * @param time_per_step Time each step takes in milliseconds. + * @param step_count Number of steps to perform. + * @param fade_dir Direction of the ramp (in-/decrease brightness). + * + * @retval 0 On success. + * @retval -EINVAL If the passed arguments are invalid or out of range. + * @retval -EIO If the underlying I2C call fails. + */ +static int lp55xx_program_ramp(const struct device *dev, enum lp55xx_led_sources engine, + uint8_t command_index, uint32_t time_per_step, uint8_t step_count, + enum lp55xx_engine_fade_dirs fade_dir) +{ + struct lp55xx_data *data = dev->data; + struct led_data *dev_data = &data->dev_data; + uint8_t prescale, step_time; + + if ((time_per_step < dev_data->min_period) || (time_per_step > dev_data->max_period)) { + return -EINVAL; + } + + lp55xx_ms_to_prescale_and_step(dev_data, time_per_step, &prescale, &step_time); + + return lp55xx_program_command(dev, engine, command_index, + LP5562_PROG_COMMAND_RAMP_TIME(prescale, step_time), + LP5562_PROG_COMMAND_STEP_COUNT(fade_dir, step_count)); +} + +/* + * @brief Program a command to do nothing for the given time. + * + * @param dev LP55xx device. + * @param engine Engine to be programmed. + * @param command_index Index of the command in the program sequence. + * @param time Time to do nothing in milliseconds. + * + * @retval 0 On success. + * @retval -EINVAL If the passed arguments are invalid or out of range. + * @retval -EIO If the underlying I2C call fails. + */ +static inline int lp55xx_program_wait(const struct device *dev, enum lp55xx_led_sources engine, + uint8_t command_index, uint32_t time) +{ + /* + * A wait command is a ramp with the step_count set to 0. The fading + * direction does not matter in this case. + */ + return lp55xx_program_ramp(dev, engine, command_index, time, 0, LP55XX_FADE_UP); +} + +/* + * @brief Program a command to go back to the beginning of the program. + * + * Can be used at the end of a program to loop it infinitely. + * + * @param dev LP55xx device. + * @param engine Engine to be programmed. + * @param command_index Index of the command in the program sequence. + * + * @retval 0 On success. + * @retval -EINVAL If the given command index is out of range or an invalid + * engine is passed. + * @retval -EIO If the underlying I2C call fails. + */ +static inline int lp55xx_program_go_to_start(const struct device *dev, + enum lp55xx_led_sources engine, uint8_t command_index) +{ + return lp55xx_program_command(dev, engine, command_index, 0x00, 0x00); +} + +/* + * @brief Change the brightness of a running blink program. + * + * We know that the current program executes a blinking pattern + * consisting of following commands: + * + * - set_brightness high + * - wait on_delay + * - set_brightness low + * - wait off_delay + * - return to start + * + * In order to change the brightness during blinking, we overwrite only + * the first command and start execution again. + * + * @param dev LP55xx device. + * @param engine Engine running the blinking program. + * @param brightness_on New brightness value. + * + * @retval 0 On Success. + * @retval -EINVAL If the engine ID or brightness is out of range. + * @retval -EIO If the underlying I2C call fails. + */ +static int lp55xx_update_blinking_brightness(const struct device *dev, + enum lp55xx_led_sources engine, uint8_t brightness_on) +{ + int ret; + + ret = lp55xx_stop_program_exec(dev, engine); + if (ret) { + return ret; + } + + ret = lp55xx_set_engine_op_mode(dev, engine, LP55XX_OP_MODE_LOAD); + if (ret) { + return ret; + } + + ret = lp55xx_program_set_brightness(dev, engine, 0, brightness_on); + if (ret) { + return ret; + } + + ret = lp55xx_start_program_exec(dev, engine); + if (ret) { + LOG_ERR("Failed to execute program."); + return ret; + } + + return 0; +} + +static int lp55xx_led_blink(const struct device *dev, uint32_t led, uint32_t delay_on, + uint32_t delay_off) +{ + struct lp55xx_data *data = dev->data; + struct led_data *dev_data = &data->dev_data; + const struct lp55xx_config *config = dev->config; + int ret; + enum lp55xx_led_sources engine; + uint8_t command_index = 0U; + + if (led >= config->total_leds) { + return -EINVAL; + } + + ret = lp55xx_get_available_engine(dev, &engine, led); + if (ret) { + return ret; + } + + ret = lp55xx_set_led_source(dev, led, engine); + if (ret) { + LOG_ERR("Failed to set LED source."); + return ret; + } + + ret = lp55xx_set_engine_op_mode(dev, engine, LP55XX_OP_MODE_LOAD); + if (ret) { + return ret; + } + + ret = lp55xx_program_set_brightness(dev, engine, command_index, dev_data->max_brightness); + if (ret) { + return ret; + } + + ret = lp55xx_program_wait(dev, engine, ++command_index, delay_on); + if (ret) { + return ret; + } + + ret = lp55xx_program_set_brightness(dev, engine, ++command_index, dev_data->min_brightness); + if (ret) { + return ret; + } + + ret = lp55xx_program_wait(dev, engine, ++command_index, delay_off); + if (ret) { + return ret; + } + + ret = lp55xx_program_go_to_start(dev, engine, ++command_index); + if (ret) { + return ret; + } + + ret = lp55xx_start_program_exec(dev, engine); + if (ret) { + LOG_ERR("Failed to execute program."); + return ret; + } + + return 0; +} + +static int lp55xx_led_set_brightness(const struct device *dev, uint32_t led, uint8_t value) +{ + const struct lp55xx_config *config = dev->config; + struct lp55xx_data *data = dev->data; + struct led_data *dev_data = &data->dev_data; + int ret; + uint8_t val, reg; + enum lp55xx_led_sources current_source; + + if ((value < dev_data->min_brightness) || (value > dev_data->max_brightness) || + (led >= config->total_leds)) { + return -EINVAL; + } + + ret = lp55xx_get_led_source(dev, led, ¤t_source); + if (ret) { + return ret; + } + if (current_source != LP5562_SOURCE_PWM) { + if (lp55xx_is_engine_executing(dev, current_source)) { + /* + * LED is blinking currently. Restart the blinking with + * the passed brightness. + */ + return lp55xx_update_blinking_brightness(dev, current_source, value); + } + if (config->id == LP5562_ID) { + ret = lp55xx_set_led_source(dev, led, LP5562_SOURCE_PWM); + } else if (config->id == LP5521_ID) { + ret = lp55xx_set_led_source( + dev, led, (enum lp55xx_led_sources)LP55XX_OP_MODE_DIRECT_CTRL); + } + if (ret) { + return ret; + } + } + + val = (value * 0xFF) / dev_data->max_brightness; + + ret = lp55xx_get_pwm_reg(config, led, ®); + if (ret) { + return ret; + } + if (i2c_reg_write_byte_dt(&config->bus, reg, val)) { + LOG_ERR("LED write failed"); + return -EIO; + } + + return 0; +} + +static inline int lp55xx_led_on(const struct device *dev, uint32_t led) +{ + struct lp55xx_data *data = dev->data; + struct led_data *dev_data = &data->dev_data; + const struct lp55xx_config *config = dev->config; + + if (led >= config->total_leds) { + return -EINVAL; + } + return lp55xx_led_set_brightness(dev, led, dev_data->max_brightness); +} + +static inline int lp55xx_led_off(const struct device *dev, uint32_t led) +{ + struct lp55xx_data *data = dev->data; + struct led_data *dev_data = &data->dev_data; + const struct lp55xx_config *config = dev->config; + int ret; + enum lp55xx_led_sources current_source; + + if (led >= config->total_leds) { + return -EINVAL; + } + + ret = lp55xx_get_led_source(dev, led, ¤t_source); + if (ret) { + return ret; + } + + if (current_source != LP5562_SOURCE_PWM) { + ret = lp55xx_stop_program_exec(dev, current_source); + if (ret) { + return ret; + } + } + + return lp55xx_led_set_brightness(dev, led, dev_data->min_brightness); +} + +static int lp55xx_led_update_current(const struct device *dev) +{ + const struct lp55xx_config *config = dev->config; + int ret; + uint8_t tx_buf[4]; + + if (config->id == LP5562_ID) { + uint8_t buf[4] = {config->registers->blue_curr_reg, config->b_current, + config->g_current, config->r_current}; + memcpy(tx_buf, buf, sizeof(buf)); + } else if (config->id == LP5521_ID) { + uint8_t buf[4] = {config->registers->red_curr_reg, config->r_current, + config->g_current, config->b_current}; + memcpy(tx_buf, buf, sizeof(buf)); + } + + ret = i2c_write_dt(&config->bus, tx_buf, sizeof(tx_buf)); + if (ret) { + return ret; + } + if (config->id == LP5562_ID) { + ret = i2c_reg_write_byte_dt(&config->bus, config->registers->white_curr_reg, + config->w_current); + } + + return ret; +} + +static int lp55xx_led_init(const struct device *dev) +{ + const struct lp55xx_config *config = dev->config; + struct lp55xx_data *data = dev->data; + struct led_data *dev_data = &data->dev_data; + const struct gpio_dt_spec *en_pin = &config->en_pin; + int ret; + + if (en_pin != 0) { + if (!device_is_ready(en_pin->port)) { + return -ENODEV; + } + if (gpio_pin_configure_dt(en_pin, GPIO_OUTPUT_ACTIVE)) { + return -ENODEV; + } + if (gpio_pin_set_dt(en_pin, true)) { + return -ENODEV; + } + k_msleep(5); + } + + if (!device_is_ready(config->bus.bus)) { + LOG_ERR("I2C device not ready"); + return -ENODEV; + } + + /* Hardware specific limits */ + if (config->id == LP5521_ID) { + dev_data->min_period = LP5521_MIN_BLINK_PERIOD; + dev_data->max_period = LP5521_MAX_BLINK_PERIOD; + dev_data->min_brightness = LP5521_MIN_BRIGHTNESS; + dev_data->max_brightness = LP5521_MAX_BRIGHTNESS; + } else if (config->id == LP5562_ID) { + dev_data->min_period = LP5562_MIN_BLINK_PERIOD; + dev_data->max_period = LP5562_MAX_BLINK_PERIOD; + dev_data->min_brightness = LP5562_MIN_BRIGHTNESS; + dev_data->max_brightness = LP5562_MAX_BRIGHTNESS; + } + + ret = lp55xx_led_update_current(dev); + if (ret) { + LOG_ERR("Setting current setting LP5562 LED chip failed,err:%d", ret); + return ret; + } + + if (i2c_reg_write_byte_dt(&config->bus, config->registers->enable_reg, + LP5562_ENABLE_CHIP_EN)) { + LOG_ERR("Enabling LP5562 LED chip failed."); + return -EIO; + } + + if (config->id == LP5521_ID) { + if (i2c_reg_write_byte_dt(&config->bus, config->registers->config_reg, 0x11)) { + LOG_ERR("Configuring LP5562 LED chip failed."); + return -EIO; + } + if (i2c_reg_write_byte_dt(&config->bus, config->registers->op_mode_reg, 0x3f)) { + LOG_ERR("Disabling all engines failed."); + return -EIO; + } + + } else if (config->id == LP5562_ID) { + if (i2c_reg_write_byte_dt( + &config->bus, config->registers->config_reg, + (LP5562_CONFIG_INTERNAL_CLOCK | LP5562_CONFIG_PWRSAVE_EN))) { + LOG_ERR("Configuring LP5562 LED chip failed."); + return -EIO; + } + if (i2c_reg_write_byte_dt(&config->bus, config->registers->op_mode_reg, 0x00)) { + LOG_ERR("Disabling all engines failed."); + return -EIO; + } + if (i2c_reg_write_byte_dt(&config->bus, config->registers->led_map_reg, 0x00)) { + LOG_ERR("Setting all LEDs to manual control failed."); + return -EIO; + } + } + + return 0; +} + +static const struct led_driver_api lp55xx_led_api = { + .blink = lp55xx_led_blink, + .set_brightness = lp55xx_led_set_brightness, + .on = lp55xx_led_on, + .off = lp55xx_led_off, +}; + +#define LP55XX_DEFINE(n, idx) \ + BUILD_ASSERT(DT_INST_PROP(n, red_output_current) <= LP##idx##_MAX_CURRENT_SETTING, \ + "Red channel current is above max limit."); \ + BUILD_ASSERT(DT_INST_PROP(n, green_output_current) <= LP##idx##_MAX_CURRENT_SETTING, \ + "Green channel current is above max limit."); \ + BUILD_ASSERT(DT_INST_PROP(n, blue_output_current) <= LP##idx##_MAX_CURRENT_SETTING, \ + "Blue channel current is above max limit."); \ + BUILD_ASSERT(DT_INST_PROP_OR(n, white_output_current, 0) <= LP##idx##_MAX_CURRENT_SETTING, \ + "White channel current is above max limit."); \ + \ + static const struct lp55xx_config lp##idx##_config_##n = { \ + .id = idx, \ + .bus = I2C_DT_SPEC_INST_GET(n), \ + .r_current = DT_INST_PROP(n, red_output_current), \ + .g_current = DT_INST_PROP(n, green_output_current), \ + .b_current = DT_INST_PROP(n, blue_output_current), \ + .w_current = DT_INST_PROP_OR(n, white_output_current, 0), \ + .registers = &lp##idx##_reg, \ + .en_pin = GPIO_DT_SPEC_INST_GET_OR(n, en_gpios, {0}), \ + .total_leds = LP##idx##_MAX_LEDS, \ + }; \ + struct lp55xx_data lp##idx##_data_##n; \ + DEVICE_DT_INST_DEFINE(n, &lp55xx_led_init, NULL, &lp##idx##_data_##n, \ + &lp##idx##_config_##n, POST_KERNEL, CONFIG_LED_INIT_PRIORITY, \ + &lp55xx_led_api); + +#undef DT_DRV_COMPAT +#define DT_DRV_COMPAT ti_lp5562 +DT_INST_FOREACH_STATUS_OKAY_VARGS(LP55XX_DEFINE, LP5562_ID) + +#undef DT_DRV_COMPAT +#define DT_DRV_COMPAT ti_lp5521 +DT_INST_FOREACH_STATUS_OKAY_VARGS(LP55XX_DEFINE, LP5521_ID) diff --git a/drivers/led/lp55xx.h b/drivers/led/lp55xx.h new file mode 100644 index 0000000000000..3aad4deae293f --- /dev/null +++ b/drivers/led/lp55xx.h @@ -0,0 +1,151 @@ +/* + * Copyright (c) 2024 Croxel, Inc. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef LP55XX_H +#define LP55XX_H + +#define LP5521_ID 5521 +#define LP5562_ID 5562 + +/* LP5521 Registers */ +#define LP5521_ENABLE 0x00 +#define LP5521_OP_MODE 0x01 +#define LP5521_R_PWM 0x02 +#define LP5521_G_PWM 0x03 +#define LP5521_B_PWM 0x04 +#define LP5521_R_CURRENT 0x05 +#define LP5521_G_CURRENT 0x06 +#define LP5521_B_CURRENT 0x07 +#define LP5521_CONFIG 0x08 +#define LP5521_R_PC 0x09 +#define LP5521_G_PC 0x0A +#define LP5521_B_PC 0x0B +#define LP5521_STATUS 0x0C +#define LP5521_RESET 0x0D +#define LP5521_GPO 0x0E +#define LP5521_PROG_MEM_RED_ENG_BASE 0x10 +#define LP5521_PROG_MEM_GREEN_ENG_BASE 0x30 +#define LP5521_PROG_MEM_BLUE_ENG_BASE 0x50 + +/* LP5562 Registers */ +#define LP5562_ENABLE 0x00 +#define LP5562_OP_MODE 0x01 +#define LP5562_B_PWM 0x02 +#define LP5562_G_PWM 0x03 +#define LP5562_R_PWM 0x04 +#define LP5562_B_CURRENT 0x05 +#define LP5562_G_CURRENT 0x06 +#define LP5562_R_CURRENT 0x07 +#define LP5562_CONFIG 0x08 +#define LP5562_ENG1_PC 0x09 +#define LP5562_ENG2_PC 0x0A +#define LP5562_ENG3_PC 0x0B +#define LP5562_STATUS 0x0C +#define LP5562_RESET 0x0D +#define LP5562_W_PWM 0x0E +#define LP5562_W_CURRENT 0x0F +#define LP5562_PROG_MEM_ENG1_BASE 0x10 +#define LP5562_PROG_MEM_ENG2_BASE 0x30 +#define LP5562_PROG_MEM_ENG3_BASE 0x50 +#define LP5562_LED_MAP 0x70 + +/* Output current limits in 0.1 mA */ +#define LP5562_MIN_CURRENT_SETTING 0 +#define LP5562_MAX_CURRENT_SETTING 255 + +/* Output current limits in 0.1 mA */ +#define LP5521_MIN_CURRENT_SETTING 0 +#define LP5521_MAX_CURRENT_SETTING 255 + +#define LP5521_MAX_BLINK_PERIOD 1000 +/* + * The minimum waiting period is 0.49ms with the prescaler set to 0 and one + * step. We round up to a full millisecond. + */ +#define LP5521_MIN_BLINK_PERIOD 1 + +/* Brightness limits in percent */ +#define LP5521_MIN_BRIGHTNESS 0 +#define LP5521_MAX_BRIGHTNESS 100 + +#define LP5562_MAX_BLINK_PERIOD 1000 + +#define LP5562_MIN_BLINK_PERIOD 1 + +/* Brightness limits in percent */ +#define LP5562_MIN_BRIGHTNESS 0 +#define LP5562_MAX_BRIGHTNESS 100 + +/* Values for ENABLE register. */ +#define LP5562_ENABLE_CHIP_EN (1 << 6) +#define LP5562_ENABLE_LOG_EN (1 << 7) + +/* Values for CONFIG register. */ +#define LP5562_CONFIG_EXTERNAL_CLOCK 0x00 +#define LP5562_CONFIG_INTERNAL_CLOCK 0x01 +#define LP5562_CONFIG_CLOCK_AUTOMATIC_SELECT 0x02 +#define LP5562_CONFIG_PWRSAVE_EN (1 << 5) +/* Enable 558 Hz frequency for PWM. Default is 256. */ +#define LP5562_CONFIG_PWM_HW_FREQ_558 (1 << 6) + +/* Values for execution engine programs. */ +#define LP5562_PROG_COMMAND_SET_PWM (1 << 6) +#define LP5562_PROG_COMMAND_RAMP_TIME(prescale, step_time) (((prescale) << 6) | (step_time)) +#define LP5562_PROG_COMMAND_STEP_COUNT(fade_direction, count) (((fade_direction) << 7) | (count)) + +/* Helper definitions. */ +#define LP5562_PROG_MAX_COMMANDS 16 +#define LP55XX_MASK 0x03 +#define LP55XX_CHANNEL_MASK(channel) ((LP55XX_MASK) << (channel << 1)) + +#define LP5521_MAX_LEDS 3 +#define LP5562_MAX_LEDS 4 + +enum lp55xx_led_sources { + LP5562_SOURCE_PWM, + LP5562_SOURCE_ENGINE_1, + LP5562_SOURCE_ENGINE_2, + LP5562_SOURCE_ENGINE_3, + + LP5562_SOURCE_COUNT, +}; + +/* + * Available channels. LP55XX driver currently supports LP5521 and LP5562 which supports 3 LEDs(RGB) + * and 4 LEDs(RGBW) respectively + */ +enum lp55xx_led_channels { + LP55XX_CHANNEL_B, + LP55XX_CHANNEL_G, + LP55XX_CHANNEL_R, + LP55XX_CHANNEL_W, + + LP55XX_CHANNEL_COUNT, +}; + +/* Operational modes of the execution engines. */ +enum lp55xx_engine_op_modes { + LP55XX_OP_MODE_DISABLED = 0x00, + LP55XX_OP_MODE_LOAD = 0x01, + LP55XX_OP_MODE_RUN = 0x02, + LP55XX_OP_MODE_DIRECT_CTRL = 0x03, +}; + +/* Execution state of the engines. */ +enum lp55xx_engine_exec_states { + LP55XX_ENGINE_MODE_HOLD = 0x00, + LP55XX_ENGINE_MODE_STEP = 0x01, + LP55XX_ENGINE_MODE_RUN = 0x02, + LP55XX_ENGINE_MODE_EXEC = 0x03, +}; + +/* Fading directions for programs executed by the engines. */ +enum lp55xx_engine_fade_dirs { + LP55XX_FADE_UP = 0x00, + LP55XX_FADE_DOWN = 0x01, +}; + +#endif /* LP55XX_H */ diff --git a/dts/bindings/led/ti,lp5521.yaml b/dts/bindings/led/ti,lp5521.yaml new file mode 100644 index 0000000000000..5857c806b6910 --- /dev/null +++ b/dts/bindings/led/ti,lp5521.yaml @@ -0,0 +1,8 @@ +# Copyright (c) 2024 Croxel, Inc. +# SPDX-License-Identifier: Apache-2.0 + +description: TI LP5521 LED + +compatible: "ti,lp5521" + +include: ti,lp55xx.yaml diff --git a/dts/bindings/led/ti,lp5562.yaml b/dts/bindings/led/ti,lp5562.yaml index b2b0c4b484e8a..394bc84172ed8 100644 --- a/dts/bindings/led/ti,lp5562.yaml +++ b/dts/bindings/led/ti,lp5562.yaml @@ -2,26 +2,4 @@ description: TI LP5562 LED compatible: "ti,lp5562" -include: i2c-device.yaml - -properties: - red-output-current: - type: int - default: 175 - description: Output current of red channel in 0.1 mA (0-25.5 mA). - Default value is the power-on default. Valid range = 0 - 255 - green-output-current: - type: int - default: 175 - description: Output current of green channel in 0.1 mA (0-25.5 mA) - Default value is the power-on default. Valid range = 0 - 255 - blue-output-current: - type: int - default: 175 - description: Output current of blue channel in 0.1 mA (0-25.5 mA) - Default value is the power-on default. Valid range = 0 - 255 - white-output-current: - type: int - default: 175 - description: Output current of white channel in 0.1 mA (0-25.5 mA) - Default value is the power-on default. Valid range = 0 - 255 +include: ti,lp55xx.yaml diff --git a/dts/bindings/led/ti,lp55xx.yaml b/dts/bindings/led/ti,lp55xx.yaml new file mode 100644 index 0000000000000..7c380cbb18b31 --- /dev/null +++ b/dts/bindings/led/ti,lp55xx.yaml @@ -0,0 +1,35 @@ +# Copyright (c) 2024 Croxel, Inc. +# SPDX-License-Identifier: Apache-2.0 + +description: TI LP55XX LED + +include: i2c-device.yaml + +properties: + red-output-current: + type: int + default: 175 + description: Output current of red channel in 0.1 mA (0-25.5 mA). + Default value is the power-on default. Valid range = 0 - 255 + green-output-current: + type: int + default: 175 + description: Output current of green channel in 0.1 mA (0-25.5 mA) + Default value is the power-on default. Valid range = 0 - 255 + blue-output-current: + type: int + default: 175 + description: Output current of blue channel in 0.1 mA (0-25.5 mA) + Default value is the power-on default. Valid range = 0 - 255 + white-output-current: + type: int + default: 175 + description: Output current of white channel in 0.1 mA (0-25.5 mA) + Default value is the power-on default. Valid range = 0 - 255 + en-gpios: + type: phandle-array + description: | + GPIO to enable LP55XX (both Charge-pump and Digital Communications interface). + + If not provided, user must ensure enable pin is already asserted externally (e.g: + pull-up resistor). diff --git a/samples/drivers/led_lp5521/CMakeLists.txt b/samples/drivers/led_lp5521/CMakeLists.txt new file mode 100644 index 0000000000000..c5599c25b0f7e --- /dev/null +++ b/samples/drivers/led_lp5521/CMakeLists.txt @@ -0,0 +1,9 @@ +# SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 3.20.0) + +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) +project(led_lp5521) + +FILE(GLOB app_sources src/*.c) +target_sources(app PRIVATE ${app_sources}) diff --git a/samples/drivers/led_lp5521/README.rst b/samples/drivers/led_lp5521/README.rst new file mode 100644 index 0000000000000..9074d4acabb68 --- /dev/null +++ b/samples/drivers/led_lp5521/README.rst @@ -0,0 +1,31 @@ +.. zephyr:code-sample:: lp5521 + :name: LP5521 RGB LED + :relevant-api: led_interface + + Control 3 RGB LEDs connected to an LP5521 driver chip. + +Overview +******** + +This sample controls 3 LEDs connected to a TI LP5521 driver. The sample turns all +LEDs on and switches all LEDs off again within a one second interval. + +Refer to the `LP5521 Manual`_ for the RGB LED connections and color channel mappings used +by this sample. + +Building and Running +******************** + +Build the application for the :ref:`croxel_cx1825_nrf52840` board, which includes an +LP5521 LED driver. + +.. zephyr-app-commands:: + :zephyr-app: samples/drivers/led_LP5521 + :board: croxel_cx1825_nrf52840 + :goals: build + :compact: + +For flashing the application, refer to the Flashing section of the :ref:`croxel_cx1825_nrf52840` +board documentation. + +.. _LP5521 Manual: https://www.ti.com/lit/ds/symlink/lp5521.pdf diff --git a/samples/drivers/led_lp5521/prj.conf b/samples/drivers/led_lp5521/prj.conf new file mode 100644 index 0000000000000..df85f3b69dfb9 --- /dev/null +++ b/samples/drivers/led_lp5521/prj.conf @@ -0,0 +1,2 @@ +CONFIG_LOG=y +CONFIG_LED=y diff --git a/samples/drivers/led_lp5521/sample.yaml b/samples/drivers/led_lp5521/sample.yaml new file mode 100644 index 0000000000000..984f0b4e80259 --- /dev/null +++ b/samples/drivers/led_lp5521/sample.yaml @@ -0,0 +1,9 @@ +# Copyright (c) 2024 Croxel, Inc. +# SPDX-License-Identifier: Apache-2.0 +sample: + description: Demonstration of the LP5521 LED driver + name: LP5521 sample +tests: + sample.drivers.led.lp5521: + platform_allow: croxel_cx1825/nrf52840 + tags: led diff --git a/samples/drivers/led_lp5521/src/main.c b/samples/drivers/led_lp5521/src/main.c new file mode 100644 index 0000000000000..0255a48fcb492 --- /dev/null +++ b/samples/drivers/led_lp5521/src/main.c @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2024 Croxel, Inc. + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include +#include +#include +#include +#include + +#define LOG_LEVEL 4 +#include +LOG_MODULE_REGISTER(main); + +#define NUM_LEDS 3 +#define BLINK_DELAY_ON 500 +#define BLINK_DELAY_OFF 500 +#define DELAY_TIME 2000 +#define COLORS_TO_SHOW 8 +#define VALUES_PER_COLOR 3 +#define DELAY_TIME_ON K_MSEC(1000) +#define DELAY_TIME_BREATHING K_MSEC(20) + +/* + * We assume that the colors are connected the way it is described in the driver + * datasheet. + */ + +int main(void) +{ + const struct device *const dev = DEVICE_DT_GET_ANY(ti_lp5521); + int ret; + uint8_t i; + + if (!dev) { + LOG_ERR("No \"ti,lp5521\" device found"); + return 0; + } else if (!device_is_ready(dev)) { + LOG_ERR("LED device %s is not ready", dev->name); + return 0; + } + + for (i = 0; i < NUM_LEDS; i++) { + ret = led_off(dev, i); + if (ret) { + return ret; + } + } + LOG_INF("Testing leds"); + + /* + * Display a continuous pattern that turns on 3 LEDs at 1 s one by + * one until it reaches the end and turns off LEDs in reverse order. + */ + + while (1) { + + /* Turn on LEDs one by one */ + for (i = 0; i < NUM_LEDS; i++) { + ret = led_on(dev, i); + if (ret) { + return ret; + } + + k_sleep(DELAY_TIME_ON); + } + + /* Turn all LEDs off slowly to demonstrate set_brightness */ + for (i = 0; i <= 100; i++) { + for (int j = 0; j < NUM_LEDS; j++) { + ret = led_set_brightness(dev, j, 100 - i); + if (ret) { + return ret; + } + } + k_sleep(DELAY_TIME_BREATHING); + } + } + return 0; +} From 954a45f89eb395b7ceed16a1069c225247746a69 Mon Sep 17 00:00:00 2001 From: Ayush Kothari Date: Sat, 15 Jun 2024 18:30:34 +0530 Subject: [PATCH 2/2] boards: croxel: croxel_cx1825: Add LP5521 ic node -Add LP5521 node in croxel_cx1825 board dts file -Remove arduino_i2c and arduino_gpio from dts file to prevent shield sample build test Signed-off-by: Ayush Kothari --- boards/croxel/croxel_cx1825/croxel_cx1825_nrf52840.dts | 9 ++++++++- boards/croxel/croxel_cx1825/croxel_cx1825_nrf52840.yaml | 2 -- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/boards/croxel/croxel_cx1825/croxel_cx1825_nrf52840.dts b/boards/croxel/croxel_cx1825/croxel_cx1825_nrf52840.dts index fac78f10b712c..94273365826a9 100644 --- a/boards/croxel/croxel_cx1825/croxel_cx1825_nrf52840.dts +++ b/boards/croxel/croxel_cx1825/croxel_cx1825_nrf52840.dts @@ -81,7 +81,7 @@ pinctrl-names = "default", "sleep"; }; -arduino_i2c: &i2c0 { +&i2c0 { compatible = "nordic,nrf-twi"; status = "okay"; pinctrl-0 = <&i2c0_default>; @@ -114,6 +114,13 @@ arduino_i2c: &i2c0 { reg = <0x18>; irq-gpios = <&gpio1 15 GPIO_ACTIVE_HIGH>; }; + + lp5521: lp5521@32 { + status = "okay"; + compatible = "ti,lp5521"; + reg = <0x32>; + en-gpios = <&gpio1 13 GPIO_ACTIVE_HIGH>; + }; }; zephyr_udc0: &usbd { diff --git a/boards/croxel/croxel_cx1825/croxel_cx1825_nrf52840.yaml b/boards/croxel/croxel_cx1825/croxel_cx1825_nrf52840.yaml index 8fa7989a6eb6f..bc550fd4cc449 100644 --- a/boards/croxel/croxel_cx1825/croxel_cx1825_nrf52840.yaml +++ b/boards/croxel/croxel_cx1825/croxel_cx1825_nrf52840.yaml @@ -9,8 +9,6 @@ toolchain: - gnuarmemb - xtools supported: - - arduino_gpio - - arduino_i2c - ble - counter - gpio