Commit ea578703 authored by Kurt Kanzenbach's avatar Kurt Kanzenbach Committed by Paolo Abeni
Browse files

igc: Add support for LEDs on i225/i226



Add support for LEDs on i225/i226. The LEDs can be controlled via sysfs
from user space using the netdev trigger. The LEDs are named as
igc-<bus><device>-<led> to be easily identified.

Offloading link speed and activity are supported. Other modes are simulated
in software by using on/off. Tested on Intel i225.

Signed-off-by: default avatarKurt Kanzenbach <kurt@linutronix.de>
Reviewed-by: default avatarAndrew Lunn <andrew@lunn.ch>
Tested-by: default avatarNaama Meir <naamax.meir@linux.intel.com>
Signed-off-by: default avatarTony Nguyen <anthony.l.nguyen@intel.com>
Link: https://lore.kernel.org/r/20240213184138.1483968-1-anthony.l.nguyen@intel.com


Signed-off-by: default avatarPaolo Abeni <pabeni@redhat.com>
parent e8d8acad
Loading
Loading
Loading
Loading
+8 −0
Original line number Diff line number Diff line
@@ -368,6 +368,14 @@ config IGC

	  To compile this driver as a module, choose M here. The module
	  will be called igc.

config IGC_LEDS
	def_bool LEDS_TRIGGER_NETDEV
	depends on IGC && LEDS_CLASS
	help
	  Optional support for controlling the NIC LED's with the netdev
	  LED trigger.

config IDPF
	tristate "Intel(R) Infrastructure Data Path Function Support"
	depends on PCI_MSI
+1 −0
Original line number Diff line number Diff line
@@ -6,6 +6,7 @@
#

obj-$(CONFIG_IGC) += igc.o
igc-$(CONFIG_IGC_LEDS) += igc_leds.o

igc-objs := igc_main.o igc_mac.o igc_i225.o igc_base.o igc_nvm.o igc_phy.o \
igc_diag.o igc_ethtool.o igc_ptp.o igc_dump.o igc_tsn.o igc_xdp.o
+5 −0
Original line number Diff line number Diff line
@@ -295,6 +295,9 @@ struct igc_adapter {
		struct timespec64 start;
		struct timespec64 period;
	} perout[IGC_N_PEROUT];

	/* LEDs */
	struct mutex led_mutex;
};

void igc_up(struct igc_adapter *adapter);
@@ -720,6 +723,8 @@ void igc_ptp_tx_hang(struct igc_adapter *adapter);
void igc_ptp_read(struct igc_adapter *adapter, struct timespec64 *ts);
void igc_ptp_tx_tstamp_event(struct igc_adapter *adapter);

int igc_led_setup(struct igc_adapter *adapter);

#define igc_rx_pg_size(_ring) (PAGE_SIZE << igc_rx_pg_order(_ring))

#define IGC_TXD_DCMD	(IGC_ADVTXD_DCMD_EOP | IGC_ADVTXD_DCMD_RS)
+280 −0
Original line number Diff line number Diff line
// 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;
}
+6 −0
Original line number Diff line number Diff line
@@ -6978,6 +6978,12 @@ static int igc_probe(struct pci_dev *pdev,

	pm_runtime_put_noidle(&pdev->dev);

	if (IS_ENABLED(CONFIG_IGC_LEDS)) {
		err = igc_led_setup(adapter);
		if (err)
			goto err_register;
	}

	return 0;

err_register:
Loading