projects/linux·blob master · refs

248 lines · 6.5 KiB

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 <[email protected]>
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
15static 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
33int 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}
47EXPORT_SYMBOL_GPL(led_mc_calc_color_components);
48
49static 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}
67static DEVICE_ATTR_RO(multi_max_intensity);
68
69static 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;
107err_out:
108 mutex_unlock(&led_cdev->led_access);
109 return ret;
110}
111
112static 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}
131static DEVICE_ATTR_RW(multi_intensity);
132
133static 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}
153static DEVICE_ATTR_RO(multi_index);
154
155static 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};
161ATTRIBUTE_GROUPS(led_multicolor);
162
163int 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}
184EXPORT_SYMBOL_GPL(led_classdev_multicolor_register_ext);
185
186void 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}
193EXPORT_SYMBOL_GPL(led_classdev_multicolor_unregister);
194
195static void devm_led_classdev_multicolor_release(struct device *dev, void *res)
196{
197 led_classdev_multicolor_unregister(*(struct led_classdev_mc **)res);
198}
199
200int 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}
224EXPORT_SYMBOL_GPL(devm_led_classdev_multicolor_register_ext);
225
226static 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
237void 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}
244EXPORT_SYMBOL_GPL(devm_led_classdev_multicolor_unregister);
245
246MODULE_AUTHOR("Dan Murphy <[email protected]>");
247MODULE_DESCRIPTION("Multicolor LED class interface");
248MODULE_LICENSE("GPL v2");