268 lines · c
1// SPDX-License-Identifier: GPL-2.0-only2/*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 */18static 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 22319183};84 85#define to_wm8350_led(led_cdev) \86 container_of(led_cdev, struct wm8350_led, cdev)87 88static 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 113static 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 141static 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 current159 * settings which results in a linear scaling of perceived160 * brightness due to the non-linear current settings provided161 * 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 178static 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 186static 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 245static 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 253static struct platform_driver wm8350_led_driver = {254 .driver = {255 .name = "wm8350-led",256 },257 .probe = wm8350_led_probe,258 .remove_new = wm8350_led_remove,259 .shutdown = wm8350_led_shutdown,260};261 262module_platform_driver(wm8350_led_driver);263 264MODULE_AUTHOR("Mark Brown");265MODULE_DESCRIPTION("WM8350 LED driver");266MODULE_LICENSE("GPL");267MODULE_ALIAS("platform:wm8350-led");268