blob: a5776d3174fc83397dcdf63bb89eb48a8d8b1dfe [file] [log] [blame]
// SPDX-License-Identifier: GPL-2.0+
/*
* Copyright (C) 2018 Doug Zobel <douglas.zobel@climate.com>
*
* Driver for TI lp5562 4 channel LED driver. There are only 3
* engines available for the 4 LEDs, so white and blue LEDs share
* the same engine. This means that the blink period is shared
* between them. Changing the period of blue blink will affect
* the white period (and vice-versa). Blue and white On/Off
* states remain independent (as would PWM brightness if that's
* ever added to the LED core).
*/
#include <dm.h>
#include <errno.h>
#include <led.h>
#include <i2c.h>
#include <asm/gpio.h>
#include <linux/delay.h>
#define DEFAULT_CURRENT 100 /* 10 mA */
#define MIN_BLINK_PERIOD 32 /* ms */
#define MAX_BLINK_PERIOD 2248 /* ms */
/* Register Map */
#define REG_ENABLE 0x00
#define REG_OP_MODE 0x01
#define REG_B_PWM 0x02
#define REG_G_PWM 0x03
#define REG_R_PWM 0x04
#define REG_B_CUR 0x05
#define REG_G_CUR 0x06
#define REG_R_CUR 0x07
#define REG_CONFIG 0x08
#define REG_ENG1_PC 0x09
#define REG_ENG2_PC 0x0A
#define REG_ENG3_PC 0x0B
#define REG_STATUS 0x0C
#define REG_RESET 0x0D
#define REG_W_PWM 0x0E
#define REG_W_CUR 0x0F
#define REG_ENG1_MEM_BEGIN 0x10
#define REG_ENG2_MEM_BEGIN 0x30
#define REG_ENG3_MEM_BEGIN 0x50
#define REG_LED_MAP 0x70
/* LED Register Values */
/* 0x00 ENABLE */
#define REG_ENABLE_CHIP_ENABLE (0x1 << 6)
#define REG_ENABLE_ENG_EXEC_HOLD 0x0
#define REG_ENABLE_ENG_EXEC_RUN 0x2
#define REG_ENABLE_ENG_EXEC_MASK 0x3
/* 0x01 OP MODE */
#define REG_OP_MODE_DISABLED 0x0
#define REG_OP_MODE_LOAD_SRAM 0x1
#define REG_OP_MODE_RUN 0x2
#define REG_OP_MODE_MASK 0x3
/* 0x02, 0x03, 0x04, 0x0E PWM */
#define REG_PWM_MIN_VALUE 0
#define REG_PWM_MAX_VALUE 0xFF
/* 0x08 CONFIG */
#define REG_CONFIG_EXT_CLK 0x0
#define REG_CONFIG_INT_CLK 0x1
#define REG_CONFIG_AUTO_CLK 0x2
#define REG_CONFIG_CLK_MASK 0x3
/* 0x0D RESET */
#define REG_RESET_RESET 0xFF
/* 0x70 LED MAP */
#define REG_LED_MAP_ENG_MASK 0x03
#define REG_LED_MAP_W_ENG_SHIFT 6
#define REG_LED_MAP_R_ENG_SHIFT 4
#define REG_LED_MAP_G_ENG_SHIFT 2
#define REG_LED_MAP_B_ENG_SHIFT 0
/* Engine program related */
#define REG_ENGINE_MEM_SIZE 0x20
#define LED_PGRM_RAMP_INCREMENT_SHIFT 0
#define LED_PGRM_RAMP_SIGN_SHIFT 7
#define LED_PGRM_RAMP_STEP_SHIFT 8
#define LED_PGRM_RAMP_PRESCALE_SHIFT 14
struct lp5562_led_wrap_priv {
struct gpio_desc enable_gpio;
};
struct lp5562_led_priv {
u8 reg_pwm;
u8 reg_current;
u8 map_shift;
u8 enginenum;
};
/* enum values map to LED_MAP (0x70) values */
enum lp5562_led_ctl_mode {
I2C = 0x0,
#ifdef CONFIG_LED_BLINK
ENGINE1 = 0x1,
ENGINE2 = 0x2,
ENGINE3 = 0x3
#endif
};
/*
* Update a register value
* dev - I2C udevice (parent of led)
* regnum - register number to update
* value - value to write to register
* mask - mask of bits that should be changed
*/
static int lp5562_led_reg_update(struct udevice *dev, int regnum,
u8 value, u8 mask)
{
int ret;
if (mask == 0xFF)
ret = dm_i2c_reg_write(dev, regnum, value);
else
ret = dm_i2c_reg_clrset(dev, regnum, mask, value);
/*
* Data sheet says "Delay between consecutive I2C writes to
* ENABLE register (00h) need to be longer than 488 us
* (typical)." and "Delay between consecutive I2C writes to
* OP_MODE register need to be longer than 153 us (typ)."
*
* The linux driver does usleep_range(500, 600) and
* usleep_range(200, 300), respectively.
*/
switch (regnum) {
case REG_ENABLE:
udelay(600);
break;
case REG_OP_MODE:
udelay(300);
break;
}
return ret;
}
#ifdef CONFIG_LED_BLINK
/*
* Program the lp5562 engine
* dev - I2C udevice (parent of led)
* program - array of commands
* size - number of commands in program array (1-16)
* engine - engine number (1-3)
*/
static int lp5562_led_program_engine(struct udevice *dev, u16 *program,
u8 size, u8 engine)
{
int ret, cmd;
u8 engine_reg = REG_ENG1_MEM_BEGIN +
((engine - 1) * REG_ENGINE_MEM_SIZE);
u8 shift = (3 - engine) * 2;
__be16 prog_be[16];
if (size < 1 || size > 16 || engine < 1 || engine > 3)
return -EINVAL;
for (cmd = 0; cmd < size; cmd++)
prog_be[cmd] = cpu_to_be16(program[cmd]);
/* set engine mode to 'disabled' */
ret = lp5562_led_reg_update(dev, REG_OP_MODE,
REG_OP_MODE_DISABLED << shift,
REG_OP_MODE_MASK << shift);
if (ret != 0)
goto done;
/* set exec mode to 'hold' */
ret = lp5562_led_reg_update(dev, REG_ENABLE,
REG_ENABLE_ENG_EXEC_HOLD << shift,
REG_ENABLE_ENG_EXEC_MASK << shift);
if (ret != 0)
goto done;
/* set engine mode to 'load SRAM' */
ret = lp5562_led_reg_update(dev, REG_OP_MODE,
REG_OP_MODE_LOAD_SRAM << shift,
REG_OP_MODE_MASK << shift);
if (ret != 0)
goto done;
/* send the re-ordered program sequence */
ret = dm_i2c_write(dev, engine_reg, (uchar *)prog_be, sizeof(u16) * size);
if (ret != 0)
goto done;
/* set engine mode to 'run' */
ret = lp5562_led_reg_update(dev, REG_OP_MODE,
REG_OP_MODE_RUN << shift,
REG_OP_MODE_MASK << shift);
if (ret != 0)
goto done;
/* set engine exec to 'run' */
ret = lp5562_led_reg_update(dev, REG_ENABLE,
REG_ENABLE_ENG_EXEC_RUN << shift,
REG_ENABLE_ENG_EXEC_MASK << shift);
done:
return ret;
}
/*
* Get the LED's current control mode (I2C or ENGINE[1-3])
* dev - led udevice (child udevice)
*/
static enum lp5562_led_ctl_mode lp5562_led_get_control_mode(struct udevice *dev)
{
struct lp5562_led_priv *priv = dev_get_priv(dev);
u8 data;
enum lp5562_led_ctl_mode mode = I2C;
if (dm_i2c_read(dev_get_parent(dev), REG_LED_MAP, &data, 1) == 0)
mode = (data & (REG_LED_MAP_ENG_MASK << priv->map_shift))
>> priv->map_shift;
return mode;
}
#endif
/*
* Set the LED's control mode to I2C or ENGINE[1-3]
* dev - led udevice (child udevice)
* mode - mode to change to
*/
static int lp5562_led_set_control_mode(struct udevice *dev,
enum lp5562_led_ctl_mode mode)
{
struct lp5562_led_priv *priv = dev_get_priv(dev);
return (lp5562_led_reg_update(dev_get_parent(dev), REG_LED_MAP,
mode << priv->map_shift,
REG_LED_MAP_ENG_MASK << priv->map_shift));
}
/*
* Return the LED's PWM value; If LED is in BLINK state, then it is
* under engine control mode which doesn't use this PWM value.
* dev - led udevice (child udevice)
*/
static int lp5562_led_get_pwm(struct udevice *dev)
{
struct lp5562_led_priv *priv = dev_get_priv(dev);
u8 data;
if (dm_i2c_read(dev_get_parent(dev), priv->reg_pwm, &data, 1) != 0)
return -EINVAL;
return data;
}
/*
* Set the LED's PWM value and configure it to use this (I2C mode).
* dev - led udevice (child udevice)
* value - PWM value (0 - 255)
*/
static int lp5562_led_set_pwm(struct udevice *dev, u8 value)
{
struct lp5562_led_priv *priv = dev_get_priv(dev);
if (lp5562_led_reg_update(dev_get_parent(dev), priv->reg_pwm,
value, 0xff) != 0)
return -EINVAL;
/* set LED to I2C register mode */
return lp5562_led_set_control_mode(dev, I2C);
}
/*
* Return the led's current state
* dev - led udevice (child udevice)
*
*/
static enum led_state_t lp5562_led_get_state(struct udevice *dev)
{
enum led_state_t state = LEDST_ON;
if (lp5562_led_get_pwm(dev) == REG_PWM_MIN_VALUE)
state = LEDST_OFF;
#ifdef CONFIG_LED_BLINK
if (lp5562_led_get_control_mode(dev) != I2C)
state = LEDST_BLINK;
#endif
return state;
}
/*
* Set the led state
* dev - led udevice (child udevice)
* state - State to set the LED to
*/
static int lp5562_led_set_state(struct udevice *dev, enum led_state_t state)
{
#ifdef CONFIG_LED_BLINK
struct lp5562_led_priv *priv = dev_get_priv(dev);
#endif
switch (state) {
case LEDST_OFF:
return lp5562_led_set_pwm(dev, REG_PWM_MIN_VALUE);
case LEDST_ON:
return lp5562_led_set_pwm(dev, REG_PWM_MAX_VALUE);
#ifdef CONFIG_LED_BLINK
case LEDST_BLINK:
return lp5562_led_set_control_mode(dev, priv->enginenum);
#endif
case LEDST_TOGGLE:
if (lp5562_led_get_state(dev) == LEDST_OFF)
return lp5562_led_set_state(dev, LEDST_ON);
else
return lp5562_led_set_state(dev, LEDST_OFF);
break;
default:
return -EINVAL;
}
return 0;
}
#ifdef CONFIG_LED_BLINK
/*
* Set the blink period of an LED; note blue and white share the same
* engine so changing the period of one affects the other.
* dev - led udevice (child udevice)
* period_ms - blink period in ms
*/
static int lp5562_led_set_period(struct udevice *dev, int period_ms)
{
struct lp5562_led_priv *priv = dev_get_priv(dev);
u8 opcode = 0;
u16 program[7];
u16 wait_time;
/* Blink is implemented as an engine program. Simple on/off
* for short periods, or fade in/fade out for longer periods:
*
* if (period_ms < 500):
* set PWM to 100%
* pause for period / 2
* set PWM to 0%
* pause for period / 2
* goto start
*
* else
* raise PWM 0% -> 50% in 62.7 ms
* raise PWM 50% -> 100% in 62.7 ms
* pause for (period - 4 * 62.7) / 2
* lower PWM 100% -> 50% in 62.7 ms
* lower PWM 50% -> 0% in 62.7 ms
* pause for (period - 4 * 62.7) / 2
* goto start
*/
if (period_ms < MIN_BLINK_PERIOD)
period_ms = MIN_BLINK_PERIOD;
else if (period_ms > MAX_BLINK_PERIOD)
period_ms = MAX_BLINK_PERIOD;
if (period_ms < 500) {
/* Simple on/off blink */
wait_time = period_ms / 2;
/* 1st command is full brightness */
program[opcode++] =
(1 << LED_PGRM_RAMP_PRESCALE_SHIFT) |
REG_PWM_MAX_VALUE;
/* 2nd command is wait (period / 2) using 15.6ms steps */
program[opcode++] =
(1 << LED_PGRM_RAMP_PRESCALE_SHIFT) |
(((wait_time * 10) / 156) << LED_PGRM_RAMP_STEP_SHIFT) |
(0 << LED_PGRM_RAMP_INCREMENT_SHIFT);
/* 3rd command is 0% brightness */
program[opcode++] =
(1 << LED_PGRM_RAMP_PRESCALE_SHIFT);
/* 4th command is wait (period / 2) using 15.6ms steps */
program[opcode++] =
(1 << LED_PGRM_RAMP_PRESCALE_SHIFT) |
(((wait_time * 10) / 156) << LED_PGRM_RAMP_STEP_SHIFT) |
(0 << LED_PGRM_RAMP_INCREMENT_SHIFT);
/* 5th command: repeat */
program[opcode++] = 0x00;
} else {
/* fade-in / fade-out blink */
wait_time = ((period_ms - 251) / 2);
/* ramp up time is 256 * 0.49ms (125.4ms) done in 2 steps */
/* 1st command is ramp up 1/2 way */
program[opcode++] =
(0 << LED_PGRM_RAMP_PRESCALE_SHIFT) |
(1 << LED_PGRM_RAMP_STEP_SHIFT) |
(127 << LED_PGRM_RAMP_INCREMENT_SHIFT);
/* 2nd command is ramp up rest of the way */
program[opcode++] =
(0 << LED_PGRM_RAMP_PRESCALE_SHIFT) |
(1 << LED_PGRM_RAMP_STEP_SHIFT) |
(127 << LED_PGRM_RAMP_INCREMENT_SHIFT);
/* 3rd: wait ((period - 2 * ramp_time) / 2) (15.6ms steps) */
program[opcode++] =
(1 << LED_PGRM_RAMP_PRESCALE_SHIFT) |
(((wait_time * 10) / 156) << LED_PGRM_RAMP_STEP_SHIFT) |
(0 << LED_PGRM_RAMP_INCREMENT_SHIFT);
/* ramp down is same as ramp up with sign bit set */
/* 4th command is ramp down 1/2 way */
program[opcode++] =
(0 << LED_PGRM_RAMP_PRESCALE_SHIFT) |
(1 << LED_PGRM_RAMP_STEP_SHIFT) |
(1 << LED_PGRM_RAMP_SIGN_SHIFT) |
(127 << LED_PGRM_RAMP_INCREMENT_SHIFT);
/* 5th command is ramp down rest of the way */
program[opcode++] =
(0 << LED_PGRM_RAMP_PRESCALE_SHIFT) |
(1 << LED_PGRM_RAMP_STEP_SHIFT) |
(1 << LED_PGRM_RAMP_SIGN_SHIFT) |
(127 << LED_PGRM_RAMP_INCREMENT_SHIFT);
/* 6th: wait ((period - 2 * ramp_time) / 2) (15.6ms steps) */
program[opcode++] =
(1 << LED_PGRM_RAMP_PRESCALE_SHIFT) |
(((wait_time * 10) / 156) << LED_PGRM_RAMP_STEP_SHIFT) |
(0 << LED_PGRM_RAMP_INCREMENT_SHIFT);
/* 7th command: repeat */
program[opcode++] = 0x00;
}
return lp5562_led_program_engine(dev_get_parent(dev), program,
opcode, priv->enginenum);
}
#endif
static const struct led_ops lp5562_led_ops = {
.get_state = lp5562_led_get_state,
.set_state = lp5562_led_set_state,
#ifdef CONFIG_LED_BLINK
.set_period = lp5562_led_set_period,
#endif
};
static int lp5562_led_probe(struct udevice *dev)
{
struct lp5562_led_priv *priv = dev_get_priv(dev);
u8 current;
int ret = 0;
/* Child LED nodes */
switch (dev_read_addr(dev)) {
case 0:
priv->reg_current = REG_R_CUR;
priv->reg_pwm = REG_R_PWM;
priv->map_shift = REG_LED_MAP_R_ENG_SHIFT;
priv->enginenum = 1;
break;
case 1:
priv->reg_current = REG_G_CUR;
priv->reg_pwm = REG_G_PWM;
priv->map_shift = REG_LED_MAP_G_ENG_SHIFT;
priv->enginenum = 2;
break;
case 2:
priv->reg_current = REG_B_CUR;
priv->reg_pwm = REG_B_PWM;
priv->map_shift = REG_LED_MAP_B_ENG_SHIFT;
priv->enginenum = 3; /* shared with white */
break;
case 3:
priv->reg_current = REG_W_CUR;
priv->map_shift = REG_LED_MAP_W_ENG_SHIFT;
priv->enginenum = 3; /* shared with blue */
break;
default:
return -EINVAL;
}
current = dev_read_u8_default(dev, "max-cur", DEFAULT_CURRENT);
ret = lp5562_led_reg_update(dev_get_parent(dev), priv->reg_current,
current, 0xff);
return ret;
}
static int lp5562_led_bind(struct udevice *dev)
{
struct led_uc_plat *uc_plat = dev_get_uclass_plat(dev);
/*
* For the child nodes, parse a "chan-name" property, since
* the DT bindings for this device use that instead of
* "label".
*/
uc_plat->label = dev_read_string(dev, "chan-name");
return 0;
}
U_BOOT_DRIVER(lp5562_led) = {
.name = "lp5562-led",
.id = UCLASS_LED,
.bind = lp5562_led_bind,
.probe = lp5562_led_probe,
.priv_auto = sizeof(struct lp5562_led_priv),
.ops = &lp5562_led_ops,
};
static int lp5562_led_wrap_probe(struct udevice *dev)
{
struct lp5562_led_wrap_priv *priv = dev_get_priv(dev);
u8 clock_mode;
int ret;
/* Enable gpio if needed */
if (gpio_request_by_name(dev, "enabled-gpios", 0,
&priv->enable_gpio, GPIOD_IS_OUT) == 0) {
dm_gpio_set_value(&priv->enable_gpio, 1);
udelay(1000);
}
/* Ensure all registers have default values. */
ret = lp5562_led_reg_update(dev, REG_RESET, REG_RESET_RESET, 0xff);
if (ret)
return ret;
udelay(10000);
/* Enable the chip */
ret = lp5562_led_reg_update(dev, REG_ENABLE, REG_ENABLE_CHIP_ENABLE, 0xff);
if (ret)
return ret;
/*
* The DT bindings say 0=auto, 1=internal, 2=external, while
* the register[0:1] values are 0=external, 1=internal,
* 2=auto.
*/
clock_mode = dev_read_u8_default(dev, "clock-mode", 0);
ret = lp5562_led_reg_update(dev, REG_CONFIG, 2 - clock_mode, REG_CONFIG_CLK_MASK);
return ret;
}
static int lp5562_led_wrap_bind(struct udevice *dev)
{
return led_bind_generic(dev, "lp5562-led");
}
static const struct udevice_id lp5562_led_ids[] = {
{ .compatible = "ti,lp5562" },
{ /* sentinel */ }
};
U_BOOT_DRIVER(lp5562_led_wrap) = {
.name = "lp5562-led-wrap",
.id = UCLASS_NOP,
.of_match = lp5562_led_ids,
.bind = lp5562_led_wrap_bind,
.probe = lp5562_led_wrap_probe,
.priv_auto = sizeof(struct lp5562_led_wrap_priv),
};