net: stmmac: Add support for PHY interrupts

Driver only supported polling mode. Add PHY interrupt support.

Change-Id: Icfd4986c5e71ccbc6e10b76553a08c86cbdee05d
Acked-by: Nagarjuna Chaganti <nchagant@qti.qualcomm.com>
Signed-off-by: Suraj Jaiswal <jsuraj@codeaurora.org>
This commit is contained in:
Suraj Jaiswal 2021-03-04 17:55:29 +05:30 • committed by Gerrit - the friendly Code Review server
commit cd9a5193bd
5 changed files with 238 additions and 2 deletions

View file

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

View file

@ -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

View file

@ -7,6 +7,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"
@ -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(&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;
}

View file

@ -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);

View file

@ -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);
}