Re: [PATCH v3 1/1] HP: wmi: added support for 4 zone keyboard rgb

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



> >  drivers/platform/x86/hp/hp-wmi.c | 248 +++++++++++++++++++++++++++++--
> >  1 file changed, 239 insertions(+), 9 deletions(-)
> > 
> > diff --git a/drivers/platform/x86/hp/hp-wmi.c b/drivers/platform/x86/hp/hp-wmi.c
> > index 5fa553023842..5eae47961f76 100644
> > --- a/drivers/platform/x86/hp/hp-wmi.c
> > +++ b/drivers/platform/x86/hp/hp-wmi.c
> > @@ -14,6 +14,8 @@
> >  #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
> >  
> >  #include <linux/kernel.h>
> > +#include <linux/led-class-multicolor.h>
> > +#include <linux/leds.h>
> 
> This means that you now also need to update Kconfig to depend on
> LEDS_CLASS_MULTICOLOR, so add the following line to the existing
> Kconfig entry for the HP WMI driver:
> 
> 	depends on LEDS_CLASS_MULTICOLOR
> 
> Adding this should fix the following errors reported by
> the kernel test robot:
> 
> ERROR: modpost: "devm_led_classdev_multicolor_unregister" [drivers/platform/x86/hp/hp-wmi.ko] undefined!
> ERROR: modpost: "devm_led_classdev_multicolor_register_ext" [drivers/platform/x86/hp/hp-wmi.ko] undefined!

Yes, i completely forgot that. Thank you.

> > +static const char * const fourzone_zone_names[4] = {
> > +	"hp:rgb:kbd_zoned_backlight-right",
> > +	"hp:rgb:kbd_zoned_backlight-middle",
> > +	"hp:rgb:kbd_zoned_backlight-left",
> > +	"hp:rgb:kbd_zoned_backlight-wasd"
> > +};
> > +
> > +struct hp_fourzone_leds {
> > +	struct led_classdev_mc leds[4];
> > +	struct mc_subled subleds[4];
> 
> The idea with the multi-color API and subleds is that
> a RGB LED really are 3 seperate LEDs (R + G + B) in one.
> This is alsohow they are actually phyiscally made.
> So for 4 zones you need 12 subleds.
> 
> I think it would be best to to have a single struct per zone:
> 
> struct hp_fourzone_led {
> 	struct led_classdev_mc mc_led;
> 	struct mc_subled subleds[3];
> 	u32 cache; /* Not sure if you still want this */
> };
> 
> And then declare an array of 4 of these:
> 
> static struct hp_fourzone_led hp_fourzone_leds[4];

That makes sense, i will do it like that.

> > +	u32 color_cache[4];
> > +};
> > +static struct hp_fourzone_leds fourzone_leds;
> > +
> > +static enum led_brightness get_fourzone_brightness(struct led_classdev *led_cdev)
> > +{
> > +	u8 buff[4];
> > +
> > +	hp_wmi_perform_query(HPWMI_LED_BRIGHTNESS_GET, HPWMI_FOURZONE,
> > +		&buff, sizeof(buff), sizeof(buff));
> > +
> > +	return buff[0] == FOURZONE_LIGHTING_ON ? LED_ON : LED_OFF;
> > +}
> > +
> > +static void fourzone_update_brightness(void)
> > +{
> > +	unsigned int br;
> > +
> > +	/* synchronize the brightness level on all zones */
> > +	br = get_fourzone_brightness(NULL);
> > +	for (size_t i = 0; i < 4; i++)
> > +		fourzone_leds.leds[i].led_cdev.brightness = br;
> > +}
> > +
> >  static void hp_wmi_notify(u32 value, void *context)
> >  {
> >  	struct acpi_buffer response = { ACPI_ALLOCATE_BUFFER, NULL };
> > @@ -932,6 +996,14 @@ static void hp_wmi_notify(u32 value, void *context)
> >  	case HPWMI_PROXIMITY_SENSOR:
> >  		break;
> >  	case HPWMI_BACKLIT_KB_BRIGHTNESS:
> > +		if (fourzone_lightning_support) {
> > +			input_report_key(hp_wmi_input_dev, KEY_KBDILLUMTOGGLE, true);
> > +			input_sync(hp_wmi_input_dev);
> > +			input_report_key(hp_wmi_input_dev, KEY_KBDILLUMTOGGLE, false);
> > +			input_sync(hp_wmi_input_dev);
> > +
> > +			fourzone_update_brightness();
> 
> Does you calling fourzone_update_brightness() here mean that the embedded
> controller (EC) if the laptop toggles the kbd backlight on/off itself when
> you press the Fn + key combo for this on the kbd ? In that case you

It switches between the on/off modes.
More about this below.

> should not be sending a KEY_KBDILLUMTOGGLE key press event. That event
> is telling userspace that it should toggle the brightness, which you
> should only do if this is not done by the EC itself.

Is there a way i could do this that i still get this nice indication on the
screen that the key was pressed?

For me it would make sense to tell the user that the brightness key was pressed.

> If the EC does indeed toggle the brightness on/off (or even cycles it
> between various brightness levels) then the right thing to do is to
> call led_classdev_notify_brightness_hw_changed() on mc_led.led_cdev for
> each of the 4 zones when receiving this event.

For this to work properly, it would depend on how we manage the brightness.
More about this below.

> > +		}
> >  		break;
> >  	case HPWMI_PEAKSHIFT_PERIOD:
> >  		break;
> > @@ -1505,6 +1577,154 @@ static int thermal_profile_setup(void)
> >  	return 0;
> >  }
> >  
> > +static int fourzone_set_colors(u32 color, size_t zone)
> > +{
> > +	u8 buff[128];
> > +	int ret;
> > +
> > +	ret = hp_wmi_perform_query(HPWMI_FOURZONE_COLOR_GET, HPWMI_FOURZONE,
> > +		&buff, sizeof(buff), sizeof(buff));
> > +	if (ret != 0)
> > +		return -EINVAL;
> 
> You are doing a read + modify + write of the kbd settings here.
> 
> This is fine, but to avoid racing against another r/m/w cycle
> done at the same time if userspace writes 2 zones at the same
> time you need to take a mutex here.

I agree with you and Ilpo, this should have a mutex.

> > +
> > +	buff[25 + zone * 3]     = FIELD_GET(FOURZONE_COLOR_R, color);
> > +	buff[25 + zone * 3 + 1] = FIELD_GET(FOURZONE_COLOR_G, color);
> > +	buff[25 + zone * 3 + 2] = FIELD_GET(FOURZONE_COLOR_B, color);
> 
> As mentioned above this is wrong. You should have a separate mc_subled
> struct for each color for each zone (so 3 mc_subled-s per zone,
> one for each of R, G and B; and the you take subled.brightness field
> from the 3 subleds for the 3 different values.
> 
> > +
> > +	return hp_wmi_perform_query(HPWMI_FOURZONE_COLOR_SET, HPWMI_FOURZONE,
> > +		&buff, sizeof(buff), sizeof(buff));
> > +}
> > +
> > +static int fourzone_get_colors(u32 *colors)
> > +{
> > +	u8 buff[128];
> > +	int ret;
> > +
> > +	ret = hp_wmi_perform_query(HPWMI_FOURZONE_COLOR_GET, HPWMI_FOURZONE,
> > +		&buff, sizeof(buff), sizeof(buff));
> > +	if (ret != 0)
> > +		return -EINVAL;
> > +
> > +	for (int i = 0; i < 4; i++) {
> > +		colors[i] = FIELD_PREP(FOURZONE_COLOR_R, buff[25 + i * 3])
> > +			  | FIELD_PREP(FOURZONE_COLOR_G, buff[25 + i * 3 + 1])
> > +			  | FIELD_PREP(FOURZONE_COLOR_B, buff[25 + i * 3 + 2]);
> > +	}
> 
> same here.
> 
> > +
> > +	return 0;
> > +}
> > +
> > +static void set_fourzone_brightness(struct led_classdev *led_cdev, enum led_brightness brightness)
> > +{
> > +	size_t zone;
> > +
> > +	for (size_t i = 0; i < 4; i++)
> > +		if (strcmp(led_cdev->name, fourzone_zone_names[i]) == 0)
> > +			zone = i;
> > +
> 
> So the way how the multicolor LED class devices work is that they have 2
> brightness controls:
> 
> /sys/class/leds/hp:rgb:kbd_zoned_backlight-right/brightness 
> /sys/class/leds/hp:rgb:kbd_zoned_backlight-right/multi_intensity
> 
> Where brightness is a single integer value for overall brightness
> control and multi_intensity is a per channel brightness control, also see
> Documentation/leds/leds-class-multicolor.rst
> 
> Now most hw does not have a main/overall brightness control only
> per channel controls (like this hp code) so there is a helper which
> you pass the overall brightness value and which then calculates the
> per channel brightnesses for you.

This keyboard uses the Fn + key combo more like a mode switcher where it puts
the kbd backligh in on/off mode.

What i was doing was taking the overall brightness and using it as a mode controller
where it would take only 2 value 1/0 for on/off respectively and propagate the values
to the other zones to avoid some edge case problems.

If i start using the overall brightness control to control the brightness of each
channel (like it should probably be) then i loose the ability to control the modes
and we would need to find a way to control them.

But i would definitely agree that having mode control and brightness control at the same
time would be better.

Please note that brightness/color control is completely independent from mode control in
this keyboard. I can set any brightness/color to it even if the backligh is off (it will not
turn the backligh on) and when turned on, the new color will be there.

Perhaps we can create a sysfs entry to control the mode and use the main/overall brightness
control the way it was intended?

Now that i think about it, what i made was kinda of a mess :/

> The LED core code caches the last multi_intensity values for you
> and there is a helper you can call from the (this) brightness_set()
> callback:
> 
> 	led_mc_calc_color_components(&fourzone_leds[i].mc_led, brightness);
> 
> which will then update the fourzone_leds[i].subleds[0 - 2].brightness
> values with the desired per channel brightness values and then you can
> use those 3 brightness values in fourzone_set_colors() to send to
> the keyboard.

Again, this would depend on how we make brightness and mode control.

> > +	if (fourzone_leds.leds[zone].subled_info->intensity == fourzone_leds.color_cache[zone]) {
> > +		u8 buff[4] = {
> > +			brightness == LED_ON ? FOURZONE_LIGHTING_ON : FOURZONE_LIGHTING_OFF,
> > +			0, 0, 0
> > +		};
> > +
> > +		hp_wmi_perform_query(HPWMI_LED_BRIGHTNESS_SET, HPWMI_FOURZONE, &buff,
> > +			sizeof(buff), 0);
> > +
> > +		fourzone_update_brightness();
> > +	} else {
> > +		fourzone_set_colors(fourzone_leds.leds[zone].subled_info->intensity, zone);
> > +		fourzone_leds.color_cache[zone] = fourzone_leds.leds[zone].subled_info->intensity;
> > +	}
> 
> And this weird cahce thing can be removed then too, just always send the newly calculated
> 3 brightness values to the kbd.

It is weird indeed but it was used to know if i should change the color or the keyboard mode.

So when the color didn't change between calls, i would take the overall brightness and
change the mode from it. Obviously, this means loosing brightness control but the way
i see it, mode control is more important.

> > @@ -1561,6 +1785,12 @@ static void __exit hp_wmi_bios_remove(struct platform_device *device)
> >  
> >  	if (platform_profile_support)
> >  		platform_profile_remove();
> > +
> > +	if (fourzone_lightning_support)
> > +		for (size_t i = 0; i < 4; i++) {
> > +			devm_led_classdev_multicolor_unregister(&device->dev,
> > +				&fourzone_leds.leds[i]);
> > +		}
> 
> The whole idea behind devm_register_foo() functions is that they get automatically
> removed when the driver is unbound from the device. So this code and
> the fourzone_lightning_support flag are not necessary.

That is good to know. I will remove it.




[Index of Archives]     [Linux Kernel Development]     [Linux USB Devel]     [Video for Linux]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]

  Powered by Linux