brintos

brintos / linux-shallow public Read only

0
0
Text · 7.2 KiB · 275522b Raw
278 lines · c
1// SPDX-License-Identifier: GPL-2.0-or-later2/*3 *  ChromeOS EC LED Driver4 *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 17static 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 27static_assert(ARRAY_SIZE(cros_ec_led_functions) == EC_LED_ID_COUNT);28 29static 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 38static_assert(ARRAY_SIZE(cros_ec_led_to_linux_id) == EC_LED_COLOR_COUNT);39 40static 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 49struct 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 55static 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 60union cros_ec_led_cmd_data {61	struct ec_params_led_control req;62	struct ec_response_led_control resp;63} __packed;64 65static 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->req80	};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 91static 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 102static struct led_hw_trigger_type cros_ec_led_trigger_type;103 104static 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 110static 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 132static 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 165static 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	else172		color = LED_COLOR_ID_MULTI;173 174	return led_get_color_name(color);175}176 177static 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 242static 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 262static const struct platform_device_id cros_ec_led_id[] = {263	{ "cros-ec-led", 0 },264	{}265};266 267static 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};272module_platform_driver(cros_ec_led_driver);273 274MODULE_DEVICE_TABLE(platform, cros_ec_led_id);275MODULE_DESCRIPTION("ChromeOS EC LED Driver");276MODULE_AUTHOR("Thomas Weißschuh <linux@weissschuh.net");277MODULE_LICENSE("GPL");278