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 static void wm8350_led_shutdown(struct platform_device *pdev) 188 { 189 struct wm8350_led *led = platform_get_drvdata(pdev); 190 191 mutex_lock(&led->mutex); 192 led->value = LED_OFF; 193 wm8350_led_disable(led); 194 mutex_unlock(&led->mutex); 195 } 196 197 static int wm8350_led_probe(struct platform_device *pdev) 198 { 199 struct regulator *isink, *dcdc; 200 struct wm8350_led *led; 201 struct wm8350_led_platform_data *pdata = pdev->dev.platform_data; 202 int ret, i; 203 204 if (pdata == NULL) { 205 dev_err(&pdev->dev, "no platform data\n"); 206 return -ENODEV; 207 } 208 209 if (pdata->max_uA < isink_cur[0]) { 210 dev_err(&pdev->dev, "Invalid maximum current %duA\n", 211 pdata->max_uA); 212 return -EINVAL; 213 } 214 215 isink = regulator_get(&pdev->dev, "led_isink"); 216 if (IS_ERR(isink)) { 217 printk(KERN_ERR "%s: cant get ISINK\n", __func__); 218 return PTR_ERR(isink); 219 } 220 221 dcdc = regulator_get(&pdev->dev, "led_vcc"); 222 if (IS_ERR(dcdc)) { 223 printk(KERN_ERR "%s: cant get DCDC\n", __func__); 224 ret = PTR_ERR(dcdc); 225 goto err_isink; 226 } 227 228 led = kzalloc(sizeof(*led), GFP_KERNEL); 229 if (led == NULL) { 230 ret = -ENOMEM; 231 goto err_dcdc; 232 } 233 234 led->cdev.brightness_set = wm8350_led_set; 235 led->cdev.default_trigger = pdata->default_trigger; 236 led->cdev.name = pdata->name; 237 led->cdev.flags |= LED_CORE_SUSPENDRESUME; 238 led->enabled = regulator_is_enabled(isink); 239 led->isink = isink; 240 led->dcdc = dcdc; 241 242 for (i = 0; i < ARRAY_SIZE(isink_cur) - 1; i++) 243 if (isink_cur[i] >= pdata->max_uA) 244 break; 245 led->max_uA_index = i; 246 if (pdata->max_uA != isink_cur[i]) 247 dev_warn(&pdev->dev, 248 "Maximum current %duA is not directly supported," 249 " check platform data\n", 250 pdata->max_uA); 251 252 spin_lock_init(&led->value_lock); 253 mutex_init(&led->mutex); 254 INIT_WORK(&led->work, led_work); 255 led->value = LED_OFF; 256 platform_set_drvdata(pdev, led); 257 258 ret = led_classdev_register(&pdev->dev, &led->cdev); 259 if (ret < 0) 260 goto err_led; 261 262 return 0; 263 264 err_led: 265 kfree(led); 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_scheduled_work(); 279 wm8350_led_disable(led); 280 regulator_put(led->dcdc); 281 regulator_put(led->isink); 282 kfree(led); 283 return 0; 284 } 285 286 static struct platform_driver wm8350_led_driver = { 287 .driver = { 288 .name = "wm8350-led", 289 .owner = THIS_MODULE, 290 }, 291 .probe = wm8350_led_probe, 292 .remove = wm8350_led_remove, 293 .shutdown = wm8350_led_shutdown, 294 }; 295 296 static int __devinit wm8350_led_init(void) 297 { 298 return platform_driver_register(&wm8350_led_driver); 299 } 300 module_init(wm8350_led_init); 301 302 static void wm8350_led_exit(void) 303 { 304 platform_driver_unregister(&wm8350_led_driver); 305 } 306 module_exit(wm8350_led_exit); 307 308 MODULE_AUTHOR("Mark Brown"); 309 MODULE_DESCRIPTION("WM8350 LED driver"); 310 MODULE_LICENSE("GPL"); 311 MODULE_ALIAS("platform:wm8350-led"); 312