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

Commit c65d152c authored by Sneh Shah's avatar Sneh Shah
Browse files

net: stmmac: Add support for PHY interrupts



Driver only supported polling mode. Add PHY interrupt support.

Change-Id: Icfd4986c5e71ccbc6e10b76553a08c86cbdee05d
Signed-off-by: default avatarSneh Shah <snehshah@codeaurora.org>
parent 4f3c8d4f
Loading
Loading
Loading
Loading
+139 −0
Original line number Diff line number Diff line
@@ -7,11 +7,22 @@
#include <linux/platform_device.h>
#include <linux/phy.h>
#include <linux/regulator/consumer.h>
#include <linux/of_gpio.h>
#include <linux/io.h>
#include <linux/iopoll.h>
#include <linux/mii.h>
#include <linux/of_mdio.h>
#include <linux/slab.h>
#include <linux/ipc_logging.h>
#include <linux/poll.h>
#include <linux/debugfs.h>

#include "stmmac.h"
#include "stmmac_platform.h"
#include "dwmac-qcom-ethqos.h"

#include "stmmac_ptp.h"

#define RGMII_IO_MACRO_DEBUG1		0x20
#define EMAC_SYSTEM_LOW_POWER_DEBUG	0x28

@@ -69,6 +80,27 @@
#define EMAC_I0_EMAC_CORE_HW_VERSION_RGOFFADDR 0x00000070
#define EMAC_HW_v2_3_2_RG 0x20030002

#define MII_BUSY 0x00000001
#define MII_WRITE 0x00000002

/* GMAC4 defines */
#define MII_GMAC4_GOC_SHIFT		2
#define MII_GMAC4_WRITE			BIT(MII_GMAC4_GOC_SHIFT)
#define MII_GMAC4_READ			(3 << MII_GMAC4_GOC_SHIFT)

#define MII_BUSY 0x00000001
#define MII_WRITE 0x00000002

#define DWC_ETH_QOS_PHY_INTR_STATUS     0x0013

#define LINK_UP 1
#define LINK_DOWN 0

#define LINK_DOWN_STATE 0x800
#define LINK_UP_STATE 0x400

bool phy_intr_en;

static int rgmii_readl(struct qcom_ethqos *ethqos, unsigned int offset)
{
	return readl(ethqos->rgmii_base + offset);
@@ -452,6 +484,104 @@ static void ethqos_fix_mac_speed(void *priv, unsigned int speed)
	ethqos_configure(ethqos);
}

static int ethqos_mdio_read(struct stmmac_priv  *priv, int phyaddr, int phyreg)
{
	unsigned int mii_address = priv->hw->mii.addr;
	unsigned int mii_data = priv->hw->mii.data;
	u32 v;
	int data;
	u32 value = MII_BUSY;

	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_READ;

	if (readl_poll_timeout(priv->ioaddr + mii_address, v, !(v & MII_BUSY),
			       100, 10000))
		return -EBUSY;

	writel_relaxed(value, priv->ioaddr + mii_address);

	if (readl_poll_timeout(priv->ioaddr + mii_address, v, !(v & MII_BUSY),
			       100, 10000))
		return -EBUSY;

	/* Read the data from the MII data register */
	data = (int)readl_relaxed(priv->ioaddr + mii_data);

	return data;
}

static int ethqos_phy_intr_config(struct qcom_ethqos *ethqos)
{
	int ret = 0;

	ethqos->phy_intr = platform_get_irq_byname(ethqos->pdev, "phy-intr");

	if (ethqos->phy_intr < 0) {
		if (ethqos->phy_intr != -EPROBE_DEFER) {
			dev_err(&ethqos->pdev->dev,
				"PHY IRQ configuration information not found\n");
		}
		ret = 1;
	}

	return ret;
}

static void ethqos_handle_phy_interrupt(struct qcom_ethqos *ethqos)
{
	int phy_intr_status = 0;
	struct platform_device *pdev = ethqos->pdev;

	struct net_device *dev = platform_get_drvdata(pdev);
	struct stmmac_priv *priv = netdev_priv(dev);

	phy_intr_status = ethqos_mdio_read(priv, priv->plat->phy_addr,
					   DWC_ETH_QOS_PHY_INTR_STATUS);

	if (phy_intr_status & LINK_UP_STATE)
		phy_mac_interrupt(dev->phydev, LINK_UP);
	else if (phy_intr_status & LINK_DOWN_STATE)
		phy_mac_interrupt(dev->phydev, LINK_DOWN);
}

static void ethqos_defer_phy_isr_work(struct work_struct *work)
{
	struct qcom_ethqos *ethqos =
		container_of(work, struct qcom_ethqos, emac_phy_work);

	ethqos_handle_phy_interrupt(ethqos);
}

static irqreturn_t ETHQOS_PHY_ISR(int irq, void *dev_data)
{
	struct qcom_ethqos *ethqos = (struct qcom_ethqos *)dev_data;

	queue_work(system_wq, &ethqos->emac_phy_work);
}

static int ethqos_phy_intr_enable(struct qcom_ethqos *ethqos)
{
	int ret = 0;
	struct net_device *dev = platform_get_drvdata(ethqos->pdev);

	INIT_WORK(&ethqos->emac_phy_work, ethqos_defer_phy_isr_work);
	ret = request_irq(ethqos->phy_intr, ETHQOS_PHY_ISR,
			  IRQF_SHARED, "stmmac", ethqos);
	if (ret) {
		ETHQOSERR("Unable to register PHY IRQ %d\n",
			  ethqos->phy_intr);
		return ret;
	}
	phy_intr_en = true;
	return ret;
}

static int qcom_ethqos_probe(struct platform_device *pdev)
{
	struct device_node *np = pdev->dev.of_node;
@@ -473,6 +603,8 @@ static int qcom_ethqos_probe(struct platform_device *pdev)
	ethqos->pdev = pdev;

	ethqos_init_reqgulators(ethqos);
	ethqos_init_gpio(ethqos);

	plat_dat = stmmac_probe_config_dt(pdev, &stmmac_res.mac);
	if (IS_ERR(plat_dat)) {
		dev_err(&pdev->dev, "dt configuration failed\n");
@@ -517,6 +649,10 @@ static int qcom_ethqos_probe(struct platform_device *pdev)
	ethqos->emac_ver = rgmii_readl(ethqos,
				       EMAC_I0_EMAC_CORE_HW_VERSION_RGOFFADDR);

	if (!ethqos_phy_intr_config(ethqos))
		ethqos_phy_intr_enable(ethqos);
	else
		ETHQOSERR("Phy interrupt configuration failed");
	rgmii_dump(ethqos);

	return ret;
@@ -541,6 +677,9 @@ static int qcom_ethqos_remove(struct platform_device *pdev)

	ret = stmmac_pltfr_remove(pdev);
	clk_disable_unprepare(ethqos->rgmii_clk);

	if (phy_intr_en)
		free_irq(ethqos->phy_intr, ethqos);
	ethqos_disable_regulators(ethqos);

	return ret;
+7 −0
Original line number Diff line number Diff line
@@ -59,6 +59,11 @@ struct qcom_ethqos {
	struct clk *rgmii_clk;
	unsigned int speed;

	int gpio_phy_intr_redirect;
	u32 phy_intr;
	/* Work struct for handling phy interrupt */
	struct work_struct emac_phy_work;

	const struct ethqos_emac_por *por;
	unsigned int num_por;
	unsigned int emac_ver;
@@ -71,4 +76,6 @@ struct qcom_ethqos {

int ethqos_init_reqgulators(struct qcom_ethqos *ethqos);
void ethqos_disable_regulators(struct qcom_ethqos *ethqos);
int ethqos_init_gpio(struct qcom_ethqos *ethqos);
void ethqos_free_gpios(struct qcom_ethqos *ethqos);
#endif
+79 −0
Original line number Diff line number Diff line
@@ -15,6 +15,7 @@
#include <linux/platform_device.h>
#include <linux/phy.h>
#include <linux/regulator/consumer.h>
#include <linux/of_gpio.h>

#include "stmmac.h"
#include "dwmac-qcom-ethqos.h"
@@ -24,6 +25,45 @@
#define EMAC_VREG_EMAC_PHY_NAME "vreg_emac_phy"
#define EMAC_VREG_RGMII_IO_PADS_NAME "vreg_rgmii_io_pads"

static int setup_gpio_input_common
	(struct device *dev, const char *name, int *gpio)
{
	int ret = 0;

	if (of_find_property(dev->of_node, name, NULL)) {
		*gpio = ret = of_get_named_gpio(dev->of_node, name, 0);
		if (ret >= 0) {
			ret = gpio_request(*gpio, name);
			if (ret) {
				ETHQOSERR("%s: Can't get GPIO %s, ret = %d\n",
					  name, *gpio);
				*gpio = -1;
				return ret;
			}

			ret = gpio_direction_input(*gpio);
			if (ret) {
				ETHQOSERR(
				   "%s: Can't set GPIO %s direction, ret = %d\n",
				   name, ret);
				return ret;
			}
		} else {
			if (ret == -EPROBE_DEFER)
				ETHQOSERR("get EMAC_GPIO probe defer\n");
			else
				ETHQOSERR("can't get gpio %s ret %d\n", name,
					  ret);
			return ret;
		}
	} else {
		ETHQOSERR("can't find gpio %s\n", name);
		ret = -EINVAL;
	}

	return ret;
}

int ethqos_init_reqgulators(struct qcom_ethqos *ethqos)
{
	int ret = 0;
@@ -139,3 +179,42 @@ void ethqos_disable_regulators(struct qcom_ethqos *ethqos)
		ethqos->gdsc_emac = NULL;
	}
}

void ethqos_free_gpios(struct qcom_ethqos *ethqos)
{
	if (gpio_is_valid(ethqos->gpio_phy_intr_redirect))
		gpio_free(ethqos->gpio_phy_intr_redirect);
	ethqos->gpio_phy_intr_redirect = -1;
}

int ethqos_init_gpio(struct qcom_ethqos *ethqos)
{
	struct pinctrl *pinctrl;

	ethqos->gpio_phy_intr_redirect = -1;
	int ret = 0;

	pinctrl = devm_pinctrl_get(&ethqos->pdev->dev);
	if (IS_ERR_OR_NULL(pinctrl)) {
		ret = PTR_ERR(pinctrl);
		ETHQOSERR("Failed to get pinctrl, err = %d\n", ret);
		return ret;
	}
	ETHQOSDBG("get pinctrl succeed\n");

	ret = setup_gpio_input_common(
			&ethqos->pdev->dev, "qcom,phy-intr-redirect",
			&ethqos->gpio_phy_intr_redirect);

	if (ret) {
		ETHQOSERR("Failed to setup <%s> gpio\n",
			  "qcom,phy-intr-redirect");
		goto gpio_error;
	}

	return ret;

gpio_error:
	ethqos_free_gpios(ethqos);
	return ret;
}
+1 −0
Original line number Diff line number Diff line
@@ -147,6 +147,7 @@ struct stmmac_priv {
#endif
};

extern bool phy_intr_en;
int stmmac_mdio_unregister(struct net_device *ndev);
int stmmac_mdio_register(struct net_device *ndev);
int stmmac_mdio_reset(struct mii_bus *mii);
+10 −0
Original line number Diff line number Diff line
@@ -983,6 +983,16 @@ static int stmmac_init_phy(struct net_device *dev)
	if (phydev->is_pseudo_fixed_link)
		phydev->irq = PHY_POLL;

	if (phy_intr_en) {
		phydev->irq = PHY_IGNORE_INTERRUPT;
		phydev->interrupts =  PHY_INTERRUPT_ENABLED;

		if (phydev->drv->config_intr &&
		    !phydev->drv->config_intr(phydev)) {
			pr_debug(" qcom-ethqos: %s config_phy_intr successful\n",
				 __func__);
		}
	}
	phy_attached_info(phydev);
	return 0;
}