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