xref: /openbmc/linux/drivers/leds/leds-wm8350.c (revision d2912cb1)
1d2912cb1SThomas Gleixner // SPDX-License-Identifier: GPL-2.0-only
20081e802SMark Brown /*
30081e802SMark Brown  * LED driver for WM8350 driven LEDS.
40081e802SMark Brown  *
50081e802SMark Brown  * Copyright(C) 2007, 2008 Wolfson Microelectronics PLC.
60081e802SMark Brown  */
70081e802SMark Brown 
80081e802SMark Brown #include <linux/kernel.h>
90081e802SMark Brown #include <linux/platform_device.h>
100081e802SMark Brown #include <linux/leds.h>
110081e802SMark Brown #include <linux/err.h>
120081e802SMark Brown #include <linux/mfd/wm8350/pmic.h>
130081e802SMark Brown #include <linux/regulator/consumer.h>
145a0e3ad6STejun Heo #include <linux/slab.h>
1554f4dedbSPaul Gortmaker #include <linux/module.h>
160081e802SMark Brown 
170081e802SMark Brown /* Microamps */
180081e802SMark Brown static const int isink_cur[] = {
190081e802SMark Brown 	4,
200081e802SMark Brown 	5,
210081e802SMark Brown 	6,
220081e802SMark Brown 	7,
230081e802SMark Brown 	8,
240081e802SMark Brown 	10,
250081e802SMark Brown 	11,
260081e802SMark Brown 	14,
270081e802SMark Brown 	16,
280081e802SMark Brown 	19,
290081e802SMark Brown 	23,
300081e802SMark Brown 	27,
310081e802SMark Brown 	32,
320081e802SMark Brown 	39,
330081e802SMark Brown 	46,
340081e802SMark Brown 	54,
350081e802SMark Brown 	65,
360081e802SMark Brown 	77,
370081e802SMark Brown 	92,
380081e802SMark Brown 	109,
390081e802SMark Brown 	130,
400081e802SMark Brown 	154,
410081e802SMark Brown 	183,
420081e802SMark Brown 	218,
430081e802SMark Brown 	259,
440081e802SMark Brown 	308,
450081e802SMark Brown 	367,
460081e802SMark Brown 	436,
470081e802SMark Brown 	518,
480081e802SMark Brown 	616,
490081e802SMark Brown 	733,
500081e802SMark Brown 	872,
510081e802SMark Brown 	1037,
520081e802SMark Brown 	1233,
530081e802SMark Brown 	1466,
540081e802SMark Brown 	1744,
550081e802SMark Brown 	2073,
560081e802SMark Brown 	2466,
570081e802SMark Brown 	2933,
580081e802SMark Brown 	3487,
590081e802SMark Brown 	4147,
600081e802SMark Brown 	4932,
610081e802SMark Brown 	5865,
620081e802SMark Brown 	6975,
630081e802SMark Brown 	8294,
640081e802SMark Brown 	9864,
650081e802SMark Brown 	11730,
660081e802SMark Brown 	13949,
670081e802SMark Brown 	16589,
680081e802SMark Brown 	19728,
690081e802SMark Brown 	23460,
700081e802SMark Brown 	27899,
710081e802SMark Brown 	33178,
720081e802SMark Brown 	39455,
730081e802SMark Brown 	46920,
740081e802SMark Brown 	55798,
750081e802SMark Brown 	66355,
760081e802SMark Brown 	78910,
770081e802SMark Brown 	93840,
780081e802SMark Brown 	111596,
790081e802SMark Brown 	132710,
800081e802SMark Brown 	157820,
810081e802SMark Brown 	187681,
820081e802SMark Brown 	223191
830081e802SMark Brown };
840081e802SMark Brown 
850081e802SMark Brown #define to_wm8350_led(led_cdev) \
860081e802SMark Brown 	container_of(led_cdev, struct wm8350_led, cdev)
870081e802SMark Brown 
wm8350_led_enable(struct wm8350_led * led)880dd756f7SAndrew Lunn static int wm8350_led_enable(struct wm8350_led *led)
890081e802SMark Brown {
900dd756f7SAndrew Lunn 	int ret = 0;
910081e802SMark Brown 
920081e802SMark Brown 	if (led->enabled)
930dd756f7SAndrew Lunn 		return ret;
940081e802SMark Brown 
950081e802SMark Brown 	ret = regulator_enable(led->isink);
960081e802SMark Brown 	if (ret != 0) {
970081e802SMark Brown 		dev_err(led->cdev.dev, "Failed to enable ISINK: %d\n", ret);
980dd756f7SAndrew Lunn 		return ret;
990081e802SMark Brown 	}
1000081e802SMark Brown 
1010081e802SMark Brown 	ret = regulator_enable(led->dcdc);
1020081e802SMark Brown 	if (ret != 0) {
1030081e802SMark Brown 		dev_err(led->cdev.dev, "Failed to enable DCDC: %d\n", ret);
1040081e802SMark Brown 		regulator_disable(led->isink);
1050dd756f7SAndrew Lunn 		return ret;
1060081e802SMark Brown 	}
1070081e802SMark Brown 
1080081e802SMark Brown 	led->enabled = 1;
1090dd756f7SAndrew Lunn 
1100dd756f7SAndrew Lunn 	return ret;
1110081e802SMark Brown }
1120081e802SMark Brown 
wm8350_led_disable(struct wm8350_led * led)1130dd756f7SAndrew Lunn static int wm8350_led_disable(struct wm8350_led *led)
1140081e802SMark Brown {
1150dd756f7SAndrew Lunn 	int ret = 0;
1160081e802SMark Brown 
1170081e802SMark Brown 	if (!led->enabled)
1180dd756f7SAndrew Lunn 		return ret;
1190081e802SMark Brown 
1200081e802SMark Brown 	ret = regulator_disable(led->dcdc);
1210081e802SMark Brown 	if (ret != 0) {
1220081e802SMark Brown 		dev_err(led->cdev.dev, "Failed to disable DCDC: %d\n", ret);
1230dd756f7SAndrew Lunn 		return ret;
1240081e802SMark Brown 	}
1250081e802SMark Brown 
1260081e802SMark Brown 	ret = regulator_disable(led->isink);
1270081e802SMark Brown 	if (ret != 0) {
1280081e802SMark Brown 		dev_err(led->cdev.dev, "Failed to disable ISINK: %d\n", ret);
129d67eb8e6SMark Brown 		ret = regulator_enable(led->dcdc);
130d67eb8e6SMark Brown 		if (ret != 0)
131d67eb8e6SMark Brown 			dev_err(led->cdev.dev, "Failed to reenable DCDC: %d\n",
132d67eb8e6SMark Brown 				ret);
1330dd756f7SAndrew Lunn 		return ret;
1340081e802SMark Brown 	}
1350081e802SMark Brown 
1360081e802SMark Brown 	led->enabled = 0;
1370dd756f7SAndrew Lunn 
1380dd756f7SAndrew Lunn 	return ret;
1390081e802SMark Brown }
1400081e802SMark Brown 
wm8350_led_set(struct led_classdev * led_cdev,enum led_brightness value)1410dd756f7SAndrew Lunn static int wm8350_led_set(struct led_classdev *led_cdev,
1420dd756f7SAndrew Lunn 			   enum led_brightness value)
1430081e802SMark Brown {
1440dd756f7SAndrew Lunn 	struct wm8350_led *led = to_wm8350_led(led_cdev);
1450dd756f7SAndrew Lunn 	unsigned long flags;
1460081e802SMark Brown 	int ret;
1470081e802SMark Brown 	int uA;
1480081e802SMark Brown 
1490dd756f7SAndrew Lunn 	led->value = value;
1500081e802SMark Brown 
1510081e802SMark Brown 	spin_lock_irqsave(&led->value_lock, flags);
1520081e802SMark Brown 
1530081e802SMark Brown 	if (led->value == LED_OFF) {
1540081e802SMark Brown 		spin_unlock_irqrestore(&led->value_lock, flags);
1550dd756f7SAndrew Lunn 		return wm8350_led_disable(led);
1560081e802SMark Brown 	}
1570081e802SMark Brown 
1580081e802SMark Brown 	/* This scales linearly into the index of valid current
1590081e802SMark Brown 	 * settings which results in a linear scaling of perceived
1600081e802SMark Brown 	 * brightness due to the non-linear current settings provided
1610081e802SMark Brown 	 * by the hardware.
1620081e802SMark Brown 	 */
1630081e802SMark Brown 	uA = (led->max_uA_index * led->value) / LED_FULL;
1640081e802SMark Brown 	spin_unlock_irqrestore(&led->value_lock, flags);
1650081e802SMark Brown 	BUG_ON(uA >= ARRAY_SIZE(isink_cur));
1660081e802SMark Brown 
1670081e802SMark Brown 	ret = regulator_set_current_limit(led->isink, isink_cur[uA],
1680081e802SMark Brown 					  isink_cur[uA]);
1690dd756f7SAndrew Lunn 	if (ret != 0) {
1700081e802SMark Brown 		dev_err(led->cdev.dev, "Failed to set %duA: %d\n",
1710081e802SMark Brown 			isink_cur[uA], ret);
1720dd756f7SAndrew Lunn 		return ret;
1730081e802SMark Brown 	}
1740081e802SMark Brown 
1750dd756f7SAndrew Lunn 	return wm8350_led_enable(led);
1760081e802SMark Brown }
1770081e802SMark Brown 
wm8350_led_shutdown(struct platform_device * pdev)1780081e802SMark Brown static void wm8350_led_shutdown(struct platform_device *pdev)
1790081e802SMark Brown {
1800081e802SMark Brown 	struct wm8350_led *led = platform_get_drvdata(pdev);
1810081e802SMark Brown 
1820081e802SMark Brown 	led->value = LED_OFF;
1830081e802SMark Brown 	wm8350_led_disable(led);
1840081e802SMark Brown }
1850081e802SMark Brown 
wm8350_led_probe(struct platform_device * pdev)1860081e802SMark Brown static int wm8350_led_probe(struct platform_device *pdev)
1870081e802SMark Brown {
1880081e802SMark Brown 	struct regulator *isink, *dcdc;
1890081e802SMark Brown 	struct wm8350_led *led;
19087aae1eaSJingoo Han 	struct wm8350_led_platform_data *pdata = dev_get_platdata(&pdev->dev);
191490dcee9SAxel Lin 	int i;
1920081e802SMark Brown 
1930081e802SMark Brown 	if (pdata == NULL) {
1940081e802SMark Brown 		dev_err(&pdev->dev, "no platform data\n");
1950081e802SMark Brown 		return -ENODEV;
1960081e802SMark Brown 	}
1970081e802SMark Brown 
1980081e802SMark Brown 	if (pdata->max_uA < isink_cur[0]) {
1990081e802SMark Brown 		dev_err(&pdev->dev, "Invalid maximum current %duA\n",
2000081e802SMark Brown 			pdata->max_uA);
2010081e802SMark Brown 		return -EINVAL;
2020081e802SMark Brown 	}
2030081e802SMark Brown 
204490dcee9SAxel Lin 	isink = devm_regulator_get(&pdev->dev, "led_isink");
2050081e802SMark Brown 	if (IS_ERR(isink)) {
206b75d2802SSachin Kamat 		dev_err(&pdev->dev, "%s: can't get ISINK\n", __func__);
2070081e802SMark Brown 		return PTR_ERR(isink);
2080081e802SMark Brown 	}
2090081e802SMark Brown 
210490dcee9SAxel Lin 	dcdc = devm_regulator_get(&pdev->dev, "led_vcc");
2110081e802SMark Brown 	if (IS_ERR(dcdc)) {
212b75d2802SSachin Kamat 		dev_err(&pdev->dev, "%s: can't get DCDC\n", __func__);
213490dcee9SAxel Lin 		return PTR_ERR(dcdc);
2140081e802SMark Brown 	}
2150081e802SMark Brown 
216c957b614SMark Brown 	led = devm_kzalloc(&pdev->dev, sizeof(*led), GFP_KERNEL);
217490dcee9SAxel Lin 	if (led == NULL)
218490dcee9SAxel Lin 		return -ENOMEM;
2190081e802SMark Brown 
2200dd756f7SAndrew Lunn 	led->cdev.brightness_set_blocking = wm8350_led_set;
2210081e802SMark Brown 	led->cdev.default_trigger = pdata->default_trigger;
2220081e802SMark Brown 	led->cdev.name = pdata->name;
223859cb7f2SRichard Purdie 	led->cdev.flags |= LED_CORE_SUSPENDRESUME;
2240081e802SMark Brown 	led->enabled = regulator_is_enabled(isink);
2250081e802SMark Brown 	led->isink = isink;
2260081e802SMark Brown 	led->dcdc = dcdc;
2270081e802SMark Brown 
2280081e802SMark Brown 	for (i = 0; i < ARRAY_SIZE(isink_cur) - 1; i++)
2290081e802SMark Brown 		if (isink_cur[i] >= pdata->max_uA)
2300081e802SMark Brown 			break;
2310081e802SMark Brown 	led->max_uA_index = i;
2320081e802SMark Brown 	if (pdata->max_uA != isink_cur[i])
2330081e802SMark Brown 		dev_warn(&pdev->dev,
2340081e802SMark Brown 			 "Maximum current %duA is not directly supported,"
2350081e802SMark Brown 			 " check platform data\n",
2360081e802SMark Brown 			 pdata->max_uA);
2370081e802SMark Brown 
2380081e802SMark Brown 	spin_lock_init(&led->value_lock);
2390081e802SMark Brown 	led->value = LED_OFF;
2400081e802SMark Brown 	platform_set_drvdata(pdev, led);
2410081e802SMark Brown 
242490dcee9SAxel Lin 	return led_classdev_register(&pdev->dev, &led->cdev);
2430081e802SMark Brown }
2440081e802SMark Brown 
wm8350_led_remove(struct platform_device * pdev)2450081e802SMark Brown static int wm8350_led_remove(struct platform_device *pdev)
2460081e802SMark Brown {
2470081e802SMark Brown 	struct wm8350_led *led = platform_get_drvdata(pdev);
2480081e802SMark Brown 
2490081e802SMark Brown 	led_classdev_unregister(&led->cdev);
2500081e802SMark Brown 	wm8350_led_disable(led);
2510081e802SMark Brown 	return 0;
2520081e802SMark Brown }
2530081e802SMark Brown 
2540081e802SMark Brown static struct platform_driver wm8350_led_driver = {
2550081e802SMark Brown 	.driver = {
2560081e802SMark Brown 		   .name = "wm8350-led",
2570081e802SMark Brown 		   },
2580081e802SMark Brown 	.probe = wm8350_led_probe,
2590081e802SMark Brown 	.remove = wm8350_led_remove,
2600081e802SMark Brown 	.shutdown = wm8350_led_shutdown,
2610081e802SMark Brown };
2620081e802SMark Brown 
263892a8843SAxel Lin module_platform_driver(wm8350_led_driver);
2640081e802SMark Brown 
2650081e802SMark Brown MODULE_AUTHOR("Mark Brown");
2660081e802SMark Brown MODULE_DESCRIPTION("WM8350 LED driver");
2670081e802SMark Brown MODULE_LICENSE("GPL");
2680081e802SMark Brown MODULE_ALIAS("platform:wm8350-led");
269