// SPDX-License-Identifier: GPL-2.0+ /* * Motorcomm 8511/8521/8531S PHY driver. * * Author: Peter Geis * Author: Frank */ #include #include #include #include #define PHY_ID_YT8511 0x0000010a #define PHY_ID_YT8521 0x0000011A #define PHY_ID_YT8531S 0x4F51E91A /* YT8521/YT8531S Register Overview * UTP Register space | FIBER Register space * ------------------------------------------------------------ * | UTP MII | FIBER MII | * | UTP MMD | | * | UTP Extended | FIBER Extended | * ------------------------------------------------------------ * | Common Extended | * ------------------------------------------------------------ */ /* 0x10 ~ 0x15 , 0x1E and 0x1F are common MII registers of yt phy */ /* Specific Function Control Register */ #define YTPHY_SPECIFIC_FUNCTION_CONTROL_REG 0x10 /* 2b00 Manual MDI configuration * 2b01 Manual MDIX configuration * 2b10 Reserved * 2b11 Enable automatic crossover for all modes *default* */ #define YTPHY_SFCR_MDI_CROSSOVER_MODE_MASK (BIT(6) | BIT(5)) #define YTPHY_SFCR_CROSSOVER_EN BIT(3) #define YTPHY_SFCR_SQE_TEST_EN BIT(2) #define YTPHY_SFCR_POLARITY_REVERSAL_EN BIT(1) #define YTPHY_SFCR_JABBER_DIS BIT(0) /* Specific Status Register */ #define YTPHY_SPECIFIC_STATUS_REG 0x11 #define YTPHY_SSR_SPEED_MODE_OFFSET 14 #define YTPHY_SSR_SPEED_MODE_MASK (BIT(15) | BIT(14)) #define YTPHY_SSR_SPEED_10M 0x0 #define YTPHY_SSR_SPEED_100M 0x1 #define YTPHY_SSR_SPEED_1000M 0x2 #define YTPHY_SSR_DUPLEX_OFFSET 13 #define YTPHY_SSR_DUPLEX BIT(13) #define YTPHY_SSR_PAGE_RECEIVED BIT(12) #define YTPHY_SSR_SPEED_DUPLEX_RESOLVED BIT(11) #define YTPHY_SSR_LINK BIT(10) #define YTPHY_SSR_MDIX_CROSSOVER BIT(6) #define YTPHY_SSR_DOWNGRADE BIT(5) #define YTPHY_SSR_TRANSMIT_PAUSE BIT(3) #define YTPHY_SSR_RECEIVE_PAUSE BIT(2) #define YTPHY_SSR_POLARITY BIT(1) #define YTPHY_SSR_JABBER BIT(0) /* Interrupt enable Register */ #define YTPHY_INTERRUPT_ENABLE_REG 0x12 #define YTPHY_IER_WOL BIT(6) /* Interrupt Status Register */ #define YTPHY_INTERRUPT_STATUS_REG 0x13 #define YTPHY_ISR_AUTONEG_ERR BIT(15) #define YTPHY_ISR_SPEED_CHANGED BIT(14) #define YTPHY_ISR_DUPLEX_CHANGED BIT(13) #define YTPHY_ISR_PAGE_RECEIVED BIT(12) #define YTPHY_ISR_LINK_FAILED BIT(11) #define YTPHY_ISR_LINK_SUCCESSED BIT(10) #define YTPHY_ISR_WOL BIT(6) #define YTPHY_ISR_WIRESPEED_DOWNGRADE BIT(5) #define YTPHY_ISR_SERDES_LINK_FAILED BIT(3) #define YTPHY_ISR_SERDES_LINK_SUCCESSED BIT(2) #define YTPHY_ISR_POLARITY_CHANGED BIT(1) #define YTPHY_ISR_JABBER_HAPPENED BIT(0) /* Speed Auto Downgrade Control Register */ #define YTPHY_SPEED_AUTO_DOWNGRADE_CONTROL_REG 0x14 #define YTPHY_SADCR_SPEED_DOWNGRADE_EN BIT(5) /* If these bits are set to 3, the PHY attempts five times ( 3(set value) + * additional 2) before downgrading, default 0x3 */ #define YTPHY_SADCR_SPEED_RETRY_LIMIT (0x3 << 2) /* Rx Error Counter Register */ #define YTPHY_RX_ERROR_COUNTER_REG 0x15 /* Extended Register's Address Offset Register */ #define YTPHY_PAGE_SELECT 0x1E /* Extended Register's Data Register */ #define YTPHY_PAGE_DATA 0x1F /* FIBER Auto-Negotiation link partner ability */ #define YTPHY_FLPA_PAUSE (0x3 << 7) #define YTPHY_FLPA_ASYM_PAUSE (0x2 << 7) #define YT8511_PAGE_SELECT 0x1e #define YT8511_PAGE 0x1f #define YT8511_EXT_CLK_GATE 0x0c #define YT8511_EXT_DELAY_DRIVE 0x0d #define YT8511_EXT_SLEEP_CTRL 0x27 /* 2b00 25m from pll * 2b01 25m from xtl *default* * 2b10 62.m from pll * 2b11 125m from pll */ #define YT8511_CLK_125M (BIT(2) | BIT(1)) #define YT8511_PLLON_SLP BIT(14) /* RX Delay enabled = 1.8ns 1000T, 8ns 10/100T */ #define YT8511_DELAY_RX BIT(0) /* TX Gig-E Delay is bits 7:4, default 0x5 * TX Fast-E Delay is bits 15:12, default 0xf * Delay = 150ps * N - 250ps * On = 2000ps, off = 50ps */ #define YT8511_DELAY_GE_TX_EN (0xf << 4) #define YT8511_DELAY_GE_TX_DIS (0x2 << 4) #define YT8511_DELAY_FE_TX_EN (0xf << 12) #define YT8511_DELAY_FE_TX_DIS (0x2 << 12) /* Extended register is different from MMD Register and MII Register. * We can use ytphy_read_ext/ytphy_write_ext/ytphy_modify_ext function to * operate extended register. * Extended Register start */ /* Phy gmii clock gating Register */ #define YT8521_CLOCK_GATING_REG 0xC #define YT8521_CGR_RX_CLK_EN BIT(12) #define YT8521_EXTREG_SLEEP_CONTROL1_REG 0x27 #define YT8521_ESC1R_SLEEP_SW BIT(15) #define YT8521_ESC1R_PLLON_SLP BIT(14) /* Phy fiber Link timer cfg2 Register */ #define YT8521_LINK_TIMER_CFG2_REG 0xA5 #define YT8521_LTCR_EN_AUTOSEN BIT(15) /* 0xA000, 0xA001, 0xA003, 0xA006 ~ 0xA00A and 0xA012 are common ext registers * of yt8521 phy. There is no need to switch reg space when operating these * registers. */ #define YT8521_REG_SPACE_SELECT_REG 0xA000 #define YT8521_RSSR_SPACE_MASK BIT(1) #define YT8521_RSSR_FIBER_SPACE (0x1 << 1) #define YT8521_RSSR_UTP_SPACE (0x0 << 1) #define YT8521_RSSR_TO_BE_ARBITRATED (0xFF) #define YT8521_CHIP_CONFIG_REG 0xA001 #define YT8521_CCR_SW_RST BIT(15) #define YT8521_CCR_MODE_SEL_MASK (BIT(2) | BIT(1) | BIT(0)) #define YT8521_CCR_MODE_UTP_TO_RGMII 0 #define YT8521_CCR_MODE_FIBER_TO_RGMII 1 #define YT8521_CCR_MODE_UTP_FIBER_TO_RGMII 2 #define YT8521_CCR_MODE_UTP_TO_SGMII 3 #define YT8521_CCR_MODE_SGPHY_TO_RGMAC 4 #define YT8521_CCR_MODE_SGMAC_TO_RGPHY 5 #define YT8521_CCR_MODE_UTP_TO_FIBER_AUTO 6 #define YT8521_CCR_MODE_UTP_TO_FIBER_FORCE 7 /* 3 phy polling modes,poll mode combines utp and fiber mode*/ #define YT8521_MODE_FIBER 0x1 #define YT8521_MODE_UTP 0x2 #define YT8521_MODE_POLL 0x3 #define YT8521_RGMII_CONFIG1_REG 0xA003 /* TX Gig-E Delay is bits 3:0, default 0x1 * TX Fast-E Delay is bits 7:4, default 0xf * RX Delay is bits 13:10, default 0x0 * Delay = 150ps * N * On = 2250ps, off = 0ps */ #define YT8521_RC1R_RX_DELAY_MASK (0xF << 10) #define YT8521_RC1R_RX_DELAY_EN (0xF << 10) #define YT8521_RC1R_RX_DELAY_DIS (0x0 << 10) #define YT8521_RC1R_FE_TX_DELAY_MASK (0xF << 4) #define YT8521_RC1R_FE_TX_DELAY_EN (0xF << 4) #define YT8521_RC1R_FE_TX_DELAY_DIS (0x0 << 4) #define YT8521_RC1R_GE_TX_DELAY_MASK (0xF << 0) #define YT8521_RC1R_GE_TX_DELAY_EN (0xF << 0) #define YT8521_RC1R_GE_TX_DELAY_DIS (0x0 << 0) #define YTPHY_MISC_CONFIG_REG 0xA006 #define YTPHY_MCR_FIBER_SPEED_MASK BIT(0) #define YTPHY_MCR_FIBER_1000BX (0x1 << 0) #define YTPHY_MCR_FIBER_100FX (0x0 << 0) /* WOL MAC ADDR: MACADDR2(highest), MACADDR1(middle), MACADDR0(lowest) */ #define YTPHY_WOL_MACADDR2_REG 0xA007 #define YTPHY_WOL_MACADDR1_REG 0xA008 #define YTPHY_WOL_MACADDR0_REG 0xA009 #define YTPHY_WOL_CONFIG_REG 0xA00A #define YTPHY_WCR_INTR_SEL BIT(6) #define YTPHY_WCR_ENABLE BIT(3) /* 2b00 84ms * 2b01 168ms *default* * 2b10 336ms * 2b11 672ms */ #define YTPHY_WCR_PULSE_WIDTH_MASK (BIT(2) | BIT(1)) #define YTPHY_WCR_PULSE_WIDTH_672MS (BIT(2) | BIT(1)) /* 1b0 Interrupt and WOL events is level triggered and active LOW *default* * 1b1 Interrupt and WOL events is pulse triggered and active LOW */ #define YTPHY_WCR_TYPE_PULSE BIT(0) #define YT8531S_SYNCE_CFG_REG 0xA012 #define YT8531S_SCR_SYNCE_ENABLE BIT(6) /* Extended Register end */ struct yt8521_priv { /* combo_advertising is used for case of YT8521 in combo mode, * this means that yt8521 may work in utp or fiber mode which depends * on which media is connected (YT8521_RSSR_TO_BE_ARBITRATED). */ __ETHTOOL_DECLARE_LINK_MODE_MASK(combo_advertising); /* YT8521_MODE_FIBER / YT8521_MODE_UTP / YT8521_MODE_POLL*/ u8 polling_mode; u8 strap_mode; /* 8 working modes */ /* current reg page of yt8521 phy: * YT8521_RSSR_UTP_SPACE * YT8521_RSSR_FIBER_SPACE * YT8521_RSSR_TO_BE_ARBITRATED */ u8 reg_page; }; /** * ytphy_read_ext() - read a PHY's extended register * @phydev: a pointer to a &struct phy_device * @regnum: register number to read * * NOTE:The caller must have taken the MDIO bus lock. * * returns the value of regnum reg or negative error code */ static int ytphy_read_ext(struct phy_device *phydev, u16 regnum) { int ret; ret = __phy_write(phydev, YTPHY_PAGE_SELECT, regnum); if (ret < 0) return ret; return __phy_read(phydev, YTPHY_PAGE_DATA); } /** * ytphy_read_ext_with_lock() - read a PHY's extended register * @phydev: a pointer to a &struct phy_device * @regnum: register number to read * * returns the value of regnum reg or negative error code */ static int ytphy_read_ext_with_lock(struct phy_device *phydev, u16 regnum) { int ret; phy_lock_mdio_bus(phydev); ret = ytphy_read_ext(phydev, regnum); phy_unlock_mdio_bus(phydev); return ret; } /** * ytphy_write_ext() - write a PHY's extended register * @phydev: a pointer to a &struct phy_device * @regnum: register number to write * @val: value to write to @regnum * * NOTE:The caller must have taken the MDIO bus lock. * * returns 0 or negative error code */ static int ytphy_write_ext(struct phy_device *phydev, u16 regnum, u16 val) { int ret; ret = __phy_write(phydev, YTPHY_PAGE_SELECT, regnum); if (ret < 0) return ret; return __phy_write(phydev, YTPHY_PAGE_DATA, val); } /** * ytphy_write_ext_with_lock() - write a PHY's extended register * @phydev: a pointer to a &struct phy_device * @regnum: register number to write * @val: value to write to @regnum * * returns 0 or negative error code */ static int ytphy_write_ext_with_lock(struct phy_device *phydev, u16 regnum, u16 val) { int ret; phy_lock_mdio_bus(phydev); ret = ytphy_write_ext(phydev, regnum, val); phy_unlock_mdio_bus(phydev); return ret; } /** * ytphy_modify_ext() - bits modify a PHY's extended register * @phydev: a pointer to a &struct phy_device * @regnum: register number to write * @mask: bit mask of bits to clear * @set: bit mask of bits to set * * NOTE: Convenience function which allows a PHY's extended register to be * modified as new register value = (old register value & ~mask) | set. * The caller must have taken the MDIO bus lock. * * returns 0 or negative error code */ static int ytphy_modify_ext(struct phy_device *phydev, u16 regnum, u16 mask, u16 set) { int ret; ret = __phy_write(phydev, YTPHY_PAGE_SELECT, regnum); if (ret < 0) return ret; return __phy_modify(phydev, YTPHY_PAGE_DATA, mask, set); } /** * ytphy_modify_ext_with_lock() - bits modify a PHY's extended register * @phydev: a pointer to a &struct phy_device * @regnum: register number to write * @mask: bit mask of bits to clear * @set: bit mask of bits to set * * NOTE: Convenience function which allows a PHY's extended register to be * modified as new register value = (old register value & ~mask) | set. * * returns 0 or negative error code */ static int ytphy_modify_ext_with_lock(struct phy_device *phydev, u16 regnum, u16 mask, u16 set) { int ret; phy_lock_mdio_bus(phydev); ret = ytphy_modify_ext(phydev, regnum, mask, set); phy_unlock_mdio_bus(phydev); return ret; } /** * ytphy_get_wol() - report whether wake-on-lan is enabled * @phydev: a pointer to a &struct phy_device * @wol: a pointer to a &struct ethtool_wolinfo * * NOTE: YTPHY_WOL_CONFIG_REG is common ext reg. */ static void ytphy_get_wol(struct phy_device *phydev, struct ethtool_wolinfo *wol) { int wol_config; wol->supported = WAKE_MAGIC; wol->wolopts = 0; wol_config = ytphy_read_ext_with_lock(phydev, YTPHY_WOL_CONFIG_REG); if (wol_config < 0) return; if (wol_config & YTPHY_WCR_ENABLE) wol->wolopts |= WAKE_MAGIC; } /** * ytphy_set_wol() - turn wake-on-lan on or off * @phydev: a pointer to a &struct phy_device * @wol: a pointer to a &struct ethtool_wolinfo * * NOTE: YTPHY_WOL_CONFIG_REG, YTPHY_WOL_MACADDR2_REG, YTPHY_WOL_MACADDR1_REG * and YTPHY_WOL_MACADDR0_REG are common ext reg. The * YTPHY_INTERRUPT_ENABLE_REG of UTP is special, fiber also use this register. * * returns 0 or negative errno code */ static int ytphy_set_wol(struct phy_device *phydev, struct ethtool_wolinfo *wol) { struct net_device *p_attached_dev; const u16 mac_addr_reg[] = { YTPHY_WOL_MACADDR2_REG, YTPHY_WOL_MACADDR1_REG, YTPHY_WOL_MACADDR0_REG, }; const u8 *mac_addr; int old_page; int ret = 0; u16 mask; u16 val; u8 i; if (wol->wolopts & WAKE_MAGIC) { p_attached_dev = phydev->attached_dev; if (!p_attached_dev) return -ENODEV; mac_addr = (const u8 *)p_attached_dev->dev_addr; if (!is_valid_ether_addr(mac_addr)) return -EINVAL; /* lock mdio bus then switch to utp reg space */ old_page = phy_select_page(phydev, YT8521_RSSR_UTP_SPACE); if (old_page < 0) goto err_restore_page; /* Store the device address for the magic packet */ for (i = 0; i < 3; i++) { ret = ytphy_write_ext(phydev, mac_addr_reg[i], ((mac_addr[i * 2] << 8)) | (mac_addr[i * 2 + 1])); if (ret < 0) goto err_restore_page; } /* Enable WOL feature */ mask = YTPHY_WCR_PULSE_WIDTH_MASK | YTPHY_WCR_INTR_SEL; val = YTPHY_WCR_ENABLE | YTPHY_WCR_INTR_SEL; val |= YTPHY_WCR_TYPE_PULSE | YTPHY_WCR_PULSE_WIDTH_672MS; ret = ytphy_modify_ext(phydev, YTPHY_WOL_CONFIG_REG, mask, val); if (ret < 0) goto err_restore_page; /* Enable WOL interrupt */ ret = __phy_modify(phydev, YTPHY_INTERRUPT_ENABLE_REG, 0, YTPHY_IER_WOL); if (ret < 0) goto err_restore_page; } else { old_page = phy_select_page(phydev, YT8521_RSSR_UTP_SPACE); if (old_page < 0) goto err_restore_page; /* Disable WOL feature */ mask = YTPHY_WCR_ENABLE | YTPHY_WCR_INTR_SEL; ret = ytphy_modify_ext(phydev, YTPHY_WOL_CONFIG_REG, mask, 0); /* Disable WOL interrupt */ ret = __phy_modify(phydev, YTPHY_INTERRUPT_ENABLE_REG, YTPHY_IER_WOL, 0); if (ret < 0) goto err_restore_page; } err_restore_page: return phy_restore_page(phydev, old_page, ret); } static int yt8511_read_page(struct phy_device *phydev) { return __phy_read(phydev, YT8511_PAGE_SELECT); }; static int yt8511_write_page(struct phy_device *phydev, int page) { return __phy_write(phydev, YT8511_PAGE_SELECT, page); }; static int yt8511_config_init(struct phy_device *phydev) { int oldpage, ret = 0; unsigned int ge, fe; oldpage = phy_select_page(phydev, YT8511_EXT_CLK_GATE); if (oldpage < 0) goto err_restore_page; /* set rgmii delay mode */ switch (phydev->interface) { case PHY_INTERFACE_MODE_RGMII: ge = YT8511_DELAY_GE_TX_DIS; fe = YT8511_DELAY_FE_TX_DIS; break; case PHY_INTERFACE_MODE_RGMII_RXID: ge = YT8511_DELAY_RX | YT8511_DELAY_GE_TX_DIS; fe = YT8511_DELAY_FE_TX_DIS; break; case PHY_INTERFACE_MODE_RGMII_TXID: ge = YT8511_DELAY_GE_TX_EN; fe = YT8511_DELAY_FE_TX_EN; break; case PHY_INTERFACE_MODE_RGMII_ID: ge = YT8511_DELAY_RX | YT8511_DELAY_GE_TX_EN; fe = YT8511_DELAY_FE_TX_EN; break; default: /* do not support other modes */ ret = -EOPNOTSUPP; goto err_restore_page; } ret = __phy_modify(phydev, YT8511_PAGE, (YT8511_DELAY_RX | YT8511_DELAY_GE_TX_EN), ge); if (ret < 0) goto err_restore_page; /* set clock mode to 125mhz */ ret = __phy_modify(phydev, YT8511_PAGE, 0, YT8511_CLK_125M); if (ret < 0) goto err_restore_page; /* fast ethernet delay is in a separate page */ ret = __phy_write(phydev, YT8511_PAGE_SELECT, YT8511_EXT_DELAY_DRIVE); if (ret < 0) goto err_restore_page; ret = __phy_modify(phydev, YT8511_PAGE, YT8511_DELAY_FE_TX_EN, fe); if (ret < 0) goto err_restore_page; /* leave pll enabled in sleep */ ret = __phy_write(phydev, YT8511_PAGE_SELECT, YT8511_EXT_SLEEP_CTRL); if (ret < 0) goto err_restore_page; ret = __phy_modify(phydev, YT8511_PAGE, 0, YT8511_PLLON_SLP); if (ret < 0) goto err_restore_page; err_restore_page: return phy_restore_page(phydev, oldpage, ret); } /** * yt8521_read_page() - read reg page * @phydev: a pointer to a &struct phy_device * * returns current reg space of yt8521 (YT8521_RSSR_FIBER_SPACE/ * YT8521_RSSR_UTP_SPACE) or negative errno code */ static int yt8521_read_page(struct phy_device *phydev) { int old_page; old_page = ytphy_read_ext(phydev, YT8521_REG_SPACE_SELECT_REG); if (old_page < 0) return old_page; if ((old_page & YT8521_RSSR_SPACE_MASK) == YT8521_RSSR_FIBER_SPACE) return YT8521_RSSR_FIBER_SPACE; return YT8521_RSSR_UTP_SPACE; }; /** * yt8521_write_page() - write reg page * @phydev: a pointer to a &struct phy_device * @page: The reg page(YT8521_RSSR_FIBER_SPACE/YT8521_RSSR_UTP_SPACE) to write. * * returns 0 or negative errno code */ static int yt8521_write_page(struct phy_device *phydev, int page) { int mask = YT8521_RSSR_SPACE_MASK; int set; if ((page & YT8521_RSSR_SPACE_MASK) == YT8521_RSSR_FIBER_SPACE) set = YT8521_RSSR_FIBER_SPACE; else set = YT8521_RSSR_UTP_SPACE; return ytphy_modify_ext(phydev, YT8521_REG_SPACE_SELECT_REG, mask, set); }; /** * yt8521_probe() - read chip config then set suitable polling_mode * @phydev: a pointer to a &struct phy_device * * returns 0 or negative errno code */ static int yt8521_probe(struct phy_device *phydev) { struct device *dev = &phydev->mdio.dev; struct yt8521_priv *priv; int chip_config; int ret; priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); if (!priv) return -ENOMEM; phydev->priv = priv; chip_config = ytphy_read_ext_with_lock(phydev, YT8521_CHIP_CONFIG_REG); if (chip_config < 0) return chip_config; priv->strap_mode = chip_config & YT8521_CCR_MODE_SEL_MASK; switch (priv->strap_mode) { case YT8521_CCR_MODE_FIBER_TO_RGMII: case YT8521_CCR_MODE_SGPHY_TO_RGMAC: case YT8521_CCR_MODE_SGMAC_TO_RGPHY: priv->polling_mode = YT8521_MODE_FIBER; priv->reg_page = YT8521_RSSR_FIBER_SPACE; phydev->port = PORT_FIBRE; break; case YT8521_CCR_MODE_UTP_FIBER_TO_RGMII: case YT8521_CCR_MODE_UTP_TO_FIBER_AUTO: case YT8521_CCR_MODE_UTP_TO_FIBER_FORCE: priv->polling_mode = YT8521_MODE_POLL; priv->reg_page = YT8521_RSSR_TO_BE_ARBITRATED; phydev->port = PORT_NONE; break; case YT8521_CCR_MODE_UTP_TO_SGMII: case YT8521_CCR_MODE_UTP_TO_RGMII: priv->polling_mode = YT8521_MODE_UTP; priv->reg_page = YT8521_RSSR_UTP_SPACE; phydev->port = PORT_TP; break; } /* set default reg space */ if (priv->reg_page != YT8521_RSSR_TO_BE_ARBITRATED) { ret = ytphy_write_ext_with_lock(phydev, YT8521_REG_SPACE_SELECT_REG, priv->reg_page); if (ret < 0) return ret; } return 0; } /** * yt8531s_probe() - read chip config then set suitable polling_mode * @phydev: a pointer to a &struct phy_device * * returns 0 or negative errno code */ static int yt8531s_probe(struct phy_device *phydev) { int ret; /* Disable SyncE clock output by default */ ret = ytphy_modify_ext_with_lock(phydev, YT8531S_SYNCE_CFG_REG, YT8531S_SCR_SYNCE_ENABLE, 0); if (ret < 0) return ret; /* same as yt8521_probe */ return yt8521_probe(phydev); } /** * ytphy_utp_read_lpa() - read LPA then setup lp_advertising for utp * @phydev: a pointer to a &struct phy_device * * NOTE:The caller must have taken the MDIO bus lock. * * returns 0 or negative errno code */ static int ytphy_utp_read_lpa(struct phy_device *phydev) { int lpa, lpagb; if (phydev->autoneg == AUTONEG_ENABLE) { if (!phydev->autoneg_complete) { mii_stat1000_mod_linkmode_lpa_t(phydev->lp_advertising, 0); mii_lpa_mod_linkmode_lpa_t(phydev->lp_advertising, 0); return 0; } if (phydev->is_gigabit_capable) { lpagb = __phy_read(phydev, MII_STAT1000); if (lpagb < 0) return lpagb; if (lpagb & LPA_1000MSFAIL) { int adv = __phy_read(phydev, MII_CTRL1000); if (adv < 0) return adv; if (adv & CTL1000_ENABLE_MASTER) phydev_err(phydev, "Master/Slave resolution failed, maybe conflicting manual settings?\n"); else phydev_err(phydev, "Master/Slave resolution failed\n"); return -ENOLINK; } mii_stat1000_mod_linkmode_lpa_t(phydev->lp_advertising, lpagb); } lpa = __phy_read(phydev, MII_LPA); if (lpa < 0) return lpa; mii_lpa_mod_linkmode_lpa_t(phydev->lp_advertising, lpa); } else { linkmode_zero(phydev->lp_advertising); } return 0; } /** * yt8521_adjust_status() - update speed and duplex to phydev. when in fiber * mode, adjust speed and duplex. * @phydev: a pointer to a &struct phy_device * @status: yt8521 status read from YTPHY_SPECIFIC_STATUS_REG * @is_utp: false(yt8521 work in fiber mode) or true(yt8521 work in utp mode) * * NOTE:The caller must have taken the MDIO bus lock. * * returns 0 */ static int yt8521_adjust_status(struct phy_device *phydev, int status, bool is_utp) { int speed_mode, duplex; int speed; int err; int lpa; if (is_utp) duplex = (status & YTPHY_SSR_DUPLEX) >> YTPHY_SSR_DUPLEX_OFFSET; else duplex = DUPLEX_FULL; /* for fiber, it always DUPLEX_FULL */ speed_mode = (status & YTPHY_SSR_SPEED_MODE_MASK) >> YTPHY_SSR_SPEED_MODE_OFFSET; switch (speed_mode) { case YTPHY_SSR_SPEED_10M: if (is_utp) speed = SPEED_10; else /* for fiber, it will never run here, default to * SPEED_UNKNOWN */ speed = SPEED_UNKNOWN; break; case YTPHY_SSR_SPEED_100M: speed = SPEED_100; break; case YTPHY_SSR_SPEED_1000M: speed = SPEED_1000; break; default: speed = SPEED_UNKNOWN; break; } phydev->speed = speed; phydev->duplex = duplex; if (is_utp) { err = ytphy_utp_read_lpa(phydev); if (err < 0) return err; phy_resolve_aneg_pause(phydev); } else { lpa = __phy_read(phydev, MII_LPA); if (lpa < 0) return lpa; /* only support 1000baseX Full */ linkmode_mod_bit(ETHTOOL_LINK_MODE_1000baseX_Full_BIT, phydev->lp_advertising, lpa & LPA_1000XFULL); if (!(lpa & YTPHY_FLPA_PAUSE)) { phydev->pause = 0; phydev->asym_pause = 0; } else if ((lpa & YTPHY_FLPA_ASYM_PAUSE)) { phydev->pause = 1; phydev->asym_pause = 1; } else { phydev->pause = 1; phydev->asym_pause = 0; } } return 0; } /** * yt8521_read_status_paged() - determines the speed and duplex of one page * @phydev: a pointer to a &struct phy_device * @page: The reg page(YT8521_RSSR_FIBER_SPACE/YT8521_RSSR_UTP_SPACE) to * operate. * * returns 1 (utp or fiber link),0 (no link) or negative errno code */ static int yt8521_read_status_paged(struct phy_device *phydev, int page) { int fiber_latch_val; int fiber_curr_val; int old_page; int ret = 0; int status; int link; linkmode_zero(phydev->lp_advertising); phydev->duplex = DUPLEX_UNKNOWN; phydev->speed = SPEED_UNKNOWN; phydev->asym_pause = 0; phydev->pause = 0; /* YT8521 has two reg space (utp/fiber) for linkup with utp/fiber * respectively. but for utp/fiber combo mode, reg space should be * arbitrated based on media priority. by default, utp takes * priority. reg space should be properly set before read * YTPHY_SPECIFIC_STATUS_REG. */ page &= YT8521_RSSR_SPACE_MASK; old_page = phy_select_page(phydev, page); if (old_page < 0) goto err_restore_page; /* Read YTPHY_SPECIFIC_STATUS_REG, which indicates the speed and duplex * of the PHY is actually using. */ ret = __phy_read(phydev, YTPHY_SPECIFIC_STATUS_REG); if (ret < 0) goto err_restore_page; status = ret; link = !!(status & YTPHY_SSR_LINK); /* When PHY is in fiber mode, speed transferred from 1000Mbps to * 100Mbps,there is not link down from YTPHY_SPECIFIC_STATUS_REG, so * we need check MII_BMSR to identify such case. */ if (page == YT8521_RSSR_FIBER_SPACE) { ret = __phy_read(phydev, MII_BMSR); if (ret < 0) goto err_restore_page; fiber_latch_val = ret; ret = __phy_read(phydev, MII_BMSR); if (ret < 0) goto err_restore_page; fiber_curr_val = ret; if (link && fiber_latch_val != fiber_curr_val) { link = 0; phydev_info(phydev, "%s, fiber link down detect, latch = %04x, curr = %04x\n", __func__, fiber_latch_val, fiber_curr_val); } } else { /* Read autonegotiation status */ ret = __phy_read(phydev, MII_BMSR); if (ret < 0) goto err_restore_page; phydev->autoneg_complete = ret & BMSR_ANEGCOMPLETE ? 1 : 0; } if (link) { if (page == YT8521_RSSR_UTP_SPACE) yt8521_adjust_status(phydev, status, true); else yt8521_adjust_status(phydev, status, false); } return phy_restore_page(phydev, old_page, link); err_restore_page: return phy_restore_page(phydev, old_page, ret); } /** * yt8521_read_status() - determines the negotiated speed and duplex * @phydev: a pointer to a &struct phy_device * * returns 0 or negative errno code */ static int yt8521_read_status(struct phy_device *phydev) { struct yt8521_priv *priv = phydev->priv; int link_fiber = 0; int link_utp; int link; int ret; if (priv->reg_page != YT8521_RSSR_TO_BE_ARBITRATED) { link = yt8521_read_status_paged(phydev, priv->reg_page); if (link < 0) return link; } else { /* when page is YT8521_RSSR_TO_BE_ARBITRATED, arbitration is * needed. by default, utp is higher priority. */ link_utp = yt8521_read_status_paged(phydev, YT8521_RSSR_UTP_SPACE); if (link_utp < 0) return link_utp; if (!link_utp) { link_fiber = yt8521_read_status_paged(phydev, YT8521_RSSR_FIBER_SPACE); if (link_fiber < 0) return link_fiber; } link = link_utp || link_fiber; } if (link) { if (phydev->link == 0) { /* arbitrate reg space based on linkup media type. */ if (priv->polling_mode == YT8521_MODE_POLL && priv->reg_page == YT8521_RSSR_TO_BE_ARBITRATED) { if (link_fiber) priv->reg_page = YT8521_RSSR_FIBER_SPACE; else priv->reg_page = YT8521_RSSR_UTP_SPACE; ret = ytphy_write_ext_with_lock(phydev, YT8521_REG_SPACE_SELECT_REG, priv->reg_page); if (ret < 0) return ret; phydev->port = link_fiber ? PORT_FIBRE : PORT_TP; phydev_info(phydev, "%s, link up, media: %s\n", __func__, (phydev->port == PORT_TP) ? "UTP" : "Fiber"); } } phydev->link = 1; } else { if (phydev->link == 1) { phydev_info(phydev, "%s, link down, media: %s\n", __func__, (phydev->port == PORT_TP) ? "UTP" : "Fiber"); /* When in YT8521_MODE_POLL mode, need prepare for next * arbitration. */ if (priv->polling_mode == YT8521_MODE_POLL) { priv->reg_page = YT8521_RSSR_TO_BE_ARBITRATED; phydev->port = PORT_NONE; } } phydev->link = 0; } return 0; } /** * yt8521_modify_bmcr_paged - bits modify a PHY's BMCR register of one page * @phydev: the phy_device struct * @page: The reg page(YT8521_RSSR_FIBER_SPACE/YT8521_RSSR_UTP_SPACE) to operate * @mask: bit mask of bits to clear * @set: bit mask of bits to set * * NOTE: Convenience function which allows a PHY's BMCR register to be * modified as new register value = (old register value & ~mask) | set. * YT8521 has two space (utp/fiber) and three mode (utp/fiber/poll), each space * has MII_BMCR. poll mode combines utp and faber,so need do both. * If it is reset, it will wait for completion. * * returns 0 or negative errno code */ static int yt8521_modify_bmcr_paged(struct phy_device *phydev, int page, u16 mask, u16 set) { int max_cnt = 500; /* the max wait time of reset ~ 500 ms */ int old_page; int ret = 0; old_page = phy_select_page(phydev, page & YT8521_RSSR_SPACE_MASK); if (old_page < 0) goto err_restore_page; ret = __phy_modify(phydev, MII_BMCR, mask, set); if (ret < 0) goto err_restore_page; /* If it is reset, need to wait for the reset to complete */ if (set == BMCR_RESET) { while (max_cnt--) { usleep_range(1000, 1100); ret = __phy_read(phydev, MII_BMCR); if (ret < 0) goto err_restore_page; if (!(ret & BMCR_RESET)) return phy_restore_page(phydev, old_page, 0); } } err_restore_page: return phy_restore_page(phydev, old_page, ret); } /** * yt8521_modify_utp_fiber_bmcr - bits modify a PHY's BMCR register * @phydev: the phy_device struct * @mask: bit mask of bits to clear * @set: bit mask of bits to set * * NOTE: Convenience function which allows a PHY's BMCR register to be * modified as new register value = (old register value & ~mask) | set. * YT8521 has two space (utp/fiber) and three mode (utp/fiber/poll), each space * has MII_BMCR. poll mode combines utp and faber,so need do both. * * returns 0 or negative errno code */ static int yt8521_modify_utp_fiber_bmcr(struct phy_device *phydev, u16 mask, u16 set) { struct yt8521_priv *priv = phydev->priv; int ret; if (priv->reg_page != YT8521_RSSR_TO_BE_ARBITRATED) { ret = yt8521_modify_bmcr_paged(phydev, priv->reg_page, mask, set); if (ret < 0) return ret; } else { ret = yt8521_modify_bmcr_paged(phydev, YT8521_RSSR_UTP_SPACE, mask, set); if (ret < 0) return ret; ret = yt8521_modify_bmcr_paged(phydev, YT8521_RSSR_FIBER_SPACE, mask, set); if (ret < 0) return ret; } return 0; } /** * yt8521_soft_reset() - called to issue a PHY software reset * @phydev: a pointer to a &struct phy_device * * returns 0 or negative errno code */ static int yt8521_soft_reset(struct phy_device *phydev) { return yt8521_modify_utp_fiber_bmcr(phydev, 0, BMCR_RESET); } /** * yt8521_suspend() - suspend the hardware * @phydev: a pointer to a &struct phy_device * * returns 0 or negative errno code */ static int yt8521_suspend(struct phy_device *phydev) { int wol_config; /* YTPHY_WOL_CONFIG_REG is common ext reg */ wol_config = ytphy_read_ext_with_lock(phydev, YTPHY_WOL_CONFIG_REG); if (wol_config < 0) return wol_config; /* if wol enable, do nothing */ if (wol_config & YTPHY_WCR_ENABLE) return 0; return yt8521_modify_utp_fiber_bmcr(phydev, 0, BMCR_PDOWN); } /** * yt8521_resume() - resume the hardware * @phydev: a pointer to a &struct phy_device * * returns 0 or negative errno code */ static int yt8521_resume(struct phy_device *phydev) { int ret; int wol_config; /* disable auto sleep */ ret = ytphy_modify_ext_with_lock(phydev, YT8521_EXTREG_SLEEP_CONTROL1_REG, YT8521_ESC1R_SLEEP_SW, 0); if (ret < 0) return ret; wol_config = ytphy_read_ext_with_lock(phydev, YTPHY_WOL_CONFIG_REG); if (wol_config < 0) return wol_config; /* if wol enable, do nothing */ if (wol_config & YTPHY_WCR_ENABLE) return 0; return yt8521_modify_utp_fiber_bmcr(phydev, BMCR_PDOWN, 0); } /** * yt8521_config_init() - called to initialize the PHY * @phydev: a pointer to a &struct phy_device * * returns 0 or negative errno code */ static int yt8521_config_init(struct phy_device *phydev) { int old_page; int ret = 0; u16 val; old_page = phy_select_page(phydev, YT8521_RSSR_UTP_SPACE); if (old_page < 0) goto err_restore_page; switch (phydev->interface) { case PHY_INTERFACE_MODE_RGMII: val = YT8521_RC1R_GE_TX_DELAY_DIS | YT8521_RC1R_FE_TX_DELAY_DIS; val |= YT8521_RC1R_RX_DELAY_DIS; break; case PHY_INTERFACE_MODE_RGMII_RXID: val = YT8521_RC1R_GE_TX_DELAY_DIS | YT8521_RC1R_FE_TX_DELAY_DIS; val |= YT8521_RC1R_RX_DELAY_EN; break; case PHY_INTERFACE_MODE_RGMII_TXID: val = YT8521_RC1R_GE_TX_DELAY_EN | YT8521_RC1R_FE_TX_DELAY_EN; val |= YT8521_RC1R_RX_DELAY_DIS; break; case PHY_INTERFACE_MODE_RGMII_ID: val = YT8521_RC1R_GE_TX_DELAY_EN | YT8521_RC1R_FE_TX_DELAY_EN; val |= YT8521_RC1R_RX_DELAY_EN; break; case PHY_INTERFACE_MODE_SGMII: break; default: /* do not support other modes */ ret = -EOPNOTSUPP; goto err_restore_page; } /* set rgmii delay mode */ if (phydev->interface != PHY_INTERFACE_MODE_SGMII) { ret = ytphy_modify_ext(phydev, YT8521_RGMII_CONFIG1_REG, (YT8521_RC1R_RX_DELAY_MASK | YT8521_RC1R_FE_TX_DELAY_MASK | YT8521_RC1R_GE_TX_DELAY_MASK), val); if (ret < 0) goto err_restore_page; } /* disable auto sleep */ ret = ytphy_modify_ext(phydev, YT8521_EXTREG_SLEEP_CONTROL1_REG, YT8521_ESC1R_SLEEP_SW, 0); if (ret < 0) goto err_restore_page; /* enable RXC clock when no wire plug */ ret = ytphy_modify_ext(phydev, YT8521_CLOCK_GATING_REG, YT8521_CGR_RX_CLK_EN, 0); if (ret < 0) goto err_restore_page; err_restore_page: return phy_restore_page(phydev, old_page, ret); } /** * yt8521_prepare_fiber_features() - A small helper function that setup * fiber's features. * @phydev: a pointer to a &struct phy_device * @dst: a pointer to store fiber's features */ static void yt8521_prepare_fiber_features(struct phy_device *phydev, unsigned long *dst) { linkmode_set_bit(ETHTOOL_LINK_MODE_100baseFX_Full_BIT, dst); linkmode_set_bit(ETHTOOL_LINK_MODE_1000baseX_Full_BIT, dst); linkmode_set_bit(ETHTOOL_LINK_MODE_Autoneg_BIT, dst); linkmode_set_bit(ETHTOOL_LINK_MODE_FIBRE_BIT, dst); } /** * yt8521_fiber_setup_forced - configures/forces speed from @phydev * @phydev: target phy_device struct * * NOTE:The caller must have taken the MDIO bus lock. * * returns 0 or negative errno code */ static int yt8521_fiber_setup_forced(struct phy_device *phydev) { u16 val; int ret; if (phydev->speed == SPEED_1000) val = YTPHY_MCR_FIBER_1000BX; else if (phydev->speed == SPEED_100) val = YTPHY_MCR_FIBER_100FX; else return -EINVAL; ret = __phy_modify(phydev, MII_BMCR, BMCR_ANENABLE, 0); if (ret < 0) return ret; /* disable Fiber auto sensing */ ret = ytphy_modify_ext(phydev, YT8521_LINK_TIMER_CFG2_REG, YT8521_LTCR_EN_AUTOSEN, 0); if (ret < 0) return ret; ret = ytphy_modify_ext(phydev, YTPHY_MISC_CONFIG_REG, YTPHY_MCR_FIBER_SPEED_MASK, val); if (ret < 0) return ret; return ytphy_modify_ext(phydev, YT8521_CHIP_CONFIG_REG, YT8521_CCR_SW_RST, 0); } /** * ytphy_check_and_restart_aneg - Enable and restart auto-negotiation * @phydev: target phy_device struct * @restart: whether aneg restart is requested * * NOTE:The caller must have taken the MDIO bus lock. * * returns 0 or negative errno code */ static int ytphy_check_and_restart_aneg(struct phy_device *phydev, bool restart) { int ret; if (!restart) { /* Advertisement hasn't changed, but maybe aneg was never on to * begin with? Or maybe phy was isolated? */ ret = __phy_read(phydev, MII_BMCR); if (ret < 0) return ret; if (!(ret & BMCR_ANENABLE) || (ret & BMCR_ISOLATE)) restart = true; } /* Enable and Restart Autonegotiation * Don't isolate the PHY if we're negotiating */ if (restart) return __phy_modify(phydev, MII_BMCR, BMCR_ISOLATE, BMCR_ANENABLE | BMCR_ANRESTART); return 0; } /** * yt8521_fiber_config_aneg - restart auto-negotiation or write * YTPHY_MISC_CONFIG_REG. * @phydev: target phy_device struct * * NOTE:The caller must have taken the MDIO bus lock. * * returns 0 or negative errno code */ static int yt8521_fiber_config_aneg(struct phy_device *phydev) { int err, changed = 0; int bmcr; u16 adv; if (phydev->autoneg != AUTONEG_ENABLE) return yt8521_fiber_setup_forced(phydev); /* enable Fiber auto sensing */ err = ytphy_modify_ext(phydev, YT8521_LINK_TIMER_CFG2_REG, 0, YT8521_LTCR_EN_AUTOSEN); if (err < 0) return err; err = ytphy_modify_ext(phydev, YT8521_CHIP_CONFIG_REG, YT8521_CCR_SW_RST, 0); if (err < 0) return err; bmcr = __phy_read(phydev, MII_BMCR); if (bmcr < 0) return bmcr; /* When it is coming from fiber forced mode, add bmcr power down * and power up to let aneg work fine. */ if (!(bmcr & BMCR_ANENABLE)) { __phy_modify(phydev, MII_BMCR, 0, BMCR_PDOWN); usleep_range(1000, 1100); __phy_modify(phydev, MII_BMCR, BMCR_PDOWN, 0); } adv = linkmode_adv_to_mii_adv_x(phydev->advertising, ETHTOOL_LINK_MODE_1000baseX_Full_BIT); /* Setup fiber advertisement */ err = __phy_modify_changed(phydev, MII_ADVERTISE, ADVERTISE_1000XHALF | ADVERTISE_1000XFULL | ADVERTISE_1000XPAUSE | ADVERTISE_1000XPSE_ASYM, adv); if (err < 0) return err; if (err > 0) changed = 1; return ytphy_check_and_restart_aneg(phydev, changed); } /** * ytphy_setup_master_slave * @phydev: target phy_device struct * * NOTE: The caller must have taken the MDIO bus lock. * * returns 0 or negative errno code */ static int ytphy_setup_master_slave(struct phy_device *phydev) { u16 ctl = 0; if (!phydev->is_gigabit_capable) return 0; switch (phydev->master_slave_set) { case MASTER_SLAVE_CFG_MASTER_PREFERRED: ctl |= CTL1000_PREFER_MASTER; break; case MASTER_SLAVE_CFG_SLAVE_PREFERRED: break; case MASTER_SLAVE_CFG_MASTER_FORCE: ctl |= CTL1000_AS_MASTER; fallthrough; case MASTER_SLAVE_CFG_SLAVE_FORCE: ctl |= CTL1000_ENABLE_MASTER; break; case MASTER_SLAVE_CFG_UNKNOWN: case MASTER_SLAVE_CFG_UNSUPPORTED: return 0; default: phydev_warn(phydev, "Unsupported Master/Slave mode\n"); return -EOPNOTSUPP; } return __phy_modify_changed(phydev, MII_CTRL1000, (CTL1000_ENABLE_MASTER | CTL1000_AS_MASTER | CTL1000_PREFER_MASTER), ctl); } /** * ytphy_utp_config_advert - sanitize and advertise auto-negotiation parameters * @phydev: target phy_device struct * * NOTE: Writes MII_ADVERTISE with the appropriate values, * after sanitizing the values to make sure we only advertise * what is supported. Returns < 0 on error, 0 if the PHY's advertisement * hasn't changed, and > 0 if it has changed. * The caller must have taken the MDIO bus lock. * * returns 0 or negative errno code */ static int ytphy_utp_config_advert(struct phy_device *phydev) { int err, bmsr, changed = 0; u32 adv; /* Only allow advertising what this PHY supports */ linkmode_and(phydev->advertising, phydev->advertising, phydev->supported); adv = linkmode_adv_to_mii_adv_t(phydev->advertising); /* Setup standard advertisement */ err = __phy_modify_changed(phydev, MII_ADVERTISE, ADVERTISE_ALL | ADVERTISE_100BASE4 | ADVERTISE_PAUSE_CAP | ADVERTISE_PAUSE_ASYM, adv); if (err < 0) return err; if (err > 0) changed = 1; bmsr = __phy_read(phydev, MII_BMSR); if (bmsr < 0) return bmsr; /* Per 802.3-2008, Section 22.2.4.2.16 Extended status all * 1000Mbits/sec capable PHYs shall have the BMSR_ESTATEN bit set to a * logical 1. */ if (!(bmsr & BMSR_ESTATEN)) return changed; adv = linkmode_adv_to_mii_ctrl1000_t(phydev->advertising); err = __phy_modify_changed(phydev, MII_CTRL1000, ADVERTISE_1000FULL | ADVERTISE_1000HALF, adv); if (err < 0) return err; if (err > 0) changed = 1; return changed; } /** * ytphy_utp_config_aneg - restart auto-negotiation or write BMCR * @phydev: target phy_device struct * @changed: whether autoneg is requested * * NOTE: If auto-negotiation is enabled, we configure the * advertising, and then restart auto-negotiation. If it is not * enabled, then we write the BMCR. * The caller must have taken the MDIO bus lock. * * returns 0 or negative errno code */ static int ytphy_utp_config_aneg(struct phy_device *phydev, bool changed) { int err; u16 ctl; err = ytphy_setup_master_slave(phydev); if (err < 0) return err; else if (err) changed = true; if (phydev->autoneg != AUTONEG_ENABLE) { /* configures/forces speed/duplex from @phydev */ ctl = mii_bmcr_encode_fixed(phydev->speed, phydev->duplex); return __phy_modify(phydev, MII_BMCR, ~(BMCR_LOOPBACK | BMCR_ISOLATE | BMCR_PDOWN), ctl); } err = ytphy_utp_config_advert(phydev); if (err < 0) /* error */ return err; else if (err) changed = true; return ytphy_check_and_restart_aneg(phydev, changed); } /** * yt8521_config_aneg_paged() - switch reg space then call genphy_config_aneg * of one page * @phydev: a pointer to a &struct phy_device * @page: The reg page(YT8521_RSSR_FIBER_SPACE/YT8521_RSSR_UTP_SPACE) to * operate. * * returns 0 or negative errno code */ static int yt8521_config_aneg_paged(struct phy_device *phydev, int page) { __ETHTOOL_DECLARE_LINK_MODE_MASK(fiber_supported); struct yt8521_priv *priv = phydev->priv; int old_page; int ret = 0; page &= YT8521_RSSR_SPACE_MASK; old_page = phy_select_page(phydev, page); if (old_page < 0) goto err_restore_page; /* If reg_page is YT8521_RSSR_TO_BE_ARBITRATED, * phydev->advertising should be updated. */ if (priv->reg_page == YT8521_RSSR_TO_BE_ARBITRATED) { linkmode_zero(fiber_supported); yt8521_prepare_fiber_features(phydev, fiber_supported); /* prepare fiber_supported, then setup advertising. */ if (page == YT8521_RSSR_FIBER_SPACE) { linkmode_set_bit(ETHTOOL_LINK_MODE_Pause_BIT, fiber_supported); linkmode_set_bit(ETHTOOL_LINK_MODE_Asym_Pause_BIT, fiber_supported); linkmode_and(phydev->advertising, priv->combo_advertising, fiber_supported); } else { /* ETHTOOL_LINK_MODE_Autoneg_BIT is also used in utp */ linkmode_clear_bit(ETHTOOL_LINK_MODE_Autoneg_BIT, fiber_supported); linkmode_andnot(phydev->advertising, priv->combo_advertising, fiber_supported); } } if (page == YT8521_RSSR_FIBER_SPACE) ret = yt8521_fiber_config_aneg(phydev); else ret = ytphy_utp_config_aneg(phydev, false); err_restore_page: return phy_restore_page(phydev, old_page, ret); } /** * yt8521_config_aneg() - change reg space then call yt8521_config_aneg_paged * @phydev: a pointer to a &struct phy_device * * returns 0 or negative errno code */ static int yt8521_config_aneg(struct phy_device *phydev) { struct yt8521_priv *priv = phydev->priv; int ret; if (priv->reg_page != YT8521_RSSR_TO_BE_ARBITRATED) { ret = yt8521_config_aneg_paged(phydev, priv->reg_page); if (ret < 0) return ret; } else { /* If reg_page is YT8521_RSSR_TO_BE_ARBITRATED, * phydev->advertising need to be saved at first run. * Because it contains the advertising which supported by both * mac and yt8521(utp and fiber). */ if (linkmode_empty(priv->combo_advertising)) { linkmode_copy(priv->combo_advertising, phydev->advertising); } ret = yt8521_config_aneg_paged(phydev, YT8521_RSSR_UTP_SPACE); if (ret < 0) return ret; ret = yt8521_config_aneg_paged(phydev, YT8521_RSSR_FIBER_SPACE); if (ret < 0) return ret; /* we don't known which will be link, so restore * phydev->advertising as default value. */ linkmode_copy(phydev->advertising, priv->combo_advertising); } return 0; } /** * yt8521_aneg_done_paged() - determines the auto negotiation result of one * page. * @phydev: a pointer to a &struct phy_device * @page: The reg page(YT8521_RSSR_FIBER_SPACE/YT8521_RSSR_UTP_SPACE) to * operate. * * returns 0(no link)or 1(fiber or utp link) or negative errno code */ static int yt8521_aneg_done_paged(struct phy_device *phydev, int page) { int old_page; int ret = 0; int link; old_page = phy_select_page(phydev, page & YT8521_RSSR_SPACE_MASK); if (old_page < 0) goto err_restore_page; ret = __phy_read(phydev, YTPHY_SPECIFIC_STATUS_REG); if (ret < 0) goto err_restore_page; link = !!(ret & YTPHY_SSR_LINK); ret = link; err_restore_page: return phy_restore_page(phydev, old_page, ret); } /** * yt8521_aneg_done() - determines the auto negotiation result * @phydev: a pointer to a &struct phy_device * * returns 0(no link)or 1(fiber or utp link) or negative errno code */ static int yt8521_aneg_done(struct phy_device *phydev) { struct yt8521_priv *priv = phydev->priv; int link_fiber = 0; int link_utp; int link; if (priv->reg_page != YT8521_RSSR_TO_BE_ARBITRATED) { link = yt8521_aneg_done_paged(phydev, priv->reg_page); } else { link_utp = yt8521_aneg_done_paged(phydev, YT8521_RSSR_UTP_SPACE); if (link_utp < 0) return link_utp; if (!link_utp) { link_fiber = yt8521_aneg_done_paged(phydev, YT8521_RSSR_FIBER_SPACE); if (link_fiber < 0) return link_fiber; } link = link_fiber || link_utp; phydev_info(phydev, "%s, link_fiber: %d, link_utp: %d\n", __func__, link_fiber, link_utp); } return link; } /** * ytphy_utp_read_abilities - read PHY abilities from Clause 22 registers * @phydev: target phy_device struct * * NOTE: Reads the PHY's abilities and populates * phydev->supported accordingly. * The caller must have taken the MDIO bus lock. * * returns 0 or negative errno code */ static int ytphy_utp_read_abilities(struct phy_device *phydev) { int val; linkmode_set_bit_array(phy_basic_ports_array, ARRAY_SIZE(phy_basic_ports_array), phydev->supported); val = __phy_read(phydev, MII_BMSR); if (val < 0) return val; linkmode_mod_bit(ETHTOOL_LINK_MODE_Autoneg_BIT, phydev->supported, val & BMSR_ANEGCAPABLE); linkmode_mod_bit(ETHTOOL_LINK_MODE_100baseT_Full_BIT, phydev->supported, val & BMSR_100FULL); linkmode_mod_bit(ETHTOOL_LINK_MODE_100baseT_Half_BIT, phydev->supported, val & BMSR_100HALF); linkmode_mod_bit(ETHTOOL_LINK_MODE_10baseT_Full_BIT, phydev->supported, val & BMSR_10FULL); linkmode_mod_bit(ETHTOOL_LINK_MODE_10baseT_Half_BIT, phydev->supported, val & BMSR_10HALF); if (val & BMSR_ESTATEN) { val = __phy_read(phydev, MII_ESTATUS); if (val < 0) return val; linkmode_mod_bit(ETHTOOL_LINK_MODE_1000baseT_Full_BIT, phydev->supported, val & ESTATUS_1000_TFULL); linkmode_mod_bit(ETHTOOL_LINK_MODE_1000baseT_Half_BIT, phydev->supported, val & ESTATUS_1000_THALF); linkmode_mod_bit(ETHTOOL_LINK_MODE_1000baseX_Full_BIT, phydev->supported, val & ESTATUS_1000_XFULL); } return 0; } /** * yt8521_get_features_paged() - read supported link modes for one page * @phydev: a pointer to a &struct phy_device * @page: The reg page(YT8521_RSSR_FIBER_SPACE/YT8521_RSSR_UTP_SPACE) to * operate. * * returns 0 or negative errno code */ static int yt8521_get_features_paged(struct phy_device *phydev, int page) { int old_page; int ret = 0; page &= YT8521_RSSR_SPACE_MASK; old_page = phy_select_page(phydev, page); if (old_page < 0) goto err_restore_page; if (page == YT8521_RSSR_FIBER_SPACE) { linkmode_zero(phydev->supported); yt8521_prepare_fiber_features(phydev, phydev->supported); } else { ret = ytphy_utp_read_abilities(phydev); if (ret < 0) goto err_restore_page; } err_restore_page: return phy_restore_page(phydev, old_page, ret); } /** * yt8521_get_features - switch reg space then call yt8521_get_features_paged * @phydev: target phy_device struct * * returns 0 or negative errno code */ static int yt8521_get_features(struct phy_device *phydev) { struct yt8521_priv *priv = phydev->priv; int ret; if (priv->reg_page != YT8521_RSSR_TO_BE_ARBITRATED) { ret = yt8521_get_features_paged(phydev, priv->reg_page); } else { ret = yt8521_get_features_paged(phydev, YT8521_RSSR_UTP_SPACE); if (ret < 0) return ret; /* add fiber's features to phydev->supported */ yt8521_prepare_fiber_features(phydev, phydev->supported); } return ret; } static struct phy_driver motorcomm_phy_drvs[] = { { PHY_ID_MATCH_EXACT(PHY_ID_YT8511), .name = "YT8511 Gigabit Ethernet", .config_init = yt8511_config_init, .suspend = genphy_suspend, .resume = genphy_resume, .read_page = yt8511_read_page, .write_page = yt8511_write_page, }, { PHY_ID_MATCH_EXACT(PHY_ID_YT8521), .name = "YT8521 Gigabit Ethernet", .get_features = yt8521_get_features, .probe = yt8521_probe, .read_page = yt8521_read_page, .write_page = yt8521_write_page, .get_wol = ytphy_get_wol, .set_wol = ytphy_set_wol, .config_aneg = yt8521_config_aneg, .aneg_done = yt8521_aneg_done, .config_init = yt8521_config_init, .read_status = yt8521_read_status, .soft_reset = yt8521_soft_reset, .suspend = yt8521_suspend, .resume = yt8521_resume, }, { PHY_ID_MATCH_EXACT(PHY_ID_YT8531S), .name = "YT8531S Gigabit Ethernet", .get_features = yt8521_get_features, .probe = yt8531s_probe, .read_page = yt8521_read_page, .write_page = yt8521_write_page, .get_wol = ytphy_get_wol, .set_wol = ytphy_set_wol, .config_aneg = yt8521_config_aneg, .aneg_done = yt8521_aneg_done, .config_init = yt8521_config_init, .read_status = yt8521_read_status, .soft_reset = yt8521_soft_reset, .suspend = yt8521_suspend, .resume = yt8521_resume, }, }; module_phy_driver(motorcomm_phy_drvs); MODULE_DESCRIPTION("Motorcomm 8511/8521/8531S PHY driver"); MODULE_AUTHOR("Peter Geis"); MODULE_AUTHOR("Frank"); MODULE_LICENSE("GPL"); static const struct mdio_device_id __maybe_unused motorcomm_tbl[] = { { PHY_ID_MATCH_EXACT(PHY_ID_YT8511) }, { PHY_ID_MATCH_EXACT(PHY_ID_YT8521) }, { PHY_ID_MATCH_EXACT(PHY_ID_YT8531S) }, { /* sentinal */ } }; MODULE_DEVICE_TABLE(mdio, motorcomm_tbl);