1 // SPDX-License-Identifier: GPL-2.0-only 2 /* 3 * LED driver for WM8350 driven LEDS. 4 * 5 * Copyright(C) 2007, 2008 Wolfson Microelectronics PLC. 6 */ 7 8 #include <linux/kernel.h> 9 #include <linux/platform_device.h> 10 #include <linux/leds.h> 11 #include <linux/err.h> 12 #include <linux/mfd/wm8350/pmic.h> 13 #include <linux/regulator/consumer.h> 14 #include <linux/slab.h> 15 #include <linux/module.h> 16 17 /* Microamps */ 18 static const int isink_cur[] = { 19 4, 20 5, 21 6, 22 7, 23 8, 24 10, 25 11, 26 14, 27 16, 28 19, 29 23, 30 27, 31 32, 32 39, 33 46, 34 54, 35 65, 36 77, 37 92, 38 109, 39 130, 40 154, 41 183, 42 218, 43 259, 44 308, 45 367, 46 436, 47 518, 48 616, 49 733, 50 872, 51 1037, 52 1233, 53 1466, 54 1744, 55 2073, 56 2466, 57 2933, 58 3487, 59 4147, 60 4932, 61 5865, 62 6975, 63 8294, 64 9864, 65 11730, 66 13949, 67 16589, 68 19728, 69 23460, 70 27899, 71 33178, 72 39455, 73 46920, 74 55798, 75 66355, 76 78910, 77 93840, 78 111596, 79 132710, 80 157820, 81 187681, 82 223191 83 }; 84 85 #define to_wm8350_led(led_cdev) \ 86 container_of(led_cdev, struct wm8350_led, cdev) 87 88 static int wm8350_led_enable(struct wm8350_led *led) 89 { 90 int ret = 0; 91 92 if (led->enabled) 93 return ret; 94 95 ret = regulator_enable(led->isink); 96 if (ret != 0) { 97 dev_err(led->cdev.dev, "Failed to enable ISINK: %d\n", ret); 98 return ret; 99 } 100 101 ret = regulator_enable(led->dcdc); 102 if (ret != 0) { 103 dev_err(led->cdev.dev, "Failed to enable DCDC: %d\n", ret); 104 regulator_disable(led->isink); 105 return ret; 106 } 107 108 led->enabled = 1; 109 110 return ret; 111 } 112 113 static int wm8350_led_disable(struct wm8350_led *led) 114 { 115 int ret = 0; 116 117 if (!led->enabled) 118 return ret; 119 120 ret = regulator_disable(led->dcdc); 121 if (ret != 0) { 122 dev_err(led->cdev.dev, "Failed to disable DCDC: %d\n", ret); 123 return ret; 124 } 125 126 ret = regulator_disable(led->isink); 127 if (ret != 0) { 128 dev_err(led->cdev.dev, "Failed to disable ISINK: %d\n", ret); 129 ret = regulator_enable(led->dcdc); 130 if (ret != 0) 131 dev_err(led->cdev.dev, "Failed to reenable DCDC: %d\n", 132 ret); 133 return ret; 134 } 135 136 led->enabled = 0; 137 138 return ret; 139 } 140 141 static int wm8350_led_set(struct led_classdev *led_cdev, 142 enum led_brightness value) 143 { 144 struct wm8350_led *led = to_wm8350_led(led_cdev); 145 unsigned long flags; 146 int ret; 147 int uA; 148 149 led->value = value; 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 return wm8350_led_disable(led); 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 return ret; 173 } 174 175 return wm8350_led_enable(led); 176 } 177 178 static void wm8350_led_shutdown(struct platform_device *pdev) 179 { 180 struct wm8350_led *led = platform_get_drvdata(pdev); 181 182 led->value = LED_OFF; 183 wm8350_led_disable(led); 184 } 185 186 static int wm8350_led_probe(struct platform_device *pdev) 187 { 188 struct regulator *isink, *dcdc; 189 struct wm8350_led *led; 190 struct wm8350_led_platform_data *pdata = dev_get_platdata(&pdev->dev); 191 int i; 192 193 if (pdata == NULL) { 194 dev_err(&pdev->dev, "no platform data\n"); 195 return -ENODEV; 196 } 197 198 if (pdata->max_uA < isink_cur[0]) { 199 dev_err(&pdev->dev, "Invalid maximum current %duA\n", 200 pdata->max_uA); 201 return -EINVAL; 202 } 203 204 isink = devm_regulator_get(&pdev->dev, "led_isink"); 205 if (IS_ERR(isink)) { 206 dev_err(&pdev->dev, "%s: can't get ISINK\n", __func__); 207 return PTR_ERR(isink); 208 } 209 210 dcdc = devm_regulator_get(&pdev->dev, "led_vcc"); 211 if (IS_ERR(dcdc)) { 212 dev_err(&pdev->dev, "%s: can't get DCDC\n", __func__); 213 return PTR_ERR(dcdc); 214 } 215 216 led = devm_kzalloc(&pdev->dev, sizeof(*led), GFP_KERNEL); 217 if (led == NULL) 218 return -ENOMEM; 219 220 led->cdev.brightness_set_blocking = wm8350_led_set; 221 led->cdev.default_trigger = pdata->default_trigger; 222 led->cdev.name = pdata->name; 223 led->cdev.flags |= LED_CORE_SUSPENDRESUME; 224 led->enabled = regulator_is_enabled(isink); 225 led->isink = isink; 226 led->dcdc = dcdc; 227 228 for (i = 0; i < ARRAY_SIZE(isink_cur) - 1; i++) 229 if (isink_cur[i] >= pdata->max_uA) 230 break; 231 led->max_uA_index = i; 232 if (pdata->max_uA != isink_cur[i]) 233 dev_warn(&pdev->dev, 234 "Maximum current %duA is not directly supported," 235 " check platform data\n", 236 pdata->max_uA); 237 238 spin_lock_init(&led->value_lock); 239 led->value = LED_OFF; 240 platform_set_drvdata(pdev, led); 241 242 return led_classdev_register(&pdev->dev, &led->cdev); 243 } 244 245 static void wm8350_led_remove(struct platform_device *pdev) 246 { 247 struct wm8350_led *led = platform_get_drvdata(pdev); 248 249 led_classdev_unregister(&led->cdev); 250 wm8350_led_disable(led); 251 } 252 253 static struct platform_driver wm8350_led_driver = { 254 .driver = { 255 .name = "wm8350-led", 256 }, 257 .probe = wm8350_led_probe, 258 .remove = wm8350_led_remove, 259 .shutdown = wm8350_led_shutdown, 260 }; 261 262 module_platform_driver(wm8350_led_driver); 263 264 MODULE_AUTHOR("Mark Brown"); 265 MODULE_DESCRIPTION("WM8350 LED driver"); 266 MODULE_LICENSE("GPL"); 267 MODULE_ALIAS("platform:wm8350-led"); 268