diff options
Diffstat (limited to 'drivers')
-rw-r--r-- | drivers/net/ethernet/marvell/Kconfig | 2 | ||||
-rw-r--r-- | drivers/net/ethernet/marvell/mvneta.c | 600 | ||||
-rw-r--r-- | drivers/net/phy/Kconfig | 25 | ||||
-rw-r--r-- | drivers/net/phy/Makefile | 6 | ||||
-rw-r--r-- | drivers/net/phy/fixed_phy.c | 31 | ||||
-rw-r--r-- | drivers/net/phy/marvell10g.c | 54 | ||||
-rw-r--r-- | drivers/net/phy/mdio-i2c.c | 109 | ||||
-rw-r--r-- | drivers/net/phy/mdio-i2c.h | 19 | ||||
-rw-r--r-- | drivers/net/phy/phy-core.c | 180 | ||||
-rw-r--r-- | drivers/net/phy/phy.c | 217 | ||||
-rw-r--r-- | drivers/net/phy/phy_device.c | 15 | ||||
-rw-r--r-- | drivers/net/phy/phylink.c | 1434 | ||||
-rw-r--r-- | drivers/net/phy/sfp-bus.c | 473 | ||||
-rw-r--r-- | drivers/net/phy/sfp.c | 1168 | ||||
-rw-r--r-- | drivers/net/phy/sfp.h | 28 | ||||
-rw-r--r-- | drivers/pci/host/pci-mvebu.c | 20 |
16 files changed, 3905 insertions, 476 deletions
diff --git a/drivers/net/ethernet/marvell/Kconfig b/drivers/net/ethernet/marvell/Kconfig index da6fb825afea..ebe5c9148935 100644 --- a/drivers/net/ethernet/marvell/Kconfig +++ b/drivers/net/ethernet/marvell/Kconfig @@ -60,7 +60,7 @@ config MVNETA depends on ARCH_MVEBU || COMPILE_TEST depends on HAS_DMA select MVMDIO - select FIXED_PHY + select PHYLINK ---help--- This driver supports the network interface units in the Marvell ARMADA XP, ARMADA 370, ARMADA 38x and diff --git a/drivers/net/ethernet/marvell/mvneta.c b/drivers/net/ethernet/marvell/mvneta.c index 0aab74c2a209..e31fe97d4b2b 100644 --- a/drivers/net/ethernet/marvell/mvneta.c +++ b/drivers/net/ethernet/marvell/mvneta.c @@ -28,7 +28,7 @@ #include <linux/of_mdio.h> #include <linux/of_net.h> #include <linux/phy.h> -#include <linux/phy_fixed.h> +#include <linux/phylink.h> #include <linux/platform_device.h> #include <linux/skbuff.h> #include <net/hwbm.h> @@ -189,6 +189,7 @@ #define MVNETA_GMAC_CTRL_0 0x2c00 #define MVNETA_GMAC_MAX_RX_SIZE_SHIFT 2 #define MVNETA_GMAC_MAX_RX_SIZE_MASK 0x7ffc +#define MVNETA_GMAC0_PORT_1000BASE_X BIT(1) #define MVNETA_GMAC0_PORT_ENABLE BIT(0) #define MVNETA_GMAC_CTRL_2 0x2c08 #define MVNETA_GMAC2_INBAND_AN_ENABLE BIT(0) @@ -204,13 +205,19 @@ #define MVNETA_GMAC_TX_FLOW_CTRL_ENABLE BIT(5) #define MVNETA_GMAC_RX_FLOW_CTRL_ACTIVE BIT(6) #define MVNETA_GMAC_TX_FLOW_CTRL_ACTIVE BIT(7) +#define MVNETA_GMAC_AN_COMPLETE BIT(11) +#define MVNETA_GMAC_SYNC_OK BIT(14) #define MVNETA_GMAC_AUTONEG_CONFIG 0x2c0c #define MVNETA_GMAC_FORCE_LINK_DOWN BIT(0) #define MVNETA_GMAC_FORCE_LINK_PASS BIT(1) #define MVNETA_GMAC_INBAND_AN_ENABLE BIT(2) +#define MVNETA_GMAC_AN_BYPASS_ENABLE BIT(3) +#define MVNETA_GMAC_INBAND_RESTART_AN BIT(4) #define MVNETA_GMAC_CONFIG_MII_SPEED BIT(5) #define MVNETA_GMAC_CONFIG_GMII_SPEED BIT(6) #define MVNETA_GMAC_AN_SPEED_EN BIT(7) +#define MVNETA_GMAC_CONFIG_FLOW_CTRL BIT(8) +#define MVNETA_GMAC_ADVERT_SYM_FLOW_CTRL BIT(9) #define MVNETA_GMAC_AN_FLOW_CTRL_EN BIT(11) #define MVNETA_GMAC_CONFIG_FULL_DUPLEX BIT(12) #define MVNETA_GMAC_AN_DUPLEX_EN BIT(13) @@ -237,6 +244,12 @@ #define MVNETA_TXQ_TOKEN_SIZE_REG(q) (0x3e40 + ((q) << 2)) #define MVNETA_TXQ_TOKEN_SIZE_MAX 0x7fffffff +#define MVNETA_LPI_CTRL_0 0x2cc0 +#define MVNETA_LPI_CTRL_1 0x2cc4 +#define MVNETA_LPI_REQUEST_ENABLE BIT(0) +#define MVNETA_LPI_CTRL_2 0x2cc8 +#define MVNETA_LPI_STATUS 0x2ccc + #define MVNETA_CAUSE_TXQ_SENT_DESC_ALL_MASK 0xff /* Descriptor ring Macros */ @@ -316,6 +329,11 @@ #define MVNETA_RX_GET_BM_POOL_ID(rxd) \ (((rxd)->status & MVNETA_RXD_BM_POOL_MASK) >> MVNETA_RXD_BM_POOL_SHIFT) +enum { + ETHTOOL_STAT_EEE_WAKEUP, + ETHTOOL_MAX_STATS, +}; + struct mvneta_statistic { unsigned short offset; unsigned short type; @@ -324,6 +342,7 @@ struct mvneta_statistic { #define T_REG_32 32 #define T_REG_64 64 +#define T_SW 1 static const struct mvneta_statistic mvneta_statistics[] = { { 0x3000, T_REG_64, "good_octets_received", }, @@ -358,6 +377,7 @@ static const struct mvneta_statistic mvneta_statistics[] = { { 0x304c, T_REG_32, "broadcast_frames_sent", }, { 0x3054, T_REG_32, "fc_sent", }, { 0x300c, T_REG_32, "internal_mac_transmit_err", }, + { ETHTOOL_STAT_EEE_WAKEUP, T_SW, "eee_wakeup_errors", }, }; struct mvneta_pcpu_stats { @@ -410,20 +430,20 @@ struct mvneta_port { u16 tx_ring_size; u16 rx_ring_size; - struct mii_bus *mii_bus; phy_interface_t phy_interface; - struct device_node *phy_node; - unsigned int link; - unsigned int duplex; - unsigned int speed; + struct device_node *dn; unsigned int tx_csum_limit; - unsigned int use_inband_status:1; + struct phylink *phylink; struct mvneta_bm *bm_priv; struct mvneta_bm_pool *pool_long; struct mvneta_bm_pool *pool_short; int bm_win_id; + bool eee_enabled; + bool eee_active; + bool tx_lpi_enabled; + u64 ethtool_stats[ARRAY_SIZE(mvneta_statistics)]; u32 indir[MVNETA_RSS_LU_TABLE_SIZE]; @@ -1273,44 +1293,6 @@ static void mvneta_set_other_mcast_table(struct mvneta_port *pp, int queue) mvreg_write(pp, MVNETA_DA_FILT_OTH_MCAST + offset, val); } -static void mvneta_set_autoneg(struct mvneta_port *pp, int enable) -{ - u32 val; - - if (enable) { - val = mvreg_read(pp, MVNETA_GMAC_AUTONEG_CONFIG); - val &= ~(MVNETA_GMAC_FORCE_LINK_PASS | - MVNETA_GMAC_FORCE_LINK_DOWN | - MVNETA_GMAC_AN_FLOW_CTRL_EN); - val |= MVNETA_GMAC_INBAND_AN_ENABLE | - MVNETA_GMAC_AN_SPEED_EN | - MVNETA_GMAC_AN_DUPLEX_EN; - mvreg_write(pp, MVNETA_GMAC_AUTONEG_CONFIG, val); - - val = mvreg_read(pp, MVNETA_GMAC_CLOCK_DIVIDER); - val |= MVNETA_GMAC_1MS_CLOCK_ENABLE; - mvreg_write(pp, MVNETA_GMAC_CLOCK_DIVIDER, val); - - val = mvreg_read(pp, MVNETA_GMAC_CTRL_2); - val |= MVNETA_GMAC2_INBAND_AN_ENABLE; - mvreg_write(pp, MVNETA_GMAC_CTRL_2, val); - } else { - val = mvreg_read(pp, MVNETA_GMAC_AUTONEG_CONFIG); - val &= ~(MVNETA_GMAC_INBAND_AN_ENABLE | - MVNETA_GMAC_AN_SPEED_EN | - MVNETA_GMAC_AN_DUPLEX_EN); - mvreg_write(pp, MVNETA_GMAC_AUTONEG_CONFIG, val); - - val = mvreg_read(pp, MVNETA_GMAC_CLOCK_DIVIDER); - val &= ~MVNETA_GMAC_1MS_CLOCK_ENABLE; - mvreg_write(pp, MVNETA_GMAC_CLOCK_DIVIDER, val); - - val = mvreg_read(pp, MVNETA_GMAC_CTRL_2); - val &= ~MVNETA_GMAC2_INBAND_AN_ENABLE; - mvreg_write(pp, MVNETA_GMAC_CTRL_2, val); - } -} - static void mvneta_percpu_unmask_interrupt(void *arg) { struct mvneta_port *pp = arg; @@ -1463,7 +1445,6 @@ static void mvneta_defaults_set(struct mvneta_port *pp) val &= ~MVNETA_PHY_POLLING_ENABLE; mvreg_write(pp, MVNETA_UNIT_CONTROL, val); - mvneta_set_autoneg(pp, pp->use_inband_status); mvneta_set_ucast_table(pp, -1); mvneta_set_special_mcast_table(pp, -1); mvneta_set_other_mcast_table(pp, -1); @@ -2688,26 +2669,11 @@ static irqreturn_t mvneta_percpu_isr(int irq, void *dev_id) return IRQ_HANDLED; } -static int mvneta_fixed_link_update(struct mvneta_port *pp, - struct phy_device *phy) +static void mvneta_link_change(struct mvneta_port *pp) { - struct fixed_phy_status status; - struct fixed_phy_status changed = {}; u32 gmac_stat = mvreg_read(pp, MVNETA_GMAC_STATUS); - status.link = !!(gmac_stat & MVNETA_GMAC_LINK_UP); - if (gmac_stat & MVNETA_GMAC_SPEED_1000) - status.speed = SPEED_1000; - else if (gmac_stat & MVNETA_GMAC_SPEED_100) - status.speed = SPEED_100; - else - status.speed = SPEED_10; - status.duplex = !!(gmac_stat & MVNETA_GMAC_FULL_DUPLEX); - changed.link = 1; - changed.speed = 1; - changed.duplex = 1; - fixed_phy_update_state(phy, &status, &changed); - return 0; + phylink_mac_change(pp->phylink, !!(gmac_stat & MVNETA_GMAC_LINK_UP)); } /* NAPI handler @@ -2723,7 +2689,6 @@ static int mvneta_poll(struct napi_struct *napi, int budget) u32 cause_rx_tx; int rx_queue; struct mvneta_port *pp = netdev_priv(napi->dev); - struct net_device *ndev = pp->dev; struct mvneta_pcpu_port *port = this_cpu_ptr(pp->ports); if (!netif_running(pp->dev)) { @@ -2737,12 +2702,10 @@ static int mvneta_poll(struct napi_struct *napi, int budget) u32 cause_misc = mvreg_read(pp, MVNETA_INTR_MISC_CAUSE); mvreg_write(pp, MVNETA_INTR_MISC_CAUSE, 0); - if (pp->use_inband_status && (cause_misc & - (MVNETA_CAUSE_PHY_STATUS_CHANGE | - MVNETA_CAUSE_LINK_CHANGE | - MVNETA_CAUSE_PSC_SYNC_CHANGE))) { - mvneta_fixed_link_update(pp, ndev->phydev); - } + + if (cause_misc & (MVNETA_CAUSE_PHY_STATUS_CHANGE | + MVNETA_CAUSE_LINK_CHANGE)) + mvneta_link_change(pp); } /* Release Tx descriptors */ @@ -3056,7 +3019,6 @@ static int mvneta_setup_txqs(struct mvneta_port *pp) static void mvneta_start_dev(struct mvneta_port *pp) { int cpu; - struct net_device *ndev = pp->dev; mvneta_max_rx_size_set(pp, pp->pkt_size); mvneta_txq_max_tx_size_set(pp, pp->pkt_size); @@ -3081,19 +3043,17 @@ static void mvneta_start_dev(struct mvneta_port *pp) mvreg_write(pp, MVNETA_INTR_MISC_MASK, MVNETA_CAUSE_PHY_STATUS_CHANGE | - MVNETA_CAUSE_LINK_CHANGE | - MVNETA_CAUSE_PSC_SYNC_CHANGE); + MVNETA_CAUSE_LINK_CHANGE); - phy_start(ndev->phydev); + phylink_start(pp->phylink); netif_tx_start_all_queues(pp->dev); } static void mvneta_stop_dev(struct mvneta_port *pp) { unsigned int cpu; - struct net_device *ndev = pp->dev; - phy_stop(ndev->phydev); + phylink_stop(pp->phylink); if (!pp->neta_armada3700) { for_each_online_cpu(cpu) { @@ -3247,103 +3207,232 @@ static int mvneta_set_mac_addr(struct net_device *dev, void *addr) return 0; } -static void mvneta_adjust_link(struct net_device *ndev) +static void mvneta_validate(struct net_device *ndev, unsigned long *supported, + struct phylink_link_state *state) +{ + __ETHTOOL_DECLARE_LINK_MODE_MASK(mask) = { 0, }; + + /* Allow all the expected bits */ + phylink_set(mask, Autoneg); + phylink_set_port_modes(mask); + + /* Asymmetric pause is unsupported */ + phylink_set(mask, Pause); + /* Half-duplex at speeds higher than 100Mbit is unsupported */ + phylink_set(mask, 1000baseT_Full); + phylink_set(mask, 1000baseX_Full); + + if (state->interface != PHY_INTERFACE_MODE_1000BASEX) { + /* 10M and 100M are only supported in non-802.3z mode */ + phylink_set(mask, 10baseT_Half); + phylink_set(mask, 10baseT_Full); + phylink_set(mask, 100baseT_Half); + phylink_set(mask, 100baseT_Full); + } + + bitmap_and(supported, supported, mask, + __ETHTOOL_LINK_MODE_MASK_NBITS); + bitmap_and(state->advertising, state->advertising, mask, + __ETHTOOL_LINK_MODE_MASK_NBITS); +} + +static int mvneta_mac_link_state(struct net_device *ndev, + struct phylink_link_state *state) { struct mvneta_port *pp = netdev_priv(ndev); - struct phy_device *phydev = ndev->phydev; - int status_change = 0; + u32 gmac_stat; - if (phydev->link) { - if ((pp->speed != phydev->speed) || - (pp->duplex != phydev->duplex)) { - u32 val; + gmac_stat = mvreg_read(pp, MVNETA_GMAC_STATUS); - val = mvreg_read(pp, MVNETA_GMAC_AUTONEG_CONFIG); - val &= ~(MVNETA_GMAC_CONFIG_MII_SPEED | - MVNETA_GMAC_CONFIG_GMII_SPEED | - MVNETA_GMAC_CONFIG_FULL_DUPLEX); + if (gmac_stat & MVNETA_GMAC_SPEED_1000) + state->speed = SPEED_1000; + else if (gmac_stat & MVNETA_GMAC_SPEED_100) + state->speed = SPEED_100; + else + state->speed = SPEED_10; - if (phydev->duplex) - val |= MVNETA_GMAC_CONFIG_FULL_DUPLEX; + state->an_complete = !!(gmac_stat & MVNETA_GMAC_AN_COMPLETE); + state->link = !!(gmac_stat & MVNETA_GMAC_LINK_UP); + state->duplex = !!(gmac_stat & MVNETA_GMAC_FULL_DUPLEX); - if (phydev->speed == SPEED_1000) - val |= MVNETA_GMAC_CONFIG_GMII_SPEED; - else if (phydev->speed == SPEED_100) - val |= MVNETA_GMAC_CONFIG_MII_SPEED; + state->pause = 0; + if (gmac_stat & MVNETA_GMAC_RX_FLOW_CTRL_ENABLE) + state->pause |= MLO_PAUSE_RX; + if (gmac_stat & MVNETA_GMAC_TX_FLOW_CTRL_ENABLE) + state->pause |= MLO_PAUSE_TX; - mvreg_write(pp, MVNETA_GMAC_AUTONEG_CONFIG, val); + return 1; +} - pp->duplex = phydev->duplex; - pp->speed = phydev->speed; - } - } +static void mvneta_mac_an_restart(struct net_device *ndev) +{ + struct mvneta_port *pp = netdev_priv(ndev); + u32 gmac_an = mvreg_read(pp, MVNETA_GMAC_AUTONEG_CONFIG); - if (phydev->link != pp->link) { - if (!phydev->link) { - pp->duplex = -1; - pp->speed = 0; - } + mvreg_write(pp, MVNETA_GMAC_AUTONEG_CONFIG, + gmac_an | MVNETA_GMAC_INBAND_RESTART_AN); + mvreg_write(pp, MVNETA_GMAC_AUTONEG_CONFIG, + gmac_an & ~MVNETA_GMAC_INBAND_RESTART_AN); +} - pp->link = phydev->link; - status_change = 1; +static void mvneta_mac_config(struct net_device *ndev, unsigned int mode, + const struct phylink_link_state *state) +{ + struct mvneta_port *pp = netdev_priv(ndev); + u32 new_ctrl0, gmac_ctrl0 = mvreg_read(pp, MVNETA_GMAC_CTRL_0); + u32 new_ctrl2, gmac_ctrl2 = mvreg_read(pp, MVNETA_GMAC_CTRL_2); + u32 new_clk, gmac_clk = mvreg_read(pp, MVNETA_GMAC_CLOCK_DIVIDER); + u32 new_an, gmac_an = mvreg_read(pp, MVNETA_GMAC_AUTONEG_CONFIG); + + new_ctrl0 = gmac_ctrl0 & ~MVNETA_GMAC0_PORT_1000BASE_X; + new_ctrl2 = gmac_ctrl2 & ~MVNETA_GMAC2_INBAND_AN_ENABLE; + new_clk = gmac_clk & ~MVNETA_GMAC_1MS_CLOCK_ENABLE; + new_an = gmac_an & ~(MVNETA_GMAC_INBAND_AN_ENABLE | + MVNETA_GMAC_INBAND_RESTART_AN | + MVNETA_GMAC_CONFIG_MII_SPEED | + MVNETA_GMAC_CONFIG_GMII_SPEED | + MVNETA_GMAC_AN_SPEED_EN | + MVNETA_GMAC_ADVERT_SYM_FLOW_CTRL | + MVNETA_GMAC_CONFIG_FLOW_CTRL | + MVNETA_GMAC_AN_FLOW_CTRL_EN | + MVNETA_GMAC_CONFIG_FULL_DUPLEX | + MVNETA_GMAC_AN_DUPLEX_EN); + + if (phylink_test(state->advertising, Pause)) + new_an |= MVNETA_GMAC_ADVERT_SYM_FLOW_CTRL; + if (state->pause & MLO_PAUSE_TXRX_MASK) + new_an |= MVNETA_GMAC_CONFIG_FLOW_CTRL; + + if (!phylink_autoneg_inband(mode)) { + /* Phy or fixed speed */ + if (state->duplex) + new_an |= MVNETA_GMAC_CONFIG_FULL_DUPLEX; + + if (state->speed == SPEED_1000) + new_an |= MVNETA_GMAC_CONFIG_GMII_SPEED; + else if (state->speed == SPEED_100) + new_an |= MVNETA_GMAC_CONFIG_MII_SPEED; + } else if (state->interface == PHY_INTERFACE_MODE_SGMII) { + /* SGMII mode receives the state from the PHY */ + new_ctrl2 |= MVNETA_GMAC2_INBAND_AN_ENABLE; + new_clk |= MVNETA_GMAC_1MS_CLOCK_ENABLE; + new_an = (new_an & ~(MVNETA_GMAC_FORCE_LINK_DOWN | + MVNETA_GMAC_FORCE_LINK_PASS)) | + MVNETA_GMAC_INBAND_AN_ENABLE | + MVNETA_GMAC_AN_SPEED_EN | + MVNETA_GMAC_AN_DUPLEX_EN; + } else { + /* 802.3z negotiation - only 1000base-X */ + new_ctrl0 |= MVNETA_GMAC0_PORT_1000BASE_X; + new_clk |= MVNETA_GMAC_1MS_CLOCK_ENABLE; + new_an = (new_an & ~(MVNETA_GMAC_FORCE_LINK_DOWN | + MVNETA_GMAC_FORCE_LINK_PASS)) | + MVNETA_GMAC_INBAND_AN_ENABLE | + MVNETA_GMAC_CONFIG_GMII_SPEED | + /* The MAC only supports FD mode */ + MVNETA_GMAC_CONFIG_FULL_DUPLEX; + + if (state->pause & MLO_PAUSE_AN && state->an_enabled) + new_an |= MVNETA_GMAC_AN_FLOW_CTRL_EN; } - if (status_change) { - if (phydev->link) { - if (!pp->use_inband_status) { - u32 val = mvreg_read(pp, - MVNETA_GMAC_AUTONEG_CONFIG); - val &= ~MVNETA_GMAC_FORCE_LINK_DOWN; - val |= MVNETA_GMAC_FORCE_LINK_PASS; - mvreg_write(pp, MVNETA_GMAC_AUTONEG_CONFIG, - val); - } - mvneta_port_up(pp); - } else { - if (!pp->use_inband_status) { - u32 val = mvreg_read(pp, - MVNETA_GMAC_AUTONEG_CONFIG); - val &= ~MVNETA_GMAC_FORCE_LINK_PASS; - val |= MVNETA_GMAC_FORCE_LINK_DOWN; - mvreg_write(pp, MVNETA_GMAC_AUTONEG_CONFIG, - val); - } - mvneta_port_down(pp); - } - phy_print_status(phydev); + /* Armada 370 documentation says we can only change the port mode + * and in-band enable when the link is down, so force it down + * while making these changes. We also do this for GMAC_CTRL2 */ + if ((new_ctrl0 ^ gmac_ctrl0) & MVNETA_GMAC0_PORT_1000BASE_X || + (new_ctrl2 ^ gmac_ctrl2) & MVNETA_GMAC2_INBAND_AN_ENABLE || + (new_an ^ gmac_an) & MVNETA_GMAC_INBAND_AN_ENABLE) { + mvreg_write(pp, MVNETA_GMAC_AUTONEG_CONFIG, + (gmac_an & ~MVNETA_GMAC_FORCE_LINK_PASS) | + MVNETA_GMAC_FORCE_LINK_DOWN); } + + if (new_ctrl0 != gmac_ctrl0) + mvreg_write(pp, MVNETA_GMAC_CTRL_0, new_ctrl0); + if (new_ctrl2 != gmac_ctrl2) + mvreg_write(pp, MVNETA_GMAC_CTRL_2, new_ctrl2); + if (new_clk != gmac_clk) + mvreg_write(pp, MVNETA_GMAC_CLOCK_DIVIDER, new_clk); + if (new_an != gmac_an) + mvreg_write(pp, MVNETA_GMAC_AUTONEG_CONFIG, new_an); } -static int mvneta_mdio_probe(struct mvneta_port *pp) +static void mvneta_set_eee(struct mvneta_port *pp, bool enable) { - struct phy_device *phy_dev; - struct ethtool_wolinfo wol = { .cmd = ETHTOOL_GWOL }; + u32 lpi_ctl1; + + lpi_ctl1 = mvreg_read(pp, MVNETA_LPI_CTRL_1); + if (enable) + lpi_ctl1 |= MVNETA_LPI_REQUEST_ENABLE; + else + lpi_ctl1 &= ~MVNETA_LPI_REQUEST_ENABLE; + mvreg_write(pp, MVNETA_LPI_CTRL_1, lpi_ctl1); +} + +static void mvneta_mac_link_down(struct net_device *ndev, unsigned int mode) +{ + struct mvneta_port *pp = netdev_priv(ndev); + u32 val; + + mvneta_port_down(pp); - phy_dev = of_phy_connect(pp->dev, pp->phy_node, mvneta_adjust_link, 0, - pp->phy_interface); - if (!phy_dev) { - netdev_err(pp->dev, "could not find the PHY\n"); - return -ENODEV; + if (!phylink_autoneg_inband(mode)) { + val = mvreg_read(pp, MVNETA_GMAC_AUTONEG_CONFIG); + val &= ~MVNETA_GMAC_FORCE_LINK_PASS; + val |= MVNETA_GMAC_FORCE_LINK_DOWN; + mvreg_write(pp, MVNETA_GMAC_AUTONEG_CONFIG, val); } - phy_ethtool_get_wol(phy_dev, &wol); - device_set_wakeup_capable(&pp->dev->dev, !!wol.supported); + pp->eee_active = false; + mvneta_set_eee(pp, false); +} - phy_dev->supported &= PHY_GBIT_FEATURES; - phy_dev->advertising = phy_dev->supported; +static void mvneta_mac_link_up(struct net_device *ndev, unsigned int mode, + struct phy_device *phy) +{ + struct mvneta_port *pp = netdev_priv(ndev); + u32 val; + + if (!phylink_autoneg_inband(mode)) { + val = mvreg_read(pp, MVNETA_GMAC_AUTONEG_CONFIG); + val &= ~MVNETA_GMAC_FORCE_LINK_DOWN; + val |= MVNETA_GMAC_FORCE_LINK_PASS; + mvreg_write(pp, MVNETA_GMAC_AUTONEG_CONFIG, val); + } - pp->link = 0; - pp->duplex = 0; - pp->speed = 0; + mvneta_port_up(pp); - return 0; + if (phy && pp->eee_enabled) { + pp->eee_active = phy_init_eee(phy, 0) >= 0; + mvneta_set_eee(pp, pp->eee_active && pp->tx_lpi_enabled); + } } -static void mvneta_mdio_remove(struct mvneta_port *pp) +static const struct phylink_mac_ops mvneta_phylink_ops = { + .validate = mvneta_validate, + .mac_link_state = mvneta_mac_link_state, + .mac_an_restart = mvneta_mac_an_restart, + .mac_config = mvneta_mac_config, + .mac_link_down = mvneta_mac_link_down, + .mac_link_up = mvneta_mac_link_up, +}; + +static int mvneta_mdio_probe(struct mvneta_port *pp) { - struct net_device *ndev = pp->dev; + struct ethtool_wolinfo wol = { .cmd = ETHTOOL_GWOL }; + int err = phylink_of_phy_connect(pp->phylink, pp->dn); + if (err) + netdev_err(pp->dev, "could not attach PHY\n"); - phy_disconnect(ndev->phydev); + phylink_ethtool_get_wol(pp->phylink, &wol); + device_set_wakeup_capable(&pp->dev->dev, !!wol.supported); + + return err; +} + +static void mvneta_mdio_remove(struct mvneta_port *pp) +{ + phylink_disconnect_phy(pp->phylink); } /* Electing a CPU must be done in an atomic way: it should be done @@ -3451,8 +3540,7 @@ static int mvneta_cpu_online(unsigned int cpu, struct hlist_node *node) on_each_cpu(mvneta_percpu_unmask_interrupt, pp, true); mvreg_write(pp, MVNETA_INTR_MISC_MASK, MVNETA_CAUSE_PHY_STATUS_CHANGE | - MVNETA_CAUSE_LINK_CHANGE | - MVNETA_CAUSE_PSC_SYNC_CHANGE); + MVNETA_CAUSE_LINK_CHANGE); netif_tx_start_all_queues(pp->dev); spin_unlock(&pp->lock); return 0; @@ -3493,8 +3581,7 @@ static int mvneta_cpu_dead(unsigned int cpu, struct hlist_node *node) on_each_cpu(mvneta_percpu_unmask_interrupt, pp, true); mvreg_write(pp, MVNETA_INTR_MISC_MASK, MVNETA_CAUSE_PHY_STATUS_CHANGE | - MVNETA_CAUSE_LINK_CHANGE | - MVNETA_CAUSE_PSC_SYNC_CHANGE); + MVNETA_CAUSE_LINK_CHANGE); netif_tx_start_all_queues(pp->dev); return 0; } @@ -3622,10 +3709,9 @@ static int mvneta_stop(struct net_device *dev) static int mvneta_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd) { - if (!dev->phydev) - return -ENOTSUPP; + struct mvneta_port *pp = netdev_priv(dev); - return phy_mii_ioctl(dev->phydev, ifr, cmd); + return phylink_mii_ioctl(pp->phylink, ifr, cmd); } /* Ethtool methods */ @@ -3636,44 +3722,25 @@ mvneta_ethtool_set_link_ksettings(struct net_device *ndev, const struct ethtool_link_ksettings *cmd) { struct mvneta_port *pp = netdev_priv(ndev); - struct phy_device *phydev = ndev->phydev; - - if (!phydev) - return -ENODEV; - - if ((cmd->base.autoneg == AUTONEG_ENABLE) != pp->use_inband_status) { - u32 val; - - mvneta_set_autoneg(pp, cmd->base.autoneg == AUTONEG_ENABLE); - if (cmd->base.autoneg == AUTONEG_DISABLE) { - val = mvreg_read(pp, MVNETA_GMAC_AUTONEG_CONFIG); - val &= ~(MVNETA_GMAC_CONFIG_MII_SPEED | - MVNETA_GMAC_CONFIG_GMII_SPEED | - MVNETA_GMAC_CONFIG_FULL_DUPLEX); - - if (phydev->duplex) - val |= MVNETA_GMAC_CONFIG_FULL_DUPLEX; - - if (phydev->speed == SPEED_1000) - val |= MVNETA_GMAC_CONFIG_GMII_SPEED; - else if (phydev->speed == SPEED_100) - val |= MVNETA_GMAC_CONFIG_MII_SPEED; + return phylink_ethtool_ksettings_set(pp->phylink, cmd); +} - mvreg_write(pp, MVNETA_GMAC_AUTONEG_CONFIG, val); - } +/* Get link ksettings for ethtools */ +static int +mvneta_ethtool_get_link_ksettings(struct net_device *ndev, + struct ethtool_link_ksettings *cmd) +{ + struct mvneta_port *pp = netdev_priv(ndev); - pp->use_inband_status = (cmd->base.autoneg == AUTONEG_ENABLE); - netdev_info(pp->dev, "autoneg status set to %i\n", - pp->use_inband_status); + return phylink_ethtool_ksettings_get(pp->phylink, cmd); +} - if (netif_running(ndev)) { - mvneta_port_down(pp); - mvneta_port_up(pp); - } - } +static int mvneta_ethtool_nway_reset(struct net_device *dev) +{ + struct mvneta_port *pp = netdev_priv(dev); - return phy_ethtool_ksettings_set(ndev->phydev, cmd); + return phylink_ethtool_nway_reset(pp->phylink); } /* Set interrupt coalescing for ethtools */ @@ -3765,6 +3832,22 @@ static int mvneta_ethtool_set_ringparam(struct net_device *dev, return 0; } +static void mvneta_ethtool_get_pauseparam(struct net_device *dev, + struct ethtool_pauseparam *pause) +{ + struct mvneta_port *pp = netdev_priv(dev); + + phylink_ethtool_get_pauseparam(pp->phylink, pause); +} + +static int mvneta_ethtool_set_pauseparam(struct net_device *dev, + struct ethtool_pauseparam *pause) +{ + struct mvneta_port *pp = netdev_priv(dev); + + return phylink_ethtool_set_pauseparam(pp->phylink, pause); +} + static void mvneta_ethtool_get_strings(struct net_device *netdev, u32 sset, u8 *data) { @@ -3781,26 +3864,35 @@ static void mvneta_ethtool_update_stats(struct mvneta_port *pp) { const struct mvneta_statistic *s; void __iomem *base = pp->base; - u32 high, low, val; - u64 val64; + u32 high, low; + u64 val; int i; for (i = 0, s = mvneta_statistics; s < mvneta_statistics + ARRAY_SIZE(mvneta_statistics); s++, i++) { + val = 0; + switch (s->type) { case T_REG_32: val = readl_relaxed(base + s->offset); - pp->ethtool_stats[i] += val; break; case T_REG_64: /* Docs say to read low 32-bit then high */ low = readl_relaxed(base + s->offset); high = readl_relaxed(base + s->offset + 4); - val64 = (u64)high << 32 | low; - pp->ethtool_stats[i] += val64; + val = (u64)high << 32 | low; + break; + case T_SW: + switch (s->offset) { + case ETHTOOL_STAT_EEE_WAKEUP: + val = phylink_get_eee_err(pp->phylink); + break; + } break; } + + pp->ethtool_stats[i] += val; } } @@ -3935,28 +4027,65 @@ static int mvneta_ethtool_get_rxfh(struct net_device *dev, u32 *indir, u8 *key, static void mvneta_ethtool_get_wol(struct net_device *dev, struct ethtool_wolinfo *wol) { - wol->supported = 0; - wol->wolopts = 0; + struct mvneta_port *pp = netdev_priv(dev); - if (dev->phydev) - phy_ethtool_get_wol(dev->phydev, wol); + phylink_ethtool_get_wol(pp->phylink, wol); } static int mvneta_ethtool_set_wol(struct net_device *dev, struct ethtool_wolinfo *wol) { + struct mvneta_port *pp = netdev_priv(dev); int ret; - if (!dev->phydev) - return -EOPNOTSUPP; - - ret = phy_ethtool_set_wol(dev->phydev, wol); + ret = phylink_ethtool_set_wol(pp->phylink, wol); if (!ret) device_set_wakeup_enable(&dev->dev, !!wol->wolopts); return ret; } +static int mvneta_ethtool_get_eee(struct net_device *dev, + struct ethtool_eee *eee) +{ + struct mvneta_port *pp = netdev_priv(dev); + u32 lpi_ctl0; + + lpi_ctl0 = mvreg_read(pp, MVNETA_LPI_CTRL_0); + + eee->eee_enabled = pp->eee_enabled; + eee->eee_active = pp->eee_active; + eee->tx_lpi_enabled = pp->tx_lpi_enabled; + eee->tx_lpi_timer = (lpi_ctl0) >> 8; // * scale; + + return phylink_ethtool_get_eee(pp->phylink, eee); +} + +static int mvneta_ethtool_set_eee(struct net_device *dev, + struct ethtool_eee *eee) +{ + struct mvneta_port *pp = netdev_priv(dev); + u32 lpi_ctl0; + + /* The Armada 37x documents do not give limits for this other than + * it being an 8-bit register. */ + if (eee->tx_lpi_enabled && + (eee->tx_lpi_timer < 0 || eee->tx_lpi_timer > 255)) + return -EINVAL; + + lpi_ctl0 = mvreg_read(pp, MVNETA_LPI_CTRL_0); + lpi_ctl0 &= ~(0xff << 8); + lpi_ctl0 |= eee->tx_lpi_timer << 8; + mvreg_write(pp, MVNETA_LPI_CTRL_0, lpi_ctl0); + + pp->eee_enabled = eee->eee_enabled; + pp->tx_lpi_enabled = eee->tx_lpi_enabled; + + mvneta_set_eee(pp, eee->tx_lpi_enabled && eee->eee_enabled); + + return phylink_ethtool_set_eee(pp->phylink, eee); +} + static const struct net_device_ops mvneta_netdev_ops = { .ndo_open = mvneta_open, .ndo_stop = mvneta_stop, @@ -3970,13 +4099,15 @@ static const struct net_device_ops mvneta_netdev_ops = { }; static const struct ethtool_ops mvneta_eth_tool_ops = { - .nway_reset = phy_ethtool_nway_reset, + .nway_reset = mvneta_ethtool_nway_reset, .get_link = ethtool_op_get_link, .set_coalesce = mvneta_ethtool_set_coalesce, .get_coalesce = mvneta_ethtool_get_coalesce, .get_drvinfo = mvneta_ethtool_get_drvinfo, .get_ringparam = mvneta_ethtool_get_ringparam, .set_ringparam = mvneta_ethtool_set_ringparam, + .get_pauseparam = mvneta_ethtool_get_pauseparam, + .set_pauseparam = mvneta_ethtool_set_pauseparam, .get_strings = mvneta_ethtool_get_strings, .get_ethtool_stats = mvneta_ethtool_get_stats, .get_sset_count = mvneta_ethtool_get_sset_count, @@ -3984,10 +4115,12 @@ static const struct ethtool_ops mvneta_eth_tool_ops = { .get_rxnfc = mvneta_ethtool_get_rxnfc, .get_rxfh = mvneta_ethtool_get_rxfh, .set_rxfh = mvneta_ethtool_set_rxfh, - .get_link_ksettings = phy_ethtool_get_link_ksettings, + .get_link_ksettings = mvneta_ethtool_get_link_ksettings, .set_link_ksettings = mvneta_ethtool_set_link_ksettings, .get_wol = mvneta_ethtool_get_wol, .set_wol = mvneta_ethtool_set_wol, + .get_eee = mvneta_ethtool_get_eee, + .set_eee = mvneta_ethtool_set_eee, }; /* Initialize hw */ @@ -4132,14 +4265,13 @@ static int mvneta_probe(struct platform_device *pdev) { struct resource *res; struct device_node *dn = pdev->dev.of_node; - struct device_node *phy_node; struct device_node *bm_node; struct mvneta_port *pp; struct net_device *dev; + struct phylink *phylink; const char *dt_mac_addr; char hw_mac_addr[ETH_ALEN]; const char *mac_from; - const char *managed; int tx_csum_limit; int phy_mode; int err; @@ -4155,31 +4287,11 @@ static int mvneta_probe(struct platform_device *pdev) goto err_free_netdev; } - phy_node = of_parse_phandle(dn, "phy", 0); - if (!phy_node) { - if (!of_phy_is_fixed_link(dn)) { - dev_err(&pdev->dev, "no PHY specified\n"); - err = -ENODEV; - goto err_free_irq; - } - - err = of_phy_register_fixed_link(dn); - if (err < 0) { - dev_err(&pdev->dev, "cannot register fixed PHY\n"); - goto err_free_irq; - } - - /* In the case of a fixed PHY, the DT node associated - * to the PHY is the Ethernet MAC DT node. - */ - phy_node = of_node_get(dn); - } - phy_mode = of_get_phy_mode(dn); if (phy_mode < 0) { dev_err(&pdev->dev, "incorrect phy-mode\n"); err = -EINVAL; - goto err_put_phy_node; + goto err_free_irq; } dev->tx_queue_len = MVNETA_MAX_TXD; @@ -4190,12 +4302,8 @@ static int mvneta_probe(struct platform_device *pdev) pp = netdev_priv(dev); spin_lock_init(&pp->lock); - pp->phy_node = phy_node; pp->phy_interface = phy_mode; - - err = of_property_read_string(dn, "managed", &managed); - pp->use_inband_status = (err == 0 && - strcmp(managed, "in-band-status") == 0); + pp->dn = dn; pp->rxq_def = rxq_def; @@ -4217,7 +4325,7 @@ static int mvneta_probe(struct platform_device *pdev) pp->clk = devm_clk_get(&pdev->dev, NULL); if (IS_ERR(pp->clk)) { err = PTR_ERR(pp->clk); - goto err_put_phy_node; + goto err_free_irq; } clk_prepare_enable(pp->clk); @@ -4343,6 +4451,14 @@ static int mvneta_probe(struct platform_device *pdev) /* 9676 == 9700 - 20 and rounding to 8 */ dev->max_mtu = 9676; + phylink = phylink_create(dev, dn, phy_mode, &mvneta_phylink_ops); + if (IS_ERR(phylink)) { + err = PTR_ERR(phylink); + goto err_free_stats; + } + + pp->phylink = phylink; + err = register_netdev(dev); if (err < 0) { dev_err(&pdev->dev, "failed to register\n"); @@ -4354,14 +4470,6 @@ static int mvneta_probe(struct platform_device *pdev) platform_set_drvdata(pdev, pp->dev); - if (pp->use_inband_status) { - struct phy_device *phy = of_phy_find_device(dn); - - mvneta_fixed_link_update(pp, phy); - - put_device(&phy->mdio.dev); - } - return 0; err_netdev: @@ -4372,16 +4480,14 @@ err_netdev: 1 << pp->id); } err_free_stats: + if (pp->phylink) + phylink_destroy(pp->phylink); free_percpu(pp->stats); err_free_ports: free_percpu(pp->ports); err_clk: clk_disable_unprepare(pp->clk_bus); clk_disable_unprepare(pp->clk); -err_put_phy_node: - of_node_put(phy_node); - if (of_phy_is_fixed_link(dn)) - of_phy_deregister_fixed_link(dn); err_free_irq: irq_dispose_mapping(dev->irq); err_free_netdev: @@ -4393,7 +4499,6 @@ err_free_netdev: static int mvneta_remove(struct platform_device *pdev) { struct net_device *dev = platform_get_drvdata(pdev); - struct device_node *dn = pdev->dev.of_node; struct mvneta_port *pp = netdev_priv(dev); unregister_netdev(dev); @@ -4401,10 +4506,8 @@ static int mvneta_remove(struct platform_device *pdev) clk_disable_unprepare(pp->clk); free_percpu(pp->ports); free_percpu(pp->stats); - if (of_phy_is_fixed_link(dn)) - of_phy_deregister_fixed_link(dn); irq_dispose_mapping(dev->irq); - of_node_put(pp->phy_node); + phylink_destroy(pp->phylink); free_netdev(dev); if (pp->bm_priv) { @@ -4456,9 +4559,6 @@ static int mvneta_resume(struct device *device) return err; } - if (pp->use_inband_status) - mvneta_fixed_link_update(pp, dev->phydev); - netif_device_attach(dev); if (netif_running(dev)) { mvneta_open(dev); diff --git a/drivers/net/phy/Kconfig b/drivers/net/phy/Kconfig index 928fd892f167..bf73969a9d2b 100644 --- a/drivers/net/phy/Kconfig +++ b/drivers/net/phy/Kconfig @@ -106,6 +106,16 @@ config MDIO_HISI_FEMAC This module provides a driver for the MDIO busses found in the Hisilicon SoC that have an Fast Ethernet MAC. +config MDIO_I2C + tristate + depends on I2C + help + Support I2C based PHYs. This provides a MDIO bus bridged + to I2C to allow PHYs connected in I2C mode to be accessed + using the existing infrastructure. + + This is library mode. + config MDIO_MOXART tristate "MOXA ART MDIO interface support" depends on ARCH_MOXART @@ -159,6 +169,16 @@ menuconfig PHYLIB devices. This option provides infrastructure for managing PHY devices. +config PHYLINK + tristate + depends on NETDEVICES + select PHYLIB + select SWPHY + help + PHYlink models the link between the PHY and MAC, allowing fixed + configuration links, PHYs, and Serdes links with MAC level + autonegotiation modes. + if PHYLIB config SWPHY @@ -180,6 +200,11 @@ config LED_TRIGGER_PHY comment "MII PHY device drivers" +config SFP + tristate "SFP cage support" + depends on I2C && PHYLINK + select MDIO_I2C + config AMD_PHY tristate "AMD PHYs" ---help--- diff --git a/drivers/net/phy/Makefile b/drivers/net/phy/Makefile index 8e9b9f349384..7237255bad68 100644 --- a/drivers/net/phy/Makefile +++ b/drivers/net/phy/Makefile @@ -18,6 +18,7 @@ endif libphy-$(CONFIG_SWPHY) += swphy.o libphy-$(CONFIG_LED_TRIGGER_PHY) += phy_led_triggers.o +obj-$(CONFIG_PHYLINK) += phylink.o obj-$(CONFIG_PHYLIB) += libphy.o obj-$(CONFIG_MDIO_BCM_IPROC) += mdio-bcm-iproc.o @@ -30,12 +31,17 @@ obj-$(CONFIG_MDIO_BUS_MUX_MMIOREG) += mdio-mux-mmioreg.o obj-$(CONFIG_MDIO_CAVIUM) += mdio-cavium.o obj-$(CONFIG_MDIO_GPIO) += mdio-gpio.o obj-$(CONFIG_MDIO_HISI_FEMAC) += mdio-hisi-femac.o +obj-$(CONFIG_MDIO_I2C) += mdio-i2c.o obj-$(CONFIG_MDIO_MOXART) += mdio-moxart.o obj-$(CONFIG_MDIO_OCTEON) += mdio-octeon.o obj-$(CONFIG_MDIO_SUN4I) += mdio-sun4i.o obj-$(CONFIG_MDIO_THUNDER) += mdio-thunder.o obj-$(CONFIG_MDIO_XGENE) += mdio-xgene.o +obj-$(CONFIG_SFP) += sfp.o +sfp-obj-$(CONFIG_SFP) += sfp-bus.o +obj-y += $(sfp-obj-y) $(sfp-obj-m) + obj-$(CONFIG_AMD_PHY) += amd.o obj-$(CONFIG_AQUANTIA_PHY) += aquantia.o obj-$(CONFIG_AT803X_PHY) += at803x.o diff --git a/drivers/net/phy/fixed_phy.c b/drivers/net/phy/fixed_phy.c index eb5167210681..001fe1df7557 100644 --- a/drivers/net/phy/fixed_phy.c +++ b/drivers/net/phy/fixed_phy.c @@ -115,37 +115,6 @@ int fixed_phy_set_link_update(struct phy_device *phydev, } EXPORT_SYMBOL_GPL(fixed_phy_set_link_update); -int fixed_phy_update_state(struct phy_device *phydev, - const struct fixed_phy_status *status, - const struct fixed_phy_status *changed) -{ - struct fixed_mdio_bus *fmb = &platform_fmb; - struct fixed_phy *fp; - - if (!phydev || phydev->mdio.bus != fmb->mii_bus) - return -EINVAL; - - list_for_each_entry(fp, &fmb->phys, node) { - if (fp->addr == phydev->mdio.addr) { - write_seqcount_begin(&fp->seqcount); -#define _UPD(x) if (changed->x) \ - fp->status.x = status->x - _UPD(link); - _UPD(speed); - _UPD(duplex); - _UPD(pause); - _UPD(asym_pause); -#undef _UPD - fixed_phy_update(fp); - write_seqcount_end(&fp->seqcount); - return 0; - } - } - - return -ENOENT; -} -EXPORT_SYMBOL(fixed_phy_update_state); - int fixed_phy_add(unsigned int irq, int phy_addr, struct fixed_phy_status *status, int link_gpio) diff --git a/drivers/net/phy/marvell10g.c b/drivers/net/phy/marvell10g.c index aebc08beceba..e3e743ab0bb4 100644 --- a/drivers/net/phy/marvell10g.c +++ b/drivers/net/phy/marvell10g.c @@ -15,7 +15,9 @@ * If both the fiber and copper ports are connected, the first to gain * link takes priority and the other port is completely locked out. */ +#include <linux/of.h> #include <linux/phy.h> +#include <linux/sfp.h> enum { MV_PCS_BASE_T = 0x0000, @@ -37,6 +39,11 @@ enum { MV_AN_RESULT_SPD_10000 = BIT(15), }; +struct mv3310_priv { + struct device_node *sfp_node; + struct sfp_bus *sfp_bus; +}; + static int mv3310_modify(struct phy_device *phydev, int devad, u16 reg, u16 mask, u16 bits) { @@ -55,17 +62,52 @@ static int mv3310_modify(struct phy_device *phydev, int devad, u16 reg, return ret < 0 ? ret : 1; } +static int mv3310_sfp_insert(void *upstream, const struct sfp_eeprom_id *id) +{ + struct phy_device *phydev = upstream; + struct mv3310_priv *priv = dev_get_drvdata(&phydev->mdio.dev); + + if (sfp_parse_interface(priv->sfp_bus, id) != PHY_INTERFACE_MODE_10GKR) { + dev_err(&phydev->mdio.dev, "incompatible SFP module inserted\n"); + return -EINVAL; + } + return 0; +} + +static const struct sfp_upstream_ops mv3310_sfp_ops = { + .module_insert = mv3310_sfp_insert, +}; + static int mv3310_probe(struct phy_device *phydev) { + struct mv3310_priv *priv; u32 mmd_mask = MDIO_DEVS_PMAPMD | MDIO_DEVS_AN; if (!phydev->is_c45 || (phydev->c45_ids.devices_in_package & mmd_mask) != mmd_mask) return -ENODEV; + priv = devm_kzalloc(&phydev->mdio.dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + dev_set_drvdata(&phydev->mdio.dev, priv); + + if (phydev->mdio.dev.of_node) + priv->sfp_node = of_parse_phandle(phydev->mdio.dev.of_node, + "sfp", 0); + return 0; } +static void mv3310_remove(struct phy_device *phydev) +{ + struct mv3310_priv *priv = dev_get_drvdata(&phydev->mdio.dev); + + if (priv->sfp_bus) + sfp_unregister_upstream(priv->sfp_bus); +} + /* * Resetting the MV88X3310 causes it to become non-responsive. Avoid * setting the reset bit(s). @@ -77,6 +119,7 @@ static int mv3310_soft_reset(struct phy_device *phydev) static int mv3310_config_init(struct phy_device *phydev) { + struct mv3310_priv *priv = dev_get_drvdata(&phydev->mdio.dev); __ETHTOOL_DECLARE_LINK_MODE_MASK(supported) = { 0, }; u32 mask; int val; @@ -165,6 +208,14 @@ static int mv3310_config_init(struct phy_device *phydev) phydev->supported &= mask; phydev->advertising &= phydev->supported; + /* Would be nice to do this in the probe function, but unfortunately, + * phylib doesn't have phydev->attached_dev set there. + */ + if (priv->sfp_node && !priv->sfp_bus) + priv->sfp_bus = sfp_register_upstream(priv->sfp_node, + phydev->attached_dev, + phydev, &mv3310_sfp_ops); + return 0; } @@ -348,12 +399,13 @@ static struct phy_driver mv3310_drivers[] = { SUPPORTED_FIBRE | SUPPORTED_10000baseT_Full | SUPPORTED_Backplane, - .probe = mv3310_probe, .soft_reset = mv3310_soft_reset, .config_init = mv3310_config_init, + .probe = mv3310_probe, .config_aneg = mv3310_config_aneg, .aneg_done = mv3310_aneg_done, .read_status = mv3310_read_status, + .remove = mv3310_remove, }, }; diff --git a/drivers/net/phy/mdio-i2c.c b/drivers/net/phy/mdio-i2c.c new file mode 100644 index 000000000000..6d24fd13ca86 --- /dev/null +++ b/drivers/net/phy/mdio-i2c.c @@ -0,0 +1,109 @@ +/* + * MDIO I2C bridge + * + * Copyright (C) 2015-2016 Russell King + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * Network PHYs can appear on I2C buses when they are part of SFP module. + * This driver exposes these PHYs to the networking PHY code, allowing + * our PHY drivers access to these PHYs, and so allowing configuration + * of their settings. + */ +#include <linux/i2c.h> +#include <linux/phy.h> + +#include "mdio-i2c.h" + +/* + * I2C bus addresses 0x50 and 0x51 are normally an EEPROM, which is + * specified to be present in SFP modules. These correspond with PHY + * addresses 16 and 17. Disallow access to these "phy" addresses. + */ +static bool i2c_mii_valid_phy_id(int phy_id) +{ + return phy_id != 0x10 && phy_id != 0x11; +} + +static unsigned int i2c_mii_phy_addr(int phy_id) +{ + return phy_id + 0x40; +} + +static int i2c_mii_read(struct mii_bus *bus, int phy_id, int reg) +{ + struct i2c_adapter *i2c = bus->priv; + struct i2c_msg msgs[2]; + u8 data[2], dev_addr = reg; + int bus_addr, ret; + + if (!i2c_mii_valid_phy_id(phy_id)) + return 0xffff; + + bus_addr = i2c_mii_phy_addr(phy_id); + msgs[0].addr = bus_addr; + msgs[0].flags = 0; + msgs[0].len = 1; + msgs[0].buf = &dev_addr; + msgs[1].addr = bus_addr; + msgs[1].flags = I2C_M_RD; + msgs[1].len = sizeof(data); + msgs[1].buf = data; + + ret = i2c_transfer(i2c, msgs, ARRAY_SIZE(msgs)); + if (ret != ARRAY_SIZE(msgs)) + return 0xffff; + + return data[0] << 8 | data[1]; +} + +static int i2c_mii_write(struct mii_bus *bus, int phy_id, int reg, u16 val) +{ + struct i2c_adapter *i2c = bus->priv; + struct i2c_msg msg; + int ret; + u8 data[3]; + + if (!i2c_mii_valid_phy_id(phy_id)) + return 0; + + data[0] = reg; + data[1] = val >> 8; + data[2] = val; + + msg.addr = i2c_mii_phy_addr(phy_id); + msg.flags = 0; + msg.len = 3; + msg.buf = data; + + ret = i2c_transfer(i2c, &msg, 1); + + return ret < 0 ? ret : 0; +} + +struct mii_bus *mdio_i2c_alloc(struct device *parent, struct i2c_adapter *i2c) +{ + struct mii_bus *mii; + + if (!i2c_check_functionality(i2c, I2C_FUNC_I2C)) + return ERR_PTR(-EINVAL); + + mii = mdiobus_alloc(); + if (!mii) + return ERR_PTR(-ENOMEM); + + snprintf(mii->id, MII_BUS_ID_SIZE, "i2c:%s", dev_name(parent)); + mii->parent = parent; + mii->read = i2c_mii_read; + mii->write = i2c_mii_write; + mii->priv = i2c; + + return mii; +} +EXPORT_SYMBOL_GPL(mdio_i2c_alloc); + +MODULE_AUTHOR("Russell King"); +MODULE_DESCRIPTION("MDIO I2C bridge library"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/net/phy/mdio-i2c.h b/drivers/net/phy/mdio-i2c.h new file mode 100644 index 000000000000..889ab57d7f3e --- /dev/null +++ b/drivers/net/phy/mdio-i2c.h @@ -0,0 +1,19 @@ +/* + * MDIO I2C bridge + * + * Copyright (C) 2015 Russell King + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ +#ifndef MDIO_I2C_H +#define MDIO_I2C_H + +struct device; +struct i2c_adapter; +struct mii_bus; + +struct mii_bus *mdio_i2c_alloc(struct device *parent, struct i2c_adapter *i2c); + +#endif diff --git a/drivers/net/phy/phy-core.c b/drivers/net/phy/phy-core.c index 6739b738bbaf..21f75ae244b3 100644 --- a/drivers/net/phy/phy-core.c +++ b/drivers/net/phy/phy-core.c @@ -9,6 +9,186 @@ #include <linux/export.h> #include <linux/phy.h> +const char *phy_speed_to_str(int speed) +{ + switch (speed) { + case SPEED_10: + return "10Mbps"; + case SPEED_100: + return "100Mbps"; + case SPEED_1000: + return "1Gbps"; + case SPEED_2500: + return "2.5Gbps"; + case SPEED_5000: + return "5Gbps"; + case SPEED_10000: + return "10Gbps"; + case SPEED_14000: + return "14Gbps"; + case SPEED_20000: + return "20Gbps"; + case SPEED_25000: + return "25Gbps"; + case SPEED_40000: + return "40Gbps"; + case SPEED_50000: + return "50Gbps"; + case SPEED_56000: + return "56Gbps"; + case SPEED_100000: + return "100Gbps"; + case SPEED_UNKNOWN: + return "Unknown"; + default: + return "Unsupported (update phy-core.c)"; + } +} +EXPORT_SYMBOL_GPL(phy_speed_to_str); + +const char *phy_duplex_to_str(unsigned int duplex) +{ + if (duplex == DUPLEX_HALF) + return "Half"; + if (duplex == DUPLEX_FULL) + return "Full"; + if (duplex == DUPLEX_UNKNOWN) + return "Unknown"; + return "Unsupported (update phy-core.c)"; +} +EXPORT_SYMBOL_GPL(phy_duplex_to_str); + +/* A mapping of all SUPPORTED settings to speed/duplex. This table + * must be grouped by speed and sorted in descending match priority + * - iow, descending speed. */ +static const struct phy_setting settings[] = { + { + .speed = SPEED_10000, + .duplex = DUPLEX_FULL, + .bit = ETHTOOL_LINK_MODE_10000baseKR_Full_BIT, + }, + { + .speed = SPEED_10000, + .duplex = DUPLEX_FULL, + .bit = ETHTOOL_LINK_MODE_10000baseKX4_Full_BIT, + }, + { + .speed = SPEED_10000, + .duplex = DUPLEX_FULL, + .bit = ETHTOOL_LINK_MODE_10000baseT_Full_BIT, + }, + { + .speed = SPEED_2500, + .duplex = DUPLEX_FULL, + .bit = ETHTOOL_LINK_MODE_2500baseX_Full_BIT, + }, + { + .speed = SPEED_1000, + .duplex = DUPLEX_FULL, + .bit = ETHTOOL_LINK_MODE_1000baseKX_Full_BIT, + }, + { + .speed = SPEED_1000, + .duplex = DUPLEX_FULL, + .bit = ETHTOOL_LINK_MODE_1000baseX_Full_BIT, + }, + { + .speed = SPEED_1000, + .duplex = DUPLEX_FULL, + .bit = ETHTOOL_LINK_MODE_1000baseT_Full_BIT, + }, + { + .speed = SPEED_1000, + .duplex = DUPLEX_HALF, + .bit = ETHTOOL_LINK_MODE_1000baseT_Half_BIT, + }, + { + .speed = SPEED_100, + .duplex = DUPLEX_FULL, + .bit = ETHTOOL_LINK_MODE_100baseT_Full_BIT, + }, + { + .speed = SPEED_100, + .duplex = DUPLEX_HALF, + .bit = ETHTOOL_LINK_MODE_100baseT_Half_BIT, + }, + { + .speed = SPEED_10, + .duplex = DUPLEX_FULL, + .bit = ETHTOOL_LINK_MODE_10baseT_Full_BIT, + }, + { + .speed = SPEED_10, + .duplex = DUPLEX_HALF, + .bit = ETHTOOL_LINK_MODE_10baseT_Half_BIT, + }, +}; + +/** + * phy_lookup_setting - lookup a PHY setting + * @speed: speed to match + * @duplex: duplex to match + * @mask: allowed link modes + * @maxbit: bit size of link modes + * @exact: an exact match is required + * + * Search the settings array for a setting that matches the speed and + * duplex, and which is supported. + * + * If @exact is unset, either an exact match or %NULL for no match will + * be returned. + * + * If @exact is set, an exact match, the fastest supported setting at + * or below the specified speed, the slowest supported setting, or if + * they all fail, %NULL will be returned. + */ +const struct phy_setting * +phy_lookup_setting(int speed, int duplex, const unsigned long *mask, + size_t maxbit, bool exact) +{ + const struct phy_setting *p, *match = NULL, *last = NULL; + int i; + + for (i = 0, p = settings; i < ARRAY_SIZE(settings); i++, p++) { + if (p->bit < maxbit && test_bit(p->bit, mask)) { + last = p; + if (p->speed == speed && p->duplex == duplex) { + /* Exact match for speed and duplex */ + match = p; + break; + } else if (!exact) { + if (!match && p->speed <= speed) + /* Candidate */ + match = p; + + if (p->speed < speed) + break; + } + } + } + + if (!match && !exact) + match = last; + + return match; +} +EXPORT_SYMBOL_GPL(phy_lookup_setting); + +size_t phy_speeds(unsigned int *speeds, size_t size, + unsigned long *mask, size_t maxbit) +{ + size_t count; + int i; + + for (i = 0, count = 0; i < ARRAY_SIZE(settings) && count < size; i++) + if (settings[i].bit < maxbit && + test_bit(settings[i].bit, mask) && + (count == 0 || speeds[count - 1] != settings[i].speed)) + speeds[count++] = settings[i].speed; + + return count; +} + static void mmd_phy_indirect(struct mii_bus *bus, int phy_addr, int devad, u16 regnum) { diff --git a/drivers/net/phy/phy.c b/drivers/net/phy/phy.c index d0626bf5c540..ba92c4ca21f3 100644 --- a/drivers/net/phy/phy.c +++ b/drivers/net/phy/phy.c @@ -39,42 +39,6 @@ #include <asm/irq.h> -static const char *phy_speed_to_str(int speed) -{ - switch (speed) { - case SPEED_10: - return "10Mbps"; - case SPEED_100: - return "100Mbps"; - case SPEED_1000: - return "1Gbps"; - case SPEED_2500: - return "2.5Gbps"; - case SPEED_5000: - return "5Gbps"; - case SPEED_10000: - return "10Gbps"; - case SPEED_14000: - return "14Gbps"; - case SPEED_20000: - return "20Gbps"; - case SPEED_25000: - return "25Gbps"; - case SPEED_40000: - return "40Gbps"; - case SPEED_50000: - return "50Gbps"; - case SPEED_56000: - return "56Gbps"; - case SPEED_100000: - return "100Gbps"; - case SPEED_UNKNOWN: - return "Unknown"; - default: - return "Unsupported (update phy.c)"; - } -} - #define PHY_STATE_STR(_state) \ case PHY_##_state: \ return __stringify(_state); \ @@ -110,7 +74,7 @@ void phy_print_status(struct phy_device *phydev) netdev_info(phydev->attached_dev, "Link is Up - %s/%s - flow control %s\n", phy_speed_to_str(phydev->speed), - DUPLEX_FULL == phydev->duplex ? "Full" : "Half", + phy_duplex_to_str(phydev->duplex), phydev->pause ? "rx/tx" : "off"); } else { netdev_info(phydev->attached_dev, "Link is Down\n"); @@ -194,123 +158,6 @@ int phy_aneg_done(struct phy_device *phydev) } EXPORT_SYMBOL(phy_aneg_done); -/* A structure for mapping a particular speed and duplex - * combination to a particular SUPPORTED and ADVERTISED value - */ -struct phy_setting { - int speed; - int duplex; - u32 setting; -}; - -/* A mapping of all SUPPORTED settings to speed/duplex. This table - * must be grouped by speed and sorted in descending match priority - * - iow, descending speed. */ -static const struct phy_setting settings[] = { - { - .speed = SPEED_10000, - .duplex = DUPLEX_FULL, - .setting = SUPPORTED_10000baseKR_Full, - }, - { - .speed = SPEED_10000, - .duplex = DUPLEX_FULL, - .setting = SUPPORTED_10000baseKX4_Full, - }, - { - .speed = SPEED_10000, - .duplex = DUPLEX_FULL, - .setting = SUPPORTED_10000baseT_Full, - }, - { - .speed = SPEED_2500, - .duplex = DUPLEX_FULL, - .setting = SUPPORTED_2500baseX_Full, - }, - { - .speed = SPEED_1000, - .duplex = DUPLEX_FULL, - .setting = SUPPORTED_1000baseKX_Full, - }, - { - .speed = SPEED_1000, - .duplex = DUPLEX_FULL, - .setting = SUPPORTED_1000baseT_Full, - }, - { - .speed = SPEED_1000, - .duplex = DUPLEX_HALF, - .setting = SUPPORTED_1000baseT_Half, - }, - { - .speed = SPEED_100, - .duplex = DUPLEX_FULL, - .setting = SUPPORTED_100baseT_Full, - }, - { - .speed = SPEED_100, - .duplex = DUPLEX_HALF, - .setting = SUPPORTED_100baseT_Half, - }, - { - .speed = SPEED_10, - .duplex = DUPLEX_FULL, - .setting = SUPPORTED_10baseT_Full, - }, - { - .speed = SPEED_10, - .duplex = DUPLEX_HALF, - .setting = SUPPORTED_10baseT_Half, - }, -}; - -/** - * phy_lookup_setting - lookup a PHY setting - * @speed: speed to match - * @duplex: duplex to match - * @features: allowed link modes - * @exact: an exact match is required - * - * Search the settings array for a setting that matches the speed and - * duplex, and which is supported. - * - * If @exact is unset, either an exact match or %NULL for no match will - * be returned. - * - * If @exact is set, an exact match, the fastest supported setting at - * or below the specified speed, the slowest supported setting, or if - * they all fail, %NULL will be returned. - */ -static const struct phy_setting * -phy_lookup_setting(int speed, int duplex, u32 features, bool exact) -{ - const struct phy_setting *p, *match = NULL, *last = NULL; - int i; - - for (i = 0, p = settings; i < ARRAY_SIZE(settings); i++, p++) { - if (p->setting & features) { - last = p; - if (p->speed == speed && p->duplex == duplex) { - /* Exact match for speed and duplex */ - match = p; - break; - } else if (!exact) { - if (!match && p->speed <= speed) - /* Candidate */ - match = p; - - if (p->speed < speed) - break; - } - } - } - - if (!match && !exact) - match = last; - - return match; -} - /** * phy_find_valid - find a PHY setting that matches the requested parameters * @speed: desired speed @@ -327,7 +174,9 @@ phy_lookup_setting(int speed, int duplex, u32 features, bool exact) static const struct phy_setting * phy_find_valid(int speed, int duplex, u32 supported) { - return phy_lookup_setting(speed, duplex, supported, false); + unsigned long mask = supported; + + return phy_lookup_setting(speed, duplex, &mask, BITS_PER_LONG, false); } /** @@ -344,16 +193,9 @@ unsigned int phy_supported_speeds(struct phy_device *phy, unsigned int *speeds, unsigned int size) { - unsigned int count = 0; - unsigned int idx = 0; + unsigned long supported = phy->supported; - for (idx = 0; idx < ARRAY_SIZE(settings) && count < size; idx++) - /* Assumes settings are grouped by speed */ - if ((settings[idx].setting & phy->supported) && - (count == 0 || speeds[count - 1] != settings[idx].speed)) - speeds[count++] = settings[idx].speed; - - return count; + return phy_speeds(speeds, size, &supported, BITS_PER_LONG); } /** @@ -367,7 +209,9 @@ unsigned int phy_supported_speeds(struct phy_device *phy, */ static inline bool phy_check_valid(int speed, int duplex, u32 features) { - return !!phy_lookup_setting(speed, duplex, features, true); + unsigned long mask = features; + + return !!phy_lookup_setting(speed, duplex, &mask, BITS_PER_LONG, true); } /** @@ -713,6 +557,7 @@ void phy_start_machine(struct phy_device *phydev) { queue_delayed_work(system_power_efficient_wq, &phydev->state_queue, HZ); } +EXPORT_SYMBOL_GPL(phy_start_machine); /** * phy_trigger_machine - trigger the state machine to run @@ -1019,9 +864,15 @@ void phy_start(struct phy_device *phydev) } EXPORT_SYMBOL(phy_start); -static void phy_adjust_link(struct phy_device *phydev) +static void phy_link_up(struct phy_device *phydev) { - phydev->adjust_link(phydev->attached_dev); + phydev->phy_link_change(phydev, true, true); + phy_led_trigger_change_speed(phydev); +} + +static void phy_link_down(struct phy_device *phydev, bool do_carrier) +{ + phydev->phy_link_change(phydev, false, do_carrier); phy_led_trigger_change_speed(phydev); } @@ -1066,8 +917,7 @@ void phy_state_machine(struct work_struct *work) /* If the link is down, give up on negotiation for now */ if (!phydev->link) { phydev->state = PHY_NOLINK; - netif_carrier_off(phydev->attached_dev); - phy_adjust_link(phydev); + phy_link_down(phydev, true); break; } @@ -1079,9 +929,7 @@ void phy_state_machine(struct work_struct *work) /* If AN is done, we're running */ if (err > 0) { phydev->state = PHY_RUNNING; - netif_carrier_on(phydev->attached_dev); - phy_adjust_link(phydev); - + phy_link_up(phydev); } else if (0 == phydev->link_timeout--) needs_aneg = true; break; @@ -1106,8 +954,7 @@ void phy_state_machine(struct work_struct *work) } } phydev->state = PHY_RUNNING; - netif_carrier_on(phydev->attached_dev); - phy_adjust_link(phydev); + phy_link_up(phydev); } break; case PHY_FORCING: @@ -1117,13 +964,12 @@ void phy_state_machine(struct work_struct *work) if (phydev->link) { phydev->state = PHY_RUNNING; - netif_carrier_on(phydev->attached_dev); + phy_link_up(phydev); } else { if (0 == phydev->link_timeout--) needs_aneg = true; + phy_link_down(phydev, false); } - - phy_adjust_link(phydev); break; case PHY_RUNNING: /* Only register a CHANGE if we are polling and link changed @@ -1155,14 +1001,12 @@ void phy_state_machine(struct work_struct *work) if (phydev->link) { phydev->state = PHY_RUNNING; - netif_carrier_on(phydev->attached_dev); + phy_link_up(phydev); } else { phydev->state = PHY_NOLINK; - netif_carrier_off(phydev->attached_dev); + phy_link_down(phydev, true); } - phy_adjust_link(phydev); - if (phy_interrupt_is_valid(phydev)) err = phy_config_interrupt(phydev, PHY_INTERRUPT_ENABLED); @@ -1170,8 +1014,7 @@ void phy_state_machine(struct work_struct *work) case PHY_HALTED: if (phydev->link) { phydev->link = 0; - netif_carrier_off(phydev->attached_dev); - phy_adjust_link(phydev); + phy_link_down(phydev, true); do_suspend = true; } break; @@ -1191,11 +1034,11 @@ void phy_state_machine(struct work_struct *work) if (phydev->link) { phydev->state = PHY_RUNNING; - netif_carrier_on(phydev->attached_dev); + phy_link_up(phydev); } else { phydev->state = PHY_NOLINK; + phy_link_down(phydev, false); } - phy_adjust_link(phydev); } else { phydev->state = PHY_AN; phydev->link_timeout = PHY_AN_TIMEOUT; @@ -1207,11 +1050,11 @@ void phy_state_machine(struct work_struct *work) if (phydev->link) { phydev->state = PHY_RUNNING; - netif_carrier_on(phydev->attached_dev); + phy_link_up(phydev); } else { phydev->state = PHY_NOLINK; + phy_link_down(phydev, false); } - phy_adjust_link(phydev); } break; } diff --git a/drivers/net/phy/phy_device.c b/drivers/net/phy/phy_device.c index 2f742ae5b92e..810f6fd2f639 100644 --- a/drivers/net/phy/phy_device.c +++ b/drivers/net/phy/phy_device.c @@ -688,6 +688,19 @@ struct phy_device *phy_find_first(struct mii_bus *bus) } EXPORT_SYMBOL(phy_find_first); +static void phy_link_change(struct phy_device *phydev, bool up, bool do_carrier) +{ + struct net_device *netdev = phydev->attached_dev; + + if (do_carrier) { + if (up) + netif_carrier_on(netdev); + else + netif_carrier_off(netdev); + } + phydev->adjust_link(netdev); +} + /** * phy_prepare_link - prepares the PHY layer to monitor link status * @phydev: target phy_device struct @@ -953,6 +966,7 @@ int phy_attach_direct(struct net_device *dev, struct phy_device *phydev, goto error; } + phydev->phy_link_change = phy_link_change; phydev->attached_dev = dev; dev->phydev = phydev; @@ -1072,6 +1086,7 @@ void phy_detach(struct phy_device *phydev) phydev->attached_dev->phydev = NULL; phydev->attached_dev = NULL; phy_suspend(phydev); + phydev->phylink = NULL; phy_led_triggers_unregister(phydev); diff --git a/drivers/net/phy/phylink.c b/drivers/net/phy/phylink.c new file mode 100644 index 000000000000..ee5772f28ddf --- /dev/null +++ b/drivers/net/phy/phylink.c @@ -0,0 +1,1434 @@ +/* + * phylink models the MAC to optional PHY connection, supporting + * technologies such as SFP cages where the PHY is hot-pluggable. + * + * Copyright (C) 2015 Russell King + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ +#include <linux/ethtool.h> +#include <linux/export.h> +#include <linux/gpio/consumer.h> +#include <linux/netdevice.h> +#include <linux/of.h> +#include <linux/of_mdio.h> +#include <linux/phy.h> +#include <linux/phy_fixed.h> +#include <linux/phylink.h> +#include <linux/rtnetlink.h> +#include <linux/spinlock.h> +#include <linux/workqueue.h> + +#include "sfp.h" +#include "swphy.h" + +#define SUPPORTED_INTERFACES \ + (SUPPORTED_TP | SUPPORTED_MII | SUPPORTED_FIBRE | \ + SUPPORTED_BNC | SUPPORTED_AUI | SUPPORTED_Backplane) +#define ADVERTISED_INTERFACES \ + (ADVERTISED_TP | ADVERTISED_MII | ADVERTISED_FIBRE | \ + ADVERTISED_BNC | ADVERTISED_AUI | ADVERTISED_Backplane) + +enum { + PHYLINK_DISABLE_STOPPED, + PHYLINK_DISABLE_LINK, +}; + +struct phylink { + struct net_device *netdev; + const struct phylink_mac_ops *ops; + + unsigned long phylink_disable_state; /* bitmask of disables */ + struct phy_device *phydev; + phy_interface_t link_interface; /* PHY_INTERFACE_xxx */ + u8 link_an_mode; /* MLO_AN_xxx */ + u8 link_port; /* The current non-phy ethtool port */ + __ETHTOOL_DECLARE_LINK_MODE_MASK(supported); + + /* The link configuration settings */ + struct phylink_link_state link_config; + struct gpio_desc *link_gpio; + + struct mutex state_mutex; + struct phylink_link_state phy_state; + struct work_struct resolve; + + bool mac_link_dropped; + + struct sfp_bus *sfp_bus; +}; + +static inline void linkmode_zero(unsigned long *dst) +{ + bitmap_zero(dst, __ETHTOOL_LINK_MODE_MASK_NBITS); +} + +static inline void linkmode_copy(unsigned long *dst, const unsigned long *src) +{ + bitmap_copy(dst, src, __ETHTOOL_LINK_MODE_MASK_NBITS); +} + +static inline void linkmode_and(unsigned long *dst, const unsigned long *a, + const unsigned long *b) +{ + bitmap_and(dst, a, b, __ETHTOOL_LINK_MODE_MASK_NBITS); +} + +static inline void linkmode_or(unsigned long *dst, const unsigned long *a, + const unsigned long *b) +{ + bitmap_or(dst, a, b, __ETHTOOL_LINK_MODE_MASK_NBITS); +} + +static inline bool linkmode_empty(const unsigned long *src) +{ + return bitmap_empty(src, __ETHTOOL_LINK_MODE_MASK_NBITS); +} + +void phylink_set_port_modes(unsigned long *mask) +{ + phylink_set(mask, TP); + phylink_set(mask, AUI); + phylink_set(mask, MII); + phylink_set(mask, FIBRE); + phylink_set(mask, BNC); + phylink_set(mask, Backplane); +} +EXPORT_SYMBOL_GPL(phylink_set_port_modes); + +static int phylink_is_empty_linkmode(const unsigned long *linkmode) +{ + __ETHTOOL_DECLARE_LINK_MODE_MASK(tmp) = { 0, }; + + phylink_set_port_modes(tmp); + phylink_set(tmp, Autoneg); + phylink_set(tmp, Pause); + phylink_set(tmp, Asym_Pause); + + bitmap_andnot(tmp, linkmode, tmp, __ETHTOOL_LINK_MODE_MASK_NBITS); + + return linkmode_empty(tmp); +} + +static const char *phylink_an_mode_str(unsigned int mode) +{ + static const char *modestr[] = { + [MLO_AN_PHY] = "phy", + [MLO_AN_FIXED] = "fixed", + [MLO_AN_SGMII] = "SGMII", + [MLO_AN_8023Z] = "802.3z", + }; + + return mode < ARRAY_SIZE(modestr) ? modestr[mode] : "unknown"; +} + +static int phylink_validate(struct phylink *pl, unsigned long *supported, + struct phylink_link_state *state) +{ + pl->ops->validate(pl->netdev, supported, state); + + return phylink_is_empty_linkmode(supported) ? -EINVAL : 0; +} + +static int phylink_parse_fixedlink(struct phylink *pl, struct device_node *np) +{ + struct device_node *fixed_node; + const struct phy_setting *s; + struct gpio_desc *desc; + const __be32 *fixed_prop; + u32 speed; + int ret, len; + + fixed_node = of_get_child_by_name(np, "fixed-link"); + if (fixed_node) { + ret = of_property_read_u32(fixed_node, "speed", &speed); + + pl->link_config.speed = speed; + pl->link_config.duplex = DUPLEX_HALF; + + if (of_property_read_bool(fixed_node, "full-duplex")) + pl->link_config.duplex = DUPLEX_FULL; + + /* We treat the "pause" and "asym-pause" terminology as + * defining the link partner's ability. */ + if (of_property_read_bool(fixed_node, "pause")) + pl->link_config.pause |= MLO_PAUSE_SYM; + if (of_property_read_bool(fixed_node, "asym-pause")) + pl->link_config.pause |= MLO_PAUSE_ASYM; + + if (ret == 0) { + desc = fwnode_get_named_gpiod(&fixed_node->fwnode, + "link-gpios", 0, + GPIOD_IN, "?"); + + if (!IS_ERR(desc)) + pl->link_gpio = desc; + else if (desc == ERR_PTR(-EPROBE_DEFER)) + ret = -EPROBE_DEFER; + } + of_node_put(fixed_node); + + if (ret) + return ret; + } else { + fixed_prop = of_get_property(np, "fixed-link", &len); + if (!fixed_prop) { + netdev_err(pl->netdev, "broken fixed-link?\n"); + return -EINVAL; + } + if (len == 5 * sizeof(*fixed_prop)) { + pl->link_config.duplex = be32_to_cpu(fixed_prop[1]) ? + DUPLEX_FULL : DUPLEX_HALF; + pl->link_config.speed = be32_to_cpu(fixed_prop[2]); + if (be32_to_cpu(fixed_prop[3])) + pl->link_config.pause |= MLO_PAUSE_SYM; + if (be32_to_cpu(fixed_prop[4])) + pl->link_config.pause |= MLO_PAUSE_ASYM; + } + } + + if (pl->link_config.speed > SPEED_1000 && + pl->link_config.duplex != DUPLEX_FULL) + netdev_warn(pl->netdev, "fixed link specifies half duplex for %dMbps link?\n", + pl->link_config.speed); + + bitmap_fill(pl->supported, __ETHTOOL_LINK_MODE_MASK_NBITS); + linkmode_copy(pl->link_config.advertising, pl->supported); + phylink_validate(pl, pl->supported, &pl->link_config); + + s = phy_lookup_setting(pl->link_config.speed, pl->link_config.duplex, + pl->supported, + __ETHTOOL_LINK_MODE_MASK_NBITS, true); + linkmode_zero(pl->supported); + phylink_set(pl->supported, MII); + if (s) { + __set_bit(s->bit, pl->supported); + } else { + netdev_warn(pl->netdev, "fixed link %s duplex %dMbps not recognised\n", + pl->link_config.duplex == DUPLEX_FULL ? "full" : "half", + pl->link_config.speed); + } + + linkmode_and(pl->link_config.advertising, pl->link_config.advertising, + pl->supported); + + pl->link_config.link = 1; + pl->link_config.an_complete = 1; + + return 0; +} + +static int phylink_parse_mode(struct phylink *pl, struct device_node *np) +{ + struct device_node *dn; + const char *managed; + + dn = of_get_child_by_name(np, "fixed-link"); + if (dn || of_find_property(np, "fixed-link", NULL)) + pl->link_an_mode = MLO_AN_FIXED; + of_node_put(dn); + + if (of_property_read_string(np, "managed", &managed) == 0 && + strcmp(managed, "in-band-status") == 0) { + if (pl->link_an_mode == MLO_AN_FIXED) { + netdev_err(pl->netdev, + "can't use both fixed-link and in-band-status\n"); + return -EINVAL; + } + + linkmode_zero(pl->supported); + phylink_set(pl->supported, MII); + phylink_set(pl->supported, Autoneg); + phylink_set(pl->supported, Asym_Pause); + phylink_set(pl->supported, Pause); + pl->link_config.an_enabled = true; + + switch (pl->link_config.interface) { + case PHY_INTERFACE_MODE_SGMII: + phylink_set(pl->supported, 10baseT_Half); + phylink_set(pl->supported, 10baseT_Full); + phylink_set(pl->supported, 100baseT_Half); + phylink_set(pl->supported, 100baseT_Full); + phylink_set(pl->supported, 1000baseT_Half); + phylink_set(pl->supported, 1000baseT_Full); + pl->link_an_mode = MLO_AN_SGMII; + break; + + case PHY_INTERFACE_MODE_1000BASEX: + phylink_set(pl->supported, 1000baseX_Full); + pl->link_an_mode = MLO_AN_8023Z; + break; + + case PHY_INTERFACE_MODE_2500BASEX: + phylink_set(pl->supported, 2500baseX_Full); + pl->link_an_mode = MLO_AN_8023Z; + break; + + case PHY_INTERFACE_MODE_10GKR: + phylink_set(pl->supported, 10baseT_Half); + phylink_set(pl->supported, 10baseT_Full); + phylink_set(pl->supported, 100baseT_Half); + phylink_set(pl->supported, 100baseT_Full); + phylink_set(pl->supported, 1000baseT_Half); + phylink_set(pl->supported, 1000baseT_Full); + phylink_set(pl->supported, 1000baseX_Full); + phylink_set(pl->supported, 10000baseKR_Full); + phylink_set(pl->supported, 10000baseCR_Full); + phylink_set(pl->supported, 10000baseSR_Full); + phylink_set(pl->supported, 10000baseLR_Full); + phylink_set(pl->supported, 10000baseLRM_Full); + phylink_set(pl->supported, 10000baseER_Full); + pl->link_an_mode = MLO_AN_SGMII; + break; + + default: + netdev_err(pl->netdev, + "incorrect link mode %s for in-band status\n", + phy_modes(pl->link_config.interface)); + return -EINVAL; + } + + linkmode_copy(pl->link_config.advertising, pl->supported); + + if (phylink_validate(pl, pl->supported, &pl->link_config)) { + netdev_err(pl->netdev, + "failed to validate link configuration for in-band status\n"); + return -EINVAL; + } + } + + return 0; +} + +static void phylink_mac_config(struct phylink *pl, + const struct phylink_link_state *state) +{ + netdev_dbg(pl->netdev, + "%s: mode=%s/%s/%s/%s adv=%*pb pause=%02x link=%u an=%u\n", + __func__, phylink_an_mode_str(pl->link_an_mode), + phy_modes(state->interface), + phy_speed_to_str(state->speed), + phy_duplex_to_str(state->duplex), + __ETHTOOL_LINK_MODE_MASK_NBITS, state->advertising, + state->pause, state->link, state->an_enabled); + + pl->ops->mac_config(pl->netdev, pl->link_an_mode, state); +} + +static void phylink_mac_an_restart(struct phylink *pl) +{ + if (pl->link_config.an_enabled && + (pl->link_config.interface == PHY_INTERFACE_MODE_1000BASEX || + pl->link_config.interface == PHY_INTERFACE_MODE_2500BASEX)) + pl->ops->mac_an_restart(pl->netdev); +} + +static int phylink_get_mac_state(struct phylink *pl, struct phylink_link_state *state) +{ + struct net_device *ndev = pl->netdev; + + linkmode_copy(state->advertising, pl->link_config.advertising); + linkmode_zero(state->lp_advertising); + state->interface = pl->link_config.interface; + state->an_enabled = pl->link_config.an_enabled; + state->link = 1; + + return pl->ops->mac_link_state(ndev, state); +} + +/* The fixed state is... fixed except for the link state, + * which may be determined by a GPIO. + */ +static void phylink_get_fixed_state(struct phylink *pl, struct phylink_link_state *state) +{ + *state = pl->link_config; + if (pl->link_gpio) + state->link = !!gpiod_get_value(pl->link_gpio); +} + +/* Flow control is resolved according to our and the link partners + * advertisments using the following drawn from the 802.3 specs: + * Local device Link partner + * Pause AsymDir Pause AsymDir Result + * 1 X 1 X TX+RX + * 0 1 1 1 RX + * 1 1 0 1 TX + */ +static void phylink_resolve_flow(struct phylink *pl, + struct phylink_link_state *state) +{ + int new_pause = 0; + + if (pl->link_config.pause & MLO_PAUSE_AN) { + int pause = 0; + + if (phylink_test(pl->link_config.advertising, Pause)) + pause |= MLO_PAUSE_SYM; + if (phylink_test(pl->link_config.advertising, Asym_Pause)) + pause |= MLO_PAUSE_ASYM; + + pause &= state->pause; + + if (pause & MLO_PAUSE_SYM) + new_pause = MLO_PAUSE_TX | MLO_PAUSE_RX; + else if (pause & MLO_PAUSE_ASYM) + new_pause = state->pause & MLO_PAUSE_SYM ? + MLO_PAUSE_RX : MLO_PAUSE_TX; + } else { + new_pause = pl->link_config.pause & MLO_PAUSE_TXRX_MASK; + } + + state->pause &= ~MLO_PAUSE_TXRX_MASK; + state->pause |= new_pause; +} + +static const char *phylink_pause_to_str(int pause) +{ + switch (pause & MLO_PAUSE_TXRX_MASK) { + case MLO_PAUSE_TX | MLO_PAUSE_RX: + return "rx/tx"; + case MLO_PAUSE_TX: + return "tx"; + case MLO_PAUSE_RX: + return "rx"; + default: + return "off"; + } +} + +static void phylink_resolve(struct work_struct *w) +{ + struct phylink *pl = container_of(w, struct phylink, resolve); + struct phylink_link_state link_state; + struct net_device *ndev = pl->netdev; + + mutex_lock(&pl->state_mutex); + if (pl->phylink_disable_state) { + pl->mac_link_dropped = false; + link_state.link = false; + } else if (pl->mac_link_dropped) { + link_state.link = false; + } else { + switch (pl->link_an_mode) { + case MLO_AN_PHY: + link_state = pl->phy_state; + phylink_resolve_flow(pl, &link_state); + phylink_mac_config(pl, &link_state); + break; + + case MLO_AN_FIXED: + phylink_get_fixed_state(pl, &link_state); + phylink_mac_config(pl, &link_state); + break; + + case MLO_AN_SGMII: + phylink_get_mac_state(pl, &link_state); + if (pl->phydev) { + bool changed = false; + + link_state.link = link_state.link && + pl->phy_state.link; + + if (pl->phy_state.interface != + link_state.interface) { + link_state.interface = pl->phy_state.interface; + changed = true; + } + + /* Propagate the flow control from the PHY + * to the MAC. Also propagate the interface + * if changed. + */ + if (pl->phy_state.link || changed) { + link_state.pause |= pl->phy_state.pause; + phylink_resolve_flow(pl, &link_state); + + phylink_mac_config(pl, &link_state); + } + } + break; + + case MLO_AN_8023Z: + phylink_get_mac_state(pl, &link_state); + break; + } + } + + if (link_state.link != netif_carrier_ok(ndev)) { + if (!link_state.link) { + netif_carrier_off(ndev); + pl->ops->mac_link_down(ndev, pl->link_an_mode); + netdev_info(ndev, "Link is Down\n"); + } else { + pl->ops->mac_link_up(ndev, pl->link_an_mode, + pl->phydev); + + netif_carrier_on(ndev); + + netdev_info(ndev, + "Link is Up - %s/%s - flow control %s\n", + phy_speed_to_str(link_state.speed), + phy_duplex_to_str(link_state.duplex), + phylink_pause_to_str(link_state.pause)); + } + } + if (!link_state.link && pl->mac_link_dropped) { + pl->mac_link_dropped = false; + queue_work(system_power_efficient_wq, &pl->resolve); + } + mutex_unlock(&pl->state_mutex); +} + +static void phylink_run_resolve(struct phylink *pl) +{ + if (!pl->phylink_disable_state) + queue_work(system_power_efficient_wq, &pl->resolve); +} + +static const struct sfp_upstream_ops sfp_phylink_ops; + +static int phylink_register_sfp(struct phylink *pl, struct device_node *np) +{ + struct device_node *sfp_np; + + sfp_np = of_parse_phandle(np, "sfp", 0); + if (!sfp_np) + return 0; + + pl->sfp_bus = sfp_register_upstream(sfp_np, pl->netdev, pl, + &sfp_phylink_ops); + if (!pl->sfp_bus) + return -ENOMEM; + + return 0; +} + +struct phylink *phylink_create(struct net_device *ndev, struct device_node *np, + phy_interface_t iface, const struct phylink_mac_ops *ops) +{ + struct phylink *pl; + int ret; + + pl = kzalloc(sizeof(*pl), GFP_KERNEL); + if (!pl) + return ERR_PTR(-ENOMEM); + + mutex_init(&pl->state_mutex); + INIT_WORK(&pl->resolve, phylink_resolve); + pl->netdev = ndev; + pl->phy_state.interface = iface; + pl->link_interface = iface; + pl->link_port = PORT_MII; + pl->link_config.interface = iface; + pl->link_config.pause = MLO_PAUSE_AN; + pl->link_config.speed = SPEED_UNKNOWN; + pl->link_config.duplex = DUPLEX_UNKNOWN; + pl->ops = ops; + __set_bit(PHYLINK_DISABLE_STOPPED, &pl->phylink_disable_state); + + bitmap_fill(pl->supported, __ETHTOOL_LINK_MODE_MASK_NBITS); + linkmode_copy(pl->link_config.advertising, pl->supported); + phylink_validate(pl, pl->supported, &pl->link_config); + + ret = phylink_parse_mode(pl, np); + if (ret < 0) { + kfree(pl); + return ERR_PTR(ret); + } + + if (pl->link_an_mode == MLO_AN_FIXED) { + ret = phylink_parse_fixedlink(pl, np); + if (ret < 0) { + kfree(pl); + return ERR_PTR(ret); + } + } + + ret = phylink_register_sfp(pl, np); + if (ret < 0) { + kfree(pl); + return ERR_PTR(ret); + } + + return pl; +} +EXPORT_SYMBOL_GPL(phylink_create); + +void phylink_destroy(struct phylink *pl) +{ + if (pl->sfp_bus) + sfp_unregister_upstream(pl->sfp_bus); + + cancel_work_sync(&pl->resolve); + kfree(pl); +} +EXPORT_SYMBOL_GPL(phylink_destroy); + +void phylink_phy_change(struct phy_device *phydev, bool up, bool do_carrier) +{ + struct phylink *pl = phydev->phylink; + + mutex_lock(&pl->state_mutex); + pl->phy_state.speed = phydev->speed; + pl->phy_state.duplex = phydev->duplex; + pl->phy_state.pause = MLO_PAUSE_NONE; + if (phydev->pause) + pl->phy_state.pause |= MLO_PAUSE_SYM; + if (phydev->asym_pause) + pl->phy_state.pause |= MLO_PAUSE_ASYM; + pl->phy_state.interface = phydev->interface; + pl->phy_state.link = up; + mutex_unlock(&pl->state_mutex); + + phylink_run_resolve(pl); + + netdev_dbg(pl->netdev, "phy link %s %s/%s/%s\n", up ? "up" : "down", + phy_modes(phydev->interface), + phy_speed_to_str(phydev->speed), + phy_duplex_to_str(phydev->duplex)); +} + +static int phylink_bringup_phy(struct phylink *pl, struct phy_device *phy) +{ + struct phylink_link_state config; + __ETHTOOL_DECLARE_LINK_MODE_MASK(supported); + u32 advertising; + int ret; + + memset(&config, 0, sizeof(config)); + ethtool_convert_legacy_u32_to_link_mode(supported, phy->supported); + ethtool_convert_legacy_u32_to_link_mode(config.advertising, + phy->advertising); + config.interface = pl->link_config.interface; + + /* + * This is the new way of dealing with flow control for PHYs, + * as described by Timur Tabi in commit 529ed1275263 ("net: phy: + * phy drivers should not set SUPPORTED_[Asym_]Pause") except + * using our validate call to the MAC, we rely upon the MAC + * clearing the bits from both supported and advertising fields. + */ + if (phylink_test(supported, Pause)) + phylink_set(config.advertising, Pause); + if (phylink_test(supported, Asym_Pause)) + phylink_set(config.advertising, Asym_Pause); + + ret = phylink_validate(pl, supported, &config); + if (ret) + return ret; + + phy->phylink = pl; + phy->phy_link_change = phylink_phy_change; + + netdev_info(pl->netdev, + "PHY [%s] driver [%s]\n", dev_name(&phy->mdio.dev), + phy->drv->name); + + mutex_lock(&phy->lock); + mutex_lock(&pl->state_mutex); + pl->netdev->phydev = phy; + pl->phydev = phy; + linkmode_copy(pl->supported, supported); + linkmode_copy(pl->link_config.advertising, config.advertising); + + /* Restrict the phy advertisment according to the MAC support. */ + ethtool_convert_link_mode_to_legacy_u32(&advertising, config.advertising); + phy->advertising = advertising; + mutex_unlock(&pl->state_mutex); + mutex_unlock(&phy->lock); + + netdev_dbg(pl->netdev, + "phy: setting supported %*pb advertising 0x%08x\n", + __ETHTOOL_LINK_MODE_MASK_NBITS, pl->supported, + phy->advertising); + + phy_start_machine(phy); + if (phy->irq > 0) + phy_start_interrupts(phy); + + return 0; +} + +int phylink_connect_phy(struct phylink *pl, struct phy_device *phy) +{ + int ret; + + ret = phy_attach_direct(pl->netdev, phy, 0, pl->link_interface); + if (ret) + return ret; + + ret = phylink_bringup_phy(pl, phy); + if (ret) + phy_detach(phy); + + return ret; +} +EXPORT_SYMBOL_GPL(phylink_connect_phy); + +int phylink_of_phy_connect(struct phylink *pl, struct device_node *dn) +{ + struct device_node *phy_node; + struct phy_device *phy_dev; + int ret; + + /* Fixed links are handled without needing a PHY */ + if (pl->link_an_mode == MLO_AN_FIXED) + return 0; + + phy_node = of_parse_phandle(dn, "phy-handle", 0); + if (!phy_node) + phy_node = of_parse_phandle(dn, "phy", 0); + if (!phy_node) + phy_node = of_parse_phandle(dn, "phy-device", 0); + + if (!phy_node) { + if (pl->link_an_mode == MLO_AN_PHY) { + netdev_err(pl->netdev, "unable to find PHY node\n"); + return -ENODEV; + } + return 0; + } + + phy_dev = of_phy_attach(pl->netdev, phy_node, 0, pl->link_interface); + /* We're done with the phy_node handle */ + of_node_put(phy_node); + + if (!phy_dev) + return -ENODEV; + + ret = phylink_bringup_phy(pl, phy_dev); + if (ret) + phy_detach(phy_dev); + + return ret; +} +EXPORT_SYMBOL_GPL(phylink_of_phy_connect); + +void phylink_disconnect_phy(struct phylink *pl) +{ + struct phy_device *phy; + + WARN_ON(!lockdep_rtnl_is_held()); + + phy = pl->phydev; + if (phy) { + mutex_lock(&phy->lock); + mutex_lock(&pl->state_mutex); + pl->netdev->phydev = NULL; + pl->phydev = NULL; + mutex_unlock(&pl->state_mutex); + mutex_unlock(&phy->lock); + flush_work(&pl->resolve); + + phy_disconnect(phy); + } +} +EXPORT_SYMBOL_GPL(phylink_disconnect_phy); + +void phylink_mac_change(struct phylink *pl, bool up) +{ + if (!up) + pl->mac_link_dropped = true; + phylink_run_resolve(pl); + netdev_dbg(pl->netdev, "mac link %s\n", up ? "up" : "down"); +} +EXPORT_SYMBOL_GPL(phylink_mac_change); + +void phylink_start(struct phylink *pl) +{ + WARN_ON(!lockdep_rtnl_is_held()); + + netdev_info(pl->netdev, "configuring for %s/%s link mode\n", + phylink_an_mode_str(pl->link_an_mode), + phy_modes(pl->link_config.interface)); + + /* Apply the link configuration to the MAC when starting. This allows + * a fixed-link to start with the correct parameters, and also + * ensures that we set the appropriate advertisment for Serdes links. + */ + phylink_resolve_flow(pl, &pl->link_config); + phylink_mac_config(pl, &pl->link_config); + + clear_bit(PHYLINK_DISABLE_STOPPED, &pl->phylink_disable_state); + phylink_run_resolve(pl); + + if (pl->netdev->sfp_bus) + sfp_upstream_start(pl->netdev->sfp_bus); + if (pl->phydev) + phy_start(pl->phydev); +} +EXPORT_SYMBOL_GPL(phylink_start); + +void phylink_stop(struct phylink *pl) +{ + WARN_ON(!lockdep_rtnl_is_held()); + + if (pl->phydev) + phy_stop(pl->phydev); + if (pl->netdev->sfp_bus) + sfp_upstream_stop(pl->netdev->sfp_bus); + + set_bit(PHYLINK_DISABLE_STOPPED, &pl->phylink_disable_state); + flush_work(&pl->resolve); +} +EXPORT_SYMBOL_GPL(phylink_stop); + +void phylink_ethtool_get_wol(struct phylink *pl, struct ethtool_wolinfo *wol) +{ + WARN_ON(!lockdep_rtnl_is_held()); + + wol->supported = 0; + wol->wolopts = 0; + + if (pl->phydev) + phy_ethtool_get_wol(pl->phydev, wol); +} +EXPORT_SYMBOL_GPL(phylink_ethtool_get_wol); + +int phylink_ethtool_set_wol(struct phylink *pl, struct ethtool_wolinfo *wol) +{ + int ret = -EOPNOTSUPP; + + WARN_ON(!lockdep_rtnl_is_held()); + + if (pl->phydev) + ret = phy_ethtool_set_wol(pl->phydev, wol); + + return ret; +} +EXPORT_SYMBOL_GPL(phylink_ethtool_set_wol); + +static void phylink_merge_link_mode(unsigned long *dst, const unsigned long *b) +{ + __ETHTOOL_DECLARE_LINK_MODE_MASK(mask); + + linkmode_zero(mask); + phylink_set_port_modes(mask); + + linkmode_and(dst, dst, mask); + linkmode_or(dst, dst, b); +} + +static void phylink_get_ksettings(const struct phylink_link_state *state, + struct ethtool_link_ksettings *kset) +{ + phylink_merge_link_mode(kset->link_modes.advertising, state->advertising); + linkmode_copy(kset->link_modes.lp_advertising, state->lp_advertising); + kset->base.speed = state->speed; + kset->base.duplex = state->duplex; + kset->base.autoneg = state->an_enabled ? AUTONEG_ENABLE : + AUTONEG_DISABLE; +} + +int phylink_ethtool_ksettings_get(struct phylink *pl, + struct ethtool_link_ksettings *kset) +{ + struct phylink_link_state link_state; + + WARN_ON(!lockdep_rtnl_is_held()); + + if (pl->phydev) { + phy_ethtool_ksettings_get(pl->phydev, kset); + } else { + kset->base.port = pl->link_port; + } + + linkmode_copy(kset->link_modes.supported, pl->supported); + + switch (pl->link_an_mode) { + case MLO_AN_FIXED: + /* We are using fixed settings. Report these as the + * current link settings - and note that these also + * represent the supported speeds/duplex/pause modes. + */ + phylink_get_fixed_state(pl, &link_state); + phylink_get_ksettings(&link_state, kset); + break; + + case MLO_AN_SGMII: + /* If there is a phy attached, then use the reported + * settings from the phy with no modification. + */ + if (pl->phydev) + break; + + case MLO_AN_8023Z: + phylink_get_mac_state(pl, &link_state); + + /* The MAC is reporting the link results from its own PCS + * layer via in-band status. Report these as the current + * link settings. + */ + phylink_get_ksettings(&link_state, kset); + break; + } + + return 0; +} +EXPORT_SYMBOL_GPL(phylink_ethtool_ksettings_get); + +int phylink_ethtool_ksettings_set(struct phylink *pl, + const struct ethtool_link_ksettings *kset) +{ + struct ethtool_link_ksettings our_kset; + struct phylink_link_state config; + int ret; + + WARN_ON(!lockdep_rtnl_is_held()); + + if (kset->base.autoneg != AUTONEG_DISABLE && + kset->base.autoneg != AUTONEG_ENABLE) + return -EINVAL; + + config = pl->link_config; + + /* Mask out unsupported advertisments */ + linkmode_and(config.advertising, kset->link_modes.advertising, + pl->supported); + + /* FIXME: should we reject autoneg if phy/mac does not support it? */ + if (kset->base.autoneg == AUTONEG_DISABLE) { + const struct phy_setting *s; + + /* Autonegotiation disabled, select a suitable speed and + * duplex. + */ + s = phy_lookup_setting(kset->base.speed, kset->base.duplex, + pl->supported, + __ETHTOOL_LINK_MODE_MASK_NBITS, false); + if (!s) + return -EINVAL; + + /* If we have a fixed link (as specified by firmware), refuse + * to change link parameters. + */ + if (pl->link_an_mode == MLO_AN_FIXED && + (s->speed != pl->link_config.speed || + s->duplex != pl->link_config.duplex)) + return -EINVAL; + + config.speed = s->speed; + config.duplex = s->duplex; + config.an_enabled = false; + + __clear_bit(ETHTOOL_LINK_MODE_Autoneg_BIT, config.advertising); + } else { + /* If we have a fixed link, refuse to enable autonegotiation */ + if (pl->link_an_mode == MLO_AN_FIXED) + return -EINVAL; + + config.speed = SPEED_UNKNOWN; + config.duplex = DUPLEX_UNKNOWN; + config.an_enabled = true; + + __set_bit(ETHTOOL_LINK_MODE_Autoneg_BIT, config.advertising); + } + + if (phylink_validate(pl, pl->supported, &config)) + return -EINVAL; + + /* If autonegotiation is enabled, we must have an advertisment */ + if (config.an_enabled && phylink_is_empty_linkmode(config.advertising)) + return -EINVAL; + + our_kset = *kset; + linkmode_copy(our_kset.link_modes.advertising, config.advertising); + our_kset.base.speed = config.speed; + our_kset.base.duplex = config.duplex; + + /* If we have a PHY, configure the phy */ + if (pl->phydev) { + ret = phy_ethtool_ksettings_set(pl->phydev, &our_kset); + if (ret) + return ret; + } + + mutex_lock(&pl->state_mutex); + /* Configure the MAC to match the new settings */ + linkmode_copy(pl->link_config.advertising, our_kset.link_modes.advertising); + pl->link_config.speed = our_kset.base.speed; + pl->link_config.duplex = our_kset.base.duplex; + pl->link_config.an_enabled = our_kset.base.autoneg != AUTONEG_DISABLE; + + if (!test_bit(PHYLINK_DISABLE_STOPPED, &pl->phylink_disable_state)) { + phylink_mac_config(pl, &pl->link_config); + phylink_mac_an_restart(pl); + } + mutex_unlock(&pl->state_mutex); + + return ret; +} +EXPORT_SYMBOL_GPL(phylink_ethtool_ksettings_set); + +int phylink_ethtool_nway_reset(struct phylink *pl) +{ + int ret = 0; + + WARN_ON(!lockdep_rtnl_is_held()); + + if (pl->phydev) + ret = phy_restart_aneg(pl->phydev); + phylink_mac_an_restart(pl); + + return ret; +} +EXPORT_SYMBOL_GPL(phylink_ethtool_nway_reset); + +void phylink_ethtool_get_pauseparam(struct phylink *pl, + struct ethtool_pauseparam *pause) +{ + WARN_ON(!lockdep_rtnl_is_held()); + + pause->autoneg = !!(pl->link_config.pause & MLO_PAUSE_AN); + pause->rx_pause = !!(pl->link_config.pause & MLO_PAUSE_RX); + pause->tx_pause = !!(pl->link_config.pause & MLO_PAUSE_TX); +} +EXPORT_SYMBOL_GPL(phylink_ethtool_get_pauseparam); + +int phylink_ethtool_set_pauseparam(struct phylink *pl, + struct ethtool_pauseparam *pause) +{ + struct phylink_link_state *config = &pl->link_config; + + WARN_ON(!lockdep_rtnl_is_held()); + + if (!phylink_test(pl->supported, Pause) && + !phylink_test(pl->supported, Asym_Pause)) + return -EOPNOTSUPP; + + if (!phylink_test(pl->supported, Asym_Pause) && + !pause->autoneg && pause->rx_pause != pause->tx_pause) + return -EINVAL; + + config->pause &= ~(MLO_PAUSE_AN | MLO_PAUSE_TXRX_MASK); + + if (pause->autoneg) + config->pause |= MLO_PAUSE_AN; + if (pause->rx_pause) + config->pause |= MLO_PAUSE_RX; + if (pause->tx_pause) + config->pause |= MLO_PAUSE_TX; + + if (!test_bit(PHYLINK_DISABLE_STOPPED, &pl->phylink_disable_state)) { + switch (pl->link_an_mode) { + case MLO_AN_PHY: + /* Silently mark the carrier down, and then trigger a resolve */ + netif_carrier_off(pl->netdev); + phylink_run_resolve(pl); + break; + + case MLO_AN_FIXED: + /* Should we allow fixed links to change against the config? */ + phylink_resolve_flow(pl, config); + phylink_mac_config(pl, config); + break; + + case MLO_AN_SGMII: + case MLO_AN_8023Z: + phylink_mac_config(pl, config); + phylink_mac_an_restart(pl); + break; + } + } + + return 0; +} +EXPORT_SYMBOL_GPL(phylink_ethtool_set_pauseparam); + +int phylink_init_eee(struct phylink *pl, bool clk_stop_enable) +{ + int ret = -EPROTONOSUPPORT; + + WARN_ON(!lockdep_rtnl_is_held()); + + if (pl->phydev) + ret = phy_init_eee(pl->phydev, clk_stop_enable); + + return ret; +} +EXPORT_SYMBOL_GPL(phylink_init_eee); + +int phylink_get_eee_err(struct phylink *pl) +{ + int ret = 0; + + WARN_ON(!lockdep_rtnl_is_held()); + + if (pl->phydev) + ret = phy_get_eee_err(pl->phydev); + + return ret; +} +EXPORT_SYMBOL_GPL(phylink_get_eee_err); + +int phylink_ethtool_get_eee(struct phylink *pl, struct ethtool_eee *eee) +{ + int ret = -EOPNOTSUPP; + + WARN_ON(!lockdep_rtnl_is_held()); + + if (pl->phydev) + ret = phy_ethtool_get_eee(pl->phydev, eee); + + return ret; +} +EXPORT_SYMBOL_GPL(phylink_ethtool_get_eee); + +int phylink_ethtool_set_eee(struct phylink *pl, struct ethtool_eee *eee) +{ + int ret = -EOPNOTSUPP; + + WARN_ON(!lockdep_rtnl_is_held()); + + if (pl->phydev) + ret = phy_ethtool_set_eee(pl->phydev, eee); + + return ret; +} +EXPORT_SYMBOL_GPL(phylink_ethtool_set_eee); + +/* This emulates MII registers for a fixed-mode phy operating as per the + * passed in state. "aneg" defines if we report negotiation is possible. + * + * FIXME: should deal with negotiation state too. + */ +static int phylink_mii_emul_read(struct net_device *ndev, unsigned int reg, + struct phylink_link_state *state, bool aneg) +{ + struct fixed_phy_status fs; + int val; + + fs.link = state->link; + fs.speed = state->speed; + fs.duplex = state->duplex; + fs.pause = state->pause & MLO_PAUSE_SYM; + fs.asym_pause = state->pause & MLO_PAUSE_ASYM; + + val = swphy_read_reg(reg, &fs); + if (reg == MII_BMSR) { + if (!state->an_complete) + val &= ~BMSR_ANEGCOMPLETE; + if (!aneg) + val &= ~BMSR_ANEGCAPABLE; + } + return val; +} + +static int phylink_phy_read(struct phylink *pl, unsigned int phy_id, + unsigned int reg) +{ + struct phy_device *phydev = pl->phydev; + int prtad, devad; + + if (mdio_phy_id_is_c45(phy_id)) { + prtad = mdio_phy_id_prtad(phy_id); + devad = mdio_phy_id_devad(phy_id); + devad = MII_ADDR_C45 | devad << 16 | reg; + } else if (phydev->is_c45) { + switch (reg) { + case MII_BMCR: + case MII_BMSR: + case MII_PHYSID1: + case MII_PHYSID2: + devad = __ffs(phydev->c45_ids.devices_in_package); + break; + case MII_ADVERTISE: + case MII_LPA: + if (!(phydev->c45_ids.devices_in_package & MDIO_DEVS_AN)) + return -EINVAL; + devad = MDIO_MMD_AN; + if (reg == MII_ADVERTISE) + reg = MDIO_AN_ADVERTISE; + else + reg = MDIO_AN_LPA; + break; + default: + return -EINVAL; + } + prtad = phy_id; + devad = MII_ADDR_C45 | devad << 16 | reg; + } else { + prtad = phy_id; + devad = reg; + } + return mdiobus_read(pl->phydev->mdio.bus, prtad, devad); +} + +static int phylink_phy_write(struct phylink *pl, unsigned int phy_id, + unsigned int reg, unsigned int val) +{ + struct phy_device *phydev = pl->phydev; + int prtad, devad; + + if (mdio_phy_id_is_c45(phy_id)) { + prtad = mdio_phy_id_prtad(phy_id); + devad = mdio_phy_id_devad(phy_id); + devad = MII_ADDR_C45 | devad << 16 | reg; + } else if (phydev->is_c45) { + switch (reg) { + case MII_BMCR: + case MII_BMSR: + case MII_PHYSID1: + case MII_PHYSID2: + devad = __ffs(phydev->c45_ids.devices_in_package); + break; + case MII_ADVERTISE: + case MII_LPA: + if (!(phydev->c45_ids.devices_in_package & MDIO_DEVS_AN)) + return -EINVAL; + devad = MDIO_MMD_AN; + if (reg == MII_ADVERTISE) + reg = MDIO_AN_ADVERTISE; + else + reg = MDIO_AN_LPA; + break; + default: + return -EINVAL; + } + prtad = phy_id; + devad = MII_ADDR_C45 | devad << 16 | reg; + } else { + prtad = phy_id; + devad = reg; + } + + return mdiobus_write(phydev->mdio.bus, prtad, devad, val); +} + +static int phylink_mii_read(struct phylink *pl, unsigned int phy_id, + unsigned int reg) +{ + struct phylink_link_state state; + int val = 0xffff; + + switch (pl->link_an_mode) { + case MLO_AN_FIXED: + if (phy_id == 0) { + phylink_get_fixed_state(pl, &state); + val = phylink_mii_emul_read(pl->netdev, reg, &state, + true); + } + break; + + case MLO_AN_PHY: + return -EOPNOTSUPP; + + case MLO_AN_SGMII: + /* No phy, fall through to 8023z method */ + case MLO_AN_8023Z: + if (phy_id == 0) { + val = phylink_get_mac_state(pl, &state); + if (val < 0) + return val; + + val = phylink_mii_emul_read(pl->netdev, reg, &state, + true); + } + break; + } + + return val & 0xffff; +} + +static int phylink_mii_write(struct phylink *pl, unsigned int phy_id, + unsigned int reg, unsigned int val) +{ + switch (pl->link_an_mode) { + case MLO_AN_FIXED: + break; + + case MLO_AN_PHY: + return -EOPNOTSUPP; + + case MLO_AN_SGMII: + /* No phy, fall through to 8023z method */ + case MLO_AN_8023Z: + break; + } + + return 0; +} + +int phylink_mii_ioctl(struct phylink *pl, struct ifreq *ifr, int cmd) +{ + struct mii_ioctl_data *mii = if_mii(ifr); + int ret; + + WARN_ON(!lockdep_rtnl_is_held()); + + if (pl->phydev) { + /* PHYs only exist for MLO_AN_PHY and MLO_AN_SGMII */ + switch (cmd) { + case SIOCGMIIPHY: + mii->phy_id = pl->phydev->mdio.addr; + + case SIOCGMIIREG: + ret = phylink_phy_read(pl, mii->phy_id, mii->reg_num); + if (ret >= 0) { + mii->val_out = ret; + ret = 0; + } + break; + + case SIOCSMIIREG: + ret = phylink_phy_write(pl, mii->phy_id, mii->reg_num, + mii->val_in); + break; + + default: + ret = phy_mii_ioctl(pl->phydev, ifr, cmd); + break; + } + } else { + switch (cmd) { + case SIOCGMIIPHY: + mii->phy_id = 0; + + case SIOCGMIIREG: + ret = phylink_mii_read(pl, mii->phy_id, mii->reg_num); + if (ret >= 0) { + mii->val_out = ret; + ret = 0; + } + break; + + case SIOCSMIIREG: + ret = phylink_mii_write(pl, mii->phy_id, mii->reg_num, + mii->val_in); + break; + + default: + ret = -EOPNOTSUPP; + break; + } + } + + return ret; +} +EXPORT_SYMBOL_GPL(phylink_mii_ioctl); + + + +static int phylink_sfp_module_insert(void *upstream, + const struct sfp_eeprom_id *id) +{ + struct phylink *pl = upstream; + __ETHTOOL_DECLARE_LINK_MODE_MASK(support) = { 0, }; + struct phylink_link_state config; + phy_interface_t iface; + int mode, ret = 0; + bool changed; + u8 port; + + sfp_parse_support(pl->sfp_bus, id, support); + port = sfp_parse_port(pl->sfp_bus, id, support); + iface = sfp_parse_interface(pl->sfp_bus, id); + + WARN_ON(!lockdep_rtnl_is_held()); + + switch (iface) { + case PHY_INTERFACE_MODE_SGMII: + mode = MLO_AN_SGMII; + break; + case PHY_INTERFACE_MODE_1000BASEX: + mode = MLO_AN_8023Z; + break; + default: + return -EINVAL; + } + + memset(&config, 0, sizeof(config)); + linkmode_copy(config.advertising, support); + config.interface = iface; + config.speed = SPEED_UNKNOWN; + config.duplex = DUPLEX_UNKNOWN; + config.pause = MLO_PAUSE_AN; + config.an_enabled = pl->link_config.an_enabled; + + /* Ignore errors if we're expecting a PHY to attach later */ + ret = phylink_validate(pl, support, &config); + if (ret) { + netdev_err(pl->netdev, "validation of %s/%s with support %*pb failed: %d\n", + phylink_an_mode_str(mode), phy_modes(config.interface), + __ETHTOOL_LINK_MODE_MASK_NBITS, support, ret); + return ret; + } + + netdev_dbg(pl->netdev, "requesting link mode %s/%s with support %*pb\n", + phylink_an_mode_str(mode), phy_modes(config.interface), + __ETHTOOL_LINK_MODE_MASK_NBITS, support); + + if (mode == MLO_AN_8023Z && pl->phydev) + return -EINVAL; + + changed = !bitmap_equal(pl->supported, support, + __ETHTOOL_LINK_MODE_MASK_NBITS); + if (changed) { + linkmode_copy(pl->supported, support); + linkmode_copy(pl->link_config.advertising, config.advertising); + } + + if (pl->link_an_mode != mode || + pl->link_config.interface != config.interface) { + pl->link_config.interface = config.interface; + pl->link_an_mode = mode; + + changed = true; + + netdev_info(pl->netdev, "switched to %s/%s link mode\n", + phylink_an_mode_str(mode), + phy_modes(config.interface)); + } + + pl->link_port = port; + + if (changed && !test_bit(PHYLINK_DISABLE_STOPPED, + &pl->phylink_disable_state)) + phylink_mac_config(pl, &pl->link_config); + + return ret; +} + +static void phylink_sfp_link_down(void *upstream) +{ + struct phylink *pl = upstream; + + WARN_ON(!lockdep_rtnl_is_held()); + + set_bit(PHYLINK_DISABLE_LINK, &pl->phylink_disable_state); + flush_work(&pl->resolve); + + netif_carrier_off(pl->netdev); +} + +static void phylink_sfp_link_up(void *upstream) +{ + struct phylink *pl = upstream; + + WARN_ON(!lockdep_rtnl_is_held()); + + clear_bit(PHYLINK_DISABLE_LINK, &pl->phylink_disable_state); + phylink_run_resolve(pl); +} + +static int phylink_sfp_connect_phy(void *upstream, struct phy_device *phy) +{ + return phylink_connect_phy(upstream, phy); +} + +static void phylink_sfp_disconnect_phy(void *upstream) +{ + phylink_disconnect_phy(upstream); +} + +static const struct sfp_upstream_ops sfp_phylink_ops = { + .module_insert = phylink_sfp_module_insert, + .link_up = phylink_sfp_link_up, + .link_down = phylink_sfp_link_down, + .connect_phy = phylink_sfp_connect_phy, + .disconnect_phy = phylink_sfp_disconnect_phy, +}; + +MODULE_LICENSE("GPL"); diff --git a/drivers/net/phy/sfp-bus.c b/drivers/net/phy/sfp-bus.c new file mode 100644 index 000000000000..8d42a8b0ccdd --- /dev/null +++ b/drivers/net/phy/sfp-bus.c @@ -0,0 +1,473 @@ +#include <linux/export.h> +#include <linux/kref.h> +#include <linux/list.h> +#include <linux/mutex.h> +#include <linux/phylink.h> +#include <linux/rtnetlink.h> +#include <linux/slab.h> + +#include "sfp.h" + +struct sfp_bus { + struct kref kref; + struct list_head node; + struct device_node *device_node; + + const struct sfp_socket_ops *socket_ops; + struct device *sfp_dev; + struct sfp *sfp; + + const struct sfp_upstream_ops *upstream_ops; + void *upstream; + struct net_device *netdev; + struct phy_device *phydev; + + bool registered; + bool started; +}; + + +int sfp_parse_port(struct sfp_bus *bus, const struct sfp_eeprom_id *id, + unsigned long *support) +{ + int port; + + /* port is the physical connector, set this from the connector field. */ + switch (id->base.connector) { + case SFP_CONNECTOR_SC: + case SFP_CONNECTOR_FIBERJACK: + case SFP_CONNECTOR_LC: + case SFP_CONNECTOR_MT_RJ: + case SFP_CONNECTOR_MU: + case SFP_CONNECTOR_OPTICAL_PIGTAIL: + if (support) + phylink_set(support, FIBRE); + port = PORT_FIBRE; + break; + + case SFP_CONNECTOR_RJ45: + if (support) + phylink_set(support, TP); + port = PORT_TP; + break; + + case SFP_CONNECTOR_UNSPEC: + if (id->base.e1000_base_t) { + if (support) + phylink_set(support, TP); + port = PORT_TP; + break; + } + /* fallthrough */ + case SFP_CONNECTOR_SG: /* guess */ + case SFP_CONNECTOR_MPO_1X12: + case SFP_CONNECTOR_MPO_2X16: + case SFP_CONNECTOR_HSSDC_II: + case SFP_CONNECTOR_COPPER_PIGTAIL: + case SFP_CONNECTOR_NOSEPARATE: + case SFP_CONNECTOR_MXC_2X16: + port = PORT_OTHER; + break; + default: + dev_warn(bus->sfp_dev, "SFP: unknown connector id 0x%02x\n", + id->base.connector); + port = PORT_OTHER; + break; + } + + return port; +} +EXPORT_SYMBOL_GPL(sfp_parse_port); + +phy_interface_t sfp_parse_interface(struct sfp_bus *bus, + const struct sfp_eeprom_id *id) +{ + phy_interface_t iface; + + /* Setting the serdes link mode is guesswork: there's no field in + * the EEPROM which indicates what mode should be used. + * + * If the module wants 64b66b, then it must be >= 10G. + * + * If it's a gigabit-only fiber module, it probably does not have + * a PHY, so switch to 802.3z negotiation mode. Otherwise, switch + * to SGMII mode (which is required to support non-gigabit speeds). + */ + switch (id->base.encoding) { + case SFP_ENCODING_8472_64B66B: + iface = PHY_INTERFACE_MODE_10GKR; + break; + + case SFP_ENCODING_8B10B: + if (!id->base.e1000_base_t && + !id->base.e100_base_lx && + !id->base.e100_base_fx) + iface = PHY_INTERFACE_MODE_1000BASEX; + else + iface = PHY_INTERFACE_MODE_SGMII; + break; + + default: + iface = PHY_INTERFACE_MODE_NA; + dev_err(bus->sfp_dev, + "SFP module encoding does not support 8b10b nor 64b66b\n"); + break; + } + + return iface; +} +EXPORT_SYMBOL_GPL(sfp_parse_interface); + +void sfp_parse_support(struct sfp_bus *bus, const struct sfp_eeprom_id *id, + unsigned long *support) +{ + phylink_set(support, Autoneg); + phylink_set(support, Pause); + phylink_set(support, Asym_Pause); + + /* Set ethtool support from the compliance fields. */ + if (id->base.e10g_base_sr) + phylink_set(support, 10000baseSR_Full); + if (id->base.e10g_base_lr) + phylink_set(support, 10000baseLR_Full); + if (id->base.e10g_base_lrm) + phylink_set(support, 10000baseLRM_Full); + if (id->base.e10g_base_er) + phylink_set(support, 10000baseER_Full); + if (id->base.e1000_base_sx || + id->base.e1000_base_lx || + id->base.e1000_base_cx) + phylink_set(support, 1000baseX_Full); + if (id->base.e1000_base_t) { + phylink_set(support, 1000baseT_Half); + phylink_set(support, 1000baseT_Full); + } + + switch (id->base.extended_cc) { + case 0x00: /* Unspecified */ + break; + case 0x02: /* 100Gbase-SR4 or 25Gbase-SR */ + phylink_set(support, 100000baseSR4_Full); + phylink_set(support, 25000baseSR_Full); + break; + case 0x03: /* 100Gbase-LR4 or 25Gbase-LR */ + case 0x04: /* 100Gbase-ER4 or 25Gbase-ER */ + phylink_set(support, 100000baseLR4_ER4_Full); + break; + case 0x0b: /* 100Gbase-CR4 or 25Gbase-CR CA-L */ + case 0x0c: /* 25Gbase-CR CA-S */ + case 0x0d: /* 25Gbase-CR CA-N */ + phylink_set(support, 100000baseCR4_Full); + phylink_set(support, 25000baseCR_Full); + break; + default: + dev_warn(bus->sfp_dev, + "Unknown/unsupported extended compliance code: 0x%02x\n", + id->base.extended_cc); + break; + } + + /* For fibre channel SFP, derive possible BaseX modes */ + if (id->base.fc_speed_100 || + id->base.fc_speed_200 || + id->base.fc_speed_400) { + if (id->base.br_nominal >= 31) + phylink_set(support, 2500baseX_Full); + if (id->base.br_nominal >= 12) + phylink_set(support, 1000baseX_Full); + } + + switch (id->base.connector) { + case SFP_CONNECTOR_SC: + case SFP_CONNECTOR_FIBERJACK: + case SFP_CONNECTOR_LC: + case SFP_CONNECTOR_MT_RJ: + case SFP_CONNECTOR_MU: + case SFP_CONNECTOR_OPTICAL_PIGTAIL: + break; + + case SFP_CONNECTOR_UNSPEC: + if (id->base.e1000_base_t) + break; + + case SFP_CONNECTOR_SG: /* guess */ + case SFP_CONNECTOR_MPO_1X12: + case SFP_CONNECTOR_MPO_2X16: + case SFP_CONNECTOR_HSSDC_II: + case SFP_CONNECTOR_COPPER_PIGTAIL: + case SFP_CONNECTOR_NOSEPARATE: + case SFP_CONNECTOR_MXC_2X16: + default: + /* a guess at the supported link modes */ + dev_warn(bus->sfp_dev, + "Guessing link modes, please report...\n"); + phylink_set(support, 1000baseT_Half); + phylink_set(support, 1000baseT_Full); + break; + } +} +EXPORT_SYMBOL_GPL(sfp_parse_support); + + +static LIST_HEAD(sfp_buses); +static DEFINE_MUTEX(sfp_mutex); + +static const struct sfp_upstream_ops *sfp_get_upstream_ops(struct sfp_bus *bus) +{ + return bus->registered ? bus->upstream_ops : NULL; +} + +static struct sfp_bus *sfp_bus_get(struct device_node *np) +{ + struct sfp_bus *sfp, *new, *found = NULL; + + new = kzalloc(sizeof(*new), GFP_KERNEL); + + mutex_lock(&sfp_mutex); + + list_for_each_entry(sfp, &sfp_buses, node) { + if (sfp->device_node == np) { + kref_get(&sfp->kref); + found = sfp; + break; + } + } + + if (!found && new) { + kref_init(&new->kref); + new->device_node = np; + list_add(&new->node, &sfp_buses); + found = new; + new = NULL; + } + + mutex_unlock(&sfp_mutex); + + kfree(new); + + return found; +} + +static void sfp_bus_release(struct kref *kref) __releases(sfp_mutex) +{ + struct sfp_bus *bus = container_of(kref, struct sfp_bus, kref); + + list_del(&bus->node); + mutex_unlock(&sfp_mutex); + kfree(bus); +} + +static void sfp_bus_put(struct sfp_bus *bus) +{ + kref_put_mutex(&bus->kref, sfp_bus_release, &sfp_mutex); +} + +static int sfp_register_bus(struct sfp_bus *bus) +{ + const struct sfp_upstream_ops *ops = bus->upstream_ops; + int ret; + + if (ops) { + if (ops->link_down) + ops->link_down(bus->upstream); + if (ops->connect_phy && bus->phydev) { + ret = ops->connect_phy(bus->upstream, bus->phydev); + if (ret) + return ret; + } + } + if (bus->started) + bus->socket_ops->start(bus->sfp); + bus->netdev->sfp_bus = bus; + bus->registered = true; + return 0; +} + +static void sfp_unregister_bus(struct sfp_bus *bus) +{ + const struct sfp_upstream_ops *ops = bus->upstream_ops; + + if (bus->registered) { + if (bus->started) + bus->socket_ops->stop(bus->sfp); + if (bus->phydev && ops && ops->disconnect_phy) + ops->disconnect_phy(bus->upstream); + } + bus->netdev->sfp_bus = NULL; + bus->registered = false; +} + + +int sfp_get_module_info(struct sfp_bus *bus, struct ethtool_modinfo *modinfo) +{ + return bus->socket_ops->module_info(bus->sfp, modinfo); +} +EXPORT_SYMBOL_GPL(sfp_get_module_info); + +int sfp_get_module_eeprom(struct sfp_bus *bus, struct ethtool_eeprom *ee, + u8 *data) +{ + return bus->socket_ops->module_eeprom(bus->sfp, ee, data); +} +EXPORT_SYMBOL_GPL(sfp_get_module_eeprom); + +void sfp_upstream_start(struct sfp_bus *bus) +{ + if (bus->registered) + bus->socket_ops->start(bus->sfp); + bus->started = true; +} +EXPORT_SYMBOL_GPL(sfp_upstream_start); + +void sfp_upstream_stop(struct sfp_bus *bus) +{ + if (bus->registered) + bus->socket_ops->stop(bus->sfp); + bus->started = false; +} +EXPORT_SYMBOL_GPL(sfp_upstream_stop); + +struct sfp_bus *sfp_register_upstream(struct device_node *np, + struct net_device *ndev, void *upstream, + const struct sfp_upstream_ops *ops) +{ + struct sfp_bus *bus = sfp_bus_get(np); + int ret = 0; + + if (bus) { + rtnl_lock(); + bus->upstream_ops = ops; + bus->upstream = upstream; + bus->netdev = ndev; + + if (bus->sfp) + ret = sfp_register_bus(bus); + rtnl_unlock(); + } + + if (ret) { + sfp_bus_put(bus); + bus = NULL; + } + + return bus; +} +EXPORT_SYMBOL_GPL(sfp_register_upstream); + +void sfp_unregister_upstream(struct sfp_bus *bus) +{ + rtnl_lock(); + sfp_unregister_bus(bus); + bus->upstream = NULL; + bus->netdev = NULL; + rtnl_unlock(); + + sfp_bus_put(bus); +} +EXPORT_SYMBOL_GPL(sfp_unregister_upstream); + + +/* Socket driver entry points */ +int sfp_add_phy(struct sfp_bus *bus, struct phy_device *phydev) +{ + const struct sfp_upstream_ops *ops = sfp_get_upstream_ops(bus); + int ret = 0; + + if (ops && ops->connect_phy) + ret = ops->connect_phy(bus->upstream, phydev); + + if (ret == 0) + bus->phydev = phydev; + + return ret; +} +EXPORT_SYMBOL_GPL(sfp_add_phy); + +void sfp_remove_phy(struct sfp_bus *bus) +{ + const struct sfp_upstream_ops *ops = sfp_get_upstream_ops(bus); + + if (ops && ops->disconnect_phy) + ops->disconnect_phy(bus->upstream); + bus->phydev = NULL; +} +EXPORT_SYMBOL_GPL(sfp_remove_phy); + + +void sfp_link_up(struct sfp_bus *bus) +{ + const struct sfp_upstream_ops *ops = sfp_get_upstream_ops(bus); + + if (ops && ops->link_up) + ops->link_up(bus->upstream); +} +EXPORT_SYMBOL_GPL(sfp_link_up); + +void sfp_link_down(struct sfp_bus *bus) +{ + const struct sfp_upstream_ops *ops = sfp_get_upstream_ops(bus); + + if (ops && ops->link_down) + ops->link_down(bus->upstream); +} +EXPORT_SYMBOL_GPL(sfp_link_down); + +int sfp_module_insert(struct sfp_bus *bus, const struct sfp_eeprom_id *id) +{ + const struct sfp_upstream_ops *ops = sfp_get_upstream_ops(bus); + int ret = 0; + + if (ops && ops->module_insert) + ret = ops->module_insert(bus->upstream, id); + + return ret; +} +EXPORT_SYMBOL_GPL(sfp_module_insert); + +void sfp_module_remove(struct sfp_bus *bus) +{ + const struct sfp_upstream_ops *ops = sfp_get_upstream_ops(bus); + + if (ops && ops->module_remove) + ops->module_remove(bus->upstream); +} +EXPORT_SYMBOL_GPL(sfp_module_remove); + +struct sfp_bus *sfp_register_socket(struct device *dev, struct sfp *sfp, + const struct sfp_socket_ops *ops) +{ + struct sfp_bus *bus = sfp_bus_get(dev->of_node); + int ret = 0; + + if (bus) { + rtnl_lock(); + bus->sfp_dev = dev; + bus->sfp = sfp; + bus->socket_ops = ops; + + if (bus->netdev) + ret = sfp_register_bus(bus); + rtnl_unlock(); + } + + if (ret) { + sfp_bus_put(bus); + bus = NULL; + } + + return bus; +} +EXPORT_SYMBOL_GPL(sfp_register_socket); + +void sfp_unregister_socket(struct sfp_bus *bus) +{ + rtnl_lock(); + sfp_unregister_bus(bus); + bus->sfp_dev = NULL; + bus->sfp = NULL; + bus->socket_ops = NULL; + rtnl_unlock(); + + sfp_bus_put(bus); +} +EXPORT_SYMBOL_GPL(sfp_unregister_socket); diff --git a/drivers/net/phy/sfp.c b/drivers/net/phy/sfp.c new file mode 100644 index 000000000000..08b4a5494912 --- /dev/null +++ b/drivers/net/phy/sfp.c @@ -0,0 +1,1168 @@ +#include <linux/delay.h> +#include <linux/gpio.h> +#include <linux/i2c.h> +#include <linux/interrupt.h> +#include <linux/jiffies.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/of.h> +#include <linux/phy.h> +#include <linux/platform_device.h> +#include <linux/rtnetlink.h> +#include <linux/slab.h> +#include <linux/workqueue.h> + +#include "mdio-i2c.h" +#include "sfp.h" +#include "swphy.h" + +enum { + GPIO_MODDEF0, + GPIO_LOS, + GPIO_TX_FAULT, + GPIO_TX_DISABLE, + GPIO_RATE_SELECT, + GPIO_MAX, + + SFP_F_PRESENT = BIT(GPIO_MODDEF0), + SFP_F_LOS = BIT(GPIO_LOS), + SFP_F_TX_FAULT = BIT(GPIO_TX_FAULT), + SFP_F_TX_DISABLE = BIT(GPIO_TX_DISABLE), + SFP_F_RATE_SELECT = BIT(GPIO_RATE_SELECT), + + SFP_E_INSERT = 0, + SFP_E_REMOVE, + SFP_E_DEV_DOWN, + SFP_E_DEV_UP, + SFP_E_TX_FAULT, + SFP_E_TX_CLEAR, + SFP_E_LOS_HIGH, + SFP_E_LOS_LOW, + SFP_E_TIMEOUT, + + SFP_MOD_EMPTY = 0, + SFP_MOD_PROBE, + SFP_MOD_PRESENT, + SFP_MOD_ERROR, + + SFP_DEV_DOWN = 0, + SFP_DEV_UP, + + SFP_S_DOWN = 0, + SFP_S_INIT, + SFP_S_WAIT_LOS, + SFP_S_LINK_UP, + SFP_S_TX_FAULT, + SFP_S_REINIT, + SFP_S_TX_DISABLE, +}; + +static const char *gpio_of_names[] = { + "moddef0", + "los", + "tx-fault", + "tx-disable", + "rate-select", +}; + +static const enum gpiod_flags gpio_flags[] = { + GPIOD_IN, + GPIOD_IN, + GPIOD_IN, + GPIOD_ASIS, + GPIOD_ASIS, +}; + +#define T_INIT_JIFFIES msecs_to_jiffies(300) +#define T_RESET_US 10 +#define T_FAULT_RECOVER msecs_to_jiffies(1000) + +/* SFP module presence detection is poor: the three MOD DEF signals are + * the same length on the PCB, which means it's possible for MOD DEF 0 to + * connect before the I2C bus on MOD DEF 1/2. + * + * The SFP MSA specifies 300ms as t_init (the time taken for TX_FAULT to + * be deasserted) but makes no mention of the earliest time before we can + * access the I2C EEPROM. However, Avago modules require 300ms. + */ +#define T_PROBE_INIT msecs_to_jiffies(300) +#define T_PROBE_RETRY msecs_to_jiffies(100) + +/* + * SFP modules appear to always have their PHY configured for bus address + * 0x56 (which with mdio-i2c, translates to a PHY address of 22). + */ +#define SFP_PHY_ADDR 22 + +/* + * Give this long for the PHY to reset. + */ +#define T_PHY_RESET_MS 50 + +static DEFINE_MUTEX(sfp_mutex); + +struct sfp { + struct device *dev; + struct i2c_adapter *i2c; + struct mii_bus *i2c_mii; + struct sfp_bus *sfp_bus; + struct phy_device *mod_phy; + + unsigned int (*get_state)(struct sfp *); + void (*set_state)(struct sfp *, unsigned int); + int (*read)(struct sfp *, bool, u8, void *, size_t); + + struct gpio_desc *gpio[GPIO_MAX]; + + unsigned int state; + struct delayed_work poll; + struct delayed_work timeout; + struct mutex sm_mutex; + unsigned char sm_mod_state; + unsigned char sm_dev_state; + unsigned short sm_state; + unsigned int sm_retries; + + struct sfp_eeprom_id id; +}; + +static unsigned long poll_jiffies; + +static unsigned int sfp_gpio_get_state(struct sfp *sfp) +{ + unsigned int i, state, v; + + for (i = state = 0; i < GPIO_MAX; i++) { + if (gpio_flags[i] != GPIOD_IN || !sfp->gpio[i]) + continue; + + v = gpiod_get_value_cansleep(sfp->gpio[i]); + if (v) + state |= BIT(i); + } + + return state; +} + +static void sfp_gpio_set_state(struct sfp *sfp, unsigned int state) +{ + if (state & SFP_F_PRESENT) { + /* If the module is present, drive the signals */ + if (sfp->gpio[GPIO_TX_DISABLE]) + gpiod_direction_output(sfp->gpio[GPIO_TX_DISABLE], + state & SFP_F_TX_DISABLE); + if (state & SFP_F_RATE_SELECT) + gpiod_direction_output(sfp->gpio[GPIO_RATE_SELECT], + state & SFP_F_RATE_SELECT); + } else { + /* Otherwise, let them float to the pull-ups */ + if (sfp->gpio[GPIO_TX_DISABLE]) + gpiod_direction_input(sfp->gpio[GPIO_TX_DISABLE]); + if (state & SFP_F_RATE_SELECT) + gpiod_direction_input(sfp->gpio[GPIO_RATE_SELECT]); + } +} + +static int sfp__i2c_read(struct i2c_adapter *i2c, u8 bus_addr, u8 dev_addr, + void *buf, size_t len) +{ + struct i2c_msg msgs[2]; + int ret; + + msgs[0].addr = bus_addr; + msgs[0].flags = 0; + msgs[0].len = 1; + msgs[0].buf = &dev_addr; + msgs[1].addr = bus_addr; + msgs[1].flags = I2C_M_RD; + msgs[1].len = len; + msgs[1].buf = buf; + + ret = i2c_transfer(i2c, msgs, ARRAY_SIZE(msgs)); + if (ret < 0) + return ret; + + return ret == ARRAY_SIZE(msgs) ? len : 0; +} + +static int sfp_i2c_read(struct sfp *sfp, bool a2, u8 addr, void *buf, + size_t len) +{ + return sfp__i2c_read(sfp->i2c, a2 ? 0x51 : 0x50, addr, buf, len); +} + +static int sfp_i2c_configure(struct sfp *sfp, struct i2c_adapter *i2c) +{ + struct mii_bus *i2c_mii; + int ret; + + if (!i2c_check_functionality(i2c, I2C_FUNC_I2C)) + return -EINVAL; + + sfp->i2c = i2c; + sfp->read = sfp_i2c_read; + + i2c_mii = mdio_i2c_alloc(sfp->dev, i2c); + if (IS_ERR(i2c_mii)) + return PTR_ERR(i2c_mii); + + i2c_mii->name = "SFP I2C Bus"; + i2c_mii->phy_mask = ~0; + + ret = mdiobus_register(i2c_mii); + if (ret < 0) { + mdiobus_free(i2c_mii); + return ret; + } + + sfp->i2c_mii = i2c_mii; + + return 0; +} + + +/* Interface */ +static unsigned int sfp_get_state(struct sfp *sfp) +{ + return sfp->get_state(sfp); +} + +static void sfp_set_state(struct sfp *sfp, unsigned int state) +{ + sfp->set_state(sfp, state); +} + +static int sfp_read(struct sfp *sfp, bool a2, u8 addr, void *buf, size_t len) +{ + return sfp->read(sfp, a2, addr, buf, len); +} + +static unsigned int sfp_check(void *buf, size_t len) +{ + u8 *p, check; + + for (p = buf, check = 0; len; p++, len--) + check += *p; + + return check; +} + +static const char *sfp_link_len(char *buf, size_t size, unsigned int length, + unsigned int multiplier) +{ + if (length == 0) + return "unsupported/unspecified"; + + if (length == 255) { + *buf++ = '>'; + size -= 1; + length -= 1; + } + + length *= multiplier; + + if (length >= 1000) + snprintf(buf, size, "%u.%0*ukm", + length / 1000, + multiplier > 100 ? 1 : + multiplier > 10 ? 2 : 3, + length % 1000); + else + snprintf(buf, size, "%um", length); + + return buf; +} + +struct bitfield { + unsigned int mask; + unsigned int val; + const char *str; +}; + +static const struct bitfield sfp_options[] = { + { + .mask = SFP_OPTIONS_HIGH_POWER_LEVEL, + .val = SFP_OPTIONS_HIGH_POWER_LEVEL, + .str = "hpl", + }, { + .mask = SFP_OPTIONS_PAGING_A2, + .val = SFP_OPTIONS_PAGING_A2, + .str = "paginga2", + }, { + .mask = SFP_OPTIONS_RETIMER, + .val = SFP_OPTIONS_RETIMER, + .str = "retimer", + }, { + .mask = SFP_OPTIONS_COOLED_XCVR, + .val = SFP_OPTIONS_COOLED_XCVR, + .str = "cooled", + }, { + .mask = SFP_OPTIONS_POWER_DECL, + .val = SFP_OPTIONS_POWER_DECL, + .str = "powerdecl", + }, { + .mask = SFP_OPTIONS_RX_LINEAR_OUT, + .val = SFP_OPTIONS_RX_LINEAR_OUT, + .str = "rxlinear", + }, { + .mask = SFP_OPTIONS_RX_DECISION_THRESH, + .val = SFP_OPTIONS_RX_DECISION_THRESH, + .str = "rxthresh", + }, { + .mask = SFP_OPTIONS_TUNABLE_TX, + .val = SFP_OPTIONS_TUNABLE_TX, + .str = "tunabletx", + }, { + .mask = SFP_OPTIONS_RATE_SELECT, + .val = SFP_OPTIONS_RATE_SELECT, + .str = "ratesel", + }, { + .mask = SFP_OPTIONS_TX_DISABLE, + .val = SFP_OPTIONS_TX_DISABLE, + .str = "txdisable", + }, { + .mask = SFP_OPTIONS_TX_FAULT, + .val = SFP_OPTIONS_TX_FAULT, + .str = "txfault", + }, { + .mask = SFP_OPTIONS_LOS_INVERTED, + .val = SFP_OPTIONS_LOS_INVERTED, + .str = "los-", + }, { + .mask = SFP_OPTIONS_LOS_NORMAL, + .val = SFP_OPTIONS_LOS_NORMAL, + .str = "los+", + }, { } +}; + +static const struct bitfield diagmon[] = { + { + .mask = SFP_DIAGMON_DDM, + .val = SFP_DIAGMON_DDM, + .str = "ddm", + }, { + .mask = SFP_DIAGMON_INT_CAL, + .val = SFP_DIAGMON_INT_CAL, + .str = "intcal", + }, { + .mask = SFP_DIAGMON_EXT_CAL, + .val = SFP_DIAGMON_EXT_CAL, + .str = "extcal", + }, { + .mask = SFP_DIAGMON_RXPWR_AVG, + .val = SFP_DIAGMON_RXPWR_AVG, + .str = "rxpwravg", + }, { } +}; + +static const char *sfp_bitfield(char *out, size_t outsz, const struct bitfield *bits, unsigned int val) +{ + char *p = out; + int n; + + *p = '\0'; + while (bits->mask) { + if ((val & bits->mask) == bits->val) { + n = snprintf(p, outsz, "%s%s", + out != p ? ", " : "", + bits->str); + if (n == outsz) + break; + p += n; + outsz -= n; + } + bits++; + } + + return out; +} + +static const char *sfp_connector(unsigned int connector) +{ + switch (connector) { + case SFP_CONNECTOR_UNSPEC: + return "unknown/unspecified"; + case SFP_CONNECTOR_SC: + return "SC"; + case SFP_CONNECTOR_FIBERJACK: + return "Fiberjack"; + case SFP_CONNECTOR_LC: + return "LC"; + case SFP_CONNECTOR_MT_RJ: + return "MT-RJ"; + case SFP_CONNECTOR_MU: + return "MU"; + case SFP_CONNECTOR_SG: + return "SG"; + case SFP_CONNECTOR_OPTICAL_PIGTAIL: + return "Optical pigtail"; + case SFP_CONNECTOR_HSSDC_II: + return "HSSDC II"; + case SFP_CONNECTOR_COPPER_PIGTAIL: + return "Copper pigtail"; + default: + return "unknown"; + } +} + +static const char *sfp_encoding(unsigned int encoding) +{ + switch (encoding) { + case SFP_ENCODING_UNSPEC: + return "unspecified"; + case SFP_ENCODING_8472_64B66B: + return "64b66b"; + case SFP_ENCODING_8B10B: + return "8b10b"; + case SFP_ENCODING_4B5B: + return "4b5b"; + case SFP_ENCODING_NRZ: + return "NRZ"; + case SFP_ENCODING_8472_MANCHESTER: + return "MANCHESTER"; + default: + return "unknown"; + } +} + +/* Helpers */ +static void sfp_module_tx_disable(struct sfp *sfp) +{ + dev_dbg(sfp->dev, "tx disable %u -> %u\n", + sfp->state & SFP_F_TX_DISABLE ? 1 : 0, 1); + sfp->state |= SFP_F_TX_DISABLE; + sfp_set_state(sfp, sfp->state); +} + +static void sfp_module_tx_enable(struct sfp *sfp) +{ + dev_dbg(sfp->dev, "tx disable %u -> %u\n", + sfp->state & SFP_F_TX_DISABLE ? 1 : 0, 0); + sfp->state &= ~SFP_F_TX_DISABLE; + sfp_set_state(sfp, sfp->state); +} + +static void sfp_module_tx_fault_reset(struct sfp *sfp) +{ + unsigned int state = sfp->state; + + if (state & SFP_F_TX_DISABLE) + return; + + sfp_set_state(sfp, state | SFP_F_TX_DISABLE); + + udelay(T_RESET_US); + + sfp_set_state(sfp, state); +} + +/* SFP state machine */ +static void sfp_sm_set_timer(struct sfp *sfp, unsigned int timeout) +{ + if (timeout) + mod_delayed_work(system_power_efficient_wq, &sfp->timeout, + timeout); + else + cancel_delayed_work(&sfp->timeout); +} + +static void sfp_sm_next(struct sfp *sfp, unsigned int state, + unsigned int timeout) +{ + sfp->sm_state = state; + sfp_sm_set_timer(sfp, timeout); +} + +static void sfp_sm_ins_next(struct sfp *sfp, unsigned int state, unsigned int timeout) +{ + sfp->sm_mod_state = state; + sfp_sm_set_timer(sfp, timeout); +} + +static void sfp_sm_phy_detach(struct sfp *sfp) +{ + phy_stop(sfp->mod_phy); + sfp_remove_phy(sfp->sfp_bus); + phy_device_remove(sfp->mod_phy); + phy_device_free(sfp->mod_phy); + sfp->mod_phy = NULL; +} + +static void sfp_sm_probe_phy(struct sfp *sfp) +{ + struct phy_device *phy; + int err; + + msleep(T_PHY_RESET_MS); + + phy = mdiobus_scan(sfp->i2c_mii, SFP_PHY_ADDR); + if (IS_ERR(phy)) { + dev_err(sfp->dev, "mdiobus scan returned %ld\n", PTR_ERR(phy)); + return; + } + if (!phy) { + dev_info(sfp->dev, "no PHY detected\n"); + return; + } + + err = sfp_add_phy(sfp->sfp_bus, phy); + if (err) { + phy_device_remove(phy); + phy_device_free(phy); + dev_err(sfp->dev, "sfp_add_phy failed: %d\n", err); + return; + } + + sfp->mod_phy = phy; + phy_start(phy); +} + +static void sfp_sm_link_up(struct sfp *sfp) +{ + sfp_link_up(sfp->sfp_bus); + sfp_sm_next(sfp, SFP_S_LINK_UP, 0); +} + +static void sfp_sm_link_down(struct sfp *sfp) +{ + sfp_link_down(sfp->sfp_bus); +} + +static void sfp_sm_link_check_los(struct sfp *sfp) +{ + unsigned int los = sfp->state & SFP_F_LOS; + + /* FIXME: what if neither SFP_OPTIONS_LOS_INVERTED nor + * SFP_OPTIONS_LOS_NORMAL are set? For now, we assume + * the same as SFP_OPTIONS_LOS_NORMAL set. + */ + if (sfp->id.ext.options & SFP_OPTIONS_LOS_INVERTED) + los ^= SFP_F_LOS; + + if (los) + sfp_sm_next(sfp, SFP_S_WAIT_LOS, 0); + else + sfp_sm_link_up(sfp); +} + +static void sfp_sm_fault(struct sfp *sfp, bool warn) +{ + if (sfp->sm_retries && !--sfp->sm_retries) { + dev_err(sfp->dev, "module persistently indicates fault, disabling\n"); + sfp_sm_next(sfp, SFP_S_TX_DISABLE, 0); + } else { + if (warn) + dev_err(sfp->dev, "module transmit fault indicated\n"); + + sfp_sm_next(sfp, SFP_S_TX_FAULT, T_FAULT_RECOVER); + } +} + +static void sfp_sm_mod_init(struct sfp *sfp) +{ + sfp_module_tx_enable(sfp); + + /* Wait t_init before indicating that the link is up, provided the + * current state indicates no TX_FAULT. If TX_FAULT clears before + * this time, that's fine too. + */ + sfp_sm_next(sfp, SFP_S_INIT, T_INIT_JIFFIES); + sfp->sm_retries = 5; + + /* Setting the serdes link mode is guesswork: there's no + * field in the EEPROM which indicates what mode should + * be used. + * + * If it's a gigabit-only fiber module, it probably does + * not have a PHY, so switch to 802.3z negotiation mode. + * Otherwise, switch to SGMII mode (which is required to + * support non-gigabit speeds) and probe for a PHY. + */ + if (sfp->id.base.e1000_base_t || + sfp->id.base.e100_base_lx || + sfp->id.base.e100_base_fx) + sfp_sm_probe_phy(sfp); +} + +static int sfp_sm_mod_probe(struct sfp *sfp) +{ + /* SFP module inserted - read I2C data */ + struct sfp_eeprom_id id; + char vendor[17]; + char part[17]; + char sn[17]; + char date[9]; + char rev[5]; + char options[80]; + u8 check; + int err; + + err = sfp_read(sfp, false, 0, &id, sizeof(id)); + if (err < 0) { + dev_err(sfp->dev, "failed to read EEPROM: %d\n", err); + return -EAGAIN; + } + + if (err != sizeof(id)) { + dev_err(sfp->dev, "EEPROM short read: %d\n", err); + return -EAGAIN; + } + + /* Validate the checksum over the base structure */ + check = sfp_check(&id.base, sizeof(id.base) - 1); + if (check != id.base.cc_base) { + dev_err(sfp->dev, + "EEPROM base structure checksum failure: 0x%02x\n", + check); + print_hex_dump(KERN_ERR, "sfp EE: ", DUMP_PREFIX_OFFSET, + 16, 1, &id, sizeof(id.base) - 1, true); + return -EINVAL; + } + + check = sfp_check(&id.ext, sizeof(id.ext) - 1); + if (check != id.ext.cc_ext) { + dev_err(sfp->dev, + "EEPROM extended structure checksum failure: 0x%02x\n", + check); + memset(&id.ext, 0, sizeof(id.ext)); + } + + sfp->id = id; + + memcpy(vendor, sfp->id.base.vendor_name, 16); + vendor[16] = '\0'; + memcpy(part, sfp->id.base.vendor_pn, 16); + part[16] = '\0'; + memcpy(rev, sfp->id.base.vendor_rev, 4); + rev[4] = '\0'; + memcpy(sn, sfp->id.ext.vendor_sn, 16); + sn[16] = '\0'; + date[0] = sfp->id.ext.datecode[4]; + date[1] = sfp->id.ext.datecode[5]; + date[2] = '-'; + date[3] = sfp->id.ext.datecode[2]; + date[4] = sfp->id.ext.datecode[3]; + date[5] = '-'; + date[6] = sfp->id.ext.datecode[0]; + date[7] = sfp->id.ext.datecode[1]; + date[8] = '\0'; + + dev_info(sfp->dev, "module %s %s rev %s sn %s dc %s\n", vendor, part, rev, sn, date); + dev_info(sfp->dev, " %s connector, encoding %s, nominal bitrate %u.%uGbps +%u%% -%u%%\n", + sfp_connector(sfp->id.base.connector), + sfp_encoding(sfp->id.base.encoding), + sfp->id.base.br_nominal / 10, + sfp->id.base.br_nominal % 10, + sfp->id.ext.br_max, sfp->id.ext.br_min); + dev_info(sfp->dev, " 1000BaseSX%c 1000BaseLX%c 1000BaseCX%c 1000BaseT%c 100BaseTLX%c 1000BaseFX%c BaseBX10%c BasePX%c\n", + sfp->id.base.e1000_base_sx ? '+' : '-', + sfp->id.base.e1000_base_lx ? '+' : '-', + sfp->id.base.e1000_base_cx ? '+' : '-', + sfp->id.base.e1000_base_t ? '+' : '-', + sfp->id.base.e100_base_lx ? '+' : '-', + sfp->id.base.e100_base_fx ? '+' : '-', + sfp->id.base.e_base_bx10 ? '+' : '-', + sfp->id.base.e_base_px ? '+' : '-'); + dev_info(sfp->dev, " 10GBaseSR%c 10GBaseLR%c 10GBaseLRM%c 10GBaseER%c\n", + sfp->id.base.e10g_base_sr ? '+' : '-', + sfp->id.base.e10g_base_lr ? '+' : '-', + sfp->id.base.e10g_base_lrm ? '+' : '-', + sfp->id.base.e10g_base_er ? '+' : '-'); + + if (!sfp->id.base.sfp_ct_passive && !sfp->id.base.sfp_ct_active && + !sfp->id.base.e1000_base_t) { + char len_9um[16], len_om[16]; + + dev_info(sfp->dev, " Wavelength %unm, fiber lengths:\n", + be16_to_cpup(&sfp->id.base.optical_wavelength)); + + if (sfp->id.base.link_len[0] == 255) + strcpy(len_9um, ">254km"); + else if (sfp->id.base.link_len[1] && sfp->id.base.link_len[1] != 255) + sprintf(len_9um, "%um", + sfp->id.base.link_len[1] * 100); + else if (sfp->id.base.link_len[0]) + sprintf(len_9um, "%ukm", sfp->id.base.link_len[0]); + else if (sfp->id.base.link_len[1] == 255) + strcpy(len_9um, ">25.4km"); + else + strcpy(len_9um, "unsupported"); + + dev_info(sfp->dev, " 9µm SM : %s\n", len_9um); + dev_info(sfp->dev, " 62.5µm MM OM1: %s\n", + sfp_link_len(len_om, sizeof(len_om), + sfp->id.base.link_len[3], 10)); + dev_info(sfp->dev, " 50µm MM OM2: %s\n", + sfp_link_len(len_om, sizeof(len_om), + sfp->id.base.link_len[2], 10)); + dev_info(sfp->dev, " 50µm MM OM3: %s\n", + sfp_link_len(len_om, sizeof(len_om), + sfp->id.base.link_len[5], 10)); + dev_info(sfp->dev, " 50µm MM OM4: %s\n", + sfp_link_len(len_om, sizeof(len_om), + sfp->id.base.link_len[4], 10)); + } else { + char len[16]; + dev_info(sfp->dev, " Copper length: %s\n", + sfp_link_len(len, sizeof(len), + sfp->id.base.link_len[4], 1)); + } + + dev_info(sfp->dev, " Options: %s\n", + sfp_bitfield(options, sizeof(options), sfp_options, + be16_to_cpu(sfp->id.ext.options))); + dev_info(sfp->dev, " Diagnostics: %s\n", + sfp_bitfield(options, sizeof(options), diagmon, + sfp->id.ext.diagmon)); + + /* We only support SFP modules, not the legacy GBIC modules. */ + if (sfp->id.base.phys_id != SFP_PHYS_ID_SFP || + sfp->id.base.phys_ext_id != SFP_PHYS_EXT_ID_SFP) { + dev_err(sfp->dev, "module is not SFP - phys id 0x%02x 0x%02x\n", + sfp->id.base.phys_id, sfp->id.base.phys_ext_id); + return -EINVAL; + } + + return sfp_module_insert(sfp->sfp_bus, &sfp->id); +} + +static void sfp_sm_mod_remove(struct sfp *sfp) +{ + sfp_module_remove(sfp->sfp_bus); + + if (sfp->mod_phy) + sfp_sm_phy_detach(sfp); + + sfp_module_tx_disable(sfp); + + memset(&sfp->id, 0, sizeof(sfp->id)); + + dev_info(sfp->dev, "module removed\n"); +} + +static void sfp_sm_event(struct sfp *sfp, unsigned int event) +{ + mutex_lock(&sfp->sm_mutex); + + dev_dbg(sfp->dev, "SM: enter %u:%u:%u event %u\n", + sfp->sm_mod_state, sfp->sm_dev_state, sfp->sm_state, event); + + /* This state machine tracks the insert/remove state of + * the module, and handles probing the on-board EEPROM. + */ + switch (sfp->sm_mod_state) { + default: + if (event == SFP_E_INSERT) { + sfp_module_tx_disable(sfp); + sfp_sm_ins_next(sfp, SFP_MOD_PROBE, T_PROBE_INIT); + } + break; + + case SFP_MOD_PROBE: + if (event == SFP_E_REMOVE) { + sfp_sm_ins_next(sfp, SFP_MOD_EMPTY, 0); + } else if (event == SFP_E_TIMEOUT) { + int err = sfp_sm_mod_probe(sfp); + + if (err == 0) + sfp_sm_ins_next(sfp, SFP_MOD_PRESENT, 0); + else if (err == -EAGAIN) + sfp_sm_set_timer(sfp, T_PROBE_RETRY); + else + sfp_sm_ins_next(sfp, SFP_MOD_ERROR, 0); + } + break; + + case SFP_MOD_PRESENT: + case SFP_MOD_ERROR: + if (event == SFP_E_REMOVE) { + sfp_sm_mod_remove(sfp); + sfp_sm_ins_next(sfp, SFP_MOD_EMPTY, 0); + } + break; + } + + /* This state machine tracks the netdev up/down state */ + switch (sfp->sm_dev_state) { + default: + if (event == SFP_E_DEV_UP) + sfp->sm_dev_state = SFP_DEV_UP; + break; + + case SFP_DEV_UP: + if (event == SFP_E_DEV_DOWN) { + /* If the module has a PHY, avoid raising TX disable + * as this resets the PHY. Otherwise, raise it to + * turn the laser off. + */ + if (!sfp->mod_phy) + sfp_module_tx_disable(sfp); + sfp->sm_dev_state = SFP_DEV_DOWN; + } + break; + } + + /* Some events are global */ + if (sfp->sm_state != SFP_S_DOWN && + (sfp->sm_mod_state != SFP_MOD_PRESENT || + sfp->sm_dev_state != SFP_DEV_UP)) { + if (sfp->sm_state == SFP_S_LINK_UP && + sfp->sm_dev_state == SFP_DEV_UP) + sfp_sm_link_down(sfp); + if (sfp->mod_phy) + sfp_sm_phy_detach(sfp); + sfp_sm_next(sfp, SFP_S_DOWN, 0); + mutex_unlock(&sfp->sm_mutex); + return; + } + + /* The main state machine */ + switch (sfp->sm_state) { + case SFP_S_DOWN: + if (sfp->sm_mod_state == SFP_MOD_PRESENT && + sfp->sm_dev_state == SFP_DEV_UP) + sfp_sm_mod_init(sfp); + break; + + case SFP_S_INIT: + if (event == SFP_E_TIMEOUT && sfp->state & SFP_F_TX_FAULT) + sfp_sm_fault(sfp, true); + else if (event == SFP_E_TIMEOUT || event == SFP_E_TX_CLEAR) + sfp_sm_link_check_los(sfp); + break; + + case SFP_S_WAIT_LOS: + if (event == SFP_E_TX_FAULT) + sfp_sm_fault(sfp, true); + else if (event == + (sfp->id.ext.options & SFP_OPTIONS_LOS_INVERTED ? + SFP_E_LOS_HIGH : SFP_E_LOS_LOW)) + sfp_sm_link_up(sfp); + break; + + case SFP_S_LINK_UP: + if (event == SFP_E_TX_FAULT) { + sfp_sm_link_down(sfp); + sfp_sm_fault(sfp, true); + } else if (event == + (sfp->id.ext.options & SFP_OPTIONS_LOS_INVERTED ? + SFP_E_LOS_LOW : SFP_E_LOS_HIGH)) { + sfp_sm_link_down(sfp); + sfp_sm_next(sfp, SFP_S_WAIT_LOS, 0); + } + break; + + case SFP_S_TX_FAULT: + if (event == SFP_E_TIMEOUT) { + sfp_module_tx_fault_reset(sfp); + sfp_sm_next(sfp, SFP_S_REINIT, T_INIT_JIFFIES); + } + break; + + case SFP_S_REINIT: + if (event == SFP_E_TIMEOUT && sfp->state & SFP_F_TX_FAULT) { + sfp_sm_fault(sfp, false); + } else if (event == SFP_E_TIMEOUT || event == SFP_E_TX_CLEAR) { + dev_info(sfp->dev, "module transmit fault recovered\n"); + sfp_sm_link_check_los(sfp); + } + break; + + case SFP_S_TX_DISABLE: + break; + } + + dev_dbg(sfp->dev, "SM: exit %u:%u:%u\n", + sfp->sm_mod_state, sfp->sm_dev_state, sfp->sm_state); + + mutex_unlock(&sfp->sm_mutex); +} + +static void sfp_start(struct sfp *sfp) +{ + sfp_sm_event(sfp, SFP_E_DEV_UP); +} + +static void sfp_stop(struct sfp *sfp) +{ + sfp_sm_event(sfp, SFP_E_DEV_DOWN); +} + +static int sfp_module_info(struct sfp *sfp, struct ethtool_modinfo *modinfo) +{ + /* locking... and check module is present */ + + if (sfp->id.ext.sff8472_compliance) { + modinfo->type = ETH_MODULE_SFF_8472; + modinfo->eeprom_len = ETH_MODULE_SFF_8472_LEN; + } else { + modinfo->type = ETH_MODULE_SFF_8079; + modinfo->eeprom_len = ETH_MODULE_SFF_8079_LEN; + } + return 0; +} + +static int sfp_module_eeprom(struct sfp *sfp, struct ethtool_eeprom *ee, + u8 *data) +{ + unsigned int first, last, len; + int ret; + + if (ee->len == 0) + return -EINVAL; + + first = ee->offset; + last = ee->offset + ee->len; + if (first < ETH_MODULE_SFF_8079_LEN) { + len = min_t(unsigned int, last, ETH_MODULE_SFF_8079_LEN); + len -= first; + + ret = sfp->read(sfp, false, first, data, len); + if (ret < 0) + return ret; + + first += len; + data += len; + } + if (first >= ETH_MODULE_SFF_8079_LEN && + first < ETH_MODULE_SFF_8472_LEN) { + len = min_t(unsigned int, last, ETH_MODULE_SFF_8472_LEN); + len -= first; + first -= ETH_MODULE_SFF_8079_LEN; + + ret = sfp->read(sfp, true, first, data, len); + if (ret < 0) + return ret; + } + return 0; +} + +static const struct sfp_socket_ops sfp_module_ops = { + .start = sfp_start, + .stop = sfp_stop, + .module_info = sfp_module_info, + .module_eeprom = sfp_module_eeprom, +}; + +static void sfp_timeout(struct work_struct *work) +{ + struct sfp *sfp = container_of(work, struct sfp, timeout.work); + + rtnl_lock(); + sfp_sm_event(sfp, SFP_E_TIMEOUT); + rtnl_unlock(); +} + +static void sfp_check_state(struct sfp *sfp) +{ + unsigned int state, i, changed; + + state = sfp_get_state(sfp); + changed = state ^ sfp->state; + changed &= SFP_F_PRESENT | SFP_F_LOS | SFP_F_TX_FAULT; + + for (i = 0; i < GPIO_MAX; i++) + if (changed & BIT(i)) + dev_dbg(sfp->dev, "%s %u -> %u\n", gpio_of_names[i], + !!(sfp->state & BIT(i)), !!(state & BIT(i))); + + state |= sfp->state & (SFP_F_TX_DISABLE | SFP_F_RATE_SELECT); + sfp->state = state; + + rtnl_lock(); + if (changed & SFP_F_PRESENT) + sfp_sm_event(sfp, state & SFP_F_PRESENT ? + SFP_E_INSERT : SFP_E_REMOVE); + + if (changed & SFP_F_TX_FAULT) + sfp_sm_event(sfp, state & SFP_F_TX_FAULT ? + SFP_E_TX_FAULT : SFP_E_TX_CLEAR); + + if (changed & SFP_F_LOS) + sfp_sm_event(sfp, state & SFP_F_LOS ? + SFP_E_LOS_HIGH : SFP_E_LOS_LOW); + rtnl_unlock(); +} + +static irqreturn_t sfp_irq(int irq, void *data) +{ + struct sfp *sfp = data; + + sfp_check_state(sfp); + + return IRQ_HANDLED; +} + +static void sfp_poll(struct work_struct *work) +{ + struct sfp *sfp = container_of(work, struct sfp, poll.work); + + sfp_check_state(sfp); + mod_delayed_work(system_wq, &sfp->poll, poll_jiffies); +} + +static struct sfp *sfp_alloc(struct device *dev) +{ + struct sfp *sfp; + + sfp = kzalloc(sizeof(*sfp), GFP_KERNEL); + if (!sfp) + return ERR_PTR(-ENOMEM); + + sfp->dev = dev; + + mutex_init(&sfp->sm_mutex); + INIT_DELAYED_WORK(&sfp->poll, sfp_poll); + INIT_DELAYED_WORK(&sfp->timeout, sfp_timeout); + + return sfp; +} + +static void sfp_cleanup(void *data) +{ + struct sfp *sfp = data; + + cancel_delayed_work_sync(&sfp->poll); + cancel_delayed_work_sync(&sfp->timeout); + if (sfp->i2c_mii) { + mdiobus_unregister(sfp->i2c_mii); + mdiobus_free(sfp->i2c_mii); + } + if (sfp->i2c) + i2c_put_adapter(sfp->i2c); + kfree(sfp); +} + +static int sfp_probe(struct platform_device *pdev) +{ + struct sfp *sfp; + bool poll = false; + int irq, err, i; + + sfp = sfp_alloc(&pdev->dev); + if (IS_ERR(sfp)) + return PTR_ERR(sfp); + + platform_set_drvdata(pdev, sfp); + + err = devm_add_action(sfp->dev, sfp_cleanup, sfp); + if (err < 0) + return err; + + if (pdev->dev.of_node) { + struct device_node *node = pdev->dev.of_node; + struct device_node *np; + + np = of_parse_phandle(node, "i2c-bus", 0); + if (np) { + struct i2c_adapter *i2c; + + i2c = of_find_i2c_adapter_by_node(np); + of_node_put(np); + if (!i2c) + return -EPROBE_DEFER; + + err = sfp_i2c_configure(sfp, i2c); + if (err < 0) { + i2c_put_adapter(i2c); + return err; + } + } + + for (i = 0; i < GPIO_MAX; i++) { + sfp->gpio[i] = devm_gpiod_get_optional(sfp->dev, + gpio_of_names[i], gpio_flags[i]); + if (IS_ERR(sfp->gpio[i])) + return PTR_ERR(sfp->gpio[i]); + } + + sfp->get_state = sfp_gpio_get_state; + sfp->set_state = sfp_gpio_set_state; + } + + sfp->sfp_bus = sfp_register_socket(sfp->dev, sfp, &sfp_module_ops); + if (!sfp->sfp_bus) + return -ENOMEM; + + /* Get the initial state, and always signal TX disable, + * since the network interface will not be up. + */ + sfp->state = sfp_get_state(sfp) | SFP_F_TX_DISABLE; + + if (sfp->gpio[GPIO_RATE_SELECT] && + gpiod_get_value_cansleep(sfp->gpio[GPIO_RATE_SELECT])) + sfp->state |= SFP_F_RATE_SELECT; + sfp_set_state(sfp, sfp->state); + sfp_module_tx_disable(sfp); + rtnl_lock(); + if (sfp->state & SFP_F_PRESENT) + sfp_sm_event(sfp, SFP_E_INSERT); + rtnl_unlock(); + + for (i = 0; i < GPIO_MAX; i++) { + if (gpio_flags[i] != GPIOD_IN || !sfp->gpio[i]) + continue; + + irq = gpiod_to_irq(sfp->gpio[i]); + if (!irq) { + poll = true; + continue; + } + + err = devm_request_threaded_irq(sfp->dev, irq, NULL, sfp_irq, + IRQF_ONESHOT | + IRQF_TRIGGER_RISING | + IRQF_TRIGGER_FALLING, + dev_name(sfp->dev), sfp); + if (err) + poll = true; + } + + if (poll) + mod_delayed_work(system_wq, &sfp->poll, poll_jiffies); + + return 0; +} + +static int sfp_remove(struct platform_device *pdev) +{ + struct sfp *sfp = platform_get_drvdata(pdev); + + sfp_unregister_socket(sfp->sfp_bus); + + return 0; +} + +static const struct of_device_id sfp_of_match[] = { + { .compatible = "sff,sfp", }, + { .compatible = "sff,sfp+", }, + { }, +}; +MODULE_DEVICE_TABLE(of, sfp_of_match); + +static struct platform_driver sfp_driver = { + .probe = sfp_probe, + .remove = sfp_remove, + .driver = { + .name = "sfp", + .of_match_table = sfp_of_match, + }, +}; + +static int sfp_init(void) +{ + poll_jiffies = msecs_to_jiffies(100); + + return platform_driver_register(&sfp_driver); +} +module_init(sfp_init); + +static void sfp_exit(void) +{ + platform_driver_unregister(&sfp_driver); +} +module_exit(sfp_exit); + +MODULE_ALIAS("platform:sfp"); +MODULE_AUTHOR("Russell King"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/net/phy/sfp.h b/drivers/net/phy/sfp.h new file mode 100644 index 000000000000..31b0acf337e2 --- /dev/null +++ b/drivers/net/phy/sfp.h @@ -0,0 +1,28 @@ +#ifndef SFP_H +#define SFP_H + +#include <linux/ethtool.h> +#include <linux/sfp.h> + +struct sfp; + +struct sfp_socket_ops { + void (*start)(struct sfp *sfp); + void (*stop)(struct sfp *sfp); + int (*module_info)(struct sfp *sfp, struct ethtool_modinfo *modinfo); + int (*module_eeprom)(struct sfp *sfp, struct ethtool_eeprom *ee, + u8 *data); +}; + +int sfp_add_phy(struct sfp_bus *bus, struct phy_device *phydev); +void sfp_remove_phy(struct sfp_bus *bus); +void sfp_link_up(struct sfp_bus *bus); +void sfp_link_down(struct sfp_bus *bus); +int sfp_module_insert(struct sfp_bus *bus, const struct sfp_eeprom_id *id); +void sfp_module_remove(struct sfp_bus *bus); +int sfp_link_configure(struct sfp_bus *bus, const struct sfp_eeprom_id *id); +struct sfp_bus *sfp_register_socket(struct device *dev, struct sfp *sfp, + const struct sfp_socket_ops *ops); +void sfp_unregister_socket(struct sfp_bus *bus); + +#endif diff --git a/drivers/pci/host/pci-mvebu.c b/drivers/pci/host/pci-mvebu.c index f353a6eb2f01..e0300c67abf6 100644 --- a/drivers/pci/host/pci-mvebu.c +++ b/drivers/pci/host/pci-mvebu.c @@ -1167,6 +1167,7 @@ static int mvebu_pcie_powerup(struct mvebu_pcie_port *port) if (port->reset_gpio) { u32 reset_udelay = PCI_PM_D3COLD_WAIT * 1000; + unsigned int i; of_property_read_u32(port->dn, "reset-delay-us", &reset_udelay); @@ -1174,7 +1175,13 @@ static int mvebu_pcie_powerup(struct mvebu_pcie_port *port) udelay(100); gpiod_set_value_cansleep(port->reset_gpio, 0); - msleep(reset_udelay / 1000); + for (i = 0; i < reset_udelay; i += 1000) { + if (mvebu_pcie_link_up(port)) + break; + msleep(1); + } + + printk("%s: reset completed in %dus\n", port->name, i); } return 0; @@ -1262,15 +1269,16 @@ static int mvebu_pcie_probe(struct platform_device *pdev) if (!child) continue; - ret = mvebu_pcie_powerup(port); - if (ret < 0) - continue; - port->base = mvebu_pcie_map_registers(pdev, child, port); if (IS_ERR(port->base)) { dev_err(dev, "%s: cannot map registers\n", port->name); port->base = NULL; - mvebu_pcie_powerdown(port); + continue; + } + + ret = mvebu_pcie_powerup(port); + if (ret < 0) { + port->base = NULL; continue; } |