|
@@ -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");
|