mirror of
				https://github.com/Ysurac/openmptcprouter.git
				synced 2025-03-09 15:40:20 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			202 lines
		
	
	
	
		
			7 KiB
		
	
	
	
		
			Diff
		
	
	
	
	
	
			
		
		
	
	
			202 lines
		
	
	
	
		
			7 KiB
		
	
	
	
		
			Diff
		
	
	
	
	
	
| From e965e0f6de60874fc0a0caed9a9e0122999e0c7b Mon Sep 17 00:00:00 2001
 | |
| From: =?UTF-8?q?Marek=20Beh=C3=BAn?= <kabel@kernel.org>
 | |
| Date: Mon, 18 Sep 2023 18:11:03 +0200
 | |
| Subject: [PATCH 5/6] leds: turris-omnia: Support HW controlled mode via
 | |
|  private trigger
 | |
| MIME-Version: 1.0
 | |
| Content-Type: text/plain; charset=UTF-8
 | |
| Content-Transfer-Encoding: 8bit
 | |
| 
 | |
| Add support for enabling MCU controlled mode of the Turris Omnia LEDs
 | |
| via a LED private trigger called "omnia-mcu". Recall that private LED
 | |
| triggers will only be listed in the sysfs trigger file for LEDs that
 | |
| support them (currently there is no user of this mechanism).
 | |
| 
 | |
| When in MCU controlled mode, the user can still set LED color, but the
 | |
| blinking is done by MCU, which does different things for different LEDs:
 | |
| - WAN LED is blinked according to the LED[0] pin of the WAN PHY
 | |
| - LAN LEDs are blinked according to the LED[0] output of the
 | |
|   corresponding port of the LAN switch
 | |
| - PCIe LEDs are blinked according to the logical OR of the MiniPCIe port
 | |
|   LED pins
 | |
| 
 | |
| In the future I want to make the netdev trigger to transparently offload
 | |
| the blinking to the HW if user sets compatible settings for the netdev
 | |
| trigger (for LEDs associated with network devices).
 | |
| There was some work on this already, and hopefully we will be able to
 | |
| complete it sometime, but for now there are still multiple blockers for
 | |
| this, and even if there weren't, we still would not be able to configure
 | |
| HW controlled mode for the LEDs associated with MiniPCIe ports.
 | |
| 
 | |
| In the meantime let's support HW controlled mode via the private LED
 | |
| trigger mechanism. If, in the future, we manage to complete the netdev
 | |
| trigger offloading, we can still keep this private trigger for backwards
 | |
| compatibility, if needed.
 | |
| 
 | |
| We also set "omnia-mcu" to cdev->default_trigger, so that the MCU keeps
 | |
| control until the user first wants to take over it. If a different
 | |
| default trigger is specified in device-tree via the
 | |
| 'linux,default-trigger' property, LED class will overwrite
 | |
| cdev->default_trigger, and so the DT property will be respected.
 | |
| 
 | |
| Signed-off-by: Marek Behún <kabel@kernel.org>
 | |
| Link: https://lore.kernel.org/r/20230918161104.20860-4-kabel@kernel.org
 | |
| Signed-off-by: Lee Jones <lee@kernel.org>
 | |
| ---
 | |
|  drivers/leds/Kconfig             |  1 +
 | |
|  drivers/leds/leds-turris-omnia.c | 98 +++++++++++++++++++++++++++++---
 | |
|  2 files changed, 91 insertions(+), 8 deletions(-)
 | |
| 
 | |
| --- a/drivers/leds/Kconfig
 | |
| +++ b/drivers/leds/Kconfig
 | |
| @@ -187,6 +187,7 @@ config LEDS_TURRIS_OMNIA
 | |
|  	depends on I2C
 | |
|  	depends on MACH_ARMADA_38X || COMPILE_TEST
 | |
|  	depends on OF
 | |
| +	select LEDS_TRIGGERS
 | |
|  	help
 | |
|  	  This option enables basic support for the LEDs found on the front
 | |
|  	  side of CZ.NIC's Turris Omnia router. There are 12 RGB LEDs on the
 | |
| --- a/drivers/leds/leds-turris-omnia.c
 | |
| +++ b/drivers/leds/leds-turris-omnia.c
 | |
| @@ -31,7 +31,7 @@ struct omnia_led {
 | |
|  	struct led_classdev_mc mc_cdev;
 | |
|  	struct mc_subled subled_info[OMNIA_LED_NUM_CHANNELS];
 | |
|  	u8 cached_channels[OMNIA_LED_NUM_CHANNELS];
 | |
| -	bool on;
 | |
| +	bool on, hwtrig;
 | |
|  	int reg;
 | |
|  };
 | |
|  
 | |
| @@ -120,12 +120,14 @@ static int omnia_led_brightness_set_bloc
 | |
|  
 | |
|  	/*
 | |
|  	 * Only recalculate RGB brightnesses from intensities if brightness is
 | |
| -	 * non-zero. Otherwise we won't be using them and we can save ourselves
 | |
| -	 * some software divisions (Omnia's CPU does not implement the division
 | |
| -	 * instruction).
 | |
| +	 * non-zero (if it is zero and the LED is in HW blinking mode, we use
 | |
| +	 * max_brightness as brightness). Otherwise we won't be using them and
 | |
| +	 * we can save ourselves some software divisions (Omnia's CPU does not
 | |
| +	 * implement the division instruction).
 | |
|  	 */
 | |
| -	if (brightness) {
 | |
| -		led_mc_calc_color_components(mc_cdev, brightness);
 | |
| +	if (brightness || led->hwtrig) {
 | |
| +		led_mc_calc_color_components(mc_cdev, brightness ?:
 | |
| +						      cdev->max_brightness);
 | |
|  
 | |
|  		/*
 | |
|  		 * Send color command only if brightness is non-zero and the RGB
 | |
| @@ -135,8 +137,11 @@ static int omnia_led_brightness_set_bloc
 | |
|  			err = omnia_led_send_color_cmd(leds->client, led);
 | |
|  	}
 | |
|  
 | |
| -	/* Send on/off state change only if (bool)brightness changed */
 | |
| -	if (!err && !brightness != !led->on) {
 | |
| +	/*
 | |
| +	 * Send on/off state change only if (bool)brightness changed and the LED
 | |
| +	 * is not being blinked by HW.
 | |
| +	 */
 | |
| +	if (!err && !led->hwtrig && !brightness != !led->on) {
 | |
|  		u8 state = CMD_LED_STATE_LED(led->reg);
 | |
|  
 | |
|  		if (brightness)
 | |
| @@ -152,6 +157,71 @@ static int omnia_led_brightness_set_bloc
 | |
|  	return err;
 | |
|  }
 | |
|  
 | |
| +static struct led_hw_trigger_type omnia_hw_trigger_type;
 | |
| +
 | |
| +static int omnia_hwtrig_activate(struct led_classdev *cdev)
 | |
| +{
 | |
| +	struct led_classdev_mc *mc_cdev = lcdev_to_mccdev(cdev);
 | |
| +	struct omnia_leds *leds = dev_get_drvdata(cdev->dev->parent);
 | |
| +	struct omnia_led *led = to_omnia_led(mc_cdev);
 | |
| +	int err = 0;
 | |
| +
 | |
| +	mutex_lock(&leds->lock);
 | |
| +
 | |
| +	if (!led->on) {
 | |
| +		/*
 | |
| +		 * If the LED is off (brightness was set to 0), the last
 | |
| +		 * configured color was not necessarily sent to the MCU.
 | |
| +		 * Recompute with max_brightness and send if needed.
 | |
| +		 */
 | |
| +		led_mc_calc_color_components(mc_cdev, cdev->max_brightness);
 | |
| +
 | |
| +		if (omnia_led_channels_changed(led))
 | |
| +			err = omnia_led_send_color_cmd(leds->client, led);
 | |
| +	}
 | |
| +
 | |
| +	if (!err) {
 | |
| +		/* Put the LED into MCU controlled mode */
 | |
| +		err = omnia_cmd_write_u8(leds->client, CMD_LED_MODE,
 | |
| +					 CMD_LED_MODE_LED(led->reg));
 | |
| +		if (!err)
 | |
| +			led->hwtrig = true;
 | |
| +	}
 | |
| +
 | |
| +	mutex_unlock(&leds->lock);
 | |
| +
 | |
| +	return err;
 | |
| +}
 | |
| +
 | |
| +static void omnia_hwtrig_deactivate(struct led_classdev *cdev)
 | |
| +{
 | |
| +	struct omnia_leds *leds = dev_get_drvdata(cdev->dev->parent);
 | |
| +	struct omnia_led *led = to_omnia_led(lcdev_to_mccdev(cdev));
 | |
| +	int err;
 | |
| +
 | |
| +	mutex_lock(&leds->lock);
 | |
| +
 | |
| +	led->hwtrig = false;
 | |
| +
 | |
| +	/* Put the LED into software mode */
 | |
| +	err = omnia_cmd_write_u8(leds->client, CMD_LED_MODE,
 | |
| +				 CMD_LED_MODE_LED(led->reg) |
 | |
| +				 CMD_LED_MODE_USER);
 | |
| +
 | |
| +	mutex_unlock(&leds->lock);
 | |
| +
 | |
| +	if (err < 0)
 | |
| +		dev_err(cdev->dev, "Cannot put LED to software mode: %i\n",
 | |
| +			err);
 | |
| +}
 | |
| +
 | |
| +static struct led_trigger omnia_hw_trigger = {
 | |
| +	.name		= "omnia-mcu",
 | |
| +	.activate	= omnia_hwtrig_activate,
 | |
| +	.deactivate	= omnia_hwtrig_deactivate,
 | |
| +	.trigger_type	= &omnia_hw_trigger_type,
 | |
| +};
 | |
| +
 | |
|  static int omnia_led_register(struct i2c_client *client, struct omnia_led *led,
 | |
|  			      struct device_node *np)
 | |
|  {
 | |
| @@ -195,6 +265,12 @@ static int omnia_led_register(struct i2c
 | |
|  	cdev = &led->mc_cdev.led_cdev;
 | |
|  	cdev->max_brightness = 255;
 | |
|  	cdev->brightness_set_blocking = omnia_led_brightness_set_blocking;
 | |
| +	cdev->trigger_type = &omnia_hw_trigger_type;
 | |
| +	/*
 | |
| +	 * Use the omnia-mcu trigger as the default trigger. It may be rewritten
 | |
| +	 * by LED class from the linux,default-trigger property.
 | |
| +	 */
 | |
| +	cdev->default_trigger = omnia_hw_trigger.name;
 | |
|  
 | |
|  	/* put the LED into software mode */
 | |
|  	ret = omnia_cmd_write_u8(client, CMD_LED_MODE,
 | |
| @@ -308,6 +384,12 @@ static int omnia_leds_probe(struct i2c_c
 | |
|  
 | |
|  	mutex_init(&leds->lock);
 | |
|  
 | |
| +	ret = devm_led_trigger_register(dev, &omnia_hw_trigger);
 | |
| +	if (ret < 0) {
 | |
| +		dev_err(dev, "Cannot register private LED trigger: %d\n", ret);
 | |
| +		return ret;
 | |
| +	}
 | |
| +
 | |
|  	led = &leds->leds[0];
 | |
|  	for_each_available_child_of_node(np, child) {
 | |
|  		ret = omnia_led_register(client, led, child);
 |