M7350/kernel/drivers/net/ethernet/qualcomm/emac/emac_phy.c
2024-09-09 08:57:42 +00:00

592 lines
14 KiB
C

/* 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 <linux/module.h>
#include <linux/of.h>
#include <linux/of_net.h>
#include <linux/pm_runtime.h>
#include <linux/phy.h>
#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);
}