1 // SPDX-License-Identifier: GPL-2.0
3 * Multi-color LED built with monochromatic LED devices
5 * This driver groups several monochromatic LED devices in a single multicolor LED device.
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.
11 * Copyright 2023 Jean-Jacques Hiblot <jjhiblot@traphandler.com>
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>
23 struct leds_multicolor
{
24 struct led_classdev_mc mc_cdev
;
25 struct led_classdev
**monochromatics
;
28 static int leds_gmc_set(struct led_classdev
*cdev
, enum led_brightness brightness
)
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
;
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
;
42 * Scale the brightness according to relative intensity of the
43 * color AND the max brightness of the monochromatic LED.
45 mono_brightness
= DIV_ROUND_CLOSEST(brightness
* intensity
* mono_max_brightness
,
46 group_max_brightness
* group_max_brightness
);
48 led_set_brightness(mono
, mono_brightness
);
54 static void restore_sysfs_write_access(void *data
)
56 struct led_classdev
*led_cdev
= data
;
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
);
64 static int leds_gmc_probe(struct platform_device
*pdev
)
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;
74 priv
= devm_kzalloc(dev
, sizeof(*priv
), GFP_KERNEL
);
79 struct led_classdev
*led_cdev
;
81 led_cdev
= devm_of_led_get_optional(dev
, count
);
83 return dev_err_probe(dev
, PTR_ERR(led_cdev
), "Unable to get LED #%d",
88 priv
->monochromatics
= devm_krealloc_array(dev
, priv
->monochromatics
,
89 count
+ 1, sizeof(*priv
->monochromatics
),
91 if (!priv
->monochromatics
)
94 common_flags
|= led_cdev
->flags
;
95 priv
->monochromatics
[count
] = led_cdev
;
97 max_brightness
= max(max_brightness
, led_cdev
->max_brightness
);
102 subled
= devm_kcalloc(dev
, count
, sizeof(*subled
), GFP_KERNEL
);
105 priv
->mc_cdev
.subled_info
= subled
;
107 for (i
= 0; i
< count
; i
++) {
108 struct led_classdev
*led_cdev
= priv
->monochromatics
[i
];
110 subled
[i
].color_index
= led_cdev
->color
;
112 /* Configure the LED intensity to its maximum */
113 subled
[i
].intensity
= max_brightness
;
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
;
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
;
127 init_data
.fwnode
= dev_fwnode(dev
);
128 ret
= devm_led_classdev_multicolor_register_ext(dev
, &priv
->mc_cdev
, &init_data
);
130 return dev_err_probe(dev
, ret
, "failed to register multicolor LED for %s.\n",
133 ret
= leds_gmc_set(cdev
, cdev
->brightness
);
135 return dev_err_probe(dev
, ret
, "failed to set LED value for %s.", cdev
->name
);
137 for (i
= 0; i
< count
; i
++) {
138 struct led_classdev
*led_cdev
= priv
->monochromatics
[i
];
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.
144 mutex_lock(&led_cdev
->led_access
);
145 led_sysfs_disable(led_cdev
);
146 mutex_unlock(&led_cdev
->led_access
);
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
);
155 static const struct of_device_id of_leds_group_multicolor_match
[] = {
156 { .compatible
= "leds-group-multicolor" },
159 MODULE_DEVICE_TABLE(of
, of_leds_group_multicolor_match
);
161 static struct platform_driver leds_group_multicolor_driver
= {
162 .probe
= leds_gmc_probe
,
164 .name
= "leds_group_multicolor",
165 .of_match_table
= of_leds_group_multicolor_match
,
168 module_platform_driver(leds_group_multicolor_driver
);
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");