lists.openwall.net   lists  /  announce  owl-users  owl-dev  john-users  john-dev  passwdqc-users  yescrypt  popa3d-users  /  oss-security  kernel-hardening  musl  sabotage  tlsify  passwords  /  crypt-dev  xvendor  /  Bugtraq  Full-Disclosure  linux-kernel  linux-netdev  linux-ext4  linux-hardening  linux-cve-announce  PHC 
Open Source and information security mailing list archives
 
Hash Suite: Windows password security audit tool. GUI, reports in PDF.
[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-ID: <eac46383-c54c-419e-b63e-c2fd003f2b6c@gmx.de>
Date: Sun, 27 Jul 2025 01:58:59 +0200
From: Armin Wolf <W_Armin@....de>
To: "Derek J. Clark" <derekjohn.clark@...il.com>,
 Ilpo Järvinen <ilpo.jarvinen@...ux.intel.com>,
 Hans de Goede <hansg@...nel.org>
Cc: Jean Delvare <jdelvare@...e.com>, Guenter Roeck <linux@...ck-us.net>,
 Alok Tiwari <alok.a.tiwari@...cle.com>,
 David Box <david.e.box@...ux.intel.com>,
 platform-driver-x86@...r.kernel.org, linux-kernel@...r.kernel.org,
 linux-hwmon@...r.kernel.org, linux-doc@...r.kernel.org,
 Lee Jones <lee@...nel.org>, pavel@...nel.org, linux-leds@...r.kernel.org
Subject: Re: [PATCH v3 3/4] platform/x86: (ayn-ec) Add RGB Interface

Am 26.07.25 um 22:40 schrieb Derek J. Clark:

> Adds an EC controlled LED Multicolor Class Device for controlling the
> RGB rings around the joysticks.
>
> The EC provides a single register for each of the colors red, green, and
> blue, as well as a mode switching register. The EC accepts values
> [0-255] for all colors. There are two available effects: breathe, which is
> the default when the device is started, and monocolor. When resuming from
> sleep the user selected effect will be overwritten by the EC, so the
> driver retains the last setting and resets on resume. When setting a
> color, each color register is set before a final "write" code is sent to
> the device. The EC may briefly reflect the "write" code when writing, but
> quickly changes to the "monocolor" value once complete. The driver
> interprets both of these values as "monocolor" in _show to simplify the
> sysfs exposed to the user.
>
> Two custom attributes are added to the standard LED parent device:
> effect, a RW file descriptor used to set the effect, and effect_index,
> which enumerates the available valid options.
>
> Signed-off-by: Derek J. Clark <derekjohn.clark@...il.com>
> ---
>   drivers/platform/x86/Kconfig  |   3 +
>   drivers/platform/x86/ayn-ec.c | 285 ++++++++++++++++++++++++++++++++++
>   2 files changed, 288 insertions(+)
>
> diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig
> index 4819bfcffb6b..85dfb88cca6f 100644
> --- a/drivers/platform/x86/Kconfig
> +++ b/drivers/platform/x86/Kconfig
> @@ -308,6 +308,9 @@ config AYN_EC
>   	tristate "AYN x86 devices EC platform control"
>   	depends on ACPI
>   	depends on HWMON
> +	depends on NEW_LEDS
> +	select LEDS_CLASS
> +	select LEDS_CLASS_MULTICOLOR
>   	help
>   	  This is a driver for AYN and Tectoy x86 handheld devices. It provides
>   	  temperature monitoring, manual fan speed control, fan curve control,
> diff --git a/drivers/platform/x86/ayn-ec.c b/drivers/platform/x86/ayn-ec.c
> index 466cc33adcb0..25f748d7db18 100644
> --- a/drivers/platform/x86/ayn-ec.c
> +++ b/drivers/platform/x86/ayn-ec.c
> @@ -28,6 +28,8 @@
>   #include <linux/hwmon.h>
>   #include <linux/init.h>
>   #include <linux/kernel.h>
> +#include <linux/led-class-multicolor.h>
> +#include <linux/leds.h>
>   #include <linux/module.h>
>   #include <linux/platform_device.h>
>   #include <linux/sysfs.h>
> @@ -68,6 +70,16 @@
>   #define AYN_SENSOR_PROC_TEMP_REG	0x09 /* CPU Core */
>   #define AYN_SENSOR_VCORE_TEMP_REG	0x08 /* vCore */
>   
> +/* EC Controlled RGB registers */
> +#define AYN_LED_MC_RED_REG	0xB0 /* Range 0x00-0xFF */
> +#define AYN_LED_MC_GREEN_REG	0xB1 /* Range 0x00-0xFF */
> +#define AYN_LED_MC_BLUE_REG	0xB2 /* Range 0x00-0xFF */
> +#define AYN_RGB_EFFECT_REG	0xB3
> +
> +/* RGB effect modes */
> +#define AYN_RGB_EFFECT_BREATHE		0x00
> +#define AYN_RGB_EFFECT_MONOCOLOR	0x55
> +#define AYN_RGB_EFFECT_WRITE		0xAA
>   
>   /* Handle ACPI lock mechanism */
>   #define ACPI_LOCK_DELAY_MS 500
> @@ -86,7 +98,9 @@ int ayn_pwm_curve_registers[10] = {
>   };
>   
>   struct ayn_device {
> +	struct led_classdev *led_cdev;
>   	u32 ayn_lock; /* ACPI EC Lock */
> +	u8 rgb_effect;
>   } drvdata;
>   
>   struct thermal_sensor {
> @@ -103,6 +117,33 @@ static struct thermal_sensor thermal_sensors[] = {
>   	{}
>   };
>   
> +#define DEVICE_ATTR_RW_NAMED(_name, _attrname)               \
> +	struct device_attribute dev_attr_##_name = {         \
> +		.attr = { .name = _attrname, .mode = 0644 }, \
> +		.show = _name##_show,                        \
> +		.store = _name##_store,                      \
> +	}
> +
> +#define DEVICE_ATTR_RO_NAMED(_name, _attrname)               \
> +	struct device_attribute dev_attr_##_name = {         \
> +		.attr = { .name = _attrname, .mode = 0444 }, \
> +		.show = _name##_show,                        \
> +	}

Please use DEVICE_ATTR_RW()/DEVICE_ATTR_RO() directly.

> +
> +/* Handle ACPI lock mechanism */
> +#define ACPI_LOCK_DELAY_MS 500

You already defined ACPI_LOCK_DELAY_MS earlier, please remove.

> +
> +/* RGB effect values */
> +enum RGB_EFFECT_OPTION {
> +	BREATHE,
> +	MONOCOLOR,
> +};
> +
> +static const char *const RGB_EFFECT_TEXT[] = {
> +	[BREATHE] = "breathe",
> +	[MONOCOLOR] = "monocolor",
> +};

No capslock for variables please.

> +
>   static bool lock_global_acpi_lock(void)
>   {
>   	return ACPI_SUCCESS(acpi_acquire_global_lock(ACPI_LOCK_DELAY_MS,
> @@ -528,10 +569,253 @@ static struct attribute *ayn_sensors_attrs[] = {
>   
>   ATTRIBUTE_GROUPS(ayn_sensors);
>   
> +/**
> + * rgb_effect_write() - Set the RGB effect stored in drvdata.rgb_effect.
> + */
> +static int rgb_effect_write(void)
> +{
> +	return write_to_ec(AYN_RGB_EFFECT_REG, drvdata.rgb_effect);
> +};
> +
> +/**
> + * rgb_effect_read() - Read the RGB effect and store it in drvdata.rgb_effect.
> + */
> +static int rgb_effect_read(void)
> +{
> +	int ret;
> +	long effect;
> +
> +	ret = read_from_ec(AYN_RGB_EFFECT_REG, 1, &effect);
> +	if (ret)
> +		return ret;
> +
> +	switch (effect) {
> +	case AYN_RGB_EFFECT_WRITE:
> +	case AYN_RGB_EFFECT_MONOCOLOR:
> +		drvdata.rgb_effect = AYN_RGB_EFFECT_WRITE;
> +		break;
> +	default:
> +		drvdata.rgb_effect = AYN_RGB_EFFECT_BREATHE;

You will need some locking around rgb_effect.

> +	}
> +
> +	return 0;
> +}
> +
> +/**
> + * rgb_effect_store() - Store the given RGB effect and set it.
> + *
> + * @dev: parent device of the given attribute.
> + * @attr: The attribute to write to.
> + * @buf: Input value string from sysfs write.
> + * @count: The number of bytes written.
> + *
> + * Return: The number of bytes written, or an error.
> + */
> +static ssize_t rgb_effect_store(struct device *dev,
> +				struct device_attribute *attr, const char *buf,
> +				size_t count)
> +{
> +	int ret;
> +
> +	ret = sysfs_match_string(RGB_EFFECT_TEXT, buf);
> +	if (ret < 0)
> +		return ret;
> +
> +	if (ret)
> +		drvdata.rgb_effect = AYN_RGB_EFFECT_WRITE;
> +	else
> +		drvdata.rgb_effect = AYN_RGB_EFFECT_BREATHE;
> +
> +	ret = rgb_effect_write();
> +	if (ret)
> +		return ret;
> +
> +	return count;
> +};
> +
> +/**
> + * rgb_effect_show() - Read the current RGB effect.
> + *
> + * @dev: parent device of the given attribute.
> + * @attr: The attribute to read.
> + * @buf: Buffer to read to.
> + *
> + * Return: The number of bytes read, or an error.
> + */
> +static ssize_t rgb_effect_show(struct device *dev,
> +			       struct device_attribute *attr, char *buf)
> +{
> +	int ret, i;
> +
> +	ret = rgb_effect_read();
> +	if (ret)
> +		return ret;
> +
> +	switch (drvdata.rgb_effect) {
> +	case AYN_RGB_EFFECT_WRITE:
> +	case AYN_RGB_EFFECT_MONOCOLOR:
> +		i = MONOCOLOR;
> +		break;
> +	default:
> +		i = BREATHE;
> +		break;
> +	}
> +
> +	return sysfs_emit(buf, "%s\n", RGB_EFFECT_TEXT[i]);
> +};
> +
> +static DEVICE_ATTR_RW_NAMED(rgb_effect, "effect");
> +
> +/**
> + * rgb_effect_show() - Display the RGB effects available.
> + *
> + * @dev: parent device of the given attribute.
> + * @attr: The attribute to read.
> + * @buf: Buffer to read to.
> + *
> + * Return: The number of bytes read, or an error.
> + */
> +static ssize_t rgb_effect_index_show(struct device *dev,
> +				     struct device_attribute *attr, char *buf)
> +{
> +	size_t count = 0;
> +	unsigned int i;
> +
> +	for (i = 0; i < ARRAY_SIZE(RGB_EFFECT_TEXT); i++)
> +		count += sysfs_emit_at(buf, count, "%s ", RGB_EFFECT_TEXT[i]);
> +
> +	buf[count - 1] = '\n';
> +
> +	return count;
> +}
> +
> +static DEVICE_ATTR_RO_NAMED(rgb_effect_index, "effect_index");

We might need to coordinate this with the LED subsystem maintainer. I CCed him so that he can
voice his opinion about those sysfs attributes. Personally i would move those attributes below
the platform device.

> +
> +/**
> + * ayn_led_mc_brightness_set() - Write the brightness for the RGB LED.
> + *
> + * @led_cdev: Parent LED device for the led_classdev_mc.
> + * @brightness: Brightness value to write [0-255].
> + */
> +static void ayn_led_mc_brightness_set(struct led_classdev *led_cdev,
> +				      enum led_brightness brightness)
> +{
> +	struct led_classdev_mc *led_cdev_mc = lcdev_to_mccdev(led_cdev);
> +	struct mc_subled s_led;
> +	int i, ret, val;
> +
> +	switch (drvdata.rgb_effect) {
> +	case AYN_RGB_EFFECT_WRITE:
> +	case AYN_RGB_EFFECT_MONOCOLOR:
> +		break;
> +	case AYN_RGB_EFFECT_BREATHE:
> +		return;
> +	}

This might confuse uses when they switch back to monocolor mode. I suggest that
you write the RGB values regardless of the currently selected effect.

> +
> +	led_cdev->brightness = brightness;
> +	for (i = 0; i < led_cdev_mc->num_colors; i++) {
> +		s_led = led_cdev_mc->subled_info[i];
> +		val = brightness * s_led.intensity / led_cdev->max_brightness;

Please check if you can use led_mc_calc_color_components() instead.

> +		ret = write_to_ec(s_led.channel, val);
> +		if (ret) {
> +			dev_err(led_cdev->dev,
> +				"Error setting brightness:  %d\n", ret);
> +			return;
> +		}
> +	}
> +
> +	/* Must write mode again to change to set color */
> +	write_to_ec(AYN_RGB_EFFECT_REG, AYN_RGB_EFFECT_WRITE);
> +};
> +
> +/**
> + * ayn_led_mc_brightness_get() - Get the brightness for the RGB LED.
> + *
> + * @led_cdev: Parent LED device for the led_classdev_mc.
> + *
> + * Return: Current brightness.
> + */
> +static enum led_brightness ayn_led_mc_brightness_get(struct led_classdev *led_cdev)
> +{
> +	return led_cdev->brightness;
> +};

This looks strange, are you sure that you have to provide this callback?

> +
> +static struct attribute *ayn_led_mc_attrs[] = {
> +	&dev_attr_rgb_effect.attr,
> +	&dev_attr_rgb_effect_index.attr,
> +	NULL,
> +};
> +
> +static struct attribute_group ayn_led_mc_group = {
> +	.attrs = ayn_led_mc_attrs,
> +};
> +
> +struct mc_subled ayn_led_mc_subled_info[] = {
> +	{
> +		.color_index = LED_COLOR_ID_RED,
> +		.brightness = 0,
> +		.intensity = 0,
> +		.channel = AYN_LED_MC_RED_REG,
> +	},
> +	{
> +		.color_index = LED_COLOR_ID_GREEN,
> +		.brightness = 0,
> +		.intensity = 0,
> +		.channel = AYN_LED_MC_GREEN_REG,
> +	},
> +	{
> +		.color_index = LED_COLOR_ID_BLUE,
> +		.brightness = 0,
> +		.intensity = 0,
> +		.channel = AYN_LED_MC_BLUE_REG,
> +	},
> +};

Please initialize the intensity fields with the current RGB register values
during probe. Also please declare the array as static.

> +
> +struct led_classdev_mc ayn_led_mc = {
> +	.led_cdev = {
> +		.name = "ayn:rgb:joystick_rings",
> +		.brightness = 0,

Does the EC support some kind of "RGB off" state? If not then please initialize the brightness field
with 0 if the RGB value during probe is not 0x000000 and 255 otherwise. Also please declare the LED device
as static.

> +		.max_brightness = 255,
> +		.brightness_set = ayn_led_mc_brightness_set,
> +		.brightness_get = ayn_led_mc_brightness_get,
> +		.color = LED_COLOR_ID_RGB,
> +	},
> +	.num_colors = ARRAY_SIZE(ayn_led_mc_subled_info),
> +	.subled_info = ayn_led_mc_subled_info,
> +};

Should the LED be disabled during suspend? If yes then please set the LED_CORE_SUSPENDRESUME flag on the LED device.

> +
> +static int ayn_ec_resume(struct platform_device *pdev)
> +{
> +	struct led_classdev *led_cdev = drvdata.led_cdev;
> +	int ret;
> +
> +	ret = rgb_effect_write();
> +	if (ret)
> +		return ret;
> +
> +	ayn_led_mc_brightness_set(led_cdev, led_cdev->brightness);
> +
> +	return 0;
> +}

Using regmap would make this much easier.

> +
>   static int ayn_ec_probe(struct platform_device *pdev)
>   {
>   	struct device *dev = &pdev->dev;
>   	struct device *hwdev;
> +	int ret;
> +
> +	ret = devm_led_classdev_multicolor_register(dev, &ayn_led_mc);
> +	if (ret)
> +		return ret;
> +
> +	ret = devm_device_add_group(ayn_led_mc.led_cdev.dev, &ayn_led_mc_group);
> +	if (ret)
> +		return ret;
> +
> +	drvdata.led_cdev = &ayn_led_mc.led_cdev;
> +	ret = rgb_effect_read();
> +	if (ret)
> +		return ret;
>   
>   	hwdev = devm_hwmon_device_register_with_info(dev, "aynec", NULL,
>   						     &ayn_ec_chip_info,
> @@ -544,6 +828,7 @@ static struct platform_driver ayn_ec_driver = {
>   		.name = "ayn-ec",
>   	},
>   	.probe = ayn_ec_probe,
> +	.resume = ayn_ec_resume,

Please do not use the legacy PM callback, instead use DEFINE_SIMPLE_DEV_PM_OPS() and the driver.pm field.

Thanks,
Armin Wolf

>   };
>   
>   static struct platform_device *ayn_ec_device;

Powered by blists - more mailing lists

Powered by Openwall GNU/*/Linux Powered by OpenVZ