diff options
Diffstat (limited to 'drivers/leds/leds-wm8350.c')
-rw-r--r-- | drivers/leds/leds-wm8350.c | 333 |
1 files changed, 333 insertions, 0 deletions
diff --git a/drivers/leds/leds-wm8350.c b/drivers/leds/leds-wm8350.c new file mode 100644 index 000000000000..846ed978103f --- /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 <linux/kernel.h> +#include <linux/init.h> +#include <linux/platform_device.h> +#include <linux/leds.h> +#include <linux/err.h> +#include <linux/mfd/wm8350/pmic.h> +#include <linux/regulator/consumer.h> + +/* 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"); |