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; 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 priv->monochromatics[count] = led_cdev; 95 96 max_brightness = max(max_brightness, led_cdev->max_brightness); 97 98 count++; 99 } 100 101 subled = devm_kcalloc(dev, count, sizeof(*subled), GFP_KERNEL); 102 if (!subled) 103 return -ENOMEM; 104 priv->mc_cdev.subled_info = subled; 105 106 for (i = 0; i < count; i++) { 107 struct led_classdev *led_cdev = priv->monochromatics[i]; 108 109 subled[i].color_index = led_cdev->color; 110 111 /* Configure the LED intensity to its maximum */ 112 subled[i].intensity = max_brightness; 113 } 114 115 /* Initialise the multicolor's LED class device */ 116 cdev = &priv->mc_cdev.led_cdev; 117 cdev->flags = LED_CORE_SUSPENDRESUME; 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 init_data.fwnode = dev_fwnode(dev); 124 ret = devm_led_classdev_multicolor_register_ext(dev, &priv->mc_cdev, &init_data); 125 if (ret) 126 return dev_err_probe(dev, ret, "failed to register multicolor LED for %s.\n", 127 cdev->name); 128 129 ret = leds_gmc_set(cdev, cdev->brightness); 130 if (ret) 131 return dev_err_probe(dev, ret, "failed to set LED value for %s.", cdev->name); 132 133 for (i = 0; i < count; i++) { 134 struct led_classdev *led_cdev = priv->monochromatics[i]; 135 136 /* 137 * Make the individual LED sysfs interface read-only to prevent the user 138 * to change the brightness of the individual LEDs of the group. 139 */ 140 mutex_lock(&led_cdev->led_access); 141 led_sysfs_disable(led_cdev); 142 mutex_unlock(&led_cdev->led_access); 143 144 /* Restore the write access to the LED sysfs when the group is destroyed */ 145 devm_add_action_or_reset(dev, restore_sysfs_write_access, led_cdev); 146 } 147 148 return 0; 149 } 150 151 static const struct of_device_id of_leds_group_multicolor_match[] = { 152 { .compatible = "leds-group-multicolor" }, 153 {} 154 }; 155 MODULE_DEVICE_TABLE(of, of_leds_group_multicolor_match); 156 157 static struct platform_driver leds_group_multicolor_driver = { 158 .probe = leds_gmc_probe, 159 .driver = { 160 .name = "leds_group_multicolor", 161 .of_match_table = of_leds_group_multicolor_match, 162 } 163 }; 164 module_platform_driver(leds_group_multicolor_driver); 165 166 MODULE_AUTHOR("Jean-Jacques Hiblot <jjhiblot@traphandler.com>"); 167 MODULE_DESCRIPTION("LEDs group multicolor driver"); 168 MODULE_LICENSE("GPL"); 169 MODULE_ALIAS("platform:leds-group-multicolor"); 170