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 int wm8350_led_enable(struct wm8350_led *led) 93 { 94 int ret = 0; 95 96 if (led->enabled) 97 return ret; 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 ret; 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 ret; 110 } 111 112 led->enabled = 1; 113 114 return ret; 115 } 116 117 static int wm8350_led_disable(struct wm8350_led *led) 118 { 119 int ret = 0; 120 121 if (!led->enabled) 122 return ret; 123 124 ret = regulator_disable(led->dcdc); 125 if (ret != 0) { 126 dev_err(led->cdev.dev, "Failed to disable DCDC: %d\n", ret); 127 return ret; 128 } 129 130 ret = regulator_disable(led->isink); 131 if (ret != 0) { 132 dev_err(led->cdev.dev, "Failed to disable ISINK: %d\n", ret); 133 ret = regulator_enable(led->dcdc); 134 if (ret != 0) 135 dev_err(led->cdev.dev, "Failed to reenable DCDC: %d\n", 136 ret); 137 return ret; 138 } 139 140 led->enabled = 0; 141 142 return ret; 143 } 144 145 static int wm8350_led_set(struct led_classdev *led_cdev, 146 enum led_brightness value) 147 { 148 struct wm8350_led *led = to_wm8350_led(led_cdev); 149 unsigned long flags; 150 int ret; 151 int uA; 152 153 led->value = value; 154 155 spin_lock_irqsave(&led->value_lock, flags); 156 157 if (led->value == LED_OFF) { 158 spin_unlock_irqrestore(&led->value_lock, flags); 159 return wm8350_led_disable(led); 160 } 161 162 /* This scales linearly into the index of valid current 163 * settings which results in a linear scaling of perceived 164 * brightness due to the non-linear current settings provided 165 * by the hardware. 166 */ 167 uA = (led->max_uA_index * led->value) / LED_FULL; 168 spin_unlock_irqrestore(&led->value_lock, flags); 169 BUG_ON(uA >= ARRAY_SIZE(isink_cur)); 170 171 ret = regulator_set_current_limit(led->isink, isink_cur[uA], 172 isink_cur[uA]); 173 if (ret != 0) { 174 dev_err(led->cdev.dev, "Failed to set %duA: %d\n", 175 isink_cur[uA], ret); 176 return ret; 177 } 178 179 return wm8350_led_enable(led); 180 } 181 182 static void wm8350_led_shutdown(struct platform_device *pdev) 183 { 184 struct wm8350_led *led = platform_get_drvdata(pdev); 185 186 led->value = LED_OFF; 187 wm8350_led_disable(led); 188 } 189 190 static int wm8350_led_probe(struct platform_device *pdev) 191 { 192 struct regulator *isink, *dcdc; 193 struct wm8350_led *led; 194 struct wm8350_led_platform_data *pdata = dev_get_platdata(&pdev->dev); 195 int i; 196 197 if (pdata == NULL) { 198 dev_err(&pdev->dev, "no platform data\n"); 199 return -ENODEV; 200 } 201 202 if (pdata->max_uA < isink_cur[0]) { 203 dev_err(&pdev->dev, "Invalid maximum current %duA\n", 204 pdata->max_uA); 205 return -EINVAL; 206 } 207 208 isink = devm_regulator_get(&pdev->dev, "led_isink"); 209 if (IS_ERR(isink)) { 210 dev_err(&pdev->dev, "%s: can't get ISINK\n", __func__); 211 return PTR_ERR(isink); 212 } 213 214 dcdc = devm_regulator_get(&pdev->dev, "led_vcc"); 215 if (IS_ERR(dcdc)) { 216 dev_err(&pdev->dev, "%s: can't get DCDC\n", __func__); 217 return PTR_ERR(dcdc); 218 } 219 220 led = devm_kzalloc(&pdev->dev, sizeof(*led), GFP_KERNEL); 221 if (led == NULL) 222 return -ENOMEM; 223 224 led->cdev.brightness_set_blocking = wm8350_led_set; 225 led->cdev.default_trigger = pdata->default_trigger; 226 led->cdev.name = pdata->name; 227 led->cdev.flags |= LED_CORE_SUSPENDRESUME; 228 led->enabled = regulator_is_enabled(isink); 229 led->isink = isink; 230 led->dcdc = dcdc; 231 232 for (i = 0; i < ARRAY_SIZE(isink_cur) - 1; i++) 233 if (isink_cur[i] >= pdata->max_uA) 234 break; 235 led->max_uA_index = i; 236 if (pdata->max_uA != isink_cur[i]) 237 dev_warn(&pdev->dev, 238 "Maximum current %duA is not directly supported," 239 " check platform data\n", 240 pdata->max_uA); 241 242 spin_lock_init(&led->value_lock); 243 led->value = LED_OFF; 244 platform_set_drvdata(pdev, led); 245 246 return led_classdev_register(&pdev->dev, &led->cdev); 247 } 248 249 static int wm8350_led_remove(struct platform_device *pdev) 250 { 251 struct wm8350_led *led = platform_get_drvdata(pdev); 252 253 led_classdev_unregister(&led->cdev); 254 wm8350_led_disable(led); 255 return 0; 256 } 257 258 static struct platform_driver wm8350_led_driver = { 259 .driver = { 260 .name = "wm8350-led", 261 }, 262 .probe = wm8350_led_probe, 263 .remove = wm8350_led_remove, 264 .shutdown = wm8350_led_shutdown, 265 }; 266 267 module_platform_driver(wm8350_led_driver); 268 269 MODULE_AUTHOR("Mark Brown"); 270 MODULE_DESCRIPTION("WM8350 LED driver"); 271 MODULE_LICENSE("GPL"); 272 MODULE_ALIAS("platform:wm8350-led"); 273