From patchwork Thu Jan 10 20:05:57 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Doug Zobel X-Patchwork-Id: 1023199 X-Patchwork-Delegate: trini@ti.com Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Authentication-Results: ozlabs.org; spf=none (mailfrom) smtp.mailfrom=lists.denx.de (client-ip=81.169.180.215; helo=lists.denx.de; envelope-from=u-boot-bounces@lists.denx.de; receiver=) Authentication-Results: ozlabs.org; dmarc=fail (p=none dis=none) header.from=climate.com Received: from lists.denx.de (dione.denx.de [81.169.180.215]) by ozlabs.org (Postfix) with ESMTP id 43bHBm0s5wz9sCr for ; Fri, 11 Jan 2019 07:09:04 +1100 (AEDT) Received: by lists.denx.de (Postfix, from userid 105) id CF869C2216F; Thu, 10 Jan 2019 20:08:36 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on lists.denx.de X-Spam-Level: X-Spam-Status: No, score=0.0 required=5.0 tests=RCVD_IN_DNSWL_BLOCKED, SPF_HELO_PASS autolearn=unavailable autolearn_force=no version=3.4.0 Received: from lists.denx.de (localhost [IPv6:::1]) by lists.denx.de (Postfix) with ESMTP id 94FFEC22125; Thu, 10 Jan 2019 20:08:12 +0000 (UTC) Received: by lists.denx.de (Postfix, from userid 105) id 23B12C220F3; Thu, 10 Jan 2019 20:07:23 +0000 (UTC) Received: from cluster-g.mailcontrol.com (cluster-g.mailcontrol.com [208.87.233.190]) by lists.denx.de (Postfix) with ESMTPS id 5A78EC220C7 for ; Thu, 10 Jan 2019 20:07:22 +0000 (UTC) Received: from mail-it1-f197.google.com (mail-it1-f197.google.com [209.85.166.197]) by rly31g.srv.mailcontrol.com (MailControl) with ESMTPS id x0AK7HP1030040 (version=TLSv1.2 cipher=ECDHE-RSA-AES256-GCM-SHA384 bits=256 verify=OK) for ; Thu, 10 Jan 2019 20:07:17 GMT Received: by mail-it1-f197.google.com with SMTP id x82so322643ita.9 for ; Thu, 10 Jan 2019 12:07:17 -0800 (PST) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:mime-version:from:to:cc:subject:date:message-id :in-reply-to:references; bh=WHbo3zdrP+UXQKjA+/KD5bt76PeMewht+aGdjDH4kaY=; b=MtZBBvDn1463yTJ2Yb2YFibnR0AVS2PhD34oer7AV3Yf+G9GE4xAMZPXTxMhtjD46b Wy9ftRR6sYpPslGieuZk0BIUD+SjnTdt98ffPDSZuDJWOlgw3fsqyMP7qHLeOvqzmeW+ oFbRel7MqOJtEb6MP43AplzY5I4QkL6kjancjY6wquPNJmVequNp4ncoPqnMfYV+AVQZ ZhOFiBsJT70m4XJzVL4FGLo1zJg7N5vKcQ691Fy1knA3u11ZHWvDbrhxQACWIvkSAPCb cw4WiOB62H66aIzPwVxGVWs/0CzXvWMe0RBm0lDn0HrVvi0JnUKi5CwUalWA/j1mRysi tv0g== X-Gm-Message-State: AJcUukf2Zr4ynybNrbx0MFP3gQhzyYm3j2Rnn98+98dpLgMykI/56UtN cUy/6GueyPInmODirmXQYt+I7UNcdjqrPOZOcsoTbzZlNpkq9wb/h01+F1v0dOdkWssJ3mzZJKw nGRK6c4RKIyjJCmh2XV1TMce7XZRxekZ0U7jo3wmK X-Received: by 2002:a24:7c97:: with SMTP id a145mr197771itd.92.1547150836737; Thu, 10 Jan 2019 12:07:16 -0800 (PST) MIME-Version: 1.0 X-Google-Smtp-Source: ALg8bN5Lc9sLFzWyve6UtOBFepvW3DWgCpN49TZcRMCUb/HSDswy5qrWAubvjx09IeqXUItG38hVqg== X-Received: by 2002:a24:7c97:: with SMTP id a145mr197737itd.92.1547150836156; Thu, 10 Jan 2019 12:07:16 -0800 (PST) Received: from zobel.corp.climate.com ([96.74.128.129]) by smtp.gmail.com with ESMTPSA id b192sm11072076itb.12.2019.01.10.12.07.15 (version=TLS1_2 cipher=ECDHE-RSA-AES128-SHA bits=128/128); Thu, 10 Jan 2019 12:07:15 -0800 (PST) From: Doug Zobel To: u-boot@lists.denx.de Date: Thu, 10 Jan 2019 14:05:57 -0600 Message-Id: <1547150757-1561-2-git-send-email-douglas.zobel@climate.com> X-Mailer: git-send-email 2.7.4 In-Reply-To: <1547150757-1561-1-git-send-email-douglas.zobel@climate.com> References: <1547150757-1561-1-git-send-email-douglas.zobel@climate.com> X-MailControl-OutInfo: MTU0NzE1MDgzODpGUEtleTEucHJpdjpLhZe8gXpoh5SoU+x0DhtrtGxIxF+qyVOYDA/ui2oGjbY40ZKKx6rTF0pkyKMRZZuQumfk6zjq71o90VwZyEhmcsypzasarJwp5xoY3Lh0o/CtsjFQgc8gTQ2oDrsXNUsyOl3L8Ug/yRPQsrh/ZdGNIA1muNycav+JDI93MsvKyrO5uMd16BF7Fim2nuzMYkmtONVB9DlfVEnJp1sIKfIonytGPWEdGVBi4iEpRstFoZYWXEhSgTtfRypxsyc/tYdazbZCDi6AwgKxs/LBIIfdVTuWHYeKLM3Bjl9Y9+UTh/vwgw9EuKiFz5azN8q9KkjBWGBEgjfOCdIf6HCesV4N X-Scanned-By: MailControl 44278.2058 (www.mailcontrol.com) on 10.71.0.141 X-Mailman-Approved-At: Thu, 10 Jan 2019 20:08:10 +0000 Subject: [U-Boot] [PATCH v1] dm: led: add TI LP5562 LED driver X-BeenThere: u-boot@lists.denx.de X-Mailman-Version: 2.1.18 Precedence: list List-Id: U-Boot discussion List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: u-boot-bounces@lists.denx.de Sender: "U-Boot" Driver for the TI LP5562 4 channel LED controller. Supports independent on/off control of all 4 channels. Supports LED_BLINK on 3 independent channels: blue/green/red. The white channel can blink, but shares the blue channel blink rate. --- doc/device-tree-bindings/leds/leds-lp5562.txt | 61 +++ drivers/led/Kconfig | 8 + drivers/led/Makefile | 1 + drivers/led/led_lp5562.c | 571 ++++++++++++++++++++++++++ 4 files changed, 641 insertions(+) create mode 100644 doc/device-tree-bindings/leds/leds-lp5562.txt create mode 100644 drivers/led/led_lp5562.c diff --git a/doc/device-tree-bindings/leds/leds-lp5562.txt b/doc/device-tree-bindings/leds/leds-lp5562.txt new file mode 100644 index 0000000..d6e3192 --- /dev/null +++ b/doc/device-tree-bindings/leds/leds-lp5562.txt @@ -0,0 +1,61 @@ +LEDs connected to TI LP5562 controller + +This driver works with a TI LP5562 4-channel LED controller. +CONFIG_LED_BLINK is supported using the controller engines. However +there are only 3 engines available for the 4 channels. This means +that the blue and white channels share the same engine. When both +blue and white LEDs are set to blink, they will share the same blink +rate. Changing the blink rate of the blue LED will affect the white +LED and vice-versa. Manual on/off is handled independtly for all +4 channels. + +Required properties: + - compatible : should be "ti,lp5562-leds". + - #address-cells : must be 1. + - #size-cells : must be 0. + - reg : LP5562 LED controller I2C address. + +Optional properties: + - gpios : Enable GPIO + - ti,external_clock : Boolean, configures controller for external + 32 kHZ clock input. Default : false (use internal clock) + +Each LED is represented as a sub-node of the ti,lp5562-leds device. + +LED sub-node required properties: + - reg : PWM register number for channel + +LED sub-node optional properties: + - label : see Documentation/devicetree/bindings/leds/common.txt + - ti,led_current : LED current at max brightness in 100uA steps (0x00 - 0xFF) + Default : 0x64 (10 ma) + +Example: + leds0: lp5562@30 { + compatible = "ti,lp5562-leds"; + #address-cells = <1>; + #size-cells = <0>; + gpios = <&gpio3 9 GPIO_ACTIVE_HIGH>; + reg = <0x30>; + + blue@0 { + reg = <0x2>; + label = "blue"; + ti,led_current = <0xC8>; /* 20ma */ + }; + green@1 { + reg = <0x3>; + label = "green"; + ti,led_current = <0xC8>; /* 20ma */ + }; + red@2 { + reg = <0x4>; + label = "red"; + ti,led_current = <0xC8>; /* 20ma */ + }; + white@3 { + reg = <0xE>; + label = "white"; + ti,led_current = <0xC8>; /* 20ma */ + }; + }; diff --git a/drivers/led/Kconfig b/drivers/led/Kconfig index 5da5c4a..101ad94 100644 --- a/drivers/led/Kconfig +++ b/drivers/led/Kconfig @@ -28,6 +28,14 @@ config LED_BCM6358 LED HW controller accessed via MMIO registers. HW has no blinking capabilities and up to 32 LEDs can be controlled. +config LED_LP5562 + bool "LED Support for LP5562" + depends on LED && DM_I2C + help + This option enables support for LEDs connected to the TI LP5562 + 4 channel I2C LED controller. Driver fully supports blink on the + B/G/R LEDs. White LED can blink, but re-uses the period from blue. + config LED_BLINK bool "Support LED blinking" depends on LED diff --git a/drivers/led/Makefile b/drivers/led/Makefile index 160a8f3..ef2ce1c 100644 --- a/drivers/led/Makefile +++ b/drivers/led/Makefile @@ -7,3 +7,4 @@ obj-y += led-uclass.o obj-$(CONFIG_LED_BCM6328) += led_bcm6328.o obj-$(CONFIG_LED_BCM6358) += led_bcm6358.o obj-$(CONFIG_$(SPL_)LED_GPIO) += led_gpio.o +obj-$(CONFIG_LED_LP5562) += led_lp5562.o diff --git a/drivers/led/led_lp5562.c b/drivers/led/led_lp5562.c new file mode 100644 index 0000000..357b175 --- /dev/null +++ b/drivers/led/led_lp5562.c @@ -0,0 +1,571 @@ +// 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 +#include + +#define DEFAULT_CURRENT 0x64 /* 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 + +/* 0x05, 0x06, 0x07, 0x0F CURRENT */ +#define REG_CUR_MAX_VALUE 0xFF + +/* 0x08 CONFIG */ +#define REG_CONFIG_EXT_CLK 0x0 +#define REG_CONFIG_INT_CLK 0x1 +#define REG_CONFIG_AUTO_CLK 0x2 + +/* 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_priv { + struct gpio_desc enable_gpio; + 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) +{ + u8 data; + + if (mask == 0xFF) { + data = value; + } else { + if (dm_i2c_read(dev, regnum, &data, 1) != 0) + return -EINVAL; + + data = (data & ~(mask)) | value; + } + + if (dm_i2c_write(dev, regnum, &data, 1) != 0) + return -EINVAL; + + /* + * The datasheet says to wait up to 488uS between i2c writes, but I + * found it had to be over 600us to be reliable. Using 1ms to be safe. + */ + udelay(1000); + + return 0; +} + +#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; + u8 *reordered; + + if (size < 1 || size > 16 || + (engine != 1 && engine != 2 && engine != 3)) + return -EINVAL; + + reordered = malloc(sizeof(u16) * size); + if (!reordered) + ret = -ENOMEM; + + for (cmd = 0; cmd < size; cmd++) { + reordered[sizeof(u16) * cmd] = program[cmd] >> 8; + reordered[(sizeof(u16) * cmd) + 1] = program[cmd] & 0xFF; + } + + /* 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 *)reordered, + 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: + free(reordered); + 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); + + /* 7th 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); + struct led_uc_plat *uc_plat = dev_get_uclass_platdata(dev); + u32 current; + int ret = 0; + + /* Top-level LED node */ + if (!uc_plat->label) { + /* Enable gpio if needed */ + if (gpio_request_by_name(dev, "gpios", 0, + &priv->enable_gpio, GPIOD_IS_OUT) == 0) + dm_gpio_set_value(&priv->enable_gpio, 1); + + /* Enable the chip */ + if (lp5562_led_reg_update(dev, REG_ENABLE, + REG_ENABLE_CHIP_ENABLE, 0xff) != 0) + return -EINVAL; + + if (!dev_read_bool(dev, "ti,external_clock")) { + if (lp5562_led_reg_update(dev, REG_CONFIG, + REG_CONFIG_INT_CLK, 0xff) + != 0) + return -EINVAL; + } + } else { + /* Child LED nodes */ + priv->reg_pwm = dev_read_addr(dev); + switch (priv->reg_pwm) { + case (REG_B_PWM): + priv->reg_current = REG_B_CUR; + priv->map_shift = REG_LED_MAP_B_ENG_SHIFT; + priv->enginenum = 1; /* shared with white */ + break; + case (REG_G_PWM): + priv->reg_current = REG_G_CUR; + priv->map_shift = REG_LED_MAP_G_ENG_SHIFT; + priv->enginenum = 2; + break; + case (REG_R_PWM): + priv->reg_current = REG_R_CUR; + priv->map_shift = REG_LED_MAP_R_ENG_SHIFT; + priv->enginenum = 3; + break; + case (REG_W_PWM): + priv->reg_current = REG_W_CUR; + priv->map_shift = REG_LED_MAP_W_ENG_SHIFT; + priv->enginenum = 1; /* shared with blue */ + break; + default: + return -EINVAL; + } + + current = dev_read_u32_default(dev, "ti,led_current", + DEFAULT_CURRENT); + if (current > REG_CUR_MAX_VALUE) { + debug("%s: node %s has invalid current limit: %d\n", + __func__, uc_plat->label, current); + return -EINVAL; + } + + if (lp5562_led_reg_update(dev_get_parent(dev), + priv->reg_current, + current, 0xff) != 0) + return -EINVAL; + } + + return ret; +} + +static int lp5562_led_bind(struct udevice *parent) +{ + ofnode node; + + dev_for_each_subnode(node, parent) { + struct led_uc_plat *uc_plat; + struct udevice *dev; + const char *label; + int ret; + + label = ofnode_read_string(node, "label"); + if (!label) { + debug("%s: node %s has no label\n", __func__, + ofnode_get_name(node)); + return -EINVAL; + } + + ret = device_bind_driver_to_node(parent, "lp5562-led", + ofnode_get_name(node), + node, &dev); + if (ret) + return ret; + + uc_plat = dev_get_uclass_platdata(dev); + uc_plat->label = label; + } + + return 0; +} + +static const struct udevice_id lp5562_led_ids[] = { + { .compatible = "ti,lp5562-leds" }, + { /* sentinel */ } +}; + +U_BOOT_DRIVER(lp5562_led) = { + .name = "lp5562-led", + .id = UCLASS_LED, + .of_match = lp5562_led_ids, + .bind = lp5562_led_bind, + .probe = lp5562_led_probe, + .priv_auto_alloc_size = sizeof(struct lp5562_led_priv), + .ops = &lp5562_led_ops, +};