Utilize the Felix and Ocelot drivers to allow control of the VSC7511, VSC7512, VSC7513 and VSC7514 chips from an external CPU over SPI. Signed-off-by: Colin Foster <colin.foster@xxxxxxxxxxxxxxxx> --- drivers/net/dsa/ocelot/Kconfig | 15 + drivers/net/dsa/ocelot/Makefile | 6 + drivers/net/dsa/ocelot/ocelot_vsc7512_spi.c | 946 ++++++++++++++++++++ drivers/net/ethernet/mscc/ocelot.c | 8 + include/soc/mscc/ocelot.h | 26 + include/soc/mscc/vsc7514_regs.h | 16 +- 6 files changed, 1009 insertions(+), 8 deletions(-) create mode 100644 drivers/net/dsa/ocelot/ocelot_vsc7512_spi.c diff --git a/drivers/net/dsa/ocelot/Kconfig b/drivers/net/dsa/ocelot/Kconfig index 220b0b027b55..43982909b6bf 100644 --- a/drivers/net/dsa/ocelot/Kconfig +++ b/drivers/net/dsa/ocelot/Kconfig @@ -15,6 +15,21 @@ config NET_DSA_MSCC_FELIX This driver supports the VSC9959 (Felix) switch, which is embedded as a PCIe function of the NXP LS1028A ENETC RCiEP. +config NET_DSA_MSCC_OCELOT_SPI + tristate "Ocelot Ethernet SPI switch support" + depends on NET_DSA && SPI + depends on NET_VENDOR_MICROSEMI + select MDIO_MSCC_MIIM + select PINCTRL_MICROCHIP_SGPIO + select PINCTRL_OCELOT + select MSCC_OCELOT_SWITCH_LIB + select NET_DSA_TAG_OCELOT_8021Q + select NET_DSA_TAG_OCELOT + help + This driver supports the VSC7511, VSC7512, VSC7513 and VSC7514 chips + when controlled through SPI. It can be used with the Microsemi dev + boards and an external CPU or custom hardware. + config NET_DSA_MSCC_SEVILLE tristate "Ocelot / Seville Ethernet switch support" depends on NET_DSA diff --git a/drivers/net/dsa/ocelot/Makefile b/drivers/net/dsa/ocelot/Makefile index 34b9b128efb8..6ccd5482de7b 100644 --- a/drivers/net/dsa/ocelot/Makefile +++ b/drivers/net/dsa/ocelot/Makefile @@ -1,11 +1,17 @@ # SPDX-License-Identifier: GPL-2.0-only obj-$(CONFIG_NET_DSA_MSCC_FELIX) += mscc_felix.o +obj-$(CONFIG_NET_DSA_MSCC_OCELOT_SPI) += mscc_ocelot_spi.o obj-$(CONFIG_NET_DSA_MSCC_SEVILLE) += mscc_seville.o mscc_felix-objs := \ felix.o \ felix_vsc9959.o +mscc_ocelot_spi-objs := \ + felix.o \ + felix_mdio.o \ + ocelot_vsc7512_spi.o + mscc_seville-objs := \ felix.o \ felix_mdio.o \ diff --git a/drivers/net/dsa/ocelot/ocelot_vsc7512_spi.c b/drivers/net/dsa/ocelot/ocelot_vsc7512_spi.c new file mode 100644 index 000000000000..c4a6f4b7a717 --- /dev/null +++ b/drivers/net/dsa/ocelot/ocelot_vsc7512_spi.c @@ -0,0 +1,946 @@ +// SPDX-License-Identifier: (GPL-2.0 OR MIT) +/* Copyright 2017 Microsemi Corporation + * Copyright 2018-2019 NXP Semiconductors + * Copyright 2021 Innovative Advantage Inc. + */ + +#include <asm/byteorder.h> +#include <linux/spi/spi.h> +#include <linux/iopoll.h> +#include <linux/kconfig.h> +#include <linux/mdio.h> +#include <linux/phylink.h> +#include <linux/pinctrl/pinctrl.h> +#include <linux/regmap.h> +#include <soc/mscc/ocelot_ana.h> +#include <soc/mscc/ocelot_dev.h> +#include <soc/mscc/ocelot_qsys.h> +#include <soc/mscc/ocelot_vcap.h> +#include <soc/mscc/ocelot_ptp.h> +#include <soc/mscc/ocelot_sys.h> +#include <soc/mscc/ocelot.h> +#include <soc/mscc/vsc7514_regs.h> +#include "felix.h" +#include "felix_mdio.h" + +struct ocelot_spi_data { + int spi_padding_bytes; + struct felix felix; + struct spi_device *spi; +}; + +static const u32 vsc7512_dev_cpuorg_regmap[] = { + REG(DEV_CPUORG_IF_CTRL, 0x0000), + REG(DEV_CPUORG_IF_CFGSTAT, 0x0004), + REG(DEV_CPUORG_ORG_CFG, 0x0008), + REG(DEV_CPUORG_ERR_CNTS, 0x000c), + REG(DEV_CPUORG_TIMEOUT_CFG, 0x0010), + REG(DEV_CPUORG_GPR, 0x0014), + REG(DEV_CPUORG_MAILBOX_SET, 0x0018), + REG(DEV_CPUORG_MAILBOX_CLR, 0x001c), + REG(DEV_CPUORG_MAILBOX, 0x0020), + REG(DEV_CPUORG_SEMA_CFG, 0x0024), + REG(DEV_CPUORG_SEMA0, 0x0028), + REG(DEV_CPUORG_SEMA0_OWNER, 0x002c), + REG(DEV_CPUORG_SEMA1, 0x0030), + REG(DEV_CPUORG_SEMA1_OWNER, 0x0034), +}; + +static const u32 vsc7512_gcb_regmap[] = { + REG(GCB_SOFT_RST, 0x0008), + REG(GCB_GPIO_GPIO_OUT_SET, 0x0034), + REG(GCB_GPIO_GPIO_OUT_CLR, 0x0038), + REG(GCB_GPIO_GPIO_OUT, 0x003c), + REG(GCB_GPIO_GPIO_IN, 0x0040), + REG(GCB_GPIO_GPIO_OE, 0x0044), + REG(GCB_GPIO_GPIO_ALT, 0x0054), + REG(GCB_MIIM_MII_STATUS, 0x009c), + REG(GCB_MIIM_MII_CMD, 0x00a4), + REG(GCB_MIIM_MII_DATA, 0x00a8), + REG(GCB_PHY_PHY_CFG, 0x00f0), + REG(GCB_PHY_PHY_STAT, 0x00f4), + REG(GCB_SIO_CTRL_SIO_INPUT_DATA, 0x00f8), +}; + +static const u32 *vsc7512_regmap[TARGET_MAX] = { + [ANA] = vsc7514_ana_regmap, + [QS] = vsc7514_qs_regmap, + [QSYS] = vsc7514_qsys_regmap, + [REW] = vsc7514_rew_regmap, + [SYS] = vsc7514_sys_regmap, + [S0] = vsc7514_vcap_regmap, + [S1] = vsc7514_vcap_regmap, + [S2] = vsc7514_vcap_regmap, + [PTP] = vsc7514_ptp_regmap, + [GCB] = vsc7512_gcb_regmap, + [DEV_GMII] = vsc7514_dev_gmii_regmap, + [DEV_CPUORG] = vsc7512_dev_cpuorg_regmap, +}; + +#define VSC7512_BYTE_ORDER_LE 0x00000000 +#define VSC7512_BYTE_ORDER_BE 0x81818181 +#define VSC7512_BIT_ORDER_MSB 0x00000000 +#define VSC7512_BIT_ORDER_LSB 0x42424242 + +static void ocelot_spi_reset_phys(struct ocelot *ocelot) +{ + ocelot_write(ocelot, 0, GCB_PHY_PHY_CFG); + ocelot_write(ocelot, 0x1ff, GCB_PHY_PHY_CFG); + mdelay(500); +} + +static struct ocelot_spi_data *felix_to_ocelot_spi(struct felix *felix) +{ + return container_of(felix, struct ocelot_spi_data, felix); +} + +static struct ocelot_spi_data *ocelot_to_ocelot_spi(struct ocelot *ocelot) +{ + struct felix *felix = ocelot_to_felix(ocelot); + + return felix_to_ocelot_spi(felix); +} + +static int ocelot_spi_init_bus(struct ocelot *ocelot) +{ + struct ocelot_spi_data *ocelot_spi; + struct spi_device *spi; + u32 val, check; + + ocelot_spi = ocelot_to_ocelot_spi(ocelot); + spi = ocelot_spi->spi; + + val = 0; + +#ifdef __LITTLE_ENDIAN + val |= VSC7512_BYTE_ORDER_LE; +#else + val |= VSC7512_BYTE_ORDER_BE; +#endif + + ocelot_write(ocelot, val, DEV_CPUORG_IF_CTRL); + + val = ocelot_spi->spi_padding_bytes; + ocelot_write(ocelot, val, DEV_CPUORG_IF_CFGSTAT); + + check = val | 0x02000000; + + val = ocelot_read(ocelot, DEV_CPUORG_IF_CFGSTAT); + if (check != val) { + dev_err(&spi->dev, + "Error configuring SPI bus. V: 0x%08x != 0x%08x\n", val, + check); + return -ENODEV; + } + + /* The internal copper phys need to be enabled before the mdio bus is + * scanned. + */ + ocelot_spi_reset_phys(ocelot); + + return 0; +} + +static int vsc7512_reset(struct ocelot *ocelot) +{ + int retries = 100; + int ret, val; + + ocelot_field_write(ocelot, GCB_SOFT_RST_CHIP_RST, 1); + + /* Note: This is adapted from the PCIe reset strategy. The manual doesn't + * suggest how to do a reset over SPI, and the register strategy isn't + * possible. + */ + msleep(100); + + ret = ocelot_spi_init_bus(ocelot); + if (ret) + return ret; + + regmap_field_write(ocelot->regfields[SYS_RESET_CFG_MEM_INIT], 1); + regmap_field_write(ocelot->regfields[SYS_RESET_CFG_MEM_ENA], 1); + + do { + msleep(1); + regmap_field_read(ocelot->regfields[SYS_RESET_CFG_MEM_INIT], + &val); + } while (val && --retries); + + if (!retries) + return -ETIMEDOUT; + + regmap_field_write(ocelot->regfields[SYS_RESET_CFG_CORE_ENA], 1); + + return 0; +} + +static u32 ocelot_offset_from_reg_base(struct ocelot *ocelot, u32 target, + u32 reg) +{ + return ocelot->map[target][reg & REG_MASK]; +} + +static void ocelot_spi_register_pinctrl(struct ocelot *ocelot) +{ + struct device_node *pinctrl_node; + struct device *dev = ocelot->dev; + struct regmap *regmap; + u32 pinctrl_offset; + int err; + + pinctrl_node = of_get_child_by_name(dev->of_node, "pinctrl"); + if (!pinctrl_node) + return; + + regmap = ocelot->targets[GCB]; + pinctrl_offset = ocelot_offset_from_reg_base(ocelot, GCB, + GCB_GPIO_GPIO_OUT_SET); + + err = ocelot_pinctrl_core_probe(dev, NULL, regmap, pinctrl_offset, NULL, + 0, pinctrl_node); + if (err) { + dev_info(dev, "error setting up pinctrl device\n"); + return; + } +} + +static int vsc7512_spi_bus_init(struct ocelot *ocelot) +{ + struct felix *felix = ocelot_to_felix(ocelot); + struct device *dev = ocelot->dev; + struct device_node *mdio_node; + int rval; + + rval = ocelot_spi_init_bus(ocelot); + if (rval) { + dev_err(ocelot->dev, "error initializing SPI bus\n"); + goto clear_mdio; + } + + /* Set up the pins before probing the MDIO bus */ + ocelot_spi_register_pinctrl(ocelot); + + mdio_node = of_get_child_by_name(dev->of_node, "mdio"); + if (!mdio_node) + dev_info(ocelot->dev, + "mdio children not found in device tree\n"); + + rval = felix_of_mdio_register(ocelot, mdio_node); + if (rval) + dev_err(ocelot->dev, "error registering MDIO bus\n"); + + felix->ds->slave_mii_bus = felix->imdio; + + return rval; + +clear_mdio: + felix->imdio = NULL; + return rval; +} + +static const struct ocelot_ops vsc7512_ops = { + .bus_init = vsc7512_spi_bus_init, + .reset = vsc7512_reset, + .wm_enc = ocelot_wm_enc, + .wm_dec = ocelot_wm_dec, + .wm_stat = ocelot_wm_stat, + .port_to_netdev = felix_port_to_netdev, + .netdev_to_port = felix_netdev_to_port, +}; + +/* Addresses are relative to the SPI device's base address, downshifted by 2*/ +static const struct resource vsc7512_target_io_res[TARGET_MAX] = { + [ANA] = { + .start = 0x71880000, + .end = 0x7188ffff, + .name = "ana", + }, + [QS] = { + .start = 0x71080000, + .end = 0x710800ff, + .name = "qs", + }, + [QSYS] = { + .start = 0x71800000, + .end = 0x719fffff, + .name = "qsys", + }, + [REW] = { + .start = 0x71030000, + .end = 0x7103ffff, + .name = "rew", + }, + [SYS] = { + .start = 0x71010000, + .end = 0x7101ffff, + .name = "sys", + }, + [S0] = { + .start = 0x71040000, + .end = 0x710403ff, + .name = "s0", + }, + [S1] = { + .start = 0x71050000, + .end = 0x710503ff, + .name = "s1", + }, + [S2] = { + .start = 0x71060000, + .end = 0x710603ff, + .name = "s2", + }, + [GCB] = { + .start = 0x71070000, + .end = 0x7107022b, + .name = "devcpu_gcb", + }, + [DEV_CPUORG] = { + .start = 0x71000000, + .end = 0x710003ff, + .name = "devcpu_org", + }, +}; + +static const struct resource vsc7512_port_io_res[] = { + { + .start = 0x711e0000, + .end = 0x711effff, + .name = "port0", + }, + { + .start = 0x711f0000, + .end = 0x711fffff, + .name = "port1", + }, + { + .start = 0x71200000, + .end = 0x7120ffff, + .name = "port2", + }, + { + .start = 0x71210000, + .end = 0x7121ffff, + .name = "port3", + }, + { + .start = 0x71220000, + .end = 0x7122ffff, + .name = "port4", + }, + { + .start = 0x71230000, + .end = 0x7123ffff, + .name = "port5", + }, + { + .start = 0x71240000, + .end = 0x7124ffff, + .name = "port6", + }, + { + .start = 0x71250000, + .end = 0x7125ffff, + .name = "port7", + }, + { + .start = 0x71260000, + .end = 0x7126ffff, + .name = "port8", + }, + { + .start = 0x71270000, + .end = 0x7127ffff, + .name = "port9", + }, + { + .start = 0x71280000, + .end = 0x7128ffff, + .name = "port10", + }, +}; + +static const struct reg_field vsc7512_regfields[REGFIELD_MAX] = { + [ANA_ADVLEARN_VLAN_CHK] = REG_FIELD(ANA_ADVLEARN, 11, 11), + [ANA_ADVLEARN_LEARN_MIRROR] = REG_FIELD(ANA_ADVLEARN, 0, 10), + [ANA_ANEVENTS_MSTI_DROP] = REG_FIELD(ANA_ANEVENTS, 27, 27), + [ANA_ANEVENTS_ACLKILL] = REG_FIELD(ANA_ANEVENTS, 26, 26), + [ANA_ANEVENTS_ACLUSED] = REG_FIELD(ANA_ANEVENTS, 25, 25), + [ANA_ANEVENTS_AUTOAGE] = REG_FIELD(ANA_ANEVENTS, 24, 24), + [ANA_ANEVENTS_VS2TTL1] = REG_FIELD(ANA_ANEVENTS, 23, 23), + [ANA_ANEVENTS_STORM_DROP] = REG_FIELD(ANA_ANEVENTS, 22, 22), + [ANA_ANEVENTS_LEARN_DROP] = REG_FIELD(ANA_ANEVENTS, 21, 21), + [ANA_ANEVENTS_AGED_ENTRY] = REG_FIELD(ANA_ANEVENTS, 20, 20), + [ANA_ANEVENTS_CPU_LEARN_FAILED] = REG_FIELD(ANA_ANEVENTS, 19, 19), + [ANA_ANEVENTS_AUTO_LEARN_FAILED] = REG_FIELD(ANA_ANEVENTS, 18, 18), + [ANA_ANEVENTS_LEARN_REMOVE] = REG_FIELD(ANA_ANEVENTS, 17, 17), + [ANA_ANEVENTS_AUTO_LEARNED] = REG_FIELD(ANA_ANEVENTS, 16, 16), + [ANA_ANEVENTS_AUTO_MOVED] = REG_FIELD(ANA_ANEVENTS, 15, 15), + [ANA_ANEVENTS_DROPPED] = REG_FIELD(ANA_ANEVENTS, 14, 14), + [ANA_ANEVENTS_CLASSIFIED_DROP] = REG_FIELD(ANA_ANEVENTS, 13, 13), + [ANA_ANEVENTS_CLASSIFIED_COPY] = REG_FIELD(ANA_ANEVENTS, 12, 12), + [ANA_ANEVENTS_VLAN_DISCARD] = REG_FIELD(ANA_ANEVENTS, 11, 11), + [ANA_ANEVENTS_FWD_DISCARD] = REG_FIELD(ANA_ANEVENTS, 10, 10), + [ANA_ANEVENTS_MULTICAST_FLOOD] = REG_FIELD(ANA_ANEVENTS, 9, 9), + [ANA_ANEVENTS_UNICAST_FLOOD] = REG_FIELD(ANA_ANEVENTS, 8, 8), + [ANA_ANEVENTS_DEST_KNOWN] = REG_FIELD(ANA_ANEVENTS, 7, 7), + [ANA_ANEVENTS_BUCKET3_MATCH] = REG_FIELD(ANA_ANEVENTS, 6, 6), + [ANA_ANEVENTS_BUCKET2_MATCH] = REG_FIELD(ANA_ANEVENTS, 5, 5), + [ANA_ANEVENTS_BUCKET1_MATCH] = REG_FIELD(ANA_ANEVENTS, 4, 4), + [ANA_ANEVENTS_BUCKET0_MATCH] = REG_FIELD(ANA_ANEVENTS, 3, 3), + [ANA_ANEVENTS_CPU_OPERATION] = REG_FIELD(ANA_ANEVENTS, 2, 2), + [ANA_ANEVENTS_DMAC_LOOKUP] = REG_FIELD(ANA_ANEVENTS, 1, 1), + [ANA_ANEVENTS_SMAC_LOOKUP] = REG_FIELD(ANA_ANEVENTS, 0, 0), + [ANA_TABLES_MACACCESS_B_DOM] = REG_FIELD(ANA_TABLES_MACACCESS, 18, 18), + [ANA_TABLES_MACTINDX_BUCKET] = REG_FIELD(ANA_TABLES_MACTINDX, 10, 11), + [ANA_TABLES_MACTINDX_M_INDEX] = REG_FIELD(ANA_TABLES_MACTINDX, 0, 9), + [GCB_SOFT_RST_SWC_RST] = REG_FIELD(GCB_SOFT_RST, 1, 1), + [GCB_SOFT_RST_CHIP_RST] = REG_FIELD(GCB_SOFT_RST, 0, 0), + [QSYS_TIMED_FRAME_ENTRY_TFRM_VLD] = REG_FIELD(QSYS_TIMED_FRAME_ENTRY, 20, 20), + [QSYS_TIMED_FRAME_ENTRY_TFRM_FP] = REG_FIELD(QSYS_TIMED_FRAME_ENTRY, 8, 19), + [QSYS_TIMED_FRAME_ENTRY_TFRM_PORTNO] = REG_FIELD(QSYS_TIMED_FRAME_ENTRY, 4, 7), + [QSYS_TIMED_FRAME_ENTRY_TFRM_TM_SEL] = REG_FIELD(QSYS_TIMED_FRAME_ENTRY, 1, 3), + [QSYS_TIMED_FRAME_ENTRY_TFRM_TM_T] = REG_FIELD(QSYS_TIMED_FRAME_ENTRY, 0, 0), + [SYS_RESET_CFG_CORE_ENA] = REG_FIELD(SYS_RESET_CFG, 2, 2), + [SYS_RESET_CFG_MEM_ENA] = REG_FIELD(SYS_RESET_CFG, 1, 1), + [SYS_RESET_CFG_MEM_INIT] = REG_FIELD(SYS_RESET_CFG, 0, 0), + /* Replicated per number of ports (12), register size 4 per port */ + [QSYS_SWITCH_PORT_MODE_PORT_ENA] = REG_FIELD_ID(QSYS_SWITCH_PORT_MODE, 14, 14, 12, 4), + [QSYS_SWITCH_PORT_MODE_SCH_NEXT_CFG] = REG_FIELD_ID(QSYS_SWITCH_PORT_MODE, 11, 13, 12, 4), + [QSYS_SWITCH_PORT_MODE_YEL_RSRVD] = REG_FIELD_ID(QSYS_SWITCH_PORT_MODE, 10, 10, 12, 4), + [QSYS_SWITCH_PORT_MODE_INGRESS_DROP_MODE] = REG_FIELD_ID(QSYS_SWITCH_PORT_MODE, 9, 9, 12, 4), + [QSYS_SWITCH_PORT_MODE_TX_PFC_ENA] = REG_FIELD_ID(QSYS_SWITCH_PORT_MODE, 1, 8, 12, 4), + [QSYS_SWITCH_PORT_MODE_TX_PFC_MODE] = REG_FIELD_ID(QSYS_SWITCH_PORT_MODE, 0, 0, 12, 4), + [SYS_PORT_MODE_DATA_WO_TS] = REG_FIELD_ID(SYS_PORT_MODE, 5, 6, 12, 4), + [SYS_PORT_MODE_INCL_INJ_HDR] = REG_FIELD_ID(SYS_PORT_MODE, 3, 4, 12, 4), + [SYS_PORT_MODE_INCL_XTR_HDR] = REG_FIELD_ID(SYS_PORT_MODE, 1, 2, 12, 4), + [SYS_PORT_MODE_INCL_HDR_ERR] = REG_FIELD_ID(SYS_PORT_MODE, 0, 0, 12, 4), + [SYS_PAUSE_CFG_PAUSE_START] = REG_FIELD_ID(SYS_PAUSE_CFG, 10, 18, 12, 4), + [SYS_PAUSE_CFG_PAUSE_STOP] = REG_FIELD_ID(SYS_PAUSE_CFG, 1, 9, 12, 4), + [SYS_PAUSE_CFG_PAUSE_ENA] = REG_FIELD_ID(SYS_PAUSE_CFG, 0, 1, 12, 4), + [GCB_MIIM_MII_STATUS_PENDING] = REG_FIELD(GCB_MIIM_MII_STATUS, 2, 2), + [GCB_MIIM_MII_STATUS_BUSY] = REG_FIELD(GCB_MIIM_MII_STATUS, 3, 3), +}; + +static const struct ocelot_stat_layout vsc7512_stats_layout[] = { + { .offset = 0x00, .name = "rx_octets", }, + { .offset = 0x01, .name = "rx_unicast", }, + { .offset = 0x02, .name = "rx_multicast", }, + { .offset = 0x03, .name = "rx_broadcast", }, + { .offset = 0x04, .name = "rx_shorts", }, + { .offset = 0x05, .name = "rx_fragments", }, + { .offset = 0x06, .name = "rx_jabbers", }, + { .offset = 0x07, .name = "rx_crc_align_errs", }, + { .offset = 0x08, .name = "rx_sym_errs", }, + { .offset = 0x09, .name = "rx_frames_below_65_octets", }, + { .offset = 0x0A, .name = "rx_frames_65_to_127_octets", }, + { .offset = 0x0B, .name = "rx_frames_128_to_255_octets", }, + { .offset = 0x0C, .name = "rx_frames_256_to_511_octets", }, + { .offset = 0x0D, .name = "rx_frames_512_to_1023_octets", }, + { .offset = 0x0E, .name = "rx_frames_1024_to_1526_octets", }, + { .offset = 0x0F, .name = "rx_frames_over_1526_octets", }, + { .offset = 0x10, .name = "rx_pause", }, + { .offset = 0x11, .name = "rx_control", }, + { .offset = 0x12, .name = "rx_longs", }, + { .offset = 0x13, .name = "rx_classified_drops", }, + { .offset = 0x14, .name = "rx_red_prio_0", }, + { .offset = 0x15, .name = "rx_red_prio_1", }, + { .offset = 0x16, .name = "rx_red_prio_2", }, + { .offset = 0x17, .name = "rx_red_prio_3", }, + { .offset = 0x18, .name = "rx_red_prio_4", }, + { .offset = 0x19, .name = "rx_red_prio_5", }, + { .offset = 0x1A, .name = "rx_red_prio_6", }, + { .offset = 0x1B, .name = "rx_red_prio_7", }, + { .offset = 0x1C, .name = "rx_yellow_prio_0", }, + { .offset = 0x1D, .name = "rx_yellow_prio_1", }, + { .offset = 0x1E, .name = "rx_yellow_prio_2", }, + { .offset = 0x1F, .name = "rx_yellow_prio_3", }, + { .offset = 0x20, .name = "rx_yellow_prio_4", }, + { .offset = 0x21, .name = "rx_yellow_prio_5", }, + { .offset = 0x22, .name = "rx_yellow_prio_6", }, + { .offset = 0x23, .name = "rx_yellow_prio_7", }, + { .offset = 0x24, .name = "rx_green_prio_0", }, + { .offset = 0x25, .name = "rx_green_prio_1", }, + { .offset = 0x26, .name = "rx_green_prio_2", }, + { .offset = 0x27, .name = "rx_green_prio_3", }, + { .offset = 0x28, .name = "rx_green_prio_4", }, + { .offset = 0x29, .name = "rx_green_prio_5", }, + { .offset = 0x2A, .name = "rx_green_prio_6", }, + { .offset = 0x2B, .name = "rx_green_prio_7", }, + { .offset = 0x40, .name = "tx_octets", }, + { .offset = 0x41, .name = "tx_unicast", }, + { .offset = 0x42, .name = "tx_multicast", }, + { .offset = 0x43, .name = "tx_broadcast", }, + { .offset = 0x44, .name = "tx_collision", }, + { .offset = 0x45, .name = "tx_drops", }, + { .offset = 0x46, .name = "tx_pause", }, + { .offset = 0x47, .name = "tx_frames_below_65_octets", }, + { .offset = 0x48, .name = "tx_frames_65_to_127_octets", }, + { .offset = 0x49, .name = "tx_frames_128_255_octets", }, + { .offset = 0x4A, .name = "tx_frames_256_511_octets", }, + { .offset = 0x4B, .name = "tx_frames_512_1023_octets", }, + { .offset = 0x4C, .name = "tx_frames_1024_1526_octets", }, + { .offset = 0x4D, .name = "tx_frames_over_1526_octets", }, + { .offset = 0x4E, .name = "tx_yellow_prio_0", }, + { .offset = 0x4F, .name = "tx_yellow_prio_1", }, + { .offset = 0x50, .name = "tx_yellow_prio_2", }, + { .offset = 0x51, .name = "tx_yellow_prio_3", }, + { .offset = 0x52, .name = "tx_yellow_prio_4", }, + { .offset = 0x53, .name = "tx_yellow_prio_5", }, + { .offset = 0x54, .name = "tx_yellow_prio_6", }, + { .offset = 0x55, .name = "tx_yellow_prio_7", }, + { .offset = 0x56, .name = "tx_green_prio_0", }, + { .offset = 0x57, .name = "tx_green_prio_1", }, + { .offset = 0x58, .name = "tx_green_prio_2", }, + { .offset = 0x59, .name = "tx_green_prio_3", }, + { .offset = 0x5A, .name = "tx_green_prio_4", }, + { .offset = 0x5B, .name = "tx_green_prio_5", }, + { .offset = 0x5C, .name = "tx_green_prio_6", }, + { .offset = 0x5D, .name = "tx_green_prio_7", }, + { .offset = 0x5E, .name = "tx_aged", }, + { .offset = 0x80, .name = "drop_local", }, + { .offset = 0x81, .name = "drop_tail", }, + { .offset = 0x82, .name = "drop_yellow_prio_0", }, + { .offset = 0x83, .name = "drop_yellow_prio_1", }, + { .offset = 0x84, .name = "drop_yellow_prio_2", }, + { .offset = 0x85, .name = "drop_yellow_prio_3", }, + { .offset = 0x86, .name = "drop_yellow_prio_4", }, + { .offset = 0x87, .name = "drop_yellow_prio_5", }, + { .offset = 0x88, .name = "drop_yellow_prio_6", }, + { .offset = 0x89, .name = "drop_yellow_prio_7", }, + { .offset = 0x8A, .name = "drop_green_prio_0", }, + { .offset = 0x8B, .name = "drop_green_prio_1", }, + { .offset = 0x8C, .name = "drop_green_prio_2", }, + { .offset = 0x8D, .name = "drop_green_prio_3", }, + { .offset = 0x8E, .name = "drop_green_prio_4", }, + { .offset = 0x8F, .name = "drop_green_prio_5", }, + { .offset = 0x90, .name = "drop_green_prio_6", }, + { .offset = 0x91, .name = "drop_green_prio_7", }, +}; + +static unsigned int ocelot_spi_translate_address(unsigned int reg) +{ + return cpu_to_be32((reg & 0xffffff) >> 2); +} + +struct ocelot_spi_regmap_context { + struct spi_device *spi; + u32 base; +}; + +static int ocelot_spi_reg_read(void *context, unsigned int reg, + unsigned int *val) +{ + struct ocelot_spi_regmap_context *regmap_context = context; + struct spi_transfer tx, padding, rx; + struct ocelot_spi_data *ocelot_spi; + struct spi_message msg; + struct spi_device *spi; + unsigned int addr; + u8 *tx_buf; + + WARN_ON(!val); + + spi = regmap_context->spi; + + ocelot_spi = spi_get_drvdata(spi); + + addr = ocelot_spi_translate_address(reg + regmap_context->base); + tx_buf = (u8 *)&addr; + + spi_message_init(&msg); + + memset(&tx, 0, sizeof(struct spi_transfer)); + + /* Ignore the first byte for the 24-bit address */ + tx.tx_buf = &tx_buf[1]; + tx.len = 3; + + spi_message_add_tail(&tx, &msg); + + if (ocelot_spi->spi_padding_bytes > 0) { + u8 dummy_buf[16] = {0}; + + memset(&padding, 0, sizeof(struct spi_transfer)); + + /* Just toggle the clock for padding bytes */ + padding.len = ocelot_spi->spi_padding_bytes; + padding.tx_buf = dummy_buf; + padding.dummy_data = 1; + + spi_message_add_tail(&padding, &msg); + } + + memset(&rx, 0, sizeof(struct spi_transfer)); + rx.rx_buf = val; + rx.len = 4; + + spi_message_add_tail(&rx, &msg); + + return spi_sync(spi, &msg); +} + +static int ocelot_spi_reg_write(void *context, unsigned int reg, + unsigned int val) +{ + struct ocelot_spi_regmap_context *regmap_context = context; + struct spi_transfer tx[2] = {0}; + struct spi_message msg; + struct spi_device *spi; + unsigned int addr; + u8 *tx_buf; + + spi = regmap_context->spi; + + addr = ocelot_spi_translate_address(reg + regmap_context->base); + tx_buf = (u8 *)&addr; + + spi_message_init(&msg); + + /* Ignore the first byte for the 24-bit address and set the write bit */ + tx_buf[1] |= BIT(7); + tx[0].tx_buf = &tx_buf[1]; + tx[0].len = 3; + + spi_message_add_tail(&tx[0], &msg); + + memset(&tx[1], 0, sizeof(struct spi_transfer)); + tx[1].tx_buf = &val; + tx[1].len = 4; + + spi_message_add_tail(&tx[1], &msg); + + return spi_sync(spi, &msg); +} + +static const struct regmap_config ocelot_spi_regmap_config = { + .reg_bits = 24, + .reg_stride = 4, + .val_bits = 32, + + .reg_read = ocelot_spi_reg_read, + .reg_write = ocelot_spi_reg_write, + + .max_register = 0xffffffff, + .use_single_write = true, + .use_single_read = true, + .can_multi_write = false, + + .reg_format_endian = REGMAP_ENDIAN_BIG, + .val_format_endian = REGMAP_ENDIAN_NATIVE, +}; + +static void vsc7512_phylink_validate(struct ocelot *ocelot, int port, + unsigned long *supported, + struct phylink_link_state *state) +{ + struct ocelot_port *ocelot_port = ocelot->ports[port]; + + __ETHTOOL_DECLARE_LINK_MODE_MASK(mask) = { 0, }; + + if (state->interface != PHY_INTERFACE_MODE_NA && + state->interface != ocelot_port->phy_mode) { + bitmap_zero(supported, __ETHTOOL_LINK_MODE_MASK_NBITS); + return; + } + + phylink_set_port_modes(mask); + + phylink_set(mask, Pause); + phylink_set(mask, Autoneg); + phylink_set(mask, Asym_Pause); + phylink_set(mask, 10baseT_Half); + phylink_set(mask, 10baseT_Full); + phylink_set(mask, 100baseT_Half); + phylink_set(mask, 100baseT_Full); + phylink_set(mask, 1000baseT_Half); + phylink_set(mask, 1000baseT_Full); + + bitmap_and(supported, supported, mask, __ETHTOOL_LINK_MODE_MASK_NBITS); + bitmap_and(state->advertising, state->advertising, mask, + __ETHTOOL_LINK_MODE_MASK_NBITS); +} + +static int vsc7512_prevalidate_phy_mode(struct ocelot *ocelot, int port, + phy_interface_t phy_mode) +{ + switch (phy_mode) { + case PHY_INTERFACE_MODE_INTERNAL: + if (port < 4) + return 0; + return -EOPNOTSUPP; + case PHY_INTERFACE_MODE_SGMII: + if (port < 8) + return 0; + return -EOPNOTSUPP; + case PHY_INTERFACE_MODE_QSGMII: + if (port == 7 || port == 8 || port == 10) + return 0; + return -EOPNOTSUPP; + default: + return -EOPNOTSUPP; + } +} + +static int vsc7512_port_setup_tc(struct dsa_switch *ds, int port, + enum tc_setup_type type, void *type_data) +{ + return -EOPNOTSUPP; +} + +static struct vcap_props vsc7512_vcap_props[] = { + [VCAP_ES0] = { + .action_type_width = 0, + .action_table = { + [ES0_ACTION_TYPE_NORMAL] = { + .width = 73, + .count = 1, + }, + }, + .target = S0, + .keys = vsc7514_vcap_es0_keys, + .actions = vsc7514_vcap_es0_actions, + }, + [VCAP_IS1] = { + .action_type_width = 0, + .action_table = { + [IS1_ACTION_TYPE_NORMAL] = { + .width = 78, + .count = 4, + }, + }, + .target = S1, + .keys = vsc7514_vcap_is1_keys, + .actions = vsc7514_vcap_is1_actions, + }, + [VCAP_IS2] = { + .action_type_width = 1, + .action_table = { + [IS2_ACTION_TYPE_NORMAL] = { + .width = 49, + .count = 2, + }, + [IS2_ACTION_TYPE_SMAC_SIP] = { + .width = 6, + .count = 4, + }, + }, + .target = S2, + .keys = vsc7514_vcap_is2_keys, + .actions = vsc7514_vcap_is2_actions, + }, +}; + +static struct regmap *vsc7512_regmap_init(struct ocelot *ocelot, + struct resource *res) +{ + struct ocelot_spi_regmap_context *context; + struct regmap_config regmap_config; + struct ocelot_spi_data *ocelot_spi; + struct regmap *regmap; + struct device *dev; + char name[32]; + + ocelot_spi = ocelot_to_ocelot_spi(ocelot); + dev = &ocelot_spi->spi->dev; + + context = devm_kzalloc(dev, sizeof(struct ocelot_spi_regmap_context), + GFP_KERNEL); + + if (IS_ERR(context)) + return ERR_CAST(context); + + context->base = res->start; + context->spi = ocelot_spi->spi; + + memcpy(®map_config, &ocelot_spi_regmap_config, + sizeof(ocelot_spi_regmap_config)); + + /* A unique bus name is required for each regmap */ + if (res->name) + snprintf(name, sizeof(name) - 1, "ocelot_spi-%s", res->name); + else + snprintf(name, sizeof(name) - 1, "ocelot_spi@0x%08x", + res->start); + + regmap_config.name = name; + regmap_config.max_register = res->end - res->start; + + regmap = devm_regmap_init(dev, NULL, context, ®map_config); + + if (IS_ERR(regmap)) + return ERR_CAST(regmap); + + return regmap; +} + +static unsigned long vsc7512_get_quirk_for_port(struct ocelot *ocelot, + int port) +{ + /* Currently Ocelot PCS is not functioning. When that happens, different + * ports will have different quirks, which will need to be addressed + * here. + */ + return 0; +} + +static const struct felix_info ocelot_spi_info = { + .target_io_res = vsc7512_target_io_res, + .port_io_res = vsc7512_port_io_res, + .regfields = vsc7512_regfields, + .map = vsc7512_regmap, + .ops = &vsc7512_ops, + .stats_layout = vsc7512_stats_layout, + .num_stats = ARRAY_SIZE(vsc7512_stats_layout), + .vcap = vsc7512_vcap_props, + .num_mact_rows = 1024, + .num_ports = 11, + .num_tx_queues = OCELOT_NUM_TC, + .mdio_bus_alloc = felix_mdio_bus_alloc, + .mdio_bus_free = felix_mdio_bus_free, + .phylink_validate = vsc7512_phylink_validate, + .prevalidate_phy_mode = vsc7512_prevalidate_phy_mode, + .port_setup_tc = vsc7512_port_setup_tc, + .init_regmap = vsc7512_regmap_init, + .get_quirk_for_port = vsc7512_get_quirk_for_port, +}; + +static void ocelot_spi_register_sgpio(struct ocelot *ocelot) +{ + struct device *dev = ocelot->dev; + struct device_node *sgpio_node; + u32 offset; + int err; + + sgpio_node = of_get_child_by_name(dev->of_node, "sgpio"); + if (!sgpio_node) + return; + + offset = ocelot_offset_from_reg_base(ocelot, GCB, + GCB_SIO_CTRL_SIO_INPUT_DATA); + err = microchip_sgpio_core_probe(dev, sgpio_node, ocelot->targets[GCB], + offset); + + if (err) + dev_info(dev, "error setting up sgpio device\n"); +} + +static int ocelot_spi_probe(struct spi_device *spi) +{ + struct ocelot_spi_data *ocelot_spi; + struct dsa_switch *ds; + struct ocelot *ocelot; + struct felix *felix; + struct device *dev; + int err; + + dev = &spi->dev; + + ocelot_spi = devm_kzalloc(dev, sizeof(struct ocelot_spi_data), + GFP_KERNEL); + + if (!ocelot_spi) + return -ENOMEM; + + if (spi->max_speed_hz <= 500000) { + ocelot_spi->spi_padding_bytes = 0; + } else { + /* Calculation taken from the manual for IF_CFGSTAT:IF_CFG. Err + * on the side of more padding bytes, as having too few can be + * difficult to detect at runtime. + */ + ocelot_spi->spi_padding_bytes = 1 + + (spi->max_speed_hz / 1000000 + 2) / 8; + } + + dev_set_drvdata(dev, ocelot_spi); + ocelot_spi->spi = spi; + + spi->bits_per_word = 8; + + err = spi_setup(spi); + if (err < 0) { + dev_err(&spi->dev, "Error %d initializing SPI\n", err); + return err; + } + + felix = &ocelot_spi->felix; + + ocelot = &felix->ocelot; + ocelot->dev = dev; + + ocelot->num_flooding_pgids = 1; + + felix->info = &ocelot_spi_info; + + ds = kzalloc(sizeof(*ds), GFP_KERNEL); + if (!ds) { + err = -ENOMEM; + dev_err(dev, "Failed to allocate DSA switch\n"); + return err; + } + + ds->dev = &spi->dev; + ds->num_ports = felix->info->num_ports; + ds->num_tx_queues = felix->info->num_tx_queues; + + ds->ops = &felix_switch_ops; + ds->priv = ocelot; + felix->ds = ds; + felix->tag_proto = DSA_TAG_PROTO_OCELOT; + + err = dsa_register_switch(ds); + + if (err) { + dev_err(dev, "Failed to register DSA switch: %d\n", err); + goto err_register_ds; + } + + ocelot_spi_register_sgpio(ocelot); + + return 0; + +err_register_ds: + kfree(ds); + return err; +} + +static int ocelot_spi_remove(struct spi_device *spi) +{ + struct ocelot_spi_data *ocelot_spi; + struct felix *felix; + + ocelot_spi = spi_get_drvdata(spi); + felix = &ocelot_spi->felix; + + dsa_unregister_switch(felix->ds); + + kfree(felix->ds); + + devm_kfree(&spi->dev, ocelot_spi); + + return 0; +} + +const struct of_device_id vsc7512_of_match[] = { + { .compatible = "mscc,vsc7514" }, + { .compatible = "mscc,vsc7513" }, + { .compatible = "mscc,vsc7512" }, + { .compatible = "mscc,vsc7511" }, + { }, +}; +MODULE_DEVICE_TABLE(of, vsc7512_of_match); + +static struct spi_driver ocelot_vsc7512_spi_driver = { + .driver = { + .name = "vsc7512", + .of_match_table = of_match_ptr(vsc7512_of_match), + }, + .probe = ocelot_spi_probe, + .remove = ocelot_spi_remove, +}; +module_spi_driver(ocelot_vsc7512_spi_driver); + +MODULE_DESCRIPTION("Ocelot Switch SPI driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/net/ethernet/mscc/ocelot.c b/drivers/net/ethernet/mscc/ocelot.c index e6c18b598d5c..5a4c046b5e20 100644 --- a/drivers/net/ethernet/mscc/ocelot.c +++ b/drivers/net/ethernet/mscc/ocelot.c @@ -2243,6 +2243,14 @@ int ocelot_init(struct ocelot *ocelot) int i, ret; u32 port; + if (ocelot->ops->bus_init) { + ret = ocelot->ops->bus_init(ocelot); + if (ret) { + dev_err(ocelot->dev, "Bus init failed\n"); + return ret; + } + } + if (ocelot->ops->reset) { ret = ocelot->ops->reset(ocelot); if (ret) { diff --git a/include/soc/mscc/ocelot.h b/include/soc/mscc/ocelot.h index 8c27f8f79fff..6aeb7eac73f5 100644 --- a/include/soc/mscc/ocelot.h +++ b/include/soc/mscc/ocelot.h @@ -121,6 +121,7 @@ enum ocelot_target { PTP, GCB, DEV_GMII, + DEV_CPUORG, TARGET_MAX, }; @@ -396,9 +397,18 @@ enum ocelot_reg { PTP_CLK_CFG_ADJ_CFG, PTP_CLK_CFG_ADJ_FREQ, GCB_SOFT_RST = GCB << TARGET_OFFSET, + GCB_GPIO_GPIO_OUT_SET, + GCB_GPIO_GPIO_OUT_CLR, + GCB_GPIO_GPIO_OUT, + GCB_GPIO_GPIO_IN, + GCB_GPIO_GPIO_OE, + GCB_GPIO_GPIO_ALT, GCB_MIIM_MII_STATUS, GCB_MIIM_MII_CMD, GCB_MIIM_MII_DATA, + GCB_PHY_PHY_CFG, + GCB_PHY_PHY_STAT, + GCB_SIO_CTRL_SIO_INPUT_DATA, DEV_CLOCK_CFG = DEV_GMII << TARGET_OFFSET, DEV_PORT_MISC, DEV_EVENTS, @@ -438,6 +448,20 @@ enum ocelot_reg { PCS1G_TSTPAT_STATUS, DEV_PCS_FX100_CFG, DEV_PCS_FX100_STATUS, + DEV_CPUORG_IF_CTRL = DEV_CPUORG << TARGET_OFFSET, + DEV_CPUORG_IF_CFGSTAT, + DEV_CPUORG_ORG_CFG, + DEV_CPUORG_ERR_CNTS, + DEV_CPUORG_TIMEOUT_CFG, + DEV_CPUORG_GPR, + DEV_CPUORG_MAILBOX_SET, + DEV_CPUORG_MAILBOX_CLR, + DEV_CPUORG_MAILBOX, + DEV_CPUORG_SEMA_CFG, + DEV_CPUORG_SEMA0, + DEV_CPUORG_SEMA0_OWNER, + DEV_CPUORG_SEMA1, + DEV_CPUORG_SEMA1_OWNER, }; enum ocelot_regfield { @@ -496,6 +520,7 @@ enum ocelot_regfield { SYS_RESET_CFG_MEM_ENA, SYS_RESET_CFG_MEM_INIT, GCB_SOFT_RST_SWC_RST, + GCB_SOFT_RST_CHIP_RST, GCB_MIIM_MII_STATUS_PENDING, GCB_MIIM_MII_STATUS_BUSY, SYS_PAUSE_CFG_PAUSE_START, @@ -552,6 +577,7 @@ struct ocelot; struct ocelot_ops { struct net_device *(*port_to_netdev)(struct ocelot *ocelot, int port); int (*netdev_to_port)(struct net_device *dev); + int (*bus_init)(struct ocelot *ocelot); int (*reset)(struct ocelot *ocelot); u16 (*wm_enc)(u16 value); u16 (*wm_dec)(u16 value); diff --git a/include/soc/mscc/vsc7514_regs.h b/include/soc/mscc/vsc7514_regs.h index c39f64079a0f..98743e252012 100644 --- a/include/soc/mscc/vsc7514_regs.h +++ b/include/soc/mscc/vsc7514_regs.h @@ -8,14 +8,14 @@ #ifndef VSC7514_REGS_H #define VSC7514_REGS_H -extern const u32 ocelot_ana_regmap[]; -extern const u32 ocelot_qs_regmap[]; -extern const u32 ocelot_qsys_regmap[]; -extern const u32 ocelot_rew_regmap[]; -extern const u32 ocelot_sys_regmap[]; -extern const u32 ocelot_vcap_regmap[]; -extern const u32 ocelot_ptp_regmap[]; -extern const u32 ocelot_dev_gmii_regmap[]; +extern const u32 vsc7514_ana_regmap[]; +extern const u32 vsc7514_qs_regmap[]; +extern const u32 vsc7514_qsys_regmap[]; +extern const u32 vsc7514_rew_regmap[]; +extern const u32 vsc7514_sys_regmap[]; +extern const u32 vsc7514_vcap_regmap[]; +extern const u32 vsc7514_ptp_regmap[]; +extern const u32 vsc7514_dev_gmii_regmap[]; extern const struct vcap_field vsc7514_vcap_es0_keys[]; extern const struct vcap_field vsc7514_vcap_es0_actions[]; -- 2.25.1