// 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),
};
