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/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 <dmurphy@ti.com>"); 247 MODULE_DESCRIPTION("Multicolor LED class interface"); 248 MODULE_LICENSE("GPL v2"); 249