xref: /openbmc/linux/drivers/leds/leds-wm8350.c (revision 99ef2121)
10081e802SMark Brown /*
20081e802SMark Brown  * LED driver for WM8350 driven LEDS.
30081e802SMark Brown  *
40081e802SMark Brown  * Copyright(C) 2007, 2008 Wolfson Microelectronics PLC.
50081e802SMark Brown  *
60081e802SMark Brown  * This program is free software; you can redistribute it and/or modify
70081e802SMark Brown  * it under the terms of the GNU General Public License version 2 as
80081e802SMark Brown  * published by the Free Software Foundation.
90081e802SMark Brown  *
100081e802SMark Brown  */
110081e802SMark Brown 
120081e802SMark Brown #include <linux/kernel.h>
130081e802SMark Brown #include <linux/init.h>
140081e802SMark Brown #include <linux/platform_device.h>
150081e802SMark Brown #include <linux/leds.h>
160081e802SMark Brown #include <linux/err.h>
170081e802SMark Brown #include <linux/mfd/wm8350/pmic.h>
180081e802SMark Brown #include <linux/regulator/consumer.h>
195a0e3ad6STejun Heo #include <linux/slab.h>
200081e802SMark Brown 
210081e802SMark Brown /* Microamps */
220081e802SMark Brown static const int isink_cur[] = {
230081e802SMark Brown 	4,
240081e802SMark Brown 	5,
250081e802SMark Brown 	6,
260081e802SMark Brown 	7,
270081e802SMark Brown 	8,
280081e802SMark Brown 	10,
290081e802SMark Brown 	11,
300081e802SMark Brown 	14,
310081e802SMark Brown 	16,
320081e802SMark Brown 	19,
330081e802SMark Brown 	23,
340081e802SMark Brown 	27,
350081e802SMark Brown 	32,
360081e802SMark Brown 	39,
370081e802SMark Brown 	46,
380081e802SMark Brown 	54,
390081e802SMark Brown 	65,
400081e802SMark Brown 	77,
410081e802SMark Brown 	92,
420081e802SMark Brown 	109,
430081e802SMark Brown 	130,
440081e802SMark Brown 	154,
450081e802SMark Brown 	183,
460081e802SMark Brown 	218,
470081e802SMark Brown 	259,
480081e802SMark Brown 	308,
490081e802SMark Brown 	367,
500081e802SMark Brown 	436,
510081e802SMark Brown 	518,
520081e802SMark Brown 	616,
530081e802SMark Brown 	733,
540081e802SMark Brown 	872,
550081e802SMark Brown 	1037,
560081e802SMark Brown 	1233,
570081e802SMark Brown 	1466,
580081e802SMark Brown 	1744,
590081e802SMark Brown 	2073,
600081e802SMark Brown 	2466,
610081e802SMark Brown 	2933,
620081e802SMark Brown 	3487,
630081e802SMark Brown 	4147,
640081e802SMark Brown 	4932,
650081e802SMark Brown 	5865,
660081e802SMark Brown 	6975,
670081e802SMark Brown 	8294,
680081e802SMark Brown 	9864,
690081e802SMark Brown 	11730,
700081e802SMark Brown 	13949,
710081e802SMark Brown 	16589,
720081e802SMark Brown 	19728,
730081e802SMark Brown 	23460,
740081e802SMark Brown 	27899,
750081e802SMark Brown 	33178,
760081e802SMark Brown 	39455,
770081e802SMark Brown 	46920,
780081e802SMark Brown 	55798,
790081e802SMark Brown 	66355,
800081e802SMark Brown 	78910,
810081e802SMark Brown 	93840,
820081e802SMark Brown 	111596,
830081e802SMark Brown 	132710,
840081e802SMark Brown 	157820,
850081e802SMark Brown 	187681,
860081e802SMark Brown 	223191
870081e802SMark Brown };
880081e802SMark Brown 
890081e802SMark Brown #define to_wm8350_led(led_cdev) \
900081e802SMark Brown 	container_of(led_cdev, struct wm8350_led, cdev)
910081e802SMark Brown 
920081e802SMark Brown static void wm8350_led_enable(struct wm8350_led *led)
930081e802SMark Brown {
940081e802SMark Brown 	int ret;
950081e802SMark Brown 
960081e802SMark Brown 	if (led->enabled)
970081e802SMark Brown 		return;
980081e802SMark Brown 
990081e802SMark Brown 	ret = regulator_enable(led->isink);
1000081e802SMark Brown 	if (ret != 0) {
1010081e802SMark Brown 		dev_err(led->cdev.dev, "Failed to enable ISINK: %d\n", ret);
1020081e802SMark Brown 		return;
1030081e802SMark Brown 	}
1040081e802SMark Brown 
1050081e802SMark Brown 	ret = regulator_enable(led->dcdc);
1060081e802SMark Brown 	if (ret != 0) {
1070081e802SMark Brown 		dev_err(led->cdev.dev, "Failed to enable DCDC: %d\n", ret);
1080081e802SMark Brown 		regulator_disable(led->isink);
1090081e802SMark Brown 		return;
1100081e802SMark Brown 	}
1110081e802SMark Brown 
1120081e802SMark Brown 	led->enabled = 1;
1130081e802SMark Brown }
1140081e802SMark Brown 
1150081e802SMark Brown static void wm8350_led_disable(struct wm8350_led *led)
1160081e802SMark Brown {
1170081e802SMark Brown 	int ret;
1180081e802SMark Brown 
1190081e802SMark Brown 	if (!led->enabled)
1200081e802SMark Brown 		return;
1210081e802SMark Brown 
1220081e802SMark Brown 	ret = regulator_disable(led->dcdc);
1230081e802SMark Brown 	if (ret != 0) {
1240081e802SMark Brown 		dev_err(led->cdev.dev, "Failed to disable DCDC: %d\n", ret);
1250081e802SMark Brown 		return;
1260081e802SMark Brown 	}
1270081e802SMark Brown 
1280081e802SMark Brown 	ret = regulator_disable(led->isink);
1290081e802SMark Brown 	if (ret != 0) {
1300081e802SMark Brown 		dev_err(led->cdev.dev, "Failed to disable ISINK: %d\n", ret);
1310081e802SMark Brown 		regulator_enable(led->dcdc);
1320081e802SMark Brown 		return;
1330081e802SMark Brown 	}
1340081e802SMark Brown 
1350081e802SMark Brown 	led->enabled = 0;
1360081e802SMark Brown }
1370081e802SMark Brown 
1380081e802SMark Brown static void led_work(struct work_struct *work)
1390081e802SMark Brown {
1400081e802SMark Brown 	struct wm8350_led *led = container_of(work, struct wm8350_led, work);
1410081e802SMark Brown 	int ret;
1420081e802SMark Brown 	int uA;
1430081e802SMark Brown 	unsigned long flags;
1440081e802SMark Brown 
1450081e802SMark Brown 	mutex_lock(&led->mutex);
1460081e802SMark Brown 
1470081e802SMark Brown 	spin_lock_irqsave(&led->value_lock, flags);
1480081e802SMark Brown 
1490081e802SMark Brown 	if (led->value == LED_OFF) {
1500081e802SMark Brown 		spin_unlock_irqrestore(&led->value_lock, flags);
1510081e802SMark Brown 		wm8350_led_disable(led);
1520081e802SMark Brown 		goto out;
1530081e802SMark Brown 	}
1540081e802SMark Brown 
1550081e802SMark Brown 	/* This scales linearly into the index of valid current
1560081e802SMark Brown 	 * settings which results in a linear scaling of perceived
1570081e802SMark Brown 	 * brightness due to the non-linear current settings provided
1580081e802SMark Brown 	 * by the hardware.
1590081e802SMark Brown 	 */
1600081e802SMark Brown 	uA = (led->max_uA_index * led->value) / LED_FULL;
1610081e802SMark Brown 	spin_unlock_irqrestore(&led->value_lock, flags);
1620081e802SMark Brown 	BUG_ON(uA >= ARRAY_SIZE(isink_cur));
1630081e802SMark Brown 
1640081e802SMark Brown 	ret = regulator_set_current_limit(led->isink, isink_cur[uA],
1650081e802SMark Brown 					  isink_cur[uA]);
1660081e802SMark Brown 	if (ret != 0)
1670081e802SMark Brown 		dev_err(led->cdev.dev, "Failed to set %duA: %d\n",
1680081e802SMark Brown 			isink_cur[uA], ret);
1690081e802SMark Brown 
1700081e802SMark Brown 	wm8350_led_enable(led);
1710081e802SMark Brown 
1720081e802SMark Brown out:
1730081e802SMark Brown 	mutex_unlock(&led->mutex);
1740081e802SMark Brown }
1750081e802SMark Brown 
1760081e802SMark Brown static void wm8350_led_set(struct led_classdev *led_cdev,
1770081e802SMark Brown 			   enum led_brightness value)
1780081e802SMark Brown {
1790081e802SMark Brown 	struct wm8350_led *led = to_wm8350_led(led_cdev);
1800081e802SMark Brown 	unsigned long flags;
1810081e802SMark Brown 
1820081e802SMark Brown 	spin_lock_irqsave(&led->value_lock, flags);
1830081e802SMark Brown 	led->value = value;
1840081e802SMark Brown 	schedule_work(&led->work);
1850081e802SMark Brown 	spin_unlock_irqrestore(&led->value_lock, flags);
1860081e802SMark Brown }
1870081e802SMark Brown 
1880081e802SMark Brown static void wm8350_led_shutdown(struct platform_device *pdev)
1890081e802SMark Brown {
1900081e802SMark Brown 	struct wm8350_led *led = platform_get_drvdata(pdev);
1910081e802SMark Brown 
1920081e802SMark Brown 	mutex_lock(&led->mutex);
1930081e802SMark Brown 	led->value = LED_OFF;
1940081e802SMark Brown 	wm8350_led_disable(led);
1950081e802SMark Brown 	mutex_unlock(&led->mutex);
1960081e802SMark Brown }
1970081e802SMark Brown 
1980081e802SMark Brown static int wm8350_led_probe(struct platform_device *pdev)
1990081e802SMark Brown {
2000081e802SMark Brown 	struct regulator *isink, *dcdc;
2010081e802SMark Brown 	struct wm8350_led *led;
2020081e802SMark Brown 	struct wm8350_led_platform_data *pdata = pdev->dev.platform_data;
2030081e802SMark Brown 	int ret, i;
2040081e802SMark Brown 
2050081e802SMark Brown 	if (pdata == NULL) {
2060081e802SMark Brown 		dev_err(&pdev->dev, "no platform data\n");
2070081e802SMark Brown 		return -ENODEV;
2080081e802SMark Brown 	}
2090081e802SMark Brown 
2100081e802SMark Brown 	if (pdata->max_uA < isink_cur[0]) {
2110081e802SMark Brown 		dev_err(&pdev->dev, "Invalid maximum current %duA\n",
2120081e802SMark Brown 			pdata->max_uA);
2130081e802SMark Brown 		return -EINVAL;
2140081e802SMark Brown 	}
2150081e802SMark Brown 
2160081e802SMark Brown 	isink = regulator_get(&pdev->dev, "led_isink");
2170081e802SMark Brown 	if (IS_ERR(isink)) {
2180081e802SMark Brown 		printk(KERN_ERR "%s: cant get ISINK\n", __func__);
2190081e802SMark Brown 		return PTR_ERR(isink);
2200081e802SMark Brown 	}
2210081e802SMark Brown 
2220081e802SMark Brown 	dcdc = regulator_get(&pdev->dev, "led_vcc");
2230081e802SMark Brown 	if (IS_ERR(dcdc)) {
2240081e802SMark Brown 		printk(KERN_ERR "%s: cant get DCDC\n", __func__);
2250081e802SMark Brown 		ret = PTR_ERR(dcdc);
2260081e802SMark Brown 		goto err_isink;
2270081e802SMark Brown 	}
2280081e802SMark Brown 
2290081e802SMark Brown 	led = kzalloc(sizeof(*led), GFP_KERNEL);
2300081e802SMark Brown 	if (led == NULL) {
2310081e802SMark Brown 		ret = -ENOMEM;
2320081e802SMark Brown 		goto err_dcdc;
2330081e802SMark Brown 	}
2340081e802SMark Brown 
2350081e802SMark Brown 	led->cdev.brightness_set = wm8350_led_set;
2360081e802SMark Brown 	led->cdev.default_trigger = pdata->default_trigger;
2370081e802SMark Brown 	led->cdev.name = pdata->name;
238859cb7f2SRichard Purdie 	led->cdev.flags |= LED_CORE_SUSPENDRESUME;
2390081e802SMark Brown 	led->enabled = regulator_is_enabled(isink);
2400081e802SMark Brown 	led->isink = isink;
2410081e802SMark Brown 	led->dcdc = dcdc;
2420081e802SMark Brown 
2430081e802SMark Brown 	for (i = 0; i < ARRAY_SIZE(isink_cur) - 1; i++)
2440081e802SMark Brown 		if (isink_cur[i] >= pdata->max_uA)
2450081e802SMark Brown 			break;
2460081e802SMark Brown 	led->max_uA_index = i;
2470081e802SMark Brown 	if (pdata->max_uA != isink_cur[i])
2480081e802SMark Brown 		dev_warn(&pdev->dev,
2490081e802SMark Brown 			 "Maximum current %duA is not directly supported,"
2500081e802SMark Brown 			 " check platform data\n",
2510081e802SMark Brown 			 pdata->max_uA);
2520081e802SMark Brown 
2530081e802SMark Brown 	spin_lock_init(&led->value_lock);
2540081e802SMark Brown 	mutex_init(&led->mutex);
2550081e802SMark Brown 	INIT_WORK(&led->work, led_work);
2560081e802SMark Brown 	led->value = LED_OFF;
2570081e802SMark Brown 	platform_set_drvdata(pdev, led);
2580081e802SMark Brown 
2590081e802SMark Brown 	ret = led_classdev_register(&pdev->dev, &led->cdev);
2600081e802SMark Brown 	if (ret < 0)
2610081e802SMark Brown 		goto err_led;
2620081e802SMark Brown 
2630081e802SMark Brown 	return 0;
2640081e802SMark Brown 
2650081e802SMark Brown  err_led:
2660081e802SMark Brown 	kfree(led);
2670081e802SMark Brown  err_dcdc:
2680081e802SMark Brown 	regulator_put(dcdc);
2690081e802SMark Brown  err_isink:
2700081e802SMark Brown 	regulator_put(isink);
2710081e802SMark Brown 	return ret;
2720081e802SMark Brown }
2730081e802SMark Brown 
2740081e802SMark Brown static int wm8350_led_remove(struct platform_device *pdev)
2750081e802SMark Brown {
2760081e802SMark Brown 	struct wm8350_led *led = platform_get_drvdata(pdev);
2770081e802SMark Brown 
2780081e802SMark Brown 	led_classdev_unregister(&led->cdev);
27999ef2121STejun Heo 	flush_work_sync(&led->work);
2800081e802SMark Brown 	wm8350_led_disable(led);
2810081e802SMark Brown 	regulator_put(led->dcdc);
2820081e802SMark Brown 	regulator_put(led->isink);
2830081e802SMark Brown 	kfree(led);
2840081e802SMark Brown 	return 0;
2850081e802SMark Brown }
2860081e802SMark Brown 
2870081e802SMark Brown static struct platform_driver wm8350_led_driver = {
2880081e802SMark Brown 	.driver = {
2890081e802SMark Brown 		   .name = "wm8350-led",
2900081e802SMark Brown 		   .owner = THIS_MODULE,
2910081e802SMark Brown 		   },
2920081e802SMark Brown 	.probe = wm8350_led_probe,
2930081e802SMark Brown 	.remove = wm8350_led_remove,
2940081e802SMark Brown 	.shutdown = wm8350_led_shutdown,
2950081e802SMark Brown };
2960081e802SMark Brown 
2970081e802SMark Brown static int __devinit wm8350_led_init(void)
2980081e802SMark Brown {
2990081e802SMark Brown 	return platform_driver_register(&wm8350_led_driver);
3000081e802SMark Brown }
3010081e802SMark Brown module_init(wm8350_led_init);
3020081e802SMark Brown 
3030081e802SMark Brown static void wm8350_led_exit(void)
3040081e802SMark Brown {
3050081e802SMark Brown 	platform_driver_unregister(&wm8350_led_driver);
3060081e802SMark Brown }
3070081e802SMark Brown module_exit(wm8350_led_exit);
3080081e802SMark Brown 
3090081e802SMark Brown MODULE_AUTHOR("Mark Brown");
3100081e802SMark Brown MODULE_DESCRIPTION("WM8350 LED driver");
3110081e802SMark Brown MODULE_LICENSE("GPL");
3120081e802SMark Brown MODULE_ALIAS("platform:wm8350-led");
313