xref: /linux/drivers/leds/led-class-multicolor.c (revision bba2c3615bd6cfee7456d1130f2e6b01b3f4e9ba)
1 // SPDX-License-Identifier: GPL-2.0
2 // LED Multicolor class interface
3 // Copyright (C) 2019-20 Texas Instruments Incorporated - http://www.ti.com/
4 // Author: Dan Murphy <dmurphy@ti.com>
5 
6 #include <linux/device.h>
7 #include <linux/init.h>
8 #include <linux/led-class-multicolor.h>
9 #include <linux/math.h>
10 #include <linux/minmax.h>
11 #include <linux/module.h>
12 #include <linux/slab.h>
13 #include <linux/uaccess.h>
14 
15 static unsigned int led_mc_get_max_intensity(struct led_classdev_mc *mcled_cdev, size_t index)
16 {
17 	unsigned int max_intensity;
18 
19 	/*
20 	 * The maximum global brightness value might still be changed by
21 	 * led_classdev_register_ext() using devicetree properties. This
22 	 * prevents us from changing subled_info[X].max_intensity when
23 	 * registering a multicolor LED class device, so we have to do
24 	 * this during runtime.
25 	 */
26 	max_intensity = mcled_cdev->subled_info[index].max_intensity;
27 	if (max_intensity)
28 		return max_intensity;
29 
30 	return mcled_cdev->led_cdev.max_brightness;
31 }
32 
33 int led_mc_calc_color_components(struct led_classdev_mc *mcled_cdev,
34 				 enum led_brightness brightness)
35 {
36 	struct led_classdev *led_cdev = &mcled_cdev->led_cdev;
37 	int i;
38 
39 	for (i = 0; i < mcled_cdev->num_colors; i++)
40 		mcled_cdev->subled_info[i].brightness =
41 			DIV_ROUND_CLOSEST(brightness *
42 					  mcled_cdev->subled_info[i].intensity,
43 					  led_cdev->max_brightness);
44 
45 	return 0;
46 }
47 EXPORT_SYMBOL_GPL(led_mc_calc_color_components);
48 
49 static ssize_t multi_max_intensity_show(struct device *dev,
50 					struct device_attribute *intensity_attr, char *buf)
51 {
52 	struct led_classdev *led_cdev = dev_get_drvdata(dev);
53 	struct led_classdev_mc *mcled_cdev = lcdev_to_mccdev(led_cdev);
54 	unsigned int max_intensity;
55 	int len = 0;
56 
57 	for (int i = 0; i < mcled_cdev->num_colors; i++) {
58 		max_intensity = led_mc_get_max_intensity(mcled_cdev, i);
59 		len += sysfs_emit_at(buf, len, "%u", max_intensity);
60 		if (i < mcled_cdev->num_colors - 1)
61 			len += sprintf(buf + len, " ");
62 	}
63 
64 	buf[len++] = '\n';
65 	return len;
66 }
67 static DEVICE_ATTR_RO(multi_max_intensity);
68 
69 static ssize_t multi_intensity_store(struct device *dev,
70 				struct device_attribute *intensity_attr,
71 				const char *buf, size_t size)
72 {
73 	struct led_classdev *led_cdev = dev_get_drvdata(dev);
74 	struct led_classdev_mc *mcled_cdev = lcdev_to_mccdev(led_cdev);
75 	int nrchars, offset = 0;
76 	unsigned int intensity_value[LED_COLOR_ID_MAX];
77 	unsigned int max_intensity;
78 	int i;
79 	ssize_t ret;
80 
81 	mutex_lock(&led_cdev->led_access);
82 
83 	for (i = 0; i < mcled_cdev->num_colors; i++) {
84 		ret = sscanf(buf + offset, "%u%n",
85 			     &intensity_value[i], &nrchars);
86 		if (ret != 1) {
87 			ret = -EINVAL;
88 			goto err_out;
89 		}
90 		offset += nrchars;
91 	}
92 
93 	offset++;
94 	if (offset < size) {
95 		ret = -EINVAL;
96 		goto err_out;
97 	}
98 
99 	for (int i = 0; i < mcled_cdev->num_colors; i++) {
100 		max_intensity = led_mc_get_max_intensity(mcled_cdev, i);
101 		mcled_cdev->subled_info[i].intensity = min(intensity_value[i], max_intensity);
102 	}
103 
104 	if (!test_bit(LED_BLINK_SW, &led_cdev->work_flags))
105 		led_set_brightness(led_cdev, led_cdev->brightness);
106 	ret = size;
107 err_out:
108 	mutex_unlock(&led_cdev->led_access);
109 	return ret;
110 }
111 
112 static ssize_t multi_intensity_show(struct device *dev,
113 			      struct device_attribute *intensity_attr,
114 			      char *buf)
115 {
116 	struct led_classdev *led_cdev = dev_get_drvdata(dev);
117 	struct led_classdev_mc *mcled_cdev = lcdev_to_mccdev(led_cdev);
118 	int len = 0;
119 	int i;
120 
121 	for (i = 0; i < mcled_cdev->num_colors; i++) {
122 		len += sprintf(buf + len, "%d",
123 			       mcled_cdev->subled_info[i].intensity);
124 		if (i < mcled_cdev->num_colors - 1)
125 			len += sprintf(buf + len, " ");
126 	}
127 
128 	buf[len++] = '\n';
129 	return len;
130 }
131 static DEVICE_ATTR_RW(multi_intensity);
132 
133 static ssize_t multi_index_show(struct device *dev,
134 			      struct device_attribute *multi_index_attr,
135 			      char *buf)
136 {
137 	struct led_classdev *led_cdev = dev_get_drvdata(dev);
138 	struct led_classdev_mc *mcled_cdev = lcdev_to_mccdev(led_cdev);
139 	int len = 0;
140 	int index;
141 	int i;
142 
143 	for (i = 0; i < mcled_cdev->num_colors; i++) {
144 		index = mcled_cdev->subled_info[i].color_index;
145 		len += sprintf(buf + len, "%s", led_get_color_name(index));
146 		if (i < mcled_cdev->num_colors - 1)
147 			len += sprintf(buf + len, " ");
148 	}
149 
150 	buf[len++] = '\n';
151 	return len;
152 }
153 static DEVICE_ATTR_RO(multi_index);
154 
155 static struct attribute *led_multicolor_attrs[] = {
156 	&dev_attr_multi_max_intensity.attr,
157 	&dev_attr_multi_intensity.attr,
158 	&dev_attr_multi_index.attr,
159 	NULL,
160 };
161 ATTRIBUTE_GROUPS(led_multicolor);
162 
163 int led_classdev_multicolor_register_ext(struct device *parent,
164 				     struct led_classdev_mc *mcled_cdev,
165 				     struct led_init_data *init_data)
166 {
167 	struct led_classdev *led_cdev;
168 
169 	if (!mcled_cdev)
170 		return -EINVAL;
171 
172 	if (mcled_cdev->num_colors <= 0)
173 		return -EINVAL;
174 
175 	if (mcled_cdev->num_colors > LED_COLOR_ID_MAX)
176 		return -EINVAL;
177 
178 	led_cdev = &mcled_cdev->led_cdev;
179 	led_cdev->flags |= LED_MULTI_COLOR;
180 	mcled_cdev->led_cdev.groups = led_multicolor_groups;
181 
182 	return led_classdev_register_ext(parent, led_cdev, init_data);
183 }
184 EXPORT_SYMBOL_GPL(led_classdev_multicolor_register_ext);
185 
186 void led_classdev_multicolor_unregister(struct led_classdev_mc *mcled_cdev)
187 {
188 	if (!mcled_cdev)
189 		return;
190 
191 	led_classdev_unregister(&mcled_cdev->led_cdev);
192 }
193 EXPORT_SYMBOL_GPL(led_classdev_multicolor_unregister);
194 
195 static void devm_led_classdev_multicolor_release(struct device *dev, void *res)
196 {
197 	led_classdev_multicolor_unregister(*(struct led_classdev_mc **)res);
198 }
199 
200 int devm_led_classdev_multicolor_register_ext(struct device *parent,
201 					     struct led_classdev_mc *mcled_cdev,
202 					     struct led_init_data *init_data)
203 {
204 	struct led_classdev_mc **dr;
205 	int ret;
206 
207 	dr = devres_alloc(devm_led_classdev_multicolor_release,
208 			  sizeof(*dr), GFP_KERNEL);
209 	if (!dr)
210 		return -ENOMEM;
211 
212 	ret = led_classdev_multicolor_register_ext(parent, mcled_cdev,
213 						   init_data);
214 	if (ret) {
215 		devres_free(dr);
216 		return ret;
217 	}
218 
219 	*dr = mcled_cdev;
220 	devres_add(parent, dr);
221 
222 	return 0;
223 }
224 EXPORT_SYMBOL_GPL(devm_led_classdev_multicolor_register_ext);
225 
226 static int devm_led_classdev_multicolor_match(struct device *dev,
227 					      void *res, void *data)
228 {
229 	struct led_classdev_mc **p = res;
230 
231 	if (WARN_ON(!p || !*p))
232 		return 0;
233 
234 	return *p == data;
235 }
236 
237 void devm_led_classdev_multicolor_unregister(struct device *dev,
238 					     struct led_classdev_mc *mcled_cdev)
239 {
240 	WARN_ON(devres_release(dev,
241 			       devm_led_classdev_multicolor_release,
242 			       devm_led_classdev_multicolor_match, mcled_cdev));
243 }
244 EXPORT_SYMBOL_GPL(devm_led_classdev_multicolor_unregister);
245 
246 MODULE_AUTHOR("Dan Murphy <dmurphy@ti.com>");
247 MODULE_DESCRIPTION("Multicolor LED class interface");
248 MODULE_LICENSE("GPL v2");
249