303 lines · c
1// SPDX-License-Identifier: GPL-2.02/* Copyright (C) 2024 Linutronix GmbH */3 4#include <linux/bits.h>5#include <linux/leds.h>6#include <linux/netdevice.h>7#include <linux/pm_runtime.h>8#include <uapi/linux/uleds.h>9 10#include "igc.h"11 12#define IGC_NUM_LEDS 313 14#define IGC_LEDCTL_LED0_MODE_SHIFT 015#define IGC_LEDCTL_LED0_MODE_MASK GENMASK(3, 0)16#define IGC_LEDCTL_LED0_BLINK BIT(7)17#define IGC_LEDCTL_LED1_MODE_SHIFT 818#define IGC_LEDCTL_LED1_MODE_MASK GENMASK(11, 8)19#define IGC_LEDCTL_LED1_BLINK BIT(15)20#define IGC_LEDCTL_LED2_MODE_SHIFT 1621#define IGC_LEDCTL_LED2_MODE_MASK GENMASK(19, 16)22#define IGC_LEDCTL_LED2_BLINK BIT(23)23 24#define IGC_LEDCTL_MODE_ON 0x0025#define IGC_LEDCTL_MODE_OFF 0x0126#define IGC_LEDCTL_MODE_LINK_10 0x0527#define IGC_LEDCTL_MODE_LINK_100 0x0628#define IGC_LEDCTL_MODE_LINK_1000 0x0729#define IGC_LEDCTL_MODE_LINK_2500 0x0830#define IGC_LEDCTL_MODE_ACTIVITY 0x0b31 32#define IGC_SUPPORTED_MODES \33 (BIT(TRIGGER_NETDEV_LINK_2500) | BIT(TRIGGER_NETDEV_LINK_1000) | \34 BIT(TRIGGER_NETDEV_LINK_100) | BIT(TRIGGER_NETDEV_LINK_10) | \35 BIT(TRIGGER_NETDEV_RX) | BIT(TRIGGER_NETDEV_TX))36 37#define IGC_ACTIVITY_MODES \38 (BIT(TRIGGER_NETDEV_RX) | BIT(TRIGGER_NETDEV_TX))39 40struct igc_led_classdev {41 struct net_device *netdev;42 struct led_classdev led;43 int index;44};45 46#define lcdev_to_igc_ldev(lcdev) \47 container_of(lcdev, struct igc_led_classdev, led)48 49static void igc_led_select(struct igc_adapter *adapter, int led,50 u32 *mask, u32 *shift, u32 *blink)51{52 switch (led) {53 case 0:54 *mask = IGC_LEDCTL_LED0_MODE_MASK;55 *shift = IGC_LEDCTL_LED0_MODE_SHIFT;56 *blink = IGC_LEDCTL_LED0_BLINK;57 break;58 case 1:59 *mask = IGC_LEDCTL_LED1_MODE_MASK;60 *shift = IGC_LEDCTL_LED1_MODE_SHIFT;61 *blink = IGC_LEDCTL_LED1_BLINK;62 break;63 case 2:64 *mask = IGC_LEDCTL_LED2_MODE_MASK;65 *shift = IGC_LEDCTL_LED2_MODE_SHIFT;66 *blink = IGC_LEDCTL_LED2_BLINK;67 break;68 default:69 *mask = *shift = *blink = 0;70 netdev_err(adapter->netdev, "Unknown LED %d selected!\n", led);71 }72}73 74static void igc_led_set(struct igc_adapter *adapter, int led, u32 mode,75 bool blink)76{77 u32 shift, mask, blink_bit, ledctl;78 struct igc_hw *hw = &adapter->hw;79 80 igc_led_select(adapter, led, &mask, &shift, &blink_bit);81 82 pm_runtime_get_sync(&adapter->pdev->dev);83 mutex_lock(&adapter->led_mutex);84 85 /* Set mode */86 ledctl = rd32(IGC_LEDCTL);87 ledctl &= ~mask;88 ledctl |= mode << shift;89 90 /* Configure blinking */91 if (blink)92 ledctl |= blink_bit;93 else94 ledctl &= ~blink_bit;95 wr32(IGC_LEDCTL, ledctl);96 97 mutex_unlock(&adapter->led_mutex);98 pm_runtime_put(&adapter->pdev->dev);99}100 101static u32 igc_led_get(struct igc_adapter *adapter, int led)102{103 u32 shift, mask, blink_bit, ledctl;104 struct igc_hw *hw = &adapter->hw;105 106 igc_led_select(adapter, led, &mask, &shift, &blink_bit);107 108 pm_runtime_get_sync(&adapter->pdev->dev);109 mutex_lock(&adapter->led_mutex);110 ledctl = rd32(IGC_LEDCTL);111 mutex_unlock(&adapter->led_mutex);112 pm_runtime_put(&adapter->pdev->dev);113 114 return (ledctl & mask) >> shift;115}116 117static int igc_led_brightness_set_blocking(struct led_classdev *led_cdev,118 enum led_brightness brightness)119{120 struct igc_led_classdev *ldev = lcdev_to_igc_ldev(led_cdev);121 struct igc_adapter *adapter = netdev_priv(ldev->netdev);122 u32 mode;123 124 if (brightness)125 mode = IGC_LEDCTL_MODE_ON;126 else127 mode = IGC_LEDCTL_MODE_OFF;128 129 netdev_dbg(adapter->netdev, "Set brightness for LED %d to mode %u!\n",130 ldev->index, mode);131 132 igc_led_set(adapter, ldev->index, mode, false);133 134 return 0;135}136 137static int igc_led_hw_control_is_supported(struct led_classdev *led_cdev,138 unsigned long flags)139{140 if (flags & ~IGC_SUPPORTED_MODES)141 return -EOPNOTSUPP;142 143 /* If Tx and Rx selected, activity can be offloaded unless some other144 * mode is selected as well.145 */146 if ((flags & BIT(TRIGGER_NETDEV_TX)) &&147 (flags & BIT(TRIGGER_NETDEV_RX)) &&148 !(flags & ~IGC_ACTIVITY_MODES))149 return 0;150 151 /* Single Rx or Tx activity is not supported. */152 if (flags & IGC_ACTIVITY_MODES)153 return -EOPNOTSUPP;154 155 /* Only one mode can be active at a given time. */156 if (flags & (flags - 1))157 return -EOPNOTSUPP;158 159 return 0;160}161 162static int igc_led_hw_control_set(struct led_classdev *led_cdev,163 unsigned long flags)164{165 struct igc_led_classdev *ldev = lcdev_to_igc_ldev(led_cdev);166 struct igc_adapter *adapter = netdev_priv(ldev->netdev);167 u32 mode = IGC_LEDCTL_MODE_OFF;168 bool blink = false;169 170 if (flags & BIT(TRIGGER_NETDEV_LINK_10))171 mode = IGC_LEDCTL_MODE_LINK_10;172 if (flags & BIT(TRIGGER_NETDEV_LINK_100))173 mode = IGC_LEDCTL_MODE_LINK_100;174 if (flags & BIT(TRIGGER_NETDEV_LINK_1000))175 mode = IGC_LEDCTL_MODE_LINK_1000;176 if (flags & BIT(TRIGGER_NETDEV_LINK_2500))177 mode = IGC_LEDCTL_MODE_LINK_2500;178 if ((flags & BIT(TRIGGER_NETDEV_TX)) &&179 (flags & BIT(TRIGGER_NETDEV_RX)))180 mode = IGC_LEDCTL_MODE_ACTIVITY;181 182 netdev_dbg(adapter->netdev, "Set HW control for LED %d to mode %u!\n",183 ldev->index, mode);184 185 /* blink is recommended for activity */186 if (mode == IGC_LEDCTL_MODE_ACTIVITY)187 blink = true;188 189 igc_led_set(adapter, ldev->index, mode, blink);190 191 return 0;192}193 194static int igc_led_hw_control_get(struct led_classdev *led_cdev,195 unsigned long *flags)196{197 struct igc_led_classdev *ldev = lcdev_to_igc_ldev(led_cdev);198 struct igc_adapter *adapter = netdev_priv(ldev->netdev);199 u32 mode;200 201 mode = igc_led_get(adapter, ldev->index);202 203 switch (mode) {204 case IGC_LEDCTL_MODE_ACTIVITY:205 *flags = BIT(TRIGGER_NETDEV_TX) | BIT(TRIGGER_NETDEV_RX);206 break;207 case IGC_LEDCTL_MODE_LINK_10:208 *flags = BIT(TRIGGER_NETDEV_LINK_10);209 break;210 case IGC_LEDCTL_MODE_LINK_100:211 *flags = BIT(TRIGGER_NETDEV_LINK_100);212 break;213 case IGC_LEDCTL_MODE_LINK_1000:214 *flags = BIT(TRIGGER_NETDEV_LINK_1000);215 break;216 case IGC_LEDCTL_MODE_LINK_2500:217 *flags = BIT(TRIGGER_NETDEV_LINK_2500);218 break;219 }220 221 return 0;222}223 224static struct device *igc_led_hw_control_get_device(struct led_classdev *led_cdev)225{226 struct igc_led_classdev *ldev = lcdev_to_igc_ldev(led_cdev);227 228 return &ldev->netdev->dev;229}230 231static void igc_led_get_name(struct igc_adapter *adapter, int index, char *buf,232 size_t buf_len)233{234 snprintf(buf, buf_len, "igc-%x%x-led%d",235 pci_domain_nr(adapter->pdev->bus),236 pci_dev_id(adapter->pdev), index);237}238 239static int igc_setup_ldev(struct igc_led_classdev *ldev,240 struct net_device *netdev, int index)241{242 struct igc_adapter *adapter = netdev_priv(netdev);243 struct led_classdev *led_cdev = &ldev->led;244 char led_name[LED_MAX_NAME_SIZE];245 246 ldev->netdev = netdev;247 ldev->index = index;248 249 igc_led_get_name(adapter, index, led_name, LED_MAX_NAME_SIZE);250 led_cdev->name = led_name;251 led_cdev->flags |= LED_RETAIN_AT_SHUTDOWN;252 led_cdev->max_brightness = 1;253 led_cdev->brightness_set_blocking = igc_led_brightness_set_blocking;254 led_cdev->hw_control_trigger = "netdev";255 led_cdev->hw_control_is_supported = igc_led_hw_control_is_supported;256 led_cdev->hw_control_set = igc_led_hw_control_set;257 led_cdev->hw_control_get = igc_led_hw_control_get;258 led_cdev->hw_control_get_device = igc_led_hw_control_get_device;259 260 return led_classdev_register(&netdev->dev, led_cdev);261}262 263int igc_led_setup(struct igc_adapter *adapter)264{265 struct net_device *netdev = adapter->netdev;266 struct igc_led_classdev *leds;267 int i, err;268 269 mutex_init(&adapter->led_mutex);270 271 leds = kcalloc(IGC_NUM_LEDS, sizeof(*leds), GFP_KERNEL);272 if (!leds)273 return -ENOMEM;274 275 for (i = 0; i < IGC_NUM_LEDS; i++) {276 err = igc_setup_ldev(leds + i, netdev, i);277 if (err)278 goto err;279 }280 281 adapter->leds = leds;282 283 return 0;284 285err:286 for (i--; i >= 0; i--)287 led_classdev_unregister(&((leds + i)->led));288 289 kfree(leds);290 return err;291}292 293void igc_led_free(struct igc_adapter *adapter)294{295 struct igc_led_classdev *leds = adapter->leds;296 int i;297 298 for (i = 0; i < IGC_NUM_LEDS; i++)299 led_classdev_unregister(&((leds + i)->led));300 301 kfree(leds);302}303