// SPDX-License-Identifier: BSD-3-Clause OR GPL-2.0 /* Copyright (c) 2019-2020 Marvell International Ltd. All rights reserved */ #include #include #include #include "prestera_ethtool.h" #include "prestera.h" #include "prestera_hw.h" #define PRESTERA_STATS_CNT \ (sizeof(struct prestera_port_stats) / sizeof(u64)) #define PRESTERA_STATS_IDX(name) \ (offsetof(struct prestera_port_stats, name) / sizeof(u64)) #define PRESTERA_STATS_FIELD(name) \ [PRESTERA_STATS_IDX(name)] = __stringify(name) static const char driver_kind[] = "prestera"; static const struct prestera_link_mode { enum ethtool_link_mode_bit_indices eth_mode; u32 speed; u64 pr_mask; u8 duplex; u8 port_type; } port_link_modes[PRESTERA_LINK_MODE_MAX] = { [PRESTERA_LINK_MODE_10baseT_Half] = { .eth_mode = ETHTOOL_LINK_MODE_10baseT_Half_BIT, .speed = 10, .pr_mask = 1 << PRESTERA_LINK_MODE_10baseT_Half, .duplex = PRESTERA_PORT_DUPLEX_HALF, .port_type = PRESTERA_PORT_TYPE_TP, }, [PRESTERA_LINK_MODE_10baseT_Full] = { .eth_mode = ETHTOOL_LINK_MODE_10baseT_Full_BIT, .speed = 10, .pr_mask = 1 << PRESTERA_LINK_MODE_10baseT_Full, .duplex = PRESTERA_PORT_DUPLEX_FULL, .port_type = PRESTERA_PORT_TYPE_TP, }, [PRESTERA_LINK_MODE_100baseT_Half] = { .eth_mode = ETHTOOL_LINK_MODE_100baseT_Half_BIT, .speed = 100, .pr_mask = 1 << PRESTERA_LINK_MODE_100baseT_Half, .duplex = PRESTERA_PORT_DUPLEX_HALF, .port_type = PRESTERA_PORT_TYPE_TP, }, [PRESTERA_LINK_MODE_100baseT_Full] = { .eth_mode = ETHTOOL_LINK_MODE_100baseT_Full_BIT, .speed = 100, .pr_mask = 1 << PRESTERA_LINK_MODE_100baseT_Full, .duplex = PRESTERA_PORT_DUPLEX_FULL, .port_type = PRESTERA_PORT_TYPE_TP, }, [PRESTERA_LINK_MODE_1000baseT_Half] = { .eth_mode = ETHTOOL_LINK_MODE_1000baseT_Half_BIT, .speed = 1000, .pr_mask = 1 << PRESTERA_LINK_MODE_1000baseT_Half, .duplex = PRESTERA_PORT_DUPLEX_HALF, .port_type = PRESTERA_PORT_TYPE_TP, }, [PRESTERA_LINK_MODE_1000baseT_Full] = { .eth_mode = ETHTOOL_LINK_MODE_1000baseT_Full_BIT, .speed = 1000, .pr_mask = 1 << PRESTERA_LINK_MODE_1000baseT_Full, .duplex = PRESTERA_PORT_DUPLEX_FULL, .port_type = PRESTERA_PORT_TYPE_TP, }, [PRESTERA_LINK_MODE_1000baseX_Full] = { .eth_mode = ETHTOOL_LINK_MODE_1000baseX_Full_BIT, .speed = 1000, .pr_mask = 1 << PRESTERA_LINK_MODE_1000baseX_Full, .duplex = PRESTERA_PORT_DUPLEX_FULL, .port_type = PRESTERA_PORT_TYPE_FIBRE, }, [PRESTERA_LINK_MODE_1000baseKX_Full] = { .eth_mode = ETHTOOL_LINK_MODE_1000baseKX_Full_BIT, .speed = 1000, .pr_mask = 1 << PRESTERA_LINK_MODE_1000baseKX_Full, .duplex = PRESTERA_PORT_DUPLEX_FULL, .port_type = PRESTERA_PORT_TYPE_TP, }, [PRESTERA_LINK_MODE_2500baseX_Full] = { .eth_mode = ETHTOOL_LINK_MODE_2500baseX_Full_BIT, .speed = 2500, .pr_mask = 1 << PRESTERA_LINK_MODE_2500baseX_Full, .duplex = PRESTERA_PORT_DUPLEX_FULL, }, [PRESTERA_LINK_MODE_10GbaseKR_Full] = { .eth_mode = ETHTOOL_LINK_MODE_10000baseKR_Full_BIT, .speed = 10000, .pr_mask = 1 << PRESTERA_LINK_MODE_10GbaseKR_Full, .duplex = PRESTERA_PORT_DUPLEX_FULL, .port_type = PRESTERA_PORT_TYPE_TP, }, [PRESTERA_LINK_MODE_10GbaseSR_Full] = { .eth_mode = ETHTOOL_LINK_MODE_10000baseSR_Full_BIT, .speed = 10000, .pr_mask = 1 << PRESTERA_LINK_MODE_10GbaseSR_Full, .duplex = PRESTERA_PORT_DUPLEX_FULL, .port_type = PRESTERA_PORT_TYPE_FIBRE, }, [PRESTERA_LINK_MODE_10GbaseLR_Full] = { .eth_mode = ETHTOOL_LINK_MODE_10000baseLR_Full_BIT, .speed = 10000, .pr_mask = 1 << PRESTERA_LINK_MODE_10GbaseLR_Full, .duplex = PRESTERA_PORT_DUPLEX_FULL, .port_type = PRESTERA_PORT_TYPE_FIBRE, }, [PRESTERA_LINK_MODE_20GbaseKR2_Full] = { .eth_mode = ETHTOOL_LINK_MODE_20000baseKR2_Full_BIT, .speed = 20000, .pr_mask = 1 << PRESTERA_LINK_MODE_20GbaseKR2_Full, .duplex = PRESTERA_PORT_DUPLEX_FULL, .port_type = PRESTERA_PORT_TYPE_TP, }, [PRESTERA_LINK_MODE_25GbaseCR_Full] = { .eth_mode = ETHTOOL_LINK_MODE_25000baseCR_Full_BIT, .speed = 25000, .pr_mask = 1 << PRESTERA_LINK_MODE_25GbaseCR_Full, .duplex = PRESTERA_PORT_DUPLEX_FULL, .port_type = PRESTERA_PORT_TYPE_DA, }, [PRESTERA_LINK_MODE_25GbaseKR_Full] = { .eth_mode = ETHTOOL_LINK_MODE_25000baseKR_Full_BIT, .speed = 25000, .pr_mask = 1 << PRESTERA_LINK_MODE_25GbaseKR_Full, .duplex = PRESTERA_PORT_DUPLEX_FULL, .port_type = PRESTERA_PORT_TYPE_TP, }, [PRESTERA_LINK_MODE_25GbaseSR_Full] = { .eth_mode = ETHTOOL_LINK_MODE_25000baseSR_Full_BIT, .speed = 25000, .pr_mask = 1 << PRESTERA_LINK_MODE_25GbaseSR_Full, .duplex = PRESTERA_PORT_DUPLEX_FULL, .port_type = PRESTERA_PORT_TYPE_FIBRE, }, [PRESTERA_LINK_MODE_40GbaseKR4_Full] = { .eth_mode = ETHTOOL_LINK_MODE_40000baseKR4_Full_BIT, .speed = 40000, .pr_mask = 1 << PRESTERA_LINK_MODE_40GbaseKR4_Full, .duplex = PRESTERA_PORT_DUPLEX_FULL, .port_type = PRESTERA_PORT_TYPE_TP, }, [PRESTERA_LINK_MODE_40GbaseCR4_Full] = { .eth_mode = ETHTOOL_LINK_MODE_40000baseCR4_Full_BIT, .speed = 40000, .pr_mask = 1 << PRESTERA_LINK_MODE_40GbaseCR4_Full, .duplex = PRESTERA_PORT_DUPLEX_FULL, .port_type = PRESTERA_PORT_TYPE_DA, }, [PRESTERA_LINK_MODE_40GbaseSR4_Full] = { .eth_mode = ETHTOOL_LINK_MODE_40000baseSR4_Full_BIT, .speed = 40000, .pr_mask = 1 << PRESTERA_LINK_MODE_40GbaseSR4_Full, .duplex = PRESTERA_PORT_DUPLEX_FULL, .port_type = PRESTERA_PORT_TYPE_FIBRE, }, [PRESTERA_LINK_MODE_50GbaseCR2_Full] = { .eth_mode = ETHTOOL_LINK_MODE_50000baseCR2_Full_BIT, .speed = 50000, .pr_mask = 1 << PRESTERA_LINK_MODE_50GbaseCR2_Full, .duplex = PRESTERA_PORT_DUPLEX_FULL, .port_type = PRESTERA_PORT_TYPE_DA, }, [PRESTERA_LINK_MODE_50GbaseKR2_Full] = { .eth_mode = ETHTOOL_LINK_MODE_50000baseKR2_Full_BIT, .speed = 50000, .pr_mask = 1 << PRESTERA_LINK_MODE_50GbaseKR2_Full, .duplex = PRESTERA_PORT_DUPLEX_FULL, .port_type = PRESTERA_PORT_TYPE_TP, }, [PRESTERA_LINK_MODE_50GbaseSR2_Full] = { .eth_mode = ETHTOOL_LINK_MODE_50000baseSR2_Full_BIT, .speed = 50000, .pr_mask = 1 << PRESTERA_LINK_MODE_50GbaseSR2_Full, .duplex = PRESTERA_PORT_DUPLEX_FULL, .port_type = PRESTERA_PORT_TYPE_FIBRE, }, [PRESTERA_LINK_MODE_100GbaseKR4_Full] = { .eth_mode = ETHTOOL_LINK_MODE_100000baseKR4_Full_BIT, .speed = 100000, .pr_mask = 1 << PRESTERA_LINK_MODE_100GbaseKR4_Full, .duplex = PRESTERA_PORT_DUPLEX_FULL, .port_type = PRESTERA_PORT_TYPE_TP, }, [PRESTERA_LINK_MODE_100GbaseSR4_Full] = { .eth_mode = ETHTOOL_LINK_MODE_100000baseSR4_Full_BIT, .speed = 100000, .pr_mask = 1 << PRESTERA_LINK_MODE_100GbaseSR4_Full, .duplex = PRESTERA_PORT_DUPLEX_FULL, .port_type = PRESTERA_PORT_TYPE_FIBRE, }, [PRESTERA_LINK_MODE_100GbaseCR4_Full] = { .eth_mode = ETHTOOL_LINK_MODE_100000baseCR4_Full_BIT, .speed = 100000, .pr_mask = 1 << PRESTERA_LINK_MODE_100GbaseCR4_Full, .duplex = PRESTERA_PORT_DUPLEX_FULL, .port_type = PRESTERA_PORT_TYPE_DA, } }; static const struct prestera_fec { u32 eth_fec; enum ethtool_link_mode_bit_indices eth_mode; u8 pr_fec; } port_fec_caps[PRESTERA_PORT_FEC_MAX] = { [PRESTERA_PORT_FEC_OFF] = { .eth_fec = ETHTOOL_FEC_OFF, .eth_mode = ETHTOOL_LINK_MODE_FEC_NONE_BIT, .pr_fec = 1 << PRESTERA_PORT_FEC_OFF, }, [PRESTERA_PORT_FEC_BASER] = { .eth_fec = ETHTOOL_FEC_BASER, .eth_mode = ETHTOOL_LINK_MODE_FEC_BASER_BIT, .pr_fec = 1 << PRESTERA_PORT_FEC_BASER, }, [PRESTERA_PORT_FEC_RS] = { .eth_fec = ETHTOOL_FEC_RS, .eth_mode = ETHTOOL_LINK_MODE_FEC_RS_BIT, .pr_fec = 1 << PRESTERA_PORT_FEC_RS, } }; static const struct prestera_port_type { enum ethtool_link_mode_bit_indices eth_mode; u8 eth_type; } port_types[PRESTERA_PORT_TYPE_MAX] = { [PRESTERA_PORT_TYPE_NONE] = { .eth_mode = __ETHTOOL_LINK_MODE_MASK_NBITS, .eth_type = PORT_NONE, }, [PRESTERA_PORT_TYPE_TP] = { .eth_mode = ETHTOOL_LINK_MODE_TP_BIT, .eth_type = PORT_TP, }, [PRESTERA_PORT_TYPE_AUI] = { .eth_mode = ETHTOOL_LINK_MODE_AUI_BIT, .eth_type = PORT_AUI, }, [PRESTERA_PORT_TYPE_MII] = { .eth_mode = ETHTOOL_LINK_MODE_MII_BIT, .eth_type = PORT_MII, }, [PRESTERA_PORT_TYPE_FIBRE] = { .eth_mode = ETHTOOL_LINK_MODE_FIBRE_BIT, .eth_type = PORT_FIBRE, }, [PRESTERA_PORT_TYPE_BNC] = { .eth_mode = ETHTOOL_LINK_MODE_BNC_BIT, .eth_type = PORT_BNC, }, [PRESTERA_PORT_TYPE_DA] = { .eth_mode = ETHTOOL_LINK_MODE_TP_BIT, .eth_type = PORT_TP, }, [PRESTERA_PORT_TYPE_OTHER] = { .eth_mode = __ETHTOOL_LINK_MODE_MASK_NBITS, .eth_type = PORT_OTHER, } }; static const char prestera_cnt_name[PRESTERA_STATS_CNT][ETH_GSTRING_LEN] = { PRESTERA_STATS_FIELD(good_octets_received), PRESTERA_STATS_FIELD(bad_octets_received), PRESTERA_STATS_FIELD(mac_trans_error), PRESTERA_STATS_FIELD(broadcast_frames_received), PRESTERA_STATS_FIELD(multicast_frames_received), PRESTERA_STATS_FIELD(frames_64_octets), PRESTERA_STATS_FIELD(frames_65_to_127_octets), PRESTERA_STATS_FIELD(frames_128_to_255_octets), PRESTERA_STATS_FIELD(frames_256_to_511_octets), PRESTERA_STATS_FIELD(frames_512_to_1023_octets), PRESTERA_STATS_FIELD(frames_1024_to_max_octets), PRESTERA_STATS_FIELD(excessive_collision), PRESTERA_STATS_FIELD(multicast_frames_sent), PRESTERA_STATS_FIELD(broadcast_frames_sent), PRESTERA_STATS_FIELD(fc_sent), PRESTERA_STATS_FIELD(fc_received), PRESTERA_STATS_FIELD(buffer_overrun), PRESTERA_STATS_FIELD(undersize), PRESTERA_STATS_FIELD(fragments), PRESTERA_STATS_FIELD(oversize), PRESTERA_STATS_FIELD(jabber), PRESTERA_STATS_FIELD(rx_error_frame_received), PRESTERA_STATS_FIELD(bad_crc), PRESTERA_STATS_FIELD(collisions), PRESTERA_STATS_FIELD(late_collision), PRESTERA_STATS_FIELD(unicast_frames_received), PRESTERA_STATS_FIELD(unicast_frames_sent), PRESTERA_STATS_FIELD(sent_multiple), PRESTERA_STATS_FIELD(sent_deferred), PRESTERA_STATS_FIELD(good_octets_sent), }; static void prestera_ethtool_get_drvinfo(struct net_device *dev, struct ethtool_drvinfo *drvinfo) { struct prestera_port *port = netdev_priv(dev); struct prestera_switch *sw = port->sw; strlcpy(drvinfo->driver, driver_kind, sizeof(drvinfo->driver)); strlcpy(drvinfo->bus_info, dev_name(prestera_dev(sw)), sizeof(drvinfo->bus_info)); snprintf(drvinfo->fw_version, sizeof(drvinfo->fw_version), "%d.%d.%d", sw->dev->fw_rev.maj, sw->dev->fw_rev.min, sw->dev->fw_rev.sub); } static u8 prestera_port_type_get(struct prestera_port *port) { if (port->caps.type < PRESTERA_PORT_TYPE_MAX) return port_types[port->caps.type].eth_type; return PORT_OTHER; } static int prestera_port_type_set(const struct ethtool_link_ksettings *ecmd, struct prestera_port *port) { u32 new_mode = PRESTERA_LINK_MODE_MAX; u32 type, mode; int err; for (type = 0; type < PRESTERA_PORT_TYPE_MAX; type++) { if (port_types[type].eth_type == ecmd->base.port && test_bit(port_types[type].eth_mode, ecmd->link_modes.supported)) { break; } } if (type == port->caps.type) return 0; if (type != port->caps.type && ecmd->base.autoneg == AUTONEG_ENABLE) return -EINVAL; if (type == PRESTERA_PORT_TYPE_MAX) return -EOPNOTSUPP; for (mode = 0; mode < PRESTERA_LINK_MODE_MAX; mode++) { if ((port_link_modes[mode].pr_mask & port->caps.supp_link_modes) && type == port_link_modes[mode].port_type) { new_mode = mode; } } if (new_mode < PRESTERA_LINK_MODE_MAX) err = prestera_hw_port_link_mode_set(port, new_mode); else err = -EINVAL; if (err) return err; port->caps.type = type; port->autoneg = false; return 0; } static void prestera_modes_to_eth(unsigned long *eth_modes, u64 link_modes, u8 fec, u8 type) { u32 mode; for (mode = 0; mode < PRESTERA_LINK_MODE_MAX; mode++) { if ((port_link_modes[mode].pr_mask & link_modes) == 0) continue; if (type != PRESTERA_PORT_TYPE_NONE && port_link_modes[mode].port_type != type) continue; __set_bit(port_link_modes[mode].eth_mode, eth_modes); } for (mode = 0; mode < PRESTERA_PORT_FEC_MAX; mode++) { if ((port_fec_caps[mode].pr_fec & fec) == 0) continue; __set_bit(port_fec_caps[mode].eth_mode, eth_modes); } } static void prestera_modes_from_eth(const unsigned long *eth_modes, u64 *link_modes, u8 *fec, u8 type) { u64 adver_modes = 0; u32 fec_modes = 0; u32 mode; for (mode = 0; mode < PRESTERA_LINK_MODE_MAX; mode++) { if (!test_bit(port_link_modes[mode].eth_mode, eth_modes)) continue; if (port_link_modes[mode].port_type != type) continue; adver_modes |= port_link_modes[mode].pr_mask; } for (mode = 0; mode < PRESTERA_PORT_FEC_MAX; mode++) { if (!test_bit(port_fec_caps[mode].eth_mode, eth_modes)) continue; fec_modes |= port_fec_caps[mode].pr_fec; } *link_modes = adver_modes; *fec = fec_modes; } static void prestera_port_supp_types_get(struct ethtool_link_ksettings *ecmd, struct prestera_port *port) { u32 mode; u8 ptype; for (mode = 0; mode < PRESTERA_LINK_MODE_MAX; mode++) { if ((port_link_modes[mode].pr_mask & port->caps.supp_link_modes) == 0) continue; ptype = port_link_modes[mode].port_type; __set_bit(port_types[ptype].eth_mode, ecmd->link_modes.supported); } } static void prestera_port_remote_cap_get(struct ethtool_link_ksettings *ecmd, struct prestera_port *port) { bool asym_pause; bool pause; u64 bitmap; int err; err = prestera_hw_port_remote_cap_get(port, &bitmap); if (!err) { prestera_modes_to_eth(ecmd->link_modes.lp_advertising, bitmap, 0, PRESTERA_PORT_TYPE_NONE); if (!bitmap_empty(ecmd->link_modes.lp_advertising, __ETHTOOL_LINK_MODE_MASK_NBITS)) { ethtool_link_ksettings_add_link_mode(ecmd, lp_advertising, Autoneg); } } err = prestera_hw_port_remote_fc_get(port, &pause, &asym_pause); if (err) return; if (pause) ethtool_link_ksettings_add_link_mode(ecmd, lp_advertising, Pause); if (asym_pause) ethtool_link_ksettings_add_link_mode(ecmd, lp_advertising, Asym_Pause); } static void prestera_port_speed_get(struct ethtool_link_ksettings *ecmd, struct prestera_port *port) { u32 speed; int err; err = prestera_hw_port_speed_get(port, &speed); ecmd->base.speed = err ? SPEED_UNKNOWN : speed; } static void prestera_port_duplex_get(struct ethtool_link_ksettings *ecmd, struct prestera_port *port) { u8 duplex; int err; err = prestera_hw_port_duplex_get(port, &duplex); if (err) { ecmd->base.duplex = DUPLEX_UNKNOWN; return; } ecmd->base.duplex = duplex == PRESTERA_PORT_DUPLEX_FULL ? DUPLEX_FULL : DUPLEX_HALF; } static int prestera_ethtool_get_link_ksettings(struct net_device *dev, struct ethtool_link_ksettings *ecmd) { struct prestera_port *port = netdev_priv(dev); ethtool_link_ksettings_zero_link_mode(ecmd, supported); ethtool_link_ksettings_zero_link_mode(ecmd, advertising); ethtool_link_ksettings_zero_link_mode(ecmd, lp_advertising); ecmd->base.autoneg = port->autoneg ? AUTONEG_ENABLE : AUTONEG_DISABLE; if (port->caps.type == PRESTERA_PORT_TYPE_TP) { ethtool_link_ksettings_add_link_mode(ecmd, supported, Autoneg); if (netif_running(dev) && (port->autoneg || port->caps.transceiver == PRESTERA_PORT_TCVR_COPPER)) ethtool_link_ksettings_add_link_mode(ecmd, advertising, Autoneg); } prestera_modes_to_eth(ecmd->link_modes.supported, port->caps.supp_link_modes, port->caps.supp_fec, port->caps.type); prestera_port_supp_types_get(ecmd, port); if (netif_carrier_ok(dev)) { prestera_port_speed_get(ecmd, port); prestera_port_duplex_get(ecmd, port); } else { ecmd->base.speed = SPEED_UNKNOWN; ecmd->base.duplex = DUPLEX_UNKNOWN; } ecmd->base.port = prestera_port_type_get(port); if (port->autoneg) { if (netif_running(dev)) prestera_modes_to_eth(ecmd->link_modes.advertising, port->adver_link_modes, port->adver_fec, port->caps.type); if (netif_carrier_ok(dev) && port->caps.transceiver == PRESTERA_PORT_TCVR_COPPER) prestera_port_remote_cap_get(ecmd, port); } if (port->caps.type == PRESTERA_PORT_TYPE_TP && port->caps.transceiver == PRESTERA_PORT_TCVR_COPPER) prestera_hw_port_mdix_get(port, &ecmd->base.eth_tp_mdix, &ecmd->base.eth_tp_mdix_ctrl); return 0; } static int prestera_port_mdix_set(const struct ethtool_link_ksettings *ecmd, struct prestera_port *port) { if (ecmd->base.eth_tp_mdix_ctrl != ETH_TP_MDI_INVALID && port->caps.transceiver == PRESTERA_PORT_TCVR_COPPER && port->caps.type == PRESTERA_PORT_TYPE_TP) return prestera_hw_port_mdix_set(port, ecmd->base.eth_tp_mdix_ctrl); return 0; } static int prestera_port_link_mode_set(struct prestera_port *port, u32 speed, u8 duplex, u8 type) { u32 new_mode = PRESTERA_LINK_MODE_MAX; u32 mode; for (mode = 0; mode < PRESTERA_LINK_MODE_MAX; mode++) { if (speed != port_link_modes[mode].speed) continue; if (duplex != port_link_modes[mode].duplex) continue; if (!(port_link_modes[mode].pr_mask & port->caps.supp_link_modes)) continue; if (type != port_link_modes[mode].port_type) continue; new_mode = mode; break; } if (new_mode == PRESTERA_LINK_MODE_MAX) return -EOPNOTSUPP; return prestera_hw_port_link_mode_set(port, new_mode); } static int prestera_port_speed_duplex_set(const struct ethtool_link_ksettings *ecmd, struct prestera_port *port) { u32 curr_mode; u8 duplex; u32 speed; int err; err = prestera_hw_port_link_mode_get(port, &curr_mode); if (err) return err; if (curr_mode >= PRESTERA_LINK_MODE_MAX) return -EINVAL; if (ecmd->base.duplex != DUPLEX_UNKNOWN) duplex = ecmd->base.duplex == DUPLEX_FULL ? PRESTERA_PORT_DUPLEX_FULL : PRESTERA_PORT_DUPLEX_HALF; else duplex = port_link_modes[curr_mode].duplex; if (ecmd->base.speed != SPEED_UNKNOWN) speed = ecmd->base.speed; else speed = port_link_modes[curr_mode].speed; return prestera_port_link_mode_set(port, speed, duplex, port->caps.type); } static int prestera_ethtool_set_link_ksettings(struct net_device *dev, const struct ethtool_link_ksettings *ecmd) { struct prestera_port *port = netdev_priv(dev); u64 adver_modes; u8 adver_fec; int err; err = prestera_port_type_set(ecmd, port); if (err) return err; if (port->caps.transceiver == PRESTERA_PORT_TCVR_COPPER) { err = prestera_port_mdix_set(ecmd, port); if (err) return err; } prestera_modes_from_eth(ecmd->link_modes.advertising, &adver_modes, &adver_fec, port->caps.type); err = prestera_port_autoneg_set(port, ecmd->base.autoneg == AUTONEG_ENABLE, adver_modes, adver_fec); if (err) return err; if (ecmd->base.autoneg == AUTONEG_DISABLE) { err = prestera_port_speed_duplex_set(ecmd, port); if (err) return err; } return 0; } static int prestera_ethtool_get_fecparam(struct net_device *dev, struct ethtool_fecparam *fecparam) { struct prestera_port *port = netdev_priv(dev); u8 active; u32 mode; int err; err = prestera_hw_port_fec_get(port, &active); if (err) return err; fecparam->fec = 0; for (mode = 0; mode < PRESTERA_PORT_FEC_MAX; mode++) { if ((port_fec_caps[mode].pr_fec & port->caps.supp_fec) == 0) continue; fecparam->fec |= port_fec_caps[mode].eth_fec; } if (active < PRESTERA_PORT_FEC_MAX) fecparam->active_fec = port_fec_caps[active].eth_fec; else fecparam->active_fec = ETHTOOL_FEC_AUTO; return 0; } static int prestera_ethtool_set_fecparam(struct net_device *dev, struct ethtool_fecparam *fecparam) { struct prestera_port *port = netdev_priv(dev); u8 fec, active; u32 mode; int err; if (port->autoneg) { netdev_err(dev, "FEC set is not allowed while autoneg is on\n"); return -EINVAL; } err = prestera_hw_port_fec_get(port, &active); if (err) return err; fec = PRESTERA_PORT_FEC_MAX; for (mode = 0; mode < PRESTERA_PORT_FEC_MAX; mode++) { if ((port_fec_caps[mode].eth_fec & fecparam->fec) && (port_fec_caps[mode].pr_fec & port->caps.supp_fec)) { fec = mode; break; } } if (fec == active) return 0; if (fec == PRESTERA_PORT_FEC_MAX) return -EOPNOTSUPP; return prestera_hw_port_fec_set(port, fec); } static int prestera_ethtool_get_sset_count(struct net_device *dev, int sset) { switch (sset) { case ETH_SS_STATS: return PRESTERA_STATS_CNT; default: return -EOPNOTSUPP; } } static void prestera_ethtool_get_strings(struct net_device *dev, u32 stringset, u8 *data) { if (stringset != ETH_SS_STATS) return; memcpy(data, prestera_cnt_name, sizeof(prestera_cnt_name)); } static void prestera_ethtool_get_stats(struct net_device *dev, struct ethtool_stats *stats, u64 *data) { struct prestera_port *port = netdev_priv(dev); struct prestera_port_stats *port_stats; port_stats = &port->cached_hw_stats.stats; memcpy(data, port_stats, sizeof(*port_stats)); } static int prestera_ethtool_nway_reset(struct net_device *dev) { struct prestera_port *port = netdev_priv(dev); if (netif_running(dev) && port->caps.transceiver == PRESTERA_PORT_TCVR_COPPER && port->caps.type == PRESTERA_PORT_TYPE_TP) return prestera_hw_port_autoneg_restart(port); return -EINVAL; } const struct ethtool_ops prestera_ethtool_ops = { .get_drvinfo = prestera_ethtool_get_drvinfo, .get_link_ksettings = prestera_ethtool_get_link_ksettings, .set_link_ksettings = prestera_ethtool_set_link_ksettings, .get_fecparam = prestera_ethtool_get_fecparam, .set_fecparam = prestera_ethtool_set_fecparam, .get_sset_count = prestera_ethtool_get_sset_count, .get_strings = prestera_ethtool_get_strings, .get_ethtool_stats = prestera_ethtool_get_stats, .get_link = ethtool_op_get_link, .nway_reset = prestera_ethtool_nway_reset };