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