xref: /linux/drivers/leds/led-class-multicolor.c (revision 7f71507851fc7764b36a3221839607d3a45c2025)
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/module.h>
11 #include <linux/slab.h>
12 #include <linux/uaccess.h>
13 
14 int led_mc_calc_color_components(struct led_classdev_mc *mcled_cdev,
15 				 enum led_brightness brightness)
16 {
17 	struct led_classdev *led_cdev = &mcled_cdev->led_cdev;
18 	int i;
19 
20 	for (i = 0; i < mcled_cdev->num_colors; i++)
21 		mcled_cdev->subled_info[i].brightness =
22 			DIV_ROUND_CLOSEST(brightness *
23 					  mcled_cdev->subled_info[i].intensity,
24 					  led_cdev->max_brightness);
25 
26 	return 0;
27 }
28 EXPORT_SYMBOL_GPL(led_mc_calc_color_components);
29 
30 static ssize_t multi_intensity_store(struct device *dev,
31 				struct device_attribute *intensity_attr,
32 				const char *buf, size_t size)
33 {
34 	struct led_classdev *led_cdev = dev_get_drvdata(dev);
35 	struct led_classdev_mc *mcled_cdev = lcdev_to_mccdev(led_cdev);
36 	int nrchars, offset = 0;
37 	int intensity_value[LED_COLOR_ID_MAX];
38 	int i;
39 	ssize_t ret;
40 
41 	mutex_lock(&led_cdev->led_access);
42 
43 	for (i = 0; i < mcled_cdev->num_colors; i++) {
44 		ret = sscanf(buf + offset, "%i%n",
45 			     &intensity_value[i], &nrchars);
46 		if (ret != 1) {
47 			ret = -EINVAL;
48 			goto err_out;
49 		}
50 		offset += nrchars;
51 	}
52 
53 	offset++;
54 	if (offset < size) {
55 		ret = -EINVAL;
56 		goto err_out;
57 	}
58 
59 	for (i = 0; i < mcled_cdev->num_colors; i++)
60 		mcled_cdev->subled_info[i].intensity = intensity_value[i];
61 
62 	led_set_brightness(led_cdev, led_cdev->brightness);
63 	ret = size;
64 err_out:
65 	mutex_unlock(&led_cdev->led_access);
66 	return ret;
67 }
68 
69 static ssize_t multi_intensity_show(struct device *dev,
70 			      struct device_attribute *intensity_attr,
71 			      char *buf)
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 len = 0;
76 	int i;
77 
78 	for (i = 0; i < mcled_cdev->num_colors; i++) {
79 		len += sprintf(buf + len, "%d",
80 			       mcled_cdev->subled_info[i].intensity);
81 		if (i < mcled_cdev->num_colors - 1)
82 			len += sprintf(buf + len, " ");
83 	}
84 
85 	buf[len++] = '\n';
86 	return len;
87 }
88 static DEVICE_ATTR_RW(multi_intensity);
89 
90 static ssize_t multi_index_show(struct device *dev,
91 			      struct device_attribute *multi_index_attr,
92 			      char *buf)
93 {
94 	struct led_classdev *led_cdev = dev_get_drvdata(dev);
95 	struct led_classdev_mc *mcled_cdev = lcdev_to_mccdev(led_cdev);
96 	int len = 0;
97 	int index;
98 	int i;
99 
100 	for (i = 0; i < mcled_cdev->num_colors; i++) {
101 		index = mcled_cdev->subled_info[i].color_index;
102 		len += sprintf(buf + len, "%s", led_get_color_name(index));
103 		if (i < mcled_cdev->num_colors - 1)
104 			len += sprintf(buf + len, " ");
105 	}
106 
107 	buf[len++] = '\n';
108 	return len;
109 }
110 static DEVICE_ATTR_RO(multi_index);
111 
112 static struct attribute *led_multicolor_attrs[] = {
113 	&dev_attr_multi_intensity.attr,
114 	&dev_attr_multi_index.attr,
115 	NULL,
116 };
117 ATTRIBUTE_GROUPS(led_multicolor);
118 
119 int led_classdev_multicolor_register_ext(struct device *parent,
120 				     struct led_classdev_mc *mcled_cdev,
121 				     struct led_init_data *init_data)
122 {
123 	struct led_classdev *led_cdev;
124 
125 	if (!mcled_cdev)
126 		return -EINVAL;
127 
128 	if (mcled_cdev->num_colors <= 0)
129 		return -EINVAL;
130 
131 	if (mcled_cdev->num_colors > LED_COLOR_ID_MAX)
132 		return -EINVAL;
133 
134 	led_cdev = &mcled_cdev->led_cdev;
135 	led_cdev->flags |= LED_MULTI_COLOR;
136 	mcled_cdev->led_cdev.groups = led_multicolor_groups;
137 
138 	return led_classdev_register_ext(parent, led_cdev, init_data);
139 }
140 EXPORT_SYMBOL_GPL(led_classdev_multicolor_register_ext);
141 
142 void led_classdev_multicolor_unregister(struct led_classdev_mc *mcled_cdev)
143 {
144 	if (!mcled_cdev)
145 		return;
146 
147 	led_classdev_unregister(&mcled_cdev->led_cdev);
148 }
149 EXPORT_SYMBOL_GPL(led_classdev_multicolor_unregister);
150 
151 static void devm_led_classdev_multicolor_release(struct device *dev, void *res)
152 {
153 	led_classdev_multicolor_unregister(*(struct led_classdev_mc **)res);
154 }
155 
156 int devm_led_classdev_multicolor_register_ext(struct device *parent,
157 					     struct led_classdev_mc *mcled_cdev,
158 					     struct led_init_data *init_data)
159 {
160 	struct led_classdev_mc **dr;
161 	int ret;
162 
163 	dr = devres_alloc(devm_led_classdev_multicolor_release,
164 			  sizeof(*dr), GFP_KERNEL);
165 	if (!dr)
166 		return -ENOMEM;
167 
168 	ret = led_classdev_multicolor_register_ext(parent, mcled_cdev,
169 						   init_data);
170 	if (ret) {
171 		devres_free(dr);
172 		return ret;
173 	}
174 
175 	*dr = mcled_cdev;
176 	devres_add(parent, dr);
177 
178 	return 0;
179 }
180 EXPORT_SYMBOL_GPL(devm_led_classdev_multicolor_register_ext);
181 
182 static int devm_led_classdev_multicolor_match(struct device *dev,
183 					      void *res, void *data)
184 {
185 	struct led_classdev_mc **p = res;
186 
187 	if (WARN_ON(!p || !*p))
188 		return 0;
189 
190 	return *p == data;
191 }
192 
193 void devm_led_classdev_multicolor_unregister(struct device *dev,
194 					     struct led_classdev_mc *mcled_cdev)
195 {
196 	WARN_ON(devres_release(dev,
197 			       devm_led_classdev_multicolor_release,
198 			       devm_led_classdev_multicolor_match, mcled_cdev));
199 }
200 EXPORT_SYMBOL_GPL(devm_led_classdev_multicolor_unregister);
201 
202 MODULE_AUTHOR("Dan Murphy <dmurphy@ti.com>");
203 MODULE_DESCRIPTION("Multicolor LED class interface");
204 MODULE_LICENSE("GPL v2");
205