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