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 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 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 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 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 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 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"); 206