mirror of
https://github.com/AsahiLinux/u-boot
synced 2024-11-25 06:00:43 +00:00
01e7dd050f
Add a DSA driver for the MV88E6xxx compatible Ethernet switches. Cc: Marek Behún <marek.behun@nic.cz> Cc: Vladimir Oltean <vladimir.oltean@nxp.com> Signed-off-by: Tim Harvey <tharvey@gateworks.com> Reviewed-by: Vladimir Oltean <vladimir.oltean@nxp.com> Reviewed-by: Fabio Estevam <festevam@denx.de>
755 lines
20 KiB
C
755 lines
20 KiB
C
// SPDX-License-Identifier: GPL-2.0+
|
|
/*
|
|
* (C) Copyright 2022
|
|
* Gateworks Corporation <www.gateworks.com>
|
|
* Tim Harvey <tharvey@gateworks.com>
|
|
*
|
|
* (C) Copyright 2015
|
|
* Elecsys Corporation <www.elecsyscorp.com>
|
|
* Kevin Smith <kevin.smith@elecsyscorp.com>
|
|
*
|
|
* Original driver:
|
|
* (C) Copyright 2009
|
|
* Marvell Semiconductor <www.marvell.com>
|
|
* Prafulla Wadaskar <prafulla@marvell.com>
|
|
*/
|
|
|
|
/*
|
|
* DSA driver for mv88e6xxx ethernet switches.
|
|
*
|
|
* This driver configures the mv88e6xxx for basic use as a DSA switch.
|
|
*
|
|
* This driver was adapted from drivers/net/phy/mv88e61xx and tested
|
|
* on the mv88e6176 via an SGMII interface.
|
|
*/
|
|
|
|
#include <common.h>
|
|
#include <dm/device.h>
|
|
#include <dm/device_compat.h>
|
|
#include <dm/device-internal.h>
|
|
#include <dm/lists.h>
|
|
#include <dm/of_extra.h>
|
|
#include <linux/delay.h>
|
|
#include <miiphy.h>
|
|
#include <net/dsa.h>
|
|
|
|
/* Device addresses */
|
|
#define DEVADDR_PHY(p) (p)
|
|
#define DEVADDR_SERDES 0x0F
|
|
|
|
/* SMI indirection registers for multichip addressing mode */
|
|
#define SMI_CMD_REG 0x00
|
|
#define SMI_DATA_REG 0x01
|
|
|
|
/* Global registers */
|
|
#define GLOBAL1_STATUS 0x00
|
|
#define GLOBAL1_CTRL 0x04
|
|
|
|
/* Global 2 registers */
|
|
#define GLOBAL2_REG_PHY_CMD 0x18
|
|
#define GLOBAL2_REG_PHY_DATA 0x19
|
|
#define GLOBAL2_REG_SCRATCH 0x1A
|
|
|
|
/* Port registers */
|
|
#define PORT_REG_STATUS 0x00
|
|
#define PORT_REG_PHYS_CTRL 0x01
|
|
#define PORT_REG_SWITCH_ID 0x03
|
|
#define PORT_REG_CTRL 0x04
|
|
|
|
/* Phy registers */
|
|
#define PHY_REG_PAGE 0x16
|
|
|
|
/* Phy page numbers */
|
|
#define PHY_PAGE_COPPER 0
|
|
#define PHY_PAGE_SERDES 1
|
|
|
|
/* Register fields */
|
|
#define GLOBAL1_CTRL_SWRESET BIT(15)
|
|
|
|
#define PORT_REG_STATUS_SPEED_SHIFT 8
|
|
#define PORT_REG_STATUS_SPEED_10 0
|
|
#define PORT_REG_STATUS_SPEED_100 1
|
|
#define PORT_REG_STATUS_SPEED_1000 2
|
|
|
|
#define PORT_REG_STATUS_CMODE_MASK 0xF
|
|
#define PORT_REG_STATUS_CMODE_SGMII 0xa
|
|
#define PORT_REG_STATUS_CMODE_1000BASE_X 0x9
|
|
#define PORT_REG_STATUS_CMODE_100BASE_X 0x8
|
|
#define PORT_REG_STATUS_CMODE_RGMII 0x7
|
|
#define PORT_REG_STATUS_CMODE_RMII 0x5
|
|
#define PORT_REG_STATUS_CMODE_RMII_PHY 0x4
|
|
#define PORT_REG_STATUS_CMODE_GMII 0x3
|
|
#define PORT_REG_STATUS_CMODE_MII 0x2
|
|
#define PORT_REG_STATUS_CMODE_MIIPHY 0x1
|
|
|
|
#define PORT_REG_PHYS_CTRL_RGMII_DELAY_RXCLK BIT(15)
|
|
#define PORT_REG_PHYS_CTRL_RGMII_DELAY_TXCLK BIT(14)
|
|
#define PORT_REG_PHYS_CTRL_PCS_AN_EN BIT(10)
|
|
#define PORT_REG_PHYS_CTRL_PCS_AN_RST BIT(9)
|
|
#define PORT_REG_PHYS_CTRL_FC_VALUE BIT(7)
|
|
#define PORT_REG_PHYS_CTRL_FC_FORCE BIT(6)
|
|
#define PORT_REG_PHYS_CTRL_LINK_VALUE BIT(5)
|
|
#define PORT_REG_PHYS_CTRL_LINK_FORCE BIT(4)
|
|
#define PORT_REG_PHYS_CTRL_DUPLEX_VALUE BIT(3)
|
|
#define PORT_REG_PHYS_CTRL_DUPLEX_FORCE BIT(2)
|
|
#define PORT_REG_PHYS_CTRL_SPD1000 BIT(1)
|
|
#define PORT_REG_PHYS_CTRL_SPD100 BIT(0)
|
|
#define PORT_REG_PHYS_CTRL_SPD_MASK (BIT(1) | BIT(0))
|
|
|
|
#define PORT_REG_CTRL_PSTATE_SHIFT 0
|
|
#define PORT_REG_CTRL_PSTATE_MASK 3
|
|
|
|
/* Field values */
|
|
#define PORT_REG_CTRL_PSTATE_DISABLED 0
|
|
#define PORT_REG_CTRL_PSTATE_FORWARD 3
|
|
|
|
/*
|
|
* Macros for building commands for indirect addressing modes. These are valid
|
|
* for both the indirect multichip addressing mode and the PHY indirection
|
|
* required for the writes to any PHY register.
|
|
*/
|
|
#define SMI_BUSY BIT(15)
|
|
#define SMI_CMD_CLAUSE_22 BIT(12)
|
|
#define SMI_CMD_CLAUSE_22_OP_READ (2 << 10)
|
|
#define SMI_CMD_CLAUSE_22_OP_WRITE (1 << 10)
|
|
#define SMI_CMD_ADDR_SHIFT 5
|
|
#define SMI_CMD_ADDR_MASK 0x1f
|
|
#define SMI_CMD_REG_SHIFT 0
|
|
#define SMI_CMD_REG_MASK 0x1f
|
|
#define SMI_CMD_READ(addr, reg) \
|
|
(SMI_BUSY | SMI_CMD_CLAUSE_22 | SMI_CMD_CLAUSE_22_OP_READ) | \
|
|
(((addr) & SMI_CMD_ADDR_MASK) << SMI_CMD_ADDR_SHIFT) | \
|
|
(((reg) & SMI_CMD_REG_MASK) << SMI_CMD_REG_SHIFT)
|
|
#define SMI_CMD_WRITE(addr, reg) \
|
|
(SMI_BUSY | SMI_CMD_CLAUSE_22 | SMI_CMD_CLAUSE_22_OP_WRITE) | \
|
|
(((addr) & SMI_CMD_ADDR_MASK) << SMI_CMD_ADDR_SHIFT) | \
|
|
(((reg) & SMI_CMD_REG_MASK) << SMI_CMD_REG_SHIFT)
|
|
|
|
/* ID register values for different switch models */
|
|
#define PORT_SWITCH_ID_6020 0x0200
|
|
#define PORT_SWITCH_ID_6070 0x0700
|
|
#define PORT_SWITCH_ID_6071 0x0710
|
|
#define PORT_SWITCH_ID_6096 0x0980
|
|
#define PORT_SWITCH_ID_6097 0x0990
|
|
#define PORT_SWITCH_ID_6172 0x1720
|
|
#define PORT_SWITCH_ID_6176 0x1760
|
|
#define PORT_SWITCH_ID_6220 0x2200
|
|
#define PORT_SWITCH_ID_6240 0x2400
|
|
#define PORT_SWITCH_ID_6250 0x2500
|
|
#define PORT_SWITCH_ID_6320 0x1150
|
|
#define PORT_SWITCH_ID_6352 0x3520
|
|
|
|
struct mv88e6xxx_priv {
|
|
int smi_addr;
|
|
int id;
|
|
int port_count; /* Number of switch ports */
|
|
int port_reg_base; /* Base of the switch port registers */
|
|
u8 global1; /* Offset of Switch Global 1 registers */
|
|
u8 global2; /* Offset of Switch Global 2 registers */
|
|
};
|
|
|
|
/* Wait for the current SMI indirect command to complete */
|
|
static int mv88e6xxx_smi_wait(struct udevice *dev, int smi_addr)
|
|
{
|
|
int val;
|
|
u32 timeout = 100;
|
|
|
|
do {
|
|
val = dm_mdio_read(dev->parent, smi_addr, MDIO_DEVAD_NONE, SMI_CMD_REG);
|
|
if (val >= 0 && (val & SMI_BUSY) == 0)
|
|
return 0;
|
|
|
|
mdelay(1);
|
|
} while (--timeout);
|
|
|
|
dev_err(dev, "SMI busy timeout\n");
|
|
return -ETIMEDOUT;
|
|
}
|
|
|
|
/*
|
|
* The mv88e6xxx has three types of addresses: the smi bus address, the device
|
|
* address, and the register address. The smi bus address distinguishes it on
|
|
* the smi bus from other PHYs or switches. The device address determines
|
|
* which on-chip register set you are reading/writing (the various PHYs, their
|
|
* associated ports, or global configuration registers). The register address
|
|
* is the offset of the register you are reading/writing.
|
|
*
|
|
* When the mv88e6xxx is hardware configured to have address zero, it behaves in
|
|
* single-chip addressing mode, where it responds to all SMI addresses, using
|
|
* the smi address as its device address. This obviously only works when this
|
|
* is the only chip on the SMI bus. This allows the driver to access device
|
|
* registers without using indirection. When the chip is configured to a
|
|
* non-zero address, it only responds to that SMI address and requires indirect
|
|
* writes to access the different device addresses.
|
|
*/
|
|
static int mv88e6xxx_reg_read(struct udevice *dev, int addr, int reg)
|
|
{
|
|
struct mv88e6xxx_priv *priv = dev_get_priv(dev);
|
|
int smi_addr = priv->smi_addr;
|
|
int res;
|
|
|
|
/* In single-chip mode, the device can be addressed directly */
|
|
if (smi_addr == 0)
|
|
return dm_mdio_read(dev->parent, addr, MDIO_DEVAD_NONE, reg);
|
|
|
|
/* Wait for the bus to become free */
|
|
res = mv88e6xxx_smi_wait(dev, smi_addr);
|
|
if (res < 0)
|
|
return res;
|
|
|
|
/* Issue the read command */
|
|
res = dm_mdio_write(dev->parent, smi_addr, MDIO_DEVAD_NONE, SMI_CMD_REG,
|
|
SMI_CMD_READ(addr, reg));
|
|
if (res < 0)
|
|
return res;
|
|
|
|
/* Wait for the read command to complete */
|
|
res = mv88e6xxx_smi_wait(dev, smi_addr);
|
|
if (res < 0)
|
|
return res;
|
|
|
|
/* Read the data */
|
|
res = dm_mdio_read(dev->parent, smi_addr, MDIO_DEVAD_NONE, SMI_DATA_REG);
|
|
if (res < 0)
|
|
return res;
|
|
|
|
return res & 0xffff;
|
|
}
|
|
|
|
/* See the comment above mv88e6xxx_reg_read */
|
|
static int mv88e6xxx_reg_write(struct udevice *dev, int addr, int reg, u16 val)
|
|
{
|
|
struct mv88e6xxx_priv *priv = dev_get_priv(dev);
|
|
int smi_addr = priv->smi_addr;
|
|
int res;
|
|
|
|
/* In single-chip mode, the device can be addressed directly */
|
|
if (smi_addr == 0)
|
|
return dm_mdio_write(dev->parent, addr, MDIO_DEVAD_NONE, reg, val);
|
|
|
|
/* Wait for the bus to become free */
|
|
res = mv88e6xxx_smi_wait(dev, smi_addr);
|
|
if (res < 0)
|
|
return res;
|
|
|
|
/* Set the data to write */
|
|
res = dm_mdio_write(dev->parent, smi_addr, MDIO_DEVAD_NONE,
|
|
SMI_DATA_REG, val);
|
|
if (res < 0)
|
|
return res;
|
|
|
|
/* Issue the write command */
|
|
res = dm_mdio_write(dev->parent, smi_addr, MDIO_DEVAD_NONE, SMI_CMD_REG,
|
|
SMI_CMD_WRITE(addr, reg));
|
|
if (res < 0)
|
|
return res;
|
|
|
|
/* Wait for the write command to complete */
|
|
res = mv88e6xxx_smi_wait(dev, smi_addr);
|
|
if (res < 0)
|
|
return res;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int mv88e6xxx_phy_wait(struct udevice *dev)
|
|
{
|
|
struct mv88e6xxx_priv *priv = dev_get_priv(dev);
|
|
int val;
|
|
u32 timeout = 100;
|
|
|
|
do {
|
|
val = mv88e6xxx_reg_read(dev, priv->global2, GLOBAL2_REG_PHY_CMD);
|
|
if (val >= 0 && (val & SMI_BUSY) == 0)
|
|
return 0;
|
|
|
|
mdelay(1);
|
|
} while (--timeout);
|
|
|
|
return -ETIMEDOUT;
|
|
}
|
|
|
|
static int mv88e6xxx_phy_read_indirect(struct udevice *dev, int phyad, int devad, int reg)
|
|
{
|
|
struct mv88e6xxx_priv *priv = dev_get_priv(dev);
|
|
int res;
|
|
|
|
/* Issue command to read */
|
|
res = mv88e6xxx_reg_write(dev, priv->global2,
|
|
GLOBAL2_REG_PHY_CMD,
|
|
SMI_CMD_READ(phyad, reg));
|
|
|
|
/* Wait for data to be read */
|
|
res = mv88e6xxx_phy_wait(dev);
|
|
if (res < 0)
|
|
return res;
|
|
|
|
/* Read retrieved data */
|
|
return mv88e6xxx_reg_read(dev, priv->global2,
|
|
GLOBAL2_REG_PHY_DATA);
|
|
}
|
|
|
|
static int mv88e6xxx_phy_write_indirect(struct udevice *dev, int phyad,
|
|
int devad, int reg, u16 data)
|
|
{
|
|
struct mv88e6xxx_priv *priv = dev_get_priv(dev);
|
|
int res;
|
|
|
|
/* Set the data to write */
|
|
res = mv88e6xxx_reg_write(dev, priv->global2,
|
|
GLOBAL2_REG_PHY_DATA, data);
|
|
if (res < 0)
|
|
return res;
|
|
/* Issue the write command */
|
|
res = mv88e6xxx_reg_write(dev, priv->global2,
|
|
GLOBAL2_REG_PHY_CMD,
|
|
SMI_CMD_WRITE(phyad, reg));
|
|
if (res < 0)
|
|
return res;
|
|
|
|
/* Wait for command to complete */
|
|
return mv88e6xxx_phy_wait(dev);
|
|
}
|
|
|
|
/* Wrapper function to make calls to phy_read_indirect simpler */
|
|
static int mv88e6xxx_phy_read(struct udevice *dev, int phy, int reg)
|
|
{
|
|
return mv88e6xxx_phy_read_indirect(dev, DEVADDR_PHY(phy),
|
|
MDIO_DEVAD_NONE, reg);
|
|
}
|
|
|
|
/* Wrapper function to make calls to phy_write_indirect simpler */
|
|
static int mv88e6xxx_phy_write(struct udevice *dev, int phy, int reg, u16 val)
|
|
{
|
|
return mv88e6xxx_phy_write_indirect(dev, DEVADDR_PHY(phy),
|
|
MDIO_DEVAD_NONE, reg, val);
|
|
}
|
|
|
|
static int mv88e6xxx_port_read(struct udevice *dev, u8 port, u8 reg)
|
|
{
|
|
struct mv88e6xxx_priv *priv = dev_get_priv(dev);
|
|
|
|
return mv88e6xxx_reg_read(dev, priv->port_reg_base + port, reg);
|
|
}
|
|
|
|
static int mv88e6xxx_port_write(struct udevice *dev, u8 port, u8 reg, u16 val)
|
|
{
|
|
struct mv88e6xxx_priv *priv = dev_get_priv(dev);
|
|
|
|
return mv88e6xxx_reg_write(dev, priv->port_reg_base + port, reg, val);
|
|
}
|
|
|
|
static int mv88e6xxx_set_page(struct udevice *dev, u8 phy, u8 page)
|
|
{
|
|
return mv88e6xxx_phy_write(dev, phy, PHY_REG_PAGE, page);
|
|
}
|
|
|
|
static int mv88e6xxx_get_switch_id(struct udevice *dev)
|
|
{
|
|
int res;
|
|
|
|
res = mv88e6xxx_port_read(dev, 0, PORT_REG_SWITCH_ID);
|
|
if (res < 0) {
|
|
dev_err(dev, "Failed to read switch ID: %d\n", res);
|
|
return res;
|
|
}
|
|
return res & 0xfff0;
|
|
}
|
|
|
|
static bool mv88e6xxx_6352_family(struct udevice *dev)
|
|
{
|
|
struct mv88e6xxx_priv *priv = dev_get_priv(dev);
|
|
|
|
switch (priv->id) {
|
|
case PORT_SWITCH_ID_6172:
|
|
case PORT_SWITCH_ID_6176:
|
|
case PORT_SWITCH_ID_6240:
|
|
case PORT_SWITCH_ID_6352:
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static int mv88e6xxx_get_cmode(struct udevice *dev, u8 port)
|
|
{
|
|
int res;
|
|
|
|
res = mv88e6xxx_port_read(dev, port, PORT_REG_STATUS);
|
|
if (res < 0)
|
|
return res;
|
|
return res & PORT_REG_STATUS_CMODE_MASK;
|
|
}
|
|
|
|
static int mv88e6xxx_switch_reset(struct udevice *dev)
|
|
{
|
|
struct mv88e6xxx_priv *priv = dev_get_priv(dev);
|
|
int time_ms;
|
|
int val;
|
|
u8 port;
|
|
|
|
/* Disable all ports */
|
|
for (port = 0; port < priv->port_count; port++) {
|
|
val = mv88e6xxx_port_read(dev, port, PORT_REG_CTRL);
|
|
if (val < 0)
|
|
return val;
|
|
val &= ~(PORT_REG_CTRL_PSTATE_MASK << PORT_REG_CTRL_PSTATE_SHIFT);
|
|
val |= (PORT_REG_CTRL_PSTATE_DISABLED << PORT_REG_CTRL_PSTATE_SHIFT);
|
|
val = mv88e6xxx_port_write(dev, port, PORT_REG_CTRL, val);
|
|
if (val < 0)
|
|
return val;
|
|
}
|
|
|
|
/* Wait 2 ms for queues to drain */
|
|
udelay(2000);
|
|
|
|
/* Reset switch */
|
|
val = mv88e6xxx_reg_read(dev, priv->global1, GLOBAL1_CTRL);
|
|
if (val < 0)
|
|
return val;
|
|
val |= GLOBAL1_CTRL_SWRESET;
|
|
val = mv88e6xxx_reg_write(dev, priv->global1, GLOBAL1_CTRL, val);
|
|
if (val < 0)
|
|
return val;
|
|
|
|
/* Wait up to 1 second for switch to reset complete */
|
|
for (time_ms = 1000; time_ms; time_ms--) {
|
|
val = mv88e6xxx_reg_read(dev, priv->global1, GLOBAL1_CTRL);
|
|
if (val >= 0 && ((val & GLOBAL1_CTRL_SWRESET) == 0))
|
|
break;
|
|
udelay(1000);
|
|
}
|
|
if (!time_ms)
|
|
return -ETIMEDOUT;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int mv88e6xxx_serdes_init(struct udevice *dev)
|
|
{
|
|
int val;
|
|
|
|
val = mv88e6xxx_set_page(dev, DEVADDR_SERDES, PHY_PAGE_SERDES);
|
|
if (val < 0)
|
|
return val;
|
|
|
|
/* Power up serdes module */
|
|
val = mv88e6xxx_phy_read(dev, DEVADDR_SERDES, MII_BMCR);
|
|
if (val < 0)
|
|
return val;
|
|
val &= ~(BMCR_PDOWN);
|
|
val = mv88e6xxx_phy_write(dev, DEVADDR_SERDES, MII_BMCR, val);
|
|
if (val < 0)
|
|
return val;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* This function is used to pre-configure the required register
|
|
* offsets, so that the indirect register access to the PHY registers
|
|
* is possible. This is necessary to be able to read the PHY ID
|
|
* while driver probing or in get_phy_id(). The globalN register
|
|
* offsets must be initialized correctly for a detected switch,
|
|
* otherwise detection of the PHY ID won't work!
|
|
*/
|
|
static int mv88e6xxx_priv_reg_offs_pre_init(struct udevice *dev)
|
|
{
|
|
struct mv88e6xxx_priv *priv = dev_get_priv(dev);
|
|
|
|
/*
|
|
* Initial 'port_reg_base' value must be an offset of existing
|
|
* port register, then reading the ID should succeed. First, try
|
|
* to read via port registers with device address 0x10 (88E6096
|
|
* and compatible switches).
|
|
*/
|
|
priv->port_reg_base = 0x10;
|
|
priv->id = mv88e6xxx_get_switch_id(dev);
|
|
if (priv->id != 0xfff0) {
|
|
priv->global1 = 0x1B;
|
|
priv->global2 = 0x1C;
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Now try via port registers with device address 0x08
|
|
* (88E6020 and compatible switches).
|
|
*/
|
|
priv->port_reg_base = 0x08;
|
|
priv->id = mv88e6xxx_get_switch_id(dev);
|
|
if (priv->id != 0xfff0) {
|
|
priv->global1 = 0x0F;
|
|
priv->global2 = 0x07;
|
|
return 0;
|
|
}
|
|
|
|
dev_warn(dev, "%s Unknown ID 0x%x\n", __func__, priv->id);
|
|
|
|
return -ENODEV;
|
|
}
|
|
|
|
static int mv88e6xxx_mdio_read(struct udevice *dev, int addr, int devad, int reg)
|
|
{
|
|
return mv88e6xxx_phy_read_indirect(dev->parent, DEVADDR_PHY(addr),
|
|
MDIO_DEVAD_NONE, reg);
|
|
}
|
|
|
|
static int mv88e6xxx_mdio_write(struct udevice *dev, int addr, int devad,
|
|
int reg, u16 val)
|
|
{
|
|
return mv88e6xxx_phy_write_indirect(dev->parent, DEVADDR_PHY(addr),
|
|
MDIO_DEVAD_NONE, reg, val);
|
|
}
|
|
|
|
static const struct mdio_ops mv88e6xxx_mdio_ops = {
|
|
.read = mv88e6xxx_mdio_read,
|
|
.write = mv88e6xxx_mdio_write,
|
|
};
|
|
|
|
static int mv88e6xxx_mdio_bind(struct udevice *dev)
|
|
{
|
|
char name[32];
|
|
static int num_devices;
|
|
|
|
sprintf(name, "mv88e6xxx-mdio-%d", num_devices++);
|
|
device_set_name(dev, name);
|
|
|
|
return 0;
|
|
}
|
|
|
|
U_BOOT_DRIVER(mv88e6xxx_mdio) = {
|
|
.name = "mv88e6xxx_mdio",
|
|
.id = UCLASS_MDIO,
|
|
.ops = &mv88e6xxx_mdio_ops,
|
|
.bind = mv88e6xxx_mdio_bind,
|
|
.plat_auto = sizeof(struct mdio_perdev_priv),
|
|
};
|
|
|
|
static int mv88e6xxx_port_probe(struct udevice *dev, int port, struct phy_device *phy)
|
|
{
|
|
struct mv88e6xxx_priv *priv = dev_get_priv(dev);
|
|
int supported;
|
|
|
|
switch (priv->id) {
|
|
case PORT_SWITCH_ID_6020:
|
|
case PORT_SWITCH_ID_6070:
|
|
case PORT_SWITCH_ID_6071:
|
|
supported = PHY_BASIC_FEATURES | SUPPORTED_MII;
|
|
break;
|
|
default:
|
|
supported = PHY_GBIT_FEATURES;
|
|
break;
|
|
}
|
|
|
|
phy->supported &= supported;
|
|
phy->advertising &= supported;
|
|
|
|
return phy_config(phy);
|
|
}
|
|
|
|
static int mv88e6xxx_port_enable(struct udevice *dev, int port, struct phy_device *phy)
|
|
{
|
|
int val, ret;
|
|
|
|
dev_dbg(dev, "%s P%d phy:0x%08x %s\n", __func__, port,
|
|
phy->phy_id, phy_string_for_interface(phy->interface));
|
|
|
|
if (phy->phy_id == PHY_FIXED_ID) {
|
|
/* Physical Control register: Table 62 */
|
|
val = mv88e6xxx_port_read(dev, port, PORT_REG_PHYS_CTRL);
|
|
|
|
/* configure RGMII delays for fixed link */
|
|
switch (phy->interface) {
|
|
case PHY_INTERFACE_MODE_RGMII:
|
|
case PHY_INTERFACE_MODE_RGMII_ID:
|
|
case PHY_INTERFACE_MODE_RGMII_RXID:
|
|
case PHY_INTERFACE_MODE_RGMII_TXID:
|
|
dev_dbg(dev, "configure internal RGMII delays\n");
|
|
|
|
/* RGMII delays */
|
|
val &= ~(PORT_REG_PHYS_CTRL_RGMII_DELAY_RXCLK ||
|
|
PORT_REG_PHYS_CTRL_RGMII_DELAY_TXCLK);
|
|
if (phy->interface == PHY_INTERFACE_MODE_RGMII_ID ||
|
|
phy->interface == PHY_INTERFACE_MODE_RGMII_RXID)
|
|
val |= PORT_REG_PHYS_CTRL_RGMII_DELAY_RXCLK;
|
|
if (phy->interface == PHY_INTERFACE_MODE_RGMII_ID ||
|
|
phy->interface == PHY_INTERFACE_MODE_RGMII_TXID)
|
|
val |= PORT_REG_PHYS_CTRL_RGMII_DELAY_TXCLK;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
/* Force Link */
|
|
val |= PORT_REG_PHYS_CTRL_LINK_VALUE |
|
|
PORT_REG_PHYS_CTRL_LINK_FORCE;
|
|
|
|
ret = mv88e6xxx_port_write(dev, port, PORT_REG_PHYS_CTRL, val);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
if (mv88e6xxx_6352_family(dev)) {
|
|
/* validate interface type */
|
|
dev_dbg(dev, "validate interface type\n");
|
|
val = mv88e6xxx_get_cmode(dev, port);
|
|
if (val < 0)
|
|
return val;
|
|
switch (phy->interface) {
|
|
case PHY_INTERFACE_MODE_RGMII:
|
|
case PHY_INTERFACE_MODE_RGMII_RXID:
|
|
case PHY_INTERFACE_MODE_RGMII_TXID:
|
|
case PHY_INTERFACE_MODE_RGMII_ID:
|
|
if (val != PORT_REG_STATUS_CMODE_RGMII)
|
|
goto mismatch;
|
|
break;
|
|
case PHY_INTERFACE_MODE_1000BASEX:
|
|
if (val != PORT_REG_STATUS_CMODE_1000BASE_X)
|
|
goto mismatch;
|
|
break;
|
|
mismatch:
|
|
default:
|
|
dev_err(dev, "Mismatched PHY mode %s on port %d!\n",
|
|
phy_string_for_interface(phy->interface), port);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* enable port */
|
|
val = mv88e6xxx_port_read(dev, port, PORT_REG_CTRL);
|
|
if (val < 0)
|
|
return val;
|
|
val &= ~(PORT_REG_CTRL_PSTATE_MASK << PORT_REG_CTRL_PSTATE_SHIFT);
|
|
val |= (PORT_REG_CTRL_PSTATE_FORWARD << PORT_REG_CTRL_PSTATE_SHIFT);
|
|
val = mv88e6xxx_port_write(dev, port, PORT_REG_CTRL, val);
|
|
if (val < 0)
|
|
return val;
|
|
|
|
return phy_startup(phy);
|
|
}
|
|
|
|
static void mv88e6xxx_port_disable(struct udevice *dev, int port, struct phy_device *phy)
|
|
{
|
|
int val;
|
|
|
|
dev_dbg(dev, "%s P%d phy:0x%08x %s\n", __func__, port,
|
|
phy->phy_id, phy_string_for_interface(phy->interface));
|
|
|
|
val = mv88e6xxx_port_read(dev, port, PORT_REG_CTRL);
|
|
val &= ~(PORT_REG_CTRL_PSTATE_MASK << PORT_REG_CTRL_PSTATE_SHIFT);
|
|
val |= (PORT_REG_CTRL_PSTATE_DISABLED << PORT_REG_CTRL_PSTATE_SHIFT);
|
|
mv88e6xxx_port_write(dev, port, PORT_REG_CTRL, val);
|
|
}
|
|
|
|
static const struct dsa_ops mv88e6xxx_dsa_ops = {
|
|
.port_probe = mv88e6xxx_port_probe,
|
|
.port_enable = mv88e6xxx_port_enable,
|
|
.port_disable = mv88e6xxx_port_disable,
|
|
};
|
|
|
|
/* bind and probe the switch mdios */
|
|
static int mv88e6xxx_probe_mdio(struct udevice *dev)
|
|
{
|
|
struct udevice *mdev;
|
|
const char *name;
|
|
ofnode node;
|
|
int ret;
|
|
|
|
/* bind phy ports of mdio child node to mv88e6xxx_mdio device */
|
|
node = dev_read_subnode(dev, "mdio");
|
|
if (!ofnode_valid(node))
|
|
return 0;
|
|
|
|
name = ofnode_get_name(node);
|
|
ret = device_bind_driver_to_node(dev,
|
|
"mv88e6xxx_mdio",
|
|
name, node, NULL);
|
|
if (ret) {
|
|
dev_err(dev, "failed to bind %s: %d\n", name, ret);
|
|
} else {
|
|
/* need to probe it as there is no compatible to do so */
|
|
ret = uclass_get_device_by_ofnode(UCLASS_MDIO, node, &mdev);
|
|
if (ret)
|
|
dev_err(dev, "failed to probe %s: %d\n", name, ret);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int mv88e6xxx_probe(struct udevice *dev)
|
|
{
|
|
struct dsa_pdata *dsa_pdata = dev_get_uclass_plat(dev);
|
|
struct mv88e6xxx_priv *priv = dev_get_priv(dev);
|
|
int val, ret;
|
|
|
|
if (ofnode_valid(dev_ofnode(dev)) &&
|
|
!ofnode_is_enabled(dev_ofnode(dev))) {
|
|
dev_dbg(dev, "switch disabled\n");
|
|
return -ENODEV;
|
|
}
|
|
|
|
/* probe internal mdio bus */
|
|
ret = mv88e6xxx_probe_mdio(dev);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = mv88e6xxx_priv_reg_offs_pre_init(dev);
|
|
if (ret)
|
|
return ret;
|
|
|
|
dev_dbg(dev, "ID=0x%x PORT_BASE=0x%02x GLOBAL1=0x%02x GLOBAL2=0x%02x\n",
|
|
priv->id, priv->port_reg_base, priv->global1, priv->global2);
|
|
switch (priv->id) {
|
|
case PORT_SWITCH_ID_6096:
|
|
case PORT_SWITCH_ID_6097:
|
|
case PORT_SWITCH_ID_6172:
|
|
case PORT_SWITCH_ID_6176:
|
|
case PORT_SWITCH_ID_6240:
|
|
case PORT_SWITCH_ID_6352:
|
|
priv->port_count = 11;
|
|
break;
|
|
case PORT_SWITCH_ID_6020:
|
|
case PORT_SWITCH_ID_6070:
|
|
case PORT_SWITCH_ID_6071:
|
|
case PORT_SWITCH_ID_6220:
|
|
case PORT_SWITCH_ID_6250:
|
|
case PORT_SWITCH_ID_6320:
|
|
priv->port_count = 7;
|
|
break;
|
|
default:
|
|
return -ENODEV;
|
|
}
|
|
|
|
ret = mv88e6xxx_switch_reset(dev);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
if (mv88e6xxx_6352_family(dev)) {
|
|
val = mv88e6xxx_get_cmode(dev, dsa_pdata->cpu_port);
|
|
if (val < 0)
|
|
return val;
|
|
/* initialize serdes */
|
|
if (val == PORT_REG_STATUS_CMODE_100BASE_X ||
|
|
val == PORT_REG_STATUS_CMODE_1000BASE_X ||
|
|
val == PORT_REG_STATUS_CMODE_SGMII) {
|
|
ret = mv88e6xxx_serdes_init(dev);
|
|
if (ret < 0)
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct udevice_id mv88e6xxx_ids[] = {
|
|
{ .compatible = "marvell,mv88e6085" },
|
|
{ }
|
|
};
|
|
|
|
U_BOOT_DRIVER(mv88e6xxx) = {
|
|
.name = "mv88e6xxx",
|
|
.id = UCLASS_DSA,
|
|
.of_match = mv88e6xxx_ids,
|
|
.probe = mv88e6xxx_probe,
|
|
.ops = &mv88e6xxx_dsa_ops,
|
|
.priv_auto = sizeof(struct mv88e6xxx_priv),
|
|
};
|