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