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 ret = regulator_enable(led->dcdc); 133 if (ret != 0) 134 dev_err(led->cdev.dev, "Failed to reenable DCDC: %d\n", 135 ret); 136 return; 137 } 138 139 led->enabled = 0; 140 } 141 142 static void led_work(struct work_struct *work) 143 { 144 struct wm8350_led *led = container_of(work, struct wm8350_led, work); 145 int ret; 146 int uA; 147 unsigned long flags; 148 149 mutex_lock(&led->mutex); 150 151 spin_lock_irqsave(&led->value_lock, flags); 152 153 if (led->value == LED_OFF) { 154 spin_unlock_irqrestore(&led->value_lock, flags); 155 wm8350_led_disable(led); 156 goto out; 157 } 158 159 /* This scales linearly into the index of valid current 160 * settings which results in a linear scaling of perceived 161 * brightness due to the non-linear current settings provided 162 * by the hardware. 163 */ 164 uA = (led->max_uA_index * led->value) / LED_FULL; 165 spin_unlock_irqrestore(&led->value_lock, flags); 166 BUG_ON(uA >= ARRAY_SIZE(isink_cur)); 167 168 ret = regulator_set_current_limit(led->isink, isink_cur[uA], 169 isink_cur[uA]); 170 if (ret != 0) 171 dev_err(led->cdev.dev, "Failed to set %duA: %d\n", 172 isink_cur[uA], ret); 173 174 wm8350_led_enable(led); 175 176 out: 177 mutex_unlock(&led->mutex); 178 } 179 180 static void wm8350_led_set(struct led_classdev *led_cdev, 181 enum led_brightness value) 182 { 183 struct wm8350_led *led = to_wm8350_led(led_cdev); 184 unsigned long flags; 185 186 spin_lock_irqsave(&led->value_lock, flags); 187 led->value = value; 188 schedule_work(&led->work); 189 spin_unlock_irqrestore(&led->value_lock, flags); 190 } 191 192 static void wm8350_led_shutdown(struct platform_device *pdev) 193 { 194 struct wm8350_led *led = platform_get_drvdata(pdev); 195 196 mutex_lock(&led->mutex); 197 led->value = LED_OFF; 198 wm8350_led_disable(led); 199 mutex_unlock(&led->mutex); 200 } 201 202 static int wm8350_led_probe(struct platform_device *pdev) 203 { 204 struct regulator *isink, *dcdc; 205 struct wm8350_led *led; 206 struct wm8350_led_platform_data *pdata = dev_get_platdata(&pdev->dev); 207 int i; 208 209 if (pdata == NULL) { 210 dev_err(&pdev->dev, "no platform data\n"); 211 return -ENODEV; 212 } 213 214 if (pdata->max_uA < isink_cur[0]) { 215 dev_err(&pdev->dev, "Invalid maximum current %duA\n", 216 pdata->max_uA); 217 return -EINVAL; 218 } 219 220 isink = devm_regulator_get(&pdev->dev, "led_isink"); 221 if (IS_ERR(isink)) { 222 dev_err(&pdev->dev, "%s: can't get ISINK\n", __func__); 223 return PTR_ERR(isink); 224 } 225 226 dcdc = devm_regulator_get(&pdev->dev, "led_vcc"); 227 if (IS_ERR(dcdc)) { 228 dev_err(&pdev->dev, "%s: can't get DCDC\n", __func__); 229 return PTR_ERR(dcdc); 230 } 231 232 led = devm_kzalloc(&pdev->dev, sizeof(*led), GFP_KERNEL); 233 if (led == NULL) 234 return -ENOMEM; 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 return led_classdev_register(&pdev->dev, &led->cdev); 261 } 262 263 static int wm8350_led_remove(struct platform_device *pdev) 264 { 265 struct wm8350_led *led = platform_get_drvdata(pdev); 266 267 led_classdev_unregister(&led->cdev); 268 flush_work(&led->work); 269 wm8350_led_disable(led); 270 return 0; 271 } 272 273 static struct platform_driver wm8350_led_driver = { 274 .driver = { 275 .name = "wm8350-led", 276 .owner = THIS_MODULE, 277 }, 278 .probe = wm8350_led_probe, 279 .remove = wm8350_led_remove, 280 .shutdown = wm8350_led_shutdown, 281 }; 282 283 module_platform_driver(wm8350_led_driver); 284 285 MODULE_AUTHOR("Mark Brown"); 286 MODULE_DESCRIPTION("WM8350 LED driver"); 287 MODULE_LICENSE("GPL"); 288 MODULE_ALIAS("platform:wm8350-led"); 289