[U-Boot] [PATCH] net: phy: mv88e61xx: Revise as a PHY driver
Kevin Smith
kevin.smith at elecsyscorp.com
Wed Dec 16 00:16:17 CET 2015
The previous version of this driver just implemented a shell
command to manually configure the switch. It did not integrate
with the PHY infrastructure to allow a MAC to use it as its PHY.
This is pretty much a complete rewrite to allow this switch to
function as a PHY driver.
This version configures the switch to have a CPU connected over
an MII interface. It will enable PHY interfaces based on the
MV88E61XX_PHY_PORTS bitmask macro. The switch is configured to
allow PHY ports to only communicate to the CPU and the CPU to
communicate to all PHY ports. This allows the switch to be used
as a basic PHY on any/all ports.
This driver was developed with a mv88e6176 switch and a CPU
connected over SGMII on port 5. Other configurations should be
supported, but this is the only one that could be tested.
Signed-off-by: Kevin Smith <kevin.smith at elecsyscorp.com>
Cc: Joe Hershberger <joe.hershberger at ni.com>
Cc: Prafulla Wadaskar <prafulla at marvell.com>
Cc: Stefan Roese <sr at denx.de>
Cc: Marek Vasut <marex at denx.de>
---
drivers/net/phy/mv88e61xx.c | 899 +++++++++++++++++++++++---------------------
drivers/net/phy/mv88e61xx.h | 61 ---
drivers/net/phy/phy.c | 3 +
include/phy.h | 1 +
4 files changed, 484 insertions(+), 480 deletions(-)
delete mode 100644 drivers/net/phy/mv88e61xx.h
diff --git a/drivers/net/phy/mv88e61xx.c b/drivers/net/phy/mv88e61xx.c
index 302abe8..4b02f29 100644
--- a/drivers/net/phy/mv88e61xx.c
+++ b/drivers/net/phy/mv88e61xx.c
@@ -7,531 +7,592 @@
*/
#include <common.h>
+#include <errno.h>
+#include <miiphy.h>
#include <netdev.h>
-#include "mv88e61xx.h"
-/*
- * Uncomment either of the following line for local debug control;
- * otherwise global debug control will apply.
- */
-
-/* #undef DEBUG */
-/* #define DEBUG */
+#define PHY_AUTONEGOTIATE_TIMEOUT 5000
+
+#define PORT_COUNT 7
+#define CPU_PORT 5
+#define PORT_MASK ((1 << PORT_COUNT) - 1)
+
+/* Device addresses */
+#define DEVADDR_PORT(p) (0x10 + (p))
+#define DEVADDR_SERDES 0x0F
+#define DEVADDR_GLOBAL_1 0x1B
+#define DEVADDR_GLOBAL_2 0x1C
+
+/* Global registers */
+#define GLOBAL1_STATUS 0x00
+#define GLOBAL1_CONTROL 0x04
+
+/* Global 2 registers */
+#define GLOBAL2_REG_PHY_CMD 0x18
+#define GLOBAL2_REG_PHY_DATA 0x19
+
+/* Port registers */
+#define PORT_REG_STATUS 0x00
+#define PORT_REG_PHYS_CONTROL 0x01
+#define PORT_REG_SWITCH_ID 0x03
+#define PORT_REG_CONTROL 0x04
+#define PORT_REG_VLAN_MAP 0x06
+#define PORT_REG_VLAN_ID 0x07
+
+/* Phy registers */
+#define PHY_REG_STATUS1 0x11
+#define PHY_REG_PAGE 0x16
+
+/* Serdes registers */
+#define SERDES_REG_CONTROL_1 0x10
+
+/* Phy page numbers */
+#define PHY_PAGE_COPPER 0
+#define PHY_PAGE_SERDES 1
+
+#define PHY_WRITE_CMD 0x9400
+#define PHY_READ_CMD 0x9800
+
+/* PHY Status Register */
+#define PHY_REG_STATUS1_SPEED 0xc000
+#define PHY_REG_STATUS1_GBIT 0x8000
+#define PHY_REG_STATUS1_100 0x4000
+#define PHY_REG_STATUS1_DUPLEX 0x2000
+#define PHY_REG_STATUS1_SPDDONE 0x0800
+#define PHY_REG_STATUS1_LINK 0x0400
+#define PHY_REG_STATUS1_ENERGY 0x0010
+
+#ifndef CONFIG_MV88E61XX_PHY_PORTS
+#error Define CONFIG_MV88E61XX_PHY_PORTS to indicate which physical ports \
+ to activate
+#endif
-#ifdef CONFIG_MV88E61XX_MULTICHIP_ADRMODE
-/* Chip Address mode
- * The Switch support two modes of operation
- * 1. single chip mode and
- * 2. Multi-chip mode
- * Refer section 9.2 &9.3 in chip datasheet-02 for more details
- *
- * By default single chip mode is configured
- * multichip mode operation can be configured in board header
- */
-static int mv88e61xx_busychk_multic(char *name, u32 devaddr)
+/* Wait for the current SMI PHY command to complete */
+static int mv88e61xx_smi_wait(struct mii_dev *bus)
{
- u16 reg = 0;
- u32 timeout = MV88E61XX_PHY_TIMEOUT;
+ int reg;
+ u32 timeout = 100;
- /* Poll till SMIBusy bit is clear */
do {
- miiphy_read(name, devaddr, 0x0, ®);
- if (timeout-- == 0) {
- printf("SMI busy timeout\n");
- return -1;
- }
- } while (reg & (1 << 15));
- return 0;
-}
+ reg = bus->read(bus, DEVADDR_GLOBAL_2, MDIO_DEVAD_NONE,
+ GLOBAL2_REG_PHY_CMD);
+ if (reg >= 0 && (reg & (1 << 15)) == 0)
+ return 0;
-static void mv88e61xx_switch_write(char *name, u32 phy_adr,
- u32 reg_ofs, u16 data)
-{
- u16 mii_dev_addr;
+ mdelay(1);
+ } while (--timeout);
- /* command to read PHY dev address */
- if (miiphy_read(name, 0xEE, 0xEE, &mii_dev_addr)) {
- printf("Error..could not read PHY dev address\n");
- return;
- }
- mv88e61xx_busychk_multic(name, mii_dev_addr);
- /* Write data to Switch indirect data register */
- miiphy_write(name, mii_dev_addr, 0x1, data);
- /* Write command to Switch indirect command register (write) */
- miiphy_write(name, mii_dev_addr, 0x0,
- reg_ofs | (phy_adr << 5) | (1 << 10) | (1 << 12) | (1 <<
- 15));
+ puts("SMI busy timeout\n");
+ return -1;
}
-static void mv88e61xx_switch_read(char *name, u32 phy_adr,
- u32 reg_ofs, u16 *data)
+
+/* Write a PHY register indirectly */
+static int mv88e61xx_phy_write_bus(struct mii_dev *bus, int addr, int devad,
+ int reg, u16 data)
{
- u16 mii_dev_addr;
+ struct mii_dev *phys_bus;
- /* command to read PHY dev address */
- if (miiphy_read(name, 0xEE, 0xEE, &mii_dev_addr)) {
- printf("Error..could not read PHY dev address\n");
- return;
- }
- mv88e61xx_busychk_multic(name, mii_dev_addr);
- /* Write command to Switch indirect command register (read) */
- miiphy_write(name, mii_dev_addr, 0x0,
- reg_ofs | (phy_adr << 5) | (1 << 11) | (1 << 12) | (1 <<
- 15));
- mv88e61xx_busychk_multic(name, mii_dev_addr);
- /* Read data from Switch indirect data register */
- miiphy_read(name, mii_dev_addr, 0x1, data);
+ /* Retrieve the actual MII bus device from private data */
+ phys_bus = (struct mii_dev *)bus->priv;
+
+ phys_bus->write(phys_bus, DEVADDR_GLOBAL_2, devad,
+ GLOBAL2_REG_PHY_DATA, data);
+ phys_bus->write(phys_bus, DEVADDR_GLOBAL_2, devad,
+ GLOBAL2_REG_PHY_CMD, (PHY_WRITE_CMD | (addr << 5) | reg));
+
+ return mv88e61xx_smi_wait(phys_bus);
}
-#endif /* CONFIG_MV88E61XX_MULTICHIP_ADRMODE */
-/*
- * Convenience macros for switch device/port reads/writes
- * These macros output valid 'mv88e61xx' U_BOOT_CMDs
- */
-#ifndef DEBUG
-#define WR_SWITCH_REG wr_switch_reg
-#define RD_SWITCH_REG rd_switch_reg
-#define WR_SWITCH_PORT_REG(n, p, r, d) \
- WR_SWITCH_REG(n, (MV88E61XX_PRT_OFST+p), r, d)
-#define RD_SWITCH_PORT_REG(n, p, r, d) \
- RD_SWITCH_REG(n, (MV88E61XX_PRT_OFST+p), r, d)
-#else
-static void WR_SWITCH_REG(char *name, u32 dev_adr, u32 reg_ofs, u16 data)
+/* Read a PHY register indirectly */
+static int mv88e61xx_phy_read_bus(struct mii_dev *bus, int addr, int devad,
+ int reg)
{
- printf("mv88e61xx %s dev %02x reg %02x write %04x\n",
- name, dev_adr, reg_ofs, data);
- wr_switch_reg(name, dev_adr, reg_ofs, data);
+ struct mii_dev *phys_bus;
+ int res;
+
+ /* Retrieve the actual MII bus device from private data */
+ phys_bus = (struct mii_dev *)bus->priv;
+
+ phys_bus->write(phys_bus, DEVADDR_GLOBAL_2, devad, GLOBAL2_REG_PHY_CMD,
+ (PHY_READ_CMD | (addr << 5) | reg));
+
+ if (mv88e61xx_smi_wait(phys_bus))
+ return -1;
+
+ res = phys_bus->read(phys_bus, DEVADDR_GLOBAL_2, devad,
+ GLOBAL2_REG_PHY_DATA);
+
+ return res;
}
-static void RD_SWITCH_REG(char *name, u32 dev_adr, u32 reg_ofs, u16 *data)
+
+
+static int mv88e61xx_phy_read(struct phy_device *phydev, int addr,
+ int reg, u16 *val)
{
- rd_switch_reg(name, dev_adr, reg_ofs, data);
- printf("mv88e61xx %s dev %02x reg %02x read %04x\n",
- name, dev_adr, reg_ofs, *data);
+ int res;
+
+ res = mv88e61xx_phy_read_bus(phydev->bus, addr, MDIO_DEVAD_NONE, reg);
+ if (res < 0)
+ return -1;
+
+ *val = (u16)res;
+ return 0;
}
-static void WR_SWITCH_PORT_REG(char *name, u32 prt_adr, u32 reg_ofs,
- u16 data)
+
+
+static int mv88e61xx_phy_write(struct phy_device *phydev, int addr,
+ int reg, u16 val)
{
- printf("mv88e61xx %s port %02x reg %02x write %04x\n",
- name, prt_adr, reg_ofs, data);
- wr_switch_reg(name, (MV88E61XX_PRT_OFST+prt_adr), reg_ofs, data);
+ return mv88e61xx_phy_write_bus(phydev->bus, addr, MDIO_DEVAD_NONE,
+ reg, val);
}
-static void RD_SWITCH_PORT_REG(char *name, u32 prt_adr, u32 reg_ofs,
- u16 *data)
+
+
+static int mv88e61xx_switch_read(struct phy_device *phydev, u8 addr, u8 reg,
+ u16 *val)
{
- rd_switch_reg(name, (MV88E61XX_PRT_OFST+prt_adr), reg_ofs, data);
- printf("mv88e61xx %s port %02x reg %02x read %04x\n",
- name, prt_adr, reg_ofs, *data);
+ struct mii_dev *bus;
+ int res;
+
+ bus = phydev->bus->priv;
+
+ res = bus->read(bus, addr, MDIO_DEVAD_NONE, reg);
+ if (res < 0)
+ return -1;
+
+ *val = (u16)res;
+
+ return 0;
}
-#endif
-/*
- * Local functions to read/write registers on the switch PHYs.
- * NOTE! This goes through switch, not direct miiphy, writes and reads!
- */
-/*
- * Make sure SMIBusy bit cleared before another
- * SMI operation can take place
- */
-static int mv88e61xx_busychk(char *name)
+static int mv88e61xx_switch_write(struct phy_device *phydev, u8 addr, u8 reg,
+ u16 val)
{
- u16 reg = 0;
- u32 timeout = MV88E61XX_PHY_TIMEOUT;
- do {
- rd_switch_reg(name, MV88E61XX_GLB2REG_DEVADR,
- MV88E61XX_PHY_CMD, ®);
- if (timeout-- == 0) {
- printf("SMI busy timeout\n");
- return -1;
- }
- } while (reg & 1 << 15); /* busy mask */
- return 0;
+ struct mii_dev *bus;
+
+ bus = phydev->bus->priv;
+
+ return bus->write(bus, addr, MDIO_DEVAD_NONE, reg, val);
}
-static inline int mv88e61xx_switch_miiphy_write(char *name, u32 phy,
- u32 reg, u16 data)
+
+static int mv88e61xx_port_read(struct phy_device *phydev, u8 port, u8 reg,
+ u16 *val)
{
- /* write switch data reg then cmd reg then check completion */
- wr_switch_reg(name, MV88E61XX_GLB2REG_DEVADR, MV88E61XX_PHY_DATA,
- data);
- wr_switch_reg(name, MV88E61XX_GLB2REG_DEVADR, MV88E61XX_PHY_CMD,
- (MV88E61XX_PHY_WRITE_CMD | (phy << 5) | reg));
- return mv88e61xx_busychk(name);
+ return mv88e61xx_switch_read(phydev, DEVADDR_PORT(port), reg, val);
}
-static inline int mv88e61xx_switch_miiphy_read(char *name, u32 phy,
- u32 reg, u16 *data)
+
+static int mv88e61xx_port_write(struct phy_device *phydev, u8 port, u8 reg,
+ u16 val)
{
- /* write switch cmd reg, check for completion */
- wr_switch_reg(name, MV88E61XX_GLB2REG_DEVADR, MV88E61XX_PHY_CMD,
- (MV88E61XX_PHY_READ_CMD | (phy << 5) | reg));
- if (mv88e61xx_busychk(name))
- return -1;
- /* read switch data reg and return success */
- rd_switch_reg(name, MV88E61XX_GLB2REG_DEVADR, MV88E61XX_PHY_DATA, data);
- return 0;
+ return mv88e61xx_switch_write(phydev, DEVADDR_PORT(port), reg, val);
}
-/*
- * Convenience macros for switch PHY reads/writes
- */
-#ifndef DEBUG
-#define WR_SWITCH_PHY_REG mv88e61xx_switch_miiphy_write
-#define RD_SWITCH_PHY_REG mv88e61xx_switch_miiphy_read
-#else
-static inline int WR_SWITCH_PHY_REG(char *name, u32 phy_adr,
- u32 reg_ofs, u16 data)
+static int mv88e61xx_set_page(struct phy_device *phydev, u8 addr, u8 page)
{
- int r = mv88e61xx_switch_miiphy_write(name, phy_adr, reg_ofs, data);
- if (r)
- printf("** ERROR writing mv88e61xx %s phy %02x reg %02x\n",
- name, phy_adr, reg_ofs);
- else
- printf("mv88e61xx %s phy %02x reg %02x write %04x\n",
- name, phy_adr, reg_ofs, data);
- return r;
+ return mv88e61xx_phy_write(phydev, addr, PHY_REG_PAGE, page);
}
-static inline int RD_SWITCH_PHY_REG(char *name, u32 phy_adr,
- u32 reg_ofs, u16 *data)
+
+
+static int mv88e61xx_parse_status(struct phy_device *phydev)
{
- int r = mv88e61xx_switch_miiphy_read(name, phy_adr, reg_ofs, data);
- if (r)
- printf("** ERROR reading mv88e61xx %s phy %02x reg %02x\n",
- name, phy_adr, reg_ofs);
+ unsigned int speed;
+ unsigned int mii_reg;
+
+ mii_reg = phy_read(phydev, MDIO_DEVAD_NONE, PHY_REG_STATUS1);
+
+ if ((mii_reg & PHY_REG_STATUS1_LINK) &&
+ !(mii_reg & PHY_REG_STATUS1_SPDDONE)) {
+ int i = 0;
+
+ puts("Waiting for PHY realtime link");
+ while (!(mii_reg & PHY_REG_STATUS1_SPDDONE)) {
+ /* Timeout reached ? */
+ if (i > PHY_AUTONEGOTIATE_TIMEOUT) {
+ puts(" TIMEOUT !\n");
+ phydev->link = 0;
+ break;
+ }
+
+ if ((i++ % 1000) == 0)
+ putc('.');
+ udelay(1000);
+ mii_reg = phy_read(phydev, MDIO_DEVAD_NONE,
+ PHY_REG_STATUS1);
+ }
+ puts(" done\n");
+ udelay(500000); /* another 500 ms (results in faster booting) */
+ } else {
+ if (mii_reg & PHY_REG_STATUS1_LINK)
+ phydev->link = 1;
+ else
+ phydev->link = 0;
+ }
+
+ if (mii_reg & PHY_REG_STATUS1_DUPLEX)
+ phydev->duplex = DUPLEX_FULL;
else
- printf("mv88e61xx %s phy %02x reg %02x read %04x\n",
- name, phy_adr, reg_ofs, *data);
- return r;
+ phydev->duplex = DUPLEX_HALF;
+
+ speed = mii_reg & PHY_REG_STATUS1_SPEED;
+
+ switch (speed) {
+ case PHY_REG_STATUS1_GBIT:
+ phydev->speed = SPEED_1000;
+ break;
+ case PHY_REG_STATUS1_100:
+ phydev->speed = SPEED_100;
+ break;
+ default:
+ phydev->speed = SPEED_10;
+ break;
+ }
+
+ return 0;
}
-#endif
-static void mv88e61xx_port_vlan_config(struct mv88e61xx_config *swconfig)
+
+static int mv88e61xx_switch_reset(struct phy_device *phydev)
{
- u32 prt;
+ int time;
+ int res;
u16 reg;
- char *name = swconfig->name;
- u32 port_mask = swconfig->ports_enabled;
-
- /* apply internal vlan config */
- for (prt = 0; prt < MV88E61XX_MAX_PORTS_NUM; prt++) {
- /* only for enabled ports */
- if ((1 << prt) & port_mask) {
- /* take vlan map from swconfig */
- u8 vlanmap = swconfig->vlancfg[prt];
- /* remove disabled ports from vlan map */
- vlanmap &= swconfig->ports_enabled;
- /* apply vlan map to port */
- RD_SWITCH_PORT_REG(name, prt,
- MV88E61XX_PRT_VMAP_REG, ®);
- reg &= ~((1 << MV88E61XX_MAX_PORTS_NUM) - 1);
- reg |= vlanmap;
- WR_SWITCH_PORT_REG(name, prt,
- MV88E61XX_PRT_VMAP_REG, reg);
- }
+ u8 port;
+
+ /* Disable all ports */
+ for (port = 0; port < PORT_COUNT; port++) {
+ if (mv88e61xx_port_read(phydev, port, PORT_REG_CONTROL, ®))
+ return -1;
+ reg &= ~0x3;
+ if (mv88e61xx_port_write(phydev, port, PORT_REG_CONTROL, reg))
+ return -1;
+ }
+
+ /* Wait 2 ms for queues to drain */
+ udelay(2000);
+
+ /* Reset switch */
+ if (mv88e61xx_switch_read(phydev, DEVADDR_GLOBAL_1,
+ GLOBAL1_CONTROL, ®))
+ return -1;
+ reg |= 0x8000;
+ if (mv88e61xx_switch_write(phydev, DEVADDR_GLOBAL_1,
+ GLOBAL1_CONTROL, reg))
+ return -1;
+
+ /* Wait up to 1 second for switch reset complete */
+ for (time = 1000; time; time--) {
+ res = mv88e61xx_switch_read(phydev, DEVADDR_GLOBAL_1,
+ GLOBAL1_CONTROL, ®);
+ if (res == 0 && ((reg & 0x8000) == 0))
+ break;
+ udelay(1000);
}
+ if (!time)
+ return -1;
+
+ return 0;
}
-/*
- * Power up the specified port and reset PHY
- */
-static int mv88361xx_powerup(struct mv88e61xx_config *swconfig, u32 phy)
+
+static int mv88e61xx_serdes_init(struct phy_device *phydev)
{
- char *name = swconfig->name;
+ u16 val;
+
+ if (mv88e61xx_set_page(phydev, DEVADDR_SERDES, 1))
+ return -1;
+
+ if (mv88e61xx_phy_read(phydev, DEVADDR_SERDES, MII_BMCR, &val))
+ return -1;
+
+ val &= ~(BMCR_PDOWN | BMCR_ANENABLE);
+ val |= BMCR_RESET;
- /* Write Copper Specific control reg1 (0x10) for-
- * Enable Phy power up
- * Energy Detect on (sense&Xmit NLP Periodically
- * reset other settings default
- */
- if (WR_SWITCH_PHY_REG(name, phy, 0x10, 0x3360))
+ if (mv88e61xx_phy_write(phydev, DEVADDR_SERDES, MII_BMCR, val))
return -1;
- /* Write PHY ctrl reg (0x0) to apply
- * Phy reset (set bit 15 low)
- * reset other default values
- */
- if (WR_SWITCH_PHY_REG(name, phy, 0x00, 0x9140))
+ /* 2 MACs cannot auto-negotiate, so we must force the link good */
+ if (mv88e61xx_phy_read(phydev, DEVADDR_SERDES, SERDES_REG_CONTROL_1,
+ &val))
+ return -1;
+ val |= 0x0400;
+ if (mv88e61xx_phy_write(phydev, DEVADDR_SERDES, SERDES_REG_CONTROL_1,
+ val))
return -1;
return 0;
}
-/*
- * Default Setup for LED[0]_Control (ref: Table 46 Datasheet-3)
- * is set to "On-1000Mb/s Link, Off Else"
- * This function sets it to "On-Link, Blink-Activity, Off-NoLink"
- *
- * This is optional settings may be needed on some boards
- * to setup PHY LEDs default configuration to detect 10/100/1000Mb/s
- * Link status
- */
-static int mv88361xx_led_init(struct mv88e61xx_config *swconfig, u32 phy)
+
+static int mv88e61xx_port_enable(struct phy_device *phydev, u8 port)
{
- char *name = swconfig->name;
+ u16 val;
- if (swconfig->led_init != MV88E61XX_LED_INIT_EN)
- return 0;
+ if (mv88e61xx_port_read(phydev, port, PORT_REG_CONTROL, &val))
+ return -1;
+ val |= 0x03;
+ if (mv88e61xx_port_write(phydev, port, PORT_REG_CONTROL, val))
+ return -1;
+
+ return 0;
+}
+
+
+static int mv88e61xx_port_set_vlan(struct phy_device *phydev, u8 port,
+ u8 mask)
+{
+ u16 val;
+
+ /* Set VID to port number plus one */
+ if (mv88e61xx_port_read(phydev, port, PORT_REG_VLAN_ID, &val))
+ return -1;
+ val &= ~((1 << 12) - 1);
+ val |= port + 1;
+ if (mv88e61xx_port_write(phydev, port, PORT_REG_VLAN_ID, val))
+ return -1;
+
+ /* Set VID mask */
+ if (mv88e61xx_port_read(phydev, port, PORT_REG_VLAN_MAP, &val))
+ return -1;
+ val &= ~PORT_MASK;
+ val |= (mask & PORT_MASK);
+ if (mv88e61xx_port_write(phydev, port, PORT_REG_VLAN_MAP, val))
+ return -1;
+
+ return 0;
+}
- /* set page address to 3 */
- if (WR_SWITCH_PHY_REG(name, phy, 0x16, 0x0003))
+
+static int mv88e61xx_set_cpu_port(struct phy_device *phydev)
+{
+ u16 val;
+
+ /* Set CPUDest */
+ if (mv88e61xx_switch_read(phydev, DEVADDR_GLOBAL_1, 0x1A, &val))
+ return -1;
+ val &= ~(0xf << 4);
+ val |= (CPU_PORT << 4);
+ if (mv88e61xx_switch_write(phydev, DEVADDR_GLOBAL_1, 0x1A, val))
return -1;
- /*
- * set LED Func Ctrl reg
- * value 0x0001 = LED[0] On-Link, Blink-Activity, Off-NoLink
- */
- if (WR_SWITCH_PHY_REG(name, phy, 0x10, 0x0001))
+ /* Enable CPU port */
+ if (mv88e61xx_port_enable(phydev, CPU_PORT))
return -1;
- /* set page address to 0 */
- if (WR_SWITCH_PHY_REG(name, phy, 0x16, 0x0000))
+ /* Allow CPU to route to any port */
+ if (mv88e61xx_port_set_vlan(phydev, CPU_PORT,
+ PORT_MASK & ~(1 << CPU_PORT)))
return -1;
return 0;
}
-/*
- * Reverse Transmit polarity for Media Dependent Interface
- * Pins (MDIP) bits in Copper Specific Control Register 3
- * (Page 0, Reg 20 for each phy (except cpu port)
- * Reference: Section 1.1 Switch datasheet-3
- *
- * This is optional settings may be needed on some boards
- * for PHY<->magnetics h/w tuning
- */
-static int mv88361xx_reverse_mdipn(struct mv88e61xx_config *swconfig, u32 phy)
+
+static int mv88e61xx_switch_init(struct phy_device *phydev)
{
- char *name = swconfig->name;
+ static int init;
+ u16 val;
- if (swconfig->mdip != MV88E61XX_MDIP_REVERSE)
+ if (init)
return 0;
- /*Reverse MDIP/N[3:0] bits */
- if (WR_SWITCH_PHY_REG(name, phy, 0x14, 0x000f))
+ /* Read switch ID */
+ if (mv88e61xx_port_read(phydev, 0, 0x03, &val))
+ return -1;
+
+ if (mv88e61xx_switch_reset(phydev))
+ return -1;
+
+ if (mv88e61xx_set_cpu_port(phydev))
return -1;
+ if (mv88e61xx_serdes_init(phydev))
+ return -1;
+
+ init = 1;
+
return 0;
}
-/*
- * Marvell 88E61XX Switch initialization
- */
-int mv88e61xx_switch_initialize(struct mv88e61xx_config *swconfig)
+
+static int mv88e61xx_phy_enable(struct phy_device *phydev, u8 phy)
{
- u32 prt;
- u16 reg;
- char *idstr;
- char *name = swconfig->name;
- int time;
+ u16 val;
- if (miiphy_set_current_dev(name)) {
- printf("%s failed\n", __FUNCTION__);
+ if (mv88e61xx_phy_read(phydev, phy, MII_BMCR, &val))
+ return -1;
+ val &= ~(BMCR_PDOWN);
+ if (mv88e61xx_phy_write(phydev, phy, MII_BMCR, val))
return -1;
- }
- if (!(swconfig->cpuport & ((1 << 4) | (1 << 5)))) {
- swconfig->cpuport = (1 << 5);
- printf("Invalid cpu port config, using default port5\n");
- }
+ return 0;
+}
- RD_SWITCH_PORT_REG(name, 0, MII_PHYSID2, ®);
- switch (reg &= 0xfff0) {
- case 0x1610:
- idstr = "88E6161";
- break;
- case 0x1650:
- idstr = "88E6165";
- break;
- case 0x1210:
- idstr = "88E6123";
- /* ports 2,3,4 not available */
- swconfig->ports_enabled &= 0x023;
- break;
- default:
- /* Could not detect switch id */
- idstr = "88E61??";
- break;
- }
- /* be sure all ports are disabled */
- for (prt = 0; prt < MV88E61XX_MAX_PORTS_NUM; prt++) {
- RD_SWITCH_PORT_REG(name, prt, MV88E61XX_PRT_CTRL_REG, ®);
- reg &= ~0x3;
- WR_SWITCH_PORT_REG(name, prt, MV88E61XX_PRT_CTRL_REG, reg);
- }
+static int mv88e61xx_phy_setup(struct phy_device *phydev, u8 phy)
+{
+ u16 val;
- /* wait 2 ms for queues to drain */
- udelay(2000);
+ if (mv88e61xx_phy_read(phydev, phy, 0x10, &val))
+ return -1;
+ val |= (0x3 << 8);
+ if (mv88e61xx_phy_write(phydev, phy, 0x10, val))
+ return -1;
- /* reset switch */
- RD_SWITCH_REG(name, MV88E61XX_GLBREG_DEVADR, MV88E61XX_SGCR, ®);
- reg |= 0x8000;
- WR_SWITCH_REG(name, MV88E61XX_GLBREG_DEVADR, MV88E61XX_SGCR, reg);
+ return 0;
+}
- /* wait up to 1 second for switch reset complete */
- for (time = 1000; time; time--) {
- RD_SWITCH_REG(name, MV88E61XX_GLBREG_DEVADR, MV88E61XX_SGSR,
- ®);
- if ((reg & 0xc800) == 0xc800)
- break;
- udelay(1000);
- }
- if (!time)
+
+static int mv88e61xx_phy_config_port(struct phy_device *phydev, u8 phy)
+{
+ if (mv88e61xx_port_enable(phydev, phy))
+ return -1;
+ if (mv88e61xx_port_set_vlan(phydev, phy, 1 << 5))
return -1;
- /* Port based VLANs configuration */
- mv88e61xx_port_vlan_config(swconfig);
-
- if (swconfig->rgmii_delay == MV88E61XX_RGMII_DELAY_EN) {
- /*
- * Enable RGMII delay on Tx and Rx for CPU port
- * Ref: sec 9.5 of chip datasheet-02
- */
- /*Force port link down */
- WR_SWITCH_PORT_REG(name, 5, MV88E61XX_PCS_CTRL_REG, 0x10);
- /* configure port RGMII delay */
- WR_SWITCH_PORT_REG(name, 4,
- MV88E61XX_RGMII_TIMECTRL_REG, 0x81e7);
- RD_SWITCH_PORT_REG(name, 5,
- MV88E61XX_RGMII_TIMECTRL_REG, ®);
- WR_SWITCH_PORT_REG(name, 5,
- MV88E61XX_RGMII_TIMECTRL_REG, reg | 0x18);
- WR_SWITCH_PORT_REG(name, 4,
- MV88E61XX_RGMII_TIMECTRL_REG, 0xc1e7);
- /* Force port to RGMII FDX 1000Base then up */
- WR_SWITCH_PORT_REG(name, 5, MV88E61XX_PCS_CTRL_REG, 0x1e);
- WR_SWITCH_PORT_REG(name, 5, MV88E61XX_PCS_CTRL_REG, 0x3e);
- }
+ return 0;
+}
- for (prt = 0; prt < MV88E61XX_MAX_PORTS_NUM; prt++) {
-
- /* configure port's PHY */
- if (!((1 << prt) & swconfig->cpuport)) {
- /* port 4 has phy 6, not 4 */
- int phy = (prt == 4) ? 6 : prt;
- if (mv88361xx_powerup(swconfig, phy))
- return -1;
- if (mv88361xx_reverse_mdipn(swconfig, phy))
- return -1;
- if (mv88361xx_led_init(swconfig, phy))
- return -1;
- }
- /* set port VID to port+1 except for cpu port */
- if (!((1 << prt) & swconfig->cpuport)) {
- RD_SWITCH_PORT_REG(name, prt,
- MV88E61XX_PRT_VID_REG, ®);
- WR_SWITCH_PORT_REG(name, prt,
- MV88E61XX_PRT_VID_REG,
- (reg & ~1023) | (prt+1));
- }
+static int mv88e61xx_probe(struct phy_device *phydev)
+{
+ struct mii_dev *mii_dev;
- /*Program port state */
- RD_SWITCH_PORT_REG(name, prt,
- MV88E61XX_PRT_CTRL_REG, ®);
- WR_SWITCH_PORT_REG(name, prt,
- MV88E61XX_PRT_CTRL_REG,
- reg | (swconfig->portstate & 0x03));
+ /* This device requires indirect reads/writes to the PHY registers
+ * which the generic PHY code can't handle. Make a fake MII device to
+ * handle reads/writes */
+ mii_dev = mdio_alloc();
+ if (!mii_dev)
+ return -1;
- }
+ /* Store the actual bus in the fake mii device */
+ mii_dev->priv = phydev->bus;
+ strncpy(mii_dev->name, "mv88e61xx_protocol", sizeof(mii_dev->name));
+ mii_dev->read = mv88e61xx_phy_read_bus;
+ mii_dev->write = mv88e61xx_phy_write_bus;
+
+ /* Replace the bus with the fake device */
+ phydev->bus = mii_dev;
- printf("%s Initialized on %s\n", idstr, name);
return 0;
}
-#ifdef CONFIG_MV88E61XX_CMD
-static int
-do_switch(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[])
+
+static int mv88e61xx_phy_config(struct phy_device *phydev)
{
- char *name, *endp;
- int write = 0;
- enum { dev, prt, phy } target = dev;
- u32 addrlo, addrhi, addr;
- u32 reglo, reghi, reg;
- u16 data, rdata;
+ int mac_addr;
+ int i;
- if (argc < 7)
+ if (mv88e61xx_switch_init(phydev))
return -1;
- name = argv[1];
+ mac_addr = phydev->addr;
- if (strcmp(argv[2], "phy") == 0)
- target = phy;
- else if (strcmp(argv[2], "port") == 0)
- target = prt;
- else if (strcmp(argv[2], "dev") != 0)
- return 1;
+ for (i = 0; i < PORT_COUNT; i++) {
+ if ((1 << i) & CONFIG_MV88E61XX_PHY_PORTS) {
+ phydev->addr = i;
+ mv88e61xx_phy_enable(phydev, i);
+ mv88e61xx_phy_setup(phydev, i);
+ mv88e61xx_phy_config_port(phydev, i);
- addrlo = simple_strtoul(argv[3], &endp, 16);
-
- if (!*endp) {
- addrhi = addrlo;
- } else {
- while (*endp < '0' || *endp > '9')
- endp++;
- addrhi = simple_strtoul(endp, NULL, 16);
+ genphy_config_aneg(phydev);
+ phy_reset(phydev);
+ }
}
- reglo = simple_strtoul(argv[5], &endp, 16);
- if (!*endp) {
- reghi = reglo;
- } else {
- while (*endp < '0' || *endp > '9')
- endp++;
- reghi = simple_strtoul(endp, NULL, 16);
- }
+ phydev->addr = mac_addr;
- if (strcmp(argv[6], "write") == 0)
- write = 1;
- else if (strcmp(argv[6], "read") != 0)
- return 1;
-
- data = simple_strtoul(argv[7], NULL, 16);
-
- for (addr = addrlo; addr <= addrhi; addr++) {
- for (reg = reglo; reg <= reghi; reg++) {
- if (write) {
- if (target == phy)
- mv88e61xx_switch_miiphy_write(
- name, addr, reg, data);
- else if (target == prt)
- wr_switch_reg(name,
- addr+MV88E61XX_PRT_OFST,
- reg, data);
- else
- wr_switch_reg(name, addr, reg, data);
- } else {
- if (target == phy)
- mv88e61xx_switch_miiphy_read(
- name, addr, reg, &rdata);
- else if (target == prt)
- rd_switch_reg(name,
- addr+MV88E61XX_PRT_OFST,
- reg, &rdata);
- else
- rd_switch_reg(name, addr, reg, &rdata);
- printf("%s %s %s %02x %s %02x %s %04x\n",
- argv[0], argv[1], argv[2], addr,
- argv[4], reg, argv[6], rdata);
- if (write && argc == 7 && rdata != data)
- return 1;
- }
+ return 0;
+}
+
+
+static int mv88e61xx_phy_is_connected(struct phy_device *phydev)
+{
+ u16 val;
+
+ if (mv88e61xx_phy_read(phydev, phydev->addr, PHY_REG_STATUS1, &val))
+ return 0;
+
+ /* After reset, the energy detect signal remains high for a few seconds
+ * regardless of whether a cable is connected. This function will
+ * return false positives during this time. */
+ return (val & PHY_REG_STATUS1_ENERGY) == 0;
+}
+
+
+static int mv88e61xx_phy_startup(struct phy_device *phydev)
+{
+ int i;
+ int mac_addr;
+ int link = 0;
+
+ mac_addr = phydev->addr;
+ for (i = 0; i < PORT_COUNT; i++) {
+ if ((1 << i) & CONFIG_MV88E61XX_PHY_PORTS) {
+ phydev->addr = i;
+ if (!mv88e61xx_phy_is_connected(phydev))
+ continue;
+ genphy_update_link(phydev);
+ mv88e61xx_parse_status(phydev);
+ link = (link || phydev->link);
}
}
+ phydev->addr = mac_addr;
+ phydev->link = link;
+
+ /* Configure MAC to the speed of the MII interface */
+ phydev->speed = SPEED_1000;
+ phydev->duplex = DUPLEX_FULL;
+
+ return 0;
+}
+
+
+static struct phy_driver mv88e61xx_driver = {
+ .name = "Marvell MV88E6176",
+ .uid = 0x01410eb1,
+ .mask = 0xfffffff0,
+ .features = PHY_GBIT_FEATURES,
+ .probe = mv88e61xx_probe,
+ .config = mv88e61xx_phy_config,
+ .startup = mv88e61xx_phy_startup,
+ .shutdown = &genphy_shutdown,
+};
+
+
+int phy_mv88e61xx_init(void)
+{
+ phy_register(&mv88e61xx_driver);
+
return 0;
}
-U_BOOT_CMD(mv88e61xx, 8, 0, do_switch,
- "Read or write mv88e61xx switch registers",
- "<ethdevice> dev|port|phy <addr> reg <reg> write <data>\n"
- "<ethdevice> dev|port|phy <addr> reg <reg> read [<data>]\n"
- " - read/write switch device, port or phy at (addr,reg)\n"
- " addr=0..0x1C for dev, 0..5 for port or phy.\n"
- " reg=0..0x1F.\n"
- " data=0..0xFFFF (tested if present against actual read).\n"
- " All numeric parameters are assumed to be hex.\n"
- " <addr> and <<reg> arguments can be ranges (x..y)"
-);
-#endif /* CONFIG_MV88E61XX_CMD */
+
+/* Overload weak get_phy_id definition since we need non-standard functions
+ * to read PHY registers */
+int get_phy_id(struct mii_dev *bus, int addr, int devad, u32 *phy_id)
+{
+ struct mii_dev fake_bus;
+ int phy_reg;
+
+ fake_bus.priv = bus;
+
+ phy_reg = mv88e61xx_phy_read_bus(&fake_bus, addr, devad, MII_PHYSID1);
+ if (phy_reg < 0)
+ return -EIO;
+
+ *phy_id = phy_reg << 16;
+
+ phy_reg = mv88e61xx_phy_read_bus(&fake_bus, addr, devad, MII_PHYSID2);
+ if (phy_reg < 0)
+ return -EIO;
+
+ *phy_id |= (phy_reg & 0xffff);
+
+ return 0;
+}
diff --git a/drivers/net/phy/mv88e61xx.h b/drivers/net/phy/mv88e61xx.h
deleted file mode 100644
index 9c62e4a..0000000
--- a/drivers/net/phy/mv88e61xx.h
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * (C) Copyright 2009
- * Marvell Semiconductor <www.marvell.com>
- * Prafulla Wadaskar <prafulla at marvell.com>
- *
- * SPDX-License-Identifier: GPL-2.0+
- */
-
-#ifndef _MV88E61XX_H
-#define _MV88E61XX_H
-
-#include <miiphy.h>
-
-#define MV88E61XX_CPU_PORT 0x5
-
-#define MV88E61XX_PHY_TIMEOUT 100000
-
-/* port dev-addr (= port + 0x10) */
-#define MV88E61XX_PRT_OFST 0x10
-/* port registers */
-#define MV88E61XX_PCS_CTRL_REG 0x1
-#define MV88E61XX_PRT_CTRL_REG 0x4
-#define MV88E61XX_PRT_VMAP_REG 0x6
-#define MV88E61XX_PRT_VID_REG 0x7
-#define MV88E61XX_RGMII_TIMECTRL_REG 0x1A
-
-/* global registers dev-addr */
-#define MV88E61XX_GLBREG_DEVADR 0x1B
-/* global registers */
-#define MV88E61XX_SGSR 0x00
-#define MV88E61XX_SGCR 0x04
-
-/* global 2 registers dev-addr */
-#define MV88E61XX_GLB2REG_DEVADR 0x1C
-/* global 2 registers */
-#define MV88E61XX_PHY_CMD 0x18
-#define MV88E61XX_PHY_DATA 0x19
-/* global 2 phy commands */
-#define MV88E61XX_PHY_WRITE_CMD 0x9400
-#define MV88E61XX_PHY_READ_CMD 0x9800
-
-#define MV88E61XX_BUSY_OFST 15
-#define MV88E61XX_MODE_OFST 12
-#define MV88E61XX_OP_OFST 10
-#define MV88E61XX_ADDR_OFST 5
-
-#ifdef CONFIG_MV88E61XX_MULTICHIP_ADRMODE
-static int mv88e61xx_busychk_multic(char *name, u32 devaddr);
-static void mv88e61xx_switch_write(char *name, u32 phy_adr,
- u32 reg_ofs, u16 data);
-static void mv88e61xx_switch_read(char *name, u32 phy_adr,
- u32 reg_ofs, u16 *data);
-#define wr_switch_reg mv88e61xx_switch_write
-#define rd_switch_reg mv88e61xx_switch_read
-#else
-/* switch appears a s simple PHY and can thus use miiphy */
-#define wr_switch_reg miiphy_write
-#define rd_switch_reg miiphy_read
-#endif /* CONFIG_MV88E61XX_MULTICHIP_ADRMODE */
-
-#endif /* _MV88E61XX_H */
diff --git a/drivers/net/phy/phy.c b/drivers/net/phy/phy.c
index 51b5746..077c9f5 100644
--- a/drivers/net/phy/phy.c
+++ b/drivers/net/phy/phy.c
@@ -446,6 +446,9 @@ static LIST_HEAD(phy_drivers);
int phy_init(void)
{
+#ifdef CONFIG_MV88E61XX_SWITCH
+ phy_mv88e61xx_init();
+#endif
#ifdef CONFIG_PHY_AQUANTIA
phy_aquantia_init();
#endif
diff --git a/include/phy.h b/include/phy.h
index 66cf61b..7cec9b8 100644
--- a/include/phy.h
+++ b/include/phy.h
@@ -238,6 +238,7 @@ int gen10g_startup(struct phy_device *phydev);
int gen10g_shutdown(struct phy_device *phydev);
int gen10g_discover_mmds(struct phy_device *phydev);
+int phy_mv88e61xx_init(void);
int phy_aquantia_init(void);
int phy_atheros_init(void);
int phy_broadcom_init(void);
--
2.4.6
More information about the U-Boot
mailing list