Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

leds: Add WM8350 LED driver

The voltage and current regulators on the WM8350 AudioPlus PMIC can be
used in concert to provide a power efficient LED driver. This driver
implements support for this within the standard LED class.

Platform initialisation code should configure the LED hardware in the
init callback provided by the WM8350 core driver. The callback should
use wm8350_isink_set_flash(), wm8350_dcdc25_set_mode() and
wm8350_dcdc_set_slot() to configure the operating parameters of the
regulators for their hardware and then then use wm8350_register_led() to
instantiate the LED driver.

This driver was originally written by Liam Girdwood, though it has been
extensively modified since then.

Signed-off-by: Mark Brown <broonie@opensource.wolfsonmicro.com>
Signed-off-by: Richard Purdie <rpurdie@linux.intel.com>

authored by

Mark Brown and committed by
Richard Purdie
0081e802 934cd3f9

+471
+7
drivers/leds/Kconfig
··· 164 164 LED driver chips accessed via the I2C bus. Supported 165 165 devices include PCA9550, PCA9551, PCA9552, and PCA9553. 166 166 167 + config LEDS_WM8350 168 + tristate "LED Support for WM8350 AudioPlus PMIC" 169 + depends on LEDS_CLASS && MFD_WM8350 170 + help 171 + This option enables support for LEDs driven by the Wolfson 172 + Microelectronics WM8350 AudioPlus PMIC. 173 + 167 174 config LEDS_DA903X 168 175 tristate "LED Support for DA9030/DA9034 PMIC" 169 176 depends on LEDS_CLASS && PMIC_DA903X
+1
drivers/leds/Makefile
··· 24 24 obj-$(CONFIG_LEDS_PCA955X) += leds-pca955x.o 25 25 obj-$(CONFIG_LEDS_DA903X) += leds-da903x.o 26 26 obj-$(CONFIG_LEDS_HP_DISK) += leds-hp-disk.o 27 + obj-$(CONFIG_LEDS_WM8350) += leds-wm8350.o 27 28 28 29 # LED Triggers 29 30 obj-$(CONFIG_LEDS_TRIGGER_TIMER) += ledtrig-timer.o
+333
drivers/leds/leds-wm8350.c
··· 1 + /* 2 + * LED driver for WM8350 driven LEDS. 3 + * 4 + * Copyright(C) 2007, 2008 Wolfson Microelectronics PLC. 5 + * 6 + * This program is free software; you can redistribute it and/or modify 7 + * it under the terms of the GNU General Public License version 2 as 8 + * published by the Free Software Foundation. 9 + * 10 + */ 11 + 12 + #include <linux/kernel.h> 13 + #include <linux/init.h> 14 + #include <linux/platform_device.h> 15 + #include <linux/leds.h> 16 + #include <linux/err.h> 17 + #include <linux/mfd/wm8350/pmic.h> 18 + #include <linux/regulator/consumer.h> 19 + 20 + /* Microamps */ 21 + static const int isink_cur[] = { 22 + 4, 23 + 5, 24 + 6, 25 + 7, 26 + 8, 27 + 10, 28 + 11, 29 + 14, 30 + 16, 31 + 19, 32 + 23, 33 + 27, 34 + 32, 35 + 39, 36 + 46, 37 + 54, 38 + 65, 39 + 77, 40 + 92, 41 + 109, 42 + 130, 43 + 154, 44 + 183, 45 + 218, 46 + 259, 47 + 308, 48 + 367, 49 + 436, 50 + 518, 51 + 616, 52 + 733, 53 + 872, 54 + 1037, 55 + 1233, 56 + 1466, 57 + 1744, 58 + 2073, 59 + 2466, 60 + 2933, 61 + 3487, 62 + 4147, 63 + 4932, 64 + 5865, 65 + 6975, 66 + 8294, 67 + 9864, 68 + 11730, 69 + 13949, 70 + 16589, 71 + 19728, 72 + 23460, 73 + 27899, 74 + 33178, 75 + 39455, 76 + 46920, 77 + 55798, 78 + 66355, 79 + 78910, 80 + 93840, 81 + 111596, 82 + 132710, 83 + 157820, 84 + 187681, 85 + 223191 86 + }; 87 + 88 + #define to_wm8350_led(led_cdev) \ 89 + container_of(led_cdev, struct wm8350_led, cdev) 90 + 91 + static void wm8350_led_enable(struct wm8350_led *led) 92 + { 93 + int ret; 94 + 95 + if (led->enabled) 96 + return; 97 + 98 + ret = regulator_enable(led->isink); 99 + if (ret != 0) { 100 + dev_err(led->cdev.dev, "Failed to enable ISINK: %d\n", ret); 101 + return; 102 + } 103 + 104 + ret = regulator_enable(led->dcdc); 105 + if (ret != 0) { 106 + dev_err(led->cdev.dev, "Failed to enable DCDC: %d\n", ret); 107 + regulator_disable(led->isink); 108 + return; 109 + } 110 + 111 + led->enabled = 1; 112 + } 113 + 114 + static void wm8350_led_disable(struct wm8350_led *led) 115 + { 116 + int ret; 117 + 118 + if (!led->enabled) 119 + return; 120 + 121 + ret = regulator_disable(led->dcdc); 122 + if (ret != 0) { 123 + dev_err(led->cdev.dev, "Failed to disable DCDC: %d\n", ret); 124 + return; 125 + } 126 + 127 + ret = regulator_disable(led->isink); 128 + if (ret != 0) { 129 + dev_err(led->cdev.dev, "Failed to disable ISINK: %d\n", ret); 130 + regulator_enable(led->dcdc); 131 + return; 132 + } 133 + 134 + led->enabled = 0; 135 + } 136 + 137 + static void led_work(struct work_struct *work) 138 + { 139 + struct wm8350_led *led = container_of(work, struct wm8350_led, work); 140 + int ret; 141 + int uA; 142 + unsigned long flags; 143 + 144 + mutex_lock(&led->mutex); 145 + 146 + spin_lock_irqsave(&led->value_lock, flags); 147 + 148 + if (led->value == LED_OFF) { 149 + spin_unlock_irqrestore(&led->value_lock, flags); 150 + wm8350_led_disable(led); 151 + goto out; 152 + } 153 + 154 + /* This scales linearly into the index of valid current 155 + * settings which results in a linear scaling of perceived 156 + * brightness due to the non-linear current settings provided 157 + * by the hardware. 158 + */ 159 + uA = (led->max_uA_index * led->value) / LED_FULL; 160 + spin_unlock_irqrestore(&led->value_lock, flags); 161 + BUG_ON(uA >= ARRAY_SIZE(isink_cur)); 162 + 163 + ret = regulator_set_current_limit(led->isink, isink_cur[uA], 164 + isink_cur[uA]); 165 + if (ret != 0) 166 + dev_err(led->cdev.dev, "Failed to set %duA: %d\n", 167 + isink_cur[uA], ret); 168 + 169 + wm8350_led_enable(led); 170 + 171 + out: 172 + mutex_unlock(&led->mutex); 173 + } 174 + 175 + static void wm8350_led_set(struct led_classdev *led_cdev, 176 + enum led_brightness value) 177 + { 178 + struct wm8350_led *led = to_wm8350_led(led_cdev); 179 + unsigned long flags; 180 + 181 + spin_lock_irqsave(&led->value_lock, flags); 182 + led->value = value; 183 + schedule_work(&led->work); 184 + spin_unlock_irqrestore(&led->value_lock, flags); 185 + } 186 + 187 + #ifdef CONFIG_PM 188 + static int wm8350_led_suspend(struct platform_device *pdev, pm_message_t state) 189 + { 190 + struct wm8350_led *led = platform_get_drvdata(pdev); 191 + 192 + led_classdev_suspend(&led->cdev); 193 + return 0; 194 + } 195 + 196 + static int wm8350_led_resume(struct platform_device *pdev) 197 + { 198 + struct wm8350_led *led = platform_get_drvdata(pdev); 199 + 200 + led_classdev_resume(&led->cdev); 201 + return 0; 202 + } 203 + #else 204 + #define wm8350_led_suspend NULL 205 + #define wm8350_led_resume NULL 206 + #endif 207 + 208 + static void wm8350_led_shutdown(struct platform_device *pdev) 209 + { 210 + struct wm8350_led *led = platform_get_drvdata(pdev); 211 + 212 + mutex_lock(&led->mutex); 213 + led->value = LED_OFF; 214 + wm8350_led_disable(led); 215 + mutex_unlock(&led->mutex); 216 + } 217 + 218 + static int wm8350_led_probe(struct platform_device *pdev) 219 + { 220 + struct regulator *isink, *dcdc; 221 + struct wm8350_led *led; 222 + struct wm8350_led_platform_data *pdata = pdev->dev.platform_data; 223 + int ret, i; 224 + 225 + if (pdata == NULL) { 226 + dev_err(&pdev->dev, "no platform data\n"); 227 + return -ENODEV; 228 + } 229 + 230 + if (pdata->max_uA < isink_cur[0]) { 231 + dev_err(&pdev->dev, "Invalid maximum current %duA\n", 232 + pdata->max_uA); 233 + return -EINVAL; 234 + } 235 + 236 + isink = regulator_get(&pdev->dev, "led_isink"); 237 + if (IS_ERR(isink)) { 238 + printk(KERN_ERR "%s: cant get ISINK\n", __func__); 239 + return PTR_ERR(isink); 240 + } 241 + 242 + dcdc = regulator_get(&pdev->dev, "led_vcc"); 243 + if (IS_ERR(dcdc)) { 244 + printk(KERN_ERR "%s: cant get DCDC\n", __func__); 245 + ret = PTR_ERR(dcdc); 246 + goto err_isink; 247 + } 248 + 249 + led = kzalloc(sizeof(*led), GFP_KERNEL); 250 + if (led == NULL) { 251 + ret = -ENOMEM; 252 + goto err_dcdc; 253 + } 254 + 255 + led->cdev.brightness_set = wm8350_led_set; 256 + led->cdev.default_trigger = pdata->default_trigger; 257 + led->cdev.name = pdata->name; 258 + led->enabled = regulator_is_enabled(isink); 259 + led->isink = isink; 260 + led->dcdc = dcdc; 261 + 262 + for (i = 0; i < ARRAY_SIZE(isink_cur) - 1; i++) 263 + if (isink_cur[i] >= pdata->max_uA) 264 + break; 265 + led->max_uA_index = i; 266 + if (pdata->max_uA != isink_cur[i]) 267 + dev_warn(&pdev->dev, 268 + "Maximum current %duA is not directly supported," 269 + " check platform data\n", 270 + pdata->max_uA); 271 + 272 + spin_lock_init(&led->value_lock); 273 + mutex_init(&led->mutex); 274 + INIT_WORK(&led->work, led_work); 275 + led->value = LED_OFF; 276 + platform_set_drvdata(pdev, led); 277 + 278 + ret = led_classdev_register(&pdev->dev, &led->cdev); 279 + if (ret < 0) 280 + goto err_led; 281 + 282 + return 0; 283 + 284 + err_led: 285 + kfree(led); 286 + err_dcdc: 287 + regulator_put(dcdc); 288 + err_isink: 289 + regulator_put(isink); 290 + return ret; 291 + } 292 + 293 + static int wm8350_led_remove(struct platform_device *pdev) 294 + { 295 + struct wm8350_led *led = platform_get_drvdata(pdev); 296 + 297 + led_classdev_unregister(&led->cdev); 298 + flush_scheduled_work(); 299 + wm8350_led_disable(led); 300 + regulator_put(led->dcdc); 301 + regulator_put(led->isink); 302 + kfree(led); 303 + return 0; 304 + } 305 + 306 + static struct platform_driver wm8350_led_driver = { 307 + .driver = { 308 + .name = "wm8350-led", 309 + .owner = THIS_MODULE, 310 + }, 311 + .probe = wm8350_led_probe, 312 + .remove = wm8350_led_remove, 313 + .shutdown = wm8350_led_shutdown, 314 + .suspend = wm8350_led_suspend, 315 + .resume = wm8350_led_resume, 316 + }; 317 + 318 + static int __devinit wm8350_led_init(void) 319 + { 320 + return platform_driver_register(&wm8350_led_driver); 321 + } 322 + module_init(wm8350_led_init); 323 + 324 + static void wm8350_led_exit(void) 325 + { 326 + platform_driver_unregister(&wm8350_led_driver); 327 + } 328 + module_exit(wm8350_led_exit); 329 + 330 + MODULE_AUTHOR("Mark Brown"); 331 + MODULE_DESCRIPTION("WM8350 LED driver"); 332 + MODULE_LICENSE("GPL"); 333 + MODULE_ALIAS("platform:wm8350-led");
+3
drivers/mfd/wm8350-core.c
··· 1453 1453 { 1454 1454 int i; 1455 1455 1456 + for (i = 0; i < ARRAY_SIZE(wm8350->pmic.led); i++) 1457 + platform_device_unregister(wm8350->pmic.led[i].pdev); 1458 + 1456 1459 for (i = 0; i < ARRAY_SIZE(wm8350->pmic.pdev); i++) 1457 1460 platform_device_unregister(wm8350->pmic.pdev[i]); 1458 1461
+91
drivers/regulator/wm8350-regulator.c
··· 1412 1412 } 1413 1413 EXPORT_SYMBOL_GPL(wm8350_register_regulator); 1414 1414 1415 + /** 1416 + * wm8350_register_led - Register a WM8350 LED output 1417 + * 1418 + * @param wm8350 The WM8350 device to configure. 1419 + * @param lednum LED device index to create. 1420 + * @param dcdc The DCDC to use for the LED. 1421 + * @param isink The ISINK to use for the LED. 1422 + * @param pdata Configuration for the LED. 1423 + * 1424 + * The WM8350 supports the use of an ISINK together with a DCDC to 1425 + * provide a power-efficient LED driver. This function registers the 1426 + * regulators and instantiates the platform device for a LED. The 1427 + * operating modes for the LED regulators must be configured using 1428 + * wm8350_isink_set_flash(), wm8350_dcdc25_set_mode() and 1429 + * wm8350_dcdc_set_slot() prior to calling this function. 1430 + */ 1431 + int wm8350_register_led(struct wm8350 *wm8350, int lednum, int dcdc, int isink, 1432 + struct wm8350_led_platform_data *pdata) 1433 + { 1434 + struct wm8350_led *led; 1435 + struct platform_device *pdev; 1436 + int ret; 1437 + 1438 + if (lednum > ARRAY_SIZE(wm8350->pmic.led) || lednum < 0) { 1439 + dev_err(wm8350->dev, "Invalid LED index %d\n", lednum); 1440 + return -ENODEV; 1441 + } 1442 + 1443 + led = &wm8350->pmic.led[lednum]; 1444 + 1445 + if (led->pdev) { 1446 + dev_err(wm8350->dev, "LED %d already allocated\n", lednum); 1447 + return -EINVAL; 1448 + } 1449 + 1450 + pdev = platform_device_alloc("wm8350-led", lednum); 1451 + if (pdev == NULL) { 1452 + dev_err(wm8350->dev, "Failed to allocate LED %d\n", lednum); 1453 + return -ENOMEM; 1454 + } 1455 + 1456 + led->isink_consumer.dev = &pdev->dev; 1457 + led->isink_consumer.supply = "led_isink"; 1458 + led->isink_init.num_consumer_supplies = 1; 1459 + led->isink_init.consumer_supplies = &led->isink_consumer; 1460 + led->isink_init.constraints.min_uA = 0; 1461 + led->isink_init.constraints.max_uA = pdata->max_uA; 1462 + led->isink_init.constraints.valid_ops_mask = REGULATOR_CHANGE_CURRENT; 1463 + led->isink_init.constraints.valid_modes_mask = REGULATOR_MODE_NORMAL; 1464 + ret = wm8350_register_regulator(wm8350, isink, &led->isink_init); 1465 + if (ret != 0) { 1466 + platform_device_put(pdev); 1467 + return ret; 1468 + } 1469 + 1470 + led->dcdc_consumer.dev = &pdev->dev; 1471 + led->dcdc_consumer.supply = "led_vcc"; 1472 + led->dcdc_init.num_consumer_supplies = 1; 1473 + led->dcdc_init.consumer_supplies = &led->dcdc_consumer; 1474 + led->dcdc_init.constraints.valid_modes_mask = REGULATOR_MODE_NORMAL; 1475 + ret = wm8350_register_regulator(wm8350, dcdc, &led->dcdc_init); 1476 + if (ret != 0) { 1477 + platform_device_put(pdev); 1478 + return ret; 1479 + } 1480 + 1481 + switch (isink) { 1482 + case WM8350_ISINK_A: 1483 + wm8350->pmic.isink_A_dcdc = dcdc; 1484 + break; 1485 + case WM8350_ISINK_B: 1486 + wm8350->pmic.isink_B_dcdc = dcdc; 1487 + break; 1488 + } 1489 + 1490 + pdev->dev.platform_data = pdata; 1491 + pdev->dev.parent = wm8350->dev; 1492 + ret = platform_device_add(pdev); 1493 + if (ret != 0) { 1494 + dev_err(wm8350->dev, "Failed to register LED %d: %d\n", 1495 + lednum, ret); 1496 + platform_device_put(pdev); 1497 + return ret; 1498 + } 1499 + 1500 + led->pdev = pdev; 1501 + 1502 + return 0; 1503 + } 1504 + EXPORT_SYMBOL_GPL(wm8350_register_led); 1505 + 1415 1506 static struct platform_driver wm8350_regulator_driver = { 1416 1507 .probe = wm8350_regulator_probe, 1417 1508 .remove = wm8350_regulator_remove,
+36
include/linux/mfd/wm8350/pmic.h
··· 13 13 #ifndef __LINUX_MFD_WM8350_PMIC_H 14 14 #define __LINUX_MFD_WM8350_PMIC_H 15 15 16 + #include <linux/platform_device.h> 17 + #include <linux/leds.h> 18 + #include <linux/regulator/machine.h> 19 + 16 20 /* 17 21 * Register values. 18 22 */ ··· 704 700 struct platform_device; 705 701 struct regulator_init_data; 706 702 703 + /* 704 + * WM8350 LED platform data 705 + */ 706 + struct wm8350_led_platform_data { 707 + const char *name; 708 + const char *default_trigger; 709 + int max_uA; 710 + }; 711 + 712 + struct wm8350_led { 713 + struct platform_device *pdev; 714 + struct mutex mutex; 715 + struct work_struct work; 716 + spinlock_t value_lock; 717 + enum led_brightness value; 718 + struct led_classdev cdev; 719 + int max_uA_index; 720 + int enabled; 721 + 722 + struct regulator *isink; 723 + struct regulator_consumer_supply isink_consumer; 724 + struct regulator_init_data isink_init; 725 + struct regulator *dcdc; 726 + struct regulator_consumer_supply dcdc_consumer; 727 + struct regulator_init_data dcdc_init; 728 + }; 729 + 707 730 struct wm8350_pmic { 708 731 /* Number of regulators of each type on this device */ 709 732 int max_dcdc; ··· 748 717 749 718 /* regulator devices */ 750 719 struct platform_device *pdev[NUM_WM8350_REGULATORS]; 720 + 721 + /* LED devices */ 722 + struct wm8350_led led[2]; 751 723 }; 752 724 753 725 int wm8350_register_regulator(struct wm8350 *wm8350, int reg, 754 726 struct regulator_init_data *initdata); 727 + int wm8350_register_led(struct wm8350 *wm8350, int lednum, int dcdc, int isink, 728 + struct wm8350_led_platform_data *pdata); 755 729 756 730 /* 757 731 * Additional DCDC control not supported via regulator API