/* * 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 #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_MASK (1 << 6) #define LP5562_ENABLE_CHIP_EN_SET (1 << 6) #define LP5562_ENABLE_CHIP_EN_CLR (0 << 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 gpio_dt_spec enable_gpio; }; 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; /* * Read current "led" source setting. This is to check * whether the "led" is in PWM mode or using an Engine. */ ret = lp5562_get_led_source(dev, led, &engine); if (ret) { return ret; } /* Find and assign new engine only if the "led" is not using any. */ if (engine == LP5562_SOURCE_PWM) { 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_enable(const struct device *dev, bool soft_reset) { const struct lp5562_config *config = dev->config; const struct gpio_dt_spec *enable_gpio = &config->enable_gpio; int err = 0; /* If ENABLE_GPIO control is enabled, we need to assert ENABLE_GPIO first. */ if (enable_gpio->port != NULL) { err = gpio_pin_set_dt(enable_gpio, 1); if (err) { LOG_ERR("%s: failed to set enable GPIO 1", dev->name); return err; } /* * The I2C host should allow at least 1ms before sending data to * the LP5562 after the rising edge of the enable line. * So let's wait for 1 ms. */ k_sleep(K_MSEC(1)); } if (soft_reset) { /* Reset all internal registers to have a deterministic state. */ err = i2c_reg_write_byte_dt(&config->bus, LP5562_RESET, 0xFF); if (err) { LOG_ERR("%s: failed to soft-reset device", dev->name); return err; } } /* Set en bit in LP5562_ENABLE register. */ err = i2c_reg_update_byte_dt(&config->bus, LP5562_ENABLE, LP5562_ENABLE_CHIP_EN_MASK, LP5562_ENABLE_CHIP_EN_SET); if (err) { LOG_ERR("%s: failed to set EN Bit in ENABLE register", dev->name); return err; } /* Allow 500 µs delay after setting chip_en bit to '1'. */ k_sleep(K_USEC(500)); return 0; } #ifdef CONFIG_PM_DEVICE static int lp5562_disable(const struct device *dev) { const struct lp5562_config *config = dev->config; const struct gpio_dt_spec *enable_gpio = &config->enable_gpio; int err = 0; /* clear en bit in register configurations */ err = i2c_reg_update_byte_dt(&config->bus, LP5562_ENABLE, LP5562_ENABLE_CHIP_EN_MASK, LP5562_ENABLE_CHIP_EN_CLR); if (err) { LOG_ERR("%s: failed to clear EN Bit in ENABLE register", dev->name); return err; } /* if gpio control is enabled, we can de-assert EN_GPIO now */ if (enable_gpio->port != NULL) { err = gpio_pin_set_dt(enable_gpio, 0); if (err) { LOG_ERR("%s: failed to set enable GPIO to 0", dev->name); return err; } } return 0; } #endif 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; const struct gpio_dt_spec *enable_gpio = &config->enable_gpio; int ret; if (enable_gpio->port != NULL) { if (!gpio_is_ready_dt(enable_gpio)) { return -ENODEV; } ret = gpio_pin_configure_dt(enable_gpio, GPIO_OUTPUT); if (ret) { LOG_ERR("LP5562 Enable GPIO Config failed"); return ret; } } if (!device_is_ready(config->bus.bus)) { LOG_ERR("I2C device not ready"); return -ENODEV; } ret = lp5562_enable(dev, true); if (ret) { return ret; } /* 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_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, }; #ifdef CONFIG_PM_DEVICE static int lp5562_pm_action(const struct device *dev, enum pm_device_action action) { switch (action) { case PM_DEVICE_ACTION_SUSPEND: return lp5562_disable(dev); case PM_DEVICE_ACTION_RESUME: return lp5562_enable(dev, false); default: return -ENOTSUP; } } #endif /* CONFIG_PM_DEVICE */ #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), \ .enable_gpio = GPIO_DT_SPEC_INST_GET_OR(id, enable_gpios, {0}), \ }; \ \ PM_DEVICE_DT_INST_DEFINE(id, lp5562_pm_action); \ \ struct lp5562_data lp5562_data_##id; \ DEVICE_DT_INST_DEFINE(id, &lp5562_led_init, PM_DEVICE_DT_INST_GET(id), \ &lp5562_data_##id, \ &lp5562_config_##id, POST_KERNEL, \ CONFIG_LED_INIT_PRIORITY, \ &lp5562_led_api); \ DT_INST_FOREACH_STATUS_OKAY(LP5562_DEFINE)