1 // SPDX-License-Identifier: GPL-2.0 2 /* 3 * Multi-color LED built with monochromatic LED devices 4 * 5 * This driver groups several monochromatic LED devices in a single multicolor LED device. 6 * 7 * Compared to handling this grouping in user-space, the benefits are: 8 * - The state of the monochromatic LED relative to each other is always consistent. 9 * - The sysfs interface of the LEDs can be used for the group as a whole. 10 * 11 * Copyright 2023 Jean-Jacques Hiblot <jjhiblot@traphandler.com> 12 */ 13 14 #include <linux/err.h> 15 #include <linux/leds.h> 16 #include <linux/led-class-multicolor.h> 17 #include <linux/math.h> 18 #include <linux/module.h> 19 #include <linux/mod_devicetable.h> 20 #include <linux/platform_device.h> 21 #include <linux/property.h> 22 23 struct leds_multicolor { 24 struct led_classdev_mc mc_cdev; 25 struct led_classdev **monochromatics; 26 }; 27 28 static int leds_gmc_set(struct led_classdev *cdev, enum led_brightness brightness) 29 { 30 struct led_classdev_mc *mc_cdev = lcdev_to_mccdev(cdev); 31 struct leds_multicolor *priv = container_of(mc_cdev, struct leds_multicolor, mc_cdev); 32 const unsigned int group_max_brightness = mc_cdev->led_cdev.max_brightness; 33 int i; 34 35 for (i = 0; i < mc_cdev->num_colors; i++) { 36 struct led_classdev *mono = priv->monochromatics[i]; 37 const unsigned int mono_max_brightness = mono->max_brightness; 38 unsigned int intensity = mc_cdev->subled_info[i].intensity; 39 int mono_brightness; 40 41 /* 42 * Scale the brightness according to relative intensity of the 43 * color AND the max brightness of the monochromatic LED. 44 */ 45 mono_brightness = DIV_ROUND_CLOSEST(brightness * intensity * mono_max_brightness, 46 group_max_brightness * group_max_brightness); 47 48 led_set_brightness(mono, mono_brightness); 49 } 50 51 return 0; 52 } 53 54 static void restore_sysfs_write_access(void *data) 55 { 56 struct led_classdev *led_cdev = data; 57 58 /* Restore the write acccess to the LED */ 59 mutex_lock(&led_cdev->led_access); 60 led_sysfs_enable(led_cdev); 61 mutex_unlock(&led_cdev->led_access); 62 } 63 64 static int leds_gmc_probe(struct platform_device *pdev) 65 { 66 struct device *dev = &pdev->dev; 67 struct led_init_data init_data = {}; 68 struct led_classdev *cdev; 69 struct mc_subled *subled; 70 struct leds_multicolor *priv; 71 unsigned int max_brightness = 0; 72 int i, ret, count = 0, common_flags = 0; 73 74 priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); 75 if (!priv) 76 return -ENOMEM; 77 78 for (;;) { 79 struct led_classdev *led_cdev; 80 81 led_cdev = devm_of_led_get_optional(dev, count); 82 if (IS_ERR(led_cdev)) 83 return dev_err_probe(dev, PTR_ERR(led_cdev), "Unable to get LED #%d", 84 count); 85 if (!led_cdev) 86 break; 87 88 priv->monochromatics = devm_krealloc_array(dev, priv->monochromatics, 89 count + 1, sizeof(*priv->monochromatics), 90 GFP_KERNEL); 91 if (!priv->monochromatics) 92 return -ENOMEM; 93 94 common_flags |= led_cdev->flags; 95 priv->monochromatics[count] = led_cdev; 96 97 max_brightness = max(max_brightness, led_cdev->max_brightness); 98 99 count++; 100 } 101 102 subled = devm_kcalloc(dev, count, sizeof(*subled), GFP_KERNEL); 103 if (!subled) 104 return -ENOMEM; 105 priv->mc_cdev.subled_info = subled; 106 107 for (i = 0; i < count; i++) { 108 struct led_classdev *led_cdev = priv->monochromatics[i]; 109 110 subled[i].color_index = led_cdev->color; 111 112 /* Configure the LED intensity to its maximum */ 113 subled[i].intensity = max_brightness; 114 } 115 116 /* Initialise the multicolor's LED class device */ 117 cdev = &priv->mc_cdev.led_cdev; 118 cdev->brightness_set_blocking = leds_gmc_set; 119 cdev->max_brightness = max_brightness; 120 cdev->color = LED_COLOR_ID_MULTI; 121 priv->mc_cdev.num_colors = count; 122 123 /* we only need suspend/resume if a sub-led requests it */ 124 if (common_flags & LED_CORE_SUSPENDRESUME) 125 cdev->flags = LED_CORE_SUSPENDRESUME; 126 127 init_data.fwnode = dev_fwnode(dev); 128 ret = devm_led_classdev_multicolor_register_ext(dev, &priv->mc_cdev, &init_data); 129 if (ret) 130 return dev_err_probe(dev, ret, "failed to register multicolor LED for %s.\n", 131 cdev->name); 132 133 ret = leds_gmc_set(cdev, cdev->brightness); 134 if (ret) 135 return dev_err_probe(dev, ret, "failed to set LED value for %s.", cdev->name); 136 137 for (i = 0; i < count; i++) { 138 struct led_classdev *led_cdev = priv->monochromatics[i]; 139 140 /* 141 * Make the individual LED sysfs interface read-only to prevent the user 142 * to change the brightness of the individual LEDs of the group. 143 */ 144 mutex_lock(&led_cdev->led_access); 145 led_sysfs_disable(led_cdev); 146 mutex_unlock(&led_cdev->led_access); 147 148 /* Restore the write access to the LED sysfs when the group is destroyed */ 149 devm_add_action_or_reset(dev, restore_sysfs_write_access, led_cdev); 150 } 151 152 return 0; 153 } 154 155 static const struct of_device_id of_leds_group_multicolor_match[] = { 156 { .compatible = "leds-group-multicolor" }, 157 {} 158 }; 159 MODULE_DEVICE_TABLE(of, of_leds_group_multicolor_match); 160 161 static struct platform_driver leds_group_multicolor_driver = { 162 .probe = leds_gmc_probe, 163 .driver = { 164 .name = "leds_group_multicolor", 165 .of_match_table = of_leds_group_multicolor_match, 166 } 167 }; 168 module_platform_driver(leds_group_multicolor_driver); 169 170 MODULE_AUTHOR("Jean-Jacques Hiblot <jjhiblot@traphandler.com>"); 171 MODULE_DESCRIPTION("LEDs group multicolor driver"); 172 MODULE_LICENSE("GPL"); 173 MODULE_ALIAS("platform:leds-group-multicolor"); 174