Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 200f6b75 authored by Suraj Jaiswal's avatar Suraj Jaiswal
Browse files

net: stmmac: FR60005 Loopback & phy off support



Add support for all Ethernet level loopback like
IO macro, MAc, PHY . Also, Provide debugfs node for
phy off/on at suspend resume.

Change-Id: I0c095791a29120929ff52ffd77a56b1151ab9c40
Signed-off-by: default avatarSuraj Jaiswal <jsuraj@codeaurora.org>
parent 9499b3ff
Loading
Loading
Loading
Loading
+618 −6
Original line number Diff line number Diff line
@@ -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;
@@ -1102,6 +1159,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)
@@ -1198,6 +1259,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,
@@ -1249,6 +1762,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,
@@ -1261,6 +1790,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__);
@@ -1294,11 +1825,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:
@@ -1909,6 +2455,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);

@@ -2010,6 +2571,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")) {
@@ -2022,6 +2585,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) {
@@ -2031,9 +2596,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;
@@ -2044,6 +2625,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"))
@@ -2055,6 +2637,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");
@@ -2067,10 +2650,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");
+45 −3
Original line number Diff line number Diff line
@@ -236,6 +236,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\
@@ -335,6 +339,26 @@ enum IO_MACRO_PHY_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

#define RGMII_IO_MACRO_CONFIG_RGOFFADDR_OFFSET (0x00000000)
@@ -459,6 +483,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 {
@@ -514,6 +551,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,
@@ -575,4 +615,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
+57 −0

File changed.

Preview size limit exceeded, changes collapsed.

+4 −0
Original line number Diff line number Diff line
@@ -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);
		}
+8 −1

File changed.

Preview size limit exceeded, changes collapsed.

Loading