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