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