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/module.h> 12 #include <linux/platform_device.h> 13 #include <linux/platform_data/cros_ec_commands.h> 14 #include <linux/platform_data/cros_ec_proto.h> 15 16 static const char * const cros_ec_led_functions[] = { 17 [EC_LED_ID_BATTERY_LED] = LED_FUNCTION_CHARGING, 18 [EC_LED_ID_POWER_LED] = LED_FUNCTION_POWER, 19 [EC_LED_ID_ADAPTER_LED] = "adapter", 20 [EC_LED_ID_LEFT_LED] = "left", 21 [EC_LED_ID_RIGHT_LED] = "right", 22 [EC_LED_ID_RECOVERY_HW_REINIT_LED] = "recovery-hw-reinit", 23 [EC_LED_ID_SYSRQ_DEBUG_LED] = "sysrq-debug", 24 }; 25 26 static_assert(ARRAY_SIZE(cros_ec_led_functions) == EC_LED_ID_COUNT); 27 28 static const int cros_ec_led_to_linux_id[] = { 29 [EC_LED_COLOR_RED] = LED_COLOR_ID_RED, 30 [EC_LED_COLOR_GREEN] = LED_COLOR_ID_GREEN, 31 [EC_LED_COLOR_BLUE] = LED_COLOR_ID_BLUE, 32 [EC_LED_COLOR_YELLOW] = LED_COLOR_ID_YELLOW, 33 [EC_LED_COLOR_WHITE] = LED_COLOR_ID_WHITE, 34 [EC_LED_COLOR_AMBER] = LED_COLOR_ID_AMBER, 35 }; 36 37 static_assert(ARRAY_SIZE(cros_ec_led_to_linux_id) == EC_LED_COLOR_COUNT); 38 39 static const int cros_ec_linux_to_ec_id[] = { 40 [LED_COLOR_ID_RED] = EC_LED_COLOR_RED, 41 [LED_COLOR_ID_GREEN] = EC_LED_COLOR_GREEN, 42 [LED_COLOR_ID_BLUE] = EC_LED_COLOR_BLUE, 43 [LED_COLOR_ID_YELLOW] = EC_LED_COLOR_YELLOW, 44 [LED_COLOR_ID_WHITE] = EC_LED_COLOR_WHITE, 45 [LED_COLOR_ID_AMBER] = EC_LED_COLOR_AMBER, 46 }; 47 48 struct cros_ec_led_priv { 49 struct led_classdev_mc led_mc_cdev; 50 struct cros_ec_device *cros_ec; 51 enum ec_led_id led_id; 52 }; 53 54 static inline struct cros_ec_led_priv *cros_ec_led_cdev_to_priv(struct led_classdev *led_cdev) 55 { 56 return container_of(lcdev_to_mccdev(led_cdev), struct cros_ec_led_priv, led_mc_cdev); 57 } 58 59 union cros_ec_led_cmd_data { 60 struct ec_params_led_control req; 61 struct ec_response_led_control resp; 62 }; 63 64 static int cros_ec_led_send_cmd(struct cros_ec_device *cros_ec, 65 union cros_ec_led_cmd_data *arg) 66 { 67 int ret; 68 69 ret = cros_ec_cmd(cros_ec, 1, EC_CMD_LED_CONTROL, &arg->req, 70 sizeof(arg->req), &arg->resp, sizeof(arg->resp)); 71 if (ret < 0) 72 return ret; 73 74 return 0; 75 } 76 77 static int cros_ec_led_trigger_activate(struct led_classdev *led_cdev) 78 { 79 struct cros_ec_led_priv *priv = cros_ec_led_cdev_to_priv(led_cdev); 80 union cros_ec_led_cmd_data arg = {}; 81 82 arg.req.led_id = priv->led_id; 83 arg.req.flags = EC_LED_FLAGS_AUTO; 84 85 return cros_ec_led_send_cmd(priv->cros_ec, &arg); 86 } 87 88 static struct led_hw_trigger_type cros_ec_led_trigger_type; 89 90 static struct led_trigger cros_ec_led_trigger = { 91 .name = "chromeos-auto", 92 .trigger_type = &cros_ec_led_trigger_type, 93 .activate = cros_ec_led_trigger_activate, 94 }; 95 96 static int cros_ec_led_brightness_set_blocking(struct led_classdev *led_cdev, 97 enum led_brightness brightness) 98 { 99 struct cros_ec_led_priv *priv = cros_ec_led_cdev_to_priv(led_cdev); 100 union cros_ec_led_cmd_data arg = {}; 101 enum ec_led_colors led_color; 102 struct mc_subled *subled; 103 size_t i; 104 105 led_mc_calc_color_components(&priv->led_mc_cdev, brightness); 106 107 arg.req.led_id = priv->led_id; 108 109 for (i = 0; i < priv->led_mc_cdev.num_colors; i++) { 110 subled = &priv->led_mc_cdev.subled_info[i]; 111 led_color = cros_ec_linux_to_ec_id[subled->color_index]; 112 arg.req.brightness[led_color] = subled->brightness; 113 } 114 115 return cros_ec_led_send_cmd(priv->cros_ec, &arg); 116 } 117 118 static int cros_ec_led_count_subleds(struct device *dev, 119 struct ec_response_led_control *resp, 120 unsigned int *max_brightness) 121 { 122 unsigned int range, common_range = 0; 123 int num_subleds = 0; 124 size_t i; 125 126 for (i = 0; i < EC_LED_COLOR_COUNT; i++) { 127 range = resp->brightness_range[i]; 128 129 if (!range) 130 continue; 131 132 num_subleds++; 133 134 if (!common_range) 135 common_range = range; 136 137 if (common_range != range) { 138 /* The multicolor LED API expects a uniform max_brightness */ 139 dev_err(dev, "Inconsistent LED brightness values\n"); 140 return -EINVAL; 141 } 142 } 143 144 *max_brightness = common_range; 145 return num_subleds; 146 } 147 148 static const char *cros_ec_led_get_color_name(struct led_classdev_mc *led_mc_cdev) 149 { 150 int color; 151 152 if (led_mc_cdev->num_colors == 1) 153 color = led_mc_cdev->subled_info[0].color_index; 154 else 155 color = LED_COLOR_ID_MULTI; 156 157 return led_get_color_name(color); 158 } 159 160 static int cros_ec_led_probe_one(struct device *dev, struct cros_ec_device *cros_ec, 161 enum ec_led_id id) 162 { 163 union cros_ec_led_cmd_data arg = {}; 164 struct cros_ec_led_priv *priv; 165 struct led_classdev *led_cdev; 166 struct mc_subled *subleds; 167 int i, ret, num_subleds; 168 size_t subled; 169 170 arg.req.led_id = id; 171 arg.req.flags = EC_LED_FLAGS_QUERY; 172 ret = cros_ec_led_send_cmd(cros_ec, &arg); 173 if (ret == -EINVAL) 174 return 0; /* Unknown LED, skip */ 175 if (ret == -EOPNOTSUPP) 176 return -ENODEV; 177 if (ret < 0) 178 return ret; 179 180 priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); 181 if (!priv) 182 return -ENOMEM; 183 184 num_subleds = cros_ec_led_count_subleds(dev, &arg.resp, 185 &priv->led_mc_cdev.led_cdev.max_brightness); 186 if (num_subleds < 0) 187 return num_subleds; 188 if (num_subleds == 0) 189 return 0; /* LED without any colors, skip */ 190 191 priv->cros_ec = cros_ec; 192 priv->led_id = id; 193 194 subleds = devm_kcalloc(dev, num_subleds, sizeof(*subleds), GFP_KERNEL); 195 if (!subleds) 196 return -ENOMEM; 197 198 subled = 0; 199 for (i = 0; i < EC_LED_COLOR_COUNT; i++) { 200 if (!arg.resp.brightness_range[i]) 201 continue; 202 203 subleds[subled].color_index = cros_ec_led_to_linux_id[i]; 204 if (subled == 0) 205 subleds[subled].intensity = 100; 206 subled++; 207 } 208 209 priv->led_mc_cdev.subled_info = subleds; 210 priv->led_mc_cdev.num_colors = num_subleds; 211 212 led_cdev = &priv->led_mc_cdev.led_cdev; 213 led_cdev->brightness_set_blocking = cros_ec_led_brightness_set_blocking; 214 led_cdev->trigger_type = &cros_ec_led_trigger_type; 215 led_cdev->default_trigger = cros_ec_led_trigger.name; 216 led_cdev->hw_control_trigger = cros_ec_led_trigger.name; 217 218 led_cdev->name = devm_kasprintf(dev, GFP_KERNEL, "chromeos:%s:%s", 219 cros_ec_led_get_color_name(&priv->led_mc_cdev), 220 cros_ec_led_functions[id]); 221 if (!led_cdev->name) 222 return -ENOMEM; 223 224 return devm_led_classdev_multicolor_register(dev, &priv->led_mc_cdev); 225 } 226 227 static int cros_ec_led_probe(struct platform_device *pdev) 228 { 229 struct device *dev = &pdev->dev; 230 struct cros_ec_dev *ec_dev = dev_get_drvdata(dev->parent); 231 struct cros_ec_device *cros_ec = ec_dev->ec_dev; 232 int i, ret = 0; 233 234 ret = devm_led_trigger_register(dev, &cros_ec_led_trigger); 235 if (ret) 236 return ret; 237 238 for (i = 0; i < EC_LED_ID_COUNT; i++) { 239 ret = cros_ec_led_probe_one(dev, cros_ec, i); 240 if (ret) 241 break; 242 } 243 244 return ret; 245 } 246 247 static const struct platform_device_id cros_ec_led_id[] = { 248 { "cros-ec-led", 0 }, 249 {} 250 }; 251 MODULE_DEVICE_TABLE(platform, cros_ec_led_id); 252 253 static struct platform_driver cros_ec_led_driver = { 254 .driver.name = "cros-ec-led", 255 .probe = cros_ec_led_probe, 256 .id_table = cros_ec_led_id, 257 }; 258 module_platform_driver(cros_ec_led_driver); 259 260 MODULE_DESCRIPTION("ChromeOS EC LED Driver"); 261 MODULE_AUTHOR("Thomas Weißschuh <linux@weissschuh.net"); 262 MODULE_LICENSE("GPL"); 263