Add a DSA driver for the MV88E61xx compatible GbE Ethernet switches. Cc: Marek BehĂșn <marek.be...@nic.cz> Cc: Vladimir Oltean <vladimir.olt...@nxp.com> Signed-off-by: Tim Harvey <thar...@gateworks.com> --- v2: - rebase on v2022.07-rc1 (use ofnode_get_phy_node) - remove unused commented out fields from struct - remove unused PORT_MASK macro - remove phy from priv struct name - refactor code from original drivers/net/phy/mv88e61xx with suggestions from review to consolidate some functions into mv88e61xx_dsa_port_enable - remove unecessary skiping of disabling of CPU port - remove unecessary dev_set_parent_priv - remove unnecessary static init flag - replace debug with a dev_warn if switch device-id unsupported - remove unnecessary xmit/recv functions as we rely on the fact that only a single port is active instead of mangling packets --- drivers/net/Kconfig | 7 + drivers/net/Makefile | 1 + drivers/net/mv88e61xx.c | 843 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 851 insertions(+) create mode 100644 drivers/net/mv88e61xx.c
diff --git a/drivers/net/Kconfig b/drivers/net/Kconfig index 7fe0e00649cf..edb1a0898986 100644 --- a/drivers/net/Kconfig +++ b/drivers/net/Kconfig @@ -433,6 +433,13 @@ config LPC32XX_ETH depends on ARCH_LPC32XX default y +config MV88E61XX + bool "Marvell MV88E61xx GbE switch DSA driver" + depends on DM_DSA && DM_MDIO + help + This driver implements a DSA switch driver for the MV88E61xx family + of GbE switches using the MDIO interface + config MVGBE bool "Marvell Orion5x/Kirkwood network interface support" depends on ARCH_KIRKWOOD || ARCH_ORION5X diff --git a/drivers/net/Makefile b/drivers/net/Makefile index 69fb3bbbf7cb..36b4c279430a 100644 --- a/drivers/net/Makefile +++ b/drivers/net/Makefile @@ -59,6 +59,7 @@ obj-$(CONFIG_MEDIATEK_ETH) += mtk_eth.o obj-$(CONFIG_MPC8XX_FEC) += mpc8xx_fec.o obj-$(CONFIG_MT7620_ETH) += mt7620-eth.o obj-$(CONFIG_MT7628_ETH) += mt7628-eth.o +obj-$(CONFIG_MV88E61XX) += mv88e61xx.o obj-$(CONFIG_MVGBE) += mvgbe.o obj-$(CONFIG_MVMDIO) += mvmdio.o obj-$(CONFIG_MVNETA) += mvneta.o diff --git a/drivers/net/mv88e61xx.c b/drivers/net/mv88e61xx.c new file mode 100644 index 000000000000..514835bf03b9 --- /dev/null +++ b/drivers/net/mv88e61xx.c @@ -0,0 +1,843 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * (C) Copyright 2022 + * Gateworks Corporation <www.gateworks.com> + * Tim Harvey <thar...@gateworks.com> + * + * (C) Copyright 2015 + * Elecsys Corporation <www.elecsyscorp.com> + * Kevin Smith <kevin.sm...@elecsyscorp.com> + * + * Original driver: + * (C) Copyright 2009 + * Marvell Semiconductor <www.marvell.com> + * Prafulla Wadaskar <prafu...@marvell.com> + */ + +/* + * DSA driver for mv88e61xx ethernet switches. + * + * This driver configures the mv88e61xx 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 <bitfield.h> +#include <common.h> +#include <miiphy.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 <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 +#define GLOBAL1_MON_CTRL 0x1A + +/* 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 +#define PORT_REG_VLAN_MAP 0x06 +#define PORT_REG_VLAN_ID 0x07 +#define PORT_REG_LED_CTRL 0x16 + +/* Phy registers */ +#define PHY_REG_CTRL1 0x10 +#define PHY_REG_STATUS1 0x11 +#define PHY_REG_PAGE 0x16 + +/* Serdes registers */ +#define SERDES_REG_CTRL_1 0x10 + +/* Phy page numbers */ +#define PHY_PAGE_COPPER 0 +#define PHY_PAGE_SERDES 1 + +/* Register fields */ +#define GLOBAL1_CTRL_SWRESET BIT(15) + +#define GLOBAL1_MON_CTRL_CPUDEST_SHIFT 4 +#define GLOBAL1_MON_CTRL_CPUDEST_WIDTH 4 + +#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_100BASE_X 0x8 +#define PORT_REG_STATUS_CMODE_1000BASE_X 0x9 +#define PORT_REG_STATUS_CMODE_SGMII 0xa + +#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_WIDTH 2 + +#define PORT_REG_VLAN_ID_DEF_VID_SHIFT 0 +#define PORT_REG_VLAN_ID_DEF_VID_WIDTH 12 + +#define PORT_REG_VLAN_MAP_TABLE_SHIFT 0 +#define PORT_REG_VLAN_MAP_TABLE_WIDTH 11 + +#define SERDES_REG_CTRL_1_FORCE_LINK BIT(10) + +/* Field values */ +#define PORT_REG_CTRL_PSTATE_DISABLED 0 +#define PORT_REG_CTRL_PSTATE_FORWARD 3 + +#define PHY_REG_CTRL1_ENERGY_DET_OFF 0 +#define PHY_REG_CTRL1_ENERGY_DET_SENSE_PULSE 1 +#define PHY_REG_CTRL1_ENERGY_DET_SENSE_ONLY 2 +#define PHY_REG_CTRL1_ENERGY_DET_SENSE_XMIT 3 + +/* 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 + +/* + * 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_READ (SMI_BUSY | SMI_CMD_CLAUSE_22 | \ + SMI_CMD_CLAUSE_22_OP_READ) +#define SMI_CMD_WRITE (SMI_BUSY | SMI_CMD_CLAUSE_22 | \ + SMI_CMD_CLAUSE_22_OP_WRITE) + +#define SMI_CMD_ADDR_SHIFT 5 +#define SMI_CMD_ADDR_WIDTH 5 +#define SMI_CMD_REG_SHIFT 0 +#define SMI_CMD_REG_WIDTH 5 + +/* Defines for Scratch and Misc Registers */ +#define SCRATCH_UPDATE BIT(15) +#define SCRATCH_ADDR_SHIFT 8 +#define SCRATCH_ADDR_WIDTH 7 + +/* Scratch registers */ +#define SCRATCH_ADDR_GPIO0DIR 0x62 /* GPIO[7:0] direction 1=input */ +#define SCRATCH_ADDR_GPIO1DIR 0x63 /* GPIO[14:8] direction 1=input */ +#define SCRATCH_ADDR_GPIO0DATA 0x64 /* GPIO[7:0] data */ +#define SCRATCH_ADDR_GPIO1DATA 0x65 /* GPIO[14:8] data */ +#define SCRATCH_ADDR_GPIO0CTRL 0x68 /* GPIO[1:0] control */ +#define SCRATCH_ADDR_GPIO1CTRL 0x69 /* GPIO[3:2] control */ + +/* 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_6352 0x3520 + +struct mv88e61xx_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 */ + u8 phy_ctrl1_en_det_shift; /* 'EDet' bit field offset */ + u8 phy_ctrl1_en_det_width; /* Width of 'EDet' bit field */ + u8 phy_ctrl1_en_det_ctrl; /* 'EDet' control value */ +}; + +static inline int smi_cmd(int cmd, int addr, int reg) +{ + cmd = bitfield_replace(cmd, SMI_CMD_ADDR_SHIFT, SMI_CMD_ADDR_WIDTH, + addr); + cmd = bitfield_replace(cmd, SMI_CMD_REG_SHIFT, SMI_CMD_REG_WIDTH, reg); + return cmd; +} + +static inline int smi_cmd_read(int addr, int reg) +{ + return smi_cmd(SMI_CMD_READ, addr, reg); +} + +static inline int smi_cmd_write(int addr, int reg) +{ + return smi_cmd(SMI_CMD_WRITE, addr, reg); +} + +/* Wait for the current SMI indirect command to complete */ +static int mv88e61xx_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 mv88e61xx 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 mv88e61xx 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 mv88e61xx_reg_read(struct udevice *dev, int addr, int reg) +{ + struct mv88e61xx_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 = mv88e61xx_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 = mv88e61xx_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 bitfield_extract(res, 0, 16); +} + +/* See the comment above mv88e61xx_reg_read */ +static int mv88e61xx_reg_write(struct udevice *dev, int addr, int reg, u16 val) +{ + struct mv88e61xx_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 = mv88e61xx_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 = mv88e61xx_smi_wait(dev, smi_addr); + if (res < 0) + return res; + + return 0; +} + +static int mv88e61xx_phy_wait(struct udevice *dev) +{ + struct mv88e61xx_priv *priv = dev_get_priv(dev); + int val; + u32 timeout = 100; + + do { + val = mv88e61xx_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 mv88e61xx_phy_read_indirect(struct udevice *dev, int phyad, int devad, int reg) +{ + struct mv88e61xx_priv *priv = dev_get_priv(dev); + int res; + + /* Issue command to read */ + res = mv88e61xx_reg_write(dev, priv->global2, + GLOBAL2_REG_PHY_CMD, + smi_cmd_read(phyad, reg)); + + /* Wait for data to be read */ + res = mv88e61xx_phy_wait(dev); + if (res < 0) + return res; + + /* Read retrieved data */ + return mv88e61xx_reg_read(dev, priv->global2, + GLOBAL2_REG_PHY_DATA); +} + +static int mv88e61xx_phy_write_indirect(struct udevice *dev, int phyad, + int devad, int reg, u16 data) +{ + struct mv88e61xx_priv *priv = dev_get_priv(dev); + int res; + + /* Set the data to write */ + res = mv88e61xx_reg_write(dev, priv->global2, + GLOBAL2_REG_PHY_DATA, data); + if (res < 0) + return res; + /* Issue the write command */ + res = mv88e61xx_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 mv88e61xx_phy_wait(dev); +} + +/* Wrapper function to make calls to phy_read_indirect simpler */ +static int mv88e61xx_phy_read(struct udevice *dev, int phy, int reg) +{ + return mv88e61xx_phy_read_indirect(dev, DEVADDR_PHY(phy), + MDIO_DEVAD_NONE, reg); +} + +/* Wrapper function to make calls to phy_write_indirect simpler */ +static int mv88e61xx_phy_write(struct udevice *dev, int phy, int reg, u16 val) +{ + return mv88e61xx_phy_write_indirect(dev, DEVADDR_PHY(phy), + MDIO_DEVAD_NONE, reg, val); +} + +static int mv88e61xx_port_read(struct udevice *dev, u8 port, u8 reg) +{ + struct mv88e61xx_priv *priv = dev_get_priv(dev); + + return mv88e61xx_reg_read(dev, priv->port_reg_base + port, reg); +} + +static int mv88e61xx_port_write(struct udevice *dev, u8 port, u8 reg, u16 val) +{ + struct mv88e61xx_priv *priv = dev_get_priv(dev); + + return mv88e61xx_reg_write(dev, priv->port_reg_base + port, reg, val); +} + +static int mv88e61xx_set_page(struct udevice *dev, u8 phy, u8 page) +{ + return mv88e61xx_phy_write(dev, phy, PHY_REG_PAGE, page); +} + +static int mv88e61xx_get_switch_id(struct udevice *dev) +{ + int res; + + res = mv88e61xx_port_read(dev, 0, PORT_REG_SWITCH_ID); + if (res < 0) + return res; + return res & 0xfff0; +} + +static bool mv88e61xx_6352_family(struct udevice *dev) +{ + struct mv88e61xx_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 mv88e61xx_get_cmode(struct udevice *dev, u8 port) +{ + int res; + + res = mv88e61xx_port_read(dev, port, PORT_REG_STATUS); + if (res < 0) + return res; + return res & PORT_REG_STATUS_CMODE_MASK; +} + +static int mv88e61xx_switch_reset(struct udevice *dev) +{ + struct mv88e61xx_priv *priv = dev_get_priv(dev); + int time; + int val; + u8 port; + + /* Disable all ports */ + for (port = 0; port < priv->port_count; port++) { + val = mv88e61xx_port_read(dev, port, PORT_REG_CTRL); + if (val < 0) + return val; + val = bitfield_replace(val, PORT_REG_CTRL_PSTATE_SHIFT, + PORT_REG_CTRL_PSTATE_WIDTH, + PORT_REG_CTRL_PSTATE_DISABLED); + val = mv88e61xx_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 = mv88e61xx_reg_read(dev, priv->global1, GLOBAL1_CTRL); + if (val < 0) + return val; + val |= GLOBAL1_CTRL_SWRESET; + val = mv88e61xx_reg_write(dev, priv->global1, GLOBAL1_CTRL, val); + if (val < 0) + return val; + + /* Wait up to 1 second for switch reset complete */ + for (time = 1000; time; time--) { + val = mv88e61xx_reg_read(dev, priv->global1, GLOBAL1_CTRL); + if (val >= 0 && ((val & GLOBAL1_CTRL_SWRESET) == 0)) + break; + udelay(1000); + } + if (!time) + return -ETIMEDOUT; + + return 0; +} + +static int mv88e61xx_serdes_init(struct udevice *dev) +{ + int val; + + val = mv88e61xx_set_page(dev, DEVADDR_SERDES, PHY_PAGE_SERDES); + if (val < 0) + return val; + + /* Power up serdes module */ + val = mv88e61xx_phy_read(dev, DEVADDR_SERDES, MII_BMCR); + if (val < 0) + return val; + val &= ~(BMCR_PDOWN); + val = mv88e61xx_phy_write(dev, DEVADDR_SERDES, MII_BMCR, val); + if (val < 0) + return val; + + return 0; +} + +static int mv88e61xx_fixed_port_setup(struct udevice *dev, u8 port) +{ + struct dsa_pdata *dsa_pdata = dev_get_uclass_plat(dev); + struct mv88e61xx_priv *priv = dev_get_priv(dev); + int val; + + val = mv88e61xx_port_read(dev, port, PORT_REG_PHYS_CTRL); + if (val < 0) + return val; + + val &= ~(PORT_REG_PHYS_CTRL_SPD_MASK | + PORT_REG_PHYS_CTRL_FC_VALUE | + PORT_REG_PHYS_CTRL_FC_FORCE); + val |= PORT_REG_PHYS_CTRL_FC_FORCE | + PORT_REG_PHYS_CTRL_DUPLEX_VALUE | + PORT_REG_PHYS_CTRL_DUPLEX_FORCE; + + if (priv->id == PORT_SWITCH_ID_6071) { + val |= PORT_REG_PHYS_CTRL_SPD100; + } else { + val |= PORT_REG_PHYS_CTRL_PCS_AN_EN | + PORT_REG_PHYS_CTRL_PCS_AN_RST | + PORT_REG_PHYS_CTRL_SPD1000; + } + + if (port == dsa_pdata->cpu_port) + val |= PORT_REG_PHYS_CTRL_LINK_VALUE | + PORT_REG_PHYS_CTRL_LINK_FORCE; + + return mv88e61xx_port_write(dev, port, PORT_REG_PHYS_CTRL, val); +} + +/* + * 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 mv88e61xx_priv_reg_offs_pre_init(struct udevice *dev) +{ + struct mv88e61xx_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 = mv88e61xx_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 = mv88e61xx_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 mv88e61xx_mdio_read(struct udevice *dev, int addr, int devad, int reg) +{ + return mv88e61xx_phy_read_indirect(dev->parent, DEVADDR_PHY(addr), + MDIO_DEVAD_NONE, reg); +} + +static int mv88e61xx_mdio_write(struct udevice *dev, int addr, int devad, + int reg, u16 val) +{ + return mv88e61xx_phy_write_indirect(dev->parent, DEVADDR_PHY(addr), + MDIO_DEVAD_NONE, reg, val); +} + +static const struct mdio_ops mv88e61xx_mdio_ops = { + .read = mv88e61xx_mdio_read, + .write = mv88e61xx_mdio_write, +}; + +static int mv88e61xx_mdio_bind(struct udevice *dev) +{ + char name[32]; + static int num_devices; + + sprintf(name, "mv88e61xx-mdio-%d", num_devices++); + device_set_name(dev, name); + + return 0; +} + +U_BOOT_DRIVER(mv88e61xx_mdio) = { + .name = "mv88e61xx_mdio", + .id = UCLASS_MDIO, + .ops = &mv88e61xx_mdio_ops, + .bind = mv88e61xx_mdio_bind, + .priv_auto = sizeof(struct mv88e61xx_priv), + .plat_auto = sizeof(struct mdio_perdev_priv), +}; + +static int mv88e61xx_dsa_port_enable(struct udevice *dev, int port, struct phy_device *phy) +{ + struct dsa_pdata *dsa_pdata = dev_get_uclass_plat(dev); + struct mv88e61xx_priv *priv = dev_get_priv(dev); + 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)); + + /* + * Enable energy-detect sensing on PHY, used to determine when a PHY + * port is physically connected + */ + val = mv88e61xx_phy_read(dev, port, PHY_REG_CTRL1); + if (val < 0) + return val; + val = bitfield_replace(val, priv->phy_ctrl1_en_det_shift, + priv->phy_ctrl1_en_det_width, + priv->phy_ctrl1_en_det_ctrl); + val = mv88e61xx_phy_write(dev, port, PHY_REG_CTRL1, val); + if (val < 0) + return val; + + /* enable port */ + val = mv88e61xx_port_read(dev, port, PORT_REG_CTRL); + if (val < 0) + return val; + val = bitfield_replace(val, PORT_REG_CTRL_PSTATE_SHIFT, + PORT_REG_CTRL_PSTATE_WIDTH, + PORT_REG_CTRL_PSTATE_FORWARD); + val = mv88e61xx_port_write(dev, port, PORT_REG_CTRL, val); + if (val < 0) + return val; + + /* configure RGMII delays for fixed link */ + if (phy->phy_id == PHY_FIXED_ID) { + /* Physical Control register: Table 62 */ + val = mv88e61xx_port_read(dev, port, PORT_REG_PHYS_CTRL); + 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; + val |= BIT(5) | BIT(4); /* Force link */ + ret = mv88e61xx_port_write(dev, port, PORT_REG_PHYS_CTRL, val); + if (ret < 0) + return ret; + + ret = mv88e61xx_fixed_port_setup(dev, port); + if (ret < 0) + return ret; + } + + if (port == dsa_pdata->cpu_port) { + /* Set CPUDest for cpu port */ + val = mv88e61xx_reg_read(dev, priv->global1, GLOBAL1_MON_CTRL); + if (val < 0) + return val; + val = bitfield_replace(val, GLOBAL1_MON_CTRL_CPUDEST_SHIFT, + GLOBAL1_MON_CTRL_CPUDEST_WIDTH, port); + val = mv88e61xx_reg_write(dev, priv->global1, GLOBAL1_MON_CTRL, val); + if (val < 0) + return val; + + if (mv88e61xx_6352_family(dev)) { + /* initialize serdes */ + val = mv88e61xx_get_cmode(dev, dsa_pdata->cpu_port); + if (val < 0) + return val; + if (val == PORT_REG_STATUS_CMODE_100BASE_X || + val == PORT_REG_STATUS_CMODE_1000BASE_X || + val == PORT_REG_STATUS_CMODE_SGMII) { + val = mv88e61xx_serdes_init(dev); + if (val < 0) + return val; + } + } + } + + return 0; +} + +static void mv88e61xx_dsa_port_disable(struct udevice *dev, int port, struct phy_device *phy) +{ + int val; + + dev_dbg(dev, "%s P%d phy:0x%x\n", __func__, port, phy->phy_id); + + val = mv88e61xx_port_read(dev, port, PORT_REG_CTRL); + if (val < 0) + return; + val = bitfield_replace(val, PORT_REG_CTRL_PSTATE_SHIFT, + PORT_REG_CTRL_PSTATE_WIDTH, + PORT_REG_CTRL_PSTATE_DISABLED); + val = mv88e61xx_port_write(dev, port, PORT_REG_CTRL, val); + if (val < 0) + return; +} + +static const struct dsa_ops mv88e61xx_dsa_ops = { + .port_enable = mv88e61xx_dsa_port_enable, + .port_disable = mv88e61xx_dsa_port_disable, +}; + +/* bind and probe the switch mdios */ +static int mv88e61xx_dsa_probe_mdio(struct udevice *dev) +{ + struct udevice *pdev; + ofnode node, mdios; + const char *name; + int ret; + + /* bind phy ports of mdios child node to mv88e61xx_mdio device */ + mdios = dev_read_subnode(dev, "mdios"); + if (ofnode_valid(mdios)) { + ofnode_for_each_subnode(node, mdios) { + name = ofnode_get_name(node); + ret = device_bind_driver_to_node(dev, + "mv88e61xx_mdio", + name, node, &pdev); + if (ret) { + dev_err(dev, "failed to bind %s: %d\n", name, ret); + continue; + } + + /* need to probe it as there is no compatible to do so */ + ret = uclass_get_device_by_ofnode(UCLASS_MDIO, node, &pdev); + if (ret) { + dev_err(dev, "failed to probe %s: %d\n", name, ret); + continue; + } + } + } + + /* bind the mdios node to the parent mdio driver */ + name = ofnode_get_name(mdios); + ret = device_bind_driver_to_node(dev, + "mv88e61xx_mdio", + name, mdios, &pdev); + if (ret) + dev_err(dev, "failed to bind %s: %d\n", name, ret); + + /* need to probe it as there is no compatible to do so */ + ret = uclass_get_device_by_ofnode(UCLASS_MDIO, mdios, &pdev); + if (ret) + dev_err(dev, "failed to probe %s: %d\n", name, ret); + + return ret; +} + +static int mv88e61xx_dsa_probe(struct udevice *dev) +{ + struct mv88e61xx_priv *priv = dev_get_priv(dev); + struct udevice *master = dsa_get_master(dev); + int ret; + + if (!master) + return -ENODEV; + + dev_dbg(dev, "%s master:%s\n", __func__, master->name); + + /* probe internal mdio bus */ + ret = mv88e61xx_dsa_probe_mdio(dev); + if (ret) + return ret; + + ret = mv88e61xx_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; + priv->phy_ctrl1_en_det_shift = 8; + priv->phy_ctrl1_en_det_width = 2; + priv->phy_ctrl1_en_det_ctrl = + PHY_REG_CTRL1_ENERGY_DET_SENSE_XMIT; + 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: + priv->port_count = 7; + priv->phy_ctrl1_en_det_shift = 14; + priv->phy_ctrl1_en_det_width = 1; + priv->phy_ctrl1_en_det_ctrl = + PHY_REG_CTRL1_ENERGY_DET_SENSE_PULSE; + break; + default: + return -ENODEV; + } + + ret = mv88e61xx_switch_reset(dev); + if (ret < 0) + return ret; + + dsa_set_tagging(dev, 0, 0); + + return 0; +} + +static const struct udevice_id mv88e61xx_ids[] = { + { .compatible = "marvell,mv88e6085" }, + { } +}; + +U_BOOT_DRIVER(mv88e61xx) = { + .name = "mv88e61xx", + .id = UCLASS_DSA, + .of_match = mv88e61xx_ids, + .probe = mv88e61xx_dsa_probe, + .ops = &mv88e61xx_dsa_ops, + .priv_auto = sizeof(struct mv88e61xx_priv), +}; -- 2.17.1