diff options
Diffstat (limited to 'drivers/net/phy/amd-xgbe-phy.c')
-rw-r--r-- | drivers/net/phy/amd-xgbe-phy.c | 627 |
1 files changed, 361 insertions, 266 deletions
diff --git a/drivers/net/phy/amd-xgbe-phy.c b/drivers/net/phy/amd-xgbe-phy.c index 5ce42e3..7fde508 100644 --- a/drivers/net/phy/amd-xgbe-phy.c +++ b/drivers/net/phy/amd-xgbe-phy.c @@ -60,6 +60,7 @@ #include <linux/interrupt.h> #include <linux/init.h> #include <linux/delay.h> +#include <linux/workqueue.h> #include <linux/netdevice.h> #include <linux/etherdevice.h> #include <linux/skbuff.h> @@ -89,6 +90,7 @@ MODULE_DESCRIPTION("AMD 10GbE (amd-xgbe) PHY driver"); #define XGBE_AN_INT_CMPLT 0x01 #define XGBE_AN_INC_LINK 0x02 #define XGBE_AN_PG_RCV 0x04 +#define XGBE_AN_INT_MASK 0x07 #define XNP_MCF_NULL_MESSAGE 0x001 #define XNP_ACK_PROCESSED BIT(12) @@ -117,18 +119,10 @@ MODULE_DESCRIPTION("AMD 10GbE (amd-xgbe) PHY driver"); #define MDIO_AN_INT 0x8002 #endif -#ifndef MDIO_AN_KR_CTRL -#define MDIO_AN_KR_CTRL 0x8003 -#endif - #ifndef MDIO_CTRL1_SPEED1G #define MDIO_CTRL1_SPEED1G (MDIO_CTRL1_SPEED10G & ~BMCR_SPEED100) #endif -#ifndef MDIO_KR_CTRL_PDETECT -#define MDIO_KR_CTRL_PDETECT 0x01 -#endif - /* SerDes integration register offsets */ #define SIR0_KR_RT_1 0x002c #define SIR0_STATUS 0x0040 @@ -294,21 +288,18 @@ do { \ enum amd_xgbe_phy_an { AMD_XGBE_AN_READY = 0, - AMD_XGBE_AN_START, - AMD_XGBE_AN_EVENT, AMD_XGBE_AN_PAGE_RECEIVED, AMD_XGBE_AN_INCOMPAT_LINK, AMD_XGBE_AN_COMPLETE, AMD_XGBE_AN_NO_LINK, - AMD_XGBE_AN_EXIT, AMD_XGBE_AN_ERROR, }; enum amd_xgbe_phy_rx { - AMD_XGBE_RX_READY = 0, - AMD_XGBE_RX_BPA, + AMD_XGBE_RX_BPA = 0, AMD_XGBE_RX_XNP, AMD_XGBE_RX_COMPLETE, + AMD_XGBE_RX_ERROR, }; enum amd_xgbe_phy_mode { @@ -337,8 +328,11 @@ struct amd_xgbe_phy_priv { void __iomem *sir0_regs; /* SerDes integration registers (1/2) */ void __iomem *sir1_regs; /* SerDes integration registers (2/2) */ - /* Maintain link status for re-starting auto-negotiation */ - unsigned int link; + int an_irq; + char an_irq_name[IFNAMSIZ + 32]; + struct work_struct an_irq_work; + unsigned int an_irq_allocated; + unsigned int speed_set; /* Auto-negotiation state machine support */ @@ -349,6 +343,7 @@ struct amd_xgbe_phy_priv { enum amd_xgbe_phy_rx kx_state; struct work_struct an_work; struct workqueue_struct *an_workqueue; + unsigned int an_supported; unsigned int parallel_detect; unsigned int lpm_ctrl; /* CTRL1 for resume */ @@ -638,6 +633,38 @@ static int amd_xgbe_phy_set_mode(struct phy_device *phydev, return ret; } +static int amd_xgbe_phy_set_an(struct phy_device *phydev, bool enable, + bool restart) +{ + int ret; + + ret = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_CTRL1); + if (ret < 0) + return ret; + + ret &= ~MDIO_AN_CTRL1_ENABLE; + + if (enable) + ret |= MDIO_AN_CTRL1_ENABLE; + + if (restart) + ret |= MDIO_AN_CTRL1_RESTART; + + phy_write_mmd(phydev, MDIO_MMD_AN, MDIO_CTRL1, ret); + + return 0; +} + +static int amd_xgbe_phy_restart_an(struct phy_device *phydev) +{ + return amd_xgbe_phy_set_an(phydev, true, true); +} + +static int amd_xgbe_phy_disable_an(struct phy_device *phydev) +{ + return amd_xgbe_phy_set_an(phydev, false, false); +} + static enum amd_xgbe_phy_an amd_xgbe_an_tx_training(struct phy_device *phydev, enum amd_xgbe_phy_rx *state) { @@ -648,7 +675,7 @@ static enum amd_xgbe_phy_an amd_xgbe_an_tx_training(struct phy_device *phydev, /* If we're not in KR mode then we're done */ if (!amd_xgbe_phy_in_kr_mode(phydev)) - return AMD_XGBE_AN_EVENT; + return AMD_XGBE_AN_PAGE_RECEIVED; /* Enable/Disable FEC */ ad_reg = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_AN_ADVERTISE + 2); @@ -682,7 +709,7 @@ static enum amd_xgbe_phy_an amd_xgbe_an_tx_training(struct phy_device *phydev, XSIR0_IOWRITE_BITS(priv, SIR0_KR_RT_1, RESET, 0); - return AMD_XGBE_AN_EVENT; + return AMD_XGBE_AN_PAGE_RECEIVED; } static enum amd_xgbe_phy_an amd_xgbe_an_tx_xnp(struct phy_device *phydev, @@ -699,7 +726,7 @@ static enum amd_xgbe_phy_an amd_xgbe_an_tx_xnp(struct phy_device *phydev, phy_write_mmd(phydev, MDIO_MMD_AN, MDIO_AN_XNP + 1, 0); phy_write_mmd(phydev, MDIO_MMD_AN, MDIO_AN_XNP, msg); - return AMD_XGBE_AN_EVENT; + return AMD_XGBE_AN_PAGE_RECEIVED; } static enum amd_xgbe_phy_an amd_xgbe_an_rx_bpa(struct phy_device *phydev, @@ -751,226 +778,255 @@ static enum amd_xgbe_phy_an amd_xgbe_an_rx_xnp(struct phy_device *phydev, amd_xgbe_an_tx_training(phydev, state); } -static enum amd_xgbe_phy_an amd_xgbe_an_start(struct phy_device *phydev) +static enum amd_xgbe_phy_an amd_xgbe_an_page_received(struct phy_device *phydev) +{ + struct amd_xgbe_phy_priv *priv = phydev->priv; + enum amd_xgbe_phy_rx *state; + int ret; + + state = amd_xgbe_phy_in_kr_mode(phydev) ? &priv->kr_state + : &priv->kx_state; + + switch (*state) { + case AMD_XGBE_RX_BPA: + ret = amd_xgbe_an_rx_bpa(phydev, state); + break; + + case AMD_XGBE_RX_XNP: + ret = amd_xgbe_an_rx_xnp(phydev, state); + break; + + default: + ret = AMD_XGBE_AN_ERROR; + } + + return ret; +} + +static enum amd_xgbe_phy_an amd_xgbe_an_incompat_link(struct phy_device *phydev) { struct amd_xgbe_phy_priv *priv = phydev->priv; int ret; /* Be sure we aren't looping trying to negotiate */ if (amd_xgbe_phy_in_kr_mode(phydev)) { - if (priv->kr_state != AMD_XGBE_RX_READY) + priv->kr_state = AMD_XGBE_RX_ERROR; + + if (!(phydev->supported & SUPPORTED_1000baseKX_Full) && + !(phydev->supported & SUPPORTED_2500baseX_Full)) + return AMD_XGBE_AN_NO_LINK; + + if (priv->kx_state != AMD_XGBE_RX_BPA) return AMD_XGBE_AN_NO_LINK; - priv->kr_state = AMD_XGBE_RX_BPA; } else { - if (priv->kx_state != AMD_XGBE_RX_READY) + priv->kx_state = AMD_XGBE_RX_ERROR; + + if (!(phydev->supported & SUPPORTED_10000baseKR_Full)) + return AMD_XGBE_AN_NO_LINK; + + if (priv->kr_state != AMD_XGBE_RX_BPA) return AMD_XGBE_AN_NO_LINK; - priv->kx_state = AMD_XGBE_RX_BPA; } - /* Set up Advertisement register 3 first */ - ret = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_AN_ADVERTISE + 2); - if (ret < 0) + ret = amd_xgbe_phy_disable_an(phydev); + if (ret) return AMD_XGBE_AN_ERROR; - if (phydev->supported & SUPPORTED_10000baseR_FEC) - ret |= 0xc000; - else - ret &= ~0xc000; - - phy_write_mmd(phydev, MDIO_MMD_AN, MDIO_AN_ADVERTISE + 2, ret); - - /* Set up Advertisement register 2 next */ - ret = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_AN_ADVERTISE + 1); - if (ret < 0) + ret = amd_xgbe_phy_switch_mode(phydev); + if (ret) return AMD_XGBE_AN_ERROR; - if (phydev->supported & SUPPORTED_10000baseKR_Full) - ret |= 0x80; - else - ret &= ~0x80; - - if ((phydev->supported & SUPPORTED_1000baseKX_Full) || - (phydev->supported & SUPPORTED_2500baseX_Full)) - ret |= 0x20; - else - ret &= ~0x20; - - phy_write_mmd(phydev, MDIO_MMD_AN, MDIO_AN_ADVERTISE + 1, ret); - - /* Set up Advertisement register 1 last */ - ret = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_AN_ADVERTISE); - if (ret < 0) + ret = amd_xgbe_phy_restart_an(phydev); + if (ret) return AMD_XGBE_AN_ERROR; - if (phydev->supported & SUPPORTED_Pause) - ret |= 0x400; - else - ret &= ~0x400; + return AMD_XGBE_AN_INCOMPAT_LINK; +} - if (phydev->supported & SUPPORTED_Asym_Pause) - ret |= 0x800; - else - ret &= ~0x800; +static irqreturn_t amd_xgbe_an_isr(int irq, void *data) +{ + struct amd_xgbe_phy_priv *priv = (struct amd_xgbe_phy_priv *)data; - /* We don't intend to perform XNP */ - ret &= ~XNP_NP_EXCHANGE; + /* Interrupt reason must be read and cleared outside of IRQ context */ + disable_irq_nosync(priv->an_irq); - phy_write_mmd(phydev, MDIO_MMD_AN, MDIO_AN_ADVERTISE, ret); + queue_work(priv->an_workqueue, &priv->an_irq_work); - /* Enable and start auto-negotiation */ - phy_write_mmd(phydev, MDIO_MMD_AN, MDIO_AN_INT, 0); + return IRQ_HANDLED; +} - ret = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_AN_KR_CTRL); - if (ret < 0) - return AMD_XGBE_AN_ERROR; +static void amd_xgbe_an_irq_work(struct work_struct *work) +{ + struct amd_xgbe_phy_priv *priv = container_of(work, + struct amd_xgbe_phy_priv, + an_irq_work); - ret |= MDIO_KR_CTRL_PDETECT; - phy_write_mmd(phydev, MDIO_MMD_AN, MDIO_AN_KR_CTRL, ret); + /* Avoid a race between enabling the IRQ and exiting the work by + * waiting for the work to finish and then queueing it + */ + flush_work(&priv->an_work); + queue_work(priv->an_workqueue, &priv->an_work); +} - ret = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_CTRL1); - if (ret < 0) - return AMD_XGBE_AN_ERROR; +static void amd_xgbe_an_state_machine(struct work_struct *work) +{ + struct amd_xgbe_phy_priv *priv = container_of(work, + struct amd_xgbe_phy_priv, + an_work); + struct phy_device *phydev = priv->phydev; + enum amd_xgbe_phy_an cur_state = priv->an_state; + int int_reg, int_mask; - ret |= MDIO_AN_CTRL1_ENABLE; - ret |= MDIO_AN_CTRL1_RESTART; - phy_write_mmd(phydev, MDIO_MMD_AN, MDIO_CTRL1, ret); + mutex_lock(&priv->an_mutex); - return AMD_XGBE_AN_EVENT; -} + /* Read the interrupt */ + int_reg = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_AN_INT); + if (!int_reg) + goto out; -static enum amd_xgbe_phy_an amd_xgbe_an_event(struct phy_device *phydev) -{ - enum amd_xgbe_phy_an new_state; - int ret; +next_int: + if (int_reg < 0) { + priv->an_state = AMD_XGBE_AN_ERROR; + int_mask = XGBE_AN_INT_MASK; + } else if (int_reg & XGBE_AN_PG_RCV) { + priv->an_state = AMD_XGBE_AN_PAGE_RECEIVED; + int_mask = XGBE_AN_PG_RCV; + } else if (int_reg & XGBE_AN_INC_LINK) { + priv->an_state = AMD_XGBE_AN_INCOMPAT_LINK; + int_mask = XGBE_AN_INC_LINK; + } else if (int_reg & XGBE_AN_INT_CMPLT) { + priv->an_state = AMD_XGBE_AN_COMPLETE; + int_mask = XGBE_AN_INT_CMPLT; + } else { + priv->an_state = AMD_XGBE_AN_ERROR; + int_mask = 0; + } - ret = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_AN_INT); - if (ret < 0) - return AMD_XGBE_AN_ERROR; + /* Clear the interrupt to be processed */ + int_reg &= ~int_mask; + phy_write_mmd(phydev, MDIO_MMD_AN, MDIO_AN_INT, int_reg); - new_state = AMD_XGBE_AN_EVENT; - if (ret & XGBE_AN_PG_RCV) - new_state = AMD_XGBE_AN_PAGE_RECEIVED; - else if (ret & XGBE_AN_INC_LINK) - new_state = AMD_XGBE_AN_INCOMPAT_LINK; - else if (ret & XGBE_AN_INT_CMPLT) - new_state = AMD_XGBE_AN_COMPLETE; + priv->an_result = priv->an_state; - if (new_state != AMD_XGBE_AN_EVENT) - phy_write_mmd(phydev, MDIO_MMD_AN, MDIO_AN_INT, 0); +again: + cur_state = priv->an_state; - return new_state; -} + switch (priv->an_state) { + case AMD_XGBE_AN_READY: + priv->an_supported = 0; + break; -static enum amd_xgbe_phy_an amd_xgbe_an_page_received(struct phy_device *phydev) -{ - struct amd_xgbe_phy_priv *priv = phydev->priv; - enum amd_xgbe_phy_rx *state; - int ret; + case AMD_XGBE_AN_PAGE_RECEIVED: + priv->an_state = amd_xgbe_an_page_received(phydev); + priv->an_supported++; + break; - state = amd_xgbe_phy_in_kr_mode(phydev) ? &priv->kr_state - : &priv->kx_state; + case AMD_XGBE_AN_INCOMPAT_LINK: + priv->an_supported = 0; + priv->parallel_detect = 0; + priv->an_state = amd_xgbe_an_incompat_link(phydev); + break; - switch (*state) { - case AMD_XGBE_RX_BPA: - ret = amd_xgbe_an_rx_bpa(phydev, state); + case AMD_XGBE_AN_COMPLETE: + priv->parallel_detect = priv->an_supported ? 0 : 1; + netdev_dbg(phydev->attached_dev, "%s successful\n", + priv->an_supported ? "Auto negotiation" + : "Parallel detection"); break; - case AMD_XGBE_RX_XNP: - ret = amd_xgbe_an_rx_xnp(phydev, state); + case AMD_XGBE_AN_NO_LINK: break; default: - ret = AMD_XGBE_AN_ERROR; + priv->an_state = AMD_XGBE_AN_ERROR; } - return ret; -} + if (priv->an_state == AMD_XGBE_AN_NO_LINK) { + int_reg = 0; + phy_write_mmd(phydev, MDIO_MMD_AN, MDIO_AN_INT, 0); + } else if (priv->an_state == AMD_XGBE_AN_ERROR) { + netdev_err(phydev->attached_dev, + "error during auto-negotiation, state=%u\n", + cur_state); -static enum amd_xgbe_phy_an amd_xgbe_an_incompat_link(struct phy_device *phydev) -{ - int ret; + int_reg = 0; + phy_write_mmd(phydev, MDIO_MMD_AN, MDIO_AN_INT, 0); + } - ret = amd_xgbe_phy_switch_mode(phydev); - if (ret) - return AMD_XGBE_AN_ERROR; + if (priv->an_state >= AMD_XGBE_AN_COMPLETE) { + priv->an_result = priv->an_state; + priv->an_state = AMD_XGBE_AN_READY; + priv->kr_state = AMD_XGBE_RX_BPA; + priv->kx_state = AMD_XGBE_RX_BPA; + } - return AMD_XGBE_AN_START; -} + if (cur_state != priv->an_state) + goto again; -static void amd_xgbe_an_state_machine(struct work_struct *work) -{ - struct amd_xgbe_phy_priv *priv = container_of(work, - struct amd_xgbe_phy_priv, - an_work); - struct phy_device *phydev = priv->phydev; - enum amd_xgbe_phy_an cur_state; - int sleep; - unsigned int an_supported = 0; + if (int_reg) + goto next_int; - /* Start in KX mode */ - if (amd_xgbe_phy_set_mode(phydev, AMD_XGBE_MODE_KX)) - priv->an_state = AMD_XGBE_AN_ERROR; +out: + enable_irq(priv->an_irq); - while (1) { - mutex_lock(&priv->an_mutex); + mutex_unlock(&priv->an_mutex); +} - cur_state = priv->an_state; +static int amd_xgbe_an_init(struct phy_device *phydev) +{ + int ret; - switch (priv->an_state) { - case AMD_XGBE_AN_START: - an_supported = 0; - priv->parallel_detect = 0; - priv->an_state = amd_xgbe_an_start(phydev); - break; + /* Set up Advertisement register 3 first */ + ret = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_AN_ADVERTISE + 2); + if (ret < 0) + return ret; - case AMD_XGBE_AN_EVENT: - priv->an_state = amd_xgbe_an_event(phydev); - break; + if (phydev->supported & SUPPORTED_10000baseR_FEC) + ret |= 0xc000; + else + ret &= ~0xc000; - case AMD_XGBE_AN_PAGE_RECEIVED: - priv->an_state = amd_xgbe_an_page_received(phydev); - an_supported++; - break; + phy_write_mmd(phydev, MDIO_MMD_AN, MDIO_AN_ADVERTISE + 2, ret); - case AMD_XGBE_AN_INCOMPAT_LINK: - priv->an_state = amd_xgbe_an_incompat_link(phydev); - break; + /* Set up Advertisement register 2 next */ + ret = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_AN_ADVERTISE + 1); + if (ret < 0) + return ret; - case AMD_XGBE_AN_COMPLETE: - priv->parallel_detect = an_supported ? 0 : 1; - netdev_info(phydev->attached_dev, "%s successful\n", - an_supported ? "Auto negotiation" - : "Parallel detection"); - /* fall through */ + if (phydev->supported & SUPPORTED_10000baseKR_Full) + ret |= 0x80; + else + ret &= ~0x80; - case AMD_XGBE_AN_NO_LINK: - case AMD_XGBE_AN_EXIT: - goto exit_unlock; + if ((phydev->supported & SUPPORTED_1000baseKX_Full) || + (phydev->supported & SUPPORTED_2500baseX_Full)) + ret |= 0x20; + else + ret &= ~0x20; - default: - priv->an_state = AMD_XGBE_AN_ERROR; - } + phy_write_mmd(phydev, MDIO_MMD_AN, MDIO_AN_ADVERTISE + 1, ret); - if (priv->an_state == AMD_XGBE_AN_ERROR) { - netdev_err(phydev->attached_dev, - "error during auto-negotiation, state=%u\n", - cur_state); - goto exit_unlock; - } + /* Set up Advertisement register 1 last */ + ret = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_AN_ADVERTISE); + if (ret < 0) + return ret; - sleep = (priv->an_state == AMD_XGBE_AN_EVENT) ? 1 : 0; + if (phydev->supported & SUPPORTED_Pause) + ret |= 0x400; + else + ret &= ~0x400; - mutex_unlock(&priv->an_mutex); + if (phydev->supported & SUPPORTED_Asym_Pause) + ret |= 0x800; + else + ret &= ~0x800; - if (sleep) - usleep_range(20, 50); - } + /* We don't intend to perform XNP */ + ret &= ~XNP_NP_EXCHANGE; -exit_unlock: - priv->an_result = priv->an_state; - priv->an_state = AMD_XGBE_AN_READY; + phy_write_mmd(phydev, MDIO_MMD_AN, MDIO_AN_ADVERTISE, ret); - mutex_unlock(&priv->an_mutex); + return 0; } static int amd_xgbe_phy_soft_reset(struct phy_device *phydev) @@ -995,13 +1051,46 @@ static int amd_xgbe_phy_soft_reset(struct phy_device *phydev) if (ret & MDIO_CTRL1_RESET) return -ETIMEDOUT; - /* Make sure the XPCS and SerDes are in compatible states */ - return amd_xgbe_phy_xgmii_mode(phydev); + /* Disable auto-negotiation for now */ + ret = amd_xgbe_phy_disable_an(phydev); + if (ret < 0) + return ret; + + /* Clear auto-negotiation interrupts */ + phy_write_mmd(phydev, MDIO_MMD_AN, MDIO_AN_INT, 0); + + return 0; } static int amd_xgbe_phy_config_init(struct phy_device *phydev) { struct amd_xgbe_phy_priv *priv = phydev->priv; + struct net_device *netdev = phydev->attached_dev; + int ret; + + if (!priv->an_irq_allocated) { + /* Allocate the auto-negotiation workqueue and interrupt */ + snprintf(priv->an_irq_name, sizeof(priv->an_irq_name) - 1, + "%s-pcs", netdev_name(netdev)); + + priv->an_workqueue = + create_singlethread_workqueue(priv->an_irq_name); + if (!priv->an_workqueue) { + netdev_err(netdev, "phy workqueue creation failed\n"); + return -ENOMEM; + } + + ret = devm_request_irq(priv->dev, priv->an_irq, + amd_xgbe_an_isr, 0, priv->an_irq_name, + priv); + if (ret) { + netdev_err(netdev, "phy irq request failed\n"); + destroy_workqueue(priv->an_workqueue); + return ret; + } + + priv->an_irq_allocated = 1; + } /* Initialize supported features */ phydev->supported = SUPPORTED_Autoneg; @@ -1019,9 +1108,27 @@ static int amd_xgbe_phy_config_init(struct phy_device *phydev) } phydev->advertising = phydev->supported; - /* Turn off and clear interrupts */ - phy_write_mmd(phydev, MDIO_MMD_AN, MDIO_AN_INTMASK, 0); - phy_write_mmd(phydev, MDIO_MMD_AN, MDIO_AN_INT, 0); + /* Set initial mode - call the mode setting routines + * directly to insure we are properly configured + */ + if (phydev->supported & SUPPORTED_10000baseKR_Full) + ret = amd_xgbe_phy_xgmii_mode(phydev); + else if (phydev->supported & SUPPORTED_1000baseKX_Full) + ret = amd_xgbe_phy_gmii_mode(phydev); + else if (phydev->supported & SUPPORTED_2500baseX_Full) + ret = amd_xgbe_phy_gmii_2500_mode(phydev); + else + ret = -EINVAL; + if (ret < 0) + return ret; + + /* Set up advertisement registers based on current settings */ + ret = amd_xgbe_an_init(phydev); + if (ret) + return ret; + + /* Enable auto-negotiation interrupts */ + phy_write_mmd(phydev, MDIO_MMD_AN, MDIO_AN_INTMASK, 0x07); return 0; } @@ -1031,25 +1138,19 @@ static int amd_xgbe_phy_setup_forced(struct phy_device *phydev) int ret; /* Disable auto-negotiation */ - ret = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_CTRL1); + ret = amd_xgbe_phy_disable_an(phydev); if (ret < 0) return ret; - ret &= ~MDIO_AN_CTRL1_ENABLE; - phy_write_mmd(phydev, MDIO_MMD_AN, MDIO_CTRL1, ret); - /* Validate/Set specified speed */ switch (phydev->speed) { case SPEED_10000: - ret = amd_xgbe_phy_xgmii_mode(phydev); + ret = amd_xgbe_phy_set_mode(phydev, AMD_XGBE_MODE_KR); break; case SPEED_2500: - ret = amd_xgbe_phy_gmii_2500_mode(phydev); - break; - case SPEED_1000: - ret = amd_xgbe_phy_gmii_mode(phydev); + ret = amd_xgbe_phy_set_mode(phydev, AMD_XGBE_MODE_KX); break; default: @@ -1069,10 +1170,11 @@ static int amd_xgbe_phy_setup_forced(struct phy_device *phydev) return 0; } -static int amd_xgbe_phy_config_aneg(struct phy_device *phydev) +static int __amd_xgbe_phy_config_aneg(struct phy_device *phydev) { struct amd_xgbe_phy_priv *priv = phydev->priv; u32 mmd_mask = phydev->c45_ids.devices_in_package; + int ret; if (phydev->autoneg != AUTONEG_ENABLE) return amd_xgbe_phy_setup_forced(phydev); @@ -1081,56 +1183,79 @@ static int amd_xgbe_phy_config_aneg(struct phy_device *phydev) if (!(mmd_mask & MDIO_DEVS_AN)) return -EINVAL; - /* Start/Restart the auto-negotiation state machine */ - mutex_lock(&priv->an_mutex); + /* Disable auto-negotiation interrupt */ + disable_irq(priv->an_irq); + + /* Start auto-negotiation in a supported mode */ + if (phydev->supported & SUPPORTED_10000baseKR_Full) + ret = amd_xgbe_phy_set_mode(phydev, AMD_XGBE_MODE_KR); + else if ((phydev->supported & SUPPORTED_1000baseKX_Full) || + (phydev->supported & SUPPORTED_2500baseX_Full)) + ret = amd_xgbe_phy_set_mode(phydev, AMD_XGBE_MODE_KX); + else + ret = -EINVAL; + if (ret < 0) { + enable_irq(priv->an_irq); + return ret; + } + + /* Disable and stop any in progress auto-negotiation */ + ret = amd_xgbe_phy_disable_an(phydev); + if (ret < 0) + return ret; + + /* Clear any auto-negotitation interrupts */ + phy_write_mmd(phydev, MDIO_MMD_AN, MDIO_AN_INT, 0); + priv->an_result = AMD_XGBE_AN_READY; - priv->an_state = AMD_XGBE_AN_START; - priv->kr_state = AMD_XGBE_RX_READY; - priv->kx_state = AMD_XGBE_RX_READY; - mutex_unlock(&priv->an_mutex); + priv->an_state = AMD_XGBE_AN_READY; + priv->kr_state = AMD_XGBE_RX_BPA; + priv->kx_state = AMD_XGBE_RX_BPA; - queue_work(priv->an_workqueue, &priv->an_work); + /* Re-enable auto-negotiation interrupt */ + enable_irq(priv->an_irq); - return 0; + /* Set up advertisement registers based on current settings */ + ret = amd_xgbe_an_init(phydev); + if (ret) + return ret; + + /* Enable and start auto-negotiation */ + return amd_xgbe_phy_restart_an(phydev); } -static int amd_xgbe_phy_aneg_done(struct phy_device *phydev) +static int amd_xgbe_phy_config_aneg(struct phy_device *phydev) { struct amd_xgbe_phy_priv *priv = phydev->priv; - enum amd_xgbe_phy_an state; + int ret; mutex_lock(&priv->an_mutex); - state = priv->an_result; + + ret = __amd_xgbe_phy_config_aneg(phydev); + mutex_unlock(&priv->an_mutex); - return (state == AMD_XGBE_AN_COMPLETE); + return ret; +} + +static int amd_xgbe_phy_aneg_done(struct phy_device *phydev) +{ + struct amd_xgbe_phy_priv *priv = phydev->priv; + + return (priv->an_result == AMD_XGBE_AN_COMPLETE); } static int amd_xgbe_phy_update_link(struct phy_device *phydev) { struct amd_xgbe_phy_priv *priv = phydev->priv; - enum amd_xgbe_phy_an state; - unsigned int check_again, autoneg; int ret; /* If we're doing auto-negotiation don't report link down */ - mutex_lock(&priv->an_mutex); - state = priv->an_state; - mutex_unlock(&priv->an_mutex); - - if (state != AMD_XGBE_AN_READY) { + if (priv->an_state != AMD_XGBE_AN_READY) { phydev->link = 1; return 0; } - /* Since the device can be in the wrong mode when a link is - * (re-)established (cable connected after the interface is - * up, etc.), the link status may report no link. If there - * is no link, try switching modes and checking the status - * again if auto negotiation is enabled. - */ - check_again = (phydev->autoneg == AUTONEG_ENABLE) ? 1 : 0; -again: /* Link status is latched low, so read once to clear * and then read again to get current state */ @@ -1144,25 +1269,6 @@ again: phydev->link = (ret & MDIO_STAT1_LSTATUS) ? 1 : 0; - if (!phydev->link) { - if (check_again) { - ret = amd_xgbe_phy_switch_mode(phydev); - if (ret < 0) - return ret; - check_again = 0; - goto again; - } - } - - autoneg = (phydev->link && !priv->link) ? 1 : 0; - priv->link = phydev->link; - if (autoneg) { - /* Link is (back) up, re-start auto-negotiation */ - ret = amd_xgbe_phy_config_aneg(phydev); - if (ret < 0) - return ret; - } - return 0; } @@ -1293,7 +1399,6 @@ static int amd_xgbe_phy_probe(struct phy_device *phydev) struct amd_xgbe_phy_priv *priv; struct platform_device *pdev; struct device *dev; - char *wq_name; const __be32 *property; unsigned int speed_set; int ret; @@ -1306,21 +1411,18 @@ static int amd_xgbe_phy_probe(struct phy_device *phydev) return -EINVAL; dev = &pdev->dev; - wq_name = kasprintf(GFP_KERNEL, "%s-amd-xgbe-phy", phydev->bus->name); - if (!wq_name) { - ret = -ENOMEM; - goto err_pdev; - } - priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); if (!priv) { ret = -ENOMEM; - goto err_name; + goto err_pdev; } priv->pdev = pdev; priv->dev = dev; priv->phydev = phydev; + mutex_init(&priv->an_mutex); + INIT_WORK(&priv->an_irq_work, amd_xgbe_an_irq_work); + INIT_WORK(&priv->an_work, amd_xgbe_an_state_machine); /* Get the device mmio areas */ priv->rxtx_res = platform_get_resource(pdev, IORESOURCE_MEM, 0); @@ -1347,6 +1449,14 @@ static int amd_xgbe_phy_probe(struct phy_device *phydev) goto err_sir0; } + /* Get the auto-negotiation interrupt */ + ret = platform_get_irq(pdev, 0); + if (ret < 0) { + dev_err(dev, "platform_get_irq failed\n"); + goto err_sir1; + } + priv->an_irq = ret; + /* Get the device speed set property */ speed_set = 0; property = of_get_property(dev->of_node, XGBE_PHY_SPEEDSET_PROPERTY, @@ -1367,19 +1477,8 @@ static int amd_xgbe_phy_probe(struct phy_device *phydev) goto err_sir1; } - priv->link = 1; - - mutex_init(&priv->an_mutex); - INIT_WORK(&priv->an_work, amd_xgbe_an_state_machine); - priv->an_workqueue = create_singlethread_workqueue(wq_name); - if (!priv->an_workqueue) { - ret = -ENOMEM; - goto err_sir1; - } - phydev->priv = priv; - kfree(wq_name); of_dev_put(pdev); return 0; @@ -1402,9 +1501,6 @@ err_rxtx: err_priv: devm_kfree(dev, priv); -err_name: - kfree(wq_name); - err_pdev: of_dev_put(pdev); @@ -1416,13 +1512,12 @@ static void amd_xgbe_phy_remove(struct phy_device *phydev) struct amd_xgbe_phy_priv *priv = phydev->priv; struct device *dev = priv->dev; - /* Stop any in process auto-negotiation */ - mutex_lock(&priv->an_mutex); - priv->an_state = AMD_XGBE_AN_EXIT; - mutex_unlock(&priv->an_mutex); + if (priv->an_irq_allocated) { + devm_free_irq(dev, priv->an_irq, priv); - flush_workqueue(priv->an_workqueue); - destroy_workqueue(priv->an_workqueue); + flush_workqueue(priv->an_workqueue); + destroy_workqueue(priv->an_workqueue); + } /* Release resources */ devm_iounmap(dev, priv->sir1_regs); |