From 0081e8020ebd814a99e45720a10e869a54ee08a6 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Thu, 4 Dec 2008 16:52:33 +0000 Subject: leds: Add WM8350 LED driver The voltage and current regulators on the WM8350 AudioPlus PMIC can be used in concert to provide a power efficient LED driver. This driver implements support for this within the standard LED class. Platform initialisation code should configure the LED hardware in the init callback provided by the WM8350 core driver. The callback should use wm8350_isink_set_flash(), wm8350_dcdc25_set_mode() and wm8350_dcdc_set_slot() to configure the operating parameters of the regulators for their hardware and then then use wm8350_register_led() to instantiate the LED driver. This driver was originally written by Liam Girdwood, though it has been extensively modified since then. Signed-off-by: Mark Brown Signed-off-by: Richard Purdie --- drivers/leds/leds-wm8350.c | 333 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 333 insertions(+) create mode 100644 drivers/leds/leds-wm8350.c (limited to 'drivers/leds/leds-wm8350.c') diff --git a/drivers/leds/leds-wm8350.c b/drivers/leds/leds-wm8350.c new file mode 100644 index 00000000000..846ed978103 --- /dev/null +++ b/drivers/leds/leds-wm8350.c @@ -0,0 +1,333 @@ +/* + * LED driver for WM8350 driven LEDS. + * + * Copyright(C) 2007, 2008 Wolfson Microelectronics PLC. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ + +#include +#include +#include +#include +#include +#include +#include + +/* Microamps */ +static const int isink_cur[] = { + 4, + 5, + 6, + 7, + 8, + 10, + 11, + 14, + 16, + 19, + 23, + 27, + 32, + 39, + 46, + 54, + 65, + 77, + 92, + 109, + 130, + 154, + 183, + 218, + 259, + 308, + 367, + 436, + 518, + 616, + 733, + 872, + 1037, + 1233, + 1466, + 1744, + 2073, + 2466, + 2933, + 3487, + 4147, + 4932, + 5865, + 6975, + 8294, + 9864, + 11730, + 13949, + 16589, + 19728, + 23460, + 27899, + 33178, + 39455, + 46920, + 55798, + 66355, + 78910, + 93840, + 111596, + 132710, + 157820, + 187681, + 223191 +}; + +#define to_wm8350_led(led_cdev) \ + container_of(led_cdev, struct wm8350_led, cdev) + +static void wm8350_led_enable(struct wm8350_led *led) +{ + int ret; + + if (led->enabled) + return; + + ret = regulator_enable(led->isink); + if (ret != 0) { + dev_err(led->cdev.dev, "Failed to enable ISINK: %d\n", ret); + return; + } + + ret = regulator_enable(led->dcdc); + if (ret != 0) { + dev_err(led->cdev.dev, "Failed to enable DCDC: %d\n", ret); + regulator_disable(led->isink); + return; + } + + led->enabled = 1; +} + +static void wm8350_led_disable(struct wm8350_led *led) +{ + int ret; + + if (!led->enabled) + return; + + ret = regulator_disable(led->dcdc); + if (ret != 0) { + dev_err(led->cdev.dev, "Failed to disable DCDC: %d\n", ret); + return; + } + + ret = regulator_disable(led->isink); + if (ret != 0) { + dev_err(led->cdev.dev, "Failed to disable ISINK: %d\n", ret); + regulator_enable(led->dcdc); + return; + } + + led->enabled = 0; +} + +static void led_work(struct work_struct *work) +{ + struct wm8350_led *led = container_of(work, struct wm8350_led, work); + int ret; + int uA; + unsigned long flags; + + mutex_lock(&led->mutex); + + spin_lock_irqsave(&led->value_lock, flags); + + if (led->value == LED_OFF) { + spin_unlock_irqrestore(&led->value_lock, flags); + wm8350_led_disable(led); + goto out; + } + + /* This scales linearly into the index of valid current + * settings which results in a linear scaling of perceived + * brightness due to the non-linear current settings provided + * by the hardware. + */ + uA = (led->max_uA_index * led->value) / LED_FULL; + spin_unlock_irqrestore(&led->value_lock, flags); + BUG_ON(uA >= ARRAY_SIZE(isink_cur)); + + ret = regulator_set_current_limit(led->isink, isink_cur[uA], + isink_cur[uA]); + if (ret != 0) + dev_err(led->cdev.dev, "Failed to set %duA: %d\n", + isink_cur[uA], ret); + + wm8350_led_enable(led); + +out: + mutex_unlock(&led->mutex); +} + +static void wm8350_led_set(struct led_classdev *led_cdev, + enum led_brightness value) +{ + struct wm8350_led *led = to_wm8350_led(led_cdev); + unsigned long flags; + + spin_lock_irqsave(&led->value_lock, flags); + led->value = value; + schedule_work(&led->work); + spin_unlock_irqrestore(&led->value_lock, flags); +} + +#ifdef CONFIG_PM +static int wm8350_led_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct wm8350_led *led = platform_get_drvdata(pdev); + + led_classdev_suspend(&led->cdev); + return 0; +} + +static int wm8350_led_resume(struct platform_device *pdev) +{ + struct wm8350_led *led = platform_get_drvdata(pdev); + + led_classdev_resume(&led->cdev); + return 0; +} +#else +#define wm8350_led_suspend NULL +#define wm8350_led_resume NULL +#endif + +static void wm8350_led_shutdown(struct platform_device *pdev) +{ + struct wm8350_led *led = platform_get_drvdata(pdev); + + mutex_lock(&led->mutex); + led->value = LED_OFF; + wm8350_led_disable(led); + mutex_unlock(&led->mutex); +} + +static int wm8350_led_probe(struct platform_device *pdev) +{ + struct regulator *isink, *dcdc; + struct wm8350_led *led; + struct wm8350_led_platform_data *pdata = pdev->dev.platform_data; + int ret, i; + + if (pdata == NULL) { + dev_err(&pdev->dev, "no platform data\n"); + return -ENODEV; + } + + if (pdata->max_uA < isink_cur[0]) { + dev_err(&pdev->dev, "Invalid maximum current %duA\n", + pdata->max_uA); + return -EINVAL; + } + + isink = regulator_get(&pdev->dev, "led_isink"); + if (IS_ERR(isink)) { + printk(KERN_ERR "%s: cant get ISINK\n", __func__); + return PTR_ERR(isink); + } + + dcdc = regulator_get(&pdev->dev, "led_vcc"); + if (IS_ERR(dcdc)) { + printk(KERN_ERR "%s: cant get DCDC\n", __func__); + ret = PTR_ERR(dcdc); + goto err_isink; + } + + led = kzalloc(sizeof(*led), GFP_KERNEL); + if (led == NULL) { + ret = -ENOMEM; + goto err_dcdc; + } + + led->cdev.brightness_set = wm8350_led_set; + led->cdev.default_trigger = pdata->default_trigger; + led->cdev.name = pdata->name; + led->enabled = regulator_is_enabled(isink); + led->isink = isink; + led->dcdc = dcdc; + + for (i = 0; i < ARRAY_SIZE(isink_cur) - 1; i++) + if (isink_cur[i] >= pdata->max_uA) + break; + led->max_uA_index = i; + if (pdata->max_uA != isink_cur[i]) + dev_warn(&pdev->dev, + "Maximum current %duA is not directly supported," + " check platform data\n", + pdata->max_uA); + + spin_lock_init(&led->value_lock); + mutex_init(&led->mutex); + INIT_WORK(&led->work, led_work); + led->value = LED_OFF; + platform_set_drvdata(pdev, led); + + ret = led_classdev_register(&pdev->dev, &led->cdev); + if (ret < 0) + goto err_led; + + return 0; + + err_led: + kfree(led); + err_dcdc: + regulator_put(dcdc); + err_isink: + regulator_put(isink); + return ret; +} + +static int wm8350_led_remove(struct platform_device *pdev) +{ + struct wm8350_led *led = platform_get_drvdata(pdev); + + led_classdev_unregister(&led->cdev); + flush_scheduled_work(); + wm8350_led_disable(led); + regulator_put(led->dcdc); + regulator_put(led->isink); + kfree(led); + return 0; +} + +static struct platform_driver wm8350_led_driver = { + .driver = { + .name = "wm8350-led", + .owner = THIS_MODULE, + }, + .probe = wm8350_led_probe, + .remove = wm8350_led_remove, + .shutdown = wm8350_led_shutdown, + .suspend = wm8350_led_suspend, + .resume = wm8350_led_resume, +}; + +static int __devinit wm8350_led_init(void) +{ + return platform_driver_register(&wm8350_led_driver); +} +module_init(wm8350_led_init); + +static void wm8350_led_exit(void) +{ + platform_driver_unregister(&wm8350_led_driver); +} +module_exit(wm8350_led_exit); + +MODULE_AUTHOR("Mark Brown"); +MODULE_DESCRIPTION("WM8350 LED driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:wm8350-led"); -- cgit v1.2.3 From 859cb7f2a4244ea6da206d3fe9cc8a6810947a68 Mon Sep 17 00:00:00 2001 From: Richard Purdie Date: Thu, 8 Jan 2009 17:55:03 +0000 Subject: leds: Add suspend/resume to the core class Add suspend/resume to the core class and remove all the now unneeded code from various drivers. Originally the class code couldn't support suspend/resume but since class_device can there is no reason for each driver doing its own suspend/resume anymore. --- drivers/leds/leds-wm8350.c | 24 +----------------------- 1 file changed, 1 insertion(+), 23 deletions(-) (limited to 'drivers/leds/leds-wm8350.c') diff --git a/drivers/leds/leds-wm8350.c b/drivers/leds/leds-wm8350.c index 846ed978103..38c6bcb07e6 100644 --- a/drivers/leds/leds-wm8350.c +++ b/drivers/leds/leds-wm8350.c @@ -184,27 +184,6 @@ static void wm8350_led_set(struct led_classdev *led_cdev, spin_unlock_irqrestore(&led->value_lock, flags); } -#ifdef CONFIG_PM -static int wm8350_led_suspend(struct platform_device *pdev, pm_message_t state) -{ - struct wm8350_led *led = platform_get_drvdata(pdev); - - led_classdev_suspend(&led->cdev); - return 0; -} - -static int wm8350_led_resume(struct platform_device *pdev) -{ - struct wm8350_led *led = platform_get_drvdata(pdev); - - led_classdev_resume(&led->cdev); - return 0; -} -#else -#define wm8350_led_suspend NULL -#define wm8350_led_resume NULL -#endif - static void wm8350_led_shutdown(struct platform_device *pdev) { struct wm8350_led *led = platform_get_drvdata(pdev); @@ -255,6 +234,7 @@ static int wm8350_led_probe(struct platform_device *pdev) led->cdev.brightness_set = wm8350_led_set; led->cdev.default_trigger = pdata->default_trigger; led->cdev.name = pdata->name; + led->cdev.flags |= LED_CORE_SUSPENDRESUME; led->enabled = regulator_is_enabled(isink); led->isink = isink; led->dcdc = dcdc; @@ -311,8 +291,6 @@ static struct platform_driver wm8350_led_driver = { .probe = wm8350_led_probe, .remove = wm8350_led_remove, .shutdown = wm8350_led_shutdown, - .suspend = wm8350_led_suspend, - .resume = wm8350_led_resume, }; static int __devinit wm8350_led_init(void) -- cgit v1.2.3