|
|
|
|
@@ -23,15 +23,28 @@
|
|
|
|
|
#include <linux/ip.h>
|
|
|
|
|
#include <linux/ipv6.h>
|
|
|
|
|
#include <linux/rtnetlink.h>
|
|
|
|
|
|
|
|
|
|
#include <linux/route.h>
|
|
|
|
|
#include <linux/if_arp.h>
|
|
|
|
|
#include <linux/inet.h>
|
|
|
|
|
#include <net/inet_common.h>
|
|
|
|
|
#include "stmmac.h"
|
|
|
|
|
#include "stmmac_platform.h"
|
|
|
|
|
#include "dwmac-qcom-ethqos.h"
|
|
|
|
|
#include "stmmac_ptp.h"
|
|
|
|
|
#include "dwmac-qcom-ipa-offload.h"
|
|
|
|
|
|
|
|
|
|
#define PHY_LOOPBACK_1000 0x4140
|
|
|
|
|
#define PHY_LOOPBACK_100 0x6100
|
|
|
|
|
#define PHY_LOOPBACK_10 0x4100
|
|
|
|
|
|
|
|
|
|
static void __iomem *tlmm_central_base_addr;
|
|
|
|
|
static void ethqos_rgmii_io_macro_loopback(struct qcom_ethqos *ethqos,
|
|
|
|
|
int mode);
|
|
|
|
|
static int phy_digital_loopback_config(
|
|
|
|
|
struct qcom_ethqos *ethqos, int speed, int config);
|
|
|
|
|
|
|
|
|
|
bool phy_intr_en;
|
|
|
|
|
static char buf[2000];
|
|
|
|
|
|
|
|
|
|
static struct ethqos_emac_por emac_por[] = {
|
|
|
|
|
{ .offset = RGMII_IO_MACRO_CONFIG, .value = 0x0 },
|
|
|
|
|
@@ -77,7 +90,7 @@ static void qcom_ethqos_read_iomacro_por_values(struct qcom_ethqos *ethqos)
|
|
|
|
|
ethqos->por[i].offset);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static inline unsigned int dwmac_qcom_get_eth_type(unsigned char *buf)
|
|
|
|
|
unsigned int dwmac_qcom_get_eth_type(unsigned char *buf)
|
|
|
|
|
{
|
|
|
|
|
return
|
|
|
|
|
((((u16)buf[QTAG_ETH_TYPE_OFFSET] << 8) |
|
|
|
|
|
@@ -926,6 +939,12 @@ static int ethqos_mdio_read(struct stmmac_priv *priv, int phyaddr, int phyreg)
|
|
|
|
|
u32 v;
|
|
|
|
|
int data;
|
|
|
|
|
u32 value = MII_BUSY;
|
|
|
|
|
struct qcom_ethqos *ethqos = priv->plat->bsp_priv;
|
|
|
|
|
|
|
|
|
|
if (ethqos->phy_state == PHY_IS_OFF) {
|
|
|
|
|
ETHQOSINFO("Phy is in off state reading is not possible\n");
|
|
|
|
|
return -EOPNOTSUPP;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
value |= (phyaddr << priv->hw->mii.addr_shift)
|
|
|
|
|
& priv->hw->mii.addr_mask;
|
|
|
|
|
@@ -951,6 +970,44 @@ static int ethqos_mdio_read(struct stmmac_priv *priv, int phyaddr, int phyreg)
|
|
|
|
|
return data;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static int ethqos_mdio_write(struct stmmac_priv *priv, int phyaddr, int phyreg,
|
|
|
|
|
u16 phydata)
|
|
|
|
|
{
|
|
|
|
|
unsigned int mii_address = priv->hw->mii.addr;
|
|
|
|
|
unsigned int mii_data = priv->hw->mii.data;
|
|
|
|
|
u32 v;
|
|
|
|
|
u32 value = MII_BUSY;
|
|
|
|
|
struct qcom_ethqos *ethqos = priv->plat->bsp_priv;
|
|
|
|
|
|
|
|
|
|
if (ethqos->phy_state == PHY_IS_OFF) {
|
|
|
|
|
ETHQOSINFO("Phy is in off state writing is not possible\n");
|
|
|
|
|
return -EOPNOTSUPP;
|
|
|
|
|
}
|
|
|
|
|
value |= (phyaddr << priv->hw->mii.addr_shift)
|
|
|
|
|
& priv->hw->mii.addr_mask;
|
|
|
|
|
value |= (phyreg << priv->hw->mii.reg_shift) & priv->hw->mii.reg_mask;
|
|
|
|
|
|
|
|
|
|
value |= (priv->clk_csr << priv->hw->mii.clk_csr_shift)
|
|
|
|
|
& priv->hw->mii.clk_csr_mask;
|
|
|
|
|
if (priv->plat->has_gmac4)
|
|
|
|
|
value |= MII_GMAC4_WRITE;
|
|
|
|
|
else
|
|
|
|
|
value |= MII_WRITE;
|
|
|
|
|
|
|
|
|
|
/* Wait until any existing MII operation is complete */
|
|
|
|
|
if (readl_poll_timeout(priv->ioaddr + mii_address, v, !(v & MII_BUSY),
|
|
|
|
|
100, 10000))
|
|
|
|
|
return -EBUSY;
|
|
|
|
|
|
|
|
|
|
/* Set the MII address register to write */
|
|
|
|
|
writel_relaxed(phydata, priv->ioaddr + mii_data);
|
|
|
|
|
writel_relaxed(value, priv->ioaddr + mii_address);
|
|
|
|
|
|
|
|
|
|
/* Wait until any existing MII operation is complete */
|
|
|
|
|
return readl_poll_timeout(priv->ioaddr + mii_address, v,
|
|
|
|
|
!(v & MII_BUSY), 100, 10000);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static int ethqos_phy_intr_config(struct qcom_ethqos *ethqos)
|
|
|
|
|
{
|
|
|
|
|
int ret = 0;
|
|
|
|
|
@@ -1107,6 +1164,10 @@ static ssize_t read_phy_reg_dump(struct file *file, char __user *user_buf,
|
|
|
|
|
ETHQOSERR("NULL Pointer\n");
|
|
|
|
|
return -EINVAL;
|
|
|
|
|
}
|
|
|
|
|
if (ethqos->phy_state == PHY_IS_OFF) {
|
|
|
|
|
ETHQOSINFO("Phy is in off state phy dump is not possible\n");
|
|
|
|
|
return -EOPNOTSUPP;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
buf = kzalloc(buf_len, GFP_KERNEL);
|
|
|
|
|
if (!buf)
|
|
|
|
|
@@ -1203,6 +1264,458 @@ static ssize_t read_rgmii_reg_dump(struct file *file,
|
|
|
|
|
return ret_cnt;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static ssize_t read_phy_off(struct file *file,
|
|
|
|
|
char __user *user_buf,
|
|
|
|
|
size_t count, loff_t *ppos)
|
|
|
|
|
{
|
|
|
|
|
unsigned int len = 0, buf_len = 2000;
|
|
|
|
|
struct qcom_ethqos *ethqos = file->private_data;
|
|
|
|
|
|
|
|
|
|
if (ethqos->current_phy_mode == DISABLE_PHY_IMMEDIATELY)
|
|
|
|
|
len += scnprintf(buf + len, buf_len - len,
|
|
|
|
|
"Disable phy immediately enabled\n");
|
|
|
|
|
else if (ethqos->current_phy_mode == ENABLE_PHY_IMMEDIATELY)
|
|
|
|
|
len += scnprintf(buf + len, buf_len - len,
|
|
|
|
|
"Enable phy immediately enabled\n");
|
|
|
|
|
else if (ethqos->current_phy_mode == DISABLE_PHY_AT_SUSPEND_ONLY) {
|
|
|
|
|
len += scnprintf(buf + len, buf_len - len,
|
|
|
|
|
"Disable Phy at suspend\n");
|
|
|
|
|
len += scnprintf(buf + len, buf_len - len,
|
|
|
|
|
" & do not enable at resume enabled\n");
|
|
|
|
|
} else if (ethqos->current_phy_mode ==
|
|
|
|
|
DISABLE_PHY_SUSPEND_ENABLE_RESUME) {
|
|
|
|
|
len += scnprintf(buf + len, buf_len - len,
|
|
|
|
|
"Disable Phy at suspend\n");
|
|
|
|
|
len += scnprintf(buf + len, buf_len - len,
|
|
|
|
|
" & enable at resume enabled\n");
|
|
|
|
|
} else if (ethqos->current_phy_mode == DISABLE_PHY_ON_OFF)
|
|
|
|
|
len += scnprintf(buf + len, buf_len - len,
|
|
|
|
|
"Disable phy on/off disabled\n");
|
|
|
|
|
else
|
|
|
|
|
len += scnprintf(buf + len, buf_len - len,
|
|
|
|
|
"Invalid Phy State\n");
|
|
|
|
|
|
|
|
|
|
if (len > buf_len)
|
|
|
|
|
len = buf_len;
|
|
|
|
|
|
|
|
|
|
return simple_read_from_buffer(user_buf, count, ppos, buf, len);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static ssize_t phy_off_config(
|
|
|
|
|
struct file *file, const char __user *user_buffer,
|
|
|
|
|
size_t count, loff_t *position)
|
|
|
|
|
{
|
|
|
|
|
char *in_buf;
|
|
|
|
|
int buf_len = 2000;
|
|
|
|
|
unsigned long ret;
|
|
|
|
|
int config = 0;
|
|
|
|
|
struct qcom_ethqos *ethqos = file->private_data;
|
|
|
|
|
|
|
|
|
|
in_buf = kzalloc(buf_len, GFP_KERNEL);
|
|
|
|
|
if (!in_buf)
|
|
|
|
|
return -ENOMEM;
|
|
|
|
|
|
|
|
|
|
ret = copy_from_user(in_buf, user_buffer, buf_len);
|
|
|
|
|
if (ret) {
|
|
|
|
|
ETHQOSERR("unable to copy from user\n");
|
|
|
|
|
return -EFAULT;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ret = sscanf(in_buf, "%d", &config);
|
|
|
|
|
if (ret != 1) {
|
|
|
|
|
ETHQOSERR("Error in reading option from user");
|
|
|
|
|
return -EINVAL;
|
|
|
|
|
}
|
|
|
|
|
if (config > DISABLE_PHY_ON_OFF || config < DISABLE_PHY_IMMEDIATELY) {
|
|
|
|
|
ETHQOSERR("Invalid option =%d", config);
|
|
|
|
|
return -EINVAL;
|
|
|
|
|
}
|
|
|
|
|
if (config == ethqos->current_phy_mode) {
|
|
|
|
|
ETHQOSERR("No effect as duplicate config");
|
|
|
|
|
return -EPERM;
|
|
|
|
|
}
|
|
|
|
|
if (config == DISABLE_PHY_IMMEDIATELY) {
|
|
|
|
|
ethqos->current_phy_mode = DISABLE_PHY_IMMEDIATELY;
|
|
|
|
|
//make phy off
|
|
|
|
|
if (ethqos->current_loopback == ENABLE_PHY_LOOPBACK) {
|
|
|
|
|
/* If Phy loopback is enabled
|
|
|
|
|
* Disabled It before phy off
|
|
|
|
|
*/
|
|
|
|
|
phy_digital_loopback_config(ethqos,
|
|
|
|
|
ethqos->loopback_speed, 0);
|
|
|
|
|
ETHQOSDBG("Disable phy Loopback");
|
|
|
|
|
ethqos->current_loopback = ENABLE_PHY_LOOPBACK;
|
|
|
|
|
}
|
|
|
|
|
ethqos_phy_power_off(ethqos);
|
|
|
|
|
} else if (config == ENABLE_PHY_IMMEDIATELY) {
|
|
|
|
|
ethqos->current_phy_mode = ENABLE_PHY_IMMEDIATELY;
|
|
|
|
|
//make phy on
|
|
|
|
|
ethqos_phy_power_on(ethqos);
|
|
|
|
|
ethqos_reset_phy_enable_interrupt(ethqos);
|
|
|
|
|
if (ethqos->current_loopback == ENABLE_PHY_LOOPBACK) {
|
|
|
|
|
/*If Phy loopback is enabled , enabled It again*/
|
|
|
|
|
phy_digital_loopback_config(ethqos,
|
|
|
|
|
ethqos->loopback_speed, 1);
|
|
|
|
|
ETHQOSDBG("Enabling Phy loopback again");
|
|
|
|
|
}
|
|
|
|
|
} else if (config == DISABLE_PHY_AT_SUSPEND_ONLY) {
|
|
|
|
|
ethqos->current_phy_mode = DISABLE_PHY_AT_SUSPEND_ONLY;
|
|
|
|
|
} else if (config == DISABLE_PHY_SUSPEND_ENABLE_RESUME) {
|
|
|
|
|
ethqos->current_phy_mode = DISABLE_PHY_SUSPEND_ENABLE_RESUME;
|
|
|
|
|
} else if (config == DISABLE_PHY_ON_OFF) {
|
|
|
|
|
ethqos->current_phy_mode = DISABLE_PHY_ON_OFF;
|
|
|
|
|
} else {
|
|
|
|
|
ETHQOSERR("Invalid option\n");
|
|
|
|
|
return -EINVAL;
|
|
|
|
|
}
|
|
|
|
|
kfree(in_buf);
|
|
|
|
|
return count;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void ethqos_rgmii_io_macro_loopback(struct qcom_ethqos *ethqos, int mode)
|
|
|
|
|
{
|
|
|
|
|
/* Set loopback mode */
|
|
|
|
|
if (mode == 1) {
|
|
|
|
|
rgmii_updatel(ethqos, RGMII_CONFIG2_TX_TO_RX_LOOPBACK_EN,
|
|
|
|
|
RGMII_CONFIG2_TX_TO_RX_LOOPBACK_EN,
|
|
|
|
|
RGMII_IO_MACRO_CONFIG2);
|
|
|
|
|
rgmii_updatel(ethqos, RGMII_CONFIG2_RX_PROG_SWAP,
|
|
|
|
|
0, RGMII_IO_MACRO_CONFIG2);
|
|
|
|
|
} else {
|
|
|
|
|
rgmii_updatel(ethqos, RGMII_CONFIG2_TX_TO_RX_LOOPBACK_EN,
|
|
|
|
|
0, RGMII_IO_MACRO_CONFIG2);
|
|
|
|
|
rgmii_updatel(ethqos, RGMII_CONFIG2_RX_PROG_SWAP,
|
|
|
|
|
RGMII_CONFIG2_RX_PROG_SWAP,
|
|
|
|
|
RGMII_IO_MACRO_CONFIG2);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void ethqos_mac_loopback(struct qcom_ethqos *ethqos, int mode)
|
|
|
|
|
{
|
|
|
|
|
u32 read_value = (u32)readl_relaxed(ethqos->ioaddr + MAC_CONFIGURATION);
|
|
|
|
|
/* Set loopback mode */
|
|
|
|
|
if (mode == 1)
|
|
|
|
|
read_value |= MAC_LM;
|
|
|
|
|
else
|
|
|
|
|
read_value &= ~MAC_LM;
|
|
|
|
|
writel_relaxed(read_value, ethqos->ioaddr + MAC_CONFIGURATION);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static int phy_digital_loopback_config(
|
|
|
|
|
struct qcom_ethqos *ethqos, int speed, int config)
|
|
|
|
|
{
|
|
|
|
|
struct platform_device *pdev = ethqos->pdev;
|
|
|
|
|
struct net_device *dev = platform_get_drvdata(pdev);
|
|
|
|
|
struct stmmac_priv *priv = netdev_priv(dev);
|
|
|
|
|
int phydata = 0;
|
|
|
|
|
|
|
|
|
|
if (config == 1) {
|
|
|
|
|
ETHQOSINFO("Request for phy digital loopback enable\n");
|
|
|
|
|
switch (speed) {
|
|
|
|
|
case SPEED_1000:
|
|
|
|
|
phydata = PHY_LOOPBACK_1000;
|
|
|
|
|
break;
|
|
|
|
|
case SPEED_100:
|
|
|
|
|
phydata = PHY_LOOPBACK_100;
|
|
|
|
|
break;
|
|
|
|
|
case SPEED_10:
|
|
|
|
|
phydata = PHY_LOOPBACK_10;
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
ETHQOSERR("Invalid link speed\n");
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
} else if (config == 0) {
|
|
|
|
|
ETHQOSINFO("Request for phy digital loopback disable\n");
|
|
|
|
|
if (ethqos->bmcr_backup)
|
|
|
|
|
phydata = ethqos->bmcr_backup;
|
|
|
|
|
else
|
|
|
|
|
phydata = 0x1140;
|
|
|
|
|
} else {
|
|
|
|
|
ETHQOSERR("Invalid option\n");
|
|
|
|
|
return -EINVAL;
|
|
|
|
|
}
|
|
|
|
|
if (phydata != 0) {
|
|
|
|
|
ethqos_mdio_write(
|
|
|
|
|
priv, priv->plat->phy_addr, MII_BMCR, phydata);
|
|
|
|
|
ETHQOSINFO("write done for phy loopback\n");
|
|
|
|
|
}
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void print_loopback_detail(enum loopback_mode loopback)
|
|
|
|
|
{
|
|
|
|
|
switch (loopback) {
|
|
|
|
|
case DISABLE_LOOPBACK:
|
|
|
|
|
ETHQOSINFO("Loopback is disabled\n");
|
|
|
|
|
break;
|
|
|
|
|
case ENABLE_IO_MACRO_LOOPBACK:
|
|
|
|
|
ETHQOSINFO("Loopback is Enabled as IO MACRO LOOPBACK\n");
|
|
|
|
|
break;
|
|
|
|
|
case ENABLE_MAC_LOOPBACK:
|
|
|
|
|
ETHQOSINFO("Loopback is Enabled as MAC LOOPBACK\n");
|
|
|
|
|
break;
|
|
|
|
|
case ENABLE_PHY_LOOPBACK:
|
|
|
|
|
ETHQOSINFO("Loopback is Enabled as PHY LOOPBACK\n");
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
ETHQOSINFO("Invalid Loopback=%d\n", loopback);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void setup_config_registers(struct qcom_ethqos *ethqos,
|
|
|
|
|
int speed, int duplex, int mode)
|
|
|
|
|
{
|
|
|
|
|
struct platform_device *pdev = ethqos->pdev;
|
|
|
|
|
struct net_device *dev = platform_get_drvdata(pdev);
|
|
|
|
|
struct stmmac_priv *priv = netdev_priv(dev);
|
|
|
|
|
u32 ctrl = 0;
|
|
|
|
|
|
|
|
|
|
ETHQOSDBG("Speed=%d,dupex=%d,mode=%d\n", speed, duplex, mode);
|
|
|
|
|
|
|
|
|
|
if (mode > DISABLE_LOOPBACK && !qcom_ethqos_is_phy_link_up(ethqos)) {
|
|
|
|
|
/*If Link is Down & need to enable Loopback*/
|
|
|
|
|
ETHQOSDBG("Enable Lower Up Flag & disable phy dev\n");
|
|
|
|
|
ETHQOSDBG("IRQ so that Rx/Tx can happen beforeee Link down\n");
|
|
|
|
|
netif_carrier_on(dev);
|
|
|
|
|
/*Disable phy interrupt by Link/Down by cable plug in/out*/
|
|
|
|
|
disable_irq(ethqos->phy_intr);
|
|
|
|
|
} else if (mode > DISABLE_LOOPBACK &&
|
|
|
|
|
qcom_ethqos_is_phy_link_up(ethqos)) {
|
|
|
|
|
ETHQOSDBG("Only disable phy irqqq Lin is UP\n");
|
|
|
|
|
/*Since link is up no need to set Lower UP flag*/
|
|
|
|
|
/*Disable phy interrupt by Link/Down by cable plug in/out*/
|
|
|
|
|
disable_irq(ethqos->phy_intr);
|
|
|
|
|
} else if (mode == DISABLE_LOOPBACK &&
|
|
|
|
|
!qcom_ethqos_is_phy_link_up(ethqos)) {
|
|
|
|
|
ETHQOSDBG("Disable Lower Up as Link is down\n");
|
|
|
|
|
netif_carrier_off(dev);
|
|
|
|
|
enable_irq(ethqos->phy_intr);
|
|
|
|
|
}
|
|
|
|
|
ETHQOSDBG("Old ctrl=%d dupex full\n", ctrl);
|
|
|
|
|
ctrl = readl_relaxed(priv->ioaddr + MAC_CTRL_REG);
|
|
|
|
|
ETHQOSDBG("Old ctrl=0x%x with mask with flow control\n", ctrl);
|
|
|
|
|
|
|
|
|
|
ctrl |= priv->hw->link.duplex;
|
|
|
|
|
priv->dev->phydev->duplex = duplex;
|
|
|
|
|
ctrl &= ~priv->hw->link.speed_mask;
|
|
|
|
|
switch (speed) {
|
|
|
|
|
case SPEED_1000:
|
|
|
|
|
ctrl |= priv->hw->link.speed1000;
|
|
|
|
|
break;
|
|
|
|
|
case SPEED_100:
|
|
|
|
|
ctrl |= priv->hw->link.speed100;
|
|
|
|
|
break;
|
|
|
|
|
case SPEED_10:
|
|
|
|
|
ctrl |= priv->hw->link.speed10;
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
speed = SPEED_UNKNOWN;
|
|
|
|
|
ETHQOSDBG("unkwon speed\n");
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
writel_relaxed(ctrl, priv->ioaddr + MAC_CTRL_REG);
|
|
|
|
|
ETHQOSDBG("New ctrl=%x priv hw speeed =%d\n", ctrl,
|
|
|
|
|
priv->hw->link.speed1000);
|
|
|
|
|
priv->dev->phydev->speed = speed;
|
|
|
|
|
priv->speed = speed;
|
|
|
|
|
|
|
|
|
|
if (mode > DISABLE_LOOPBACK && !qcom_ethqos_is_phy_link_up(ethqos)) {
|
|
|
|
|
/*If Link is Down & need to enable Loopback*/
|
|
|
|
|
ETHQOSDBG("Link is down . manual ipa setting up\n");
|
|
|
|
|
if (priv->tx_queue[IPA_DMA_TX_CH].skip_sw)
|
|
|
|
|
ethqos_ipa_offload_event_handler(priv,
|
|
|
|
|
EV_PHY_LINK_UP);
|
|
|
|
|
} else if (mode == DISABLE_LOOPBACK &&
|
|
|
|
|
!qcom_ethqos_is_phy_link_up(ethqos)) {
|
|
|
|
|
ETHQOSDBG("Disable request since link was down disable ipa\n");
|
|
|
|
|
if (priv->tx_queue[IPA_DMA_TX_CH].skip_sw)
|
|
|
|
|
ethqos_ipa_offload_event_handler(priv,
|
|
|
|
|
EV_PHY_LINK_DOWN);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (priv->dev->phydev->speed != SPEED_UNKNOWN)
|
|
|
|
|
ethqos_fix_mac_speed(ethqos, speed);
|
|
|
|
|
|
|
|
|
|
if (mode > DISABLE_LOOPBACK) {
|
|
|
|
|
if (mode == ENABLE_MAC_LOOPBACK ||
|
|
|
|
|
mode == ENABLE_IO_MACRO_LOOPBACK)
|
|
|
|
|
rgmii_updatel(ethqos, RGMII_CONFIG_LOOPBACK_EN,
|
|
|
|
|
RGMII_CONFIG_LOOPBACK_EN,
|
|
|
|
|
RGMII_IO_MACRO_CONFIG);
|
|
|
|
|
} else if (mode == DISABLE_LOOPBACK) {
|
|
|
|
|
if (ethqos->emac_ver == EMAC_HW_v2_3_2 ||
|
|
|
|
|
ethqos->emac_ver == EMAC_HW_v2_1_2)
|
|
|
|
|
rgmii_updatel(ethqos, RGMII_CONFIG_LOOPBACK_EN,
|
|
|
|
|
0, RGMII_IO_MACRO_CONFIG);
|
|
|
|
|
}
|
|
|
|
|
ETHQOSERR("End\n");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static ssize_t loopback_handling_config(
|
|
|
|
|
struct file *file, const char __user *user_buffer,
|
|
|
|
|
size_t count, loff_t *position)
|
|
|
|
|
{
|
|
|
|
|
char *in_buf;
|
|
|
|
|
int buf_len = 2000;
|
|
|
|
|
unsigned long ret;
|
|
|
|
|
int config = 0;
|
|
|
|
|
struct qcom_ethqos *ethqos = file->private_data;
|
|
|
|
|
struct platform_device *pdev = ethqos->pdev;
|
|
|
|
|
struct net_device *dev = platform_get_drvdata(pdev);
|
|
|
|
|
struct stmmac_priv *priv = netdev_priv(dev);
|
|
|
|
|
int speed = 0;
|
|
|
|
|
|
|
|
|
|
in_buf = kzalloc(buf_len, GFP_KERNEL);
|
|
|
|
|
if (!in_buf)
|
|
|
|
|
return -ENOMEM;
|
|
|
|
|
|
|
|
|
|
ret = copy_from_user(in_buf, user_buffer, buf_len);
|
|
|
|
|
if (ret) {
|
|
|
|
|
ETHQOSERR("unable to copy from user\n");
|
|
|
|
|
return -EFAULT;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ret = sscanf(in_buf, "%d %d", &config, &speed);
|
|
|
|
|
if (config > DISABLE_LOOPBACK && ret != 2) {
|
|
|
|
|
ETHQOSERR("Speed is also needed while enabling loopback\n");
|
|
|
|
|
return -EINVAL;
|
|
|
|
|
}
|
|
|
|
|
if (config < DISABLE_LOOPBACK || config > ENABLE_PHY_LOOPBACK) {
|
|
|
|
|
ETHQOSERR("Invalid config =%d\n", config);
|
|
|
|
|
return -EINVAL;
|
|
|
|
|
}
|
|
|
|
|
if ((config == ENABLE_PHY_LOOPBACK || ethqos->current_loopback ==
|
|
|
|
|
ENABLE_PHY_LOOPBACK) &&
|
|
|
|
|
ethqos->current_phy_mode == DISABLE_PHY_IMMEDIATELY) {
|
|
|
|
|
ETHQOSERR("Can't enabled/disable ");
|
|
|
|
|
ETHQOSERR("phy loopback when phy is off\n");
|
|
|
|
|
return -EPERM;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*Argument validation*/
|
|
|
|
|
if (config == DISABLE_LOOPBACK || config == ENABLE_IO_MACRO_LOOPBACK ||
|
|
|
|
|
config == ENABLE_MAC_LOOPBACK || config == ENABLE_PHY_LOOPBACK) {
|
|
|
|
|
if (speed != SPEED_1000 && speed != SPEED_100 &&
|
|
|
|
|
speed != SPEED_10)
|
|
|
|
|
return -EINVAL;
|
|
|
|
|
} else {
|
|
|
|
|
return -EINVAL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (config == ethqos->current_loopback) {
|
|
|
|
|
switch (config) {
|
|
|
|
|
case DISABLE_LOOPBACK:
|
|
|
|
|
ETHQOSINFO("Loopback is already disabled\n");
|
|
|
|
|
break;
|
|
|
|
|
case ENABLE_IO_MACRO_LOOPBACK:
|
|
|
|
|
ETHQOSINFO("Loopback is already Enabled as ");
|
|
|
|
|
ETHQOSINFO("IO MACRO LOOPBACK\n");
|
|
|
|
|
break;
|
|
|
|
|
case ENABLE_MAC_LOOPBACK:
|
|
|
|
|
ETHQOSINFO("Loopback is already Enabled as ");
|
|
|
|
|
ETHQOSINFO("MAC LOOPBACK\n");
|
|
|
|
|
break;
|
|
|
|
|
case ENABLE_PHY_LOOPBACK:
|
|
|
|
|
ETHQOSINFO("Loopback is already Enabled as ");
|
|
|
|
|
ETHQOSINFO("PHY LOOPBACK\n");
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
return -EINVAL;
|
|
|
|
|
}
|
|
|
|
|
/*If request to enable loopback & some other loopback already enabled*/
|
|
|
|
|
if (config != DISABLE_LOOPBACK &&
|
|
|
|
|
ethqos->current_loopback > DISABLE_LOOPBACK) {
|
|
|
|
|
ETHQOSINFO("Loopback is already enabled\n");
|
|
|
|
|
print_loopback_detail(ethqos->current_loopback);
|
|
|
|
|
return -EINVAL;
|
|
|
|
|
}
|
|
|
|
|
ETHQOSINFO("enable loopback = %d with link speed = %d backup now\n",
|
|
|
|
|
config, speed);
|
|
|
|
|
|
|
|
|
|
/*Backup speed & duplex before Enabling Loopback */
|
|
|
|
|
if (ethqos->current_loopback == DISABLE_LOOPBACK &&
|
|
|
|
|
config > DISABLE_LOOPBACK) {
|
|
|
|
|
/*Backup old speed & duplex*/
|
|
|
|
|
ethqos->backup_speed = priv->speed;
|
|
|
|
|
ethqos->backup_duplex = priv->dev->phydev->duplex;
|
|
|
|
|
}
|
|
|
|
|
/*Backup BMCR before Enabling Phy LoopbackLoopback */
|
|
|
|
|
if (ethqos->current_loopback == DISABLE_LOOPBACK &&
|
|
|
|
|
config == ENABLE_PHY_LOOPBACK)
|
|
|
|
|
ethqos->bmcr_backup = ethqos_mdio_read(priv,
|
|
|
|
|
priv->plat->phy_addr,
|
|
|
|
|
MII_BMCR);
|
|
|
|
|
|
|
|
|
|
if (config == DISABLE_LOOPBACK)
|
|
|
|
|
setup_config_registers(ethqos, ethqos->backup_speed,
|
|
|
|
|
ethqos->backup_duplex, 0);
|
|
|
|
|
else
|
|
|
|
|
setup_config_registers(ethqos, speed, DUPLEX_FULL, config);
|
|
|
|
|
|
|
|
|
|
switch (config) {
|
|
|
|
|
case DISABLE_LOOPBACK:
|
|
|
|
|
ETHQOSINFO("Request to Disable Loopback\n");
|
|
|
|
|
if (ethqos->current_loopback == ENABLE_IO_MACRO_LOOPBACK)
|
|
|
|
|
ethqos_rgmii_io_macro_loopback(ethqos, 0);
|
|
|
|
|
else if (ethqos->current_loopback == ENABLE_MAC_LOOPBACK)
|
|
|
|
|
ethqos_mac_loopback(ethqos, 0);
|
|
|
|
|
else if (ethqos->current_loopback == ENABLE_PHY_LOOPBACK)
|
|
|
|
|
phy_digital_loopback_config(ethqos,
|
|
|
|
|
ethqos->backup_speed, 0);
|
|
|
|
|
break;
|
|
|
|
|
case ENABLE_IO_MACRO_LOOPBACK:
|
|
|
|
|
ETHQOSINFO("Request to Enable IO MACRO LOOPBACK\n");
|
|
|
|
|
ethqos_rgmii_io_macro_loopback(ethqos, 1);
|
|
|
|
|
break;
|
|
|
|
|
case ENABLE_MAC_LOOPBACK:
|
|
|
|
|
ETHQOSINFO("Request to Enable MAC LOOPBACK\n");
|
|
|
|
|
ethqos_mac_loopback(ethqos, 1);
|
|
|
|
|
break;
|
|
|
|
|
case ENABLE_PHY_LOOPBACK:
|
|
|
|
|
ETHQOSINFO("Request to Enable PHY LOOPBACK\n");
|
|
|
|
|
ethqos->loopback_speed = speed;
|
|
|
|
|
phy_digital_loopback_config(ethqos, speed, 1);
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
ETHQOSINFO("Invalid Loopback=%d\n", config);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ethqos->current_loopback = config;
|
|
|
|
|
kfree(in_buf);
|
|
|
|
|
return count;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static ssize_t read_loopback_config(struct file *file,
|
|
|
|
|
char __user *user_buf,
|
|
|
|
|
size_t count, loff_t *ppos)
|
|
|
|
|
{
|
|
|
|
|
unsigned int len = 0, buf_len = 2000;
|
|
|
|
|
struct qcom_ethqos *ethqos = file->private_data;
|
|
|
|
|
|
|
|
|
|
if (ethqos->current_loopback == DISABLE_LOOPBACK)
|
|
|
|
|
len += scnprintf(buf + len, buf_len - len,
|
|
|
|
|
"Loopback is Disabled\n");
|
|
|
|
|
else if (ethqos->current_loopback == ENABLE_IO_MACRO_LOOPBACK)
|
|
|
|
|
len += scnprintf(buf + len, buf_len - len,
|
|
|
|
|
"Current Loopback is IO MACRO LOOPBACK\n");
|
|
|
|
|
else if (ethqos->current_loopback == ENABLE_MAC_LOOPBACK)
|
|
|
|
|
len += scnprintf(buf + len, buf_len - len,
|
|
|
|
|
"Current Loopback is MAC LOOPBACK\n");
|
|
|
|
|
else if (ethqos->current_loopback == ENABLE_PHY_LOOPBACK)
|
|
|
|
|
len += scnprintf(buf + len, buf_len - len,
|
|
|
|
|
"Current Loopback is PHY LOOPBACK\n");
|
|
|
|
|
else
|
|
|
|
|
len += scnprintf(buf + len, buf_len - len,
|
|
|
|
|
"Invalid LOOPBACK Config\n");
|
|
|
|
|
if (len > buf_len)
|
|
|
|
|
len = buf_len;
|
|
|
|
|
|
|
|
|
|
return simple_read_from_buffer(user_buf, count, ppos, buf, len);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static const struct file_operations fops_phy_reg_dump = {
|
|
|
|
|
.read = read_phy_reg_dump,
|
|
|
|
|
.open = simple_open,
|
|
|
|
|
@@ -1254,6 +1767,22 @@ static ssize_t write_ipc_stmmac_log_ctxt_low(struct file *file,
|
|
|
|
|
return count;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static const struct file_operations fops_phy_off = {
|
|
|
|
|
.read = read_phy_off,
|
|
|
|
|
.write = phy_off_config,
|
|
|
|
|
.open = simple_open,
|
|
|
|
|
.owner = THIS_MODULE,
|
|
|
|
|
.llseek = default_llseek,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
static const struct file_operations fops_loopback_config = {
|
|
|
|
|
.read = read_loopback_config,
|
|
|
|
|
.write = loopback_handling_config,
|
|
|
|
|
.open = simple_open,
|
|
|
|
|
.owner = THIS_MODULE,
|
|
|
|
|
.llseek = default_llseek,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
static const struct file_operations fops_ipc_stmmac_log_low = {
|
|
|
|
|
.write = write_ipc_stmmac_log_ctxt_low,
|
|
|
|
|
.open = simple_open,
|
|
|
|
|
@@ -1266,6 +1795,8 @@ static int ethqos_create_debugfs(struct qcom_ethqos *ethqos)
|
|
|
|
|
static struct dentry *phy_reg_dump;
|
|
|
|
|
static struct dentry *rgmii_reg_dump;
|
|
|
|
|
static struct dentry *ipc_stmmac_log_low;
|
|
|
|
|
static struct dentry *phy_off;
|
|
|
|
|
static struct dentry *loopback_enable_mode;
|
|
|
|
|
|
|
|
|
|
if (!ethqos) {
|
|
|
|
|
ETHQOSERR("Null Param %s\n", __func__);
|
|
|
|
|
@@ -1299,11 +1830,26 @@ static int ethqos_create_debugfs(struct qcom_ethqos *ethqos)
|
|
|
|
|
ethqos->debugfs_dir, ethqos,
|
|
|
|
|
&fops_ipc_stmmac_log_low);
|
|
|
|
|
if (!ipc_stmmac_log_low || IS_ERR(ipc_stmmac_log_low)) {
|
|
|
|
|
ETHQOSERR("Cannot create debugfs ipc_stmmac_log_low %d\n",
|
|
|
|
|
(int)ipc_stmmac_log_low);
|
|
|
|
|
ETHQOSERR("Cannot create debugfs ipc_stmmac_log_low %x\n",
|
|
|
|
|
ipc_stmmac_log_low);
|
|
|
|
|
goto fail;
|
|
|
|
|
}
|
|
|
|
|
phy_off = debugfs_create_file("phy_off", 0400,
|
|
|
|
|
ethqos->debugfs_dir, ethqos,
|
|
|
|
|
&fops_phy_off);
|
|
|
|
|
if (!phy_off || IS_ERR(phy_off)) {
|
|
|
|
|
ETHQOSERR("Can't create phy_off %x\n", phy_off);
|
|
|
|
|
goto fail;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
loopback_enable_mode = debugfs_create_file("loopback_enable_mode", 0400,
|
|
|
|
|
ethqos->debugfs_dir, ethqos,
|
|
|
|
|
&fops_loopback_config);
|
|
|
|
|
if (!loopback_enable_mode || IS_ERR(loopback_enable_mode)) {
|
|
|
|
|
ETHQOSERR("Can't create loopback_enable_mode %d\n",
|
|
|
|
|
(int)loopback_enable_mode);
|
|
|
|
|
goto fail;
|
|
|
|
|
}
|
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
|
|
fail:
|
|
|
|
|
@@ -1914,6 +2460,21 @@ static int qcom_ethqos_probe(struct platform_device *pdev)
|
|
|
|
|
}
|
|
|
|
|
ETHQOSDBG(": emac_core_version = %d\n", ethqos->emac_ver);
|
|
|
|
|
|
|
|
|
|
if (of_property_read_bool(pdev->dev.of_node,
|
|
|
|
|
"emac-phy-off-suspend")) {
|
|
|
|
|
/* Read emac core version value from dtsi */
|
|
|
|
|
ret = of_property_read_u32(pdev->dev.of_node,
|
|
|
|
|
"emac-phy-off-suspend",
|
|
|
|
|
ðqos->current_phy_mode);
|
|
|
|
|
if (ret) {
|
|
|
|
|
ETHQOSDBG(":resource emac-phy-off-suspend! ");
|
|
|
|
|
ETHQOSDBG("not in dtsi\n");
|
|
|
|
|
ethqos->current_phy_mode = 0;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
ETHQOSINFO("emac-phy-off-suspend = %d\n",
|
|
|
|
|
ethqos->current_phy_mode);
|
|
|
|
|
|
|
|
|
|
ethqos->ioaddr = (&stmmac_res)->addr;
|
|
|
|
|
ethqos_update_rgmii_tx_drv_strength(ethqos);
|
|
|
|
|
|
|
|
|
|
@@ -2011,6 +2572,8 @@ static int qcom_ethqos_suspend(struct device *dev)
|
|
|
|
|
struct net_device *ndev = NULL;
|
|
|
|
|
int ret;
|
|
|
|
|
int allow_suspend = 0;
|
|
|
|
|
struct stmmac_priv *priv;
|
|
|
|
|
struct plat_stmmacenet_data *plat;
|
|
|
|
|
|
|
|
|
|
ETHQOSDBG("Suspend Enter\n");
|
|
|
|
|
if (of_device_is_compatible(dev->of_node, "qcom,emac-smmu-embedded")) {
|
|
|
|
|
@@ -2023,6 +2586,8 @@ static int qcom_ethqos_suspend(struct device *dev)
|
|
|
|
|
return -ENODEV;
|
|
|
|
|
|
|
|
|
|
ndev = dev_get_drvdata(dev);
|
|
|
|
|
priv = netdev_priv(ndev);
|
|
|
|
|
plat = priv->plat;
|
|
|
|
|
|
|
|
|
|
ethqos_ipa_offload_event_handler(&allow_suspend, EV_DPM_SUSPEND);
|
|
|
|
|
if (!allow_suspend) {
|
|
|
|
|
@@ -2032,9 +2597,25 @@ static int qcom_ethqos_suspend(struct device *dev)
|
|
|
|
|
}
|
|
|
|
|
if (!ndev || !netif_running(ndev))
|
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
|
|
if (ethqos->current_phy_mode == DISABLE_PHY_AT_SUSPEND_ONLY ||
|
|
|
|
|
ethqos->current_phy_mode == DISABLE_PHY_SUSPEND_ENABLE_RESUME) {
|
|
|
|
|
/*Backup phy related data*/
|
|
|
|
|
if (priv->phydev->autoneg == AUTONEG_DISABLE) {
|
|
|
|
|
ethqos->backup_autoneg = priv->phydev->autoneg;
|
|
|
|
|
ethqos->backup_bmcr = ethqos_mdio_read(priv,
|
|
|
|
|
plat->phy_addr,
|
|
|
|
|
MII_BMCR);
|
|
|
|
|
} else {
|
|
|
|
|
ethqos->backup_autoneg = AUTONEG_ENABLE;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
ret = stmmac_suspend(dev);
|
|
|
|
|
qcom_ethqos_phy_suspend_clks(ethqos);
|
|
|
|
|
if (ethqos->current_phy_mode == DISABLE_PHY_AT_SUSPEND_ONLY ||
|
|
|
|
|
ethqos->current_phy_mode == DISABLE_PHY_SUSPEND_ENABLE_RESUME) {
|
|
|
|
|
ETHQOSINFO("disable phy at suspend\n");
|
|
|
|
|
ethqos_phy_power_off(ethqos);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ETHQOSDBG(" ret = %d\n", ret);
|
|
|
|
|
return ret;
|
|
|
|
|
@@ -2045,6 +2626,7 @@ static int qcom_ethqos_resume(struct device *dev)
|
|
|
|
|
struct net_device *ndev = NULL;
|
|
|
|
|
struct qcom_ethqos *ethqos;
|
|
|
|
|
int ret;
|
|
|
|
|
struct stmmac_priv *priv;
|
|
|
|
|
|
|
|
|
|
ETHQOSDBG("Resume Enter\n");
|
|
|
|
|
if (of_device_is_compatible(dev->of_node, "qcom,emac-smmu-embedded"))
|
|
|
|
|
@@ -2056,6 +2638,7 @@ static int qcom_ethqos_resume(struct device *dev)
|
|
|
|
|
return -ENODEV;
|
|
|
|
|
|
|
|
|
|
ndev = dev_get_drvdata(dev);
|
|
|
|
|
priv = netdev_priv(ndev);
|
|
|
|
|
|
|
|
|
|
if (!ndev || !netif_running(ndev)) {
|
|
|
|
|
ETHQOSERR(" Resume not possible\n");
|
|
|
|
|
@@ -2068,10 +2651,39 @@ static int qcom_ethqos_resume(struct device *dev)
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (ethqos->current_phy_mode == DISABLE_PHY_SUSPEND_ENABLE_RESUME) {
|
|
|
|
|
ETHQOSINFO("enable phy at resume\n");
|
|
|
|
|
ethqos_phy_power_on(ethqos);
|
|
|
|
|
}
|
|
|
|
|
qcom_ethqos_phy_resume_clks(ethqos);
|
|
|
|
|
|
|
|
|
|
ret = stmmac_resume(dev);
|
|
|
|
|
if (ethqos->current_phy_mode == DISABLE_PHY_SUSPEND_ENABLE_RESUME) {
|
|
|
|
|
ETHQOSINFO("reset phy after clock\n");
|
|
|
|
|
ethqos_reset_phy_enable_interrupt(ethqos);
|
|
|
|
|
if (ethqos->backup_autoneg == AUTONEG_DISABLE) {
|
|
|
|
|
priv->phydev->autoneg = ethqos->backup_autoneg;
|
|
|
|
|
ethqos_mdio_write(
|
|
|
|
|
priv, priv->plat->phy_addr,
|
|
|
|
|
MII_BMCR, ethqos->backup_bmcr);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (ethqos->current_phy_mode == DISABLE_PHY_AT_SUSPEND_ONLY) {
|
|
|
|
|
/* Temp Enable LOOPBACK_EN.
|
|
|
|
|
* TX clock needed for reset As Phy is off
|
|
|
|
|
*/
|
|
|
|
|
rgmii_updatel(ethqos, RGMII_CONFIG_LOOPBACK_EN,
|
|
|
|
|
RGMII_CONFIG_LOOPBACK_EN,
|
|
|
|
|
RGMII_IO_MACRO_CONFIG);
|
|
|
|
|
ETHQOSINFO("Loopback EN Enabled\n");
|
|
|
|
|
}
|
|
|
|
|
ret = stmmac_resume(dev);
|
|
|
|
|
if (ethqos->current_phy_mode == DISABLE_PHY_AT_SUSPEND_ONLY) {
|
|
|
|
|
//Disable LOOPBACK_EN
|
|
|
|
|
rgmii_updatel(ethqos, RGMII_CONFIG_LOOPBACK_EN,
|
|
|
|
|
0, RGMII_IO_MACRO_CONFIG);
|
|
|
|
|
ETHQOSINFO("Loopback EN Disabled\n");
|
|
|
|
|
}
|
|
|
|
|
ethqos_ipa_offload_event_handler(NULL, EV_DPM_RESUME);
|
|
|
|
|
|
|
|
|
|
ETHQOSDBG("<--Resume Exit\n");
|
|
|
|
|
|