net: emaclite: Add MDIO support to driver
authorMichal Simek <michal.simek@xilinx.com>
Thu, 10 Dec 2015 12:33:20 +0000 (13:33 +0100)
committerMichal Simek <michal.simek@xilinx.com>
Wed, 27 Jan 2016 14:55:51 +0000 (15:55 +0100)
Add MDIO support before move to DM.

Signed-off-by: Michal Simek <michal.simek@xilinx.com>
Acked-by: Joe Hershberger <joe.hershberger@ni.com>
drivers/net/xilinx_emaclite.c

index d3d40b12adf2ce06017f13ea309692eaaf14ac30..f5e4c029ed75d661f6766ab16fbfb7cfa1c72bb2 100644 (file)
 #include <common.h>
 #include <net.h>
 #include <config.h>
+#include <console.h>
 #include <malloc.h>
 #include <asm/io.h>
+#include <phy.h>
+#include <miiphy.h>
 #include <fdtdec.h>
+#include <asm-generic/errno.h>
 
 #undef DEBUG
 
 /* Recv interrupt enable bit */
 #define XEL_RSR_RECV_IE_MASK           0x00000008UL
 
+/* MDIO */
+#define XEL_MDIOADDR_OFFSET    0x07E4          /* MDIO Address Register */
+#define XEL_MDIOWR_OFFSET      0x07E8          /* MDIO Write Data Register */
+#define XEL_MDIORD_OFFSET      0x07EC          /* MDIO Read Data Register */
+#define XEL_MDIOCTRL_OFFSET    0x07F0          /* MDIO Control Register */
+
+/* MDIO Address Register Bit Masks */
+#define XEL_MDIOADDR_REGADR_MASK  0x0000001F   /* Register Address */
+#define XEL_MDIOADDR_PHYADR_MASK  0x000003E0   /* PHY Address */
+#define XEL_MDIOADDR_PHYADR_SHIFT 5
+#define XEL_MDIOADDR_OP_MASK     0x00000400    /* RD/WR Operation */
+
+/* MDIO Write Data Register Bit Masks */
+#define XEL_MDIOWR_WRDATA_MASK   0x0000FFFF    /* Data to be Written */
+
+/* MDIO Read Data Register Bit Masks */
+#define XEL_MDIORD_RDDATA_MASK   0x0000FFFF    /* Data to be Read */
+
+/* MDIO Control Register Bit Masks */
+#define XEL_MDIOCTRL_MDIOSTS_MASK 0x00000001   /* MDIO Status Mask */
+#define XEL_MDIOCTRL_MDIOEN_MASK  0x00000008   /* MDIO Enable */
+
 struct xemaclite {
        u32 nexttxbuffertouse;  /* Next TX buffer to write to */
        u32 nextrxbuffertouse;  /* Next RX buffer to read from */
        u32 txpp;               /* TX ping pong buffer */
        u32 rxpp;               /* RX ping pong buffer */
+       int phyaddr;
+       struct phy_device *phydev;
+       struct mii_dev *bus;
 };
 
 static u32 etherrxbuff[PKTSIZE_ALIGN/4]; /* Receive buffer */
@@ -107,11 +136,177 @@ static void xemaclite_alignedwrite(void *srcptr, u32 destptr, u32 bytecount)
        *to32ptr++ = alignbuffer;
 }
 
+#if defined(CONFIG_MII) || defined(CONFIG_CMD_MII) || defined(CONFIG_PHYLIB)
+static int wait_for_bit(const char *func, u32 *reg, const u32 mask,
+                       bool set, unsigned int timeout)
+{
+       u32 val;
+       unsigned long start = get_timer(0);
+
+       while (1) {
+               val = readl(reg);
+
+               if (!set)
+                       val = ~val;
+
+               if ((val & mask) == mask)
+                       return 0;
+
+               if (get_timer(start) > timeout)
+                       break;
+
+               if (ctrlc()) {
+                       puts("Abort\n");
+                       return -EINTR;
+               }
+
+               udelay(1);
+       }
+
+       debug("%s: Timeout (reg=%p mask=%08x wait_set=%i)\n",
+             func, reg, mask, set);
+
+       return -ETIMEDOUT;
+}
+
+static int mdio_wait(struct eth_device *dev)
+{
+       return wait_for_bit(__func__,
+                           (u32 *)(dev->iobase + XEL_MDIOCTRL_OFFSET),
+                           XEL_MDIOCTRL_MDIOSTS_MASK, false, 2000);
+}
+
+static u32 phyread(struct eth_device *dev, u32 phyaddress, u32 registernum,
+                  u16 *data)
+{
+       if (mdio_wait(dev))
+               return 1;
+
+       u32 ctrl_reg = in_be32(dev->iobase + XEL_MDIOCTRL_OFFSET);
+       out_be32(dev->iobase + XEL_MDIOADDR_OFFSET,
+                XEL_MDIOADDR_OP_MASK |
+                ((phyaddress << XEL_MDIOADDR_PHYADR_SHIFT) | registernum));
+       out_be32(dev->iobase + XEL_MDIOCTRL_OFFSET,
+                ctrl_reg | XEL_MDIOCTRL_MDIOSTS_MASK);
+
+       if (mdio_wait(dev))
+               return 1;
+
+       /* Read data */
+       *data = in_be32(dev->iobase + XEL_MDIORD_OFFSET);
+       return 0;
+}
+
+static u32 phywrite(struct eth_device *dev, u32 phyaddress, u32 registernum,
+                   u16 data)
+{
+       if (mdio_wait(dev))
+               return 1;
+
+       /*
+        * Write the PHY address, register number and clear the OP bit in the
+        * MDIO Address register and then write the value into the MDIO Write
+        * Data register. Finally, set the Status bit in the MDIO Control
+        * register to start a MDIO write transaction.
+        */
+       u32 ctrl_reg = in_be32(dev->iobase + XEL_MDIOCTRL_OFFSET);
+       out_be32(dev->iobase + XEL_MDIOADDR_OFFSET,
+                ~XEL_MDIOADDR_OP_MASK &
+                ((phyaddress << XEL_MDIOADDR_PHYADR_SHIFT) | registernum));
+       out_be32(dev->iobase + XEL_MDIOWR_OFFSET, data);
+       out_be32(dev->iobase + XEL_MDIOCTRL_OFFSET,
+                ctrl_reg | XEL_MDIOCTRL_MDIOSTS_MASK);
+
+       if (mdio_wait(dev))
+               return 1;
+
+       return 0;
+}
+#endif
+
 static void emaclite_halt(struct eth_device *dev)
 {
        debug("eth_halt\n");
 }
 
+/* Use MII register 1 (MII status register) to detect PHY */
+#define PHY_DETECT_REG  1
+
+/* Mask used to verify certain PHY features (or register contents)
+ * in the register above:
+ *  0x1000: 10Mbps full duplex support
+ *  0x0800: 10Mbps half duplex support
+ *  0x0008: Auto-negotiation support
+ */
+#define PHY_DETECT_MASK 0x1808
+
+#if defined(CONFIG_MII) || defined(CONFIG_CMD_MII) || defined(CONFIG_PHYLIB)
+static int setup_phy(struct eth_device *dev)
+{
+       int i;
+       u16 phyreg;
+       struct xemaclite *emaclite = dev->priv;
+       struct phy_device *phydev;
+
+       u32 supported = SUPPORTED_10baseT_Half |
+                       SUPPORTED_10baseT_Full |
+                       SUPPORTED_100baseT_Half |
+                       SUPPORTED_100baseT_Full;
+
+       if (emaclite->phyaddr != -1) {
+               phyread(dev, emaclite->phyaddr, PHY_DETECT_REG, &phyreg);
+               if ((phyreg != 0xFFFF) &&
+                   ((phyreg & PHY_DETECT_MASK) == PHY_DETECT_MASK)) {
+                       /* Found a valid PHY address */
+                       debug("Default phy address %d is valid\n",
+                             emaclite->phyaddr);
+               } else {
+                       debug("PHY address is not setup correctly %d\n",
+                             emaclite->phyaddr);
+                       emaclite->phyaddr = -1;
+               }
+       }
+
+       if (emaclite->phyaddr == -1) {
+               /* detect the PHY address */
+               for (i = 31; i >= 0; i--) {
+                       phyread(dev, i, PHY_DETECT_REG, &phyreg);
+                       if ((phyreg != 0xFFFF) &&
+                           ((phyreg & PHY_DETECT_MASK) == PHY_DETECT_MASK)) {
+                               /* Found a valid PHY address */
+                               emaclite->phyaddr = i;
+                               debug("emaclite: Found valid phy address, %d\n",
+                                     i);
+                               break;
+                       }
+               }
+       }
+
+       /* interface - look at tsec */
+       phydev = phy_connect(emaclite->bus, emaclite->phyaddr, dev,
+                            PHY_INTERFACE_MODE_MII);
+       /*
+        * Phy can support 1000baseT but device NOT that's why phydev->supported
+        * must be setup for 1000baseT. phydev->advertising setups what speeds
+        * will be used for autonegotiation where 1000baseT must be disabled.
+        */
+       phydev->supported = supported | SUPPORTED_1000baseT_Half |
+                                               SUPPORTED_1000baseT_Full;
+       phydev->advertising = supported;
+       emaclite->phydev = phydev;
+       phy_config(phydev);
+       phy_startup(phydev);
+
+       if (!phydev->link) {
+               printf("%s: No link.\n", phydev->dev->name);
+               return 0;
+       }
+
+       /* Do not setup anything */
+       return 1;
+}
+#endif
+
 static int emaclite_init(struct eth_device *dev, bd_t *bis)
 {
        struct xemaclite *emaclite = dev->priv;
@@ -156,6 +351,13 @@ static int emaclite_init(struct eth_device *dev, bd_t *bis)
                out_be32 (dev->iobase + XEL_RSR_OFFSET + XEL_BUFFER_OFFSET,
                        XEL_RSR_RECV_IE_MASK);
 
+#if defined(CONFIG_MII) || defined(CONFIG_CMD_MII) || defined(CONFIG_PHYLIB)
+       out_be32(dev->iobase + XEL_MDIOCTRL_OFFSET, XEL_MDIOCTRL_MDIOEN_MASK);
+       if (in_be32(dev->iobase + XEL_MDIOCTRL_OFFSET) &
+                   XEL_MDIOCTRL_MDIOEN_MASK)
+               if (!setup_phy(dev))
+                       return -1;
+#endif
        debug("EmacLite Initialization complete\n");
        return 0;
 }
@@ -327,6 +529,28 @@ static int emaclite_recv(struct eth_device *dev)
 
 }
 
+#if defined(CONFIG_MII) || defined(CONFIG_CMD_MII) || defined(CONFIG_PHYLIB)
+static int emaclite_miiphy_read(const char *devname, uchar addr,
+                               uchar reg, ushort *val)
+{
+       u32 ret;
+       struct eth_device *dev = eth_get_dev();
+
+       ret = phyread(dev, addr, reg, val);
+       debug("emaclite: Read MII 0x%x, 0x%x, 0x%x\n", addr, reg, *val);
+       return ret;
+}
+
+static int emaclite_miiphy_write(const char *devname, uchar addr,
+                                uchar reg, ushort val)
+{
+       struct eth_device *dev = eth_get_dev();
+
+       debug("emaclite: Write MII 0x%x, 0x%x, 0x%x\n", addr, reg, val);
+       return phywrite(dev, addr, reg, val);
+}
+#endif
+
 int xilinx_emaclite_initialize(bd_t *bis, unsigned long base_addr,
                                                        int txpp, int rxpp)
 {
@@ -356,7 +580,21 @@ int xilinx_emaclite_initialize(bd_t *bis, unsigned long base_addr,
        dev->send = emaclite_send;
        dev->recv = emaclite_recv;
 
+#ifdef CONFIG_PHY_ADDR
+       emaclite->phyaddr = CONFIG_PHY_ADDR;
+#else
+       emaclite->phyaddr = -1;
+#endif
+
        eth_register(dev);
 
+#if defined(CONFIG_MII) || defined(CONFIG_CMD_MII) || defined(CONFIG_PHYLIB)
+       miiphy_register(dev->name, emaclite_miiphy_read, emaclite_miiphy_write);
+       emaclite->bus = miiphy_get_dev_by_name(dev->name);
+
+       out_be32(dev->iobase + XEL_MDIOCTRL_OFFSET,
+                XEL_MDIOCTRL_MDIOEN_MASK);
+#endif
+
        return 1;
 }