// SPDX-License-Identifier: GPL-2.0+ /* * Copyright (C) 2018 Doug Zobel * * 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 #include #include #include #include #include #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 μs * (typical)." and "Delay between consecutive I2C writes to * OP_MODE register need to be longer than 153 μs (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), };