]> git.karo-electronics.de Git - linux-beck.git/commitdiff
amd-xgbe: Add support for a KR redriver
authorLendacky, Thomas <Thomas.Lendacky@amd.com>
Thu, 10 Nov 2016 23:11:41 +0000 (17:11 -0600)
committerDavid S. Miller <davem@davemloft.net>
Sun, 13 Nov 2016 05:56:26 +0000 (00:56 -0500)
This patch provides support for the presence of a KR redriver chip in
between the device PCS and an external PHY.  When a redriver chip is
present the device must perform clause 73 auto-negotiation in order to
set the redriver chip for the downstream connection.

Signed-off-by: Tom Lendacky <thomas.lendacky@amd.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
drivers/net/ethernet/amd/xgbe/xgbe-common.h
drivers/net/ethernet/amd/xgbe/xgbe-mdio.c
drivers/net/ethernet/amd/xgbe/xgbe-phy-v1.c
drivers/net/ethernet/amd/xgbe/xgbe-phy-v2.c
drivers/net/ethernet/amd/xgbe/xgbe.h

index ecd4f4dce5721196f16bdaad407c331f0d297c57..5b7ba25e006522a6143d6c73a9b8141a7ac76f85 100644 (file)
 #define XP_PROP_4_MUX_ADDR_LO_WIDTH            3
 #define XP_PROP_4_MUX_CHAN_INDEX               4
 #define XP_PROP_4_MUX_CHAN_WIDTH               3
+#define XP_PROP_4_REDRV_ADDR_INDEX             16
+#define XP_PROP_4_REDRV_ADDR_WIDTH             7
+#define XP_PROP_4_REDRV_IF_INDEX               23
+#define XP_PROP_4_REDRV_IF_WIDTH               1
+#define XP_PROP_4_REDRV_LANE_INDEX             24
+#define XP_PROP_4_REDRV_LANE_WIDTH             3
+#define XP_PROP_4_REDRV_MODEL_INDEX            28
+#define XP_PROP_4_REDRV_MODEL_WIDTH            3
+#define XP_PROP_4_REDRV_PRESENT_INDEX          31
+#define XP_PROP_4_REDRV_PRESENT_WIDTH          1
 
 /* I2C Control register offsets */
 #define IC_CON                                 0x0000
index 622675ae4de62985c6c00c82bc5e408e885af93d..0ecae70450445ee61f0cab8b389b8d4144ed62cc 100644 (file)
@@ -179,6 +179,7 @@ static void xgbe_an_enable_interrupts(struct xgbe_prv_data *pdata)
 {
        switch (pdata->an_mode) {
        case XGBE_AN_MODE_CL73:
+       case XGBE_AN_MODE_CL73_REDRV:
                xgbe_an73_enable_interrupts(pdata);
                break;
        case XGBE_AN_MODE_CL37:
@@ -254,6 +255,10 @@ static void xgbe_kx_1000_mode(struct xgbe_prv_data *pdata)
 
 static void xgbe_sfi_mode(struct xgbe_prv_data *pdata)
 {
+       /* If a KR re-driver is present, change to KR mode instead */
+       if (pdata->kr_redrv)
+               return xgbe_kr_mode(pdata);
+
        /* Disable KR training */
        xgbe_an73_disable_kr_training(pdata);
 
@@ -433,6 +438,7 @@ static void xgbe_an_restart(struct xgbe_prv_data *pdata)
 {
        switch (pdata->an_mode) {
        case XGBE_AN_MODE_CL73:
+       case XGBE_AN_MODE_CL73_REDRV:
                xgbe_an73_restart(pdata);
                break;
        case XGBE_AN_MODE_CL37:
@@ -448,6 +454,7 @@ static void xgbe_an_disable(struct xgbe_prv_data *pdata)
 {
        switch (pdata->an_mode) {
        case XGBE_AN_MODE_CL73:
+       case XGBE_AN_MODE_CL73_REDRV:
                xgbe_an73_disable(pdata);
                break;
        case XGBE_AN_MODE_CL37:
@@ -687,6 +694,7 @@ static irqreturn_t xgbe_an_isr(int irq, void *data)
 
        switch (pdata->an_mode) {
        case XGBE_AN_MODE_CL73:
+       case XGBE_AN_MODE_CL73_REDRV:
                xgbe_an73_isr(pdata);
                break;
        case XGBE_AN_MODE_CL37:
@@ -895,6 +903,7 @@ static void xgbe_an_state_machine(struct work_struct *work)
 
        switch (pdata->an_mode) {
        case XGBE_AN_MODE_CL73:
+       case XGBE_AN_MODE_CL73_REDRV:
                xgbe_an73_state_machine(pdata);
                break;
        case XGBE_AN_MODE_CL37:
@@ -910,16 +919,18 @@ static void xgbe_an_state_machine(struct work_struct *work)
 
 static void xgbe_an37_init(struct xgbe_prv_data *pdata)
 {
-       unsigned int reg;
+       unsigned int advertising, reg;
+
+       advertising = pdata->phy_if.phy_impl.an_advertising(pdata);
 
        /* Set up Advertisement register */
        reg = XMDIO_READ(pdata, MDIO_MMD_VEND2, MDIO_VEND2_AN_ADVERTISE);
-       if (pdata->phy.advertising & ADVERTISED_Pause)
+       if (advertising & ADVERTISED_Pause)
                reg |= 0x100;
        else
                reg &= ~0x100;
 
-       if (pdata->phy.advertising & ADVERTISED_Asym_Pause)
+       if (advertising & ADVERTISED_Asym_Pause)
                reg |= 0x80;
        else
                reg &= ~0x80;
@@ -954,11 +965,13 @@ static void xgbe_an37_init(struct xgbe_prv_data *pdata)
 
 static void xgbe_an73_init(struct xgbe_prv_data *pdata)
 {
-       unsigned int reg;
+       unsigned int advertising, reg;
+
+       advertising = pdata->phy_if.phy_impl.an_advertising(pdata);
 
        /* Set up Advertisement register 3 first */
        reg = XMDIO_READ(pdata, MDIO_MMD_AN, MDIO_AN_ADVERTISE + 2);
-       if (pdata->phy.advertising & ADVERTISED_10000baseR_FEC)
+       if (advertising & ADVERTISED_10000baseR_FEC)
                reg |= 0xc000;
        else
                reg &= ~0xc000;
@@ -967,13 +980,13 @@ static void xgbe_an73_init(struct xgbe_prv_data *pdata)
 
        /* Set up Advertisement register 2 next */
        reg = XMDIO_READ(pdata, MDIO_MMD_AN, MDIO_AN_ADVERTISE + 1);
-       if (pdata->phy.advertising & ADVERTISED_10000baseKR_Full)
+       if (advertising & ADVERTISED_10000baseKR_Full)
                reg |= 0x80;
        else
                reg &= ~0x80;
 
-       if ((pdata->phy.advertising & ADVERTISED_1000baseKX_Full) ||
-           (pdata->phy.advertising & ADVERTISED_2500baseX_Full))
+       if ((advertising & ADVERTISED_1000baseKX_Full) ||
+           (advertising & ADVERTISED_2500baseX_Full))
                reg |= 0x20;
        else
                reg &= ~0x20;
@@ -982,12 +995,12 @@ static void xgbe_an73_init(struct xgbe_prv_data *pdata)
 
        /* Set up Advertisement register 1 last */
        reg = XMDIO_READ(pdata, MDIO_MMD_AN, MDIO_AN_ADVERTISE);
-       if (pdata->phy.advertising & ADVERTISED_Pause)
+       if (advertising & ADVERTISED_Pause)
                reg |= 0x400;
        else
                reg &= ~0x400;
 
-       if (pdata->phy.advertising & ADVERTISED_Asym_Pause)
+       if (advertising & ADVERTISED_Asym_Pause)
                reg |= 0x800;
        else
                reg &= ~0x800;
@@ -1006,6 +1019,7 @@ static void xgbe_an_init(struct xgbe_prv_data *pdata)
        pdata->an_mode = pdata->phy_if.phy_impl.an_mode(pdata);
        switch (pdata->an_mode) {
        case XGBE_AN_MODE_CL73:
+       case XGBE_AN_MODE_CL73_REDRV:
                xgbe_an73_init(pdata);
                break;
        case XGBE_AN_MODE_CL37:
@@ -1149,10 +1163,15 @@ static int __xgbe_phy_config_aneg(struct xgbe_prv_data *pdata)
        if (ret)
                return ret;
 
-       if (pdata->phy.autoneg != AUTONEG_ENABLE)
-               return xgbe_phy_config_fixed(pdata);
+       if (pdata->phy.autoneg != AUTONEG_ENABLE) {
+               ret = xgbe_phy_config_fixed(pdata);
+               if (ret || !pdata->kr_redrv)
+                       return ret;
 
-       netif_dbg(pdata, link, pdata->netdev, "AN PHY configuration\n");
+               netif_dbg(pdata, link, pdata->netdev, "AN redriver support\n");
+       } else {
+               netif_dbg(pdata, link, pdata->netdev, "AN PHY configuration\n");
+       }
 
        /* Disable auto-negotiation interrupt */
        disable_irq(pdata->an_irq);
index 6c64d11d0815d0156dbfee53374c5264631bf917..c75edcac5e0a08f45c92cb97129b2b374992fa83 100644 (file)
@@ -295,6 +295,11 @@ static enum xgbe_mode xgbe_phy_an_outcome(struct xgbe_prv_data *pdata)
        return mode;
 }
 
+static unsigned int xgbe_phy_an_advertising(struct xgbe_prv_data *pdata)
+{
+       return pdata->phy.advertising;
+}
+
 static int xgbe_phy_an_config(struct xgbe_prv_data *pdata)
 {
        /* Nothing uniquely required for an configuration */
@@ -831,6 +836,8 @@ void xgbe_init_function_ptrs_phy_v1(struct xgbe_phy_if *phy_if)
 
        phy_impl->an_config             = xgbe_phy_an_config;
 
+       phy_impl->an_advertising        = xgbe_phy_an_advertising;
+
        phy_impl->an_outcome            = xgbe_phy_an_outcome;
 
        phy_impl->kr_training_pre       = xgbe_phy_kr_training_pre;
index 9848f741f44f29862b78645428c45583be08efeb..4ba43328d99ed1b273448b0f6fd4d7d357ec076d 100644 (file)
@@ -277,6 +277,26 @@ enum xgbe_mdio_reset {
        XGBE_MDIO_RESET_MAX,
 };
 
+/* Re-driver related definitions */
+enum xgbe_phy_redrv_if {
+       XGBE_PHY_REDRV_IF_MDIO = 0,
+       XGBE_PHY_REDRV_IF_I2C,
+       XGBE_PHY_REDRV_IF_MAX,
+};
+
+enum xgbe_phy_redrv_model {
+       XGBE_PHY_REDRV_MODEL_4223 = 0,
+       XGBE_PHY_REDRV_MODEL_4227,
+       XGBE_PHY_REDRV_MODEL_MAX,
+};
+
+enum xgbe_phy_redrv_mode {
+       XGBE_PHY_REDRV_MODE_CX = 5,
+       XGBE_PHY_REDRV_MODE_SR = 9,
+};
+
+#define XGBE_PHY_REDRV_MODE_REG        0x12b0
+
 /* PHY related configuration information */
 struct xgbe_phy_data {
        enum xgbe_port_mode port_mode;
@@ -327,6 +347,13 @@ struct xgbe_phy_data {
        enum xgbe_mdio_reset mdio_reset;
        unsigned int mdio_reset_addr;
        unsigned int mdio_reset_gpio;
+
+       /* Re-driver support */
+       unsigned int redrv;
+       unsigned int redrv_if;
+       unsigned int redrv_addr;
+       unsigned int redrv_lane;
+       unsigned int redrv_model;
 };
 
 /* I2C, MDIO and GPIO lines are muxed, so only one device at a time */
@@ -346,6 +373,68 @@ static int xgbe_phy_i2c_xfer(struct xgbe_prv_data *pdata,
        return pdata->i2c_if.i2c_xfer(pdata, i2c_op);
 }
 
+static int xgbe_phy_redrv_write(struct xgbe_prv_data *pdata, unsigned int reg,
+                               unsigned int val)
+{
+       struct xgbe_phy_data *phy_data = pdata->phy_data;
+       struct xgbe_i2c_op i2c_op;
+       __be16 *redrv_val;
+       u8 redrv_data[5], csum;
+       unsigned int i, retry;
+       int ret;
+
+       /* High byte of register contains read/write indicator */
+       redrv_data[0] = ((reg >> 8) & 0xff) << 1;
+       redrv_data[1] = reg & 0xff;
+       redrv_val = (__be16 *)&redrv_data[2];
+       *redrv_val = cpu_to_be16(val);
+
+       /* Calculate 1 byte checksum */
+       csum = 0;
+       for (i = 0; i < 4; i++) {
+               csum += redrv_data[i];
+               if (redrv_data[i] > csum)
+                       csum++;
+       }
+       redrv_data[4] = ~csum;
+
+       retry = 1;
+again1:
+       i2c_op.cmd = XGBE_I2C_CMD_WRITE;
+       i2c_op.target = phy_data->redrv_addr;
+       i2c_op.len = sizeof(redrv_data);
+       i2c_op.buf = redrv_data;
+       ret = xgbe_phy_i2c_xfer(pdata, &i2c_op);
+       if (ret) {
+               if ((ret == -EAGAIN) && retry--)
+                       goto again1;
+
+               return ret;
+       }
+
+       retry = 1;
+again2:
+       i2c_op.cmd = XGBE_I2C_CMD_READ;
+       i2c_op.target = phy_data->redrv_addr;
+       i2c_op.len = 1;
+       i2c_op.buf = redrv_data;
+       ret = xgbe_phy_i2c_xfer(pdata, &i2c_op);
+       if (ret) {
+               if ((ret == -EAGAIN) && retry--)
+                       goto again2;
+
+               return ret;
+       }
+
+       if (redrv_data[0] != 0xff) {
+               netif_dbg(pdata, drv, pdata->netdev,
+                         "Redriver write checksum error\n");
+               ret = -EIO;
+       }
+
+       return ret;
+}
+
 static int xgbe_phy_i2c_write(struct xgbe_prv_data *pdata, unsigned int target,
                              void *val, unsigned int val_len)
 {
@@ -1144,38 +1233,49 @@ put:
        xgbe_phy_put_comm_ownership(pdata);
 }
 
-static enum xgbe_mode xgbe_phy_an37_sgmii_outcome(struct xgbe_prv_data *pdata)
+static void xgbe_phy_phydev_flowctrl(struct xgbe_prv_data *pdata)
 {
        struct xgbe_phy_data *phy_data = pdata->phy_data;
-       enum xgbe_mode mode;
+       u16 lcl_adv = 0, rmt_adv = 0;
+       u8 fc;
 
-       pdata->phy.lp_advertising |= ADVERTISED_Autoneg;
-       pdata->phy.lp_advertising |= ADVERTISED_TP;
+       pdata->phy.tx_pause = 0;
+       pdata->phy.rx_pause = 0;
 
-       if (pdata->phy.pause_autoneg && phy_data->phydev) {
-               /* Flow control is obtained from the attached PHY */
-               u16 lcl_adv = 0, rmt_adv = 0;
-               u8 fc;
+       if (!phy_data->phydev)
+               return;
 
-               pdata->phy.tx_pause = 0;
-               pdata->phy.rx_pause = 0;
+       if (phy_data->phydev->advertising & ADVERTISED_Pause)
+               lcl_adv |= ADVERTISE_PAUSE_CAP;
+       if (phy_data->phydev->advertising & ADVERTISED_Asym_Pause)
+               lcl_adv |= ADVERTISE_PAUSE_ASYM;
 
-               if (phy_data->phydev->advertising & ADVERTISED_Pause)
-                       lcl_adv |= ADVERTISE_PAUSE_CAP;
-               if (phy_data->phydev->advertising & ADVERTISED_Asym_Pause)
-                       lcl_adv |= ADVERTISE_PAUSE_ASYM;
+       if (phy_data->phydev->pause) {
+               pdata->phy.lp_advertising |= ADVERTISED_Pause;
+               rmt_adv |= LPA_PAUSE_CAP;
+       }
+       if (phy_data->phydev->asym_pause) {
+               pdata->phy.lp_advertising |= ADVERTISED_Asym_Pause;
+               rmt_adv |= LPA_PAUSE_ASYM;
+       }
 
-               if (phy_data->phydev->pause)
-                       rmt_adv |= LPA_PAUSE_CAP;
-               if (phy_data->phydev->asym_pause)
-                       rmt_adv |= LPA_PAUSE_ASYM;
+       fc = mii_resolve_flowctrl_fdx(lcl_adv, rmt_adv);
+       if (fc & FLOW_CTRL_TX)
+               pdata->phy.tx_pause = 1;
+       if (fc & FLOW_CTRL_RX)
+               pdata->phy.rx_pause = 1;
+}
 
-               fc = mii_resolve_flowctrl_fdx(lcl_adv, rmt_adv);
-               if (fc & FLOW_CTRL_TX)
-                       pdata->phy.tx_pause = 1;
-               if (fc & FLOW_CTRL_RX)
-                       pdata->phy.rx_pause = 1;
-       }
+static enum xgbe_mode xgbe_phy_an37_sgmii_outcome(struct xgbe_prv_data *pdata)
+{
+       enum xgbe_mode mode;
+
+       pdata->phy.lp_advertising |= ADVERTISED_Autoneg;
+       pdata->phy.lp_advertising |= ADVERTISED_TP;
+
+       /* Use external PHY to determine flow control */
+       if (pdata->phy.pause_autoneg)
+               xgbe_phy_phydev_flowctrl(pdata);
 
        switch (pdata->an_status & XGBE_SGMII_AN_LINK_SPEED) {
        case XGBE_SGMII_AN_LINK_SPEED_100:
@@ -1249,6 +1349,83 @@ static enum xgbe_mode xgbe_phy_an37_outcome(struct xgbe_prv_data *pdata)
        return mode;
 }
 
+static enum xgbe_mode xgbe_phy_an73_redrv_outcome(struct xgbe_prv_data *pdata)
+{
+       struct xgbe_phy_data *phy_data = pdata->phy_data;
+       enum xgbe_mode mode;
+       unsigned int ad_reg, lp_reg;
+
+       pdata->phy.lp_advertising |= ADVERTISED_Autoneg;
+       pdata->phy.lp_advertising |= ADVERTISED_Backplane;
+
+       /* Use external PHY to determine flow control */
+       if (pdata->phy.pause_autoneg)
+               xgbe_phy_phydev_flowctrl(pdata);
+
+       /* Compare Advertisement and Link Partner register 2 */
+       ad_reg = XMDIO_READ(pdata, MDIO_MMD_AN, MDIO_AN_ADVERTISE + 1);
+       lp_reg = XMDIO_READ(pdata, MDIO_MMD_AN, MDIO_AN_LPA + 1);
+       if (lp_reg & 0x80)
+               pdata->phy.lp_advertising |= ADVERTISED_10000baseKR_Full;
+       if (lp_reg & 0x20)
+               pdata->phy.lp_advertising |= ADVERTISED_1000baseKX_Full;
+
+       ad_reg &= lp_reg;
+       if (ad_reg & 0x80) {
+               switch (phy_data->port_mode) {
+               case XGBE_PORT_MODE_BACKPLANE:
+                       mode = XGBE_MODE_KR;
+                       break;
+               default:
+                       mode = XGBE_MODE_SFI;
+                       break;
+               }
+       } else if (ad_reg & 0x20) {
+               switch (phy_data->port_mode) {
+               case XGBE_PORT_MODE_BACKPLANE:
+                       mode = XGBE_MODE_KX_1000;
+                       break;
+               case XGBE_PORT_MODE_1000BASE_X:
+                       mode = XGBE_MODE_X;
+                       break;
+               case XGBE_PORT_MODE_SFP:
+                       switch (phy_data->sfp_base) {
+                       case XGBE_SFP_BASE_1000_T:
+                               if (phy_data->phydev &&
+                                   (phy_data->phydev->speed == SPEED_100))
+                                       mode = XGBE_MODE_SGMII_100;
+                               else
+                                       mode = XGBE_MODE_SGMII_1000;
+                               break;
+                       case XGBE_SFP_BASE_1000_SX:
+                       case XGBE_SFP_BASE_1000_LX:
+                       case XGBE_SFP_BASE_1000_CX:
+                       default:
+                               mode = XGBE_MODE_X;
+                               break;
+                       }
+                       break;
+               default:
+                       if (phy_data->phydev &&
+                           (phy_data->phydev->speed == SPEED_100))
+                               mode = XGBE_MODE_SGMII_100;
+                       else
+                               mode = XGBE_MODE_SGMII_1000;
+                       break;
+               }
+       } else {
+               mode = XGBE_MODE_UNKNOWN;
+       }
+
+       /* Compare Advertisement and Link Partner register 3 */
+       ad_reg = XMDIO_READ(pdata, MDIO_MMD_AN, MDIO_AN_ADVERTISE + 2);
+       lp_reg = XMDIO_READ(pdata, MDIO_MMD_AN, MDIO_AN_LPA + 2);
+       if (lp_reg & 0xc000)
+               pdata->phy.lp_advertising |= ADVERTISED_10000baseR_FEC;
+
+       return mode;
+}
+
 static enum xgbe_mode xgbe_phy_an73_outcome(struct xgbe_prv_data *pdata)
 {
        enum xgbe_mode mode;
@@ -1311,6 +1488,8 @@ static enum xgbe_mode xgbe_phy_an_outcome(struct xgbe_prv_data *pdata)
        switch (pdata->an_mode) {
        case XGBE_AN_MODE_CL73:
                return xgbe_phy_an73_outcome(pdata);
+       case XGBE_AN_MODE_CL73_REDRV:
+               return xgbe_phy_an73_redrv_outcome(pdata);
        case XGBE_AN_MODE_CL37:
                return xgbe_phy_an37_outcome(pdata);
        case XGBE_AN_MODE_CL37_SGMII:
@@ -1320,6 +1499,63 @@ static enum xgbe_mode xgbe_phy_an_outcome(struct xgbe_prv_data *pdata)
        }
 }
 
+static unsigned int xgbe_phy_an_advertising(struct xgbe_prv_data *pdata)
+{
+       struct xgbe_phy_data *phy_data = pdata->phy_data;
+       unsigned int advertising;
+
+       /* Without a re-driver, just return current advertising */
+       if (!phy_data->redrv)
+               return pdata->phy.advertising;
+
+       /* With the KR re-driver we need to advertise a single speed */
+       advertising = pdata->phy.advertising;
+       advertising &= ~ADVERTISED_1000baseKX_Full;
+       advertising &= ~ADVERTISED_10000baseKR_Full;
+
+       switch (phy_data->port_mode) {
+       case XGBE_PORT_MODE_BACKPLANE:
+               advertising |= ADVERTISED_10000baseKR_Full;
+               break;
+       case XGBE_PORT_MODE_BACKPLANE_2500:
+               advertising |= ADVERTISED_1000baseKX_Full;
+               break;
+       case XGBE_PORT_MODE_1000BASE_T:
+       case XGBE_PORT_MODE_1000BASE_X:
+       case XGBE_PORT_MODE_NBASE_T:
+               advertising |= ADVERTISED_1000baseKX_Full;
+               break;
+       case XGBE_PORT_MODE_10GBASE_T:
+               if (phy_data->phydev &&
+                   (phy_data->phydev->speed == SPEED_10000))
+                       advertising |= ADVERTISED_10000baseKR_Full;
+               else
+                       advertising |= ADVERTISED_1000baseKX_Full;
+               break;
+       case XGBE_PORT_MODE_10GBASE_R:
+               advertising |= ADVERTISED_10000baseKR_Full;
+               break;
+       case XGBE_PORT_MODE_SFP:
+               switch (phy_data->sfp_base) {
+               case XGBE_SFP_BASE_1000_T:
+               case XGBE_SFP_BASE_1000_SX:
+               case XGBE_SFP_BASE_1000_LX:
+               case XGBE_SFP_BASE_1000_CX:
+                       advertising |= ADVERTISED_1000baseKX_Full;
+                       break;
+               default:
+                       advertising |= ADVERTISED_10000baseKR_Full;
+                       break;
+               }
+               break;
+       default:
+               advertising |= ADVERTISED_10000baseKR_Full;
+               break;
+       }
+
+       return advertising;
+}
+
 static int xgbe_phy_an_config(struct xgbe_prv_data *pdata)
 {
        struct xgbe_phy_data *phy_data = pdata->phy_data;
@@ -1364,6 +1600,10 @@ static enum xgbe_an_mode xgbe_phy_an_mode(struct xgbe_prv_data *pdata)
 {
        struct xgbe_phy_data *phy_data = pdata->phy_data;
 
+       /* A KR re-driver will always require CL73 AN */
+       if (phy_data->redrv)
+               return XGBE_AN_MODE_CL73_REDRV;
+
        switch (phy_data->port_mode) {
        case XGBE_PORT_MODE_BACKPLANE:
                return XGBE_AN_MODE_CL73;
@@ -1386,6 +1626,61 @@ static enum xgbe_an_mode xgbe_phy_an_mode(struct xgbe_prv_data *pdata)
        }
 }
 
+static int xgbe_phy_set_redrv_mode_mdio(struct xgbe_prv_data *pdata,
+                                       enum xgbe_phy_redrv_mode mode)
+{
+       struct xgbe_phy_data *phy_data = pdata->phy_data;
+       u16 redrv_reg, redrv_val;
+
+       redrv_reg = XGBE_PHY_REDRV_MODE_REG + (phy_data->redrv_lane * 0x1000);
+       redrv_val = (u16)mode;
+
+       return pdata->hw_if.write_ext_mii_regs(pdata, phy_data->redrv_addr,
+                                              redrv_reg, redrv_val);
+}
+
+static int xgbe_phy_set_redrv_mode_i2c(struct xgbe_prv_data *pdata,
+                                      enum xgbe_phy_redrv_mode mode)
+{
+       struct xgbe_phy_data *phy_data = pdata->phy_data;
+       unsigned int redrv_reg;
+       int ret;
+
+       /* Calculate the register to write */
+       redrv_reg = XGBE_PHY_REDRV_MODE_REG + (phy_data->redrv_lane * 0x1000);
+
+       ret = xgbe_phy_redrv_write(pdata, redrv_reg, mode);
+
+       return ret;
+}
+
+static void xgbe_phy_set_redrv_mode(struct xgbe_prv_data *pdata)
+{
+       struct xgbe_phy_data *phy_data = pdata->phy_data;
+       enum xgbe_phy_redrv_mode mode;
+       int ret;
+
+       if (!phy_data->redrv)
+               return;
+
+       mode = XGBE_PHY_REDRV_MODE_CX;
+       if ((phy_data->port_mode == XGBE_PORT_MODE_SFP) &&
+           (phy_data->sfp_base != XGBE_SFP_BASE_1000_CX) &&
+           (phy_data->sfp_base != XGBE_SFP_BASE_10000_CR))
+               mode = XGBE_PHY_REDRV_MODE_SR;
+
+       ret = xgbe_phy_get_comm_ownership(pdata);
+       if (ret)
+               return;
+
+       if (phy_data->redrv_if)
+               xgbe_phy_set_redrv_mode_i2c(pdata, mode);
+       else
+               xgbe_phy_set_redrv_mode_mdio(pdata, mode);
+
+       xgbe_phy_put_comm_ownership(pdata);
+}
+
 static void xgbe_phy_start_ratechange(struct xgbe_prv_data *pdata)
 {
        if (!XP_IOREAD_BITS(pdata, XP_DRIVER_INT_RO, STATUS))
@@ -1457,6 +1752,8 @@ static void xgbe_phy_sfi_mode(struct xgbe_prv_data *pdata)
        struct xgbe_phy_data *phy_data = pdata->phy_data;
        unsigned int s0;
 
+       xgbe_phy_set_redrv_mode(pdata);
+
        xgbe_phy_start_ratechange(pdata);
 
        /* 10G/SFI */
@@ -1492,6 +1789,8 @@ static void xgbe_phy_x_mode(struct xgbe_prv_data *pdata)
        struct xgbe_phy_data *phy_data = pdata->phy_data;
        unsigned int s0;
 
+       xgbe_phy_set_redrv_mode(pdata);
+
        xgbe_phy_start_ratechange(pdata);
 
        /* 1G/X */
@@ -1516,6 +1815,8 @@ static void xgbe_phy_sgmii_1000_mode(struct xgbe_prv_data *pdata)
        struct xgbe_phy_data *phy_data = pdata->phy_data;
        unsigned int s0;
 
+       xgbe_phy_set_redrv_mode(pdata);
+
        xgbe_phy_start_ratechange(pdata);
 
        /* 1G/SGMII */
@@ -1540,6 +1841,8 @@ static void xgbe_phy_sgmii_100_mode(struct xgbe_prv_data *pdata)
        struct xgbe_phy_data *phy_data = pdata->phy_data;
        unsigned int s0;
 
+       xgbe_phy_set_redrv_mode(pdata);
+
        xgbe_phy_start_ratechange(pdata);
 
        /* 1G/SGMII */
@@ -1564,6 +1867,8 @@ static void xgbe_phy_kr_mode(struct xgbe_prv_data *pdata)
        struct xgbe_phy_data *phy_data = pdata->phy_data;
        unsigned int s0;
 
+       xgbe_phy_set_redrv_mode(pdata);
+
        xgbe_phy_start_ratechange(pdata);
 
        /* 10G/KR */
@@ -1588,6 +1893,8 @@ static void xgbe_phy_kx_2500_mode(struct xgbe_prv_data *pdata)
        struct xgbe_phy_data *phy_data = pdata->phy_data;
        unsigned int s0;
 
+       xgbe_phy_set_redrv_mode(pdata);
+
        xgbe_phy_start_ratechange(pdata);
 
        /* 2.5G/KX */
@@ -1612,6 +1919,8 @@ static void xgbe_phy_kx_1000_mode(struct xgbe_prv_data *pdata)
        struct xgbe_phy_data *phy_data = pdata->phy_data;
        unsigned int s0;
 
+       xgbe_phy_set_redrv_mode(pdata);
+
        xgbe_phy_start_ratechange(pdata);
 
        /* 1G/KX */
@@ -2232,6 +2541,30 @@ static int xgbe_phy_mdio_reset(struct xgbe_prv_data *pdata)
        return ret;
 }
 
+static bool xgbe_phy_redrv_error(struct xgbe_phy_data *phy_data)
+{
+       if (!phy_data->redrv)
+               return false;
+
+       if (phy_data->redrv_if >= XGBE_PHY_REDRV_IF_MAX)
+               return true;
+
+       switch (phy_data->redrv_model) {
+       case XGBE_PHY_REDRV_MODEL_4223:
+               if (phy_data->redrv_lane > 3)
+                       return true;
+               break;
+       case XGBE_PHY_REDRV_MODEL_4227:
+               if (phy_data->redrv_lane > 1)
+                       return true;
+               break;
+       default:
+               return true;
+       }
+
+       return false;
+}
+
 static int xgbe_phy_mdio_reset_setup(struct xgbe_prv_data *pdata)
 {
        struct xgbe_phy_data *phy_data = pdata->phy_data;
@@ -2481,6 +2814,20 @@ static int xgbe_phy_init(struct xgbe_prv_data *pdata)
                dev_dbg(pdata->dev, "mdio addr=%u\n", phy_data->mdio_addr);
        }
 
+       reg = XP_IOREAD(pdata, XP_PROP_4);
+       phy_data->redrv = XP_GET_BITS(reg, XP_PROP_4, REDRV_PRESENT);
+       phy_data->redrv_if = XP_GET_BITS(reg, XP_PROP_4, REDRV_IF);
+       phy_data->redrv_addr = XP_GET_BITS(reg, XP_PROP_4, REDRV_ADDR);
+       phy_data->redrv_lane = XP_GET_BITS(reg, XP_PROP_4, REDRV_LANE);
+       phy_data->redrv_model = XP_GET_BITS(reg, XP_PROP_4, REDRV_MODEL);
+       if (phy_data->redrv && netif_msg_probe(pdata)) {
+               dev_dbg(pdata->dev, "redrv present\n");
+               dev_dbg(pdata->dev, "redrv i/f=%u\n", phy_data->redrv_if);
+               dev_dbg(pdata->dev, "redrv addr=%#x\n", phy_data->redrv_addr);
+               dev_dbg(pdata->dev, "redrv lane=%u\n", phy_data->redrv_lane);
+               dev_dbg(pdata->dev, "redrv model=%u\n", phy_data->redrv_model);
+       }
+
        /* Validate the connection requested */
        if (xgbe_phy_conn_type_mismatch(pdata)) {
                dev_err(pdata->dev, "phy mode/connection mismatch (%#x/%#x)\n",
@@ -2499,6 +2846,13 @@ static int xgbe_phy_init(struct xgbe_prv_data *pdata)
        if (ret)
                return ret;
 
+       /* Validate the re-driver information */
+       if (xgbe_phy_redrv_error(phy_data)) {
+               dev_err(pdata->dev, "phy re-driver settings error\n");
+               return -EINVAL;
+       }
+       pdata->kr_redrv = phy_data->redrv;
+
        /* Indicate current mode is unknown */
        phy_data->cur_mode = XGBE_MODE_UNKNOWN;
 
@@ -2651,6 +3005,29 @@ static int xgbe_phy_init(struct xgbe_prv_data *pdata)
                dev_dbg(pdata->dev, "phy supported=%#x\n",
                        pdata->phy.supported);
 
+       if ((phy_data->conn_type & XGBE_CONN_TYPE_MDIO) &&
+           (phy_data->phydev_mode != XGBE_MDIO_MODE_NONE)) {
+               ret = pdata->hw_if.set_ext_mii_mode(pdata, phy_data->mdio_addr,
+                                                   phy_data->phydev_mode);
+               if (ret) {
+                       dev_err(pdata->dev,
+                               "mdio port/clause not compatible (%d/%u)\n",
+                               phy_data->mdio_addr, phy_data->phydev_mode);
+                       return -EINVAL;
+               }
+       }
+
+       if (phy_data->redrv && !phy_data->redrv_if) {
+               ret = pdata->hw_if.set_ext_mii_mode(pdata, phy_data->redrv_addr,
+                                                   XGBE_MDIO_MODE_CL22);
+               if (ret) {
+                       dev_err(pdata->dev,
+                               "redriver mdio port not compatible (%u)\n",
+                               phy_data->redrv_addr);
+                       return -EINVAL;
+               }
+       }
+
        /* Register for driving external PHYs */
        mii = devm_mdiobus_alloc(pdata->dev);
        if (!mii) {
@@ -2700,5 +3077,7 @@ void xgbe_init_function_ptrs_phy_v2(struct xgbe_phy_if *phy_if)
 
        phy_impl->an_config             = xgbe_phy_an_config;
 
+       phy_impl->an_advertising        = xgbe_phy_an_advertising;
+
        phy_impl->an_outcome            = xgbe_phy_an_outcome;
 }
index 34db47094c612f7bbdd3c43aa69ee9e341d52eea..f52a9bd05baca559d7afd6e1184010d103e1c08a 100644 (file)
@@ -508,6 +508,7 @@ enum xgbe_xpcs_access {
 
 enum xgbe_an_mode {
        XGBE_AN_MODE_CL73 = 0,
+       XGBE_AN_MODE_CL73_REDRV,
        XGBE_AN_MODE_CL37,
        XGBE_AN_MODE_CL37_SGMII,
        XGBE_AN_MODE_NONE,
@@ -807,6 +808,9 @@ struct xgbe_phy_impl_if {
        /* Configure auto-negotiation settings */
        int (*an_config)(struct xgbe_prv_data *);
 
+       /* Set/override auto-negotiation advertisement settings */
+       unsigned int (*an_advertising)(struct xgbe_prv_data *);
+
        /* Process results of auto-negotiation */
        enum xgbe_mode (*an_outcome)(struct xgbe_prv_data *);
 
@@ -1124,6 +1128,8 @@ struct xgbe_prv_data {
        unsigned long link_check;
        struct completion mdio_complete;
 
+       unsigned int kr_redrv;
+
        char an_name[IFNAMSIZ + 32];
        struct workqueue_struct *an_workqueue;