1 // SPDX-License-Identifier: GPL-2.0-or-later
3 * ChromeOS EC LED Driver
5 * Copyright (C) 2024 Thomas Weißschuh <linux@weissschuh.net>
8 #include <linux/device.h>
9 #include <linux/leds.h>
10 #include <linux/led-class-multicolor.h>
11 #include <linux/mod_devicetable.h>
12 #include <linux/module.h>
13 #include <linux/platform_device.h>
14 #include <linux/platform_data/cros_ec_commands.h>
15 #include <linux/platform_data/cros_ec_proto.h>
17 static const char * const cros_ec_led_functions
[] = {
18 [EC_LED_ID_BATTERY_LED
] = LED_FUNCTION_CHARGING
,
19 [EC_LED_ID_POWER_LED
] = LED_FUNCTION_POWER
,
20 [EC_LED_ID_ADAPTER_LED
] = "adapter",
21 [EC_LED_ID_LEFT_LED
] = "left",
22 [EC_LED_ID_RIGHT_LED
] = "right",
23 [EC_LED_ID_RECOVERY_HW_REINIT_LED
] = "recovery-hw-reinit",
24 [EC_LED_ID_SYSRQ_DEBUG_LED
] = "sysrq-debug",
27 static_assert(ARRAY_SIZE(cros_ec_led_functions
) == EC_LED_ID_COUNT
);
29 static const int cros_ec_led_to_linux_id
[] = {
30 [EC_LED_COLOR_RED
] = LED_COLOR_ID_RED
,
31 [EC_LED_COLOR_GREEN
] = LED_COLOR_ID_GREEN
,
32 [EC_LED_COLOR_BLUE
] = LED_COLOR_ID_BLUE
,
33 [EC_LED_COLOR_YELLOW
] = LED_COLOR_ID_YELLOW
,
34 [EC_LED_COLOR_WHITE
] = LED_COLOR_ID_WHITE
,
35 [EC_LED_COLOR_AMBER
] = LED_COLOR_ID_AMBER
,
38 static_assert(ARRAY_SIZE(cros_ec_led_to_linux_id
) == EC_LED_COLOR_COUNT
);
40 static const int cros_ec_linux_to_ec_id
[] = {
41 [LED_COLOR_ID_RED
] = EC_LED_COLOR_RED
,
42 [LED_COLOR_ID_GREEN
] = EC_LED_COLOR_GREEN
,
43 [LED_COLOR_ID_BLUE
] = EC_LED_COLOR_BLUE
,
44 [LED_COLOR_ID_YELLOW
] = EC_LED_COLOR_YELLOW
,
45 [LED_COLOR_ID_WHITE
] = EC_LED_COLOR_WHITE
,
46 [LED_COLOR_ID_AMBER
] = EC_LED_COLOR_AMBER
,
49 struct cros_ec_led_priv
{
50 struct led_classdev_mc led_mc_cdev
;
51 struct cros_ec_device
*cros_ec
;
52 enum ec_led_id led_id
;
55 static inline struct cros_ec_led_priv
*cros_ec_led_cdev_to_priv(struct led_classdev
*led_cdev
)
57 return container_of(lcdev_to_mccdev(led_cdev
), struct cros_ec_led_priv
, led_mc_cdev
);
60 union cros_ec_led_cmd_data
{
61 struct ec_params_led_control req
;
62 struct ec_response_led_control resp
;
65 static int cros_ec_led_send_cmd(struct cros_ec_device
*cros_ec
,
66 union cros_ec_led_cmd_data
*arg
)
70 struct cros_ec_command msg
;
71 union cros_ec_led_cmd_data data
;
75 .command
= EC_CMD_LED_CONTROL
,
76 .insize
= sizeof(arg
->resp
),
77 .outsize
= sizeof(arg
->req
),
82 ret
= cros_ec_cmd_xfer_status(cros_ec
, &buf
.msg
);
86 arg
->resp
= buf
.data
.resp
;
91 static int cros_ec_led_trigger_activate(struct led_classdev
*led_cdev
)
93 struct cros_ec_led_priv
*priv
= cros_ec_led_cdev_to_priv(led_cdev
);
94 union cros_ec_led_cmd_data arg
= {};
96 arg
.req
.led_id
= priv
->led_id
;
97 arg
.req
.flags
= EC_LED_FLAGS_AUTO
;
99 return cros_ec_led_send_cmd(priv
->cros_ec
, &arg
);
102 static struct led_hw_trigger_type cros_ec_led_trigger_type
;
104 static struct led_trigger cros_ec_led_trigger
= {
105 .name
= "chromeos-auto",
106 .trigger_type
= &cros_ec_led_trigger_type
,
107 .activate
= cros_ec_led_trigger_activate
,
110 static int cros_ec_led_brightness_set_blocking(struct led_classdev
*led_cdev
,
111 enum led_brightness brightness
)
113 struct cros_ec_led_priv
*priv
= cros_ec_led_cdev_to_priv(led_cdev
);
114 union cros_ec_led_cmd_data arg
= {};
115 enum ec_led_colors led_color
;
116 struct mc_subled
*subled
;
119 led_mc_calc_color_components(&priv
->led_mc_cdev
, brightness
);
121 arg
.req
.led_id
= priv
->led_id
;
123 for (i
= 0; i
< priv
->led_mc_cdev
.num_colors
; i
++) {
124 subled
= &priv
->led_mc_cdev
.subled_info
[i
];
125 led_color
= cros_ec_linux_to_ec_id
[subled
->color_index
];
126 arg
.req
.brightness
[led_color
] = subled
->brightness
;
129 return cros_ec_led_send_cmd(priv
->cros_ec
, &arg
);
132 static int cros_ec_led_count_subleds(struct device
*dev
,
133 struct ec_response_led_control
*resp
,
134 unsigned int *max_brightness
)
136 unsigned int range
, common_range
= 0;
140 for (i
= 0; i
< EC_LED_COLOR_COUNT
; i
++) {
141 range
= resp
->brightness_range
[i
];
149 common_range
= range
;
151 if (common_range
!= range
) {
152 /* The multicolor LED API expects a uniform max_brightness */
153 dev_err(dev
, "Inconsistent LED brightness values\n");
161 *max_brightness
= common_range
;
165 static const char *cros_ec_led_get_color_name(struct led_classdev_mc
*led_mc_cdev
)
169 if (led_mc_cdev
->num_colors
== 1)
170 color
= led_mc_cdev
->subled_info
[0].color_index
;
172 color
= LED_COLOR_ID_MULTI
;
174 return led_get_color_name(color
);
177 static int cros_ec_led_probe_one(struct device
*dev
, struct cros_ec_device
*cros_ec
,
180 union cros_ec_led_cmd_data arg
= {};
181 struct cros_ec_led_priv
*priv
;
182 struct led_classdev
*led_cdev
;
183 struct mc_subled
*subleds
;
184 int i
, ret
, num_subleds
;
188 arg
.req
.flags
= EC_LED_FLAGS_QUERY
;
189 ret
= cros_ec_led_send_cmd(cros_ec
, &arg
);
191 return 0; /* Unknown LED, skip */
192 if (ret
== -EOPNOTSUPP
)
197 priv
= devm_kzalloc(dev
, sizeof(*priv
), GFP_KERNEL
);
201 num_subleds
= cros_ec_led_count_subleds(dev
, &arg
.resp
,
202 &priv
->led_mc_cdev
.led_cdev
.max_brightness
);
206 priv
->cros_ec
= cros_ec
;
209 subleds
= devm_kcalloc(dev
, num_subleds
, sizeof(*subleds
), GFP_KERNEL
);
214 for (i
= 0; i
< EC_LED_COLOR_COUNT
; i
++) {
215 if (!arg
.resp
.brightness_range
[i
])
218 subleds
[subled
].color_index
= cros_ec_led_to_linux_id
[i
];
220 subleds
[subled
].intensity
= 100;
224 priv
->led_mc_cdev
.subled_info
= subleds
;
225 priv
->led_mc_cdev
.num_colors
= num_subleds
;
227 led_cdev
= &priv
->led_mc_cdev
.led_cdev
;
228 led_cdev
->brightness_set_blocking
= cros_ec_led_brightness_set_blocking
;
229 led_cdev
->trigger_type
= &cros_ec_led_trigger_type
;
230 led_cdev
->default_trigger
= cros_ec_led_trigger
.name
;
231 led_cdev
->hw_control_trigger
= cros_ec_led_trigger
.name
;
233 led_cdev
->name
= devm_kasprintf(dev
, GFP_KERNEL
, "chromeos:%s:%s",
234 cros_ec_led_get_color_name(&priv
->led_mc_cdev
),
235 cros_ec_led_functions
[id
]);
239 return devm_led_classdev_multicolor_register(dev
, &priv
->led_mc_cdev
);
242 static int cros_ec_led_probe(struct platform_device
*pdev
)
244 struct device
*dev
= &pdev
->dev
;
245 struct cros_ec_dev
*ec_dev
= dev_get_drvdata(dev
->parent
);
246 struct cros_ec_device
*cros_ec
= ec_dev
->ec_dev
;
249 ret
= devm_led_trigger_register(dev
, &cros_ec_led_trigger
);
253 for (i
= 0; i
< EC_LED_ID_COUNT
; i
++) {
254 ret
= cros_ec_led_probe_one(dev
, cros_ec
, i
);
262 static const struct platform_device_id cros_ec_led_id
[] = {
263 { "cros-ec-led", 0 },
267 static struct platform_driver cros_ec_led_driver
= {
268 .driver
.name
= "cros-ec-led",
269 .probe
= cros_ec_led_probe
,
270 .id_table
= cros_ec_led_id
,
272 module_platform_driver(cros_ec_led_driver
);
274 MODULE_DEVICE_TABLE(platform
, cros_ec_led_id
);
275 MODULE_DESCRIPTION("ChromeOS EC LED Driver");
276 MODULE_AUTHOR("Thomas Weißschuh <linux@weissschuh.net");
277 MODULE_LICENSE("GPL");