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
led_mc_calc_color_components(struct led_classdev_mc * mcled_cdev,enum led_brightness brightness)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
multi_intensity_store(struct device * dev,struct device_attribute * intensity_attr,const char * buf,size_t size)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 if (!test_bit(LED_BLINK_SW, &led_cdev->work_flags))
63 led_set_brightness(led_cdev, led_cdev->brightness);
64 ret = size;
65 err_out:
66 mutex_unlock(&led_cdev->led_access);
67 return ret;
68 }
69
multi_intensity_show(struct device * dev,struct device_attribute * intensity_attr,char * buf)70 static ssize_t multi_intensity_show(struct device *dev,
71 struct device_attribute *intensity_attr,
72 char *buf)
73 {
74 struct led_classdev *led_cdev = dev_get_drvdata(dev);
75 struct led_classdev_mc *mcled_cdev = lcdev_to_mccdev(led_cdev);
76 int len = 0;
77 int i;
78
79 for (i = 0; i < mcled_cdev->num_colors; i++) {
80 len += sprintf(buf + len, "%d",
81 mcled_cdev->subled_info[i].intensity);
82 if (i < mcled_cdev->num_colors - 1)
83 len += sprintf(buf + len, " ");
84 }
85
86 buf[len++] = '\n';
87 return len;
88 }
89 static DEVICE_ATTR_RW(multi_intensity);
90
multi_index_show(struct device * dev,struct device_attribute * multi_index_attr,char * buf)91 static ssize_t multi_index_show(struct device *dev,
92 struct device_attribute *multi_index_attr,
93 char *buf)
94 {
95 struct led_classdev *led_cdev = dev_get_drvdata(dev);
96 struct led_classdev_mc *mcled_cdev = lcdev_to_mccdev(led_cdev);
97 int len = 0;
98 int index;
99 int i;
100
101 for (i = 0; i < mcled_cdev->num_colors; i++) {
102 index = mcled_cdev->subled_info[i].color_index;
103 len += sprintf(buf + len, "%s", led_get_color_name(index));
104 if (i < mcled_cdev->num_colors - 1)
105 len += sprintf(buf + len, " ");
106 }
107
108 buf[len++] = '\n';
109 return len;
110 }
111 static DEVICE_ATTR_RO(multi_index);
112
113 static struct attribute *led_multicolor_attrs[] = {
114 &dev_attr_multi_intensity.attr,
115 &dev_attr_multi_index.attr,
116 NULL,
117 };
118 ATTRIBUTE_GROUPS(led_multicolor);
119
led_classdev_multicolor_register_ext(struct device * parent,struct led_classdev_mc * mcled_cdev,struct led_init_data * init_data)120 int led_classdev_multicolor_register_ext(struct device *parent,
121 struct led_classdev_mc *mcled_cdev,
122 struct led_init_data *init_data)
123 {
124 struct led_classdev *led_cdev;
125
126 if (!mcled_cdev)
127 return -EINVAL;
128
129 if (mcled_cdev->num_colors <= 0)
130 return -EINVAL;
131
132 if (mcled_cdev->num_colors > LED_COLOR_ID_MAX)
133 return -EINVAL;
134
135 led_cdev = &mcled_cdev->led_cdev;
136 led_cdev->flags |= LED_MULTI_COLOR;
137 mcled_cdev->led_cdev.groups = led_multicolor_groups;
138
139 return led_classdev_register_ext(parent, led_cdev, init_data);
140 }
141 EXPORT_SYMBOL_GPL(led_classdev_multicolor_register_ext);
142
led_classdev_multicolor_unregister(struct led_classdev_mc * mcled_cdev)143 void led_classdev_multicolor_unregister(struct led_classdev_mc *mcled_cdev)
144 {
145 if (!mcled_cdev)
146 return;
147
148 led_classdev_unregister(&mcled_cdev->led_cdev);
149 }
150 EXPORT_SYMBOL_GPL(led_classdev_multicolor_unregister);
151
devm_led_classdev_multicolor_release(struct device * dev,void * res)152 static void devm_led_classdev_multicolor_release(struct device *dev, void *res)
153 {
154 led_classdev_multicolor_unregister(*(struct led_classdev_mc **)res);
155 }
156
devm_led_classdev_multicolor_register_ext(struct device * parent,struct led_classdev_mc * mcled_cdev,struct led_init_data * init_data)157 int devm_led_classdev_multicolor_register_ext(struct device *parent,
158 struct led_classdev_mc *mcled_cdev,
159 struct led_init_data *init_data)
160 {
161 struct led_classdev_mc **dr;
162 int ret;
163
164 dr = devres_alloc(devm_led_classdev_multicolor_release,
165 sizeof(*dr), GFP_KERNEL);
166 if (!dr)
167 return -ENOMEM;
168
169 ret = led_classdev_multicolor_register_ext(parent, mcled_cdev,
170 init_data);
171 if (ret) {
172 devres_free(dr);
173 return ret;
174 }
175
176 *dr = mcled_cdev;
177 devres_add(parent, dr);
178
179 return 0;
180 }
181 EXPORT_SYMBOL_GPL(devm_led_classdev_multicolor_register_ext);
182
devm_led_classdev_multicolor_match(struct device * dev,void * res,void * data)183 static int devm_led_classdev_multicolor_match(struct device *dev,
184 void *res, void *data)
185 {
186 struct led_classdev_mc **p = res;
187
188 if (WARN_ON(!p || !*p))
189 return 0;
190
191 return *p == data;
192 }
193
devm_led_classdev_multicolor_unregister(struct device * dev,struct led_classdev_mc * mcled_cdev)194 void devm_led_classdev_multicolor_unregister(struct device *dev,
195 struct led_classdev_mc *mcled_cdev)
196 {
197 WARN_ON(devres_release(dev,
198 devm_led_classdev_multicolor_release,
199 devm_led_classdev_multicolor_match, mcled_cdev));
200 }
201 EXPORT_SYMBOL_GPL(devm_led_classdev_multicolor_unregister);
202
203 MODULE_AUTHOR("Dan Murphy <dmurphy@ti.com>");
204 MODULE_DESCRIPTION("Multicolor LED class interface");
205 MODULE_LICENSE("GPL v2");
206