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