Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

leds: Add ChromeOS EC driver

The ChromeOS Embedded Controller exposes an LED control command.
Expose its functionality through the leds subsystem.

The LEDs are exposed as multicolor devices.
A hardware trigger, which is active by default, is provided to let the
EC itself take over control over the LED.

The driver is designed to be probed via the cros_ec mfd device.

Signed-off-by: Thomas Weißschuh <linux@weissschuh.net>
Link: https://lore.kernel.org/r/20240613-cros_ec-led-v3-4-500b50f41e0f@weissschuh.net
Signed-off-by: Lee Jones <lee@kernel.org>

authored by

Thomas Weißschuh and committed by
Lee Jones
8d6ce6f3 493179e6

+298
+5
MAINTAINERS
··· 5135 5135 F: Documentation/devicetree/bindings/sound/google,cros-ec-codec.yaml 5136 5136 F: sound/soc/codecs/cros_ec_codec.* 5137 5137 5138 + CHROMEOS EC LED DRIVER 5139 + M: Thomas Weißschuh <thomas@weissschuh.net> 5140 + S: Maintained 5141 + F: drivers/leds/leds-cros_ec.c 5142 + 5138 5143 CHROMEOS EC SUBDRIVERS 5139 5144 M: Benson Leung <bleung@chromium.org> 5140 5145 R: Guenter Roeck <groeck@chromium.org>
+15
drivers/leds/Kconfig
··· 179 179 To compile this driver as a module, choose M here: the module 180 180 will be called leds-cr0014114. 181 181 182 + config LEDS_CROS_EC 183 + tristate "LED Support for ChromeOS EC" 184 + depends on MFD_CROS_EC_DEV 185 + depends on LEDS_CLASS_MULTICOLOR 186 + select LEDS_TRIGGERS 187 + default MFD_CROS_EC_DEV 188 + help 189 + This option enables support for LEDs managed by ChromeOS ECs. 190 + All LEDs exposed by the EC are supported in multicolor mode. 191 + A hardware trigger to switch back to the automatic behaviour is 192 + provided. 193 + 194 + To compile this driver as a module, choose M here: the module 195 + will be called leds-cros_ec. 196 + 182 197 config LEDS_EL15203000 183 198 tristate "LED Support for Crane EL15203000" 184 199 depends on LEDS_CLASS
+1
drivers/leds/Makefile
··· 26 26 obj-$(CONFIG_LEDS_COBALT_QUBE) += leds-cobalt-qube.o 27 27 obj-$(CONFIG_LEDS_COBALT_RAQ) += leds-cobalt-raq.o 28 28 obj-$(CONFIG_LEDS_CPCAP) += leds-cpcap.o 29 + obj-$(CONFIG_LEDS_CROS_EC) += leds-cros_ec.o 29 30 obj-$(CONFIG_LEDS_DA903X) += leds-da903x.o 30 31 obj-$(CONFIG_LEDS_DA9052) += leds-da9052.o 31 32 obj-$(CONFIG_LEDS_GPIO) += leds-gpio.o
+277
drivers/leds/leds-cros_ec.c
··· 1 + // SPDX-License-Identifier: GPL-2.0-or-later 2 + /* 3 + * ChromeOS EC LED Driver 4 + * 5 + * Copyright (C) 2024 Thomas Weißschuh <linux@weissschuh.net> 6 + */ 7 + 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> 16 + 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", 25 + }; 26 + 27 + static_assert(ARRAY_SIZE(cros_ec_led_functions) == EC_LED_ID_COUNT); 28 + 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, 36 + }; 37 + 38 + static_assert(ARRAY_SIZE(cros_ec_led_to_linux_id) == EC_LED_COLOR_COUNT); 39 + 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, 47 + }; 48 + 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; 53 + }; 54 + 55 + static inline struct cros_ec_led_priv *cros_ec_led_cdev_to_priv(struct led_classdev *led_cdev) 56 + { 57 + return container_of(lcdev_to_mccdev(led_cdev), struct cros_ec_led_priv, led_mc_cdev); 58 + } 59 + 60 + union cros_ec_led_cmd_data { 61 + struct ec_params_led_control req; 62 + struct ec_response_led_control resp; 63 + } __packed; 64 + 65 + static int cros_ec_led_send_cmd(struct cros_ec_device *cros_ec, 66 + union cros_ec_led_cmd_data *arg) 67 + { 68 + int ret; 69 + struct { 70 + struct cros_ec_command msg; 71 + union cros_ec_led_cmd_data data; 72 + } __packed buf = { 73 + .msg = { 74 + .version = 1, 75 + .command = EC_CMD_LED_CONTROL, 76 + .insize = sizeof(arg->resp), 77 + .outsize = sizeof(arg->req), 78 + }, 79 + .data.req = arg->req 80 + }; 81 + 82 + ret = cros_ec_cmd_xfer_status(cros_ec, &buf.msg); 83 + if (ret < 0) 84 + return ret; 85 + 86 + arg->resp = buf.data.resp; 87 + 88 + return 0; 89 + } 90 + 91 + static int cros_ec_led_trigger_activate(struct led_classdev *led_cdev) 92 + { 93 + struct cros_ec_led_priv *priv = cros_ec_led_cdev_to_priv(led_cdev); 94 + union cros_ec_led_cmd_data arg = {}; 95 + 96 + arg.req.led_id = priv->led_id; 97 + arg.req.flags = EC_LED_FLAGS_AUTO; 98 + 99 + return cros_ec_led_send_cmd(priv->cros_ec, &arg); 100 + } 101 + 102 + static struct led_hw_trigger_type cros_ec_led_trigger_type; 103 + 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, 108 + }; 109 + 110 + static int cros_ec_led_brightness_set_blocking(struct led_classdev *led_cdev, 111 + enum led_brightness brightness) 112 + { 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; 117 + size_t i; 118 + 119 + led_mc_calc_color_components(&priv->led_mc_cdev, brightness); 120 + 121 + arg.req.led_id = priv->led_id; 122 + 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; 127 + } 128 + 129 + return cros_ec_led_send_cmd(priv->cros_ec, &arg); 130 + } 131 + 132 + static int cros_ec_led_count_subleds(struct device *dev, 133 + struct ec_response_led_control *resp, 134 + unsigned int *max_brightness) 135 + { 136 + unsigned int range, common_range = 0; 137 + int num_subleds = 0; 138 + size_t i; 139 + 140 + for (i = 0; i < EC_LED_COLOR_COUNT; i++) { 141 + range = resp->brightness_range[i]; 142 + 143 + if (!range) 144 + continue; 145 + 146 + num_subleds++; 147 + 148 + if (!common_range) 149 + common_range = range; 150 + 151 + if (common_range != range) { 152 + /* The multicolor LED API expects a uniform max_brightness */ 153 + dev_err(dev, "Inconsistent LED brightness values\n"); 154 + return -EINVAL; 155 + } 156 + } 157 + 158 + if (!num_subleds) 159 + return -EINVAL; 160 + 161 + *max_brightness = common_range; 162 + return num_subleds; 163 + } 164 + 165 + static const char *cros_ec_led_get_color_name(struct led_classdev_mc *led_mc_cdev) 166 + { 167 + int color; 168 + 169 + if (led_mc_cdev->num_colors == 1) 170 + color = led_mc_cdev->subled_info[0].color_index; 171 + else 172 + color = LED_COLOR_ID_MULTI; 173 + 174 + return led_get_color_name(color); 175 + } 176 + 177 + static int cros_ec_led_probe_one(struct device *dev, struct cros_ec_device *cros_ec, 178 + enum ec_led_id id) 179 + { 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; 185 + size_t subled; 186 + 187 + arg.req.led_id = id; 188 + arg.req.flags = EC_LED_FLAGS_QUERY; 189 + ret = cros_ec_led_send_cmd(cros_ec, &arg); 190 + if (ret == -EINVAL) 191 + return 0; /* Unknown LED, skip */ 192 + if (ret == -EOPNOTSUPP) 193 + return -ENODEV; 194 + if (ret < 0) 195 + return ret; 196 + 197 + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); 198 + if (!priv) 199 + return -ENOMEM; 200 + 201 + num_subleds = cros_ec_led_count_subleds(dev, &arg.resp, 202 + &priv->led_mc_cdev.led_cdev.max_brightness); 203 + if (num_subleds < 0) 204 + return num_subleds; 205 + 206 + priv->cros_ec = cros_ec; 207 + priv->led_id = id; 208 + 209 + subleds = devm_kcalloc(dev, num_subleds, sizeof(*subleds), GFP_KERNEL); 210 + if (!subleds) 211 + return -ENOMEM; 212 + 213 + subled = 0; 214 + for (i = 0; i < EC_LED_COLOR_COUNT; i++) { 215 + if (!arg.resp.brightness_range[i]) 216 + continue; 217 + 218 + subleds[subled].color_index = cros_ec_led_to_linux_id[i]; 219 + if (subled == 0) 220 + subleds[subled].intensity = 100; 221 + subled++; 222 + } 223 + 224 + priv->led_mc_cdev.subled_info = subleds; 225 + priv->led_mc_cdev.num_colors = num_subleds; 226 + 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; 232 + 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]); 236 + if (!led_cdev->name) 237 + return -ENOMEM; 238 + 239 + return devm_led_classdev_multicolor_register(dev, &priv->led_mc_cdev); 240 + } 241 + 242 + static int cros_ec_led_probe(struct platform_device *pdev) 243 + { 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; 247 + int i, ret = 0; 248 + 249 + ret = devm_led_trigger_register(dev, &cros_ec_led_trigger); 250 + if (ret) 251 + return ret; 252 + 253 + for (i = 0; i < EC_LED_ID_COUNT; i++) { 254 + ret = cros_ec_led_probe_one(dev, cros_ec, i); 255 + if (ret) 256 + break; 257 + } 258 + 259 + return ret; 260 + } 261 + 262 + static const struct platform_device_id cros_ec_led_id[] = { 263 + { "cros-ec-led", 0 }, 264 + {} 265 + }; 266 + 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, 271 + }; 272 + module_platform_driver(cros_ec_led_driver); 273 + 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");