From cd9a5193bdec74e76cea84cbb670a879aa810702 Mon Sep 17 00:00:00 2001 From: Suraj Jaiswal Date: Thu, 4 Mar 2021 17:55:29 +0530 Subject: [PATCH] net: stmmac: Add support for PHY interrupts Driver only supported polling mode. Add PHY interrupt support. Change-Id: Icfd4986c5e71ccbc6e10b76553a08c86cbdee05d Acked-by: Nagarjuna Chaganti Signed-off-by: Suraj Jaiswal --- .../stmicro/stmmac/dwmac-qcom-ethqos.c | 140 +++++++++++++++++- .../stmicro/stmmac/dwmac-qcom-ethqos.h | 7 + .../ethernet/stmicro/stmmac/dwmac-qcom-gpio.c | 78 ++++++++++ drivers/net/ethernet/stmicro/stmmac/stmmac.h | 1 + .../net/ethernet/stmicro/stmmac/stmmac_main.c | 12 ++ 5 files changed, 237 insertions(+), 1 deletion(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac-qcom-ethqos.c b/drivers/net/ethernet/stmicro/stmmac/dwmac-qcom-ethqos.c index d3371c29ce18..5cfd943434bc 100644 --- a/drivers/net/ethernet/stmicro/stmmac/dwmac-qcom-ethqos.c +++ b/drivers/net/ethernet/stmicro/stmmac/dwmac-qcom-ethqos.c @@ -7,11 +7,22 @@ #include #include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include #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); @@ -468,6 +500,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(ðqos->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) + phylink_mac_change(priv->phylink, LINK_UP); + else if (phy_intr_status & LINK_DOWN_STATE) + phylink_mac_change(priv->phylink, 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, ðqos->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(ðqos->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; @@ -490,6 +620,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"); @@ -549,7 +681,10 @@ static int qcom_ethqos_probe(struct platform_device *pdev) ret = stmmac_dvr_probe(&pdev->dev, plat_dat, &stmmac_res); if (ret) goto err_clk; - } + if (!ethqos_phy_intr_config(ethqos)) + ethqos_phy_intr_enable(ethqos); + else + ETHQOSERR("Phy interrupt configuration failed"); rgmii_dump(ethqos); return ret; @@ -573,6 +708,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; diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac-qcom-ethqos.h b/drivers/net/ethernet/stmicro/stmmac/dwmac-qcom-ethqos.h index 7a5cdaea6f5c..1592b32476b2 100644 --- a/drivers/net/ethernet/stmicro/stmmac/dwmac-qcom-ethqos.h +++ b/drivers/net/ethernet/stmicro/stmmac/dwmac-qcom-ethqos.h @@ -57,6 +57,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; @@ -69,4 +74,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 diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac-qcom-gpio.c b/drivers/net/ethernet/stmicro/stmmac/dwmac-qcom-gpio.c index b9c73446ba99..dde2f98fb6dc 100644 --- a/drivers/net/ethernet/stmicro/stmmac/dwmac-qcom-gpio.c +++ b/drivers/net/ethernet/stmicro/stmmac/dwmac-qcom-gpio.c @@ -7,6 +7,7 @@ #include #include #include +#include #include "stmmac.h" #include "dwmac-qcom-ethqos.h" @@ -16,6 +17,44 @@ #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("Can't get GPIO %s, ret = %d\n", + name, *gpio); + *gpio = -1; + return ret; + } + + ret = gpio_direction_input(*gpio); + if (ret) { + ETHQOSERR("failed 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; @@ -127,3 +166,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(ðqos->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(ðqos->pdev->dev, + "qcom,phy-intr-redirect", + ðqos->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; +} diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac.h b/drivers/net/ethernet/stmicro/stmmac/stmmac.h index d993fc7e82c3..98b49aefb734 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac.h +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac.h @@ -243,6 +243,7 @@ enum stmmac_state { STMMAC_SERVICE_SCHED, }; +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); diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c index 982be75fde83..8a33b42b5ab8 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c @@ -1029,7 +1029,19 @@ static int stmmac_init_phy(struct net_device *dev) netdev_err(priv->dev, "no phy at addr %d\n", addr); return -ENODEV; } + if (phy_intr_en) { + phydev->irq = PHY_IGNORE_INTERRUPT; + phydev->interrupts = PHY_INTERRUPT_ENABLED; + if (phydev->drv && phydev->drv->config_intr && + !phydev->drv->config_intr(phydev)) { + pr_debug(" qcom-ethqos: %s config_phy_intr successful\n", + __func__); + } + } else { + pr_err("stmmac phy polling mode\n"); + phydev->irq = PHY_POLL; + } ret = phylink_connect_phy(priv->phylink, phydev); }