]> git.karo-electronics.de Git - mv-sheeva.git/blobdiff - drivers/net/phy/broadcom.c
Merge branch 'cpus4096-for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git...
[mv-sheeva.git] / drivers / net / phy / broadcom.c
index 5b80358af65840926a1de33d4517e41396d6f835..4b4dc98ad165df01b96dcd9753419d383132461d 100644 (file)
 #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)
+
+/*
+ * 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 */
+
+/*
+ * 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
+ * and Secondary SerDes registers (when sec_serdes=1).
+ */
+static int bcm54xx_exp_read(struct phy_device *phydev,
+                           int sec_serdes, u8 regnum)
+{
+       int val;
+
+       phy_write(phydev, MII_BCM54XX_EXP_SEL,
+                 (sec_serdes ? MII_BCM54XX_EXP_SEL_SSD :
+                               MII_BCM54XX_EXP_SEL_ER) |
+                 regnum);
+       val = phy_read(phydev, MII_BCM54XX_EXP_DATA);
+       phy_write(phydev, MII_BCM54XX_EXP_SEL, regnum);
+
+       return val;
+}
+
+static int bcm54xx_exp_write(struct phy_device *phydev,
+                            int sec_serdes, u8 regnum, u16 val)
+{
+       int ret;
+
+       phy_write(phydev, MII_BCM54XX_EXP_SEL,
+                 (sec_serdes ? MII_BCM54XX_EXP_SEL_SSD :
+                               MII_BCM54XX_EXP_SEL_ER) |
+                 regnum);
+       ret = phy_write(phydev, MII_BCM54XX_EXP_DATA, val);
+       phy_write(phydev, MII_BCM54XX_EXP_SEL, regnum);
+
+       return ret;
+}
+
 static int bcm54xx_config_init(struct phy_device *phydev)
 {
        int reg, err;
@@ -70,6 +186,87 @@ static int bcm54xx_config_init(struct phy_device *phydev)
        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 = bcm54xx_exp_read(phydev, 1, BCM5482_SSD_SGMII_SLAVE);
+               bcm54xx_exp_write(phydev, 1, BCM5482_SSD_SGMII_SLAVE,
+                                 reg |
+                                 BCM5482_SSD_SGMII_SLAVE_EN |
+                                 BCM5482_SSD_SGMII_SLAVE_AD);
+
+               /*
+                * Disable secondary SerDes powerdown
+                */
+               reg = bcm54xx_exp_read(phydev, 1, BCM5482_SSD_1000BX_CTL);
+               bcm54xx_exp_write(phydev, 1, BCM5482_SSD_1000BX_CTL,
+                                 reg & ~BCM5482_SSD_1000BX_CTL_PWRDOWN);
+
+               /*
+                * 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;
@@ -99,6 +296,41 @@ static int bcm54xx_config_intr(struct phy_device *phydev)
        return err;
 }
 
+static int bcm5481_config_aneg(struct phy_device *phydev)
+{
+       int ret;
+
+       /* Aneg firsly. */
+       ret = genphy_config_aneg(phydev);
+
+       /* Then we can set up the delay. */
+       if (phydev->interface == PHY_INTERFACE_MODE_RGMII_RXID) {
+               u16 reg;
+
+               /*
+                * There is no BCM5481 specification available, so down
+                * here is everything we know about "register 0x18". This
+                * at least helps BCM5481 to successfuly receive packets
+                * on MPC8360E-RDK board. Peter Barada <peterb@logicpd.com>
+                * says: "This sets delay between the RXD and RXC signals
+                * instead of using trace lengths to achieve timing".
+                */
+
+               /* Set RDX clk delay. */
+               reg = 0x7 | (0x7 << 12);
+               phy_write(phydev, 0x18, reg);
+
+               reg = phy_read(phydev, 0x18);
+               /* Set RDX-RXC skew. */
+               reg |= (1 << 8);
+               /* Write bits 14:0. */
+               reg |= (1 << 15);
+               phy_write(phydev, 0x18, reg);
+       }
+
+       return ret;
+}
+
 static struct phy_driver bcm5411_driver = {
        .phy_id         = 0x00206070,
        .phy_id_mask    = 0xfffffff0,
@@ -141,10 +373,10 @@ static struct phy_driver bcm5461_driver = {
        .driver         = { .owner = THIS_MODULE },
 };
 
-static struct phy_driver bcm5482_driver = {
-    .phy_id            = 0x0143bcb0,
+static struct phy_driver bcm5464_driver = {
+       .phy_id         = 0x002060b0,
        .phy_id_mask    = 0xfffffff0,
-       .name           = "Broadcom BCM5482",
+       .name           = "Broadcom BCM5464",
        .features       = PHY_GBIT_FEATURES,
        .flags          = PHY_HAS_MAGICANEG | PHY_HAS_INTERRUPT,
        .config_init    = bcm54xx_config_init,
@@ -155,6 +387,34 @@ static struct phy_driver bcm5482_driver = {
        .driver         = { .owner = THIS_MODULE },
 };
 
+static struct phy_driver bcm5481_driver = {
+       .phy_id         = 0x0143bca0,
+       .phy_id_mask    = 0xfffffff0,
+       .name           = "Broadcom BCM5481",
+       .features       = PHY_GBIT_FEATURES,
+       .flags          = PHY_HAS_MAGICANEG | PHY_HAS_INTERRUPT,
+       .config_init    = bcm54xx_config_init,
+       .config_aneg    = bcm5481_config_aneg,
+       .read_status    = genphy_read_status,
+       .ack_interrupt  = bcm54xx_ack_interrupt,
+       .config_intr    = bcm54xx_config_intr,
+       .driver         = { .owner = THIS_MODULE },
+};
+
+static struct phy_driver bcm5482_driver = {
+       .phy_id         = 0x0143bcb0,
+       .phy_id_mask    = 0xfffffff0,
+       .name           = "Broadcom BCM5482",
+       .features       = PHY_GBIT_FEATURES,
+       .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 int __init broadcom_init(void)
 {
        int ret;
@@ -168,12 +428,22 @@ static int __init broadcom_init(void)
        ret = phy_driver_register(&bcm5461_driver);
        if (ret)
                goto out_5461;
+       ret = phy_driver_register(&bcm5464_driver);
+       if (ret)
+               goto out_5464;
+       ret = phy_driver_register(&bcm5481_driver);
+       if (ret)
+               goto out_5481;
        ret = phy_driver_register(&bcm5482_driver);
        if (ret)
                goto out_5482;
        return ret;
 
 out_5482:
+       phy_driver_unregister(&bcm5481_driver);
+out_5481:
+       phy_driver_unregister(&bcm5464_driver);
+out_5464:
        phy_driver_unregister(&bcm5461_driver);
 out_5461:
        phy_driver_unregister(&bcm5421_driver);
@@ -186,6 +456,8 @@ out_5411:
 static void __exit broadcom_exit(void)
 {
        phy_driver_unregister(&bcm5482_driver);
+       phy_driver_unregister(&bcm5481_driver);
+       phy_driver_unregister(&bcm5464_driver);
        phy_driver_unregister(&bcm5461_driver);
        phy_driver_unregister(&bcm5421_driver);
        phy_driver_unregister(&bcm5411_driver);