From: Armin Wolf <W_Armin@gmx.de>
To: Derek John Clark <derekjohn.clark@gmail.com>
Cc: "Ilpo Järvinen" <ilpo.jarvinen@linux.intel.com>,
"Hans de Goede" <hansg@kernel.org>,
"Jean Delvare" <jdelvare@suse.com>,
"Guenter Roeck" <linux@roeck-us.net>,
"Alok Tiwari" <alok.a.tiwari@oracle.com>,
"David Box" <david.e.box@linux.intel.com>,
platform-driver-x86@vger.kernel.org,
linux-kernel@vger.kernel.org, linux-hwmon@vger.kernel.org,
linux-doc@vger.kernel.org, "Lee Jones" <lee@kernel.org>,
pavel@kernel.org, linux-leds@vger.kernel.org
Subject: Re: [PATCH v3 3/4] platform/x86: (ayn-ec) Add RGB Interface
Date: Tue, 29 Jul 2025 06:18:24 +0200 [thread overview]
Message-ID: <86888be2-3920-4232-9335-d411625bd8fd@gmx.de> (raw)
In-Reply-To: <CAFqHKT=YRoSsThEbqXLPHR_1M3=zRHw9f758JKm++7TfN8ZWKA@mail.gmail.com>
Am 29.07.25 um 05:25 schrieb Derek John Clark:
> On Sat, Jul 26, 2025 at 4:59 PM Armin Wolf <W_Armin@gmx.de> wrote:
>> 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@gmail.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.
>>
> The main reason I added them to the LED interface is to make writing
> udev rules more intuitive. Quite a few folks using the DKMS version of
> this driver just want to set a specific color on boot (usually off).
> IMO it makes logical sense that all the settings related to the LEDs
> would be on the LED device. I'll wait for the response from your CC
> before sending a v4.
>
>>> +
>>> +/**
>>> + * 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.
>>
> I'll test if this interferes with breathe mode. I wrote this driver a
> couple years ago as a DKMS module so I don't remember immediately if I
> had to add this mitigation to prevent switching to monocolor if the
> multi_index or brightness was written to. If that does turn out to be
> the case, should I cache the latest write until monocolor is set?
In such a case i think led_sysfs_disable()/led_sysfs_enable() should be called
to signal userspace applications that the LED cannot currently be accessed.
>>> +
>>> + 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?
> Hmm, maybe not.
>
>>> +
>>> +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.
>>
> Good idea, thanks.
>
>>> +
>>> +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.
>>
> Off is done by setting all color registers to 0. Simple enough to add.
> I'm thinking something like:
>
> if (red || green || blue)
> led_cdev.brightness = 255;
> else
> led_cdev.brightness = 0;
Looks good.
>>> + .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.
>>
> The EC takes over during suspend and switches back to breathe mode.
> Resume exists to return to user settings.
I see, in this case the LED core should indeed not perform any suspend/resume handling by itself.
Thanks,
Armin Wolf
>>> +
>>> +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.
>>
> Okay, I wasn't aware of the newer callbacks. I'll look them up.
>
> Thanks,
> Derek
>
>> Thanks,
>> Armin Wolf
>>
>>> };
>>>
>>> static struct platform_device *ayn_ec_device;
next prev parent reply other threads:[~2025-07-29 4:18 UTC|newest]
Thread overview: 21+ messages / expand[flat|nested] mbox.gz Atom feed top
2025-07-26 20:40 [PATCH v3 0/4] Add AYN EC Platform Driver Derek J. Clark
2025-07-26 20:40 ` [PATCH v3 1/4] platform/x86: (ayn-ec) Add PWM Fan HWMON Interface Derek J. Clark
2025-07-26 23:32 ` Armin Wolf
2025-07-29 2:58 ` Derek John Clark
2025-07-29 4:09 ` Armin Wolf
2025-08-10 22:27 ` Derek John Clark
2025-08-11 8:38 ` Armin Wolf
2025-07-28 16:31 ` kernel test robot
2025-07-29 3:39 ` Randy Dunlap
2025-07-28 21:34 ` kernel test robot
2025-07-26 20:40 ` [PATCH v3 2/4] platform/x86: (ayn-ec) Add Temperature Sensors Derek J. Clark
2025-07-26 23:37 ` Armin Wolf
2025-07-29 3:02 ` Derek John Clark
2025-07-26 20:40 ` [PATCH v3 3/4] platform/x86: (ayn-ec) Add RGB Interface Derek J. Clark
2025-07-26 23:58 ` Armin Wolf
2025-07-29 3:25 ` Derek John Clark
2025-07-29 4:18 ` Armin Wolf [this message]
2025-07-28 23:36 ` kernel test robot
2025-07-26 20:40 ` [PATCH v3 4/4] platform/x86: (ayn-ec) Add AYN EC Platform Documentation Derek J. Clark
2025-07-27 0:03 ` Armin Wolf
2025-07-29 3:26 ` Derek John Clark
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=86888be2-3920-4232-9335-d411625bd8fd@gmx.de \
--to=w_armin@gmx.de \
--cc=alok.a.tiwari@oracle.com \
--cc=david.e.box@linux.intel.com \
--cc=derekjohn.clark@gmail.com \
--cc=hansg@kernel.org \
--cc=ilpo.jarvinen@linux.intel.com \
--cc=jdelvare@suse.com \
--cc=lee@kernel.org \
--cc=linux-doc@vger.kernel.org \
--cc=linux-hwmon@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-leds@vger.kernel.org \
--cc=linux@roeck-us.net \
--cc=pavel@kernel.org \
--cc=platform-driver-x86@vger.kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).