Merge "net: stmmac: FR60005 Loopback & phy off support"

This commit is contained in:
qctecmdr 2020-08-07 21:32:25 -07:00 committed by Gerrit - the friendly Code Review server
commit 7ef2e3cc7a
7 changed files with 786 additions and 10 deletions

View File

@ -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",
&ethqos->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");

View File

@ -235,6 +235,10 @@ do {\
#define VOTE_IDX_100MBPS 2
#define VOTE_IDX_1000MBPS 3
//Mac config
#define MAC_CONFIGURATION 0x0
#define MAC_LM BIT(12)
#define TLMM_BASE_ADDRESS (tlmm_central_base_addr)
#define TLMM_RGMII_HDRV_PULL_CTL1_ADDRESS_OFFSET\
@ -329,9 +333,29 @@ static inline u32 PPSX_MASK(u32 x)
}
enum IO_MACRO_PHY_MODE {
RGMII_MODE,
RMII_MODE,
MII_MODE
RGMII_MODE,
RMII_MODE,
MII_MODE
};
enum loopback_mode {
DISABLE_LOOPBACK = 0,
ENABLE_IO_MACRO_LOOPBACK,
ENABLE_MAC_LOOPBACK,
ENABLE_PHY_LOOPBACK
};
enum phy_power_mode {
DISABLE_PHY_IMMEDIATELY = 1,
ENABLE_PHY_IMMEDIATELY,
DISABLE_PHY_AT_SUSPEND_ONLY,
DISABLE_PHY_SUSPEND_ENABLE_RESUME,
DISABLE_PHY_ON_OFF,
};
enum current_phy_state {
PHY_IS_ON = 0,
PHY_IS_OFF,
};
#define RGMII_IO_BASE_ADDRESS ethqos->rgmii_base
@ -458,6 +482,19 @@ struct qcom_ethqos {
bool ipa_enabled;
/* Key Performance Indicators */
bool print_kpi;
unsigned int emac_phy_off_suspend;
int loopback_speed;
enum loopback_mode current_loopback;
enum phy_power_mode current_phy_mode;
enum current_phy_state phy_state;
/*Backup variable for phy loopback*/
int backup_duplex;
int backup_speed;
u32 bmcr_backup;
/*Backup variable for suspend resume*/
int backup_suspend_speed;
u32 backup_bmcr;
unsigned backup_autoneg:1;
};
struct pps_cfg {
@ -513,6 +550,9 @@ bool qcom_ethqos_is_phy_link_up(struct qcom_ethqos *ethqos);
void *qcom_ethqos_get_priv(struct qcom_ethqos *ethqos);
int ppsout_config(struct stmmac_priv *priv, struct pps_cfg *eth_pps_cfg);
int ethqos_phy_power_on(struct qcom_ethqos *ethqos);
void ethqos_phy_power_off(struct qcom_ethqos *ethqos);
void ethqos_reset_phy_enable_interrupt(struct qcom_ethqos *ethqos);
u16 dwmac_qcom_select_queue(
struct net_device *dev,
@ -574,4 +614,6 @@ int dwmac_qcom_program_avb_algorithm(
struct stmmac_priv *priv, struct ifr_data_struct *req);
unsigned int dwmac_qcom_get_plat_tx_coal_frames(
struct sk_buff *skb);
unsigned int dwmac_qcom_get_eth_type(unsigned char *buf);
#endif

View File

@ -183,6 +183,63 @@ void ethqos_disable_regulators(struct qcom_ethqos *ethqos)
}
}
void ethqos_reset_phy_enable_interrupt(struct qcom_ethqos *ethqos)
{
struct stmmac_priv *priv = qcom_ethqos_get_priv(ethqos);
struct phy_device *phydev = priv->dev->phydev;
/* reset the phy so that it's ready */
if (priv->mii) {
ETHQOSERR("do mdio reset\n");
stmmac_mdio_reset(priv->mii);
}
/*Enable phy interrupt*/
if (phy_intr_en && phydev) {
ETHQOSDBG("PHY interrupt Mode enabled\n");
phydev->irq = PHY_IGNORE_INTERRUPT;
phydev->interrupts = PHY_INTERRUPT_ENABLED;
if (phydev->drv->config_intr &&
!phydev->drv->config_intr(phydev)) {
ETHQOSERR("config_phy_intr successful after phy on\n");
}
qcom_ethqos_request_phy_wol(priv->plat);
} else if (!phy_intr_en) {
phydev->irq = PHY_POLL;
ETHQOSDBG("PHY Polling Mode enabled\n");
} else {
ETHQOSERR("phydev is null , intr value=%d\n", phy_intr_en);
}
}
int ethqos_phy_power_on(struct qcom_ethqos *ethqos)
{
int ret = 0;
if (ethqos->reg_emac_phy) {
ret = regulator_enable(ethqos->reg_emac_phy);
if (ret) {
ETHQOSERR("Can not enable <%s>\n",
EMAC_VREG_EMAC_PHY_NAME);
return ret;
}
ethqos->phy_state = PHY_IS_ON;
} else {
ETHQOSERR("reg_emac_phy is NULL\n");
}
return ret;
}
void ethqos_phy_power_off(struct qcom_ethqos *ethqos)
{
if (ethqos->reg_emac_phy) {
regulator_disable(ethqos->reg_emac_phy);
ethqos->phy_state = PHY_IS_OFF;
} else {
ETHQOSERR("reg_emac_phy is NULL\n");
}
}
void ethqos_free_gpios(struct qcom_ethqos *ethqos)
{
if (gpio_is_valid(ethqos->gpio_phy_intr_redirect))

View File

@ -1046,6 +1046,7 @@ static void ntn_ipa_notify_cb(void *priv, enum ipa_dp_evt_type evt,
int stat = NET_RX_SUCCESS;
struct platform_device *pdev;
struct net_device *dev;
struct stmmac_priv *pdata;
if (!ethqos || !skb) {
ETHQOSERR("Null Param pdata %p skb %pK\n", ethqos, skb);
@ -1065,6 +1066,7 @@ static void ntn_ipa_notify_cb(void *priv, enum ipa_dp_evt_type evt,
pdev = ethqos->pdev;
dev = platform_get_drvdata(pdev);
pdata = netdev_priv(dev);
if (evt == IPA_RECEIVE) {
/*Exception packets to network stack*/
@ -1075,6 +1077,8 @@ static void ntn_ipa_notify_cb(void *priv, enum ipa_dp_evt_type evt,
skb->protocol = htons(ETH_P_IP);
iph = (struct iphdr *)skb->data;
} else {
if (ethqos->current_loopback > DISABLE_LOOPBACK)
swap_ip_port(skb, ETH_P_IP);
skb->protocol = eth_type_trans(skb, skb->dev);
iph = (struct iphdr *)(skb_mac_header(skb) + ETH_HLEN);
}

View File

@ -33,6 +33,10 @@
#include <soc/qcom/boot_stats.h>
#endif
#include "dwmac-qcom-ipa-offload.h"
#include <linux/udp.h>
#include <linux/if_ether.h>
#include <linux/if_arp.h>
#include <linux/icmp.h>
struct stmmac_resources {
void __iomem *addr;
@ -149,6 +153,7 @@ struct stmmac_priv {
void __iomem *ptpaddr;
u32 mss;
bool boot_kpi;
int current_loopback;
#ifdef CONFIG_DEBUG_FS
struct dentry *dbgfs_dir;
struct dentry *dbgfs_rings_status;
@ -198,5 +203,7 @@ int stmmac_dvr_probe(struct device *device,
void stmmac_disable_eee_mode(struct stmmac_priv *priv);
bool stmmac_eee_init(struct stmmac_priv *priv);
bool qcom_ethqos_ipa_enabled(void);
u16 icmp_fast_csum(u16 old_csum);
void swap_ip_port(struct sk_buff *skb, unsigned int eth_type);
unsigned int dwmac_qcom_get_eth_type(unsigned char *buf);
#endif /* __STMMAC_H__ */

View File

@ -647,6 +647,10 @@ static int stmmac_set_wol(struct net_device *dev, struct ethtool_wolinfo *wol)
u32 emac_wol_support = 0;
int ret;
if (ethqos->phy_state == PHY_IS_OFF) {
ETHQOSINFO("Phy is in off state Wol set not possible\n");
return -EOPNOTSUPP;
}
/* By default almost all GMAC devices support the WoL via
* magic frame but we can disable it if the HW capability
* register shows no support for pmt_magic_frame. */

View File

@ -3521,6 +3521,47 @@ static inline void stmmac_rx_refill(struct stmmac_priv *priv, u32 queue)
rx_q->dirty_rx = entry;
}
static u16 csum(u16 old_csum)
{
u16 new_checksum = 0;
new_checksum = ~(~old_csum + (-8) + 0);
return new_checksum;
}
void swap_ip_port(struct sk_buff *skb, unsigned int eth_type)
{
__be32 temp_addr;
unsigned char *buf = skb->data;
struct icmphdr *icmp_hdr;
unsigned char eth_temp[ETH_ALEN] = {};
struct ethhdr *eth = (struct ethhdr *)(buf);
struct iphdr *ip_header;
if (eth_type == ETH_P_IP) {
ip_header = (struct iphdr *)(buf + sizeof(struct ethhdr));
if (ip_header->protocol == IPPROTO_UDP ||
ip_header->protocol == IPPROTO_ICMP) {
//swap mac address
memcpy(eth_temp, eth->h_dest, ETH_ALEN);
memcpy(eth->h_dest, eth->h_source, ETH_ALEN);
memcpy(eth->h_source, eth_temp, ETH_ALEN);
//swap ip address
temp_addr = ip_header->daddr;
ip_header->daddr = ip_header->saddr;
ip_header->saddr = temp_addr;
icmp_hdr = (struct icmphdr *)(buf
+ sizeof(struct ethhdr)
+ sizeof(struct iphdr));
if (icmp_hdr->type == ICMP_ECHO) {
icmp_hdr->type = ICMP_ECHOREPLY;
icmp_hdr->checksum = csum(icmp_hdr->checksum);
}
}
}
}
/**
* stmmac_rx - manage the receive process
* @priv: driver private structure
@ -3535,6 +3576,9 @@ static int stmmac_rx(struct stmmac_priv *priv, int limit, u32 queue)
int coe = priv->hw->rx_csum;
unsigned int next_entry = rx_q->cur_rx;
unsigned int count = 0;
#ifndef CONFIG_ETH_IPA_OFFLOAD
unsigned int eth_type;
#endif
if (netif_msg_rx_status(priv)) {
void *rx_head;
@ -3709,7 +3753,13 @@ static int stmmac_rx(struct stmmac_priv *priv, int limit, u32 queue)
stmmac_get_rx_hwtstamp(priv, p, np, skb);
stmmac_rx_vlan(priv->dev, skb);
#ifndef CONFIG_ETH_IPA_OFFLOAD
eth_type = dwmac_qcom_get_eth_type(skb->data);
if (priv->current_loopback > 0 &&
eth_type == ETH_P_IP)
swap_ip_port(skb, eth_type);
#endif
skb->protocol = eth_type_trans(skb, priv->dev);
if (unlikely(!coe))