iwlwifi: move iwl_print_hex_dump to iwl-debug.h
[safe/jmp/linux-2.6] / drivers / net / phy / broadcom.c
index 60c5cfe..fd4fc66 100644 (file)
@@ -17,6 +17,8 @@
 #include <linux/module.h>
 #include <linux/phy.h>
 
+#define PHY_ID_BCM50610                0x0143bd60
+
 #define MII_BCM54XX_ECR                0x10    /* BCM54xx extended control register */
 #define MII_BCM54XX_ECR_IM     0x1000  /* Interrupt mask */
 #define MII_BCM54XX_ECR_IF     0x0800  /* Interrupt force */
 #define MII_BCM54XX_ESR                0x11    /* BCM54xx extended status register */
 #define MII_BCM54XX_ESR_IS     0x1000  /* Interrupt status */
 
+#define MII_BCM54XX_EXP_DATA   0x15    /* Expansion register data */
+#define MII_BCM54XX_EXP_SEL    0x17    /* Expansion register select */
+#define MII_BCM54XX_EXP_SEL_SSD        0x0e00  /* Secondary SerDes select */
+#define MII_BCM54XX_EXP_SEL_ER 0x0f00  /* Expansion register select */
+
+#define MII_BCM54XX_AUX_CTL    0x18    /* Auxiliary control register */
 #define MII_BCM54XX_ISR                0x1a    /* BCM54xx interrupt status register */
 #define MII_BCM54XX_IMR                0x1b    /* BCM54xx interrupt mask register */
 #define MII_BCM54XX_INT_CRCERR 0x0001  /* CRC error */
 #define MII_BCM54XX_INT_MDIX   0x2000  /* MDIX status change */
 #define MII_BCM54XX_INT_PSERR  0x4000  /* Pair swap error */
 
+#define MII_BCM54XX_SHD                0x1c    /* 0x1c shadow registers */
+#define MII_BCM54XX_SHD_WRITE  0x8000
+#define MII_BCM54XX_SHD_VAL(x) ((x & 0x1f) << 10)
+#define MII_BCM54XX_SHD_DATA(x)        ((x & 0x3ff) << 0)
+
+/*
+ * AUXILIARY CONTROL SHADOW ACCESS REGISTERS.  (PHY REG 0x18)
+ */
+#define MII_BCM54XX_AUXCTL_SHDWSEL_AUXCTL      0x0000
+#define MII_BCM54XX_AUXCTL_ACTL_TX_6DB         0x0400
+#define MII_BCM54XX_AUXCTL_ACTL_SMDSP_ENA      0x0800
+
+#define MII_BCM54XX_AUXCTL_MISC_WREN   0x8000
+#define MII_BCM54XX_AUXCTL_MISC_FORCE_AMDIX    0x0200
+#define MII_BCM54XX_AUXCTL_MISC_RDSEL_MISC     0x7000
+#define MII_BCM54XX_AUXCTL_SHDWSEL_MISC        0x0007
+
+#define MII_BCM54XX_AUXCTL_SHDWSEL_AUXCTL      0x0000
+
+
+/*
+ * Broadcom LED source encodings.  These are used in BCM5461, BCM5481,
+ * BCM5482, and possibly some others.
+ */
+#define BCM_LED_SRC_LINKSPD1   0x0
+#define BCM_LED_SRC_LINKSPD2   0x1
+#define BCM_LED_SRC_XMITLED    0x2
+#define BCM_LED_SRC_ACTIVITYLED        0x3
+#define BCM_LED_SRC_FDXLED     0x4
+#define BCM_LED_SRC_SLAVE      0x5
+#define BCM_LED_SRC_INTR       0x6
+#define BCM_LED_SRC_QUALITY    0x7
+#define BCM_LED_SRC_RCVLED     0x8
+#define BCM_LED_SRC_MULTICOLOR1        0xa
+#define BCM_LED_SRC_OPENSHORT  0xb
+#define BCM_LED_SRC_OFF                0xe     /* Tied high */
+#define BCM_LED_SRC_ON         0xf     /* Tied low */
+
+/*
+ * BCM5482: Shadow registers
+ * Shadow values go into bits [14:10] of register 0x1c to select a shadow
+ * register to access.
+ */
+#define BCM5482_SHD_LEDS1      0x0d    /* 01101: LED Selector 1 */
+                                       /* LED3 / ~LINKSPD[2] selector */
+#define BCM5482_SHD_LEDS1_LED3(src)    ((src & 0xf) << 4)
+                                       /* LED1 / ~LINKSPD[1] selector */
+#define BCM5482_SHD_LEDS1_LED1(src)    ((src & 0xf) << 0)
+#define BCM5482_SHD_SSD                0x14    /* 10100: Secondary SerDes control */
+#define BCM5482_SHD_SSD_LEDM   0x0008  /* SSD LED Mode enable */
+#define BCM5482_SHD_SSD_EN     0x0001  /* SSD enable */
+#define BCM5482_SHD_MODE       0x1f    /* 11111: Mode Control Register */
+#define BCM5482_SHD_MODE_1000BX        0x0001  /* Enable 1000BASE-X registers */
+
+/*
+ * EXPANSION SHADOW ACCESS REGISTERS.  (PHY REG 0x15, 0x16, and 0x17)
+ */
+#define MII_BCM54XX_EXP_AADJ1CH0               0x001f
+#define  MII_BCM54XX_EXP_AADJ1CH0_SWP_ABCD_OEN 0x0200
+#define  MII_BCM54XX_EXP_AADJ1CH0_SWSEL_THPF   0x0100
+#define MII_BCM54XX_EXP_AADJ1CH3               0x601f
+#define  MII_BCM54XX_EXP_AADJ1CH3_ADCCKADJ     0x0002
+#define MII_BCM54XX_EXP_EXP08                  0x0F08
+#define  MII_BCM54XX_EXP_EXP08_RJCT_2MHZ       0x0001
+#define  MII_BCM54XX_EXP_EXP08_EARLY_DAC_WAKE  0x0200
+#define MII_BCM54XX_EXP_EXP75                  0x0f75
+#define  MII_BCM54XX_EXP_EXP75_VDACCTRL                0x003c
+#define MII_BCM54XX_EXP_EXP96                  0x0f96
+#define  MII_BCM54XX_EXP_EXP96_MYST            0x0010
+#define MII_BCM54XX_EXP_EXP97                  0x0f97
+#define  MII_BCM54XX_EXP_EXP97_MYST            0x0c0c
+
+/*
+ * BCM5482: Secondary SerDes registers
+ */
+#define BCM5482_SSD_1000BX_CTL         0x00    /* 1000BASE-X Control */
+#define BCM5482_SSD_1000BX_CTL_PWRDOWN 0x0800  /* Power-down SSD */
+#define BCM5482_SSD_SGMII_SLAVE                0x15    /* SGMII Slave Register */
+#define BCM5482_SSD_SGMII_SLAVE_EN     0x0002  /* Slave mode enable */
+#define BCM5482_SSD_SGMII_SLAVE_AD     0x0001  /* Slave auto-detection */
+
+/*
+ * Device flags for PHYs that can be configured for different operating
+ * modes.
+ */
+#define PHY_BCM_FLAGS_VALID            0x80000000
+#define PHY_BCM_FLAGS_INTF_XAUI                0x00000020
+#define PHY_BCM_FLAGS_INTF_SGMII       0x00000010
+#define PHY_BCM_FLAGS_MODE_1000BX      0x00000002
+#define PHY_BCM_FLAGS_MODE_COPPER      0x00000001
+
 MODULE_DESCRIPTION("Broadcom PHY driver");
 MODULE_AUTHOR("Maciej W. Rozycki");
 MODULE_LICENSE("GPL");
 
+/*
+ * Indirect register access functions for the 1000BASE-T/100BASE-TX/10BASE-T
+ * 0x1c shadow registers.
+ */
+static int bcm54xx_shadow_read(struct phy_device *phydev, u16 shadow)
+{
+       phy_write(phydev, MII_BCM54XX_SHD, MII_BCM54XX_SHD_VAL(shadow));
+       return MII_BCM54XX_SHD_DATA(phy_read(phydev, MII_BCM54XX_SHD));
+}
+
+static int bcm54xx_shadow_write(struct phy_device *phydev, u16 shadow, u16 val)
+{
+       return phy_write(phydev, MII_BCM54XX_SHD,
+                        MII_BCM54XX_SHD_WRITE |
+                        MII_BCM54XX_SHD_VAL(shadow) |
+                        MII_BCM54XX_SHD_DATA(val));
+}
+
+/* Indirect register access functions for the Expansion Registers */
+static int bcm54xx_exp_read(struct phy_device *phydev, u8 regnum)
+{
+       int val;
+
+       val = phy_write(phydev, MII_BCM54XX_EXP_SEL, regnum);
+       if (val < 0)
+               return val;
+
+       val = phy_read(phydev, MII_BCM54XX_EXP_DATA);
+
+       /* Restore default value.  It's O.K. if this write fails. */
+       phy_write(phydev, MII_BCM54XX_EXP_SEL, 0);
+
+       return val;
+}
+
+static int bcm54xx_exp_write(struct phy_device *phydev, u16 regnum, u16 val)
+{
+       int ret;
+
+       ret = phy_write(phydev, MII_BCM54XX_EXP_SEL, regnum);
+       if (ret < 0)
+               return ret;
+
+       ret = phy_write(phydev, MII_BCM54XX_EXP_DATA, val);
+
+       /* Restore default value.  It's O.K. if this write fails. */
+       phy_write(phydev, MII_BCM54XX_EXP_SEL, 0);
+
+       return ret;
+}
+
+static int bcm54xx_auxctl_write(struct phy_device *phydev, u16 regnum, u16 val)
+{
+       return phy_write(phydev, MII_BCM54XX_AUX_CTL, regnum | val);
+}
+
+static int bcm50610_a0_workaround(struct phy_device *phydev)
+{
+       int err;
+
+       err = bcm54xx_auxctl_write(phydev,
+                                  MII_BCM54XX_AUXCTL_SHDWSEL_AUXCTL,
+                                  MII_BCM54XX_AUXCTL_ACTL_SMDSP_ENA |
+                                  MII_BCM54XX_AUXCTL_ACTL_TX_6DB);
+       if (err < 0)
+               return err;
+
+       err = bcm54xx_exp_write(phydev, MII_BCM54XX_EXP_EXP08,
+                               MII_BCM54XX_EXP_EXP08_RJCT_2MHZ |
+                               MII_BCM54XX_EXP_EXP08_EARLY_DAC_WAKE);
+       if (err < 0)
+               goto error;
+
+       err = bcm54xx_exp_write(phydev, MII_BCM54XX_EXP_AADJ1CH0,
+                               MII_BCM54XX_EXP_AADJ1CH0_SWP_ABCD_OEN |
+                               MII_BCM54XX_EXP_AADJ1CH0_SWSEL_THPF);
+       if (err < 0)
+               goto error;
+
+       err = bcm54xx_exp_write(phydev, MII_BCM54XX_EXP_AADJ1CH3,
+                                       MII_BCM54XX_EXP_AADJ1CH3_ADCCKADJ);
+       if (err < 0)
+               goto error;
+
+       err = bcm54xx_exp_write(phydev, MII_BCM54XX_EXP_EXP75,
+                               MII_BCM54XX_EXP_EXP75_VDACCTRL);
+       if (err < 0)
+               goto error;
+
+       err = bcm54xx_exp_write(phydev, MII_BCM54XX_EXP_EXP96,
+                               MII_BCM54XX_EXP_EXP96_MYST);
+       if (err < 0)
+               goto error;
+
+       err = bcm54xx_exp_write(phydev, MII_BCM54XX_EXP_EXP97,
+                               MII_BCM54XX_EXP_EXP97_MYST);
+
+error:
+       bcm54xx_auxctl_write(phydev,
+                            MII_BCM54XX_AUXCTL_SHDWSEL_AUXCTL,
+                            MII_BCM54XX_AUXCTL_ACTL_TX_6DB);
+
+       return err;
+}
+
 static int bcm54xx_config_init(struct phy_device *phydev)
 {
        int reg, err;
@@ -67,9 +271,106 @@ static int bcm54xx_config_init(struct phy_device *phydev)
        err = phy_write(phydev, MII_BCM54XX_IMR, reg);
        if (err < 0)
                return err;
+
+       if (phydev->drv->phy_id == PHY_ID_BCM50610) {
+               err = bcm50610_a0_workaround(phydev);
+               if (err < 0)
+                       return err;
+       }
+
        return 0;
 }
 
+static int bcm5482_config_init(struct phy_device *phydev)
+{
+       int err, reg;
+
+       err = bcm54xx_config_init(phydev);
+
+       if (phydev->dev_flags & PHY_BCM_FLAGS_MODE_1000BX) {
+               /*
+                * Enable secondary SerDes and its use as an LED source
+                */
+               reg = bcm54xx_shadow_read(phydev, BCM5482_SHD_SSD);
+               bcm54xx_shadow_write(phydev, BCM5482_SHD_SSD,
+                                    reg |
+                                    BCM5482_SHD_SSD_LEDM |
+                                    BCM5482_SHD_SSD_EN);
+
+               /*
+                * Enable SGMII slave mode and auto-detection
+                */
+               reg = BCM5482_SSD_SGMII_SLAVE | MII_BCM54XX_EXP_SEL_SSD;
+               err = bcm54xx_exp_read(phydev, reg);
+               if (err < 0)
+                       return err;
+               err = bcm54xx_exp_write(phydev, reg, err |
+                                       BCM5482_SSD_SGMII_SLAVE_EN |
+                                       BCM5482_SSD_SGMII_SLAVE_AD);
+               if (err < 0)
+                       return err;
+
+               /*
+                * Disable secondary SerDes powerdown
+                */
+               reg = BCM5482_SSD_1000BX_CTL | MII_BCM54XX_EXP_SEL_SSD;
+               err = bcm54xx_exp_read(phydev, reg);
+               if (err < 0)
+                       return err;
+               err = bcm54xx_exp_write(phydev, reg,
+                                       err & ~BCM5482_SSD_1000BX_CTL_PWRDOWN);
+               if (err < 0)
+                       return err;
+
+               /*
+                * Select 1000BASE-X register set (primary SerDes)
+                */
+               reg = bcm54xx_shadow_read(phydev, BCM5482_SHD_MODE);
+               bcm54xx_shadow_write(phydev, BCM5482_SHD_MODE,
+                                    reg | BCM5482_SHD_MODE_1000BX);
+
+               /*
+                * LED1=ACTIVITYLED, LED3=LINKSPD[2]
+                * (Use LED1 as secondary SerDes ACTIVITY LED)
+                */
+               bcm54xx_shadow_write(phydev, BCM5482_SHD_LEDS1,
+                       BCM5482_SHD_LEDS1_LED1(BCM_LED_SRC_ACTIVITYLED) |
+                       BCM5482_SHD_LEDS1_LED3(BCM_LED_SRC_LINKSPD2));
+
+               /*
+                * Auto-negotiation doesn't seem to work quite right
+                * in this mode, so we disable it and force it to the
+                * right speed/duplex setting.  Only 'link status'
+                * is important.
+                */
+               phydev->autoneg = AUTONEG_DISABLE;
+               phydev->speed = SPEED_1000;
+               phydev->duplex = DUPLEX_FULL;
+       }
+
+       return err;
+}
+
+static int bcm5482_read_status(struct phy_device *phydev)
+{
+       int err;
+
+       err = genphy_read_status(phydev);
+
+       if (phydev->dev_flags & PHY_BCM_FLAGS_MODE_1000BX) {
+               /*
+                * Only link status matters for 1000Base-X mode, so force
+                * 1000 Mbit/s full-duplex status
+                */
+               if (phydev->link) {
+                       phydev->speed = SPEED_1000;
+                       phydev->duplex = DUPLEX_FULL;
+               }
+       }
+
+       return err;
+}
+
 static int bcm54xx_ack_interrupt(struct phy_device *phydev)
 {
        int reg;
@@ -138,7 +439,8 @@ static struct phy_driver bcm5411_driver = {
        .phy_id         = 0x00206070,
        .phy_id_mask    = 0xfffffff0,
        .name           = "Broadcom BCM5411",
-       .features       = PHY_GBIT_FEATURES,
+       .features       = PHY_GBIT_FEATURES |
+                         SUPPORTED_Pause | SUPPORTED_Asym_Pause,
        .flags          = PHY_HAS_MAGICANEG | PHY_HAS_INTERRUPT,
        .config_init    = bcm54xx_config_init,
        .config_aneg    = genphy_config_aneg,
@@ -152,7 +454,8 @@ static struct phy_driver bcm5421_driver = {
        .phy_id         = 0x002060e0,
        .phy_id_mask    = 0xfffffff0,
        .name           = "Broadcom BCM5421",
-       .features       = PHY_GBIT_FEATURES,
+       .features       = PHY_GBIT_FEATURES |
+                         SUPPORTED_Pause | SUPPORTED_Asym_Pause,
        .flags          = PHY_HAS_MAGICANEG | PHY_HAS_INTERRUPT,
        .config_init    = bcm54xx_config_init,
        .config_aneg    = genphy_config_aneg,
@@ -166,7 +469,8 @@ static struct phy_driver bcm5461_driver = {
        .phy_id         = 0x002060c0,
        .phy_id_mask    = 0xfffffff0,
        .name           = "Broadcom BCM5461",
-       .features       = PHY_GBIT_FEATURES,
+       .features       = PHY_GBIT_FEATURES |
+                         SUPPORTED_Pause | SUPPORTED_Asym_Pause,
        .flags          = PHY_HAS_MAGICANEG | PHY_HAS_INTERRUPT,
        .config_init    = bcm54xx_config_init,
        .config_aneg    = genphy_config_aneg,
@@ -180,7 +484,8 @@ static struct phy_driver bcm5464_driver = {
        .phy_id         = 0x002060b0,
        .phy_id_mask    = 0xfffffff0,
        .name           = "Broadcom BCM5464",
-       .features       = PHY_GBIT_FEATURES,
+       .features       = PHY_GBIT_FEATURES |
+                         SUPPORTED_Pause | SUPPORTED_Asym_Pause,
        .flags          = PHY_HAS_MAGICANEG | PHY_HAS_INTERRUPT,
        .config_init    = bcm54xx_config_init,
        .config_aneg    = genphy_config_aneg,
@@ -194,7 +499,8 @@ static struct phy_driver bcm5481_driver = {
        .phy_id         = 0x0143bca0,
        .phy_id_mask    = 0xfffffff0,
        .name           = "Broadcom BCM5481",
-       .features       = PHY_GBIT_FEATURES,
+       .features       = PHY_GBIT_FEATURES |
+                         SUPPORTED_Pause | SUPPORTED_Asym_Pause,
        .flags          = PHY_HAS_MAGICANEG | PHY_HAS_INTERRUPT,
        .config_init    = bcm54xx_config_init,
        .config_aneg    = bcm5481_config_aneg,
@@ -208,7 +514,23 @@ static struct phy_driver bcm5482_driver = {
        .phy_id         = 0x0143bcb0,
        .phy_id_mask    = 0xfffffff0,
        .name           = "Broadcom BCM5482",
-       .features       = PHY_GBIT_FEATURES,
+       .features       = PHY_GBIT_FEATURES |
+                         SUPPORTED_Pause | SUPPORTED_Asym_Pause,
+       .flags          = PHY_HAS_MAGICANEG | PHY_HAS_INTERRUPT,
+       .config_init    = bcm5482_config_init,
+       .config_aneg    = genphy_config_aneg,
+       .read_status    = bcm5482_read_status,
+       .ack_interrupt  = bcm54xx_ack_interrupt,
+       .config_intr    = bcm54xx_config_intr,
+       .driver         = { .owner = THIS_MODULE },
+};
+
+static struct phy_driver bcm50610_driver = {
+       .phy_id         = PHY_ID_BCM50610,
+       .phy_id_mask    = 0xfffffff0,
+       .name           = "Broadcom BCM50610",
+       .features       = PHY_GBIT_FEATURES |
+                         SUPPORTED_Pause | SUPPORTED_Asym_Pause,
        .flags          = PHY_HAS_MAGICANEG | PHY_HAS_INTERRUPT,
        .config_init    = bcm54xx_config_init,
        .config_aneg    = genphy_config_aneg,
@@ -240,8 +562,13 @@ static int __init broadcom_init(void)
        ret = phy_driver_register(&bcm5482_driver);
        if (ret)
                goto out_5482;
+       ret = phy_driver_register(&bcm50610_driver);
+       if (ret)
+               goto out_50610;
        return ret;
 
+out_50610:
+       phy_driver_unregister(&bcm5482_driver);
 out_5482:
        phy_driver_unregister(&bcm5481_driver);
 out_5481:
@@ -258,6 +585,7 @@ out_5411:
 
 static void __exit broadcom_exit(void)
 {
+       phy_driver_unregister(&bcm50610_driver);
        phy_driver_unregister(&bcm5482_driver);
        phy_driver_unregister(&bcm5481_driver);
        phy_driver_unregister(&bcm5464_driver);