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

Commit 093f1148 authored by qctecmdr's avatar qctecmdr Committed by Gerrit - the friendly Code Review server
Browse files

Merge "net: stmmac: Low power management optimization"

parents cd0266e4 336fdc58
Loading
Loading
Loading
Loading
+202 −5
Original line number Diff line number Diff line
@@ -320,7 +320,8 @@ static void rgmii_dump(struct qcom_ethqos *ethqos)
#define RGMII_ID_MODE_10_LOW_SVS_CLK_FREQ	  (5 * 1000 * 1000UL)

static void
ethqos_update_rgmii_clk(struct qcom_ethqos *ethqos, unsigned int speed)
ethqos_update_rgmii_clk_and_bus_cfg(struct qcom_ethqos *ethqos,
				    unsigned int speed)
{
	switch (speed) {
	case SPEED_1000:
@@ -336,6 +337,21 @@ ethqos_update_rgmii_clk(struct qcom_ethqos *ethqos, unsigned int speed)
		break;
	}

	switch (speed) {
	case SPEED_1000:
		ethqos->vote_idx = VOTE_IDX_1000MBPS;
		break;
	case SPEED_100:
		ethqos->vote_idx = VOTE_IDX_100MBPS;
		break;
	case SPEED_10:
		ethqos->vote_idx = VOTE_IDX_10MBPS;
		break;
	case 0:
		ethqos->vote_idx = VOTE_IDX_0MBPS;
		ethqos->rgmii_clk_rate = 0;
		break;
	}
	clk_set_rate(ethqos->rgmii_clk, ethqos->rgmii_clk_rate);
}

@@ -665,7 +681,7 @@ static void ethqos_fix_mac_speed(void *priv, unsigned int speed)
	struct qcom_ethqos *ethqos = priv;

	ethqos->speed = speed;
	ethqos_update_rgmii_clk(ethqos, speed);
	ethqos_update_rgmii_clk_and_bus_cfg(ethqos, speed);
	ethqos_configure(ethqos);
}

@@ -741,6 +757,13 @@ static void ethqos_handle_phy_interrupt(struct qcom_ethqos *ethqos)
		ETHQOSDBG("MICREL PHY Intr EN Reg (%#x) = %#x\n",
			  DWC_ETH_QOS_MICREL_PHY_INTCS, micrel_intr_status);

		/**
		 * Call ack interrupt to clear the WOL
		 * interrupt status fields
		 */
		if (priv->phydev->drv->ack_interrupt)
			priv->phydev->drv->ack_interrupt(priv->phydev);

		/* Interrupt received for link state change */
		if (phy_intr_status & LINK_STATE_MASK) {
			if (micrel_intr_status & MICREL_LINK_UP_INTR_STATUS)
@@ -769,6 +792,9 @@ static void ethqos_defer_phy_isr_work(struct work_struct *work)
	struct qcom_ethqos *ethqos =
		container_of(work, struct qcom_ethqos, emac_phy_work);

	if (ethqos->clks_suspended)
		wait_for_completion(&ethqos->clk_enable_done);

	ethqos_handle_phy_interrupt(ethqos);
}

@@ -776,6 +802,8 @@ static irqreturn_t ETHQOS_PHY_ISR(int irq, void *dev_data)
{
	struct qcom_ethqos *ethqos = (struct qcom_ethqos *)dev_data;

	pm_wakeup_event(&ethqos->pdev->dev, 5000);

	queue_work(system_wq, &ethqos->emac_phy_work);
	return IRQ_HANDLED;
}
@@ -785,6 +813,8 @@ static int ethqos_phy_intr_enable(struct qcom_ethqos *ethqos)
	int ret = 0;

	INIT_WORK(&ethqos->emac_phy_work, ethqos_defer_phy_isr_work);
	init_completion(&ethqos->clk_enable_done);

	ret = request_irq(ethqos->phy_intr, ETHQOS_PHY_ISR,
			  IRQF_SHARED, "stmmac", ethqos);
	if (ret) {
@@ -867,6 +897,106 @@ static void ethqos_pps_irq_config(struct qcom_ethqos *ethqos)
	}
}

static void qcom_ethqos_phy_suspend_clks(struct qcom_ethqos *ethqos)
{
	ETHQOSINFO("Enter\n");

	if (phy_intr_en)
		reinit_completion(&ethqos->clk_enable_done);

	ethqos->clks_suspended = 1;

	ethqos_update_rgmii_clk_and_bus_cfg(ethqos, 0);

	ETHQOSINFO("Exit\n");
}

inline void *qcom_ethqos_get_priv(struct qcom_ethqos *ethqos)
{
	struct platform_device *pdev = ethqos->pdev;
	struct net_device *dev = platform_get_drvdata(pdev);
	struct stmmac_priv *priv = netdev_priv(dev);

	return priv;
}

inline bool qcom_ethqos_is_phy_link_up(struct qcom_ethqos *ethqos)
{
	/* PHY driver initializes phydev->link=1.
	 * So, phydev->link is 1 even on bootup with no PHY connected.
	 * phydev->link is valid only after adjust_link is called once.
	 */
	struct stmmac_priv *priv = qcom_ethqos_get_priv(ethqos);

	return (priv->dev->phydev && priv->dev->phydev->link);
}

static void qcom_ethqos_phy_resume_clks(struct qcom_ethqos *ethqos)
{
	ETHQOSINFO("Enter\n");

	if (qcom_ethqos_is_phy_link_up(ethqos))
		ethqos_update_rgmii_clk_and_bus_cfg(ethqos, ethqos->speed);
	else
		ethqos_update_rgmii_clk_and_bus_cfg(ethqos, SPEED_10);

	ethqos->clks_suspended = 0;

	if (phy_intr_en)
		complete_all(&ethqos->clk_enable_done);

	ETHQOSINFO("Exit\n");
}

void qcom_ethqos_request_phy_wol(struct plat_stmmacenet_data *plat)
{
	struct qcom_ethqos *ethqos = plat->bsp_priv;
	struct platform_device *pdev = ethqos->pdev;
	struct net_device *ndev = platform_get_drvdata(pdev);

	ethqos->phy_wol_supported = 0;
	ethqos->phy_wol_wolopts = 0;
	/* Check if phydev is valid*/
	/* Check and enable Wake-on-LAN functionality in PHY*/

	if (ndev->phydev) {
		struct ethtool_wolinfo wol = {.cmd = ETHTOOL_GWOL};

		wol.supported = 0;
		wol.wolopts = 0;
		ETHQOSINFO("phydev addr: 0x%pK\n", ndev->phydev);
		phy_ethtool_get_wol(ndev->phydev, &wol);
		ethqos->phy_wol_supported = wol.supported;
		ETHQOSINFO("Get WoL[0x%x] in %s\n", wol.supported,
			   ndev->phydev->drv->name);

	/* Try to enable supported Wake-on-LAN features in PHY*/
		if (wol.supported) {
			device_set_wakeup_capable(&ethqos->pdev->dev, 1);

			wol.cmd = ETHTOOL_SWOL;
			wol.wolopts = wol.supported;

			if (!phy_ethtool_set_wol(ndev->phydev, &wol)) {
				ethqos->phy_wol_wolopts = wol.wolopts;

				enable_irq_wake(ethqos->phy_intr);
				device_set_wakeup_enable(&ethqos->pdev->dev, 1);

				ETHQOSINFO("Enabled WoL[0x%x] in %s\n",
					   wol.wolopts,
					   ndev->phydev->drv->name);
			} else {
				ETHQOSINFO("Disabled WoL[0x%x] in %s\n",
					   wol.wolopts,
					   ndev->phydev->drv->name);
			}
		} else {
			ETHQOSINFO("WoL Not Supported\n");
		}
	}
}

static int qcom_ethqos_probe(struct platform_device *pdev)
{
	struct device_node *np = pdev->dev.of_node;
@@ -888,6 +1018,7 @@ static int qcom_ethqos_probe(struct platform_device *pdev)
		ret = -ENOMEM;
		goto err_mem;
	}

	ethqos->pdev = pdev;

	ethqos_init_reqgulators(ethqos);
@@ -920,8 +1051,8 @@ static int qcom_ethqos_probe(struct platform_device *pdev)
	if (ret)
		goto err_mem;

	ethqos->speed = SPEED_1000;
	ethqos_update_rgmii_clk(ethqos, SPEED_1000);
	ethqos->speed = SPEED_10;
	ethqos_update_rgmii_clk_and_bus_cfg(ethqos, SPEED_10);
	ethqos_set_func_clk_en(ethqos);

	plat_dat->bsp_priv = ethqos;
@@ -1010,20 +1141,86 @@ static int qcom_ethqos_remove(struct platform_device *pdev)

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

	if (phy_intr_en)
		cancel_work_sync(&ethqos->emac_phy_work);

	emac_emb_smmu_exit();
	ethqos_disable_regulators(ethqos);

	return ret;
}

static int qcom_ethqos_suspend(struct device *dev)
{
	struct qcom_ethqos *ethqos;
	struct net_device *ndev = NULL;
	int ret;

	if (of_device_is_compatible(dev->of_node, "qcom,emac-smmu-embedded")) {
		ETHQOSDBG("smmu return\n");
		return 0;
	}

	ethqos = get_stmmac_bsp_priv(dev);
	if (!ethqos)
		return -ENODEV;

	ndev = dev_get_drvdata(dev);

	if (!ndev || !netif_running(ndev))
		return -EINVAL;

	ret = stmmac_suspend(dev);
	qcom_ethqos_phy_suspend_clks(ethqos);

	ETHQOSDBG(" ret = %d\n", ret);
	return ret;
}

static int qcom_ethqos_resume(struct device *dev)
{
	struct net_device *ndev = NULL;
	struct qcom_ethqos *ethqos;
	int ret;

	ETHQOSDBG("Resume Enter\n");
	if (of_device_is_compatible(dev->of_node, "qcom,emac-smmu-embedded"))
		return 0;

	ethqos = get_stmmac_bsp_priv(dev);

	if (!ethqos)
		return -ENODEV;

	ndev = dev_get_drvdata(dev);

	if (!ndev || !netif_running(ndev)) {
		ETHQOSERR(" Resume not possible\n");
		return -EINVAL;
	}

	qcom_ethqos_phy_resume_clks(ethqos);

	ret = stmmac_resume(dev);

	ETHQOSDBG("<--Resume Exit\n");
	return ret;
}

MODULE_DEVICE_TABLE(of, qcom_ethqos_match);

static const struct dev_pm_ops qcom_ethqos_pm_ops = {
	.suspend = qcom_ethqos_suspend,
	.resume = qcom_ethqos_resume,
};

static struct platform_driver qcom_ethqos_driver = {
	.probe  = qcom_ethqos_probe,
	.remove = qcom_ethqos_remove,
	.driver = {
		.name           = DRV_NAME,
		.pm		= &stmmac_pltfr_pm_ops,
		.pm		= &qcom_ethqos_pm_ops,
		.of_match_table = of_match_ptr(qcom_ethqos_match),
	},
};
+31 −0
Original line number Diff line number Diff line
@@ -4,6 +4,8 @@
#ifndef	_DWMAC_QCOM_ETHQOS_H
#define	_DWMAC_QCOM_ETHQOS_H

//#include <linux/msm-bus.h>

#define DRV_NAME "qcom-ethqos"
#define ETHQOSDBG(fmt, args...) \
	pr_debug(DRV_NAME " %s:%d " fmt, __func__, __LINE__, ## args)
@@ -50,6 +52,11 @@
#define AVB_CLASS_A_CHANNEL_NUM 2
#define AVB_CLASS_B_CHANNEL_NUM 3

#define VOTE_IDX_0MBPS 0
#define VOTE_IDX_10MBPS 1
#define VOTE_IDX_100MBPS 2
#define VOTE_IDX_1000MBPS 3

static inline u32 PPSCMDX(u32 x, u32 val)
{
	return (GENMASK(PPS_MINIDX(x) + 3, PPS_MINIDX(x)) &
@@ -67,6 +74,12 @@ static inline u32 PPSX_MASK(u32 x)
	return GENMASK(PPS_MAXIDX(x), PPS_MINIDX(x));
}

enum IO_MACRO_PHY_MODE {
		RGMII_MODE,
		RMII_MODE,
		MII_MODE
};

struct ethqos_emac_por {
	unsigned int offset;
	unsigned int value;
@@ -94,9 +107,12 @@ struct qcom_ethqos {
	struct platform_device *pdev;
	void __iomem *rgmii_base;

	struct msm_bus_scale_pdata *bus_scale_vec;
	u32 bus_hdl;
	unsigned int rgmii_clk_rate;
	struct clk *rgmii_clk;
	unsigned int speed;
	unsigned int vote_idx;

	int gpio_phy_intr_redirect;
	u32 phy_intr;
@@ -129,6 +145,17 @@ struct qcom_ethqos {

	unsigned long avb_class_a_intr_cnt;
	unsigned long avb_class_b_intr_cnt;

	/* saving state for Wake-on-LAN */
	int wolopts;
	/* state of enabled wol options in PHY*/
	u32 phy_wol_wolopts;
	/* state of supported wol options in PHY*/
	u32 phy_wol_supported;
	/* Boolean to check if clock is suspended*/
	int clks_suspended;
	/* Structure which holds done and wait members */
	struct completion clk_enable_done;
};

struct pps_cfg {
@@ -164,6 +191,9 @@ int create_pps_interrupt_device_node(dev_t *pps_dev_t,
				     struct cdev **pps_cdev,
				     struct class **pps_class,
				     char *pps_dev_node_name);
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 ifr_data_struct *req);

u16 dwmac_qcom_select_queue(struct net_device *dev,
@@ -221,4 +251,5 @@ struct dwmac_qcom_avb_algorithm {
void 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);
void qcom_ethqos_request_phy_wol(struct plat_stmmacenet_data *plat);
#endif
+2 −0
Original line number Diff line number Diff line
@@ -264,6 +264,8 @@ int ethqos_handle_prv_ioctl(struct net_device *dev, struct ifreq *rq, int cmd);
int ethqos_init_pps(struct stmmac_priv *priv);

extern bool phy_intr_en;
void qcom_ethqos_request_phy_wol(struct plat_stmmacenet_data *plat_dat);

int stmmac_mdio_unregister(struct net_device *ndev);
int stmmac_mdio_register(struct net_device *ndev);
int stmmac_mdio_reset(struct mii_bus *mii);
+1 −16
Original line number Diff line number Diff line
@@ -1054,6 +1054,7 @@ static int stmmac_init_phy(struct net_device *dev)
			    !priv->phydev->drv->config_intr(priv->phydev)) {
				pr_err(" qcom-ethqos: %s config_phy_intr successful aftre connect\n",
				       __func__);
				qcom_ethqos_request_phy_wol(priv->plat);
			}
		} else {
			pr_info("stmmac phy polling mode\n");
@@ -4042,22 +4043,6 @@ static int stmmac_setup_tc(struct net_device *ndev, enum tc_setup_type type,
	}
}

static u16 stmmac_select_queue(struct net_device *dev, struct sk_buff *skb,
			       struct net_device *sb_dev)
{
	if (skb_shinfo(skb)->gso_type & (SKB_GSO_TCPV4 | SKB_GSO_TCPV6)) {
		/*
		 * There is no way to determine the number of TSO
		 * capable Queues. Let's use always the Queue 0
		 * because if TSO is supported then at least this
		 * one will be capable.
		 */
		return 0;
	}

	return netdev_pick_tx(dev, skb, NULL) % dev->real_num_tx_queues;
}

static int stmmac_set_mac_address(struct net_device *ndev, void *addr)
{
	struct stmmac_priv *priv = netdev_priv(ndev);
+139 −3
Original line number Diff line number Diff line
@@ -26,6 +26,8 @@
#include <linux/of.h>
#include <linux/clk.h>
#include <linux/delay.h>
#include <linux/netdevice.h>
#include <linux/etherdevice.h>

/* Operation Mode Strap Override */
#define MII_KSZPHY_OMSO				0x16
@@ -72,6 +74,21 @@

#define PS_TO_REG				200

/*Register 2.10. 15:14 PME Output Select*/
#define MII_KSZPHY_OMSO_PME_N2                  BIT(10)
/*Register 2.10. BITS 6, 1 and 0 to detect the type of WOL */
#define MII_KSZPHY_WOL_MAGIC_PKT                BIT(6)
#define MII_KSZPHY_WOL_LINK_DOWN                BIT(1)
#define MII_KSZPHY_WOL_LINK_UP                  BIT(0)
/* Register 2.10.15:14 PME Output Select */
#define MII_KSZPHY_WOL_CTRL_PME_N2              BIT(15)
#define MII_KSZPHY_WOL_CTRL_INT_N               BIT(14)

/* MMD Address 2h, Register 2h Operation Mode Strap Override*/
#define MII_KSZPHY_OMSO_REG                     0x2
/* MMD Address 2h, Register 10h Wake-On-LAN Control   */
#define MII_KSZPHY_WOL_CTRL_REG                 0x10

struct kszphy_hw_stat {
	const char *string;
	u8 reg;
@@ -498,6 +515,32 @@ static int ksz9021_config_init(struct phy_device *phydev)
#define MII_KSZ9031RN_EDPD		0x23
#define MII_KSZ9031RN_EDPD_ENABLE	BIT(0)

static int ksz9031_ack_interrupt(struct phy_device *phydev)
{
	/* bit[7..0] int status, which is a read and clear register. */
	int rc;
	u32 reg_value;

	rc = phy_read(phydev, MII_KSZPHY_INTCS);

	reg_value = phy_read_mmd(phydev, 0x2, MII_KSZPHY_OMSO_REG);
	if (reg_value & MII_KSZPHY_OMSO_PME_N2) {
		/* PME output is cleared by disabling the PME trigger src */
		reg_value = phy_read_mmd(phydev, 0x2, MII_KSZPHY_WOL_CTRL_REG);
		reg_value &= ~MII_KSZPHY_WOL_MAGIC_PKT;
		reg_value &= ~MII_KSZPHY_WOL_LINK_UP;
		reg_value &= ~MII_KSZPHY_WOL_LINK_DOWN;
		phy_write_mmd(phydev, 0x2, MII_KSZPHY_WOL_CTRL_REG, reg_value);
		reg_value = phy_read_mmd(phydev, 0x2, MII_KSZPHY_WOL_CTRL_REG);
		reg_value |= MII_KSZPHY_WOL_MAGIC_PKT;
		reg_value |= MII_KSZPHY_WOL_LINK_UP;
		reg_value |= MII_KSZPHY_WOL_LINK_DOWN;
		phy_write_mmd(phydev, 0x2, MII_KSZPHY_WOL_CTRL_REG, reg_value);
	}

	return (rc < 0) ? rc : 0;
}

static int ksz9031_of_load_skew_values(struct phy_device *phydev,
				       const struct device_node *of_node,
				       u16 reg, size_t field_sz,
@@ -984,6 +1027,97 @@ static int kszphy_probe(struct phy_device *phydev)
	return 0;
}

static void ksz9031_set_wol_settings(struct phy_device *phydev)
{
	u32 reg_value;

	/* Enable both PHY and PME_N2 interrupts */
	reg_value = phy_read_mmd(phydev, 0x2, MII_KSZPHY_WOL_CTRL_REG);
	reg_value |= MII_KSZPHY_WOL_CTRL_PME_N2;
	reg_value &= ~MII_KSZPHY_WOL_CTRL_INT_N;
	reg_value |= MII_KSZPHY_WOL_MAGIC_PKT;
	reg_value |= MII_KSZPHY_WOL_LINK_UP;
	reg_value |= MII_KSZPHY_WOL_LINK_DOWN;
	phy_write_mmd(phydev, 0x2, MII_KSZPHY_WOL_CTRL_REG, reg_value);
}

static int ksz9031_set_wol(struct phy_device *phydev,
			   struct ethtool_wolinfo *wol)
{
	struct net_device *ndev = phydev->attached_dev;
	const u8 *mac;
	int ret = 0;
	u32 reg_value;

	if (!ndev)
		return -ENODEV;

	if (wol->wolopts & WAKE_MAGIC) {
		mac = (const u8 *)ndev->dev_addr;

		if (!is_valid_ether_addr(mac))
			return -EINVAL;

		phy_write_mmd(phydev, 0x2, 0x11, mac[5] | (mac[4] << 8));
		phy_write_mmd(phydev, 0x2, 0x12, mac[3] | (mac[2] << 8));
		phy_write_mmd(phydev, 0x2, 0x13, mac[1] | (mac[0] << 8));

		/* Enable WOL interrupt for magic pkt, link up and down */
		ksz9031_set_wol_settings(phydev);

		/* Enable PME_N2 output */
		reg_value = phy_read_mmd(phydev, 0x2, MII_KSZPHY_OMSO_REG);
		reg_value |= MII_KSZPHY_OMSO_PME_N2;
		phy_write_mmd(phydev, 0x2, MII_KSZPHY_OMSO_REG, reg_value);
	}

	return ret;
}

static void ksz9031_get_wol(struct phy_device *phydev,
			    struct ethtool_wolinfo *wol)
{
	u32 reg_value;

	wol->supported = WAKE_MAGIC;
	wol->wolopts = 0;

	reg_value = phy_read_mmd(phydev, 0x2, MII_KSZPHY_OMSO_REG);
	if (reg_value & MII_KSZPHY_OMSO_PME_N2)
		wol->wolopts |= WAKE_MAGIC;
}

static int ksz9031_suspend(struct phy_device *phydev)
{
	int value;
	int wol_enabled;
	u32 reg_value;

	reg_value = phy_read_mmd(phydev, 0x2, MII_KSZPHY_OMSO_REG);
	wol_enabled = reg_value & MII_KSZPHY_OMSO_PME_N2;

	value = phy_read(phydev, MII_BMCR);
	if (wol_enabled)
		value |= BMCR_ISOLATE;
	else
		value |= BMCR_PDOWN;

	phy_write(phydev, MII_BMCR, value);

	return 0;
}

static int ksz9031_resume(struct phy_device *phydev)
{
	int value;

	value = phy_read(phydev, MII_BMCR);
	value &= ~(BMCR_PDOWN | BMCR_ISOLATE);
	phy_write(phydev, MII_BMCR, value);

	return 0;
}

static struct phy_driver ksphy_driver[] = {
{
	.phy_id		= PHY_ID_KS8737,
@@ -1139,13 +1273,15 @@ static struct phy_driver ksphy_driver[] = {
	.config_init	= ksz9031_config_init,
	.soft_reset	= genphy_soft_reset,
	.read_status	= ksz9031_read_status,
	.ack_interrupt	= kszphy_ack_interrupt,
	.ack_interrupt	= ksz9031_ack_interrupt,
	.config_intr	= kszphy_config_intr,
	.get_sset_count = kszphy_get_sset_count,
	.get_strings	= kszphy_get_strings,
	.get_stats	= kszphy_get_stats,
	.suspend	= genphy_suspend,
	.resume		= kszphy_resume,
	.set_wol	= ksz9031_set_wol,
	.get_wol	 = ksz9031_get_wol,
	.suspend	= ksz9031_suspend,
	.resume		= ksz9031_resume,
}, {
	.phy_id		= PHY_ID_KSZ9131,
	.phy_id_mask	= MICREL_PHY_ID_MASK,