From 2a2ee7aafa9a1fab4e610cfd2967991a35b44ef0 Mon Sep 17 00:00:00 2001 From: Uwe Kleine-König Date: Wed, 11 Jan 2017 16:59:20 +0100 Subject: net/phy: marvell: add support for 88e1510 to marvell phy driver MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This is mostly copied verbatim from the Linux driver. Signed-off-by: Uwe Kleine-König Signed-off-by: Sascha Hauer --- drivers/net/phy/marvell.c | 204 +++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 201 insertions(+), 3 deletions(-) (limited to 'drivers') diff --git a/drivers/net/phy/marvell.c b/drivers/net/phy/marvell.c index 6bb50a3a1f..38b2ad31f8 100644 --- a/drivers/net/phy/marvell.c +++ b/drivers/net/phy/marvell.c @@ -31,6 +31,9 @@ #define MII_M1011_PHY_STATUS_RESOLVED BIT(11) #define MII_M1011_PHY_STATUS_LINK BIT(10) +#define MII_M1111_COPPER 0 +#define MII_M1111_FIBER 1 + #define MII_88E1121_PHY_MSCR_PAGE 2 #define MII_88E1121_PHY_MSCR_REG 21 #define MII_88E1121_PHY_MSCR_TX_DELAY BIT(4) @@ -45,6 +48,20 @@ #define MII_88E1540_QSGMII_CONTROL 0x0 #define MII_88E1540_QSGMII_AUTONEG_EN BIT(12) +#define MII_88E1510_GEN_CTRL_REG_1 0x14 +#define MII_88E1510_GEN_CTRL_REG_1_MODE_MASK 0x7 +#define MII_88E1510_GEN_CTRL_REG_1_MODE_SGMII 0x1 /* SGMII to copper */ +#define MII_88E1510_GEN_CTRL_REG_1_RESET 0x8000 /* Soft reset */ + +#define LPA_PAUSE_FIBER 0x180 +#define LPA_PAUSE_ASYM_FIBER 0x100 + +#define ADVERTISE_FIBER_1000HALF 0x40 +#define ADVERTISE_FIBER_1000FULL 0x20 + +#define ADVERTISE_PAUSE_FIBER 0x180 +#define ADVERTISE_PAUSE_ASYM_FIBER 0x100 + /* * marvell_read_status * @@ -133,12 +150,24 @@ static int marvell_read_status(struct phy_device *phydev) return 0; } +#define MII_88E1510_GEN_CTRL_REG_1 0x14 + static inline bool phy_interface_is_rgmii(struct phy_device *phydev) { return phydev->interface >= PHY_INTERFACE_MODE_RGMII && phydev->interface <= PHY_INTERFACE_MODE_RGMII_TXID; }; +/* + * This same function in the Linux kernel parses the marvell,reg-init dt + * property and does the necessary register writes. It's kept as an exercise for + * a future user to implement this. :-) + */ +static int marvell_of_reg_init(struct phy_device *phydev) +{ + return 0; +} + static int m88e1121_config_aneg(struct phy_device *phydev) { int err, oldpage, mscr; @@ -207,6 +236,130 @@ static int m88e1318_config_aneg(struct phy_device *phydev) return m88e1121_config_aneg(phydev); } +/** + * ethtool_adv_to_fiber_adv_t + * @ethadv: the ethtool advertisement settings + * + * A small helper function that translates ethtool advertisement + * settings to phy autonegotiation advertisements for the + * MII_ADV register for fiber link. + */ +static inline u32 ethtool_adv_to_fiber_adv_t(u32 ethadv) +{ + u32 result = 0; + + if (ethadv & ADVERTISED_1000baseT_Half) + result |= ADVERTISE_FIBER_1000HALF; + if (ethadv & ADVERTISED_1000baseT_Full) + result |= ADVERTISE_FIBER_1000FULL; + + if ((ethadv & ADVERTISE_PAUSE_ASYM) && (ethadv & ADVERTISE_PAUSE_CAP)) + result |= LPA_PAUSE_ASYM_FIBER; + else if (ethadv & ADVERTISE_PAUSE_CAP) + result |= (ADVERTISE_PAUSE_FIBER + & (~ADVERTISE_PAUSE_ASYM_FIBER)); + + return result; +} + +/** + * marvell_config_aneg_fiber - restart auto-negotiation or write BMCR + * @phydev: target phy_device struct + * + * Description: If auto-negotiation is enabled, we configure the + * advertising, and then restart auto-negotiation. If it is not + * enabled, then we write the BMCR. Adapted for fiber link in + * some Marvell's devices. + */ +static int marvell_config_aneg_fiber(struct phy_device *phydev) +{ + int changed = 0; + int err; + int adv, oldadv; + u32 advertise; + + if (phydev->autoneg != AUTONEG_ENABLE) + return genphy_setup_forced(phydev); + + /* Only allow advertising what this PHY supports */ + phydev->advertising &= phydev->supported; + advertise = phydev->advertising; + + /* Setup fiber advertisement */ + adv = phy_read(phydev, MII_ADVERTISE); + if (adv < 0) + return adv; + + oldadv = adv; + adv &= ~(ADVERTISE_FIBER_1000HALF | ADVERTISE_FIBER_1000FULL + | LPA_PAUSE_FIBER); + adv |= ethtool_adv_to_fiber_adv_t(advertise); + + if (adv != oldadv) { + err = phy_write(phydev, MII_ADVERTISE, adv); + if (err < 0) + return err; + + changed = 1; + } + + if (changed == 0) { + /* Advertisement hasn't changed, but maybe aneg was never on to + * begin with? Or maybe phy was isolated? + */ + int ctl = phy_read(phydev, MII_BMCR); + + if (ctl < 0) + return ctl; + + if (!(ctl & BMCR_ANENABLE) || (ctl & BMCR_ISOLATE)) + changed = 1; /* do restart aneg */ + } + + /* Only restart aneg if we are advertising something different + * than we were before. + */ + if (changed > 0) + changed = genphy_restart_aneg(phydev); + + return changed; +} + +static int m88e1510_config_aneg(struct phy_device *phydev) +{ + int err; + + err = phy_write(phydev, MII_MARVELL_PHY_PAGE, MII_M1111_COPPER); + if (err < 0) + goto error; + + /* Configure the copper link first */ + err = m88e1318_config_aneg(phydev); + if (err < 0) + goto error; + + /* Then the fiber link */ + err = phy_write(phydev, MII_MARVELL_PHY_PAGE, MII_M1111_FIBER); + if (err < 0) + goto error; + + err = marvell_config_aneg_fiber(phydev); + if (err < 0) + goto error; + + return phy_write(phydev, MII_MARVELL_PHY_PAGE, MII_M1111_COPPER); + +error: + phy_write(phydev, MII_MARVELL_PHY_PAGE, MII_M1111_COPPER); + return err; +} + +static int marvell_config_init(struct phy_device *phydev) +{ + /* Set registers from marvell,reg-init DT property */ + return marvell_of_reg_init(phydev); +} + static int m88e1540_config_init(struct phy_device *phydev) { u16 reg; @@ -246,7 +399,7 @@ static int m88e1540_config_init(struct phy_device *phydev) if (ret < 0) return ret; - return 0; + return marvell_config_init(phydev); } static int m88e1121_config_init(struct phy_device *phydev) @@ -289,9 +442,10 @@ static int m88e1121_config_init(struct phy_device *phydev) if (ret < 0) return ret; - return 0; + return marvell_config_init(phydev); } + static int m88e1318s_config_init(struct phy_device *phydev) { u16 reg; @@ -311,6 +465,41 @@ static int m88e1318s_config_init(struct phy_device *phydev) return m88e1121_config_init(phydev); } +static int m88e1510_config_init(struct phy_device *phydev) +{ + int err; + int temp; + + /* SGMII-to-Copper mode initialization */ + if (phydev->interface == PHY_INTERFACE_MODE_SGMII) { + /* Select page 18 */ + err = phy_write(phydev, MII_MARVELL_PHY_PAGE, 18); + if (err < 0) + return err; + + /* In reg 20, write MODE[2:0] = 0x1 (SGMII to Copper) */ + temp = phy_read(phydev, MII_88E1510_GEN_CTRL_REG_1); + temp &= ~MII_88E1510_GEN_CTRL_REG_1_MODE_MASK; + temp |= MII_88E1510_GEN_CTRL_REG_1_MODE_SGMII; + err = phy_write(phydev, MII_88E1510_GEN_CTRL_REG_1, temp); + if (err < 0) + return err; + + /* PHY reset is necessary after changing MODE[2:0] */ + temp |= MII_88E1510_GEN_CTRL_REG_1_RESET; + err = phy_write(phydev, MII_88E1510_GEN_CTRL_REG_1, temp); + if (err < 0) + return err; + + /* Reset page selection */ + err = phy_write(phydev, MII_MARVELL_PHY_PAGE, 0); + if (err < 0) + return err; + } + + return m88e1121_config_init(phydev); +} + static struct phy_driver marvell_drivers[] = { { .phy_id = MARVELL_PHY_ID_88E1121R, @@ -339,13 +528,22 @@ static struct phy_driver marvell_drivers[] = { .config_aneg = &genphy_config_aneg, .read_status = &marvell_read_status, }, + { + .phy_id = MARVELL_PHY_ID_88E1510, + .phy_id_mask = MARVELL_PHY_ID_MASK, + .drv.name = "Marvell 88E1510", + .features = PHY_GBIT_FEATURES | SUPPORTED_FIBRE, + .config_init = &m88e1510_config_init, + .config_aneg = &m88e1510_config_aneg, + .read_status = &marvell_read_status, + }, { .phy_id = MARVELL_PHY_ID_88E1540, .phy_id_mask = MARVELL_PHY_ID_MASK, .drv.name = "Marvell 88E1540", .features = PHY_GBIT_FEATURES, .config_init = &m88e1540_config_init, - .config_aneg = &genphy_config_aneg, + .config_aneg = &m88e1510_config_aneg, .read_status = &marvell_read_status, }, }; -- cgit v1.2.3