zephyr/drivers/led/lp5562.c

1120 lines
30 KiB
C

/*
* 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 <zephyr/drivers/i2c.h>
#include <zephyr/drivers/led.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/device.h>
#include <zephyr/kernel.h>
#include <zephyr/pm/device.h>
#define LOG_LEVEL CONFIG_LED_LOG_LEVEL
#include <zephyr/logging/log.h>
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, &current_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, &reg);
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, &current_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)