--- /dev/null
+Commit-Id: 9b1e36566c5fafbcc732c971acfcf8580332931a
+From: Divy Le Ray <divy@chelsio.com>
+Date: Wed, 8 Oct 2008 17:39:31 -0700
+Acked-by: Karsten Keil <kkeil@novell.com>
+Subject: [PATCH] cxgb3: commnonize LASI phy code
+Reference: bnc#446739
+
+Add generic code to manage interrupt driven PHYs.
+Do not reset the phy after link parameters update,
+the new values might get lost.
+Return early from link change notification
+when the link parameters remain unchanged.
+
+Signed-off-by: Divy Le Ray <divy@chelsio.com>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+
+Index: linux-2.6.27/drivers/net/cxgb3/ael1002.c
+===================================================================
+--- linux-2.6.27.orig/drivers/net/cxgb3/ael1002.c
++++ linux-2.6.27/drivers/net/cxgb3/ael1002.c
+@@ -39,9 +39,6 @@ enum {
+ AEL1002_PWR_DOWN_LO = 0xc012,
+ AEL1002_XFI_EQL = 0xc015,
+ AEL1002_LB_EN = 0xc017,
+-
+- LASI_CTRL = 0x9002,
+- LASI_STAT = 0x9005
+ };
+
+ static void ael100x_txon(struct cphy *phy)
+@@ -134,33 +131,6 @@ static int ael1006_reset(struct cphy *ph
+ return t3_phy_reset(phy, MDIO_DEV_PMA_PMD, wait);
+ }
+
+-static int ael1006_intr_enable(struct cphy *phy)
+-{
+- return mdio_write(phy, MDIO_DEV_PMA_PMD, LASI_CTRL, 1);
+-}
+-
+-static int ael1006_intr_disable(struct cphy *phy)
+-{
+- return mdio_write(phy, MDIO_DEV_PMA_PMD, LASI_CTRL, 0);
+-}
+-
+-static int ael1006_intr_clear(struct cphy *phy)
+-{
+- u32 val;
+-
+- return mdio_read(phy, MDIO_DEV_PMA_PMD, LASI_STAT, &val);
+-}
+-
+-static int ael1006_intr_handler(struct cphy *phy)
+-{
+- unsigned int status;
+- int err = mdio_read(phy, MDIO_DEV_PMA_PMD, LASI_STAT, &status);
+-
+- if (err)
+- return err;
+- return (status & 1) ? cphy_cause_link_change : 0;
+-}
+-
+ static int ael1006_power_down(struct cphy *phy, int enable)
+ {
+ return t3_mdio_change_bits(phy, MDIO_DEV_PMA_PMD, MII_BMCR,
+@@ -169,10 +139,10 @@ static int ael1006_power_down(struct cph
+
+ static struct cphy_ops ael1006_ops = {
+ .reset = ael1006_reset,
+- .intr_enable = ael1006_intr_enable,
+- .intr_disable = ael1006_intr_disable,
+- .intr_clear = ael1006_intr_clear,
+- .intr_handler = ael1006_intr_handler,
++ .intr_enable = t3_phy_lasi_intr_enable,
++ .intr_disable = t3_phy_lasi_intr_disable,
++ .intr_clear = t3_phy_lasi_intr_clear,
++ .intr_handler = t3_phy_lasi_intr_handler,
+ .get_link_status = ael100x_get_link_status,
+ .power_down = ael1006_power_down,
+ };
+@@ -189,10 +159,10 @@ int t3_ael1006_phy_prep(struct cphy *phy
+
+ static struct cphy_ops qt2045_ops = {
+ .reset = ael1006_reset,
+- .intr_enable = ael1006_intr_enable,
+- .intr_disable = ael1006_intr_disable,
+- .intr_clear = ael1006_intr_clear,
+- .intr_handler = ael1006_intr_handler,
++ .intr_enable = t3_phy_lasi_intr_enable,
++ .intr_disable = t3_phy_lasi_intr_disable,
++ .intr_clear = t3_phy_lasi_intr_clear,
++ .intr_handler = t3_phy_lasi_intr_handler,
+ .get_link_status = ael100x_get_link_status,
+ .power_down = ael1006_power_down,
+ };
+Index: linux-2.6.27/drivers/net/cxgb3/common.h
+===================================================================
+--- linux-2.6.27.orig/drivers/net/cxgb3/common.h
++++ linux-2.6.27/drivers/net/cxgb3/common.h
+@@ -522,7 +522,20 @@ enum {
+ MDIO_DEV_PMA_PMD = 1,
+ MDIO_DEV_WIS = 2,
+ MDIO_DEV_PCS = 3,
+- MDIO_DEV_XGXS = 4
++ MDIO_DEV_XGXS = 4,
++ MDIO_DEV_ANEG = 7,
++ MDIO_DEV_VEND1 = 30,
++ MDIO_DEV_VEND2 = 31
++};
++
++/* LASI control and status registers */
++enum {
++ RX_ALARM_CTRL = 0x9000,
++ TX_ALARM_CTRL = 0x9001,
++ LASI_CTRL = 0x9002,
++ RX_ALARM_STAT = 0x9003,
++ TX_ALARM_STAT = 0x9004,
++ LASI_STAT = 0x9005
+ };
+
+ /* PHY loopback direction */
+@@ -665,6 +678,10 @@ int t3_mdio_change_bits(struct cphy *phy
+ int t3_phy_reset(struct cphy *phy, int mmd, int wait);
+ int t3_phy_advertise(struct cphy *phy, unsigned int advert);
+ int t3_set_phy_speed_duplex(struct cphy *phy, int speed, int duplex);
++int t3_phy_lasi_intr_enable(struct cphy *phy);
++int t3_phy_lasi_intr_disable(struct cphy *phy);
++int t3_phy_lasi_intr_clear(struct cphy *phy);
++int t3_phy_lasi_intr_handler(struct cphy *phy);
+
+ void t3_intr_enable(struct adapter *adapter);
+ void t3_intr_disable(struct adapter *adapter);
+Index: linux-2.6.27/drivers/net/cxgb3/cxgb3_main.c
+===================================================================
+--- linux-2.6.27.orig/drivers/net/cxgb3/cxgb3_main.c
++++ linux-2.6.27/drivers/net/cxgb3/cxgb3_main.c
+@@ -1586,11 +1586,22 @@ static int speed_duplex_to_caps(int spee
+
+ static int set_settings(struct net_device *dev, struct ethtool_cmd *cmd)
+ {
++ int cap;
+ struct port_info *p = netdev_priv(dev);
+ struct link_config *lc = &p->link_config;
+
+- if (!(lc->supported & SUPPORTED_Autoneg))
+- return -EOPNOTSUPP; /* can't change speed/duplex */
++ if (!(lc->supported & SUPPORTED_Autoneg)) {
++ /*
++ * PHY offers a single speed/duplex. See if that's what's
++ * being requested.
++ */
++ if (cmd->autoneg == AUTONEG_DISABLE) {
++ cap = speed_duplex_to_caps(cmd->speed, cmd->duplex);
++ if (lc->supported & cap)
++ return 0;
++ }
++ return -EINVAL;
++ }
+
+ if (cmd->autoneg == AUTONEG_DISABLE) {
+ int cap = speed_duplex_to_caps(cmd->speed, cmd->duplex);
+@@ -2281,7 +2292,7 @@ static int cxgb_ioctl(struct net_device
+ mmd = data->phy_id >> 8;
+ if (!mmd)
+ mmd = MDIO_DEV_PCS;
+- else if (mmd > MDIO_DEV_XGXS)
++ else if (mmd > MDIO_DEV_VEND2)
+ return -EINVAL;
+
+ ret =
+@@ -2307,7 +2318,7 @@ static int cxgb_ioctl(struct net_device
+ mmd = data->phy_id >> 8;
+ if (!mmd)
+ mmd = MDIO_DEV_PCS;
+- else if (mmd > MDIO_DEV_XGXS)
++ else if (mmd > MDIO_DEV_VEND2)
+ return -EINVAL;
+
+ ret =
+Index: linux-2.6.27/drivers/net/cxgb3/t3_hw.c
+===================================================================
+--- linux-2.6.27.orig/drivers/net/cxgb3/t3_hw.c
++++ linux-2.6.27/drivers/net/cxgb3/t3_hw.c
+@@ -442,6 +442,33 @@ int t3_set_phy_speed_duplex(struct cphy
+ return mdio_write(phy, 0, MII_BMCR, ctl);
+ }
+
++int t3_phy_lasi_intr_enable(struct cphy *phy)
++{
++ return mdio_write(phy, MDIO_DEV_PMA_PMD, LASI_CTRL, 1);
++}
++
++int t3_phy_lasi_intr_disable(struct cphy *phy)
++{
++ return mdio_write(phy, MDIO_DEV_PMA_PMD, LASI_CTRL, 0);
++}
++
++int t3_phy_lasi_intr_clear(struct cphy *phy)
++{
++ u32 val;
++
++ return mdio_read(phy, MDIO_DEV_PMA_PMD, LASI_STAT, &val);
++}
++
++int t3_phy_lasi_intr_handler(struct cphy *phy)
++{
++ unsigned int status;
++ int err = mdio_read(phy, MDIO_DEV_PMA_PMD, LASI_STAT, &status);
++
++ if (err)
++ return err;
++ return (status & 1) ? cphy_cause_link_change : 0;
++}
++
+ static const struct adapter_info t3_adap_info[] = {
+ {2, 0,
+ F_GPIO2_OEN | F_GPIO4_OEN |
+@@ -1132,6 +1159,15 @@ void t3_link_changed(struct adapter *ada
+
+ phy->ops->get_link_status(phy, &link_ok, &speed, &duplex, &fc);
+
++ if (lc->requested_fc & PAUSE_AUTONEG)
++ fc &= lc->requested_fc;
++ else
++ fc = lc->requested_fc & (PAUSE_RX | PAUSE_TX);
++
++ if (link_ok == lc->link_ok && speed == lc->speed &&
++ duplex == lc->duplex && fc == lc->fc)
++ return; /* nothing changed */
++
+ if (link_ok != lc->link_ok && adapter->params.rev > 0 &&
+ uses_xaui(adapter)) {
+ if (link_ok)
+@@ -1142,10 +1178,6 @@ void t3_link_changed(struct adapter *ada
+ lc->link_ok = link_ok;
+ lc->speed = speed < 0 ? SPEED_INVALID : speed;
+ lc->duplex = duplex < 0 ? DUPLEX_INVALID : duplex;
+- if (lc->requested_fc & PAUSE_AUTONEG)
+- fc &= lc->requested_fc;
+- else
+- fc = lc->requested_fc & (PAUSE_RX | PAUSE_TX);
+
+ if (link_ok && speed >= 0 && lc->autoneg == AUTONEG_ENABLE) {
+ /* Set MAC speed, duplex, and flow control to match PHY. */
+@@ -1191,7 +1223,6 @@ int t3_link_start(struct cphy *phy, stru
+ fc);
+ /* Also disables autoneg */
+ phy->ops->set_speed_duplex(phy, lc->speed, lc->duplex);
+- phy->ops->reset(phy, 0);
+ } else
+ phy->ops->autoneg_enable(phy);
+ } else {