/* Copyright (c) 2013-2016, The Linux Foundation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and * only version 2 as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. */ /* MSM EMAC PHY Controller driver. */ #include #include #include #include #include #include "emac.h" #include "emac_hw.h" #include "emac_defines.h" #include "emac_regs.h" #include "emac_phy.h" #include "emac_rgmii.h" #include "emac_sgmii_v1.h" static int emac_disable_mdio_autopoll(struct emac_hw *hw) { u32 i, val; emac_reg_update32(hw, EMAC, EMAC_MDIO_CTRL, MDIO_AP_EN, 0); wmb(); /* ensure mdio autopoll disable is requested */ /* wait for any mdio polling to complete */ for (i = 0; i < MDIO_WAIT_TIMES; i++) { val = emac_reg_r32(hw, EMAC, EMAC_MDIO_CTRL); if (!(val & MDIO_BUSY)) return 0; udelay(10); /* atomic context */ } /* failed to disable; ensure it is enabled before returning */ emac_reg_update32(hw, EMAC, EMAC_MDIO_CTRL, 0, MDIO_AP_EN); wmb(); /* ensure mdio autopoll is enabled */ return -EBUSY; } static void emac_enable_mdio_autopoll(struct emac_hw *hw) { emac_reg_update32(hw, EMAC, EMAC_MDIO_CTRL, 0, MDIO_AP_EN); wmb(); /* ensure mdio autopoll is enabled */ } int emac_hw_read_phy_reg(struct emac_adapter *adpt, bool ext, u8 dev, bool fast, u16 reg_addr, u16 *phy_data) { struct emac_phy *phy = &adpt->phy; struct emac_hw *hw = &adpt->hw; u32 i, clk_sel, val = 0; int retval = 0; *phy_data = 0; clk_sel = fast ? MDIO_CLK_25_4 : MDIO_CLK_25_28; if (phy->external) { retval = emac_disable_mdio_autopoll(hw); if (retval) return retval; } emac_reg_update32(hw, EMAC, EMAC_PHY_STS, PHY_ADDR_BMSK, (dev << PHY_ADDR_SHFT)); wmb(); /* ensure PHY address is set before we proceed */ if (ext) { val = ((dev << DEVAD_SHFT) & DEVAD_BMSK) | ((reg_addr << EX_REG_ADDR_SHFT) & EX_REG_ADDR_BMSK); emac_reg_w32(hw, EMAC, EMAC_MDIO_EX_CTRL, val); wmb(); /* ensure proper address is set before proceeding */ val = SUP_PREAMBLE | ((clk_sel << MDIO_CLK_SEL_SHFT) & MDIO_CLK_SEL_BMSK) | MDIO_START | MDIO_MODE | MDIO_RD_NWR; } else { val = val & ~(MDIO_REG_ADDR_BMSK | MDIO_CLK_SEL_BMSK | MDIO_MODE | MDIO_PR); val = SUP_PREAMBLE | ((clk_sel << MDIO_CLK_SEL_SHFT) & MDIO_CLK_SEL_BMSK) | ((reg_addr << MDIO_REG_ADDR_SHFT) & MDIO_REG_ADDR_BMSK) | MDIO_START | MDIO_RD_NWR; } emac_reg_w32(hw, EMAC, EMAC_MDIO_CTRL, val); mb(); /* ensure hw starts the operation before we check for result */ for (i = 0; i < MDIO_WAIT_TIMES; i++) { val = emac_reg_r32(hw, EMAC, EMAC_MDIO_CTRL); if (!(val & (MDIO_START | MDIO_BUSY))) { *phy_data = (u16)((val >> MDIO_DATA_SHFT) & MDIO_DATA_BMSK); break; } udelay(10); /* atomic context */ } if (i == MDIO_WAIT_TIMES) retval = -EIO; if (phy->external) emac_enable_mdio_autopoll(hw); return retval; } int emac_hw_write_phy_reg(struct emac_adapter *adpt, bool ext, u8 dev, bool fast, u16 reg_addr, u16 phy_data) { struct emac_phy *phy = &adpt->phy; struct emac_hw *hw = &adpt->hw; u32 i, clk_sel, val = 0; int retval = 0; clk_sel = fast ? MDIO_CLK_25_4 : MDIO_CLK_25_28; if (phy->external) { retval = emac_disable_mdio_autopoll(hw); if (retval) return retval; } emac_reg_update32(hw, EMAC, EMAC_PHY_STS, PHY_ADDR_BMSK, (dev << PHY_ADDR_SHFT)); wmb(); /* ensure PHY address is set before we proceed */ if (ext) { val = ((dev << DEVAD_SHFT) & DEVAD_BMSK) | ((reg_addr << EX_REG_ADDR_SHFT) & EX_REG_ADDR_BMSK); emac_reg_w32(hw, EMAC, EMAC_MDIO_EX_CTRL, val); wmb(); /* ensure proper address is set before proceeding */ val = SUP_PREAMBLE | ((clk_sel << MDIO_CLK_SEL_SHFT) & MDIO_CLK_SEL_BMSK) | ((phy_data << MDIO_DATA_SHFT) & MDIO_DATA_BMSK) | MDIO_START | MDIO_MODE; } else { val = val & ~(MDIO_REG_ADDR_BMSK | MDIO_CLK_SEL_BMSK | MDIO_DATA_BMSK | MDIO_MODE | MDIO_PR); val = SUP_PREAMBLE | ((clk_sel << MDIO_CLK_SEL_SHFT) & MDIO_CLK_SEL_BMSK) | ((reg_addr << MDIO_REG_ADDR_SHFT) & MDIO_REG_ADDR_BMSK) | ((phy_data << MDIO_DATA_SHFT) & MDIO_DATA_BMSK) | MDIO_START; } emac_reg_w32(hw, EMAC, EMAC_MDIO_CTRL, val); mb(); /* ensure hw starts the operation before we check for result */ for (i = 0; i < MDIO_WAIT_TIMES; i++) { val = emac_reg_r32(hw, EMAC, EMAC_MDIO_CTRL); if (!(val & (MDIO_START | MDIO_BUSY))) break; udelay(10); /* atomic context */ } if (i == MDIO_WAIT_TIMES) retval = -EIO; if (phy->external) emac_enable_mdio_autopoll(hw); return retval; } int emac_phy_read(struct emac_adapter *adpt, u16 phy_addr, u16 reg_addr, u16 *phy_data) { struct emac_phy *phy = &adpt->phy; unsigned long flags; int retval; spin_lock_irqsave(&phy->lock, flags); retval = emac_hw_read_phy_reg(adpt, false, phy_addr, true, reg_addr, phy_data); spin_unlock_irqrestore(&phy->lock, flags); if (retval) emac_err(adpt, "error reading phy reg 0x%02x\n", reg_addr); else emac_dbg(adpt, hw, "EMAC PHY RD: 0x%02x -> 0x%04x\n", reg_addr, *phy_data); return retval; } int emac_phy_write(struct emac_adapter *adpt, u16 phy_addr, u16 reg_addr, u16 phy_data) { struct emac_phy *phy = &adpt->phy; unsigned long flags; int retval; spin_lock_irqsave(&phy->lock, flags); retval = emac_hw_write_phy_reg(adpt, false, phy_addr, true, reg_addr, phy_data); spin_unlock_irqrestore(&phy->lock, flags); if (retval) emac_err(adpt, "error writing phy reg 0x%02x\n", reg_addr); else emac_dbg(adpt, hw, "EMAC PHY WR: 0x%02x <- 0x%04x\n", reg_addr, phy_data); return retval; } /* reset external phy */ void emac_phy_reset_external(struct emac_adapter *adpt) { /* Trigger ephy reset by pulling line low */ adpt->gpio_off(adpt, false, true); /* need delay to complete ephy reset */ usleep_range(10000, 20000); /* Complete ephy reset by pulling line back up */ adpt->gpio_on(adpt, false, true); /* need delay to complete ephy reset */ usleep_range(10000, 20000); } /* initialize external phy */ int emac_phy_init_external(struct emac_adapter *adpt) { struct emac_phy *phy = &adpt->phy; struct emac_hw *hw = &adpt->hw; u16 phy_id[2]; int retval = 0; if (phy->external) { emac_phy_reset_external(adpt); retval = emac_phy_read(adpt, phy->addr, MII_PHYSID1, &phy_id[0]); if (retval) return retval; retval = emac_phy_read(adpt, phy->addr, MII_PHYSID2, &phy_id[1]); if (retval) return retval; phy->id[0] = phy_id[0]; phy->id[1] = phy_id[1]; } else { emac_disable_mdio_autopoll(hw); } return phy->ops.init_ephy(adpt); } static int emac_hw_setup_phy_link(struct emac_adapter *adpt, enum emac_flow_ctrl req_fc_mode, u32 speed, bool autoneg, bool fc) { struct emac_phy *phy = &adpt->phy; u16 adv, bmcr, ctrl1000 = 0; int retval = 0; if (autoneg) { switch (req_fc_mode) { case EMAC_FC_FULL: case EMAC_FC_RX_PAUSE: adv = ADVERTISE_PAUSE_CAP | ADVERTISE_PAUSE_ASYM; break; case EMAC_FC_TX_PAUSE: adv = ADVERTISE_PAUSE_ASYM; break; default: adv = 0; break; } if (!fc) adv &= ~(ADVERTISE_PAUSE_CAP | ADVERTISE_PAUSE_ASYM); if (speed & EMAC_LINK_SPEED_10_HALF) adv |= ADVERTISE_10HALF; if (speed & EMAC_LINK_SPEED_10_FULL) adv |= ADVERTISE_10HALF | ADVERTISE_10FULL; if (speed & EMAC_LINK_SPEED_100_HALF) adv |= ADVERTISE_100HALF; if (speed & EMAC_LINK_SPEED_100_FULL) adv |= ADVERTISE_100HALF | ADVERTISE_100FULL; if (speed & EMAC_LINK_SPEED_1GB_FULL) ctrl1000 |= ADVERTISE_1000FULL; retval |= emac_phy_write(adpt, phy->addr, MII_ADVERTISE, adv); retval |= emac_phy_write(adpt, phy->addr, MII_CTRL1000, ctrl1000); bmcr = BMCR_RESET | BMCR_ANENABLE | BMCR_ANRESTART; retval |= emac_phy_write(adpt, phy->addr, MII_BMCR, bmcr); } else { bmcr = BMCR_RESET; switch (speed) { case EMAC_LINK_SPEED_10_HALF: bmcr |= BMCR_SPEED10; break; case EMAC_LINK_SPEED_10_FULL: bmcr |= BMCR_SPEED10 | BMCR_FULLDPLX; break; case EMAC_LINK_SPEED_100_HALF: bmcr |= BMCR_SPEED100; break; case EMAC_LINK_SPEED_100_FULL: bmcr |= BMCR_SPEED100 | BMCR_FULLDPLX; break; case EMAC_LINK_SPEED_1GB_FULL: bmcr |= BMCR_SPEED1000 | BMCR_FULLDPLX; break; default: return -EINVAL; } retval |= emac_phy_write(adpt, phy->addr, MII_BMCR, bmcr); } return retval; } int emac_phy_setup_link(struct emac_adapter *adpt, u32 speed, bool autoneg, bool fc) { struct emac_phy *phy = &adpt->phy; int retval = 0; if (!phy->external) return phy->ops.link_setup_no_ephy(adpt, speed, autoneg); if (emac_hw_setup_phy_link(adpt, phy->req_fc_mode, speed, autoneg, fc)) { emac_err(adpt, "error on setup_phy(speed:%d autoneg:%d fc:%d)\n", speed, autoneg, fc); retval = -EINVAL; } else { phy->autoneg = autoneg; } return retval; } int emac_phy_setup_link_speed(struct emac_adapter *adpt, u32 speed, bool autoneg, bool fc) { struct emac_phy *phy = &adpt->phy; /* update speed based on input link speed */ phy->autoneg_advertised = speed & EMAC_LINK_SPEED_DEFAULT; return emac_phy_setup_link(adpt, phy->autoneg_advertised, autoneg, fc); } int emac_phy_check_link(struct emac_adapter *adpt, u32 *speed, bool *link_up) { struct emac_phy *phy = &adpt->phy; u16 bmsr, pssr; int retval; if (!phy->external) return phy->ops.link_check_no_ephy(adpt, speed, link_up); retval = emac_phy_read(adpt, phy->addr, MII_BMSR, &bmsr); if (retval) return retval; if (!(bmsr & BMSR_LSTATUS)) { *link_up = false; *speed = EMAC_LINK_SPEED_UNKNOWN; return 0; } *link_up = true; retval = emac_phy_read(adpt, phy->addr, MII_PSSR, &pssr); if (retval) return retval; if (!(pssr & PSSR_SPD_DPLX_RESOLVED)) { emac_err(adpt, "error for speed duplex resolved\n"); return -EINVAL; } switch (pssr & PSSR_SPEED) { case PSSR_1000MBS: if (pssr & PSSR_DPLX) *speed = EMAC_LINK_SPEED_1GB_FULL; else emac_err(adpt, "1000M half duplex is invalid"); break; case PSSR_100MBS: if (pssr & PSSR_DPLX) *speed = EMAC_LINK_SPEED_100_FULL; else *speed = EMAC_LINK_SPEED_100_HALF; break; case PSSR_10MBS: if (pssr & PSSR_DPLX) *speed = EMAC_LINK_SPEED_10_FULL; else *speed = EMAC_LINK_SPEED_10_HALF; break; default: *speed = EMAC_LINK_SPEED_UNKNOWN; retval = -EINVAL; break; } return retval; } int emac_phy_get_lpa_speed(struct emac_adapter *adpt, u32 *speed) { struct emac_phy *phy = &adpt->phy; int retval; u16 lpa, stat1000; bool link; if (!phy->external) return phy->ops.link_check_no_ephy(adpt, speed, &link); retval = emac_phy_read(adpt, phy->addr, MII_LPA, &lpa); retval |= emac_phy_read(adpt, phy->addr, MII_STAT1000, &stat1000); if (retval) return retval; *speed = EMAC_LINK_SPEED_10_HALF; if (lpa & LPA_10FULL) *speed = EMAC_LINK_SPEED_10_FULL; else if (lpa & LPA_10HALF) *speed = EMAC_LINK_SPEED_10_HALF; else if (lpa & LPA_100FULL) *speed = EMAC_LINK_SPEED_100_FULL; else if (lpa & LPA_100HALF) *speed = EMAC_LINK_SPEED_100_HALF; else if (stat1000 & LPA_1000FULL) *speed = EMAC_LINK_SPEED_1GB_FULL; return 0; } /* read phy configuration and initialize it */ int emac_phy_config(struct platform_device *pdev, struct emac_adapter *adpt) { struct emac_phy *phy = &adpt->phy; struct device_node *dt = pdev->dev.of_node; int ret; phy->external = !of_property_read_bool(dt, "qcom,no-external-phy"); /* get phy address on MDIO bus */ if (phy->external) { ret = of_property_read_u32(dt, "phy-addr", &phy->addr); if (ret) return ret; } /* get phy mode */ ret = of_get_phy_mode(dt); if (ret < 0) return ret; phy->phy_mode = ret; /* sgmii v2 is always using ACPI */ phy->ops = (phy->phy_mode == PHY_INTERFACE_MODE_RGMII) ? emac_rgmii_ops : emac_sgmii_v1_ops; if (!phy->external) phy->uses_gpios = false; ret = phy->ops.config(pdev, adpt); if (ret) return ret; spin_lock_init(&phy->lock); phy->autoneg = true; phy->autoneg_advertised = EMAC_LINK_SPEED_DEFAULT; return phy->ops.init(adpt); } /* Flow Control (fc) */ static int emac_get_fc_mode(struct emac_adapter *adpt, enum emac_flow_ctrl *mode) { struct emac_phy *phy = &adpt->phy; u16 i, bmsr = 0, pssr = 0; int retval = 0; for (i = 0; i < EMAC_MAX_SETUP_LNK_CYCLE; i++) { retval = emac_phy_read(adpt, phy->addr, MII_BMSR, &bmsr); if (retval) return retval; if (bmsr & BMSR_LSTATUS) { retval = emac_phy_read(adpt, phy->addr, MII_PSSR, &pssr); if (retval) return retval; if (!(pssr & PSSR_SPD_DPLX_RESOLVED)) { emac_err(adpt, "error for speed duplex resolved\n"); return -EINVAL; } if ((pssr & PSSR_FC_TXEN) && (pssr & PSSR_FC_RXEN)) { *mode = (phy->req_fc_mode == EMAC_FC_FULL) ? EMAC_FC_FULL : EMAC_FC_RX_PAUSE; } else if (pssr & PSSR_FC_TXEN) { *mode = EMAC_FC_TX_PAUSE; } else if (pssr & PSSR_FC_RXEN) { *mode = EMAC_FC_RX_PAUSE; } else { *mode = EMAC_FC_NONE; } break; } msleep(100); /* link can take upto few seconds to come up */ } if (i == EMAC_MAX_SETUP_LNK_CYCLE) { emac_err(adpt, "error when get flow control mode\n"); retval = -EINVAL; } return retval; } int emac_phy_config_fc(struct emac_adapter *adpt) { struct emac_phy *phy = &adpt->phy; struct emac_hw *hw = &adpt->hw; u32 mac; int retval; if (phy->disable_fc_autoneg || !phy->external) { phy->cur_fc_mode = phy->req_fc_mode; } else { retval = emac_get_fc_mode(adpt, &phy->cur_fc_mode); if (retval) return retval; } mac = emac_reg_r32(hw, EMAC, EMAC_MAC_CTRL); switch (phy->cur_fc_mode) { case EMAC_FC_NONE: mac &= ~(RXFC | TXFC); break; case EMAC_FC_RX_PAUSE: mac &= ~TXFC; mac |= RXFC; break; case EMAC_FC_TX_PAUSE: mac |= TXFC; mac &= ~RXFC; break; case EMAC_FC_FULL: case EMAC_FC_DEFAULT: mac |= (TXFC | RXFC); break; default: emac_err(adpt, "flow control param set incorrectly\n"); return -EINVAL; } emac_reg_w32(hw, EMAC, EMAC_MAC_CTRL, mac); /* ensure flow control config is slushed to hw */ wmb(); return 0; } void emac_reg_write_all(void __iomem *base, const struct emac_reg_write *itr) { for (; itr->offset != END_MARKER; ++itr) writel_relaxed(itr->val, base + itr->offset); }