1*d2912cb1SThomas 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