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 #include <linux/slab.h> 20 #include <linux/module.h> 21 22 /* Microamps */ 23 static const int isink_cur[] = { 24 4, 25 5, 26 6, 27 7, 28 8, 29 10, 30 11, 31 14, 32 16, 33 19, 34 23, 35 27, 36 32, 37 39, 38 46, 39 54, 40 65, 41 77, 42 92, 43 109, 44 130, 45 154, 46 183, 47 218, 48 259, 49 308, 50 367, 51 436, 52 518, 53 616, 54 733, 55 872, 56 1037, 57 1233, 58 1466, 59 1744, 60 2073, 61 2466, 62 2933, 63 3487, 64 4147, 65 4932, 66 5865, 67 6975, 68 8294, 69 9864, 70 11730, 71 13949, 72 16589, 73 19728, 74 23460, 75 27899, 76 33178, 77 39455, 78 46920, 79 55798, 80 66355, 81 78910, 82 93840, 83 111596, 84 132710, 85 157820, 86 187681, 87 223191 88 }; 89 90 #define to_wm8350_led(led_cdev) \ 91 container_of(led_cdev, struct wm8350_led, cdev) 92 93 static void wm8350_led_enable(struct wm8350_led *led) 94 { 95 int ret; 96 97 if (led->enabled) 98 return; 99 100 ret = regulator_enable(led->isink); 101 if (ret != 0) { 102 dev_err(led->cdev.dev, "Failed to enable ISINK: %d\n", ret); 103 return; 104 } 105 106 ret = regulator_enable(led->dcdc); 107 if (ret != 0) { 108 dev_err(led->cdev.dev, "Failed to enable DCDC: %d\n", ret); 109 regulator_disable(led->isink); 110 return; 111 } 112 113 led->enabled = 1; 114 } 115 116 static void wm8350_led_disable(struct wm8350_led *led) 117 { 118 int ret; 119 120 if (!led->enabled) 121 return; 122 123 ret = regulator_disable(led->dcdc); 124 if (ret != 0) { 125 dev_err(led->cdev.dev, "Failed to disable DCDC: %d\n", ret); 126 return; 127 } 128 129 ret = regulator_disable(led->isink); 130 if (ret != 0) { 131 dev_err(led->cdev.dev, "Failed to disable ISINK: %d\n", ret); 132 regulator_enable(led->dcdc); 133 return; 134 } 135 136 led->enabled = 0; 137 } 138 139 static void led_work(struct work_struct *work) 140 { 141 struct wm8350_led *led = container_of(work, struct wm8350_led, work); 142 int ret; 143 int uA; 144 unsigned long flags; 145 146 mutex_lock(&led->mutex); 147 148 spin_lock_irqsave(&led->value_lock, flags); 149 150 if (led->value == LED_OFF) { 151 spin_unlock_irqrestore(&led->value_lock, flags); 152 wm8350_led_disable(led); 153 goto out; 154 } 155 156 /* This scales linearly into the index of valid current 157 * settings which results in a linear scaling of perceived 158 * brightness due to the non-linear current settings provided 159 * by the hardware. 160 */ 161 uA = (led->max_uA_index * led->value) / LED_FULL; 162 spin_unlock_irqrestore(&led->value_lock, flags); 163 BUG_ON(uA >= ARRAY_SIZE(isink_cur)); 164 165 ret = regulator_set_current_limit(led->isink, isink_cur[uA], 166 isink_cur[uA]); 167 if (ret != 0) 168 dev_err(led->cdev.dev, "Failed to set %duA: %d\n", 169 isink_cur[uA], ret); 170 171 wm8350_led_enable(led); 172 173 out: 174 mutex_unlock(&led->mutex); 175 } 176 177 static void wm8350_led_set(struct led_classdev *led_cdev, 178 enum led_brightness value) 179 { 180 struct wm8350_led *led = to_wm8350_led(led_cdev); 181 unsigned long flags; 182 183 spin_lock_irqsave(&led->value_lock, flags); 184 led->value = value; 185 schedule_work(&led->work); 186 spin_unlock_irqrestore(&led->value_lock, flags); 187 } 188 189 static void wm8350_led_shutdown(struct platform_device *pdev) 190 { 191 struct wm8350_led *led = platform_get_drvdata(pdev); 192 193 mutex_lock(&led->mutex); 194 led->value = LED_OFF; 195 wm8350_led_disable(led); 196 mutex_unlock(&led->mutex); 197 } 198 199 static int wm8350_led_probe(struct platform_device *pdev) 200 { 201 struct regulator *isink, *dcdc; 202 struct wm8350_led *led; 203 struct wm8350_led_platform_data *pdata = pdev->dev.platform_data; 204 int ret, i; 205 206 if (pdata == NULL) { 207 dev_err(&pdev->dev, "no platform data\n"); 208 return -ENODEV; 209 } 210 211 if (pdata->max_uA < isink_cur[0]) { 212 dev_err(&pdev->dev, "Invalid maximum current %duA\n", 213 pdata->max_uA); 214 return -EINVAL; 215 } 216 217 isink = regulator_get(&pdev->dev, "led_isink"); 218 if (IS_ERR(isink)) { 219 printk(KERN_ERR "%s: can't get ISINK\n", __func__); 220 return PTR_ERR(isink); 221 } 222 223 dcdc = regulator_get(&pdev->dev, "led_vcc"); 224 if (IS_ERR(dcdc)) { 225 printk(KERN_ERR "%s: can't get DCDC\n", __func__); 226 ret = PTR_ERR(dcdc); 227 goto err_isink; 228 } 229 230 led = devm_kzalloc(&pdev->dev, sizeof(*led), GFP_KERNEL); 231 if (led == NULL) { 232 ret = -ENOMEM; 233 goto err_dcdc; 234 } 235 236 led->cdev.brightness_set = wm8350_led_set; 237 led->cdev.default_trigger = pdata->default_trigger; 238 led->cdev.name = pdata->name; 239 led->cdev.flags |= LED_CORE_SUSPENDRESUME; 240 led->enabled = regulator_is_enabled(isink); 241 led->isink = isink; 242 led->dcdc = dcdc; 243 244 for (i = 0; i < ARRAY_SIZE(isink_cur) - 1; i++) 245 if (isink_cur[i] >= pdata->max_uA) 246 break; 247 led->max_uA_index = i; 248 if (pdata->max_uA != isink_cur[i]) 249 dev_warn(&pdev->dev, 250 "Maximum current %duA is not directly supported," 251 " check platform data\n", 252 pdata->max_uA); 253 254 spin_lock_init(&led->value_lock); 255 mutex_init(&led->mutex); 256 INIT_WORK(&led->work, led_work); 257 led->value = LED_OFF; 258 platform_set_drvdata(pdev, led); 259 260 ret = led_classdev_register(&pdev->dev, &led->cdev); 261 if (ret < 0) 262 goto err_dcdc; 263 264 return 0; 265 266 err_dcdc: 267 regulator_put(dcdc); 268 err_isink: 269 regulator_put(isink); 270 return ret; 271 } 272 273 static int wm8350_led_remove(struct platform_device *pdev) 274 { 275 struct wm8350_led *led = platform_get_drvdata(pdev); 276 277 led_classdev_unregister(&led->cdev); 278 flush_work_sync(&led->work); 279 wm8350_led_disable(led); 280 regulator_put(led->dcdc); 281 regulator_put(led->isink); 282 return 0; 283 } 284 285 static struct platform_driver wm8350_led_driver = { 286 .driver = { 287 .name = "wm8350-led", 288 .owner = THIS_MODULE, 289 }, 290 .probe = wm8350_led_probe, 291 .remove = wm8350_led_remove, 292 .shutdown = wm8350_led_shutdown, 293 }; 294 295 module_platform_driver(wm8350_led_driver); 296 297 MODULE_AUTHOR("Mark Brown"); 298 MODULE_DESCRIPTION("WM8350 LED driver"); 299 MODULE_LICENSE("GPL"); 300 MODULE_ALIAS("platform:wm8350-led"); 301