The TPS68470 PMIC provides a third LED driver in addition to the two indicator LEDs. Add support for the WLED. To ensure the LED is active for as long as the kernel instructs it to be we need to re-trigger it periodically to avoid the IC's internal timeouts. Signed-off-by: Daniel Scally <dan.scally@xxxxxxxxxxxxxxxx> --- drivers/leds/leds-tps68470.c | 102 ++++++++++++++++++++++++++++++++++- include/linux/mfd/tps68470.h | 12 +++++ 2 files changed, 113 insertions(+), 1 deletion(-) diff --git a/drivers/leds/leds-tps68470.c b/drivers/leds/leds-tps68470.c index 44df175d25de..abcd3494b1a8 100644 --- a/drivers/leds/leds-tps68470.c +++ b/drivers/leds/leds-tps68470.c @@ -8,6 +8,7 @@ * Kate Hsuan <hpa@xxxxxxxxxx> */ +#include <linux/clk.h> #include <linux/leds.h> #include <linux/mfd/tps68470.h> #include <linux/module.h> @@ -15,7 +16,10 @@ #include <linux/platform_device.h> #include <linux/property.h> #include <linux/regmap.h> +#include <linux/workqueue.h> +#define work_to_led(work) \ + container_of(work, struct tps68470_led, keepalive_work) #define lcdev_to_led(led_cdev) \ container_of(led_cdev, struct tps68470_led, lcdev) @@ -26,20 +30,25 @@ enum tps68470_led_ids { TPS68470_ILED_A, TPS68470_ILED_B, + TPS68470_WLED, TPS68470_NUM_LEDS }; static const char *tps68470_led_names[] = { [TPS68470_ILED_A] = "tps68470-iled_a", [TPS68470_ILED_B] = "tps68470-iled_b", + [TPS68470_WLED] = "tps68470-wled", }; struct tps68470_led { unsigned int led_id; struct led_classdev lcdev; + enum led_brightness state; + struct work_struct keepalive_work; }; struct tps68470_device { + struct clk *clk; struct device *dev; struct regmap *regmap; struct tps68470_led leds[TPS68470_NUM_LEDS]; @@ -52,11 +61,33 @@ enum ctrlb_current { CTRLB_16MA = 3, }; +/* + * The WLED can operate in different modes, including a Flash and Torch mode. In + * each mode there's a timeout which ranges from a matter of milliseconds to up + * to 13 seconds. We don't want that timeout to apply though because the LED + * should be lit until we say that it should no longer be lit, re-trigger the + * LED periodically to keep it alive. + */ +static void tps68470_wled_keepalive_work(struct work_struct *work) +{ + struct tps68470_device *tps68470; + struct tps68470_led *led; + + led = work_to_led(work); + tps68470 = led_to_tps68470(led, led->led_id); + + regmap_update_bits_async(tps68470->regmap, TPS68470_REG_WLEDCTL, + TPS68470_WLED_CTL_MASK, TPS68470_WLED_CTL_MASK); + schedule_work(&led->keepalive_work); +} + static int tps68470_brightness_set(struct led_classdev *led_cdev, enum led_brightness brightness) { struct tps68470_led *led = lcdev_to_led(led_cdev); struct tps68470_device *tps68470 = led_to_tps68470(led, led->led_id); struct regmap *regmap = tps68470->regmap; + const char *errmsg; + int ret; switch (led->led_id) { case TPS68470_ILED_A: @@ -65,8 +96,59 @@ static int tps68470_brightness_set(struct led_classdev *led_cdev, enum led_brigh case TPS68470_ILED_B: return regmap_update_bits(regmap, TPS68470_REG_ILEDCTL, TPS68470_ILEDCTL_ENB, brightness ? TPS68470_ILEDCTL_ENB : 0); + case TPS68470_WLED: + /* + * LED core does not prevent re-setting brightness to its current + * value; we need to do so here to avoid unbalanced calls to clk + * enable/disable. + */ + if (led->state == brightness) + return 0; + + if (brightness) { + schedule_work(&led->keepalive_work); + + ret = clk_prepare_enable(tps68470->clk); + if (ret) { + errmsg = "failed to start clock\n"; + goto err_cancel_work; + } + } else { + cancel_work_sync(&led->keepalive_work); + clk_disable_unprepare(tps68470->clk); + } + + ret = regmap_update_bits(tps68470->regmap, TPS68470_REG_WLEDCTL, + TPS68470_WLED_EN_MASK, + brightness ? TPS68470_WLED_EN_MASK : + ~TPS68470_WLED_EN_MASK); + if (ret) { + errmsg = "failed to set WLED EN\n"; + goto err_disable_clk; + } + + ret = regmap_update_bits(tps68470->regmap, TPS68470_REG_WLEDCTL, + TPS68470_WLED_CTL_MASK, + brightness ? TPS68470_WLED_CTL_MASK : + ~TPS68470_WLED_CTL_MASK); + if (ret) { + errmsg = "failed to set WLED START\n"; + goto err_disable_clk; + } + + led->state = brightness; + break; + default: + return dev_err_probe(led_cdev->dev, -EINVAL, "invalid LED ID\n"); } - return -EINVAL; + + return ret; + +err_disable_clk: + clk_disable_unprepare(tps68470->clk); +err_cancel_work: + cancel_work_sync(&led->keepalive_work); + return dev_err_probe(tps68470->dev, ret, errmsg); } static enum led_brightness tps68470_brightness_get(struct led_classdev *led_cdev) @@ -88,6 +170,14 @@ static enum led_brightness tps68470_brightness_get(struct led_classdev *led_cdev value &= led->led_id == TPS68470_ILED_A ? TPS68470_ILEDCTL_ENA : TPS68470_ILEDCTL_ENB; break; + case TPS68470_WLED: + ret = regmap_read(regmap, TPS68470_REG_WLEDCTL, &value); + if (ret) + return dev_err_probe(led_cdev->dev, ret, + "failed to read LED status\n"); + + value &= TPS68470_WLED_CTL_MASK; + break; default: return dev_err_probe(led_cdev->dev, -EINVAL, "invalid LED ID\n"); } @@ -177,6 +267,11 @@ static int tps68470_leds_probe(struct platform_device *pdev) tps68470->dev = &pdev->dev; tps68470->regmap = dev_get_drvdata(pdev->dev.parent); + tps68470->clk = devm_clk_get(tps68470->dev, NULL); + if (IS_ERR(tps68470->clk)) + return dev_err_probe(tps68470->dev, PTR_ERR(tps68470->clk), + "failed to get clock\n"); + for (i = 0; i < TPS68470_NUM_LEDS; i++) { led = &tps68470->leds[i]; lcdev = &led->lcdev; @@ -206,6 +301,11 @@ static int tps68470_leds_probe(struct platform_device *pdev) if (ret) goto err_exit; } + + if (led->led_id == TPS68470_WLED) { + INIT_WORK(&led->keepalive_work, + tps68470_wled_keepalive_work); + } } ret = tps68470_leds_init(tps68470); diff --git a/include/linux/mfd/tps68470.h b/include/linux/mfd/tps68470.h index 2d2abb25b944..103ff730e028 100644 --- a/include/linux/mfd/tps68470.h +++ b/include/linux/mfd/tps68470.h @@ -35,6 +35,11 @@ #define TPS68470_REG_GPDI 0x26 #define TPS68470_REG_GPDO 0x27 #define TPS68470_REG_ILEDCTL 0x28 +#define TPS68470_REG_WLEDMAXF 0x2F +#define TPS68470_REG_WLEDTO 0x30 +#define TPS68470_REG_WLEDC1 0x34 +#define TPS68470_REG_WLEDC2 0x35 +#define TPS68470_REG_WLEDCTL 0x36 #define TPS68470_REG_VCMVAL 0x3C #define TPS68470_REG_VAUX1VAL 0x3D #define TPS68470_REG_VAUX2VAL 0x3E @@ -98,5 +103,12 @@ #define TPS68470_ILEDCTL_ENA BIT(2) #define TPS68470_ILEDCTL_ENB BIT(6) #define TPS68470_ILEDCTL_CTRLB GENMASK(5, 4) +#define TPS68470_WLEDMAXF_MAX_CUR_MASK GENMASK(4, 0) +#define TPS68470_WLEDC_ILED_MASK GENMASK(4, 0) +#define TPS68470_WLED_MODE_MASK GENMASK(1, 0) +#define TPS68470_WLED_EN_MASK BIT(2) +#define TPS68470_WLED_DISLED1 BIT(3) +#define TPS68470_WLED_DISLED2 BIT(4) +#define TPS68470_WLED_CTL_MASK BIT(5) #endif /* __LINUX_MFD_TPS68470_H */ -- 2.34.1