Commit | Line | Data |
---|---|---|
d2912cb1 | 1 | // SPDX-License-Identifier: GPL-2.0-only |
d4cc6a2e AO |
2 | /* |
3 | * leds-regulator.c - LED class driver for regulator driven LEDs. | |
4 | * | |
5 | * Copyright (C) 2009 Antonio Ospite <ospite@studenti.unina.it> | |
6 | * | |
7 | * Inspired by leds-wm8350 driver. | |
d4cc6a2e AO |
8 | */ |
9 | ||
10 | #include <linux/module.h> | |
835fc89e | 11 | #include <linux/mod_devicetable.h> |
d4cc6a2e | 12 | #include <linux/err.h> |
5a0e3ad6 | 13 | #include <linux/slab.h> |
d4cc6a2e AO |
14 | #include <linux/leds.h> |
15 | #include <linux/leds-regulator.h> | |
16 | #include <linux/platform_device.h> | |
17 | #include <linux/regulator/consumer.h> | |
18 | ||
19 | #define to_regulator_led(led_cdev) \ | |
20 | container_of(led_cdev, struct regulator_led, cdev) | |
21 | ||
22 | struct regulator_led { | |
23 | struct led_classdev cdev; | |
d4cc6a2e AO |
24 | int enabled; |
25 | struct mutex mutex; | |
d4cc6a2e AO |
26 | |
27 | struct regulator *vcc; | |
28 | }; | |
29 | ||
30 | static inline int led_regulator_get_max_brightness(struct regulator *supply) | |
31 | { | |
32 | int ret; | |
33 | int voltage = regulator_list_voltage(supply, 0); | |
34 | ||
35 | if (voltage <= 0) | |
36 | return 1; | |
37 | ||
38 | /* even if regulator can't change voltages, | |
39 | * we still assume it can change status | |
40 | * and the LED can be turned on and off. | |
41 | */ | |
42 | ret = regulator_set_voltage(supply, voltage, voltage); | |
43 | if (ret < 0) | |
44 | return 1; | |
45 | ||
46 | return regulator_count_voltages(supply); | |
47 | } | |
48 | ||
49 | static int led_regulator_get_voltage(struct regulator *supply, | |
50 | enum led_brightness brightness) | |
51 | { | |
52 | if (brightness == 0) | |
53 | return -EINVAL; | |
54 | ||
55 | return regulator_list_voltage(supply, brightness - 1); | |
56 | } | |
57 | ||
58 | ||
59 | static void regulator_led_enable(struct regulator_led *led) | |
60 | { | |
61 | int ret; | |
62 | ||
63 | if (led->enabled) | |
64 | return; | |
65 | ||
66 | ret = regulator_enable(led->vcc); | |
67 | if (ret != 0) { | |
68 | dev_err(led->cdev.dev, "Failed to enable vcc: %d\n", ret); | |
69 | return; | |
70 | } | |
71 | ||
72 | led->enabled = 1; | |
73 | } | |
74 | ||
75 | static void regulator_led_disable(struct regulator_led *led) | |
76 | { | |
77 | int ret; | |
78 | ||
79 | if (!led->enabled) | |
80 | return; | |
81 | ||
82 | ret = regulator_disable(led->vcc); | |
83 | if (ret != 0) { | |
84 | dev_err(led->cdev.dev, "Failed to disable vcc: %d\n", ret); | |
85 | return; | |
86 | } | |
87 | ||
88 | led->enabled = 0; | |
89 | } | |
90 | ||
77e85036 AL |
91 | static int regulator_led_brightness_set(struct led_classdev *led_cdev, |
92 | enum led_brightness value) | |
d4cc6a2e | 93 | { |
77e85036 | 94 | struct regulator_led *led = to_regulator_led(led_cdev); |
d4cc6a2e | 95 | int voltage; |
77e85036 | 96 | int ret = 0; |
d4cc6a2e AO |
97 | |
98 | mutex_lock(&led->mutex); | |
99 | ||
77e85036 | 100 | if (value == LED_OFF) { |
d4cc6a2e AO |
101 | regulator_led_disable(led); |
102 | goto out; | |
103 | } | |
104 | ||
105 | if (led->cdev.max_brightness > 1) { | |
77e85036 | 106 | voltage = led_regulator_get_voltage(led->vcc, value); |
d4cc6a2e | 107 | dev_dbg(led->cdev.dev, "brightness: %d voltage: %d\n", |
77e85036 | 108 | value, voltage); |
d4cc6a2e AO |
109 | |
110 | ret = regulator_set_voltage(led->vcc, voltage, voltage); | |
111 | if (ret != 0) | |
112 | dev_err(led->cdev.dev, "Failed to set voltage %d: %d\n", | |
113 | voltage, ret); | |
114 | } | |
115 | ||
116 | regulator_led_enable(led); | |
117 | ||
118 | out: | |
119 | mutex_unlock(&led->mutex); | |
77e85036 | 120 | return ret; |
d4cc6a2e AO |
121 | } |
122 | ||
98ea1ea2 | 123 | static int regulator_led_probe(struct platform_device *pdev) |
d4cc6a2e | 124 | { |
87aae1ea JH |
125 | struct led_regulator_platform_data *pdata = |
126 | dev_get_platdata(&pdev->dev); | |
4c350c65 | 127 | struct device *dev = &pdev->dev; |
835fc89e | 128 | struct led_init_data init_data = {}; |
d4cc6a2e AO |
129 | struct regulator_led *led; |
130 | struct regulator *vcc; | |
131 | int ret = 0; | |
132 | ||
4c350c65 | 133 | vcc = devm_regulator_get_exclusive(dev, "vled"); |
d4cc6a2e | 134 | if (IS_ERR(vcc)) { |
835fc89e | 135 | dev_err(dev, "Cannot get vcc\n"); |
d4cc6a2e AO |
136 | return PTR_ERR(vcc); |
137 | } | |
138 | ||
4c350c65 | 139 | led = devm_kzalloc(dev, sizeof(*led), GFP_KERNEL); |
29ce9feb AL |
140 | if (led == NULL) |
141 | return -ENOMEM; | |
d4cc6a2e | 142 | |
835fc89e LW |
143 | init_data.fwnode = dev->fwnode; |
144 | ||
d4cc6a2e | 145 | led->cdev.max_brightness = led_regulator_get_max_brightness(vcc); |
835fc89e LW |
146 | /* Legacy platform data label assignment */ |
147 | if (pdata) { | |
148 | if (pdata->brightness > led->cdev.max_brightness) { | |
149 | dev_err(dev, "Invalid default brightness %d\n", | |
d4cc6a2e | 150 | pdata->brightness); |
835fc89e LW |
151 | return -EINVAL; |
152 | } | |
153 | led->cdev.brightness = pdata->brightness; | |
154 | init_data.default_label = pdata->name; | |
d4cc6a2e | 155 | } |
d4cc6a2e | 156 | |
77e85036 | 157 | led->cdev.brightness_set_blocking = regulator_led_brightness_set; |
d4cc6a2e AO |
158 | led->cdev.flags |= LED_CORE_SUSPENDRESUME; |
159 | led->vcc = vcc; | |
160 | ||
592ce316 AO |
161 | /* to handle correctly an already enabled regulator */ |
162 | if (regulator_is_enabled(led->vcc)) | |
163 | led->enabled = 1; | |
164 | ||
d4cc6a2e | 165 | mutex_init(&led->mutex); |
d4cc6a2e AO |
166 | |
167 | platform_set_drvdata(pdev, led); | |
168 | ||
835fc89e | 169 | ret = led_classdev_register_ext(dev, &led->cdev, &init_data); |
77e85036 | 170 | if (ret < 0) |
29ce9feb | 171 | return ret; |
d4cc6a2e | 172 | |
d4cc6a2e | 173 | return 0; |
d4cc6a2e AO |
174 | } |
175 | ||
60613020 | 176 | static void regulator_led_remove(struct platform_device *pdev) |
d4cc6a2e AO |
177 | { |
178 | struct regulator_led *led = platform_get_drvdata(pdev); | |
179 | ||
180 | led_classdev_unregister(&led->cdev); | |
d4cc6a2e | 181 | regulator_led_disable(led); |
d4cc6a2e AO |
182 | } |
183 | ||
835fc89e LW |
184 | static const struct of_device_id regulator_led_of_match[] = { |
185 | { .compatible = "regulator-led", }, | |
186 | {} | |
187 | }; | |
188 | MODULE_DEVICE_TABLE(of, regulator_led_of_match); | |
189 | ||
d4cc6a2e AO |
190 | static struct platform_driver regulator_led_driver = { |
191 | .driver = { | |
835fc89e LW |
192 | .name = "leds-regulator", |
193 | .of_match_table = regulator_led_of_match, | |
194 | }, | |
d4cc6a2e | 195 | .probe = regulator_led_probe, |
60613020 | 196 | .remove_new = regulator_led_remove, |
d4cc6a2e AO |
197 | }; |
198 | ||
892a8843 | 199 | module_platform_driver(regulator_led_driver); |
d4cc6a2e AO |
200 | |
201 | MODULE_AUTHOR("Antonio Ospite <ospite@studenti.unina.it>"); | |
202 | MODULE_DESCRIPTION("Regulator driven LED driver"); | |
203 | MODULE_LICENSE("GPL"); | |
204 | MODULE_ALIAS("platform:leds-regulator"); |