303 lines
7.6 KiB
C
303 lines
7.6 KiB
C
// 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 int 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;
|
|
|
|
return led_classdev_register(&netdev->dev, led_cdev);
|
|
}
|
|
|
|
int igc_led_setup(struct igc_adapter *adapter)
|
|
{
|
|
struct net_device *netdev = adapter->netdev;
|
|
struct igc_led_classdev *leds;
|
|
int i, err;
|
|
|
|
mutex_init(&adapter->led_mutex);
|
|
|
|
leds = kcalloc(IGC_NUM_LEDS, sizeof(*leds), GFP_KERNEL);
|
|
if (!leds)
|
|
return -ENOMEM;
|
|
|
|
for (i = 0; i < IGC_NUM_LEDS; i++) {
|
|
err = igc_setup_ldev(leds + i, netdev, i);
|
|
if (err)
|
|
goto err;
|
|
}
|
|
|
|
adapter->leds = leds;
|
|
|
|
return 0;
|
|
|
|
err:
|
|
for (i--; i >= 0; i--)
|
|
led_classdev_unregister(&((leds + i)->led));
|
|
|
|
kfree(leds);
|
|
return err;
|
|
}
|
|
|
|
void igc_led_free(struct igc_adapter *adapter)
|
|
{
|
|
struct igc_led_classdev *leds = adapter->leds;
|
|
int i;
|
|
|
|
for (i = 0; i < IGC_NUM_LEDS; i++)
|
|
led_classdev_unregister(&((leds + i)->led));
|
|
|
|
kfree(leds);
|
|
}
|