diff options
Diffstat (limited to 'drivers/net/ethernet/intel/igc/igc_leds.c')
| -rw-r--r-- | drivers/net/ethernet/intel/igc/igc_leds.c | 280 | 
1 files changed, 280 insertions, 0 deletions
diff --git a/drivers/net/ethernet/intel/igc/igc_leds.c b/drivers/net/ethernet/intel/igc/igc_leds.c new file mode 100644 index 000000000000..bf240c5daf86 --- /dev/null +++ b/drivers/net/ethernet/intel/igc/igc_leds.c @@ -0,0 +1,280 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Copyright (C) 2024 Linutronix GmbH */ + +#include <linux/bits.h> +#include <linux/leds.h> +#include <linux/netdevice.h> +#include <linux/pm_runtime.h> +#include <uapi/linux/uleds.h> + +#include "igc.h" + +#define IGC_NUM_LEDS			3 + +#define IGC_LEDCTL_LED0_MODE_SHIFT	0 +#define IGC_LEDCTL_LED0_MODE_MASK	GENMASK(3, 0) +#define IGC_LEDCTL_LED0_BLINK		BIT(7) +#define IGC_LEDCTL_LED1_MODE_SHIFT	8 +#define IGC_LEDCTL_LED1_MODE_MASK	GENMASK(11, 8) +#define IGC_LEDCTL_LED1_BLINK		BIT(15) +#define IGC_LEDCTL_LED2_MODE_SHIFT	16 +#define IGC_LEDCTL_LED2_MODE_MASK	GENMASK(19, 16) +#define IGC_LEDCTL_LED2_BLINK		BIT(23) + +#define IGC_LEDCTL_MODE_ON		0x00 +#define IGC_LEDCTL_MODE_OFF		0x01 +#define IGC_LEDCTL_MODE_LINK_10		0x05 +#define IGC_LEDCTL_MODE_LINK_100	0x06 +#define IGC_LEDCTL_MODE_LINK_1000	0x07 +#define IGC_LEDCTL_MODE_LINK_2500	0x08 +#define IGC_LEDCTL_MODE_ACTIVITY	0x0b + +#define IGC_SUPPORTED_MODES						 \ +	(BIT(TRIGGER_NETDEV_LINK_2500) | BIT(TRIGGER_NETDEV_LINK_1000) | \ +	 BIT(TRIGGER_NETDEV_LINK_100) | BIT(TRIGGER_NETDEV_LINK_10) |	 \ +	 BIT(TRIGGER_NETDEV_RX) | BIT(TRIGGER_NETDEV_TX)) + +#define IGC_ACTIVITY_MODES					\ +	(BIT(TRIGGER_NETDEV_RX) | BIT(TRIGGER_NETDEV_TX)) + +struct igc_led_classdev { +	struct net_device *netdev; +	struct led_classdev led; +	int index; +}; + +#define lcdev_to_igc_ldev(lcdev)				\ +	container_of(lcdev, struct igc_led_classdev, led) + +static void igc_led_select(struct igc_adapter *adapter, int led, +			   u32 *mask, u32 *shift, u32 *blink) +{ +	switch (led) { +	case 0: +		*mask  = IGC_LEDCTL_LED0_MODE_MASK; +		*shift = IGC_LEDCTL_LED0_MODE_SHIFT; +		*blink = IGC_LEDCTL_LED0_BLINK; +		break; +	case 1: +		*mask  = IGC_LEDCTL_LED1_MODE_MASK; +		*shift = IGC_LEDCTL_LED1_MODE_SHIFT; +		*blink = IGC_LEDCTL_LED1_BLINK; +		break; +	case 2: +		*mask  = IGC_LEDCTL_LED2_MODE_MASK; +		*shift = IGC_LEDCTL_LED2_MODE_SHIFT; +		*blink = IGC_LEDCTL_LED2_BLINK; +		break; +	default: +		*mask = *shift = *blink = 0; +		netdev_err(adapter->netdev, "Unknown LED %d selected!\n", led); +	} +} + +static void igc_led_set(struct igc_adapter *adapter, int led, u32 mode, +			bool blink) +{ +	u32 shift, mask, blink_bit, ledctl; +	struct igc_hw *hw = &adapter->hw; + +	igc_led_select(adapter, led, &mask, &shift, &blink_bit); + +	pm_runtime_get_sync(&adapter->pdev->dev); +	mutex_lock(&adapter->led_mutex); + +	/* Set mode */ +	ledctl = rd32(IGC_LEDCTL); +	ledctl &= ~mask; +	ledctl |= mode << shift; + +	/* Configure blinking */ +	if (blink) +		ledctl |= blink_bit; +	else +		ledctl &= ~blink_bit; +	wr32(IGC_LEDCTL, ledctl); + +	mutex_unlock(&adapter->led_mutex); +	pm_runtime_put(&adapter->pdev->dev); +} + +static u32 igc_led_get(struct igc_adapter *adapter, int led) +{ +	u32 shift, mask, blink_bit, ledctl; +	struct igc_hw *hw = &adapter->hw; + +	igc_led_select(adapter, led, &mask, &shift, &blink_bit); + +	pm_runtime_get_sync(&adapter->pdev->dev); +	mutex_lock(&adapter->led_mutex); +	ledctl = rd32(IGC_LEDCTL); +	mutex_unlock(&adapter->led_mutex); +	pm_runtime_put(&adapter->pdev->dev); + +	return (ledctl & mask) >> shift; +} + +static int igc_led_brightness_set_blocking(struct led_classdev *led_cdev, +					   enum led_brightness brightness) +{ +	struct igc_led_classdev *ldev = lcdev_to_igc_ldev(led_cdev); +	struct igc_adapter *adapter = netdev_priv(ldev->netdev); +	u32 mode; + +	if (brightness) +		mode = IGC_LEDCTL_MODE_ON; +	else +		mode = IGC_LEDCTL_MODE_OFF; + +	netdev_dbg(adapter->netdev, "Set brightness for LED %d to mode %u!\n", +		   ldev->index, mode); + +	igc_led_set(adapter, ldev->index, mode, false); + +	return 0; +} + +static int igc_led_hw_control_is_supported(struct led_classdev *led_cdev, +					   unsigned long flags) +{ +	if (flags & ~IGC_SUPPORTED_MODES) +		return -EOPNOTSUPP; + +	/* If Tx and Rx selected, activity can be offloaded unless some other +	 * mode is selected as well. +	 */ +	if ((flags & BIT(TRIGGER_NETDEV_TX)) && +	    (flags & BIT(TRIGGER_NETDEV_RX)) && +	    !(flags & ~IGC_ACTIVITY_MODES)) +		return 0; + +	/* Single Rx or Tx activity is not supported. */ +	if (flags & IGC_ACTIVITY_MODES) +		return -EOPNOTSUPP; + +	/* Only one mode can be active at a given time. */ +	if (flags & (flags - 1)) +		return -EOPNOTSUPP; + +	return 0; +} + +static int igc_led_hw_control_set(struct led_classdev *led_cdev, +				  unsigned long flags) +{ +	struct igc_led_classdev *ldev = lcdev_to_igc_ldev(led_cdev); +	struct igc_adapter *adapter = netdev_priv(ldev->netdev); +	u32 mode = IGC_LEDCTL_MODE_OFF; +	bool blink = false; + +	if (flags & BIT(TRIGGER_NETDEV_LINK_10)) +		mode = IGC_LEDCTL_MODE_LINK_10; +	if (flags & BIT(TRIGGER_NETDEV_LINK_100)) +		mode = IGC_LEDCTL_MODE_LINK_100; +	if (flags & BIT(TRIGGER_NETDEV_LINK_1000)) +		mode = IGC_LEDCTL_MODE_LINK_1000; +	if (flags & BIT(TRIGGER_NETDEV_LINK_2500)) +		mode = IGC_LEDCTL_MODE_LINK_2500; +	if ((flags & BIT(TRIGGER_NETDEV_TX)) && +	    (flags & BIT(TRIGGER_NETDEV_RX))) +		mode = IGC_LEDCTL_MODE_ACTIVITY; + +	netdev_dbg(adapter->netdev, "Set HW control for LED %d to mode %u!\n", +		   ldev->index, mode); + +	/* blink is recommended for activity */ +	if (mode == IGC_LEDCTL_MODE_ACTIVITY) +		blink = true; + +	igc_led_set(adapter, ldev->index, mode, blink); + +	return 0; +} + +static int igc_led_hw_control_get(struct led_classdev *led_cdev, +				  unsigned long *flags) +{ +	struct igc_led_classdev *ldev = lcdev_to_igc_ldev(led_cdev); +	struct igc_adapter *adapter = netdev_priv(ldev->netdev); +	u32 mode; + +	mode = igc_led_get(adapter, ldev->index); + +	switch (mode) { +	case IGC_LEDCTL_MODE_ACTIVITY: +		*flags = BIT(TRIGGER_NETDEV_TX) | BIT(TRIGGER_NETDEV_RX); +		break; +	case IGC_LEDCTL_MODE_LINK_10: +		*flags = BIT(TRIGGER_NETDEV_LINK_10); +		break; +	case IGC_LEDCTL_MODE_LINK_100: +		*flags = BIT(TRIGGER_NETDEV_LINK_100); +		break; +	case IGC_LEDCTL_MODE_LINK_1000: +		*flags = BIT(TRIGGER_NETDEV_LINK_1000); +		break; +	case IGC_LEDCTL_MODE_LINK_2500: +		*flags = BIT(TRIGGER_NETDEV_LINK_2500); +		break; +	} + +	return 0; +} + +static struct device *igc_led_hw_control_get_device(struct led_classdev *led_cdev) +{ +	struct igc_led_classdev *ldev = lcdev_to_igc_ldev(led_cdev); + +	return &ldev->netdev->dev; +} + +static void igc_led_get_name(struct igc_adapter *adapter, int index, char *buf, +			     size_t buf_len) +{ +	snprintf(buf, buf_len, "igc-%x%x-led%d", +		 pci_domain_nr(adapter->pdev->bus), +		 pci_dev_id(adapter->pdev), index); +} + +static void igc_setup_ldev(struct igc_led_classdev *ldev, +			   struct net_device *netdev, int index) +{ +	struct igc_adapter *adapter = netdev_priv(netdev); +	struct led_classdev *led_cdev = &ldev->led; +	char led_name[LED_MAX_NAME_SIZE]; + +	ldev->netdev = netdev; +	ldev->index = index; + +	igc_led_get_name(adapter, index, led_name, LED_MAX_NAME_SIZE); +	led_cdev->name = led_name; +	led_cdev->flags |= LED_RETAIN_AT_SHUTDOWN; +	led_cdev->max_brightness = 1; +	led_cdev->brightness_set_blocking = igc_led_brightness_set_blocking; +	led_cdev->hw_control_trigger = "netdev"; +	led_cdev->hw_control_is_supported = igc_led_hw_control_is_supported; +	led_cdev->hw_control_set = igc_led_hw_control_set; +	led_cdev->hw_control_get = igc_led_hw_control_get; +	led_cdev->hw_control_get_device = igc_led_hw_control_get_device; + +	devm_led_classdev_register(&netdev->dev, led_cdev); +} + +int igc_led_setup(struct igc_adapter *adapter) +{ +	struct net_device *netdev = adapter->netdev; +	struct device *dev = &netdev->dev; +	struct igc_led_classdev *leds; +	int i; + +	mutex_init(&adapter->led_mutex); + +	leds = devm_kcalloc(dev, IGC_NUM_LEDS, sizeof(*leds), GFP_KERNEL); +	if (!leds) +		return -ENOMEM; + +	for (i = 0; i < IGC_NUM_LEDS; i++) +		igc_setup_ldev(leds + i, netdev, i); + +	return 0; +}  | 
