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

Commit bc645e59 authored by Linux Build Service Account's avatar Linux Build Service Account Committed by Gerrit - the friendly Code Review server
Browse files

Merge "msm: phy: Add WOL functions in micrel driver"

parents 23d075d0 67991a9b
Loading
Loading
Loading
Loading
+160 −3
Original line number Diff line number Diff line
@@ -28,6 +28,8 @@
#include <linux/micrel_phy.h>
#include <linux/of.h>
#include <linux/clk.h>
#include <linux/netdevice.h>
#include <linux/etherdevice.h>

/* Operation Mode Strap Override */
#define MII_KSZPHY_OMSO				0x16
@@ -73,6 +75,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;
@@ -461,6 +478,37 @@ static int ksz9031_extended_read(struct phy_device *phydev,
	return phy_read(phydev, MII_KSZ9031RN_MMD_REGDATA_REG);
}

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 = ksz9031_extended_read(
	   phydev, OP_DATA, 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 = ksz9031_extended_read(
		   phydev, OP_DATA, 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;
		ksz9031_extended_write(
		   phydev, OP_DATA, 0x2, MII_KSZPHY_WOL_CTRL_REG, reg_value);
		reg_value = ksz9031_extended_read(
		   phydev, OP_DATA, 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;
		ksz9031_extended_write(
		   phydev, OP_DATA, 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,
@@ -790,6 +838,113 @@ 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 = ksz9031_extended_read(
	   phydev, OP_DATA, 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;
	ksz9031_extended_write(
	   phydev, OP_DATA, 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;

		ksz9031_extended_write(
		   phydev, OP_DATA, 0x2, 0x11, mac[5] | (mac[4] << 8));
		ksz9031_extended_write(
		   phydev, OP_DATA, 0x2, 0x12, mac[3] | (mac[2] << 8));
		ksz9031_extended_write(
		   phydev, OP_DATA, 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 = ksz9031_extended_read(
		   phydev, OP_DATA, 0x2, MII_KSZPHY_OMSO_REG);
		reg_value |= MII_KSZPHY_OMSO_PME_N2;
		ksz9031_extended_write(
		   phydev, OP_DATA, 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 = ksz9031_extended_read(
	   phydev, OP_DATA, 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;

	mutex_lock(&phydev->lock);

	reg_value = ksz9031_extended_read(
	   phydev, OP_DATA, 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);
	mutex_unlock(&phydev->lock);

	return 0;
}

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

	mutex_lock(&phydev->lock);

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

	mutex_unlock(&phydev->lock);

	return 0;
}

static struct phy_driver ksphy_driver[] = {
{
	.phy_id		= PHY_ID_KS8737,
@@ -981,13 +1136,15 @@ static struct phy_driver ksphy_driver[] = {
	.config_init	= ksz9031_config_init,
	.config_aneg	= genphy_config_aneg,
	.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_KSZ8873MLL,
	.phy_id_mask	= MICREL_PHY_ID_MASK,