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