net: emaclite: Add MDIO support to driver
Add MDIO support before move to DM.
Signed-off-by: Michal Simek <michal.simek@xilinx.com>
Acked-by: Joe Hershberger <joe.hershberger@ni.com>
diff --git a/drivers/net/xilinx_emaclite.c b/drivers/net/xilinx_emaclite.c
index d3d40b1..f5e4c02 100644
--- a/drivers/net/xilinx_emaclite.c
+++ b/drivers/net/xilinx_emaclite.c
@@ -10,9 +10,13 @@
#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
@@ -46,11 +50,36 @@
/* 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 @@
*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 @@
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 @@
}
+#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 @@
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;
}