mirror of
https://github.com/BobTheBlinker/android_kernel_motorola_sm6375.git
synced 2026-10-10 22:40:54 -04:00
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:
parent
851e8c7a77
commit
cd9a5193bd
5 changed files with 238 additions and 2 deletions
|
|
@ -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(ð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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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(ð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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue