Linux 6.9-rc1
[linux-2.6-microblaze.git] / drivers / leds / led-class-multicolor.c
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
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
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
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
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_colors[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
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         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
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
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
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
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
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");