diff options
Diffstat (limited to 'drivers/net/dsa')
45 files changed, 3017 insertions, 970 deletions
diff --git a/drivers/net/dsa/Kconfig b/drivers/net/dsa/Kconfig index b91e78e3598f..f6232ce8481f 100644 --- a/drivers/net/dsa/Kconfig +++ b/drivers/net/dsa/Kconfig @@ -99,8 +99,8 @@ config NET_DSA_SMSC_LAN9303_MDIO for MDIO managed mode. config NET_DSA_VITESSE_VSC73XX - tristate "Vitesse VSC7385/7388/7395/7398 support" - depends on OF && SPI + tristate + depends on OF depends on NET_DSA select FIXED_PHY select VITESSE_PHY @@ -109,4 +109,24 @@ config NET_DSA_VITESSE_VSC73XX This enables support for the Vitesse VSC7385, VSC7388, VSC7395 and VSC7398 SparX integrated ethernet switches. +config NET_DSA_VITESSE_VSC73XX_SPI + tristate "Vitesse VSC7385/7388/7395/7398 SPI mode support" + depends on OF + depends on NET_DSA + depends on SPI + select NET_DSA_VITESSE_VSC73XX + ---help--- + This enables support for the Vitesse VSC7385, VSC7388, VSC7395 + and VSC7398 SparX integrated ethernet switches in SPI managed mode. + +config NET_DSA_VITESSE_VSC73XX_PLATFORM + tristate "Vitesse VSC7385/7388/7395/7398 Platform mode support" + depends on OF + depends on NET_DSA + depends on HAS_IOMEM + select NET_DSA_VITESSE_VSC73XX + ---help--- + This enables support for the Vitesse VSC7385, VSC7388, VSC7395 + and VSC7398 SparX integrated ethernet switches, connected over + a CPU-attached address bus and work in memory-mapped I/O mode. endmenu diff --git a/drivers/net/dsa/Makefile b/drivers/net/dsa/Makefile index d99dc6de0006..ae70b79628d6 100644 --- a/drivers/net/dsa/Makefile +++ b/drivers/net/dsa/Makefile @@ -14,7 +14,9 @@ realtek-smi-objs := realtek-smi-core.o rtl8366.o rtl8366rb.o obj-$(CONFIG_NET_DSA_SMSC_LAN9303) += lan9303-core.o obj-$(CONFIG_NET_DSA_SMSC_LAN9303_I2C) += lan9303_i2c.o obj-$(CONFIG_NET_DSA_SMSC_LAN9303_MDIO) += lan9303_mdio.o -obj-$(CONFIG_NET_DSA_VITESSE_VSC73XX) += vitesse-vsc73xx.o +obj-$(CONFIG_NET_DSA_VITESSE_VSC73XX) += vitesse-vsc73xx-core.o +obj-$(CONFIG_NET_DSA_VITESSE_VSC73XX_PLATFORM) += vitesse-vsc73xx-platform.o +obj-$(CONFIG_NET_DSA_VITESSE_VSC73XX_SPI) += vitesse-vsc73xx-spi.o obj-y += b53/ obj-y += microchip/ obj-y += mv88e6xxx/ diff --git a/drivers/net/dsa/b53/b53_common.c b/drivers/net/dsa/b53/b53_common.c index c8040ecf4425..907af62846ba 100644 --- a/drivers/net/dsa/b53/b53_common.c +++ b/drivers/net/dsa/b53/b53_common.c @@ -955,13 +955,13 @@ static int b53_setup(struct dsa_switch *ds) if (ret) dev_err(ds->dev, "failed to apply configuration\n"); - /* Configure IMP/CPU port, disable unused ports. Enabled + /* Configure IMP/CPU port, disable all other ports. Enabled * ports will be configured with .port_enable */ for (port = 0; port < dev->num_ports; port++) { if (dsa_is_cpu_port(ds, port)) b53_enable_cpu_port(dev, port); - else if (dsa_is_unused_port(ds, port)) + else b53_disable_port(ds, port); } diff --git a/drivers/net/dsa/microchip/Kconfig b/drivers/net/dsa/microchip/Kconfig index 2c3a6751bdaf..fe0a13b79c4b 100644 --- a/drivers/net/dsa/microchip/Kconfig +++ b/drivers/net/dsa/microchip/Kconfig @@ -13,5 +13,6 @@ menuconfig NET_DSA_MICROCHIP_KSZ9477 config NET_DSA_MICROCHIP_KSZ9477_SPI tristate "KSZ9477 series SPI connected switch driver" depends on NET_DSA_MICROCHIP_KSZ9477 && SPI + select REGMAP_SPI help Select to enable support for registering switches configured through SPI. diff --git a/drivers/net/dsa/microchip/ksz9477.c b/drivers/net/dsa/microchip/ksz9477.c index c026d15721f6..a8c97f7a79b7 100644 --- a/drivers/net/dsa/microchip/ksz9477.c +++ b/drivers/net/dsa/microchip/ksz9477.c @@ -65,51 +65,36 @@ static const struct { { 0x83, "tx_discards" }, }; -static void ksz9477_cfg32(struct ksz_device *dev, u32 addr, u32 bits, bool set) +static void ksz_cfg(struct ksz_device *dev, u32 addr, u8 bits, bool set) { - u32 data; + regmap_update_bits(dev->regmap[0], addr, bits, set ? bits : 0); +} - ksz_read32(dev, addr, &data); - if (set) - data |= bits; - else - data &= ~bits; - ksz_write32(dev, addr, data); +static void ksz_port_cfg(struct ksz_device *dev, int port, int offset, u8 bits, + bool set) +{ + regmap_update_bits(dev->regmap[0], PORT_CTRL_ADDR(port, offset), + bits, set ? bits : 0); +} + +static void ksz9477_cfg32(struct ksz_device *dev, u32 addr, u32 bits, bool set) +{ + regmap_update_bits(dev->regmap[2], addr, bits, set ? bits : 0); } static void ksz9477_port_cfg32(struct ksz_device *dev, int port, int offset, u32 bits, bool set) { - u32 addr; - u32 data; - - addr = PORT_CTRL_ADDR(port, offset); - ksz_read32(dev, addr, &data); - - if (set) - data |= bits; - else - data &= ~bits; - - ksz_write32(dev, addr, data); + regmap_update_bits(dev->regmap[2], PORT_CTRL_ADDR(port, offset), + bits, set ? bits : 0); } -static int ksz9477_wait_vlan_ctrl_ready(struct ksz_device *dev, u32 waiton, - int timeout) +static int ksz9477_wait_vlan_ctrl_ready(struct ksz_device *dev) { - u8 data; + unsigned int val; - do { - ksz_read8(dev, REG_SW_VLAN_CTRL, &data); - if (!(data & waiton)) - break; - usleep_range(1, 10); - } while (timeout-- > 0); - - if (timeout <= 0) - return -ETIMEDOUT; - - return 0; + return regmap_read_poll_timeout(dev->regmap[0], REG_SW_VLAN_CTRL, + val, !(val & VLAN_START), 10, 1000); } static int ksz9477_get_vlan_table(struct ksz_device *dev, u16 vid, @@ -123,8 +108,8 @@ static int ksz9477_get_vlan_table(struct ksz_device *dev, u16 vid, ksz_write8(dev, REG_SW_VLAN_CTRL, VLAN_READ | VLAN_START); /* wait to be cleared */ - ret = ksz9477_wait_vlan_ctrl_ready(dev, VLAN_START, 1000); - if (ret < 0) { + ret = ksz9477_wait_vlan_ctrl_ready(dev); + if (ret) { dev_dbg(dev->dev, "Failed to read vlan table\n"); goto exit; } @@ -156,8 +141,8 @@ static int ksz9477_set_vlan_table(struct ksz_device *dev, u16 vid, ksz_write8(dev, REG_SW_VLAN_CTRL, VLAN_START | VLAN_WRITE); /* wait to be cleared */ - ret = ksz9477_wait_vlan_ctrl_ready(dev, VLAN_START, 1000); - if (ret < 0) { + ret = ksz9477_wait_vlan_ctrl_ready(dev); + if (ret) { dev_dbg(dev->dev, "Failed to write vlan table\n"); goto exit; } @@ -191,55 +176,35 @@ static void ksz9477_write_table(struct ksz_device *dev, u32 *table) ksz_write32(dev, REG_SW_ALU_VAL_D, table[3]); } -static int ksz9477_wait_alu_ready(struct ksz_device *dev, u32 waiton, - int timeout) +static int ksz9477_wait_alu_ready(struct ksz_device *dev) { - u32 data; - - do { - ksz_read32(dev, REG_SW_ALU_CTRL__4, &data); - if (!(data & waiton)) - break; - usleep_range(1, 10); - } while (timeout-- > 0); + unsigned int val; - if (timeout <= 0) - return -ETIMEDOUT; - - return 0; + return regmap_read_poll_timeout(dev->regmap[2], REG_SW_ALU_CTRL__4, + val, !(val & ALU_START), 10, 1000); } -static int ksz9477_wait_alu_sta_ready(struct ksz_device *dev, u32 waiton, - int timeout) +static int ksz9477_wait_alu_sta_ready(struct ksz_device *dev) { - u32 data; + unsigned int val; - do { - ksz_read32(dev, REG_SW_ALU_STAT_CTRL__4, &data); - if (!(data & waiton)) - break; - usleep_range(1, 10); - } while (timeout-- > 0); - - if (timeout <= 0) - return -ETIMEDOUT; - - return 0; + return regmap_read_poll_timeout(dev->regmap[2], + REG_SW_ALU_STAT_CTRL__4, + val, !(val & ALU_STAT_START), + 10, 1000); } static int ksz9477_reset_switch(struct ksz_device *dev) { u8 data8; - u16 data16; u32 data32; /* reset switch */ ksz_cfg(dev, REG_SW_OPERATION, SW_RESET, true); /* turn off SPI DO Edge select */ - ksz_read8(dev, REG_SW_GLOBAL_SERIAL_CTRL_0, &data8); - data8 &= ~SPI_AUTO_EDGE_DETECTION; - ksz_write8(dev, REG_SW_GLOBAL_SERIAL_CTRL_0, data8); + regmap_update_bits(dev->regmap[0], REG_SW_GLOBAL_SERIAL_CTRL_0, + SPI_AUTO_EDGE_DETECTION, 0); /* default configuration */ ksz_read8(dev, REG_SW_LUE_CTRL_1, &data8); @@ -253,10 +218,14 @@ static int ksz9477_reset_switch(struct ksz_device *dev) ksz_read32(dev, REG_SW_PORT_INT_STATUS__4, &data32); /* set broadcast storm protection 10% rate */ - ksz_read16(dev, REG_SW_MAC_CTRL_2, &data16); - data16 &= ~BROADCAST_STORM_RATE; - data16 |= (BROADCAST_STORM_VALUE * BROADCAST_STORM_PROT_RATE) / 100; - ksz_write16(dev, REG_SW_MAC_CTRL_2, data16); + regmap_update_bits(dev->regmap[1], REG_SW_MAC_CTRL_2, + BROADCAST_STORM_RATE, + (BROADCAST_STORM_VALUE * + BROADCAST_STORM_PROT_RATE) / 100); + + if (dev->synclko_125) + ksz_write8(dev, REG_SW_GLOBAL_OUTPUT_CTRL__1, + SW_ENABLE_REFCLKO | SW_REFCLKO_IS_125MHZ); return 0; } @@ -264,12 +233,8 @@ static int ksz9477_reset_switch(struct ksz_device *dev) static void ksz9477_r_mib_cnt(struct ksz_device *dev, int port, u16 addr, u64 *cnt) { - struct ksz_poll_ctx ctx = { - .dev = dev, - .port = port, - .offset = REG_PORT_MIB_CTRL_STAT__4, - }; struct ksz_port *p = &dev->ports[port]; + unsigned int val; u32 data; int ret; @@ -279,11 +244,11 @@ static void ksz9477_r_mib_cnt(struct ksz_device *dev, int port, u16 addr, data |= (addr << MIB_COUNTER_INDEX_S); ksz_pwrite32(dev, port, REG_PORT_MIB_CTRL_STAT__4, data); - ret = readx_poll_timeout(ksz_pread32_poll, &ctx, data, - !(data & MIB_COUNTER_READ), 10, 1000); - + ret = regmap_read_poll_timeout(dev->regmap[2], + PORT_CTRL_ADDR(port, REG_PORT_MIB_CTRL_STAT__4), + val, !(val & MIB_COUNTER_READ), 10, 1000); /* failed to read MIB. get out of loop */ - if (ret < 0) { + if (ret) { dev_dbg(dev->dev, "Failed to get MIB\n"); return; } @@ -518,10 +483,10 @@ static void ksz9477_flush_dyn_mac_table(struct ksz_device *dev, int port) { u8 data; - ksz_read8(dev, REG_SW_LUE_CTRL_2, &data); - data &= ~(SW_FLUSH_OPTION_M << SW_FLUSH_OPTION_S); - data |= (SW_FLUSH_OPTION_DYN_MAC << SW_FLUSH_OPTION_S); - ksz_write8(dev, REG_SW_LUE_CTRL_2, data); + regmap_update_bits(dev->regmap[0], REG_SW_LUE_CTRL_2, + SW_FLUSH_OPTION_M << SW_FLUSH_OPTION_S, + SW_FLUSH_OPTION_DYN_MAC << SW_FLUSH_OPTION_S); + if (port < dev->mib_port_cnt) { /* flush individual port */ ksz_pread8(dev, port, P_STP_CTRL, &data); @@ -648,8 +613,8 @@ static int ksz9477_port_fdb_add(struct dsa_switch *ds, int port, ksz_write32(dev, REG_SW_ALU_CTRL__4, ALU_READ | ALU_START); /* wait to be finished */ - ret = ksz9477_wait_alu_ready(dev, ALU_START, 1000); - if (ret < 0) { + ret = ksz9477_wait_alu_ready(dev); + if (ret) { dev_dbg(dev->dev, "Failed to read ALU\n"); goto exit; } @@ -672,8 +637,8 @@ static int ksz9477_port_fdb_add(struct dsa_switch *ds, int port, ksz_write32(dev, REG_SW_ALU_CTRL__4, ALU_WRITE | ALU_START); /* wait to be finished */ - ret = ksz9477_wait_alu_ready(dev, ALU_START, 1000); - if (ret < 0) + ret = ksz9477_wait_alu_ready(dev); + if (ret) dev_dbg(dev->dev, "Failed to write ALU\n"); exit: @@ -705,8 +670,8 @@ static int ksz9477_port_fdb_del(struct dsa_switch *ds, int port, ksz_write32(dev, REG_SW_ALU_CTRL__4, ALU_READ | ALU_START); /* wait to be finished */ - ret = ksz9477_wait_alu_ready(dev, ALU_START, 1000); - if (ret < 0) { + ret = ksz9477_wait_alu_ready(dev); + if (ret) { dev_dbg(dev->dev, "Failed to read ALU\n"); goto exit; } @@ -739,8 +704,8 @@ static int ksz9477_port_fdb_del(struct dsa_switch *ds, int port, ksz_write32(dev, REG_SW_ALU_CTRL__4, ALU_WRITE | ALU_START); /* wait to be finished */ - ret = ksz9477_wait_alu_ready(dev, ALU_START, 1000); - if (ret < 0) + ret = ksz9477_wait_alu_ready(dev); + if (ret) dev_dbg(dev->dev, "Failed to write ALU\n"); exit: @@ -846,7 +811,7 @@ static void ksz9477_port_mdb_add(struct dsa_switch *ds, int port, ksz_write32(dev, REG_SW_ALU_STAT_CTRL__4, data); /* wait to be finished */ - if (ksz9477_wait_alu_sta_ready(dev, ALU_STAT_START, 1000) < 0) { + if (ksz9477_wait_alu_sta_ready(dev)) { dev_dbg(dev->dev, "Failed to read ALU STATIC\n"); goto exit; } @@ -887,7 +852,7 @@ static void ksz9477_port_mdb_add(struct dsa_switch *ds, int port, ksz_write32(dev, REG_SW_ALU_STAT_CTRL__4, data); /* wait to be finished */ - if (ksz9477_wait_alu_sta_ready(dev, ALU_STAT_START, 1000) < 0) + if (ksz9477_wait_alu_sta_ready(dev)) dev_dbg(dev->dev, "Failed to read ALU STATIC\n"); exit: @@ -917,8 +882,8 @@ static int ksz9477_port_mdb_del(struct dsa_switch *ds, int port, ksz_write32(dev, REG_SW_ALU_STAT_CTRL__4, data); /* wait to be finished */ - ret = ksz9477_wait_alu_sta_ready(dev, ALU_STAT_START, 1000); - if (ret < 0) { + ret = ksz9477_wait_alu_sta_ready(dev); + if (ret) { dev_dbg(dev->dev, "Failed to read ALU STATIC\n"); goto exit; } @@ -959,8 +924,8 @@ static int ksz9477_port_mdb_del(struct dsa_switch *ds, int port, ksz_write32(dev, REG_SW_ALU_STAT_CTRL__4, data); /* wait to be finished */ - ret = ksz9477_wait_alu_sta_ready(dev, ALU_STAT_START, 1000); - if (ret < 0) + ret = ksz9477_wait_alu_sta_ready(dev); + if (ret) dev_dbg(dev->dev, "Failed to read ALU STATIC\n"); exit: @@ -1165,6 +1130,62 @@ static phy_interface_t ksz9477_get_interface(struct ksz_device *dev, int port) return interface; } +static void ksz9477_port_mmd_write(struct ksz_device *dev, int port, + u8 dev_addr, u16 reg_addr, u16 val) +{ + ksz_pwrite16(dev, port, REG_PORT_PHY_MMD_SETUP, + MMD_SETUP(PORT_MMD_OP_INDEX, dev_addr)); + ksz_pwrite16(dev, port, REG_PORT_PHY_MMD_INDEX_DATA, reg_addr); + ksz_pwrite16(dev, port, REG_PORT_PHY_MMD_SETUP, + MMD_SETUP(PORT_MMD_OP_DATA_NO_INCR, dev_addr)); + ksz_pwrite16(dev, port, REG_PORT_PHY_MMD_INDEX_DATA, val); +} + +static void ksz9477_phy_errata_setup(struct ksz_device *dev, int port) +{ + /* Apply PHY settings to address errata listed in + * KSZ9477, KSZ9897, KSZ9896, KSZ9567, KSZ8565 + * Silicon Errata and Data Sheet Clarification documents: + * + * Register settings are needed to improve PHY receive performance + */ + ksz9477_port_mmd_write(dev, port, 0x01, 0x6f, 0xdd0b); + ksz9477_port_mmd_write(dev, port, 0x01, 0x8f, 0x6032); + ksz9477_port_mmd_write(dev, port, 0x01, 0x9d, 0x248c); + ksz9477_port_mmd_write(dev, port, 0x01, 0x75, 0x0060); + ksz9477_port_mmd_write(dev, port, 0x01, 0xd3, 0x7777); + ksz9477_port_mmd_write(dev, port, 0x1c, 0x06, 0x3008); + ksz9477_port_mmd_write(dev, port, 0x1c, 0x08, 0x2001); + + /* Transmit waveform amplitude can be improved + * (1000BASE-T, 100BASE-TX, 10BASE-Te) + */ + ksz9477_port_mmd_write(dev, port, 0x1c, 0x04, 0x00d0); + + /* Energy Efficient Ethernet (EEE) feature select must + * be manually disabled (except on KSZ8565 which is 100Mbit) + */ + if (dev->features & GBIT_SUPPORT) + ksz9477_port_mmd_write(dev, port, 0x07, 0x3c, 0x0000); + + /* Register settings are required to meet data sheet + * supply current specifications + */ + ksz9477_port_mmd_write(dev, port, 0x1c, 0x13, 0x6eff); + ksz9477_port_mmd_write(dev, port, 0x1c, 0x14, 0xe6ff); + ksz9477_port_mmd_write(dev, port, 0x1c, 0x15, 0x6eff); + ksz9477_port_mmd_write(dev, port, 0x1c, 0x16, 0xe6ff); + ksz9477_port_mmd_write(dev, port, 0x1c, 0x17, 0x00ff); + ksz9477_port_mmd_write(dev, port, 0x1c, 0x18, 0x43ff); + ksz9477_port_mmd_write(dev, port, 0x1c, 0x19, 0xc3ff); + ksz9477_port_mmd_write(dev, port, 0x1c, 0x1a, 0x6fff); + ksz9477_port_mmd_write(dev, port, 0x1c, 0x1b, 0x07ff); + ksz9477_port_mmd_write(dev, port, 0x1c, 0x1c, 0x0fff); + ksz9477_port_mmd_write(dev, port, 0x1c, 0x1d, 0xe7ff); + ksz9477_port_mmd_write(dev, port, 0x1c, 0x1e, 0xefff); + ksz9477_port_mmd_write(dev, port, 0x1c, 0x20, 0xeeee); +} + static void ksz9477_port_setup(struct ksz_device *dev, int port, bool cpu_port) { u8 data8; @@ -1203,6 +1224,8 @@ static void ksz9477_port_setup(struct ksz_device *dev, int port, bool cpu_port) PORT_FORCE_TX_FLOW_CTRL | PORT_FORCE_RX_FLOW_CTRL, false); + if (dev->phy_errata_9477) + ksz9477_phy_errata_setup(dev, port); } else { /* force flow control */ ksz_port_cfg(dev, port, REG_PORT_CTRL_0, @@ -1474,6 +1497,7 @@ struct ksz_chip_data { int num_statics; int cpu_ports; int port_cnt; + bool phy_errata_9477; }; static const struct ksz_chip_data ksz9477_switch_chips[] = { @@ -1485,6 +1509,7 @@ static const struct ksz_chip_data ksz9477_switch_chips[] = { .num_statics = 16, .cpu_ports = 0x7F, /* can be configured as cpu port */ .port_cnt = 7, /* total physical port count */ + .phy_errata_9477 = true, }, { .chip_id = 0x00989700, @@ -1494,6 +1519,7 @@ static const struct ksz_chip_data ksz9477_switch_chips[] = { .num_statics = 16, .cpu_ports = 0x7F, /* can be configured as cpu port */ .port_cnt = 7, /* total physical port count */ + .phy_errata_9477 = true, }, { .chip_id = 0x00989300, @@ -1522,6 +1548,7 @@ static int ksz9477_switch_init(struct ksz_device *dev) dev->num_statics = chip->num_statics; dev->port_cnt = chip->port_cnt; dev->cpu_ports = chip->cpu_ports; + dev->phy_errata_9477 = chip->phy_errata_9477; break; } diff --git a/drivers/net/dsa/microchip/ksz9477_spi.c b/drivers/net/dsa/microchip/ksz9477_spi.c index 75178624d3f5..5a9e27b337a8 100644 --- a/drivers/net/dsa/microchip/ksz9477_spi.c +++ b/drivers/net/dsa/microchip/ksz9477_spi.c @@ -10,119 +10,43 @@ #include <linux/delay.h> #include <linux/kernel.h> #include <linux/module.h> +#include <linux/regmap.h> #include <linux/spi/spi.h> #include "ksz_priv.h" -#include "ksz_spi.h" - -/* SPI frame opcodes */ -#define KS_SPIOP_RD 3 -#define KS_SPIOP_WR 2 +#include "ksz_common.h" #define SPI_ADDR_SHIFT 24 -#define SPI_ADDR_MASK (BIT(SPI_ADDR_SHIFT) - 1) +#define SPI_ADDR_ALIGN 3 #define SPI_TURNAROUND_SHIFT 5 -/* Enough to read all switch port registers. */ -#define SPI_TX_BUF_LEN 0x100 - -static int ksz9477_spi_read_reg(struct spi_device *spi, u32 reg, u8 *val, - unsigned int len) -{ - u32 txbuf; - int ret; - - txbuf = reg & SPI_ADDR_MASK; - txbuf |= KS_SPIOP_RD << SPI_ADDR_SHIFT; - txbuf <<= SPI_TURNAROUND_SHIFT; - txbuf = cpu_to_be32(txbuf); - - ret = spi_write_then_read(spi, &txbuf, 4, val, len); - return ret; -} - -static int ksz9477_spi_write_reg(struct spi_device *spi, u32 reg, u8 *val, - unsigned int len) -{ - u32 *txbuf = (u32 *)val; - - *txbuf = reg & SPI_ADDR_MASK; - *txbuf |= (KS_SPIOP_WR << SPI_ADDR_SHIFT); - *txbuf <<= SPI_TURNAROUND_SHIFT; - *txbuf = cpu_to_be32(*txbuf); - - return spi_write(spi, txbuf, 4 + len); -} - -static int ksz_spi_read(struct ksz_device *dev, u32 reg, u8 *data, - unsigned int len) -{ - struct spi_device *spi = dev->priv; - - return ksz9477_spi_read_reg(spi, reg, data, len); -} - -static int ksz_spi_write(struct ksz_device *dev, u32 reg, void *data, - unsigned int len) -{ - struct spi_device *spi = dev->priv; - - if (len > SPI_TX_BUF_LEN) - len = SPI_TX_BUF_LEN; - memcpy(&dev->txbuf[4], data, len); - return ksz9477_spi_write_reg(spi, reg, dev->txbuf, len); -} - -static int ksz_spi_read24(struct ksz_device *dev, u32 reg, u32 *val) -{ - int ret; - - *val = 0; - ret = ksz_spi_read(dev, reg, (u8 *)val, 3); - if (!ret) { - *val = be32_to_cpu(*val); - /* convert to 24bit */ - *val >>= 8; - } - - return ret; -} - -static int ksz_spi_write24(struct ksz_device *dev, u32 reg, u32 value) -{ - /* make it to big endian 24bit from MSB */ - value <<= 8; - value = cpu_to_be32(value); - return ksz_spi_write(dev, reg, &value, 3); -} - -static const struct ksz_io_ops ksz9477_spi_ops = { - .read8 = ksz_spi_read8, - .read16 = ksz_spi_read16, - .read24 = ksz_spi_read24, - .read32 = ksz_spi_read32, - .write8 = ksz_spi_write8, - .write16 = ksz_spi_write16, - .write24 = ksz_spi_write24, - .write32 = ksz_spi_write32, - .get = ksz_spi_get, - .set = ksz_spi_set, -}; +KSZ_REGMAP_TABLE(ksz9477, 32, SPI_ADDR_SHIFT, + SPI_TURNAROUND_SHIFT, SPI_ADDR_ALIGN); static int ksz9477_spi_probe(struct spi_device *spi) { struct ksz_device *dev; - int ret; + int i, ret; - dev = ksz_switch_alloc(&spi->dev, &ksz9477_spi_ops, spi); + dev = ksz_switch_alloc(&spi->dev, spi); if (!dev) return -ENOMEM; + for (i = 0; i < ARRAY_SIZE(ksz9477_regmap_config); i++) { + dev->regmap[i] = devm_regmap_init_spi(spi, + &ksz9477_regmap_config[i]); + if (IS_ERR(dev->regmap[i])) { + ret = PTR_ERR(dev->regmap[i]); + dev_err(&spi->dev, + "Failed to initialize regmap%i: %d\n", + ksz9477_regmap_config[i].val_bits, ret); + return ret; + } + } + if (spi->dev.platform_data) dev->pdata = spi->dev.platform_data; - dev->txbuf = devm_kzalloc(dev->dev, 4 + SPI_TX_BUF_LEN, GFP_KERNEL); - ret = ksz9477_switch_register(dev); /* Main DSA driver may not be started yet. */ diff --git a/drivers/net/dsa/microchip/ksz_common.c b/drivers/net/dsa/microchip/ksz_common.c index db91b213eae1..a3d2d67894bd 100644 --- a/drivers/net/dsa/microchip/ksz_common.c +++ b/drivers/net/dsa/microchip/ksz_common.c @@ -396,9 +396,7 @@ void ksz_disable_port(struct dsa_switch *ds, int port) } EXPORT_SYMBOL_GPL(ksz_disable_port); -struct ksz_device *ksz_switch_alloc(struct device *base, - const struct ksz_io_ops *ops, - void *priv) +struct ksz_device *ksz_switch_alloc(struct device *base, void *priv) { struct dsa_switch *ds; struct ksz_device *swdev; @@ -416,7 +414,6 @@ struct ksz_device *ksz_switch_alloc(struct device *base, swdev->ds = ds; swdev->priv = priv; - swdev->ops = ops; return swdev; } @@ -442,7 +439,6 @@ int ksz_switch_register(struct ksz_device *dev, } mutex_init(&dev->dev_mutex); - mutex_init(&dev->reg_mutex); mutex_init(&dev->stats_mutex); mutex_init(&dev->alu_mutex); mutex_init(&dev->vlan_mutex); @@ -463,6 +459,8 @@ int ksz_switch_register(struct ksz_device *dev, ret = of_get_phy_mode(dev->dev->of_node); if (ret >= 0) dev->interface = ret; + dev->synclko_125 = of_property_read_bool(dev->dev->of_node, + "microchip,synclko-125"); } ret = dsa_register_switch(dev->ds); diff --git a/drivers/net/dsa/microchip/ksz_common.h b/drivers/net/dsa/microchip/ksz_common.h index 21cd794e18f1..ee7096d8af07 100644 --- a/drivers/net/dsa/microchip/ksz_common.h +++ b/drivers/net/dsa/microchip/ksz_common.h @@ -7,6 +7,8 @@ #ifndef __KSZ_COMMON_H #define __KSZ_COMMON_H +#include <linux/regmap.h> + void ksz_port_cleanup(struct ksz_device *dev, int port); void ksz_update_port_member(struct ksz_device *dev, int port); void ksz_init_mib_timer(struct ksz_device *dev); @@ -41,114 +43,44 @@ void ksz_disable_port(struct dsa_switch *ds, int port); static inline int ksz_read8(struct ksz_device *dev, u32 reg, u8 *val) { - int ret; - - mutex_lock(&dev->reg_mutex); - ret = dev->ops->read8(dev, reg, val); - mutex_unlock(&dev->reg_mutex); + unsigned int value; + int ret = regmap_read(dev->regmap[0], reg, &value); + *val = value; return ret; } static inline int ksz_read16(struct ksz_device *dev, u32 reg, u16 *val) { - int ret; - - mutex_lock(&dev->reg_mutex); - ret = dev->ops->read16(dev, reg, val); - mutex_unlock(&dev->reg_mutex); - - return ret; -} - -static inline int ksz_read24(struct ksz_device *dev, u32 reg, u32 *val) -{ - int ret; - - mutex_lock(&dev->reg_mutex); - ret = dev->ops->read24(dev, reg, val); - mutex_unlock(&dev->reg_mutex); + unsigned int value; + int ret = regmap_read(dev->regmap[1], reg, &value); + *val = value; return ret; } static inline int ksz_read32(struct ksz_device *dev, u32 reg, u32 *val) { - int ret; - - mutex_lock(&dev->reg_mutex); - ret = dev->ops->read32(dev, reg, val); - mutex_unlock(&dev->reg_mutex); + unsigned int value; + int ret = regmap_read(dev->regmap[2], reg, &value); + *val = value; return ret; } static inline int ksz_write8(struct ksz_device *dev, u32 reg, u8 value) { - int ret; - - mutex_lock(&dev->reg_mutex); - ret = dev->ops->write8(dev, reg, value); - mutex_unlock(&dev->reg_mutex); - - return ret; + return regmap_write(dev->regmap[0], reg, value); } static inline int ksz_write16(struct ksz_device *dev, u32 reg, u16 value) { - int ret; - - mutex_lock(&dev->reg_mutex); - ret = dev->ops->write16(dev, reg, value); - mutex_unlock(&dev->reg_mutex); - - return ret; -} - -static inline int ksz_write24(struct ksz_device *dev, u32 reg, u32 value) -{ - int ret; - - mutex_lock(&dev->reg_mutex); - ret = dev->ops->write24(dev, reg, value); - mutex_unlock(&dev->reg_mutex); - - return ret; + return regmap_write(dev->regmap[1], reg, value); } static inline int ksz_write32(struct ksz_device *dev, u32 reg, u32 value) { - int ret; - - mutex_lock(&dev->reg_mutex); - ret = dev->ops->write32(dev, reg, value); - mutex_unlock(&dev->reg_mutex); - - return ret; -} - -static inline int ksz_get(struct ksz_device *dev, u32 reg, void *data, - size_t len) -{ - int ret; - - mutex_lock(&dev->reg_mutex); - ret = dev->ops->get(dev, reg, data, len); - mutex_unlock(&dev->reg_mutex); - - return ret; -} - -static inline int ksz_set(struct ksz_device *dev, u32 reg, void *data, - size_t len) -{ - int ret; - - mutex_lock(&dev->reg_mutex); - ret = dev->ops->set(dev, reg, data, len); - mutex_unlock(&dev->reg_mutex); - - return ret; + return regmap_write(dev->regmap[2], reg, value); } static inline void ksz_pread8(struct ksz_device *dev, int port, int offset, @@ -187,47 +119,36 @@ static inline void ksz_pwrite32(struct ksz_device *dev, int port, int offset, ksz_write32(dev, dev->dev_ops->get_port_addr(port, offset), data); } -static void ksz_cfg(struct ksz_device *dev, u32 addr, u8 bits, bool set) -{ - u8 data; - - ksz_read8(dev, addr, &data); - if (set) - data |= bits; - else - data &= ~bits; - ksz_write8(dev, addr, data); -} - -static void ksz_port_cfg(struct ksz_device *dev, int port, int offset, u8 bits, - bool set) -{ - u32 addr; - u8 data; - - addr = dev->dev_ops->get_port_addr(port, offset); - ksz_read8(dev, addr, &data); - - if (set) - data |= bits; - else - data &= ~bits; - - ksz_write8(dev, addr, data); -} - -struct ksz_poll_ctx { - struct ksz_device *dev; - int port; - int offset; -}; - -static inline u32 ksz_pread32_poll(struct ksz_poll_ctx *ctx) -{ - u32 data; - - ksz_pread32(ctx->dev, ctx->port, ctx->offset, &data); - return data; -} +/* Regmap tables generation */ +#define KSZ_SPI_OP_RD 3 +#define KSZ_SPI_OP_WR 2 + +#define KSZ_SPI_OP_FLAG_MASK(opcode, swp, regbits, regpad) \ + swab##swp((opcode) << ((regbits) + (regpad))) + +#define KSZ_REGMAP_ENTRY(width, swp, regbits, regpad, regalign) \ + { \ + .val_bits = (width), \ + .reg_stride = (width) / 8, \ + .reg_bits = (regbits) + (regalign), \ + .pad_bits = (regpad), \ + .max_register = BIT(regbits) - 1, \ + .cache_type = REGCACHE_NONE, \ + .read_flag_mask = \ + KSZ_SPI_OP_FLAG_MASK(KSZ_SPI_OP_RD, swp, \ + regbits, regpad), \ + .write_flag_mask = \ + KSZ_SPI_OP_FLAG_MASK(KSZ_SPI_OP_WR, swp, \ + regbits, regpad), \ + .reg_format_endian = REGMAP_ENDIAN_BIG, \ + .val_format_endian = REGMAP_ENDIAN_BIG \ + } + +#define KSZ_REGMAP_TABLE(ksz, swp, regbits, regpad, regalign) \ + static const struct regmap_config ksz##_regmap_config[] = { \ + KSZ_REGMAP_ENTRY(8, swp, (regbits), (regpad), (regalign)), \ + KSZ_REGMAP_ENTRY(16, swp, (regbits), (regpad), (regalign)), \ + KSZ_REGMAP_ENTRY(32, swp, (regbits), (regpad), (regalign)), \ + } #endif diff --git a/drivers/net/dsa/microchip/ksz_priv.h b/drivers/net/dsa/microchip/ksz_priv.h index b52e5ca17ab4..beacf0e40f42 100644 --- a/drivers/net/dsa/microchip/ksz_priv.h +++ b/drivers/net/dsa/microchip/ksz_priv.h @@ -14,8 +14,6 @@ #include <linux/etherdevice.h> #include <net/dsa.h> -struct ksz_io_ops; - struct vlan_table { u32 table[3]; }; @@ -49,14 +47,13 @@ struct ksz_device { const char *name; struct mutex dev_mutex; /* device access */ - struct mutex reg_mutex; /* register access */ struct mutex stats_mutex; /* status access */ struct mutex alu_mutex; /* ALU access */ struct mutex vlan_mutex; /* vlan access */ - const struct ksz_io_ops *ops; const struct ksz_dev_ops *dev_ops; struct device *dev; + struct regmap *regmap[3]; void *priv; @@ -77,11 +74,11 @@ struct ksz_device { int last_port; /* ports after that not used */ phy_interface_t interface; u32 regs_size; + bool phy_errata_9477; + bool synclko_125; struct vlan_table *vlan_cache; - u8 *txbuf; - struct ksz_port *ports; struct timer_list mib_read_timer; struct work_struct mib_read; @@ -100,19 +97,6 @@ struct ksz_device { u16 port_mask; }; -struct ksz_io_ops { - int (*read8)(struct ksz_device *dev, u32 reg, u8 *value); - int (*read16)(struct ksz_device *dev, u32 reg, u16 *value); - int (*read24)(struct ksz_device *dev, u32 reg, u32 *value); - int (*read32)(struct ksz_device *dev, u32 reg, u32 *value); - int (*write8)(struct ksz_device *dev, u32 reg, u8 value); - int (*write16)(struct ksz_device *dev, u32 reg, u16 value); - int (*write24)(struct ksz_device *dev, u32 reg, u32 value); - int (*write32)(struct ksz_device *dev, u32 reg, u32 value); - int (*get)(struct ksz_device *dev, u32 reg, void *data, size_t len); - int (*set)(struct ksz_device *dev, u32 reg, void *data, size_t len); -}; - struct alu_struct { /* entry 1 */ u8 is_static:1; @@ -161,8 +145,7 @@ struct ksz_dev_ops { void (*exit)(struct ksz_device *dev); }; -struct ksz_device *ksz_switch_alloc(struct device *base, - const struct ksz_io_ops *ops, void *priv); +struct ksz_device *ksz_switch_alloc(struct device *base, void *priv); int ksz_switch_register(struct ksz_device *dev, const struct ksz_dev_ops *ops); void ksz_switch_remove(struct ksz_device *dev); diff --git a/drivers/net/dsa/microchip/ksz_spi.h b/drivers/net/dsa/microchip/ksz_spi.h deleted file mode 100644 index 427811bd60b3..000000000000 --- a/drivers/net/dsa/microchip/ksz_spi.h +++ /dev/null @@ -1,69 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0 - * Microchip KSZ series SPI access common header - * - * Copyright (C) 2017-2018 Microchip Technology Inc. - * Tristram Ha <Tristram.Ha@microchip.com> - */ - -#ifndef __KSZ_SPI_H -#define __KSZ_SPI_H - -/* Chip dependent SPI access */ -static int ksz_spi_read(struct ksz_device *dev, u32 reg, u8 *data, - unsigned int len); -static int ksz_spi_write(struct ksz_device *dev, u32 reg, void *data, - unsigned int len); - -static int ksz_spi_read8(struct ksz_device *dev, u32 reg, u8 *val) -{ - return ksz_spi_read(dev, reg, val, 1); -} - -static int ksz_spi_read16(struct ksz_device *dev, u32 reg, u16 *val) -{ - int ret = ksz_spi_read(dev, reg, (u8 *)val, 2); - - if (!ret) - *val = be16_to_cpu(*val); - - return ret; -} - -static int ksz_spi_read32(struct ksz_device *dev, u32 reg, u32 *val) -{ - int ret = ksz_spi_read(dev, reg, (u8 *)val, 4); - - if (!ret) - *val = be32_to_cpu(*val); - - return ret; -} - -static int ksz_spi_write8(struct ksz_device *dev, u32 reg, u8 value) -{ - return ksz_spi_write(dev, reg, &value, 1); -} - -static int ksz_spi_write16(struct ksz_device *dev, u32 reg, u16 value) -{ - value = cpu_to_be16(value); - return ksz_spi_write(dev, reg, &value, 2); -} - -static int ksz_spi_write32(struct ksz_device *dev, u32 reg, u32 value) -{ - value = cpu_to_be32(value); - return ksz_spi_write(dev, reg, &value, 4); -} - -static int ksz_spi_get(struct ksz_device *dev, u32 reg, void *data, size_t len) -{ - return ksz_spi_read(dev, reg, data, len); -} - -static int ksz_spi_set(struct ksz_device *dev, u32 reg, void *data, size_t len) -{ - return ksz_spi_write(dev, reg, data, len); -} - -#endif diff --git a/drivers/net/dsa/mt7530.c b/drivers/net/dsa/mt7530.c index c7d352da5448..3181e95586d6 100644 --- a/drivers/net/dsa/mt7530.c +++ b/drivers/net/dsa/mt7530.c @@ -428,24 +428,48 @@ static int mt7530_pad_clk_setup(struct dsa_switch *ds, int mode) { struct mt7530_priv *priv = ds->priv; - u32 ncpo1, ssc_delta, trgint, i; + u32 ncpo1, ssc_delta, trgint, i, xtal; + + xtal = mt7530_read(priv, MT7530_MHWTRAP) & HWTRAP_XTAL_MASK; + + if (xtal == HWTRAP_XTAL_20MHZ) { + dev_err(priv->dev, + "%s: MT7530 with a 20MHz XTAL is not supported!\n", + __func__); + return -EINVAL; + } switch (mode) { case PHY_INTERFACE_MODE_RGMII: trgint = 0; + /* PLL frequency: 125MHz */ ncpo1 = 0x0c80; - ssc_delta = 0x87; break; case PHY_INTERFACE_MODE_TRGMII: trgint = 1; - ncpo1 = 0x1400; - ssc_delta = 0x57; + if (priv->id == ID_MT7621) { + /* PLL frequency: 150MHz: 1.2GBit */ + if (xtal == HWTRAP_XTAL_40MHZ) + ncpo1 = 0x0780; + if (xtal == HWTRAP_XTAL_25MHZ) + ncpo1 = 0x0a00; + } else { /* PLL frequency: 250MHz: 2.0Gbit */ + if (xtal == HWTRAP_XTAL_40MHZ) + ncpo1 = 0x0c80; + if (xtal == HWTRAP_XTAL_25MHZ) + ncpo1 = 0x1400; + } break; default: dev_err(priv->dev, "xMII mode %d not supported\n", mode); return -EINVAL; } + if (xtal == HWTRAP_XTAL_25MHZ) + ssc_delta = 0x57; + else + ssc_delta = 0x87; + mt7530_rmw(priv, MT7530_P6ECR, P6_INTF_MODE_MASK, P6_INTF_MODE(trgint)); @@ -507,7 +531,9 @@ mt7530_pad_clk_setup(struct dsa_switch *ds, int mode) mt7530_rmw(priv, MT7530_TRGMII_RD(i), RD_TAP_MASK, RD_TAP(16)); else - mt7623_trgmii_set(priv, GSW_INTF_MODE, INTF_MODE_TRGMII); + if (priv->id != ID_MT7621) + mt7623_trgmii_set(priv, GSW_INTF_MODE, + INTF_MODE_TRGMII); return 0; } @@ -613,13 +639,13 @@ static void mt7530_adjust_link(struct dsa_switch *ds, int port, struct mt7530_priv *priv = ds->priv; if (phy_is_pseudo_fixed_link(phydev)) { - if (priv->id == ID_MT7530) { - dev_dbg(priv->dev, "phy-mode for master device = %x\n", - phydev->interface); + dev_dbg(priv->dev, "phy-mode for master device = %x\n", + phydev->interface); - /* Setup TX circuit incluing relevant PAD and driving */ - mt7530_pad_clk_setup(ds, phydev->interface); + /* Setup TX circuit incluing relevant PAD and driving */ + mt7530_pad_clk_setup(ds, phydev->interface); + if (priv->id == ID_MT7530) { /* Setup RX circuit, relevant PAD and driving on the * host which must be placed after the setup on the * device side is all finished. diff --git a/drivers/net/dsa/mt7530.h b/drivers/net/dsa/mt7530.h index 4331429969fa..bfac90f48102 100644 --- a/drivers/net/dsa/mt7530.h +++ b/drivers/net/dsa/mt7530.h @@ -244,6 +244,10 @@ enum mt7530_vlan_port_attr { /* Register for hw trap status */ #define MT7530_HWTRAP 0x7800 +#define HWTRAP_XTAL_MASK (BIT(10) | BIT(9)) +#define HWTRAP_XTAL_25MHZ (BIT(10) | BIT(9)) +#define HWTRAP_XTAL_40MHZ (BIT(10)) +#define HWTRAP_XTAL_20MHZ (BIT(9)) /* Register for hw trap modification */ #define MT7530_MHWTRAP 0x7804 diff --git a/drivers/net/dsa/mv88e6xxx/chip.c b/drivers/net/dsa/mv88e6xxx/chip.c index 063c7a671b41..6b17cd961d06 100644 --- a/drivers/net/dsa/mv88e6xxx/chip.c +++ b/drivers/net/dsa/mv88e6xxx/chip.c @@ -118,9 +118,9 @@ static irqreturn_t mv88e6xxx_g1_irq_thread_work(struct mv88e6xxx_chip *chip) u16 ctl1; int err; - mutex_lock(&chip->reg_lock); + mv88e6xxx_reg_lock(chip); err = mv88e6xxx_g1_read(chip, MV88E6XXX_G1_STS, ®); - mutex_unlock(&chip->reg_lock); + mv88e6xxx_reg_unlock(chip); if (err) goto out; @@ -135,13 +135,13 @@ static irqreturn_t mv88e6xxx_g1_irq_thread_work(struct mv88e6xxx_chip *chip) } } - mutex_lock(&chip->reg_lock); + mv88e6xxx_reg_lock(chip); err = mv88e6xxx_g1_read(chip, MV88E6XXX_G1_CTL1, &ctl1); if (err) goto unlock; err = mv88e6xxx_g1_read(chip, MV88E6XXX_G1_STS, ®); unlock: - mutex_unlock(&chip->reg_lock); + mv88e6xxx_reg_unlock(chip); if (err) goto out; ctl1 &= GENMASK(chip->g1_irq.nirqs, 0); @@ -162,7 +162,7 @@ static void mv88e6xxx_g1_irq_bus_lock(struct irq_data *d) { struct mv88e6xxx_chip *chip = irq_data_get_irq_chip_data(d); - mutex_lock(&chip->reg_lock); + mv88e6xxx_reg_lock(chip); } static void mv88e6xxx_g1_irq_bus_sync_unlock(struct irq_data *d) @@ -184,7 +184,7 @@ static void mv88e6xxx_g1_irq_bus_sync_unlock(struct irq_data *d) goto out; out: - mutex_unlock(&chip->reg_lock); + mv88e6xxx_reg_unlock(chip); } static const struct irq_chip mv88e6xxx_g1_irq_chip = { @@ -239,9 +239,9 @@ static void mv88e6xxx_g1_irq_free(struct mv88e6xxx_chip *chip) */ free_irq(chip->irq, chip); - mutex_lock(&chip->reg_lock); + mv88e6xxx_reg_lock(chip); mv88e6xxx_g1_irq_free_common(chip); - mutex_unlock(&chip->reg_lock); + mv88e6xxx_reg_unlock(chip); } static int mv88e6xxx_g1_irq_setup_common(struct mv88e6xxx_chip *chip) @@ -310,12 +310,12 @@ static int mv88e6xxx_g1_irq_setup(struct mv88e6xxx_chip *chip) */ irq_set_lockdep_class(chip->irq, &lock_key, &request_key); - mutex_unlock(&chip->reg_lock); + mv88e6xxx_reg_unlock(chip); err = request_threaded_irq(chip->irq, NULL, mv88e6xxx_g1_irq_thread_fn, IRQF_ONESHOT | IRQF_SHARED, dev_name(chip->dev), chip); - mutex_lock(&chip->reg_lock); + mv88e6xxx_reg_lock(chip); if (err) mv88e6xxx_g1_irq_free_common(chip); @@ -359,9 +359,9 @@ static void mv88e6xxx_irq_poll_free(struct mv88e6xxx_chip *chip) kthread_cancel_delayed_work_sync(&chip->irq_poll_work); kthread_destroy_worker(chip->kworker); - mutex_lock(&chip->reg_lock); + mv88e6xxx_reg_lock(chip); mv88e6xxx_g1_irq_free_common(chip); - mutex_unlock(&chip->reg_lock); + mv88e6xxx_reg_unlock(chip); } int mv88e6xxx_wait(struct mv88e6xxx_chip *chip, int addr, int reg, u16 mask) @@ -496,11 +496,11 @@ static void mv88e6xxx_adjust_link(struct dsa_switch *ds, int port, mv88e6xxx_phy_is_internal(ds, port)) return; - mutex_lock(&chip->reg_lock); + mv88e6xxx_reg_lock(chip); err = mv88e6xxx_port_setup_mac(chip, port, phydev->link, phydev->speed, phydev->duplex, phydev->pause, phydev->interface); - mutex_unlock(&chip->reg_lock); + mv88e6xxx_reg_unlock(chip); if (err && err != -EOPNOTSUPP) dev_err(ds->dev, "p%d: failed to configure MAC\n", port); @@ -616,12 +616,12 @@ static int mv88e6xxx_link_state(struct dsa_switch *ds, int port, struct mv88e6xxx_chip *chip = ds->priv; int err; - mutex_lock(&chip->reg_lock); + mv88e6xxx_reg_lock(chip); if (chip->info->ops->port_link_state) err = chip->info->ops->port_link_state(chip, port, state); else err = -EOPNOTSUPP; - mutex_unlock(&chip->reg_lock); + mv88e6xxx_reg_unlock(chip); return err; } @@ -651,10 +651,10 @@ static void mv88e6xxx_mac_config(struct dsa_switch *ds, int port, } pause = !!phylink_test(state->advertising, Pause); - mutex_lock(&chip->reg_lock); + mv88e6xxx_reg_lock(chip); err = mv88e6xxx_port_setup_mac(chip, port, link, speed, duplex, pause, state->interface); - mutex_unlock(&chip->reg_lock); + mv88e6xxx_reg_unlock(chip); if (err && err != -EOPNOTSUPP) dev_err(ds->dev, "p%d: failed to configure MAC\n", port); @@ -665,9 +665,9 @@ static void mv88e6xxx_mac_link_force(struct dsa_switch *ds, int port, int link) struct mv88e6xxx_chip *chip = ds->priv; int err; - mutex_lock(&chip->reg_lock); + mv88e6xxx_reg_lock(chip); err = chip->info->ops->port_set_link(chip, port, link); - mutex_unlock(&chip->reg_lock); + mv88e6xxx_reg_unlock(chip); if (err) dev_err(chip->dev, "p%d: failed to force MAC link\n", port); @@ -825,6 +825,12 @@ static int mv88e6095_stats_get_strings(struct mv88e6xxx_chip *chip, STATS_TYPE_BANK0 | STATS_TYPE_PORT); } +static int mv88e6250_stats_get_strings(struct mv88e6xxx_chip *chip, + uint8_t *data) +{ + return mv88e6xxx_stats_get_strings(chip, data, STATS_TYPE_BANK0); +} + static int mv88e6320_stats_get_strings(struct mv88e6xxx_chip *chip, uint8_t *data) { @@ -859,7 +865,7 @@ static void mv88e6xxx_get_strings(struct dsa_switch *ds, int port, if (stringset != ETH_SS_STATS) return; - mutex_lock(&chip->reg_lock); + mv88e6xxx_reg_lock(chip); if (chip->info->ops->stats_get_strings) count = chip->info->ops->stats_get_strings(chip, data); @@ -872,7 +878,7 @@ static void mv88e6xxx_get_strings(struct dsa_switch *ds, int port, data += count * ETH_GSTRING_LEN; mv88e6xxx_atu_vtu_get_strings(data); - mutex_unlock(&chip->reg_lock); + mv88e6xxx_reg_unlock(chip); } static int mv88e6xxx_stats_get_sset_count(struct mv88e6xxx_chip *chip, @@ -895,6 +901,11 @@ static int mv88e6095_stats_get_sset_count(struct mv88e6xxx_chip *chip) STATS_TYPE_PORT); } +static int mv88e6250_stats_get_sset_count(struct mv88e6xxx_chip *chip) +{ + return mv88e6xxx_stats_get_sset_count(chip, STATS_TYPE_BANK0); +} + static int mv88e6320_stats_get_sset_count(struct mv88e6xxx_chip *chip) { return mv88e6xxx_stats_get_sset_count(chip, STATS_TYPE_BANK0 | @@ -910,7 +921,7 @@ static int mv88e6xxx_get_sset_count(struct dsa_switch *ds, int port, int sset) if (sset != ETH_SS_STATS) return 0; - mutex_lock(&chip->reg_lock); + mv88e6xxx_reg_lock(chip); if (chip->info->ops->stats_get_sset_count) count = chip->info->ops->stats_get_sset_count(chip); if (count < 0) @@ -927,7 +938,7 @@ static int mv88e6xxx_get_sset_count(struct dsa_switch *ds, int port, int sset) count += ARRAY_SIZE(mv88e6xxx_atu_vtu_stats_strings); out: - mutex_unlock(&chip->reg_lock); + mv88e6xxx_reg_unlock(chip); return count; } @@ -942,11 +953,11 @@ static int mv88e6xxx_stats_get_stats(struct mv88e6xxx_chip *chip, int port, for (i = 0, j = 0; i < ARRAY_SIZE(mv88e6xxx_hw_stats); i++) { stat = &mv88e6xxx_hw_stats[i]; if (stat->type & types) { - mutex_lock(&chip->reg_lock); + mv88e6xxx_reg_lock(chip); data[j] = _mv88e6xxx_get_ethtool_stat(chip, stat, port, bank1_select, histogram); - mutex_unlock(&chip->reg_lock); + mv88e6xxx_reg_unlock(chip); j++; } @@ -962,6 +973,13 @@ static int mv88e6095_stats_get_stats(struct mv88e6xxx_chip *chip, int port, 0, MV88E6XXX_G1_STATS_OP_HIST_RX_TX); } +static int mv88e6250_stats_get_stats(struct mv88e6xxx_chip *chip, int port, + uint64_t *data) +{ + return mv88e6xxx_stats_get_stats(chip, port, data, STATS_TYPE_BANK0, + 0, MV88E6XXX_G1_STATS_OP_HIST_RX_TX); +} + static int mv88e6320_stats_get_stats(struct mv88e6xxx_chip *chip, int port, uint64_t *data) { @@ -998,14 +1016,14 @@ static void mv88e6xxx_get_stats(struct mv88e6xxx_chip *chip, int port, if (chip->info->ops->stats_get_stats) count = chip->info->ops->stats_get_stats(chip, port, data); - mutex_lock(&chip->reg_lock); + mv88e6xxx_reg_lock(chip); if (chip->info->ops->serdes_get_stats) { data += count; count = chip->info->ops->serdes_get_stats(chip, port, data); } data += count; mv88e6xxx_atu_vtu_get_stats(chip, port, data); - mutex_unlock(&chip->reg_lock); + mv88e6xxx_reg_unlock(chip); } static void mv88e6xxx_get_ethtool_stats(struct dsa_switch *ds, int port, @@ -1014,10 +1032,10 @@ static void mv88e6xxx_get_ethtool_stats(struct dsa_switch *ds, int port, struct mv88e6xxx_chip *chip = ds->priv; int ret; - mutex_lock(&chip->reg_lock); + mv88e6xxx_reg_lock(chip); ret = mv88e6xxx_stats_snapshot(chip, port); - mutex_unlock(&chip->reg_lock); + mv88e6xxx_reg_unlock(chip); if (ret < 0) return; @@ -1044,7 +1062,7 @@ static void mv88e6xxx_get_regs(struct dsa_switch *ds, int port, memset(p, 0xff, 32 * sizeof(u16)); - mutex_lock(&chip->reg_lock); + mv88e6xxx_reg_lock(chip); for (i = 0; i < 32; i++) { @@ -1053,7 +1071,7 @@ static void mv88e6xxx_get_regs(struct dsa_switch *ds, int port, p[i] = reg; } - mutex_unlock(&chip->reg_lock); + mv88e6xxx_reg_unlock(chip); } static int mv88e6xxx_get_mac_eee(struct dsa_switch *ds, int port, @@ -1119,9 +1137,9 @@ static void mv88e6xxx_port_stp_state_set(struct dsa_switch *ds, int port, struct mv88e6xxx_chip *chip = ds->priv; int err; - mutex_lock(&chip->reg_lock); + mv88e6xxx_reg_lock(chip); err = mv88e6xxx_port_set_state(chip, port, state); - mutex_unlock(&chip->reg_lock); + mv88e6xxx_reg_unlock(chip); if (err) dev_err(ds->dev, "p%d: failed to update state\n", port); @@ -1306,9 +1324,9 @@ static void mv88e6xxx_port_fast_age(struct dsa_switch *ds, int port) struct mv88e6xxx_chip *chip = ds->priv; int err; - mutex_lock(&chip->reg_lock); + mv88e6xxx_reg_lock(chip); err = mv88e6xxx_g1_atu_remove(chip, 0, port, false); - mutex_unlock(&chip->reg_lock); + mv88e6xxx_reg_unlock(chip); if (err) dev_err(ds->dev, "p%d: failed to flush ATU\n", port); @@ -1436,7 +1454,7 @@ static int mv88e6xxx_port_check_hw_vlan(struct dsa_switch *ds, int port, if (!vid_begin) return -EOPNOTSUPP; - mutex_lock(&chip->reg_lock); + mv88e6xxx_reg_lock(chip); do { err = mv88e6xxx_vtu_getnext(chip, &vlan); @@ -1476,7 +1494,7 @@ static int mv88e6xxx_port_check_hw_vlan(struct dsa_switch *ds, int port, } while (vlan.vid < vid_end); unlock: - mutex_unlock(&chip->reg_lock); + mv88e6xxx_reg_unlock(chip); return err; } @@ -1492,9 +1510,9 @@ static int mv88e6xxx_port_vlan_filtering(struct dsa_switch *ds, int port, if (!chip->info->max_vid) return -EOPNOTSUPP; - mutex_lock(&chip->reg_lock); + mv88e6xxx_reg_lock(chip); err = mv88e6xxx_port_set_8021q_mode(chip, port, mode); - mutex_unlock(&chip->reg_lock); + mv88e6xxx_reg_unlock(chip); return err; } @@ -1628,7 +1646,7 @@ static void mv88e6xxx_port_vlan_add(struct dsa_switch *ds, int port, else member = MV88E6XXX_G1_VTU_DATA_MEMBER_TAG_TAGGED; - mutex_lock(&chip->reg_lock); + mv88e6xxx_reg_lock(chip); for (vid = vlan->vid_begin; vid <= vlan->vid_end; ++vid) if (_mv88e6xxx_port_vlan_add(chip, port, vid, member)) @@ -1639,7 +1657,7 @@ static void mv88e6xxx_port_vlan_add(struct dsa_switch *ds, int port, dev_err(ds->dev, "p%d: failed to set PVID %d\n", port, vlan->vid_end); - mutex_unlock(&chip->reg_lock); + mv88e6xxx_reg_unlock(chip); } static int _mv88e6xxx_port_vlan_del(struct mv88e6xxx_chip *chip, @@ -1685,7 +1703,7 @@ static int mv88e6xxx_port_vlan_del(struct dsa_switch *ds, int port, if (!chip->info->max_vid) return -EOPNOTSUPP; - mutex_lock(&chip->reg_lock); + mv88e6xxx_reg_lock(chip); err = mv88e6xxx_port_get_pvid(chip, port, &pvid); if (err) @@ -1704,7 +1722,7 @@ static int mv88e6xxx_port_vlan_del(struct dsa_switch *ds, int port, } unlock: - mutex_unlock(&chip->reg_lock); + mv88e6xxx_reg_unlock(chip); return err; } @@ -1715,10 +1733,10 @@ static int mv88e6xxx_port_fdb_add(struct dsa_switch *ds, int port, struct mv88e6xxx_chip *chip = ds->priv; int err; - mutex_lock(&chip->reg_lock); + mv88e6xxx_reg_lock(chip); err = mv88e6xxx_port_db_load_purge(chip, port, addr, vid, MV88E6XXX_G1_ATU_DATA_STATE_UC_STATIC); - mutex_unlock(&chip->reg_lock); + mv88e6xxx_reg_unlock(chip); return err; } @@ -1729,10 +1747,10 @@ static int mv88e6xxx_port_fdb_del(struct dsa_switch *ds, int port, struct mv88e6xxx_chip *chip = ds->priv; int err; - mutex_lock(&chip->reg_lock); + mv88e6xxx_reg_lock(chip); err = mv88e6xxx_port_db_load_purge(chip, port, addr, vid, MV88E6XXX_G1_ATU_DATA_STATE_UNUSED); - mutex_unlock(&chip->reg_lock); + mv88e6xxx_reg_unlock(chip); return err; } @@ -1749,9 +1767,7 @@ static int mv88e6xxx_port_db_dump_fid(struct mv88e6xxx_chip *chip, eth_broadcast_addr(addr.mac); do { - mutex_lock(&chip->reg_lock); err = mv88e6xxx_g1_atu_getnext(chip, fid, &addr); - mutex_unlock(&chip->reg_lock); if (err) return err; @@ -1784,10 +1800,7 @@ static int mv88e6xxx_port_db_dump(struct mv88e6xxx_chip *chip, int port, int err; /* Dump port's default Filtering Information Database (VLAN ID 0) */ - mutex_lock(&chip->reg_lock); err = mv88e6xxx_port_get_fid(chip, port, &fid); - mutex_unlock(&chip->reg_lock); - if (err) return err; @@ -1797,9 +1810,7 @@ static int mv88e6xxx_port_db_dump(struct mv88e6xxx_chip *chip, int port, /* Dump VLANs' Filtering Information Databases */ do { - mutex_lock(&chip->reg_lock); err = mv88e6xxx_vtu_getnext(chip, &vlan); - mutex_unlock(&chip->reg_lock); if (err) return err; @@ -1819,8 +1830,13 @@ static int mv88e6xxx_port_fdb_dump(struct dsa_switch *ds, int port, dsa_fdb_dump_cb_t *cb, void *data) { struct mv88e6xxx_chip *chip = ds->priv; + int err; - return mv88e6xxx_port_db_dump(chip, port, cb, data); + mv88e6xxx_reg_lock(chip); + err = mv88e6xxx_port_db_dump(chip, port, cb, data); + mv88e6xxx_reg_unlock(chip); + + return err; } static int mv88e6xxx_bridge_map(struct mv88e6xxx_chip *chip, @@ -1867,9 +1883,9 @@ static int mv88e6xxx_port_bridge_join(struct dsa_switch *ds, int port, struct mv88e6xxx_chip *chip = ds->priv; int err; - mutex_lock(&chip->reg_lock); + mv88e6xxx_reg_lock(chip); err = mv88e6xxx_bridge_map(chip, br); - mutex_unlock(&chip->reg_lock); + mv88e6xxx_reg_unlock(chip); return err; } @@ -1879,11 +1895,11 @@ static void mv88e6xxx_port_bridge_leave(struct dsa_switch *ds, int port, { struct mv88e6xxx_chip *chip = ds->priv; - mutex_lock(&chip->reg_lock); + mv88e6xxx_reg_lock(chip); if (mv88e6xxx_bridge_map(chip, br) || mv88e6xxx_port_vlan_map(chip, port)) dev_err(ds->dev, "failed to remap in-chip Port VLAN\n"); - mutex_unlock(&chip->reg_lock); + mv88e6xxx_reg_unlock(chip); } static int mv88e6xxx_crosschip_bridge_join(struct dsa_switch *ds, int dev, @@ -1895,9 +1911,9 @@ static int mv88e6xxx_crosschip_bridge_join(struct dsa_switch *ds, int dev, if (!mv88e6xxx_has_pvt(chip)) return 0; - mutex_lock(&chip->reg_lock); + mv88e6xxx_reg_lock(chip); err = mv88e6xxx_pvt_map(chip, dev, port); - mutex_unlock(&chip->reg_lock); + mv88e6xxx_reg_unlock(chip); return err; } @@ -1910,10 +1926,10 @@ static void mv88e6xxx_crosschip_bridge_leave(struct dsa_switch *ds, int dev, if (!mv88e6xxx_has_pvt(chip)) return; - mutex_lock(&chip->reg_lock); + mv88e6xxx_reg_lock(chip); if (mv88e6xxx_pvt_map(chip, dev, port)) dev_err(ds->dev, "failed to remap cross-chip Port VLAN\n"); - mutex_unlock(&chip->reg_lock); + mv88e6xxx_reg_unlock(chip); } static int mv88e6xxx_software_reset(struct mv88e6xxx_chip *chip) @@ -2264,14 +2280,14 @@ static int mv88e6xxx_port_enable(struct dsa_switch *ds, int port, struct mv88e6xxx_chip *chip = ds->priv; int err; - mutex_lock(&chip->reg_lock); + mv88e6xxx_reg_lock(chip); err = mv88e6xxx_serdes_power(chip, port, true); if (!err && chip->info->ops->serdes_irq_setup) err = chip->info->ops->serdes_irq_setup(chip, port); - mutex_unlock(&chip->reg_lock); + mv88e6xxx_reg_unlock(chip); return err; } @@ -2280,7 +2296,7 @@ static void mv88e6xxx_port_disable(struct dsa_switch *ds, int port) { struct mv88e6xxx_chip *chip = ds->priv; - mutex_lock(&chip->reg_lock); + mv88e6xxx_reg_lock(chip); if (mv88e6xxx_port_set_state(chip, port, BR_STATE_DISABLED)) dev_err(chip->dev, "failed to disable port\n"); @@ -2291,7 +2307,7 @@ static void mv88e6xxx_port_disable(struct dsa_switch *ds, int port) if (mv88e6xxx_serdes_power(chip, port, false)) dev_err(chip->dev, "failed to power off SERDES\n"); - mutex_unlock(&chip->reg_lock); + mv88e6xxx_reg_unlock(chip); } static int mv88e6xxx_set_ageing_time(struct dsa_switch *ds, @@ -2300,9 +2316,9 @@ static int mv88e6xxx_set_ageing_time(struct dsa_switch *ds, struct mv88e6xxx_chip *chip = ds->priv; int err; - mutex_lock(&chip->reg_lock); + mv88e6xxx_reg_lock(chip); err = mv88e6xxx_g1_atu_set_age_time(chip, ageing_time); - mutex_unlock(&chip->reg_lock); + mv88e6xxx_reg_unlock(chip); return err; } @@ -2432,7 +2448,7 @@ static int mv88e6xxx_setup(struct dsa_switch *ds) chip->ds = ds; ds->slave_mii_bus = mv88e6xxx_default_mdio_bus(chip); - mutex_lock(&chip->reg_lock); + mv88e6xxx_reg_lock(chip); if (chip->info->ops->setup_errata) { err = chip->info->ops->setup_errata(chip); @@ -2539,7 +2555,7 @@ static int mv88e6xxx_setup(struct dsa_switch *ds) goto unlock; unlock: - mutex_unlock(&chip->reg_lock); + mv88e6xxx_reg_unlock(chip); return err; } @@ -2554,9 +2570,9 @@ static int mv88e6xxx_mdio_read(struct mii_bus *bus, int phy, int reg) if (!chip->info->ops->phy_read) return -EOPNOTSUPP; - mutex_lock(&chip->reg_lock); + mv88e6xxx_reg_lock(chip); err = chip->info->ops->phy_read(chip, bus, phy, reg, &val); - mutex_unlock(&chip->reg_lock); + mv88e6xxx_reg_unlock(chip); if (reg == MII_PHYSID2) { /* Some internal PHYs don't have a model number. */ @@ -2589,9 +2605,9 @@ static int mv88e6xxx_mdio_write(struct mii_bus *bus, int phy, int reg, u16 val) if (!chip->info->ops->phy_write) return -EOPNOTSUPP; - mutex_lock(&chip->reg_lock); + mv88e6xxx_reg_lock(chip); err = chip->info->ops->phy_write(chip, bus, phy, reg, val); - mutex_unlock(&chip->reg_lock); + mv88e6xxx_reg_unlock(chip); return err; } @@ -2606,9 +2622,9 @@ static int mv88e6xxx_mdio_register(struct mv88e6xxx_chip *chip, int err; if (external) { - mutex_lock(&chip->reg_lock); + mv88e6xxx_reg_lock(chip); err = mv88e6xxx_g2_scratch_gpio_set_smi(chip, true); - mutex_unlock(&chip->reg_lock); + mv88e6xxx_reg_unlock(chip); if (err) return err; @@ -2729,9 +2745,9 @@ static int mv88e6xxx_get_eeprom(struct dsa_switch *ds, if (!chip->info->ops->get_eeprom) return -EOPNOTSUPP; - mutex_lock(&chip->reg_lock); + mv88e6xxx_reg_lock(chip); err = chip->info->ops->get_eeprom(chip, eeprom, data); - mutex_unlock(&chip->reg_lock); + mv88e6xxx_reg_unlock(chip); if (err) return err; @@ -2753,9 +2769,9 @@ static int mv88e6xxx_set_eeprom(struct dsa_switch *ds, if (eeprom->magic != 0xc3ec4951) return -EINVAL; - mutex_lock(&chip->reg_lock); + mv88e6xxx_reg_lock(chip); err = chip->info->ops->set_eeprom(chip, eeprom, data); - mutex_unlock(&chip->reg_lock); + mv88e6xxx_reg_unlock(chip); return err; } @@ -3444,6 +3460,44 @@ static const struct mv88e6xxx_ops mv88e6240_ops = { .phylink_validate = mv88e6352_phylink_validate, }; +static const struct mv88e6xxx_ops mv88e6250_ops = { + /* MV88E6XXX_FAMILY_6250 */ + .ieee_pri_map = mv88e6250_g1_ieee_pri_map, + .ip_pri_map = mv88e6085_g1_ip_pri_map, + .irl_init_all = mv88e6352_g2_irl_init_all, + .get_eeprom = mv88e6xxx_g2_get_eeprom16, + .set_eeprom = mv88e6xxx_g2_set_eeprom16, + .set_switch_mac = mv88e6xxx_g2_set_switch_mac, + .phy_read = mv88e6xxx_g2_smi_phy_read, + .phy_write = mv88e6xxx_g2_smi_phy_write, + .port_set_link = mv88e6xxx_port_set_link, + .port_set_duplex = mv88e6xxx_port_set_duplex, + .port_set_rgmii_delay = mv88e6352_port_set_rgmii_delay, + .port_set_speed = mv88e6250_port_set_speed, + .port_tag_remap = mv88e6095_port_tag_remap, + .port_set_frame_mode = mv88e6351_port_set_frame_mode, + .port_set_egress_floods = mv88e6352_port_set_egress_floods, + .port_set_ether_type = mv88e6351_port_set_ether_type, + .port_egress_rate_limiting = mv88e6097_port_egress_rate_limiting, + .port_pause_limit = mv88e6097_port_pause_limit, + .port_disable_pri_override = mv88e6xxx_port_disable_pri_override, + .port_link_state = mv88e6250_port_link_state, + .stats_snapshot = mv88e6320_g1_stats_snapshot, + .stats_set_histogram = mv88e6095_g1_stats_set_histogram, + .stats_get_sset_count = mv88e6250_stats_get_sset_count, + .stats_get_strings = mv88e6250_stats_get_strings, + .stats_get_stats = mv88e6250_stats_get_stats, + .set_cpu_port = mv88e6095_g1_set_cpu_port, + .set_egress_port = mv88e6095_g1_set_egress_port, + .watchdog_ops = &mv88e6250_watchdog_ops, + .mgmt_rsvd2cpu = mv88e6352_g2_mgmt_rsvd2cpu, + .pot_clear = mv88e6xxx_g2_pot_clear, + .reset = mv88e6250_g1_reset, + .vtu_getnext = mv88e6250_g1_vtu_getnext, + .vtu_loadpurge = mv88e6250_g1_vtu_loadpurge, + .phylink_validate = mv88e6065_phylink_validate, +}; + static const struct mv88e6xxx_ops mv88e6290_ops = { /* MV88E6XXX_FAMILY_6390 */ .setup_errata = mv88e6390_setup_errata, @@ -4229,6 +4283,27 @@ static const struct mv88e6xxx_info mv88e6xxx_table[] = { .ops = &mv88e6240_ops, }, + [MV88E6250] = { + .prod_num = MV88E6XXX_PORT_SWITCH_ID_PROD_6250, + .family = MV88E6XXX_FAMILY_6250, + .name = "Marvell 88E6250", + .num_databases = 64, + .num_ports = 7, + .num_internal_phys = 5, + .max_vid = 4095, + .port_base_addr = 0x08, + .phy_base_addr = 0x00, + .global1_addr = 0x0f, + .global2_addr = 0x07, + .age_time_coeff = 15000, + .g1_irqs = 9, + .g2_irqs = 10, + .atu_move_port_mask = 0xf, + .dual_chip = true, + .tag_protocol = DSA_TAG_PROTO_DSA, + .ops = &mv88e6250_ops, + }, + [MV88E6290] = { .prod_num = MV88E6XXX_PORT_SWITCH_ID_PROD_6290, .family = MV88E6XXX_FAMILY_6390, @@ -4457,9 +4532,9 @@ static int mv88e6xxx_detect(struct mv88e6xxx_chip *chip) u16 id; int err; - mutex_lock(&chip->reg_lock); + mv88e6xxx_reg_lock(chip); err = mv88e6xxx_port_read(chip, 0, MV88E6XXX_PORT_SWITCH_ID, &id); - mutex_unlock(&chip->reg_lock); + mv88e6xxx_reg_unlock(chip); if (err) return err; @@ -4522,12 +4597,12 @@ static void mv88e6xxx_port_mdb_add(struct dsa_switch *ds, int port, { struct mv88e6xxx_chip *chip = ds->priv; - mutex_lock(&chip->reg_lock); + mv88e6xxx_reg_lock(chip); if (mv88e6xxx_port_db_load_purge(chip, port, mdb->addr, mdb->vid, MV88E6XXX_G1_ATU_DATA_STATE_MC_STATIC)) dev_err(ds->dev, "p%d: failed to load multicast MAC address\n", port); - mutex_unlock(&chip->reg_lock); + mv88e6xxx_reg_unlock(chip); } static int mv88e6xxx_port_mdb_del(struct dsa_switch *ds, int port, @@ -4536,10 +4611,10 @@ static int mv88e6xxx_port_mdb_del(struct dsa_switch *ds, int port, struct mv88e6xxx_chip *chip = ds->priv; int err; - mutex_lock(&chip->reg_lock); + mv88e6xxx_reg_lock(chip); err = mv88e6xxx_port_db_load_purge(chip, port, mdb->addr, mdb->vid, MV88E6XXX_G1_ATU_DATA_STATE_UNUSED); - mutex_unlock(&chip->reg_lock); + mv88e6xxx_reg_unlock(chip); return err; } @@ -4550,12 +4625,12 @@ static int mv88e6xxx_port_egress_floods(struct dsa_switch *ds, int port, struct mv88e6xxx_chip *chip = ds->priv; int err = -EOPNOTSUPP; - mutex_lock(&chip->reg_lock); + mv88e6xxx_reg_lock(chip); if (chip->info->ops->port_set_egress_floods) err = chip->info->ops->port_set_egress_floods(chip, port, unicast, multicast); - mutex_unlock(&chip->reg_lock); + mv88e6xxx_reg_unlock(chip); return err; } @@ -4711,6 +4786,8 @@ static int mv88e6xxx_probe(struct mdio_device *mdiodev) err = PTR_ERR(chip->reset); goto out; } + if (chip->reset) + usleep_range(1000, 2000); err = mv88e6xxx_detect(chip); if (err) @@ -4726,9 +4803,9 @@ static int mv88e6xxx_probe(struct mdio_device *mdiodev) chip->eeprom_len = pdata->eeprom_len; } - mutex_lock(&chip->reg_lock); + mv88e6xxx_reg_lock(chip); err = mv88e6xxx_switch_reset(chip); - mutex_unlock(&chip->reg_lock); + mv88e6xxx_reg_unlock(chip); if (err) goto out; @@ -4747,12 +4824,12 @@ static int mv88e6xxx_probe(struct mdio_device *mdiodev) * the PHYs will link their interrupts to these interrupt * controllers */ - mutex_lock(&chip->reg_lock); + mv88e6xxx_reg_lock(chip); if (chip->irq > 0) err = mv88e6xxx_g1_irq_setup(chip); else err = mv88e6xxx_irq_poll_setup(chip); - mutex_unlock(&chip->reg_lock); + mv88e6xxx_reg_unlock(chip); if (err) goto out; @@ -4837,6 +4914,10 @@ static const struct of_device_id mv88e6xxx_of_match[] = { .compatible = "marvell,mv88e6190", .data = &mv88e6xxx_table[MV88E6190], }, + { + .compatible = "marvell,mv88e6250", + .data = &mv88e6xxx_table[MV88E6250], + }, { /* sentinel */ }, }; diff --git a/drivers/net/dsa/mv88e6xxx/chip.h b/drivers/net/dsa/mv88e6xxx/chip.h index d3e10111a6fe..4646e46d47f2 100644 --- a/drivers/net/dsa/mv88e6xxx/chip.h +++ b/drivers/net/dsa/mv88e6xxx/chip.h @@ -58,6 +58,7 @@ enum mv88e6xxx_model { MV88E6190X, MV88E6191, MV88E6240, + MV88E6250, MV88E6290, MV88E6320, MV88E6321, @@ -76,6 +77,7 @@ enum mv88e6xxx_family { MV88E6XXX_FAMILY_6097, /* 6046 6085 6096 6097 */ MV88E6XXX_FAMILY_6165, /* 6123 6161 6165 */ MV88E6XXX_FAMILY_6185, /* 6108 6121 6122 6131 6152 6155 6182 6185 */ + MV88E6XXX_FAMILY_6250, /* 6250 */ MV88E6XXX_FAMILY_6320, /* 6320 6321 */ MV88E6XXX_FAMILY_6341, /* 6141 6341 */ MV88E6XXX_FAMILY_6351, /* 6171 6175 6350 6351 */ @@ -108,6 +110,12 @@ struct mv88e6xxx_info { * when it is non-zero, and use indirect access to internal registers. */ bool multi_chip; + /* Dual-chip Addressing Mode + * Some chips respond to only half of the 32 SMI addresses, + * allowing two to coexist on the same SMI interface. + */ + bool dual_chip; + enum dsa_tag_protocol tag_protocol; /* Mask for FromPort and ToPort value of PortVec used in ATU Move @@ -572,4 +580,14 @@ int mv88e6xxx_port_setup_mac(struct mv88e6xxx_chip *chip, int port, int link, phy_interface_t mode); struct mii_bus *mv88e6xxx_default_mdio_bus(struct mv88e6xxx_chip *chip); +static inline void mv88e6xxx_reg_lock(struct mv88e6xxx_chip *chip) +{ + mutex_lock(&chip->reg_lock); +} + +static inline void mv88e6xxx_reg_unlock(struct mv88e6xxx_chip *chip) +{ + mutex_unlock(&chip->reg_lock); +} + #endif /* _MV88E6XXX_CHIP_H */ diff --git a/drivers/net/dsa/mv88e6xxx/global1.c b/drivers/net/dsa/mv88e6xxx/global1.c index 09b8a3d0dd37..1323ef30a5e9 100644 --- a/drivers/net/dsa/mv88e6xxx/global1.c +++ b/drivers/net/dsa/mv88e6xxx/global1.c @@ -178,7 +178,7 @@ int mv88e6185_g1_reset(struct mv88e6xxx_chip *chip) return mv88e6185_g1_wait_ppu_polling(chip); } -int mv88e6352_g1_reset(struct mv88e6xxx_chip *chip) +int mv88e6250_g1_reset(struct mv88e6xxx_chip *chip) { u16 val; int err; @@ -194,7 +194,14 @@ int mv88e6352_g1_reset(struct mv88e6xxx_chip *chip) if (err) return err; - err = mv88e6xxx_g1_wait_init_ready(chip); + return mv88e6xxx_g1_wait_init_ready(chip); +} + +int mv88e6352_g1_reset(struct mv88e6xxx_chip *chip) +{ + int err; + + err = mv88e6250_g1_reset(chip); if (err) return err; @@ -295,6 +302,12 @@ int mv88e6085_g1_ieee_pri_map(struct mv88e6xxx_chip *chip) return mv88e6xxx_g1_write(chip, MV88E6XXX_G1_IEEE_PRI, 0xfa41); } +int mv88e6250_g1_ieee_pri_map(struct mv88e6xxx_chip *chip) +{ + /* Reset the IEEE Tag priorities to defaults */ + return mv88e6xxx_g1_write(chip, MV88E6XXX_G1_IEEE_PRI, 0xfa50); +} + /* Offset 0x1a: Monitor Control */ /* Offset 0x1a: Monitor & MGMT Control on some devices */ @@ -375,26 +388,26 @@ int mv88e6390_g1_mgmt_rsvd2cpu(struct mv88e6xxx_chip *chip) u16 ptr; int err; - /* 01:c2:80:00:00:00:00-01:c2:80:00:00:00:07 are Management */ - ptr = MV88E6390_G1_MONITOR_MGMT_CTL_PTR_0180C280000000XLO; + /* 01:80:c2:00:00:00-01:80:c2:00:00:07 are Management */ + ptr = MV88E6390_G1_MONITOR_MGMT_CTL_PTR_0180C200000XLO; err = mv88e6390_g1_monitor_write(chip, ptr, 0xff); if (err) return err; - /* 01:c2:80:00:00:00:08-01:c2:80:00:00:00:0f are Management */ - ptr = MV88E6390_G1_MONITOR_MGMT_CTL_PTR_0180C280000000XHI; + /* 01:80:c2:00:00:08-01:80:c2:00:00:0f are Management */ + ptr = MV88E6390_G1_MONITOR_MGMT_CTL_PTR_0180C200000XHI; err = mv88e6390_g1_monitor_write(chip, ptr, 0xff); if (err) return err; - /* 01:c2:80:00:00:00:20-01:c2:80:00:00:00:27 are Management */ - ptr = MV88E6390_G1_MONITOR_MGMT_CTL_PTR_0180C280000002XLO; + /* 01:80:c2:00:00:20-01:80:c2:00:00:27 are Management */ + ptr = MV88E6390_G1_MONITOR_MGMT_CTL_PTR_0180C200002XLO; err = mv88e6390_g1_monitor_write(chip, ptr, 0xff); if (err) return err; - /* 01:c2:80:00:00:00:28-01:c2:80:00:00:00:2f are Management */ - ptr = MV88E6390_G1_MONITOR_MGMT_CTL_PTR_0180C280000002XHI; + /* 01:80:c2:00:00:28-01:80:c2:00:00:2f are Management */ + ptr = MV88E6390_G1_MONITOR_MGMT_CTL_PTR_0180C200002XHI; err = mv88e6390_g1_monitor_write(chip, ptr, 0xff); if (err) return err; @@ -461,7 +474,7 @@ int mv88e6xxx_g1_set_device_number(struct mv88e6xxx_chip *chip, int index) /* Offset 0x1d: Statistics Operation 2 */ -int mv88e6xxx_g1_stats_wait(struct mv88e6xxx_chip *chip) +static int mv88e6xxx_g1_stats_wait(struct mv88e6xxx_chip *chip) { return mv88e6xxx_g1_wait(chip, MV88E6XXX_G1_STATS_OP, MV88E6XXX_G1_STATS_OP_BUSY); diff --git a/drivers/net/dsa/mv88e6xxx/global1.h b/drivers/net/dsa/mv88e6xxx/global1.h index 7bd5ab733a3f..d444266f7d78 100644 --- a/drivers/net/dsa/mv88e6xxx/global1.h +++ b/drivers/net/dsa/mv88e6xxx/global1.h @@ -186,10 +186,10 @@ #define MV88E6390_G1_MONITOR_MGMT_CTL 0x1a #define MV88E6390_G1_MONITOR_MGMT_CTL_UPDATE 0x8000 #define MV88E6390_G1_MONITOR_MGMT_CTL_PTR_MASK 0x3f00 -#define MV88E6390_G1_MONITOR_MGMT_CTL_PTR_0180C280000000XLO 0x0000 -#define MV88E6390_G1_MONITOR_MGMT_CTL_PTR_0180C280000000XHI 0x0100 -#define MV88E6390_G1_MONITOR_MGMT_CTL_PTR_0180C280000002XLO 0x0200 -#define MV88E6390_G1_MONITOR_MGMT_CTL_PTR_0180C280000002XHI 0x0300 +#define MV88E6390_G1_MONITOR_MGMT_CTL_PTR_0180C200000XLO 0x0000 +#define MV88E6390_G1_MONITOR_MGMT_CTL_PTR_0180C200000XHI 0x0100 +#define MV88E6390_G1_MONITOR_MGMT_CTL_PTR_0180C200002XLO 0x0200 +#define MV88E6390_G1_MONITOR_MGMT_CTL_PTR_0180C200002XHI 0x0300 #define MV88E6390_G1_MONITOR_MGMT_CTL_PTR_INGRESS_DEST 0x2000 #define MV88E6390_G1_MONITOR_MGMT_CTL_PTR_EGRESS_DEST 0x2100 #define MV88E6390_G1_MONITOR_MGMT_CTL_PTR_CPU_DEST 0x3000 @@ -255,11 +255,11 @@ int mv88e6xxx_g1_set_switch_mac(struct mv88e6xxx_chip *chip, u8 *addr); int mv88e6185_g1_reset(struct mv88e6xxx_chip *chip); int mv88e6352_g1_reset(struct mv88e6xxx_chip *chip); +int mv88e6250_g1_reset(struct mv88e6xxx_chip *chip); int mv88e6185_g1_ppu_enable(struct mv88e6xxx_chip *chip); int mv88e6185_g1_ppu_disable(struct mv88e6xxx_chip *chip); -int mv88e6xxx_g1_stats_wait(struct mv88e6xxx_chip *chip); int mv88e6xxx_g1_stats_snapshot(struct mv88e6xxx_chip *chip, int port); int mv88e6320_g1_stats_snapshot(struct mv88e6xxx_chip *chip, int port); int mv88e6390_g1_stats_snapshot(struct mv88e6xxx_chip *chip, int port); @@ -274,7 +274,9 @@ int mv88e6390_g1_set_cpu_port(struct mv88e6xxx_chip *chip, int port); int mv88e6390_g1_mgmt_rsvd2cpu(struct mv88e6xxx_chip *chip); int mv88e6085_g1_ip_pri_map(struct mv88e6xxx_chip *chip); + int mv88e6085_g1_ieee_pri_map(struct mv88e6xxx_chip *chip); +int mv88e6250_g1_ieee_pri_map(struct mv88e6xxx_chip *chip); int mv88e6185_g1_set_cascade_port(struct mv88e6xxx_chip *chip, int port); @@ -301,6 +303,10 @@ int mv88e6185_g1_vtu_getnext(struct mv88e6xxx_chip *chip, struct mv88e6xxx_vtu_entry *entry); int mv88e6185_g1_vtu_loadpurge(struct mv88e6xxx_chip *chip, struct mv88e6xxx_vtu_entry *entry); +int mv88e6250_g1_vtu_getnext(struct mv88e6xxx_chip *chip, + struct mv88e6xxx_vtu_entry *entry); +int mv88e6250_g1_vtu_loadpurge(struct mv88e6xxx_chip *chip, + struct mv88e6xxx_vtu_entry *entry); int mv88e6352_g1_vtu_getnext(struct mv88e6xxx_chip *chip, struct mv88e6xxx_vtu_entry *entry); int mv88e6352_g1_vtu_loadpurge(struct mv88e6xxx_chip *chip, diff --git a/drivers/net/dsa/mv88e6xxx/global1_atu.c b/drivers/net/dsa/mv88e6xxx/global1_atu.c index 4542dfa5fc69..1cf388e9bd94 100644 --- a/drivers/net/dsa/mv88e6xxx/global1_atu.c +++ b/drivers/net/dsa/mv88e6xxx/global1_atu.c @@ -90,7 +90,7 @@ static int mv88e6xxx_g1_atu_op(struct mv88e6xxx_chip *chip, u16 fid, u16 op) if (err) return err; } else { - if (mv88e6xxx_num_databases(chip) > 16) { + if (mv88e6xxx_num_databases(chip) > 64) { /* ATU DBNum[7:4] are located in ATU Control 15:12 */ err = mv88e6xxx_g1_read(chip, MV88E6XXX_G1_ATU_CTL, &val); @@ -102,6 +102,9 @@ static int mv88e6xxx_g1_atu_op(struct mv88e6xxx_chip *chip, u16 fid, u16 op) val); if (err) return err; + } else if (mv88e6xxx_num_databases(chip) > 16) { + /* ATU DBNum[5:4] are located in ATU Operation 9:8 */ + op |= (fid & 0x30) << 4; } /* ATU DBNum[3:0] are located in ATU Operation 3:0 */ @@ -314,7 +317,7 @@ static irqreturn_t mv88e6xxx_g1_atu_prob_irq_thread_fn(int irq, void *dev_id) int err; u16 val; - mutex_lock(&chip->reg_lock); + mv88e6xxx_reg_lock(chip); err = mv88e6xxx_g1_atu_op(chip, 0, MV88E6XXX_G1_ATU_OP_GET_CLR_VIOLATION); @@ -361,12 +364,12 @@ static irqreturn_t mv88e6xxx_g1_atu_prob_irq_thread_fn(int irq, void *dev_id) entry.mac, entry.portvec, spid); chip->ports[spid].atu_full_violation++; } - mutex_unlock(&chip->reg_lock); + mv88e6xxx_reg_unlock(chip); return IRQ_HANDLED; out: - mutex_unlock(&chip->reg_lock); + mv88e6xxx_reg_unlock(chip); dev_err(chip->dev, "ATU problem: error %d while handling interrupt\n", err); diff --git a/drivers/net/dsa/mv88e6xxx/global1_vtu.c b/drivers/net/dsa/mv88e6xxx/global1_vtu.c index ecef69045a42..6cac997360e8 100644 --- a/drivers/net/dsa/mv88e6xxx/global1_vtu.c +++ b/drivers/net/dsa/mv88e6xxx/global1_vtu.c @@ -303,6 +303,35 @@ static int mv88e6xxx_g1_vtu_getnext(struct mv88e6xxx_chip *chip, return mv88e6xxx_g1_vtu_vid_read(chip, entry); } +int mv88e6250_g1_vtu_getnext(struct mv88e6xxx_chip *chip, + struct mv88e6xxx_vtu_entry *entry) +{ + u16 val; + int err; + + err = mv88e6xxx_g1_vtu_getnext(chip, entry); + if (err) + return err; + + if (entry->valid) { + err = mv88e6185_g1_vtu_data_read(chip, entry); + if (err) + return err; + + /* VTU DBNum[3:0] are located in VTU Operation 3:0 + * VTU DBNum[5:4] are located in VTU Operation 9:8 + */ + err = mv88e6xxx_g1_read(chip, MV88E6XXX_G1_VTU_OP, &val); + if (err) + return err; + + entry->fid = val & 0x000f; + entry->fid |= (val & 0x0300) >> 4; + } + + return 0; +} + int mv88e6185_g1_vtu_getnext(struct mv88e6xxx_chip *chip, struct mv88e6xxx_vtu_entry *entry) { @@ -392,6 +421,35 @@ int mv88e6390_g1_vtu_getnext(struct mv88e6xxx_chip *chip, return 0; } +int mv88e6250_g1_vtu_loadpurge(struct mv88e6xxx_chip *chip, + struct mv88e6xxx_vtu_entry *entry) +{ + u16 op = MV88E6XXX_G1_VTU_OP_VTU_LOAD_PURGE; + int err; + + err = mv88e6xxx_g1_vtu_op_wait(chip); + if (err) + return err; + + err = mv88e6xxx_g1_vtu_vid_write(chip, entry); + if (err) + return err; + + if (entry->valid) { + err = mv88e6185_g1_vtu_data_write(chip, entry); + if (err) + return err; + + /* VTU DBNum[3:0] are located in VTU Operation 3:0 + * VTU DBNum[5:4] are located in VTU Operation 9:8 + */ + op |= entry->fid & 0x000f; + op |= (entry->fid & 0x0030) << 4; + } + + return mv88e6xxx_g1_vtu_op(chip, op); +} + int mv88e6185_g1_vtu_loadpurge(struct mv88e6xxx_chip *chip, struct mv88e6xxx_vtu_entry *entry) { @@ -521,7 +579,7 @@ static irqreturn_t mv88e6xxx_g1_vtu_prob_irq_thread_fn(int irq, void *dev_id) int err; u16 val; - mutex_lock(&chip->reg_lock); + mv88e6xxx_reg_lock(chip); err = mv88e6xxx_g1_vtu_op(chip, MV88E6XXX_G1_VTU_OP_GET_CLR_VIOLATION); if (err) @@ -549,12 +607,12 @@ static irqreturn_t mv88e6xxx_g1_vtu_prob_irq_thread_fn(int irq, void *dev_id) chip->ports[spid].vtu_miss_violation++; } - mutex_unlock(&chip->reg_lock); + mv88e6xxx_reg_unlock(chip); return IRQ_HANDLED; out: - mutex_unlock(&chip->reg_lock); + mv88e6xxx_reg_unlock(chip); dev_err(chip->dev, "VTU problem: error %d while handling interrupt\n", err); diff --git a/drivers/net/dsa/mv88e6xxx/global2.c b/drivers/net/dsa/mv88e6xxx/global2.c index 1546171210a1..2305b94b3051 100644 --- a/drivers/net/dsa/mv88e6xxx/global2.c +++ b/drivers/net/dsa/mv88e6xxx/global2.c @@ -812,6 +812,32 @@ const struct mv88e6xxx_irq_ops mv88e6097_watchdog_ops = { .irq_free = mv88e6097_watchdog_free, }; +static void mv88e6250_watchdog_free(struct mv88e6xxx_chip *chip) +{ + u16 reg; + + mv88e6xxx_g2_read(chip, MV88E6250_G2_WDOG_CTL, ®); + + reg &= ~(MV88E6250_G2_WDOG_CTL_EGRESS_ENABLE | + MV88E6250_G2_WDOG_CTL_QC_ENABLE); + + mv88e6xxx_g2_write(chip, MV88E6250_G2_WDOG_CTL, reg); +} + +static int mv88e6250_watchdog_setup(struct mv88e6xxx_chip *chip) +{ + return mv88e6xxx_g2_write(chip, MV88E6250_G2_WDOG_CTL, + MV88E6250_G2_WDOG_CTL_EGRESS_ENABLE | + MV88E6250_G2_WDOG_CTL_QC_ENABLE | + MV88E6250_G2_WDOG_CTL_SWRESET); +} + +const struct mv88e6xxx_irq_ops mv88e6250_watchdog_ops = { + .irq_action = mv88e6097_watchdog_action, + .irq_setup = mv88e6250_watchdog_setup, + .irq_free = mv88e6250_watchdog_free, +}; + static int mv88e6390_watchdog_setup(struct mv88e6xxx_chip *chip) { return mv88e6xxx_g2_update(chip, MV88E6390_G2_WDOG_CTL, @@ -867,20 +893,20 @@ static irqreturn_t mv88e6xxx_g2_watchdog_thread_fn(int irq, void *dev_id) struct mv88e6xxx_chip *chip = dev_id; irqreturn_t ret = IRQ_NONE; - mutex_lock(&chip->reg_lock); + mv88e6xxx_reg_lock(chip); if (chip->info->ops->watchdog_ops->irq_action) ret = chip->info->ops->watchdog_ops->irq_action(chip, irq); - mutex_unlock(&chip->reg_lock); + mv88e6xxx_reg_unlock(chip); return ret; } static void mv88e6xxx_g2_watchdog_free(struct mv88e6xxx_chip *chip) { - mutex_lock(&chip->reg_lock); + mv88e6xxx_reg_lock(chip); if (chip->info->ops->watchdog_ops->irq_free) chip->info->ops->watchdog_ops->irq_free(chip); - mutex_unlock(&chip->reg_lock); + mv88e6xxx_reg_unlock(chip); free_irq(chip->watchdog_irq, chip); irq_dispose_mapping(chip->watchdog_irq); @@ -902,10 +928,10 @@ static int mv88e6xxx_g2_watchdog_setup(struct mv88e6xxx_chip *chip) if (err) return err; - mutex_lock(&chip->reg_lock); + mv88e6xxx_reg_lock(chip); if (chip->info->ops->watchdog_ops->irq_setup) err = chip->info->ops->watchdog_ops->irq_setup(chip); - mutex_unlock(&chip->reg_lock); + mv88e6xxx_reg_unlock(chip); return err; } @@ -960,9 +986,9 @@ static irqreturn_t mv88e6xxx_g2_irq_thread_fn(int irq, void *dev_id) int err; u16 reg; - mutex_lock(&chip->reg_lock); + mv88e6xxx_reg_lock(chip); err = mv88e6xxx_g2_int_source(chip, ®); - mutex_unlock(&chip->reg_lock); + mv88e6xxx_reg_unlock(chip); if (err) goto out; @@ -981,7 +1007,7 @@ static void mv88e6xxx_g2_irq_bus_lock(struct irq_data *d) { struct mv88e6xxx_chip *chip = irq_data_get_irq_chip_data(d); - mutex_lock(&chip->reg_lock); + mv88e6xxx_reg_lock(chip); } static void mv88e6xxx_g2_irq_bus_sync_unlock(struct irq_data *d) @@ -993,7 +1019,7 @@ static void mv88e6xxx_g2_irq_bus_sync_unlock(struct irq_data *d) if (err) dev_err(chip->dev, "failed to mask interrupts\n"); - mutex_unlock(&chip->reg_lock); + mv88e6xxx_reg_unlock(chip); } static const struct irq_chip mv88e6xxx_g2_irq_chip = { diff --git a/drivers/net/dsa/mv88e6xxx/global2.h b/drivers/net/dsa/mv88e6xxx/global2.h index bfb2c6123f55..a664fc25f132 100644 --- a/drivers/net/dsa/mv88e6xxx/global2.h +++ b/drivers/net/dsa/mv88e6xxx/global2.h @@ -202,6 +202,18 @@ #define MV88E6XXX_G2_SCRATCH_MISC_DATA_MASK 0x00ff /* Offset 0x1B: Watch Dog Control Register */ +#define MV88E6250_G2_WDOG_CTL 0x1b +#define MV88E6250_G2_WDOG_CTL_QC_HISTORY 0x0100 +#define MV88E6250_G2_WDOG_CTL_QC_EVENT 0x0080 +#define MV88E6250_G2_WDOG_CTL_QC_ENABLE 0x0040 +#define MV88E6250_G2_WDOG_CTL_EGRESS_HISTORY 0x0020 +#define MV88E6250_G2_WDOG_CTL_EGRESS_EVENT 0x0010 +#define MV88E6250_G2_WDOG_CTL_EGRESS_ENABLE 0x0008 +#define MV88E6250_G2_WDOG_CTL_FORCE_IRQ 0x0004 +#define MV88E6250_G2_WDOG_CTL_HISTORY 0x0002 +#define MV88E6250_G2_WDOG_CTL_SWRESET 0x0001 + +/* Offset 0x1B: Watch Dog Control Register */ #define MV88E6352_G2_WDOG_CTL 0x1b #define MV88E6352_G2_WDOG_CTL_EGRESS_EVENT 0x0080 #define MV88E6352_G2_WDOG_CTL_RMU_TIMEOUT 0x0040 @@ -330,6 +342,7 @@ int mv88e6xxx_g2_device_mapping_write(struct mv88e6xxx_chip *chip, int target, int port); extern const struct mv88e6xxx_irq_ops mv88e6097_watchdog_ops; +extern const struct mv88e6xxx_irq_ops mv88e6250_watchdog_ops; extern const struct mv88e6xxx_irq_ops mv88e6390_watchdog_ops; extern const struct mv88e6xxx_avb_ops mv88e6165_avb_ops; @@ -480,6 +493,7 @@ static inline int mv88e6xxx_g2_pot_clear(struct mv88e6xxx_chip *chip) } static const struct mv88e6xxx_irq_ops mv88e6097_watchdog_ops = {}; +static const struct mv88e6xxx_irq_ops mv88e6250_watchdog_ops = {}; static const struct mv88e6xxx_irq_ops mv88e6390_watchdog_ops = {}; static const struct mv88e6xxx_avb_ops mv88e6165_avb_ops = {}; diff --git a/drivers/net/dsa/mv88e6xxx/hwtstamp.c b/drivers/net/dsa/mv88e6xxx/hwtstamp.c index 7f95a636561d..a4c488b12e8f 100644 --- a/drivers/net/dsa/mv88e6xxx/hwtstamp.c +++ b/drivers/net/dsa/mv88e6xxx/hwtstamp.c @@ -147,7 +147,7 @@ static int mv88e6xxx_set_hwtstamp_config(struct mv88e6xxx_chip *chip, int port, return -ERANGE; } - mutex_lock(&chip->reg_lock); + mv88e6xxx_reg_lock(chip); if (tstamp_enable) { chip->enable_count += 1; if (chip->enable_count == 1 && ptp_ops->global_enable) @@ -161,7 +161,7 @@ static int mv88e6xxx_set_hwtstamp_config(struct mv88e6xxx_chip *chip, int port, if (chip->enable_count == 0 && ptp_ops->global_disable) ptp_ops->global_disable(chip); } - mutex_unlock(&chip->reg_lock); + mv88e6xxx_reg_unlock(chip); /* Once hardware has been configured, enable timestamp checks * in the RX/TX paths. @@ -301,10 +301,10 @@ static void mv88e6xxx_get_rxts(struct mv88e6xxx_chip *chip, skb_queue_splice_tail_init(rxq, &received); spin_unlock_irqrestore(&rxq->lock, flags); - mutex_lock(&chip->reg_lock); + mv88e6xxx_reg_lock(chip); err = mv88e6xxx_port_ptp_read(chip, ps->port_id, reg, buf, ARRAY_SIZE(buf)); - mutex_unlock(&chip->reg_lock); + mv88e6xxx_reg_unlock(chip); if (err) pr_err("failed to get the receive time stamp\n"); @@ -314,9 +314,9 @@ static void mv88e6xxx_get_rxts(struct mv88e6xxx_chip *chip, seq_id = buf[3]; if (status & MV88E6XXX_PTP_TS_VALID) { - mutex_lock(&chip->reg_lock); + mv88e6xxx_reg_lock(chip); err = mv88e6xxx_port_ptp_write(chip, ps->port_id, reg, 0); - mutex_unlock(&chip->reg_lock); + mv88e6xxx_reg_unlock(chip); if (err) pr_err("failed to clear the receive status\n"); } @@ -327,9 +327,9 @@ static void mv88e6xxx_get_rxts(struct mv88e6xxx_chip *chip, if (mv88e6xxx_ts_valid(status) && seq_match(skb, seq_id)) { ns = timehi << 16 | timelo; - mutex_lock(&chip->reg_lock); + mv88e6xxx_reg_lock(chip); ns = timecounter_cyc2time(&chip->tstamp_tc, ns); - mutex_unlock(&chip->reg_lock); + mv88e6xxx_reg_unlock(chip); shwt = skb_hwtstamps(skb); memset(shwt, 0, sizeof(*shwt)); shwt->hwtstamp = ns_to_ktime(ns); @@ -405,12 +405,12 @@ static int mv88e6xxx_txtstamp_work(struct mv88e6xxx_chip *chip, if (!ps->tx_skb) return 0; - mutex_lock(&chip->reg_lock); + mv88e6xxx_reg_lock(chip); err = mv88e6xxx_port_ptp_read(chip, ps->port_id, ptp_ops->dep_sts_reg, departure_block, ARRAY_SIZE(departure_block)); - mutex_unlock(&chip->reg_lock); + mv88e6xxx_reg_unlock(chip); if (err) goto free_and_clear_skb; @@ -430,9 +430,9 @@ static int mv88e6xxx_txtstamp_work(struct mv88e6xxx_chip *chip, } /* We have the timestamp; go ahead and clear valid now */ - mutex_lock(&chip->reg_lock); + mv88e6xxx_reg_lock(chip); mv88e6xxx_port_ptp_write(chip, ps->port_id, ptp_ops->dep_sts_reg, 0); - mutex_unlock(&chip->reg_lock); + mv88e6xxx_reg_unlock(chip); status = departure_block[0] & MV88E6XXX_PTP_TS_STATUS_MASK; if (status != MV88E6XXX_PTP_TS_STATUS_NORMAL) { @@ -447,9 +447,9 @@ static int mv88e6xxx_txtstamp_work(struct mv88e6xxx_chip *chip, memset(&shhwtstamps, 0, sizeof(shhwtstamps)); time_raw = ((u32)departure_block[2] << 16) | departure_block[1]; - mutex_lock(&chip->reg_lock); + mv88e6xxx_reg_lock(chip); ns = timecounter_cyc2time(&chip->tstamp_tc, time_raw); - mutex_unlock(&chip->reg_lock); + mv88e6xxx_reg_unlock(chip); shhwtstamps.hwtstamp = ns_to_ktime(ns); dev_dbg(chip->dev, diff --git a/drivers/net/dsa/mv88e6xxx/phy.c b/drivers/net/dsa/mv88e6xxx/phy.c index 2952db73f55c..252b5b3a3efe 100644 --- a/drivers/net/dsa/mv88e6xxx/phy.c +++ b/drivers/net/dsa/mv88e6xxx/phy.c @@ -137,7 +137,7 @@ static void mv88e6xxx_phy_ppu_reenable_work(struct work_struct *ugly) chip = container_of(ugly, struct mv88e6xxx_chip, ppu_work); - mutex_lock(&chip->reg_lock); + mv88e6xxx_reg_lock(chip); if (mutex_trylock(&chip->ppu_mutex)) { if (mv88e6xxx_phy_ppu_enable(chip) == 0) @@ -145,7 +145,7 @@ static void mv88e6xxx_phy_ppu_reenable_work(struct work_struct *ugly) mutex_unlock(&chip->ppu_mutex); } - mutex_unlock(&chip->reg_lock); + mv88e6xxx_reg_unlock(chip); } static void mv88e6xxx_phy_ppu_reenable_timer(struct timer_list *t) diff --git a/drivers/net/dsa/mv88e6xxx/port.c b/drivers/net/dsa/mv88e6xxx/port.c index 9a2b4b385a2c..04309ef0a1cc 100644 --- a/drivers/net/dsa/mv88e6xxx/port.c +++ b/drivers/net/dsa/mv88e6xxx/port.c @@ -290,6 +290,18 @@ int mv88e6185_port_set_speed(struct mv88e6xxx_chip *chip, int port, int speed) return mv88e6xxx_port_set_speed(chip, port, speed, false, false); } +/* Support 10, 100 Mbps (e.g. 88E6250 family) */ +int mv88e6250_port_set_speed(struct mv88e6xxx_chip *chip, int port, int speed) +{ + if (speed == SPEED_MAX) + speed = 100; + + if (speed > 100) + return -EOPNOTSUPP; + + return mv88e6xxx_port_set_speed(chip, port, speed, false, false); +} + /* Support 10, 100, 200, 1000, 2500 Mbps (e.g. 88E6341) */ int mv88e6341_port_set_speed(struct mv88e6xxx_chip *chip, int port, int speed) { @@ -517,6 +529,71 @@ int mv88e6352_port_get_cmode(struct mv88e6xxx_chip *chip, int port, u8 *cmode) return 0; } +int mv88e6250_port_link_state(struct mv88e6xxx_chip *chip, int port, + struct phylink_link_state *state) +{ + int err; + u16 reg; + + err = mv88e6xxx_port_read(chip, port, MV88E6XXX_PORT_STS, ®); + if (err) + return err; + + if (port < 5) { + switch (reg & MV88E6250_PORT_STS_PORTMODE_MASK) { + case MV88E6250_PORT_STS_PORTMODE_PHY_10_HALF: + state->speed = SPEED_10; + state->duplex = DUPLEX_HALF; + break; + case MV88E6250_PORT_STS_PORTMODE_PHY_100_HALF: + state->speed = SPEED_100; + state->duplex = DUPLEX_HALF; + break; + case MV88E6250_PORT_STS_PORTMODE_PHY_10_FULL: + state->speed = SPEED_10; + state->duplex = DUPLEX_FULL; + break; + case MV88E6250_PORT_STS_PORTMODE_PHY_100_FULL: + state->speed = SPEED_100; + state->duplex = DUPLEX_FULL; + break; + default: + state->speed = SPEED_UNKNOWN; + state->duplex = DUPLEX_UNKNOWN; + break; + } + } else { + switch (reg & MV88E6250_PORT_STS_PORTMODE_MASK) { + case MV88E6250_PORT_STS_PORTMODE_MII_10_HALF: + state->speed = SPEED_10; + state->duplex = DUPLEX_HALF; + break; + case MV88E6250_PORT_STS_PORTMODE_MII_100_HALF: + state->speed = SPEED_100; + state->duplex = DUPLEX_HALF; + break; + case MV88E6250_PORT_STS_PORTMODE_MII_10_FULL: + state->speed = SPEED_10; + state->duplex = DUPLEX_FULL; + break; + case MV88E6250_PORT_STS_PORTMODE_MII_100_FULL: + state->speed = SPEED_100; + state->duplex = DUPLEX_FULL; + break; + default: + state->speed = SPEED_UNKNOWN; + state->duplex = DUPLEX_UNKNOWN; + break; + } + } + + state->link = !!(reg & MV88E6250_PORT_STS_LINK); + state->an_enabled = 1; + state->an_complete = state->link; + + return 0; +} + int mv88e6352_port_link_state(struct mv88e6xxx_chip *chip, int port, struct phylink_link_state *state) { diff --git a/drivers/net/dsa/mv88e6xxx/port.h b/drivers/net/dsa/mv88e6xxx/port.h index f2fba3f73199..8d5a6cd6fb19 100644 --- a/drivers/net/dsa/mv88e6xxx/port.h +++ b/drivers/net/dsa/mv88e6xxx/port.h @@ -19,6 +19,16 @@ #define MV88E6XXX_PORT_STS_MY_PAUSE 0x4000 #define MV88E6XXX_PORT_STS_HD_FLOW 0x2000 #define MV88E6XXX_PORT_STS_PHY_DETECT 0x1000 +#define MV88E6250_PORT_STS_LINK 0x1000 +#define MV88E6250_PORT_STS_PORTMODE_MASK 0x0f00 +#define MV88E6250_PORT_STS_PORTMODE_PHY_10_HALF 0x0800 +#define MV88E6250_PORT_STS_PORTMODE_PHY_100_HALF 0x0900 +#define MV88E6250_PORT_STS_PORTMODE_PHY_10_FULL 0x0a00 +#define MV88E6250_PORT_STS_PORTMODE_PHY_100_FULL 0x0b00 +#define MV88E6250_PORT_STS_PORTMODE_MII_10_HALF 0x0c00 +#define MV88E6250_PORT_STS_PORTMODE_MII_100_HALF 0x0d00 +#define MV88E6250_PORT_STS_PORTMODE_MII_10_FULL 0x0e00 +#define MV88E6250_PORT_STS_PORTMODE_MII_100_FULL 0x0f00 #define MV88E6XXX_PORT_STS_LINK 0x0800 #define MV88E6XXX_PORT_STS_DUPLEX 0x0400 #define MV88E6XXX_PORT_STS_SPEED_MASK 0x0300 @@ -108,6 +118,7 @@ #define MV88E6XXX_PORT_SWITCH_ID_PROD_6191 0x1910 #define MV88E6XXX_PORT_SWITCH_ID_PROD_6185 0x1a70 #define MV88E6XXX_PORT_SWITCH_ID_PROD_6240 0x2400 +#define MV88E6XXX_PORT_SWITCH_ID_PROD_6250 0x2500 #define MV88E6XXX_PORT_SWITCH_ID_PROD_6290 0x2900 #define MV88E6XXX_PORT_SWITCH_ID_PROD_6321 0x3100 #define MV88E6XXX_PORT_SWITCH_ID_PROD_6141 0x3400 @@ -275,6 +286,7 @@ int mv88e6xxx_port_set_duplex(struct mv88e6xxx_chip *chip, int port, int dup); int mv88e6065_port_set_speed(struct mv88e6xxx_chip *chip, int port, int speed); int mv88e6185_port_set_speed(struct mv88e6xxx_chip *chip, int port, int speed); +int mv88e6250_port_set_speed(struct mv88e6xxx_chip *chip, int port, int speed); int mv88e6341_port_set_speed(struct mv88e6xxx_chip *chip, int port, int speed); int mv88e6352_port_set_speed(struct mv88e6xxx_chip *chip, int port, int speed); int mv88e6390_port_set_speed(struct mv88e6xxx_chip *chip, int port, int speed); @@ -328,6 +340,8 @@ int mv88e6185_port_get_cmode(struct mv88e6xxx_chip *chip, int port, u8 *cmode); int mv88e6352_port_get_cmode(struct mv88e6xxx_chip *chip, int port, u8 *cmode); int mv88e6185_port_link_state(struct mv88e6xxx_chip *chip, int port, struct phylink_link_state *state); +int mv88e6250_port_link_state(struct mv88e6xxx_chip *chip, int port, + struct phylink_link_state *state); int mv88e6352_port_link_state(struct mv88e6xxx_chip *chip, int port, struct phylink_link_state *state); int mv88e6xxx_port_set_map_da(struct mv88e6xxx_chip *chip, int port); diff --git a/drivers/net/dsa/mv88e6xxx/ptp.c b/drivers/net/dsa/mv88e6xxx/ptp.c index 7b40c5886b75..768d256f7c9f 100644 --- a/drivers/net/dsa/mv88e6xxx/ptp.c +++ b/drivers/net/dsa/mv88e6xxx/ptp.c @@ -138,10 +138,10 @@ static void mv88e6352_tai_event_work(struct work_struct *ugly) u32 raw_ts; int err; - mutex_lock(&chip->reg_lock); + mv88e6xxx_reg_lock(chip); err = mv88e6xxx_tai_read(chip, MV88E6XXX_TAI_EVENT_STATUS, status, ARRAY_SIZE(status)); - mutex_unlock(&chip->reg_lock); + mv88e6xxx_reg_unlock(chip); if (err) { dev_err(chip->dev, "failed to read TAI status register\n"); @@ -158,18 +158,18 @@ static void mv88e6352_tai_event_work(struct work_struct *ugly) /* Clear the valid bit so the next timestamp can come in */ status[0] &= ~MV88E6XXX_TAI_EVENT_STATUS_VALID; - mutex_lock(&chip->reg_lock); + mv88e6xxx_reg_lock(chip); err = mv88e6xxx_tai_write(chip, MV88E6XXX_TAI_EVENT_STATUS, status[0]); - mutex_unlock(&chip->reg_lock); + mv88e6xxx_reg_unlock(chip); /* This is an external timestamp */ ev.type = PTP_CLOCK_EXTTS; /* We only have one timestamping channel. */ ev.index = 0; - mutex_lock(&chip->reg_lock); + mv88e6xxx_reg_lock(chip); ev.timestamp = timecounter_cyc2time(&chip->tstamp_tc, raw_ts); - mutex_unlock(&chip->reg_lock); + mv88e6xxx_reg_unlock(chip); ptp_clock_event(chip->ptp_clock, &ev); out: @@ -192,12 +192,12 @@ static int mv88e6xxx_ptp_adjfine(struct ptp_clock_info *ptp, long scaled_ppm) adj *= scaled_ppm; diff = div_u64(adj, CC_MULT_DEM); - mutex_lock(&chip->reg_lock); + mv88e6xxx_reg_lock(chip); timecounter_read(&chip->tstamp_tc); chip->tstamp_cc.mult = neg_adj ? mult - diff : mult + diff; - mutex_unlock(&chip->reg_lock); + mv88e6xxx_reg_unlock(chip); return 0; } @@ -206,9 +206,9 @@ static int mv88e6xxx_ptp_adjtime(struct ptp_clock_info *ptp, s64 delta) { struct mv88e6xxx_chip *chip = ptp_to_chip(ptp); - mutex_lock(&chip->reg_lock); + mv88e6xxx_reg_lock(chip); timecounter_adjtime(&chip->tstamp_tc, delta); - mutex_unlock(&chip->reg_lock); + mv88e6xxx_reg_unlock(chip); return 0; } @@ -219,9 +219,9 @@ static int mv88e6xxx_ptp_gettime(struct ptp_clock_info *ptp, struct mv88e6xxx_chip *chip = ptp_to_chip(ptp); u64 ns; - mutex_lock(&chip->reg_lock); + mv88e6xxx_reg_lock(chip); ns = timecounter_read(&chip->tstamp_tc); - mutex_unlock(&chip->reg_lock); + mv88e6xxx_reg_unlock(chip); *ts = ns_to_timespec64(ns); @@ -236,9 +236,9 @@ static int mv88e6xxx_ptp_settime(struct ptp_clock_info *ptp, ns = timespec64_to_ns(ts); - mutex_lock(&chip->reg_lock); + mv88e6xxx_reg_lock(chip); timecounter_init(&chip->tstamp_tc, &chip->tstamp_cc, ns); - mutex_unlock(&chip->reg_lock); + mv88e6xxx_reg_unlock(chip); return 0; } @@ -256,7 +256,7 @@ static int mv88e6352_ptp_enable_extts(struct mv88e6xxx_chip *chip, if (pin < 0) return -EBUSY; - mutex_lock(&chip->reg_lock); + mv88e6xxx_reg_lock(chip); if (on) { func = MV88E6352_G2_SCRATCH_GPIO_PCTL_EVREQ; @@ -278,7 +278,7 @@ static int mv88e6352_ptp_enable_extts(struct mv88e6xxx_chip *chip, } out: - mutex_unlock(&chip->reg_lock); + mv88e6xxx_reg_unlock(chip); return err; } diff --git a/drivers/net/dsa/mv88e6xxx/serdes.c b/drivers/net/dsa/mv88e6xxx/serdes.c index d986c5d55bf1..20c526c2a9ee 100644 --- a/drivers/net/dsa/mv88e6xxx/serdes.c +++ b/drivers/net/dsa/mv88e6xxx/serdes.c @@ -208,7 +208,7 @@ static irqreturn_t mv88e6352_serdes_thread_fn(int irq, void *dev_id) u16 status; int err; - mutex_lock(&chip->reg_lock); + mv88e6xxx_reg_lock(chip); err = mv88e6352_serdes_read(chip, MV88E6352_SERDES_INT_STATUS, &status); if (err) @@ -219,7 +219,7 @@ static irqreturn_t mv88e6352_serdes_thread_fn(int irq, void *dev_id) mv88e6352_serdes_irq_link(chip, port->port); } out: - mutex_unlock(&chip->reg_lock); + mv88e6xxx_reg_unlock(chip); return ret; } @@ -253,12 +253,12 @@ int mv88e6352_serdes_irq_setup(struct mv88e6xxx_chip *chip, int port) /* Requesting the IRQ will trigger irq callbacks. So we cannot * hold the reg_lock. */ - mutex_unlock(&chip->reg_lock); + mv88e6xxx_reg_unlock(chip); err = request_threaded_irq(chip->ports[port].serdes_irq, NULL, mv88e6352_serdes_thread_fn, IRQF_ONESHOT, "mv88e6xxx-serdes", &chip->ports[port]); - mutex_lock(&chip->reg_lock); + mv88e6xxx_reg_lock(chip); if (err) { dev_err(chip->dev, "Unable to request SERDES interrupt: %d\n", @@ -279,9 +279,9 @@ void mv88e6352_serdes_irq_free(struct mv88e6xxx_chip *chip, int port) /* Freeing the IRQ will trigger irq callbacks. So we cannot * hold the reg_lock. */ - mutex_unlock(&chip->reg_lock); + mv88e6xxx_reg_unlock(chip); free_irq(chip->ports[port].serdes_irq, &chip->ports[port]); - mutex_lock(&chip->reg_lock); + mv88e6xxx_reg_lock(chip); chip->ports[port].serdes_irq = 0; } @@ -621,7 +621,7 @@ static irqreturn_t mv88e6390_serdes_thread_fn(int irq, void *dev_id) lane = mv88e6390x_serdes_get_lane(chip, port->port); - mutex_lock(&chip->reg_lock); + mv88e6xxx_reg_lock(chip); switch (cmode) { case MV88E6XXX_PORT_STS_CMODE_SGMII: @@ -637,7 +637,7 @@ static irqreturn_t mv88e6390_serdes_thread_fn(int irq, void *dev_id) } } out: - mutex_unlock(&chip->reg_lock); + mv88e6xxx_reg_unlock(chip); return ret; } @@ -666,12 +666,12 @@ int mv88e6390x_serdes_irq_setup(struct mv88e6xxx_chip *chip, int port) /* Requesting the IRQ will trigger irq callbacks. So we cannot * hold the reg_lock. */ - mutex_unlock(&chip->reg_lock); + mv88e6xxx_reg_unlock(chip); err = request_threaded_irq(chip->ports[port].serdes_irq, NULL, mv88e6390_serdes_thread_fn, IRQF_ONESHOT, "mv88e6xxx-serdes", &chip->ports[port]); - mutex_lock(&chip->reg_lock); + mv88e6xxx_reg_lock(chip); if (err) { dev_err(chip->dev, "Unable to request SERDES interrupt: %d\n", @@ -705,9 +705,9 @@ void mv88e6390x_serdes_irq_free(struct mv88e6xxx_chip *chip, int port) /* Freeing the IRQ will trigger irq callbacks. So we cannot * hold the reg_lock. */ - mutex_unlock(&chip->reg_lock); + mv88e6xxx_reg_unlock(chip); free_irq(chip->ports[port].serdes_irq, &chip->ports[port]); - mutex_lock(&chip->reg_lock); + mv88e6xxx_reg_lock(chip); chip->ports[port].serdes_irq = 0; } diff --git a/drivers/net/dsa/mv88e6xxx/smi.c b/drivers/net/dsa/mv88e6xxx/smi.c index 92e9324f1fb9..5fc78a063843 100644 --- a/drivers/net/dsa/mv88e6xxx/smi.c +++ b/drivers/net/dsa/mv88e6xxx/smi.c @@ -20,6 +20,10 @@ * When ADDR is non-zero, the chip uses Multi-chip Addressing Mode, allowing * multiple devices to share the SMI interface. In this mode it responds to only * 2 registers, used to indirectly access the internal SMI devices. + * + * Some chips use a different scheme: Only the ADDR4 pin is used for + * configuration, and the device responds to 16 of the 32 SMI + * addresses, allowing two to coexist on the same SMI interface. */ static int mv88e6xxx_smi_direct_read(struct mv88e6xxx_chip *chip, @@ -72,6 +76,23 @@ static const struct mv88e6xxx_bus_ops mv88e6xxx_smi_direct_ops = { .write = mv88e6xxx_smi_direct_write, }; +static int mv88e6xxx_smi_dual_direct_read(struct mv88e6xxx_chip *chip, + int dev, int reg, u16 *data) +{ + return mv88e6xxx_smi_direct_read(chip, chip->sw_addr + dev, reg, data); +} + +static int mv88e6xxx_smi_dual_direct_write(struct mv88e6xxx_chip *chip, + int dev, int reg, u16 data) +{ + return mv88e6xxx_smi_direct_write(chip, chip->sw_addr + dev, reg, data); +} + +static const struct mv88e6xxx_bus_ops mv88e6xxx_smi_dual_direct_ops = { + .read = mv88e6xxx_smi_dual_direct_read, + .write = mv88e6xxx_smi_dual_direct_write, +}; + /* Offset 0x00: SMI Command Register * Offset 0x01: SMI Data Register */ @@ -140,7 +161,9 @@ static const struct mv88e6xxx_bus_ops mv88e6xxx_smi_indirect_ops = { int mv88e6xxx_smi_init(struct mv88e6xxx_chip *chip, struct mii_bus *bus, int sw_addr) { - if (sw_addr == 0) + if (chip->info->dual_chip) + chip->smi_ops = &mv88e6xxx_smi_dual_direct_ops; + else if (sw_addr == 0) chip->smi_ops = &mv88e6xxx_smi_direct_ops; else if (chip->info->multi_chip) chip->smi_ops = &mv88e6xxx_smi_indirect_ops; diff --git a/drivers/net/dsa/qca8k.c b/drivers/net/dsa/qca8k.c index c4fa400efdcc..27709f866c23 100644 --- a/drivers/net/dsa/qca8k.c +++ b/drivers/net/dsa/qca8k.c @@ -14,6 +14,7 @@ #include <linux/of_platform.h> #include <linux/if_bridge.h> #include <linux/mdio.h> +#include <linux/gpio.h> #include <linux/etherdevice.h> #include "qca8k.h" @@ -1046,6 +1047,20 @@ qca8k_sw_probe(struct mdio_device *mdiodev) priv->bus = mdiodev->bus; priv->dev = &mdiodev->dev; + priv->reset_gpio = devm_gpiod_get_optional(priv->dev, "reset", + GPIOD_ASIS); + if (IS_ERR(priv->reset_gpio)) + return PTR_ERR(priv->reset_gpio); + + if (priv->reset_gpio) { + gpiod_set_value_cansleep(priv->reset_gpio, 1); + /* The active low duration must be greater than 10 ms + * and checkpatch.pl wants 20 ms. + */ + msleep(20); + gpiod_set_value_cansleep(priv->reset_gpio, 0); + } + /* read the switches ID register */ id = qca8k_read(priv, QCA8K_REG_MASK_CTRL); id >>= QCA8K_MASK_CTRL_ID_S; diff --git a/drivers/net/dsa/qca8k.h b/drivers/net/dsa/qca8k.h index 91557433ce2f..42d6ea24eb14 100644 --- a/drivers/net/dsa/qca8k.h +++ b/drivers/net/dsa/qca8k.h @@ -10,6 +10,7 @@ #include <linux/delay.h> #include <linux/regmap.h> +#include <linux/gpio.h> #define QCA8K_NUM_PORTS 7 @@ -174,6 +175,7 @@ struct qca8k_priv { struct mutex reg_mutex; struct device *dev; struct dsa_switch_ops ops; + struct gpio_desc *reset_gpio; }; struct qca8k_mib_desc { diff --git a/drivers/net/dsa/sja1105/Kconfig b/drivers/net/dsa/sja1105/Kconfig index 1144fc5f61a8..770134a66e48 100644 --- a/drivers/net/dsa/sja1105/Kconfig +++ b/drivers/net/dsa/sja1105/Kconfig @@ -9,10 +9,17 @@ tristate "NXP SJA1105 Ethernet switch family support" This is the driver for the NXP SJA1105 automotive Ethernet switch family. These are 5-port devices and are managed over an SPI interface. Probing is handled based on OF bindings and so is the - linkage to phylib. The driver supports the following revisions: + linkage to PHYLINK. The driver supports the following revisions: - SJA1105E (Gen. 1, No TT-Ethernet) - SJA1105T (Gen. 1, TT-Ethernet) - SJA1105P (Gen. 2, No SGMII, No TT-Ethernet) - SJA1105Q (Gen. 2, No SGMII, TT-Ethernet) - SJA1105R (Gen. 2, SGMII, No TT-Ethernet) - SJA1105S (Gen. 2, SGMII, TT-Ethernet) + +config NET_DSA_SJA1105_PTP + bool "Support for the PTP clock on the NXP SJA1105 Ethernet switch" + depends on NET_DSA_SJA1105 + help + This enables support for timestamping and PTP clock manipulations in + the SJA1105 DSA driver. diff --git a/drivers/net/dsa/sja1105/Makefile b/drivers/net/dsa/sja1105/Makefile index 941848de8b46..4483113e6259 100644 --- a/drivers/net/dsa/sja1105/Makefile +++ b/drivers/net/dsa/sja1105/Makefile @@ -8,3 +8,7 @@ sja1105-objs := \ sja1105_clocking.o \ sja1105_static_config.o \ sja1105_dynamic_config.o \ + +ifdef CONFIG_NET_DSA_SJA1105_PTP +sja1105-objs += sja1105_ptp.o +endif diff --git a/drivers/net/dsa/sja1105/sja1105.h b/drivers/net/dsa/sja1105/sja1105.h index b043bfc408f2..78094db32622 100644 --- a/drivers/net/dsa/sja1105/sja1105.h +++ b/drivers/net/dsa/sja1105/sja1105.h @@ -5,6 +5,8 @@ #ifndef _SJA1105_H #define _SJA1105_H +#include <linux/ptp_clock_kernel.h> +#include <linux/timecounter.h> #include <linux/dsa/sja1105.h> #include <net/dsa.h> #include <linux/mutex.h> @@ -27,9 +29,14 @@ struct sja1105_regs { u64 rgu; u64 config; u64 rmii_pll1; + u64 ptp_control; + u64 ptpclk; + u64 ptpclkrate; + u64 ptptsclk; + u64 ptpegr_ts[SJA1105_NUM_PORTS]; u64 pad_mii_tx[SJA1105_NUM_PORTS]; + u64 pad_mii_id[SJA1105_NUM_PORTS]; u64 cgu_idiv[SJA1105_NUM_PORTS]; - u64 rgmii_pad_mii_tx[SJA1105_NUM_PORTS]; u64 mii_tx_clk[SJA1105_NUM_PORTS]; u64 mii_rx_clk[SJA1105_NUM_PORTS]; u64 mii_ext_tx_clk[SJA1105_NUM_PORTS]; @@ -50,11 +57,26 @@ struct sja1105_info { * switch core and device_id) */ u64 part_no; + /* E/T and P/Q/R/S have partial timestamps of different sizes. + * They must be reconstructed on both families anyway to get the full + * 64-bit values back. + */ + int ptp_ts_bits; + /* Also SPI commands are of different sizes to retrieve + * the egress timestamps. + */ + int ptpegr_ts_bytes; const struct sja1105_dynamic_table_ops *dyn_ops; const struct sja1105_table_ops *static_ops; const struct sja1105_regs *regs; + int (*ptp_cmd)(const void *ctx, const void *data); int (*reset_cmd)(const void *ctx, const void *data); int (*setup_rgmii_delay)(const void *ctx, int port); + /* Prototypes from include/net/dsa.h */ + int (*fdb_add_cmd)(struct dsa_switch *ds, int port, + const unsigned char *addr, u16 vid); + int (*fdb_del_cmd)(struct dsa_switch *ds, int port, + const unsigned char *addr, u16 vid); const char *name; }; @@ -67,13 +89,25 @@ struct sja1105_private { struct spi_device *spidev; struct dsa_switch *ds; struct sja1105_port ports[SJA1105_NUM_PORTS]; + struct ptp_clock_info ptp_caps; + struct ptp_clock *clock; + /* The cycle counter translates the PTP timestamps (based on + * a free-running counter) into a software time domain. + */ + struct cyclecounter tstamp_cc; + struct timecounter tstamp_tc; + struct delayed_work refresh_work; + /* Serializes all operations on the cycle counter */ + struct mutex ptp_lock; /* Serializes transmission of management frames so that * the switch doesn't confuse them with one another. */ struct mutex mgmt_lock; + struct sja1105_tagger_data tagger_data; }; #include "sja1105_dynamic_config.h" +#include "sja1105_ptp.h" struct sja1105_spi_message { u64 access; @@ -97,6 +131,8 @@ int sja1105_spi_send_long_packed_buf(const struct sja1105_private *priv, sja1105_spi_rw_mode_t rw, u64 base_addr, void *packed_buf, u64 buf_len); int sja1105_static_config_upload(struct sja1105_private *priv); +int sja1105_inhibit_tx(const struct sja1105_private *priv, + unsigned long port_bitmap, bool tx_inhibited); extern struct sja1105_info sja1105e_info; extern struct sja1105_info sja1105t_info; @@ -125,6 +161,7 @@ typedef enum { SJA1105_SPEED_AUTO = 0, } sja1105_speed_t; +int sja1105pqrs_setup_rgmii_delay(const void *ctx, int port); int sja1105_clocking_setup_port(struct sja1105_private *priv, int port); int sja1105_clocking_setup(struct sja1105_private *priv); @@ -142,7 +179,20 @@ int sja1105_dynamic_config_write(struct sja1105_private *priv, enum sja1105_blk_idx blk_idx, int index, void *entry, bool keep); -u8 sja1105_fdb_hash(struct sja1105_private *priv, const u8 *addr, u16 vid); +enum sja1105_iotag { + SJA1105_C_TAG = 0, /* Inner VLAN header */ + SJA1105_S_TAG = 1, /* Outer VLAN header */ +}; + +u8 sja1105et_fdb_hash(struct sja1105_private *priv, const u8 *addr, u16 vid); +int sja1105et_fdb_add(struct dsa_switch *ds, int port, + const unsigned char *addr, u16 vid); +int sja1105et_fdb_del(struct dsa_switch *ds, int port, + const unsigned char *addr, u16 vid); +int sja1105pqrs_fdb_add(struct dsa_switch *ds, int port, + const unsigned char *addr, u16 vid); +int sja1105pqrs_fdb_del(struct dsa_switch *ds, int port, + const unsigned char *addr, u16 vid); /* Common implementations for the static and dynamic configs */ size_t sja1105_l2_forwarding_entry_packing(void *buf, void *entry_ptr, diff --git a/drivers/net/dsa/sja1105/sja1105_clocking.c b/drivers/net/dsa/sja1105/sja1105_clocking.c index 94bfe0ee50a8..608126a15d72 100644 --- a/drivers/net/dsa/sja1105/sja1105_clocking.c +++ b/drivers/net/dsa/sja1105/sja1105_clocking.c @@ -19,6 +19,17 @@ struct sja1105_cfg_pad_mii_tx { u64 clk_ipud; }; +struct sja1105_cfg_pad_mii_id { + u64 rxc_stable_ovr; + u64 rxc_delay; + u64 rxc_bypass; + u64 rxc_pd; + u64 txc_stable_ovr; + u64 txc_delay; + u64 txc_bypass; + u64 txc_pd; +}; + /* UM10944 Table 82. * IDIV_0_C to IDIV_4_C control registers * (addr. 10000Bh to 10000Fh) @@ -373,11 +384,88 @@ static int sja1105_rgmii_cfg_pad_tx_config(struct sja1105_private *priv, sja1105_cfg_pad_mii_tx_packing(packed_buf, &pad_mii_tx, PACK); return sja1105_spi_send_packed_buf(priv, SPI_WRITE, - regs->rgmii_pad_mii_tx[port], + regs->pad_mii_tx[port], packed_buf, SJA1105_SIZE_CGU_CMD); } -static int sja1105_rgmii_clocking_setup(struct sja1105_private *priv, int port) +static void +sja1105_cfg_pad_mii_id_packing(void *buf, struct sja1105_cfg_pad_mii_id *cmd, + enum packing_op op) +{ + const int size = SJA1105_SIZE_CGU_CMD; + + sja1105_packing(buf, &cmd->rxc_stable_ovr, 15, 15, size, op); + sja1105_packing(buf, &cmd->rxc_delay, 14, 10, size, op); + sja1105_packing(buf, &cmd->rxc_bypass, 9, 9, size, op); + sja1105_packing(buf, &cmd->rxc_pd, 8, 8, size, op); + sja1105_packing(buf, &cmd->txc_stable_ovr, 7, 7, size, op); + sja1105_packing(buf, &cmd->txc_delay, 6, 2, size, op); + sja1105_packing(buf, &cmd->txc_bypass, 1, 1, size, op); + sja1105_packing(buf, &cmd->txc_pd, 0, 0, size, op); +} + +/* Valid range in degrees is an integer between 73.8 and 101.7 */ +static inline u64 sja1105_rgmii_delay(u64 phase) +{ + /* UM11040.pdf: The delay in degree phase is 73.8 + delay_tune * 0.9. + * To avoid floating point operations we'll multiply by 10 + * and get 1 decimal point precision. + */ + phase *= 10; + return (phase - 738) / 9; +} + +/* The RGMII delay setup procedure is 2-step and gets called upon each + * .phylink_mac_config. Both are strategic. + * The reason is that the RX Tunable Delay Line of the SJA1105 MAC has issues + * with recovering from a frequency change of the link partner's RGMII clock. + * The easiest way to recover from this is to temporarily power down the TDL, + * as it will re-lock at the new frequency afterwards. + */ +int sja1105pqrs_setup_rgmii_delay(const void *ctx, int port) +{ + const struct sja1105_private *priv = ctx; + const struct sja1105_regs *regs = priv->info->regs; + struct sja1105_cfg_pad_mii_id pad_mii_id = {0}; + u8 packed_buf[SJA1105_SIZE_CGU_CMD] = {0}; + int rc; + + if (priv->rgmii_rx_delay[port]) + pad_mii_id.rxc_delay = sja1105_rgmii_delay(90); + if (priv->rgmii_tx_delay[port]) + pad_mii_id.txc_delay = sja1105_rgmii_delay(90); + + /* Stage 1: Turn the RGMII delay lines off. */ + pad_mii_id.rxc_bypass = 1; + pad_mii_id.rxc_pd = 1; + pad_mii_id.txc_bypass = 1; + pad_mii_id.txc_pd = 1; + sja1105_cfg_pad_mii_id_packing(packed_buf, &pad_mii_id, PACK); + + rc = sja1105_spi_send_packed_buf(priv, SPI_WRITE, + regs->pad_mii_id[port], + packed_buf, SJA1105_SIZE_CGU_CMD); + if (rc < 0) + return rc; + + /* Stage 2: Turn the RGMII delay lines on. */ + if (priv->rgmii_rx_delay[port]) { + pad_mii_id.rxc_bypass = 0; + pad_mii_id.rxc_pd = 0; + } + if (priv->rgmii_tx_delay[port]) { + pad_mii_id.txc_bypass = 0; + pad_mii_id.txc_pd = 0; + } + sja1105_cfg_pad_mii_id_packing(packed_buf, &pad_mii_id, PACK); + + return sja1105_spi_send_packed_buf(priv, SPI_WRITE, + regs->pad_mii_id[port], + packed_buf, SJA1105_SIZE_CGU_CMD); +} + +static int sja1105_rgmii_clocking_setup(struct sja1105_private *priv, int port, + sja1105_mii_role_t role) { struct device *dev = priv->ds->dev; struct sja1105_mac_config_entry *mac; @@ -429,6 +517,12 @@ static int sja1105_rgmii_clocking_setup(struct sja1105_private *priv, int port) } if (!priv->info->setup_rgmii_delay) return 0; + /* The role has no hardware effect for RGMII. However we use it as + * a proxy for this interface being a MAC-to-MAC connection, with + * the RGMII internal delays needing to be applied by us. + */ + if (role == XMII_MAC) + return 0; return priv->info->setup_rgmii_delay(priv, port); } @@ -575,7 +669,7 @@ int sja1105_clocking_setup_port(struct sja1105_private *priv, int port) rc = sja1105_rmii_clocking_setup(priv, port, role); break; case XMII_MODE_RGMII: - rc = sja1105_rgmii_clocking_setup(priv, port); + rc = sja1105_rgmii_clocking_setup(priv, port, role); break; default: dev_err(dev, "Invalid interface mode specified: %d\n", diff --git a/drivers/net/dsa/sja1105/sja1105_dynamic_config.c b/drivers/net/dsa/sja1105/sja1105_dynamic_config.c index e73ab28bf632..6bfb1696a6f2 100644 --- a/drivers/net/dsa/sja1105/sja1105_dynamic_config.c +++ b/drivers/net/dsa/sja1105/sja1105_dynamic_config.c @@ -3,6 +3,98 @@ */ #include "sja1105.h" +/* In the dynamic configuration interface, the switch exposes a register-like + * view of some of the static configuration tables. + * Many times the field organization of the dynamic tables is abbreviated (not + * all fields are dynamically reconfigurable) and different from the static + * ones, but the key reason for having it is that we can spare a switch reset + * for settings that can be changed dynamically. + * + * This file creates a per-switch-family abstraction called + * struct sja1105_dynamic_table_ops and two operations that work with it: + * - sja1105_dynamic_config_write + * - sja1105_dynamic_config_read + * + * Compared to the struct sja1105_table_ops from sja1105_static_config.c, + * the dynamic accessors work with a compound buffer: + * + * packed_buf + * + * | + * V + * +-----------------------------------------+------------------+ + * | ENTRY BUFFER | COMMAND BUFFER | + * +-----------------------------------------+------------------+ + * + * <----------------------- packed_size ------------------------> + * + * The ENTRY BUFFER may or may not have the same layout, or size, as its static + * configuration table entry counterpart. When it does, the same packing + * function is reused (bar exceptional cases - see + * sja1105pqrs_dyn_l2_lookup_entry_packing). + * + * The reason for the COMMAND BUFFER being at the end is to be able to send + * a dynamic write command through a single SPI burst. By the time the switch + * reacts to the command, the ENTRY BUFFER is already populated with the data + * sent by the core. + * + * The COMMAND BUFFER is always SJA1105_SIZE_DYN_CMD bytes (one 32-bit word) in + * size. + * + * Sometimes the ENTRY BUFFER does not really exist (when the number of fields + * that can be reconfigured is small), then the switch repurposes some of the + * unused 32 bits of the COMMAND BUFFER to hold ENTRY data. + * + * The key members of struct sja1105_dynamic_table_ops are: + * - .entry_packing: A function that deals with packing an ENTRY structure + * into an SPI buffer, or retrieving an ENTRY structure + * from one. + * The @packed_buf pointer it's given does always point to + * the ENTRY portion of the buffer. + * - .cmd_packing: A function that deals with packing/unpacking the COMMAND + * structure to/from the SPI buffer. + * It is given the same @packed_buf pointer as .entry_packing, + * so most of the time, the @packed_buf points *behind* the + * COMMAND offset inside the buffer. + * To access the COMMAND portion of the buffer, the function + * knows its correct offset. + * Giving both functions the same pointer is handy because in + * extreme cases (see sja1105pqrs_dyn_l2_lookup_entry_packing) + * the .entry_packing is able to jump to the COMMAND portion, + * or vice-versa (sja1105pqrs_l2_lookup_cmd_packing). + * - .access: A bitmap of: + * OP_READ: Set if the hardware manual marks the ENTRY portion of the + * dynamic configuration table buffer as R (readable) after + * an SPI read command (the switch will populate the buffer). + * OP_WRITE: Set if the manual marks the ENTRY portion of the dynamic + * table buffer as W (writable) after an SPI write command + * (the switch will read the fields provided in the buffer). + * OP_DEL: Set if the manual says the VALIDENT bit is supported in the + * COMMAND portion of this dynamic config buffer (i.e. the + * specified entry can be invalidated through a SPI write + * command). + * OP_SEARCH: Set if the manual says that the index of an entry can + * be retrieved in the COMMAND portion of the buffer based + * on its ENTRY portion, as a result of a SPI write command. + * Only the TCAM-based FDB table on SJA1105 P/Q/R/S supports + * this. + * - .max_entry_count: The number of entries, counting from zero, that can be + * reconfigured through the dynamic interface. If a static + * table can be reconfigured at all dynamically, this + * number always matches the maximum number of supported + * static entries. + * - .packed_size: The length in bytes of the compound ENTRY + COMMAND BUFFER. + * Note that sometimes the compound buffer may contain holes in + * it (see sja1105_vlan_lookup_cmd_packing). The @packed_buf is + * contiguous however, so @packed_size includes any unused + * bytes. + * - .addr: The base SPI address at which the buffer must be written to the + * switch's memory. When looking at the hardware manual, this must + * always match the lowest documented address for the ENTRY, and not + * that of the COMMAND, since the other 32-bit words will follow along + * at the correct addresses. + */ + #define SJA1105_SIZE_DYN_CMD 4 #define SJA1105ET_SIZE_MAC_CONFIG_DYN_ENTRY \ @@ -35,17 +127,70 @@ #define SJA1105_MAX_DYN_CMD_SIZE \ SJA1105PQRS_SIZE_MAC_CONFIG_DYN_CMD +struct sja1105_dyn_cmd { + bool search; + u64 valid; + u64 rdwrset; + u64 errors; + u64 valident; + u64 index; +}; + +enum sja1105_hostcmd { + SJA1105_HOSTCMD_SEARCH = 1, + SJA1105_HOSTCMD_READ = 2, + SJA1105_HOSTCMD_WRITE = 3, + SJA1105_HOSTCMD_INVALIDATE = 4, +}; + static void sja1105pqrs_l2_lookup_cmd_packing(void *buf, struct sja1105_dyn_cmd *cmd, enum packing_op op) { u8 *p = buf + SJA1105PQRS_SIZE_L2_LOOKUP_ENTRY; const int size = SJA1105_SIZE_DYN_CMD; + u64 hostcmd; sja1105_packing(p, &cmd->valid, 31, 31, size, op); sja1105_packing(p, &cmd->rdwrset, 30, 30, size, op); sja1105_packing(p, &cmd->errors, 29, 29, size, op); sja1105_packing(p, &cmd->valident, 27, 27, size, op); + + /* VALIDENT is supposed to indicate "keep or not", but in SJA1105 E/T, + * using it to delete a management route was unsupported. UM10944 + * said about it: + * + * In case of a write access with the MGMTROUTE flag set, + * the flag will be ignored. It will always be found cleared + * for read accesses with the MGMTROUTE flag set. + * + * SJA1105 P/Q/R/S keeps the same behavior w.r.t. VALIDENT, but there + * is now another flag called HOSTCMD which does more stuff (quoting + * from UM11040): + * + * A write request is accepted only when HOSTCMD is set to write host + * or invalid. A read request is accepted only when HOSTCMD is set to + * search host or read host. + * + * So it is possible to translate a RDWRSET/VALIDENT combination into + * HOSTCMD so that we keep the dynamic command API in place, and + * at the same time achieve compatibility with the management route + * command structure. + */ + if (cmd->rdwrset == SPI_READ) { + if (cmd->search) + hostcmd = SJA1105_HOSTCMD_SEARCH; + else + hostcmd = SJA1105_HOSTCMD_READ; + } else { + /* SPI_WRITE */ + if (cmd->valident) + hostcmd = SJA1105_HOSTCMD_WRITE; + else + hostcmd = SJA1105_HOSTCMD_INVALIDATE; + } + sja1105_packing(p, &hostcmd, 25, 23, size, op); + /* Hack - The hardware takes the 'index' field within * struct sja1105_l2_lookup_entry as the index on which this command * will operate. However it will ignore everything else, so 'index' @@ -54,9 +199,66 @@ sja1105pqrs_l2_lookup_cmd_packing(void *buf, struct sja1105_dyn_cmd *cmd, * such that our API doesn't need to ask for a full-blown entry * structure when e.g. a delete is requested. */ - sja1105_packing(buf, &cmd->index, 29, 20, + sja1105_packing(buf, &cmd->index, 15, 6, SJA1105PQRS_SIZE_L2_LOOKUP_ENTRY, op); - /* TODO hostcmd */ +} + +/* The switch is so retarded that it makes our command/entry abstraction + * crumble apart. + * + * On P/Q/R/S, the switch tries to say whether a FDB entry + * is statically programmed or dynamically learned via a flag called LOCKEDS. + * The hardware manual says about this fiels: + * + * On write will specify the format of ENTRY. + * On read the flag will be found cleared at times the VALID flag is found + * set. The flag will also be found cleared in response to a read having the + * MGMTROUTE flag set. In response to a read with the MGMTROUTE flag + * cleared, the flag be set if the most recent access operated on an entry + * that was either loaded by configuration or through dynamic reconfiguration + * (as opposed to automatically learned entries). + * + * The trouble with this flag is that it's part of the *command* to access the + * dynamic interface, and not part of the *entry* retrieved from it. + * Otherwise said, for a sja1105_dynamic_config_read, LOCKEDS is supposed to be + * an output from the switch into the command buffer, and for a + * sja1105_dynamic_config_write, the switch treats LOCKEDS as an input + * (hence we can write either static, or automatically learned entries, from + * the core). + * But the manual contradicts itself in the last phrase where it says that on + * read, LOCKEDS will be set to 1 for all FDB entries written through the + * dynamic interface (therefore, the value of LOCKEDS from the + * sja1105_dynamic_config_write is not really used for anything, it'll store a + * 1 anyway). + * This means you can't really write a FDB entry with LOCKEDS=0 (automatically + * learned) into the switch, which kind of makes sense. + * As for reading through the dynamic interface, it doesn't make too much sense + * to put LOCKEDS into the command, since the switch will inevitably have to + * ignore it (otherwise a command would be like "read the FDB entry 123, but + * only if it's dynamically learned" <- well how am I supposed to know?) and + * just use it as an output buffer for its findings. But guess what... that's + * what the entry buffer is for! + * Unfortunately, what really breaks this abstraction is the fact that it + * wasn't designed having the fact in mind that the switch can output + * entry-related data as writeback through the command buffer. + * However, whether a FDB entry is statically or dynamically learned *is* part + * of the entry and not the command data, no matter what the switch thinks. + * In order to do that, we'll need to wrap around the + * sja1105pqrs_l2_lookup_entry_packing from sja1105_static_config.c, and take + * a peek outside of the caller-supplied @buf (the entry buffer), to reach the + * command buffer. + */ +static size_t +sja1105pqrs_dyn_l2_lookup_entry_packing(void *buf, void *entry_ptr, + enum packing_op op) +{ + struct sja1105_l2_lookup_entry *entry = entry_ptr; + u8 *cmd = buf + SJA1105PQRS_SIZE_L2_LOOKUP_ENTRY; + const int size = SJA1105_SIZE_DYN_CMD; + + sja1105_packing(cmd, &entry->lockeds, 28, 28, size, op); + + return sja1105pqrs_l2_lookup_entry_packing(buf, entry_ptr, op); } static void @@ -107,6 +309,36 @@ static size_t sja1105et_mgmt_route_entry_packing(void *buf, void *entry_ptr, return size; } +static void +sja1105pqrs_mgmt_route_cmd_packing(void *buf, struct sja1105_dyn_cmd *cmd, + enum packing_op op) +{ + u8 *p = buf + SJA1105PQRS_SIZE_L2_LOOKUP_ENTRY; + u64 mgmtroute = 1; + + sja1105pqrs_l2_lookup_cmd_packing(buf, cmd, op); + if (op == PACK) + sja1105_pack(p, &mgmtroute, 26, 26, SJA1105_SIZE_DYN_CMD); +} + +static size_t sja1105pqrs_mgmt_route_entry_packing(void *buf, void *entry_ptr, + enum packing_op op) +{ + const size_t size = SJA1105PQRS_SIZE_L2_LOOKUP_ENTRY; + struct sja1105_mgmt_entry *entry = entry_ptr; + + /* In P/Q/R/S, enfport got renamed to mgmtvalid, but its purpose + * is the same (driver uses it to confirm that frame was sent). + * So just keep the name from E/T. + */ + sja1105_packing(buf, &entry->tsreg, 71, 71, size, op); + sja1105_packing(buf, &entry->takets, 70, 70, size, op); + sja1105_packing(buf, &entry->macaddr, 69, 22, size, op); + sja1105_packing(buf, &entry->destports, 21, 17, size, op); + sja1105_packing(buf, &entry->enfport, 16, 16, size, op); + return size; +} + /* In E/T, entry is at addresses 0x27-0x28. There is a 4 byte gap at 0x29, * and command is at 0x2a. Similarly in P/Q/R/S there is a 1 register gap * between entry (0x2d, 0x2e) and command (0x30). @@ -240,6 +472,7 @@ sja1105et_general_params_entry_packing(void *buf, void *entry_ptr, #define OP_READ BIT(0) #define OP_WRITE BIT(1) #define OP_DEL BIT(2) +#define OP_SEARCH BIT(3) /* SJA1105E/T: First generation */ struct sja1105_dynamic_table_ops sja1105et_dyn_ops[BLK_IDX_MAX_DYN] = { @@ -293,6 +526,7 @@ struct sja1105_dynamic_table_ops sja1105et_dyn_ops[BLK_IDX_MAX_DYN] = { .addr = 0x38, }, [BLK_IDX_L2_FORWARDING_PARAMS] = {0}, + [BLK_IDX_AVB_PARAMS] = {0}, [BLK_IDX_GENERAL_PARAMS] = { .entry_packing = sja1105et_general_params_entry_packing, .cmd_packing = sja1105et_general_params_cmd_packing, @@ -304,14 +538,22 @@ struct sja1105_dynamic_table_ops sja1105et_dyn_ops[BLK_IDX_MAX_DYN] = { [BLK_IDX_XMII_PARAMS] = {0}, }; -/* SJA1105P/Q/R/S: Second generation: TODO */ +/* SJA1105P/Q/R/S: Second generation */ struct sja1105_dynamic_table_ops sja1105pqrs_dyn_ops[BLK_IDX_MAX_DYN] = { [BLK_IDX_L2_LOOKUP] = { - .entry_packing = sja1105pqrs_l2_lookup_entry_packing, + .entry_packing = sja1105pqrs_dyn_l2_lookup_entry_packing, .cmd_packing = sja1105pqrs_l2_lookup_cmd_packing, - .access = (OP_READ | OP_WRITE | OP_DEL), + .access = (OP_READ | OP_WRITE | OP_DEL | OP_SEARCH), .max_entry_count = SJA1105_MAX_L2_LOOKUP_COUNT, - .packed_size = SJA1105ET_SIZE_L2_LOOKUP_DYN_CMD, + .packed_size = SJA1105PQRS_SIZE_L2_LOOKUP_DYN_CMD, + .addr = 0x24, + }, + [BLK_IDX_MGMT_ROUTE] = { + .entry_packing = sja1105pqrs_mgmt_route_entry_packing, + .cmd_packing = sja1105pqrs_mgmt_route_cmd_packing, + .access = (OP_READ | OP_WRITE | OP_DEL | OP_SEARCH), + .max_entry_count = SJA1105_NUM_PORTS, + .packed_size = SJA1105PQRS_SIZE_L2_LOOKUP_DYN_CMD, .addr = 0x24, }, [BLK_IDX_L2_POLICING] = {0}, @@ -348,6 +590,7 @@ struct sja1105_dynamic_table_ops sja1105pqrs_dyn_ops[BLK_IDX_MAX_DYN] = { .addr = 0x38, }, [BLK_IDX_L2_FORWARDING_PARAMS] = {0}, + [BLK_IDX_AVB_PARAMS] = {0}, [BLK_IDX_GENERAL_PARAMS] = { .entry_packing = sja1105et_general_params_entry_packing, .cmd_packing = sja1105et_general_params_cmd_packing, @@ -359,6 +602,24 @@ struct sja1105_dynamic_table_ops sja1105pqrs_dyn_ops[BLK_IDX_MAX_DYN] = { [BLK_IDX_XMII_PARAMS] = {0}, }; +/* Provides read access to the settings through the dynamic interface + * of the switch. + * @blk_idx is used as key to select from the sja1105_dynamic_table_ops. + * The selection is limited by the hardware in respect to which + * configuration blocks can be read through the dynamic interface. + * @index is used to retrieve a particular table entry. If negative, + * (and if the @blk_idx supports the searching operation) a search + * is performed by the @entry parameter. + * @entry Type-casted to an unpacked structure that holds a table entry + * of the type specified in @blk_idx. + * Usually an output argument. If @index is negative, then this + * argument is used as input/output: it should be pre-populated + * with the element to search for. Entries which support the + * search operation will have an "index" field (not the @index + * argument to this function) and that is where the found index + * will be returned (or left unmodified - thus negative - if not + * found). + */ int sja1105_dynamic_config_read(struct sja1105_private *priv, enum sja1105_blk_idx blk_idx, int index, void *entry) @@ -375,8 +636,10 @@ int sja1105_dynamic_config_read(struct sja1105_private *priv, ops = &priv->info->dyn_ops[blk_idx]; - if (index >= ops->max_entry_count) + if (index >= 0 && index >= ops->max_entry_count) return -ERANGE; + if (index < 0 && !(ops->access & OP_SEARCH)) + return -EOPNOTSUPP; if (!(ops->access & OP_READ)) return -EOPNOTSUPP; if (ops->packed_size > SJA1105_MAX_DYN_CMD_SIZE) @@ -388,9 +651,20 @@ int sja1105_dynamic_config_read(struct sja1105_private *priv, cmd.valid = true; /* Trigger action on table entry */ cmd.rdwrset = SPI_READ; /* Action is read */ - cmd.index = index; + if (index < 0) { + /* Avoid copying a signed negative number to an u64 */ + cmd.index = 0; + cmd.search = true; + } else { + cmd.index = index; + cmd.search = false; + } + cmd.valident = true; ops->cmd_packing(packed_buf, &cmd, PACK); + if (cmd.search) + ops->entry_packing(packed_buf, entry, PACK); + /* Send SPI write operation: read config table entry */ rc = sja1105_spi_send_packed_buf(priv, SPI_WRITE, ops->addr, packed_buf, ops->packed_size); @@ -416,7 +690,7 @@ int sja1105_dynamic_config_read(struct sja1105_private *priv, * So don't error out in that case. */ if (!cmd.valident && blk_idx != BLK_IDX_MGMT_ROUTE) - return -EINVAL; + return -ENOENT; cpu_relax(); } while (cmd.valid && --retries); @@ -448,6 +722,8 @@ int sja1105_dynamic_config_write(struct sja1105_private *priv, if (index >= ops->max_entry_count) return -ERANGE; + if (index < 0) + return -ERANGE; if (!(ops->access & OP_WRITE)) return -EOPNOTSUPP; if (!keep && !(ops->access & OP_DEL)) @@ -510,7 +786,7 @@ static u8 sja1105_crc8_add(u8 crc, u8 byte, u8 poly) * is also received as argument in the Koopman notation that the switch * hardware stores it in. */ -u8 sja1105_fdb_hash(struct sja1105_private *priv, const u8 *addr, u16 vid) +u8 sja1105et_fdb_hash(struct sja1105_private *priv, const u8 *addr, u16 vid) { struct sja1105_l2_lookup_params_entry *l2_lookup_params = priv->static_config.tables[BLK_IDX_L2_LOOKUP_PARAMS].entries; diff --git a/drivers/net/dsa/sja1105/sja1105_dynamic_config.h b/drivers/net/dsa/sja1105/sja1105_dynamic_config.h index 77be59546a55..740dadf43f01 100644 --- a/drivers/net/dsa/sja1105/sja1105_dynamic_config.h +++ b/drivers/net/dsa/sja1105/sja1105_dynamic_config.h @@ -7,13 +7,10 @@ #include "sja1105.h" #include <linux/packing.h> -struct sja1105_dyn_cmd { - u64 valid; - u64 rdwrset; - u64 errors; - u64 valident; - u64 index; -}; +/* Special index that can be used for sja1105_dynamic_config_read */ +#define SJA1105_SEARCH -1 + +struct sja1105_dyn_cmd; struct sja1105_dynamic_table_ops { /* This returns size_t just to keep same prototype as the diff --git a/drivers/net/dsa/sja1105/sja1105_main.c b/drivers/net/dsa/sja1105/sja1105_main.c index 1c3959efebc4..32bf3a7cc3b6 100644 --- a/drivers/net/dsa/sja1105/sja1105_main.c +++ b/drivers/net/dsa/sja1105/sja1105_main.c @@ -70,8 +70,7 @@ static int sja1105_init_mac_settings(struct sja1105_private *priv) /* Keep standard IFG of 12 bytes on egress. */ .ifg = 0, /* Always put the MAC speed in automatic mode, where it can be - * retrieved from the PHY object through phylib and - * sja1105_adjust_port_config. + * adjusted at runtime by PHYLINK. */ .speed = SJA1105_SPEED_AUTO, /* No static correction for 1-step 1588 events */ @@ -81,7 +80,7 @@ static int sja1105_init_mac_settings(struct sja1105_private *priv) .maxage = 0xFF, /* Internal VLAN (pvid) to apply to untagged ingress */ .vlanprio = 0, - .vlanid = 0, + .vlanid = 1, .ing_mirr = false, .egr_mirr = false, /* Don't drop traffic with other EtherType than ETH_P_IP */ @@ -116,7 +115,6 @@ static int sja1105_init_mac_settings(struct sja1105_private *priv) if (!table->entries) return -ENOMEM; - /* Override table based on phylib DT bindings */ table->entry_count = SJA1105_NUM_PORTS; mac = table->entries; @@ -157,7 +155,7 @@ static int sja1105_init_mii_settings(struct sja1105_private *priv, if (!table->entries) return -ENOMEM; - /* Override table based on phylib DT bindings */ + /* Override table based on PHYLINK DT bindings */ table->entry_count = SJA1105_MAX_XMII_PARAMS_COUNT; mii = table->entries; @@ -205,11 +203,16 @@ static int sja1105_init_static_fdb(struct sja1105_private *priv) static int sja1105_init_l2_lookup_params(struct sja1105_private *priv) { struct sja1105_table *table; + u64 max_fdb_entries = SJA1105_MAX_L2_LOOKUP_COUNT / SJA1105_NUM_PORTS; struct sja1105_l2_lookup_params_entry default_l2_lookup_params = { /* Learned FDB entries are forgotten after 300 seconds */ .maxage = SJA1105_AGEING_TIME_MS(300000), /* All entries within a FDB bin are available for learning */ .dyn_tbsz = SJA1105ET_FDB_BIN_SIZE, + /* And the P/Q/R/S equivalent setting: */ + .start_dynspc = 0, + .maxaddrp = {max_fdb_entries, max_fdb_entries, max_fdb_entries, + max_fdb_entries, max_fdb_entries, }, /* 2^8 + 2^5 + 2^3 + 2^2 + 2^1 + 1 in Koopman notation */ .poly = 0x97, /* This selects between Independent VLAN Learning (IVL) and @@ -225,6 +228,13 @@ static int sja1105_init_l2_lookup_params(struct sja1105_private *priv) * Maybe correlate with no_linklocal_learn from bridge driver? */ .no_mgmt_learn = true, + /* P/Q/R/S only */ + .use_static = true, + /* Dynamically learned FDB entries can overwrite other (older) + * dynamic FDB entries + */ + .owr_dyn = true, + .drpnolearn = true, }; table = &priv->static_config.tables[BLK_IDX_L2_LOOKUP_PARAMS]; @@ -257,20 +267,15 @@ static int sja1105_init_static_vlan(struct sja1105_private *priv) .vmemb_port = 0, .vlan_bc = 0, .tag_port = 0, - .vlanid = 0, + .vlanid = 1, }; int i; table = &priv->static_config.tables[BLK_IDX_VLAN_LOOKUP]; - /* The static VLAN table will only contain the initial pvid of 0. + /* The static VLAN table will only contain the initial pvid of 1. * All other VLANs are to be configured through dynamic entries, * and kept in the static configuration table as backing memory. - * The pvid of 0 is sufficient to pass traffic while the ports are - * standalone and when vlan_filtering is disabled. When filtering - * gets enabled, the switchdev core sets up the VLAN ID 1 and sets - * it as the new pvid. Actually 'pvid 1' still comes up in 'bridge - * vlan' even when vlan_filtering is off, but it has no effect. */ if (table->entry_count) { kfree(table->entries); @@ -284,7 +289,7 @@ static int sja1105_init_static_vlan(struct sja1105_private *priv) table->entry_count = 1; - /* VLAN ID 0: all DT-defined ports are members; no restrictions on + /* VLAN 1: all DT-defined ports are members; no restrictions on * forwarding; always transmit priority-tagged frames as untagged. */ for (i = 0; i < SJA1105_NUM_PORTS; i++) { @@ -380,14 +385,14 @@ static int sja1105_init_general_params(struct sja1105_private *priv) .mirr_ptacu = 0, .switchid = priv->ds->index, /* Priority queue for link-local frames trapped to CPU */ - .hostprio = 0, + .hostprio = 7, .mac_fltres1 = SJA1105_LINKLOCAL_FILTER_A, .mac_flt1 = SJA1105_LINKLOCAL_FILTER_A_MASK, - .incl_srcpt1 = true, + .incl_srcpt1 = false, .send_meta1 = false, .mac_fltres0 = SJA1105_LINKLOCAL_FILTER_B, .mac_flt0 = SJA1105_LINKLOCAL_FILTER_B_MASK, - .incl_srcpt0 = true, + .incl_srcpt0 = false, .send_meta0 = false, /* The destination for traffic matching mac_fltres1 and * mac_fltres0 on all ports except host_port. Such traffic @@ -499,6 +504,39 @@ static int sja1105_init_l2_policing(struct sja1105_private *priv) return 0; } +static int sja1105_init_avb_params(struct sja1105_private *priv, + bool on) +{ + struct sja1105_avb_params_entry *avb; + struct sja1105_table *table; + + table = &priv->static_config.tables[BLK_IDX_AVB_PARAMS]; + + /* Discard previous AVB Parameters Table */ + if (table->entry_count) { + kfree(table->entries); + table->entry_count = 0; + } + + /* Configure the reception of meta frames only if requested */ + if (!on) + return 0; + + table->entries = kcalloc(SJA1105_MAX_AVB_PARAMS_COUNT, + table->ops->unpacked_entry_size, GFP_KERNEL); + if (!table->entries) + return -ENOMEM; + + table->entry_count = SJA1105_MAX_AVB_PARAMS_COUNT; + + avb = table->entries; + + avb->destmeta = SJA1105_META_DMAC; + avb->srcmeta = SJA1105_META_SMAC; + + return 0; +} + static int sja1105_static_config_load(struct sja1105_private *priv, struct sja1105_dt_port *ports) { @@ -539,6 +577,9 @@ static int sja1105_static_config_load(struct sja1105_private *priv, rc = sja1105_init_general_params(priv); if (rc < 0) return rc; + rc = sja1105_init_avb_params(priv, false); + if (rc < 0) + return rc; /* Send initial configuration to hardware via SPI */ return sja1105_static_config_upload(priv); @@ -644,26 +685,18 @@ static int sja1105_parse_dt(struct sja1105_private *priv, return rc; } -/* Convert back and forth MAC speed from Mbps to SJA1105 encoding */ +/* Convert link speed from SJA1105 to ethtool encoding */ static int sja1105_speed[] = { - [SJA1105_SPEED_AUTO] = 0, - [SJA1105_SPEED_10MBPS] = 10, - [SJA1105_SPEED_100MBPS] = 100, - [SJA1105_SPEED_1000MBPS] = 1000, + [SJA1105_SPEED_AUTO] = SPEED_UNKNOWN, + [SJA1105_SPEED_10MBPS] = SPEED_10, + [SJA1105_SPEED_100MBPS] = SPEED_100, + [SJA1105_SPEED_1000MBPS] = SPEED_1000, }; -/* Set link speed and enable/disable traffic I/O in the MAC configuration - * for a specific port. - * - * @speed_mbps: If 0, leave the speed unchanged, else adapt MAC to PHY speed. - * @enabled: Manage Rx and Tx settings for this port. If false, overrides the - * settings from the STP state, but not persistently (does not - * overwrite the static MAC info for this port). - */ +/* Set link speed in the MAC configuration for a specific port. */ static int sja1105_adjust_port_config(struct sja1105_private *priv, int port, - int speed_mbps, bool enabled) + int speed_mbps) { - struct sja1105_mac_config_entry dyn_mac; struct sja1105_xmii_params_entry *mii; struct sja1105_mac_config_entry *mac; struct device *dev = priv->ds->dev; @@ -671,21 +704,33 @@ static int sja1105_adjust_port_config(struct sja1105_private *priv, int port, sja1105_speed_t speed; int rc; - mii = priv->static_config.tables[BLK_IDX_XMII_PARAMS].entries; + /* On P/Q/R/S, one can read from the device via the MAC reconfiguration + * tables. On E/T, MAC reconfig tables are not readable, only writable. + * We have to *know* what the MAC looks like. For the sake of keeping + * the code common, we'll use the static configuration tables as a + * reasonable approximation for both E/T and P/Q/R/S. + */ mac = priv->static_config.tables[BLK_IDX_MAC_CONFIG].entries; + mii = priv->static_config.tables[BLK_IDX_XMII_PARAMS].entries; switch (speed_mbps) { - case 0: - /* No speed update requested */ + case SPEED_UNKNOWN: + /* PHYLINK called sja1105_mac_config() to inform us about + * the state->interface, but AN has not completed and the + * speed is not yet valid. UM10944.pdf says that setting + * SJA1105_SPEED_AUTO at runtime disables the port, so that is + * ok for power consumption in case AN will never complete - + * otherwise PHYLINK should come back with a new update. + */ speed = SJA1105_SPEED_AUTO; break; - case 10: + case SPEED_10: speed = SJA1105_SPEED_10MBPS; break; - case 100: + case SPEED_100: speed = SJA1105_SPEED_100MBPS; break; - case 1000: + case SPEED_1000: speed = SJA1105_SPEED_1000MBPS; break; default: @@ -693,26 +738,16 @@ static int sja1105_adjust_port_config(struct sja1105_private *priv, int port, return -EINVAL; } - /* If requested, overwrite SJA1105_SPEED_AUTO from the static MAC - * configuration table, since this will be used for the clocking setup, - * and we no longer need to store it in the static config (already told - * hardware we want auto during upload phase). + /* Overwrite SJA1105_SPEED_AUTO from the static MAC configuration + * table, since this will be used for the clocking setup, and we no + * longer need to store it in the static config (already told hardware + * we want auto during upload phase). */ mac[port].speed = speed; - /* On P/Q/R/S, one can read from the device via the MAC reconfiguration - * tables. On E/T, MAC reconfig tables are not readable, only writable. - * We have to *know* what the MAC looks like. For the sake of keeping - * the code common, we'll use the static configuration tables as a - * reasonable approximation for both E/T and P/Q/R/S. - */ - dyn_mac = mac[port]; - dyn_mac.ingress = enabled && mac[port].ingress; - dyn_mac.egress = enabled && mac[port].egress; - /* Write to the dynamic reconfiguration tables */ - rc = sja1105_dynamic_config_write(priv, BLK_IDX_MAC_CONFIG, - port, &dyn_mac, true); + rc = sja1105_dynamic_config_write(priv, BLK_IDX_MAC_CONFIG, port, + &mac[port], true); if (rc < 0) { dev_err(dev, "Failed to write MAC config: %d\n", rc); return rc; @@ -724,9 +759,6 @@ static int sja1105_adjust_port_config(struct sja1105_private *priv, int port, * the clock setup does interrupt the clock signal for a certain time * which causes trouble for all PHYs relying on this signal. */ - if (!enabled) - return 0; - phy_mode = mii->xmii_mode[port]; if (phy_mode != XMII_MODE_RGMII) return 0; @@ -734,15 +766,67 @@ static int sja1105_adjust_port_config(struct sja1105_private *priv, int port, return sja1105_clocking_setup_port(priv, port); } -static void sja1105_adjust_link(struct dsa_switch *ds, int port, - struct phy_device *phydev) +/* The SJA1105 MAC programming model is through the static config (the xMII + * Mode table cannot be dynamically reconfigured), and we have to program + * that early (earlier than PHYLINK calls us, anyway). + * So just error out in case the connected PHY attempts to change the initial + * system interface MII protocol from what is defined in the DT, at least for + * now. + */ +static bool sja1105_phy_mode_mismatch(struct sja1105_private *priv, int port, + phy_interface_t interface) +{ + struct sja1105_xmii_params_entry *mii; + sja1105_phy_interface_t phy_mode; + + mii = priv->static_config.tables[BLK_IDX_XMII_PARAMS].entries; + phy_mode = mii->xmii_mode[port]; + + switch (interface) { + case PHY_INTERFACE_MODE_MII: + return (phy_mode != XMII_MODE_MII); + case PHY_INTERFACE_MODE_RMII: + return (phy_mode != XMII_MODE_RMII); + case PHY_INTERFACE_MODE_RGMII: + case PHY_INTERFACE_MODE_RGMII_ID: + case PHY_INTERFACE_MODE_RGMII_RXID: + case PHY_INTERFACE_MODE_RGMII_TXID: + return (phy_mode != XMII_MODE_RGMII); + default: + return true; + } +} + +static void sja1105_mac_config(struct dsa_switch *ds, int port, + unsigned int link_an_mode, + const struct phylink_link_state *state) { struct sja1105_private *priv = ds->priv; - if (!phydev->link) - sja1105_adjust_port_config(priv, port, 0, false); - else - sja1105_adjust_port_config(priv, port, phydev->speed, true); + if (sja1105_phy_mode_mismatch(priv, port, state->interface)) + return; + + if (link_an_mode == MLO_AN_INBAND) { + dev_err(ds->dev, "In-band AN not supported!\n"); + return; + } + + sja1105_adjust_port_config(priv, port, state->speed); +} + +static void sja1105_mac_link_down(struct dsa_switch *ds, int port, + unsigned int mode, + phy_interface_t interface) +{ + sja1105_inhibit_tx(ds->priv, BIT(port), true); +} + +static void sja1105_mac_link_up(struct dsa_switch *ds, int port, + unsigned int mode, + phy_interface_t interface, + struct phy_device *phydev) +{ + sja1105_inhibit_tx(ds->priv, BIT(port), false); } static void sja1105_phylink_validate(struct dsa_switch *ds, int port, @@ -759,6 +843,16 @@ static void sja1105_phylink_validate(struct dsa_switch *ds, int port, mii = priv->static_config.tables[BLK_IDX_XMII_PARAMS].entries; + /* include/linux/phylink.h says: + * When @state->interface is %PHY_INTERFACE_MODE_NA, phylink + * expects the MAC driver to return all supported link modes. + */ + if (state->interface != PHY_INTERFACE_MODE_NA && + sja1105_phy_mode_mismatch(priv, port, state->interface)) { + bitmap_zero(supported, __ETHTOOL_LINK_MODE_MASK_NBITS); + return; + } + /* The MAC does not support pause frames, and also doesn't * support half-duplex traffic modes. */ @@ -774,6 +868,77 @@ static void sja1105_phylink_validate(struct dsa_switch *ds, int port, __ETHTOOL_LINK_MODE_MASK_NBITS); } +static int +sja1105_find_static_fdb_entry(struct sja1105_private *priv, int port, + const struct sja1105_l2_lookup_entry *requested) +{ + struct sja1105_l2_lookup_entry *l2_lookup; + struct sja1105_table *table; + int i; + + table = &priv->static_config.tables[BLK_IDX_L2_LOOKUP]; + l2_lookup = table->entries; + + for (i = 0; i < table->entry_count; i++) + if (l2_lookup[i].macaddr == requested->macaddr && + l2_lookup[i].vlanid == requested->vlanid && + l2_lookup[i].destports & BIT(port)) + return i; + + return -1; +} + +/* We want FDB entries added statically through the bridge command to persist + * across switch resets, which are a common thing during normal SJA1105 + * operation. So we have to back them up in the static configuration tables + * and hence apply them on next static config upload... yay! + */ +static int +sja1105_static_fdb_change(struct sja1105_private *priv, int port, + const struct sja1105_l2_lookup_entry *requested, + bool keep) +{ + struct sja1105_l2_lookup_entry *l2_lookup; + struct sja1105_table *table; + int rc, match; + + table = &priv->static_config.tables[BLK_IDX_L2_LOOKUP]; + + match = sja1105_find_static_fdb_entry(priv, port, requested); + if (match < 0) { + /* Can't delete a missing entry. */ + if (!keep) + return 0; + + /* No match => new entry */ + rc = sja1105_table_resize(table, table->entry_count + 1); + if (rc) + return rc; + + match = table->entry_count - 1; + } + + /* Assign pointer after the resize (it may be new memory) */ + l2_lookup = table->entries; + + /* We have a match. + * If the job was to add this FDB entry, it's already done (mostly + * anyway, since the port forwarding mask may have changed, case in + * which we update it). + * Otherwise we have to delete it. + */ + if (keep) { + l2_lookup[match] = *requested; + return 0; + } + + /* To remove, the strategy is to overwrite the element with + * the last one, and then reduce the array size by 1 + */ + l2_lookup[match] = l2_lookup[table->entry_count - 1]; + return sja1105_table_resize(table, table->entry_count - 1); +} + /* First-generation switches have a 4-way set associative TCAM that * holds the FDB entries. An FDB index spans from 0 to 1023 and is comprised of * a "bin" (grouping of 4 entries) and a "way" (an entry within a bin). @@ -785,10 +950,10 @@ static inline int sja1105et_fdb_index(int bin, int way) return bin * SJA1105ET_FDB_BIN_SIZE + way; } -static int sja1105_is_fdb_entry_in_bin(struct sja1105_private *priv, int bin, - const u8 *addr, u16 vid, - struct sja1105_l2_lookup_entry *match, - int *last_unused) +static int sja1105et_is_fdb_entry_in_bin(struct sja1105_private *priv, int bin, + const u8 *addr, u16 vid, + struct sja1105_l2_lookup_entry *match, + int *last_unused) { int way; @@ -817,19 +982,19 @@ static int sja1105_is_fdb_entry_in_bin(struct sja1105_private *priv, int bin, return -1; } -static int sja1105_fdb_add(struct dsa_switch *ds, int port, - const unsigned char *addr, u16 vid) +int sja1105et_fdb_add(struct dsa_switch *ds, int port, + const unsigned char *addr, u16 vid) { struct sja1105_l2_lookup_entry l2_lookup = {0}; struct sja1105_private *priv = ds->priv; struct device *dev = ds->dev; int last_unused = -1; - int bin, way; + int bin, way, rc; - bin = sja1105_fdb_hash(priv, addr, vid); + bin = sja1105et_fdb_hash(priv, addr, vid); - way = sja1105_is_fdb_entry_in_bin(priv, bin, addr, vid, - &l2_lookup, &last_unused); + way = sja1105et_is_fdb_entry_in_bin(priv, bin, addr, vid, + &l2_lookup, &last_unused); if (way >= 0) { /* We have an FDB entry. Is our port in the destination * mask? If yes, we need to do nothing. If not, we need @@ -868,22 +1033,26 @@ static int sja1105_fdb_add(struct dsa_switch *ds, int port, } l2_lookup.index = sja1105et_fdb_index(bin, way); - return sja1105_dynamic_config_write(priv, BLK_IDX_L2_LOOKUP, - l2_lookup.index, &l2_lookup, - true); + rc = sja1105_dynamic_config_write(priv, BLK_IDX_L2_LOOKUP, + l2_lookup.index, &l2_lookup, + true); + if (rc < 0) + return rc; + + return sja1105_static_fdb_change(priv, port, &l2_lookup, true); } -static int sja1105_fdb_del(struct dsa_switch *ds, int port, - const unsigned char *addr, u16 vid) +int sja1105et_fdb_del(struct dsa_switch *ds, int port, + const unsigned char *addr, u16 vid) { struct sja1105_l2_lookup_entry l2_lookup = {0}; struct sja1105_private *priv = ds->priv; - int index, bin, way; + int index, bin, way, rc; bool keep; - bin = sja1105_fdb_hash(priv, addr, vid); - way = sja1105_is_fdb_entry_in_bin(priv, bin, addr, vid, - &l2_lookup, NULL); + bin = sja1105et_fdb_hash(priv, addr, vid); + way = sja1105et_is_fdb_entry_in_bin(priv, bin, addr, vid, + &l2_lookup, NULL); if (way < 0) return 0; index = sja1105et_fdb_index(bin, way); @@ -893,15 +1062,176 @@ static int sja1105_fdb_del(struct dsa_switch *ds, int port, * need to completely evict the FDB entry. * Otherwise we just write it back. */ - if (l2_lookup.destports & BIT(port)) - l2_lookup.destports &= ~BIT(port); + l2_lookup.destports &= ~BIT(port); + + if (l2_lookup.destports) + keep = true; + else + keep = false; + + rc = sja1105_dynamic_config_write(priv, BLK_IDX_L2_LOOKUP, + index, &l2_lookup, keep); + if (rc < 0) + return rc; + + return sja1105_static_fdb_change(priv, port, &l2_lookup, keep); +} + +int sja1105pqrs_fdb_add(struct dsa_switch *ds, int port, + const unsigned char *addr, u16 vid) +{ + struct sja1105_l2_lookup_entry l2_lookup = {0}; + struct sja1105_private *priv = ds->priv; + int rc, i; + + /* Search for an existing entry in the FDB table */ + l2_lookup.macaddr = ether_addr_to_u64(addr); + l2_lookup.vlanid = vid; + l2_lookup.iotag = SJA1105_S_TAG; + l2_lookup.mask_macaddr = GENMASK_ULL(ETH_ALEN * 8 - 1, 0); + l2_lookup.mask_vlanid = VLAN_VID_MASK; + l2_lookup.mask_iotag = BIT(0); + l2_lookup.destports = BIT(port); + + rc = sja1105_dynamic_config_read(priv, BLK_IDX_L2_LOOKUP, + SJA1105_SEARCH, &l2_lookup); + if (rc == 0) { + /* Found and this port is already in the entry's + * port mask => job done + */ + if (l2_lookup.destports & BIT(port)) + return 0; + /* l2_lookup.index is populated by the switch in case it + * found something. + */ + l2_lookup.destports |= BIT(port); + goto skip_finding_an_index; + } + + /* Not found, so try to find an unused spot in the FDB. + * This is slightly inefficient because the strategy is knock-knock at + * every possible position from 0 to 1023. + */ + for (i = 0; i < SJA1105_MAX_L2_LOOKUP_COUNT; i++) { + rc = sja1105_dynamic_config_read(priv, BLK_IDX_L2_LOOKUP, + i, NULL); + if (rc < 0) + break; + } + if (i == SJA1105_MAX_L2_LOOKUP_COUNT) { + dev_err(ds->dev, "FDB is full, cannot add entry.\n"); + return -EINVAL; + } + l2_lookup.lockeds = true; + l2_lookup.index = i; + +skip_finding_an_index: + rc = sja1105_dynamic_config_write(priv, BLK_IDX_L2_LOOKUP, + l2_lookup.index, &l2_lookup, + true); + if (rc < 0) + return rc; + + return sja1105_static_fdb_change(priv, port, &l2_lookup, true); +} + +int sja1105pqrs_fdb_del(struct dsa_switch *ds, int port, + const unsigned char *addr, u16 vid) +{ + struct sja1105_l2_lookup_entry l2_lookup = {0}; + struct sja1105_private *priv = ds->priv; + bool keep; + int rc; + + l2_lookup.macaddr = ether_addr_to_u64(addr); + l2_lookup.vlanid = vid; + l2_lookup.iotag = SJA1105_S_TAG; + l2_lookup.mask_macaddr = GENMASK_ULL(ETH_ALEN * 8 - 1, 0); + l2_lookup.mask_vlanid = VLAN_VID_MASK; + l2_lookup.mask_iotag = BIT(0); + l2_lookup.destports = BIT(port); + + rc = sja1105_dynamic_config_read(priv, BLK_IDX_L2_LOOKUP, + SJA1105_SEARCH, &l2_lookup); + if (rc < 0) + return 0; + + l2_lookup.destports &= ~BIT(port); + + /* Decide whether we remove just this port from the FDB entry, + * or if we remove it completely. + */ if (l2_lookup.destports) keep = true; else keep = false; - return sja1105_dynamic_config_write(priv, BLK_IDX_L2_LOOKUP, - index, &l2_lookup, keep); + rc = sja1105_dynamic_config_write(priv, BLK_IDX_L2_LOOKUP, + l2_lookup.index, &l2_lookup, keep); + if (rc < 0) + return rc; + + return sja1105_static_fdb_change(priv, port, &l2_lookup, keep); +} + +static int sja1105_fdb_add(struct dsa_switch *ds, int port, + const unsigned char *addr, u16 vid) +{ + struct sja1105_private *priv = ds->priv; + u16 rx_vid, tx_vid; + int rc, i; + + if (dsa_port_is_vlan_filtering(&ds->ports[port])) + return priv->info->fdb_add_cmd(ds, port, addr, vid); + + /* Since we make use of VLANs even when the bridge core doesn't tell us + * to, translate these FDB entries into the correct dsa_8021q ones. + * The basic idea (also repeats for removal below) is: + * - Each of the other front-panel ports needs to be able to forward a + * pvid-tagged (aka tagged with their rx_vid) frame that matches this + * DMAC. + * - The CPU port (aka the tx_vid of this port) needs to be able to + * send a frame matching this DMAC to the specified port. + * For a better picture see net/dsa/tag_8021q.c. + */ + for (i = 0; i < SJA1105_NUM_PORTS; i++) { + if (i == port) + continue; + if (i == dsa_upstream_port(priv->ds, port)) + continue; + + rx_vid = dsa_8021q_rx_vid(ds, i); + rc = priv->info->fdb_add_cmd(ds, port, addr, rx_vid); + if (rc < 0) + return rc; + } + tx_vid = dsa_8021q_tx_vid(ds, port); + return priv->info->fdb_add_cmd(ds, port, addr, tx_vid); +} + +static int sja1105_fdb_del(struct dsa_switch *ds, int port, + const unsigned char *addr, u16 vid) +{ + struct sja1105_private *priv = ds->priv; + u16 rx_vid, tx_vid; + int rc, i; + + if (dsa_port_is_vlan_filtering(&ds->ports[port])) + return priv->info->fdb_del_cmd(ds, port, addr, vid); + + for (i = 0; i < SJA1105_NUM_PORTS; i++) { + if (i == port) + continue; + if (i == dsa_upstream_port(priv->ds, port)) + continue; + + rx_vid = dsa_8021q_rx_vid(ds, i); + rc = priv->info->fdb_del_cmd(ds, port, addr, rx_vid); + if (rc < 0) + return rc; + } + tx_vid = dsa_8021q_tx_vid(ds, port); + return priv->info->fdb_del_cmd(ds, port, addr, tx_vid); } static int sja1105_fdb_dump(struct dsa_switch *ds, int port, @@ -909,8 +1239,12 @@ static int sja1105_fdb_dump(struct dsa_switch *ds, int port, { struct sja1105_private *priv = ds->priv; struct device *dev = ds->dev; + u16 rx_vid, tx_vid; int i; + rx_vid = dsa_8021q_rx_vid(ds, port); + tx_vid = dsa_8021q_tx_vid(ds, port); + for (i = 0; i < SJA1105_MAX_L2_LOOKUP_COUNT; i++) { struct sja1105_l2_lookup_entry l2_lookup = {0}; u8 macaddr[ETH_ALEN]; @@ -919,7 +1253,7 @@ static int sja1105_fdb_dump(struct dsa_switch *ds, int port, rc = sja1105_dynamic_config_read(priv, BLK_IDX_L2_LOOKUP, i, &l2_lookup); /* No fdb entry at i, not an issue */ - if (rc == -EINVAL) + if (rc == -ENOENT) continue; if (rc) { dev_err(dev, "Failed to dump FDB: %d\n", rc); @@ -935,7 +1269,41 @@ static int sja1105_fdb_dump(struct dsa_switch *ds, int port, if (!(l2_lookup.destports & BIT(port))) continue; u64_to_ether_addr(l2_lookup.macaddr, macaddr); - cb(macaddr, l2_lookup.vlanid, false, data); + + /* On SJA1105 E/T, the switch doesn't implement the LOCKEDS + * bit, so it doesn't tell us whether a FDB entry is static + * or not. + * But, of course, we can find out - we're the ones who added + * it in the first place. + */ + if (priv->info->device_id == SJA1105E_DEVICE_ID || + priv->info->device_id == SJA1105T_DEVICE_ID) { + int match; + + match = sja1105_find_static_fdb_entry(priv, port, + &l2_lookup); + l2_lookup.lockeds = (match >= 0); + } + + /* We need to hide the dsa_8021q VLANs from the user. This + * basically means hiding the duplicates and only showing + * the pvid that is supposed to be active in standalone and + * non-vlan_filtering modes (aka 1). + * - For statically added FDB entries (bridge fdb add), we + * can convert the TX VID (coming from the CPU port) into the + * pvid and ignore the RX VIDs of the other ports. + * - For dynamically learned FDB entries, a single entry with + * no duplicates is learned - that which has the real port's + * pvid, aka RX VID. + */ + if (!dsa_port_is_vlan_filtering(&ds->ports[port])) { + if (l2_lookup.vlanid == tx_vid || + l2_lookup.vlanid == rx_vid) + l2_lookup.vlanid = 1; + else + continue; + } + cb(macaddr, l2_lookup.vlanid, l2_lookup.lockeds, data); } return 0; } @@ -1056,27 +1424,6 @@ static void sja1105_bridge_leave(struct dsa_switch *ds, int port, sja1105_bridge_member(ds, port, br, false); } -static u8 sja1105_stp_state_get(struct sja1105_private *priv, int port) -{ - struct sja1105_mac_config_entry *mac; - - mac = priv->static_config.tables[BLK_IDX_MAC_CONFIG].entries; - - if (!mac[port].ingress && !mac[port].egress && !mac[port].dyn_learn) - return BR_STATE_BLOCKING; - if (mac[port].ingress && !mac[port].egress && !mac[port].dyn_learn) - return BR_STATE_LISTENING; - if (mac[port].ingress && !mac[port].egress && mac[port].dyn_learn) - return BR_STATE_LEARNING; - if (mac[port].ingress && mac[port].egress && mac[port].dyn_learn) - return BR_STATE_FORWARDING; - /* This is really an error condition if the MAC was in none of the STP - * states above. But treating the port as disabled does nothing, which - * is adequate, and it also resets the MAC to a known state later on. - */ - return BR_STATE_DISABLED; -} - /* For situations where we need to change a setting at runtime that is only * available through the static configuration, resetting the switch in order * to upload the new static config is unavoidable. Back up the settings we @@ -1087,27 +1434,18 @@ static int sja1105_static_config_reload(struct sja1105_private *priv) { struct sja1105_mac_config_entry *mac; int speed_mbps[SJA1105_NUM_PORTS]; - u8 stp_state[SJA1105_NUM_PORTS]; int rc, i; mac = priv->static_config.tables[BLK_IDX_MAC_CONFIG].entries; - /* Back up settings changed by sja1105_adjust_port_config and - * sja1105_bridge_stp_state_set and restore their defaults. + /* Back up the dynamic link speed changed by sja1105_adjust_port_config + * in order to temporarily restore it to SJA1105_SPEED_AUTO - which the + * switch wants to see in the static config in order to allow us to + * change it through the dynamic interface later. */ for (i = 0; i < SJA1105_NUM_PORTS; i++) { speed_mbps[i] = sja1105_speed[mac[i].speed]; mac[i].speed = SJA1105_SPEED_AUTO; - if (i == dsa_upstream_port(priv->ds, i)) { - mac[i].ingress = true; - mac[i].egress = true; - mac[i].dyn_learn = true; - } else { - stp_state[i] = sja1105_stp_state_get(priv, i); - mac[i].ingress = false; - mac[i].egress = false; - mac[i].dyn_learn = false; - } } /* Reset switch and send updated static configuration */ @@ -1124,13 +1462,7 @@ static int sja1105_static_config_reload(struct sja1105_private *priv) goto out; for (i = 0; i < SJA1105_NUM_PORTS; i++) { - bool enabled = (speed_mbps[i] != 0); - - if (i != dsa_upstream_port(priv->ds, i)) - sja1105_bridge_stp_state_set(priv->ds, i, stp_state[i]); - - rc = sja1105_adjust_port_config(priv, i, speed_mbps[i], - enabled); + rc = sja1105_adjust_port_config(priv, i, speed_mbps[i]); if (rc < 0) goto out; } @@ -1138,23 +1470,6 @@ out: return rc; } -/* The TPID setting belongs to the General Parameters table, - * which can only be partially reconfigured at runtime (and not the TPID). - * So a switch reset is required. - */ -static int sja1105_change_tpid(struct sja1105_private *priv, - u16 tpid, u16 tpid2) -{ - struct sja1105_general_params_entry *general_params; - struct sja1105_table *table; - - table = &priv->static_config.tables[BLK_IDX_GENERAL_PARAMS]; - general_params = table->entries; - general_params->tpid = tpid; - general_params->tpid2 = tpid2; - return sja1105_static_config_reload(priv); -} - static int sja1105_pvid_apply(struct sja1105_private *priv, int port, u16 pvid) { struct sja1105_mac_config_entry *mac; @@ -1273,17 +1588,41 @@ static int sja1105_vlan_prepare(struct dsa_switch *ds, int port, return 0; } +/* The TPID setting belongs to the General Parameters table, + * which can only be partially reconfigured at runtime (and not the TPID). + * So a switch reset is required. + */ static int sja1105_vlan_filtering(struct dsa_switch *ds, int port, bool enabled) { + struct sja1105_general_params_entry *general_params; struct sja1105_private *priv = ds->priv; + struct sja1105_table *table; + u16 tpid, tpid2; int rc; - if (enabled) + if (enabled) { /* Enable VLAN filtering. */ - rc = sja1105_change_tpid(priv, ETH_P_8021Q, ETH_P_8021AD); - else + tpid = ETH_P_8021AD; + tpid2 = ETH_P_8021Q; + } else { /* Disable VLAN filtering. */ - rc = sja1105_change_tpid(priv, ETH_P_SJA1105, ETH_P_SJA1105); + tpid = ETH_P_SJA1105; + tpid2 = ETH_P_SJA1105; + } + + table = &priv->static_config.tables[BLK_IDX_GENERAL_PARAMS]; + general_params = table->entries; + /* EtherType used to identify outer tagged (S-tag) VLAN traffic */ + general_params->tpid = tpid; + /* EtherType used to identify inner tagged (C-tag) VLAN traffic */ + general_params->tpid2 = tpid2; + /* When VLAN filtering is on, we need to at least be able to + * decode management traffic through the "backup plan". + */ + general_params->incl_srcpt1 = enabled; + general_params->incl_srcpt0 = enabled; + + rc = sja1105_static_config_reload(priv); if (rc) dev_err(ds->dev, "Failed to change VLAN Ethertype\n"); @@ -1372,6 +1711,11 @@ static int sja1105_setup(struct dsa_switch *ds) return rc; } + rc = sja1105_ptp_clock_register(priv); + if (rc < 0) { + dev_err(ds->dev, "Failed to register PTP clock: %d\n", rc); + return rc; + } /* Create and send configuration down to device */ rc = sja1105_static_config_load(priv, ports); if (rc < 0) { @@ -1401,8 +1745,16 @@ static int sja1105_setup(struct dsa_switch *ds) return sja1105_setup_8021q_tagging(ds, true); } +static void sja1105_teardown(struct dsa_switch *ds) +{ + struct sja1105_private *priv = ds->priv; + + cancel_work_sync(&priv->tagger_data.rxtstamp_work); + skb_queue_purge(&priv->tagger_data.skb_rxtstamp_queue); +} + static int sja1105_mgmt_xmit(struct dsa_switch *ds, int port, int slot, - struct sk_buff *skb) + struct sk_buff *skb, bool takets) { struct sja1105_mgmt_entry mgmt_route = {0}; struct sja1105_private *priv = ds->priv; @@ -1415,6 +1767,8 @@ static int sja1105_mgmt_xmit(struct dsa_switch *ds, int port, int slot, mgmt_route.macaddr = ether_addr_to_u64(hdr->h_dest); mgmt_route.destports = BIT(port); mgmt_route.enfport = 1; + mgmt_route.tsreg = 0; + mgmt_route.takets = takets; rc = sja1105_dynamic_config_write(priv, BLK_IDX_MGMT_ROUTE, slot, &mgmt_route, true); @@ -1446,6 +1800,8 @@ static int sja1105_mgmt_xmit(struct dsa_switch *ds, int port, int slot, if (!timeout) { /* Clean up the management route so that a follow-up * frame may not match on it by mistake. + * This is only hardware supported on P/Q/R/S - on E/T it is + * a no-op and we are silently discarding the -EOPNOTSUPP. */ sja1105_dynamic_config_write(priv, BLK_IDX_MGMT_ROUTE, slot, &mgmt_route, false); @@ -1464,7 +1820,11 @@ static netdev_tx_t sja1105_port_deferred_xmit(struct dsa_switch *ds, int port, { struct sja1105_private *priv = ds->priv; struct sja1105_port *sp = &priv->ports[port]; + struct skb_shared_hwtstamps shwt = {0}; int slot = sp->mgmt_slot; + struct sk_buff *clone; + u64 now, ts; + int rc; /* The tragic fact about the switch having 4x2 slots for installing * management routes is that all of them except one are actually @@ -1482,8 +1842,36 @@ static netdev_tx_t sja1105_port_deferred_xmit(struct dsa_switch *ds, int port, */ mutex_lock(&priv->mgmt_lock); - sja1105_mgmt_xmit(ds, port, slot, skb); + /* The clone, if there, was made by dsa_skb_tx_timestamp */ + clone = DSA_SKB_CB(skb)->clone; + + sja1105_mgmt_xmit(ds, port, slot, skb, !!clone); + + if (!clone) + goto out; + + skb_shinfo(clone)->tx_flags |= SKBTX_IN_PROGRESS; + + mutex_lock(&priv->ptp_lock); + + now = priv->tstamp_cc.read(&priv->tstamp_cc); + + rc = sja1105_ptpegr_ts_poll(priv, slot, &ts); + if (rc < 0) { + dev_err(ds->dev, "xmit: timed out polling for tstamp\n"); + kfree_skb(clone); + goto out_unlock_ptp; + } + + ts = sja1105_tstamp_reconstruct(priv, now, ts); + ts = timecounter_cyc2time(&priv->tstamp_tc, ts); + + shwt.hwtstamp = ns_to_ktime(ts); + skb_complete_tx_timestamp(clone, &shwt); +out_unlock_ptp: + mutex_unlock(&priv->ptp_lock); +out: mutex_unlock(&priv->mgmt_lock); return NETDEV_TX_OK; } @@ -1512,15 +1900,180 @@ static int sja1105_set_ageing_time(struct dsa_switch *ds, return sja1105_static_config_reload(priv); } +/* Caller must hold priv->tagger_data.meta_lock */ +static int sja1105_change_rxtstamping(struct sja1105_private *priv, + bool on) +{ + struct sja1105_general_params_entry *general_params; + struct sja1105_table *table; + int rc; + + table = &priv->static_config.tables[BLK_IDX_GENERAL_PARAMS]; + general_params = table->entries; + general_params->send_meta1 = on; + general_params->send_meta0 = on; + + rc = sja1105_init_avb_params(priv, on); + if (rc < 0) + return rc; + + /* Initialize the meta state machine to a known state */ + if (priv->tagger_data.stampable_skb) { + kfree_skb(priv->tagger_data.stampable_skb); + priv->tagger_data.stampable_skb = NULL; + } + + return sja1105_static_config_reload(priv); +} + +static int sja1105_hwtstamp_set(struct dsa_switch *ds, int port, + struct ifreq *ifr) +{ + struct sja1105_private *priv = ds->priv; + struct hwtstamp_config config; + bool rx_on; + int rc; + + if (copy_from_user(&config, ifr->ifr_data, sizeof(config))) + return -EFAULT; + + switch (config.tx_type) { + case HWTSTAMP_TX_OFF: + priv->ports[port].hwts_tx_en = false; + break; + case HWTSTAMP_TX_ON: + priv->ports[port].hwts_tx_en = true; + break; + default: + return -ERANGE; + } + + switch (config.rx_filter) { + case HWTSTAMP_FILTER_NONE: + rx_on = false; + break; + default: + rx_on = true; + break; + } + + if (rx_on != priv->tagger_data.hwts_rx_en) { + spin_lock(&priv->tagger_data.meta_lock); + rc = sja1105_change_rxtstamping(priv, rx_on); + spin_unlock(&priv->tagger_data.meta_lock); + if (rc < 0) { + dev_err(ds->dev, + "Failed to change RX timestamping: %d\n", rc); + return -EFAULT; + } + priv->tagger_data.hwts_rx_en = rx_on; + } + + if (copy_to_user(ifr->ifr_data, &config, sizeof(config))) + return -EFAULT; + return 0; +} + +static int sja1105_hwtstamp_get(struct dsa_switch *ds, int port, + struct ifreq *ifr) +{ + struct sja1105_private *priv = ds->priv; + struct hwtstamp_config config; + + config.flags = 0; + if (priv->ports[port].hwts_tx_en) + config.tx_type = HWTSTAMP_TX_ON; + else + config.tx_type = HWTSTAMP_TX_OFF; + if (priv->tagger_data.hwts_rx_en) + config.rx_filter = HWTSTAMP_FILTER_PTP_V2_L2_EVENT; + else + config.rx_filter = HWTSTAMP_FILTER_NONE; + + return copy_to_user(ifr->ifr_data, &config, sizeof(config)) ? + -EFAULT : 0; +} + +#define to_tagger(d) \ + container_of((d), struct sja1105_tagger_data, rxtstamp_work) +#define to_sja1105(d) \ + container_of((d), struct sja1105_private, tagger_data) + +static void sja1105_rxtstamp_work(struct work_struct *work) +{ + struct sja1105_tagger_data *data = to_tagger(work); + struct sja1105_private *priv = to_sja1105(data); + struct sk_buff *skb; + u64 now; + + mutex_lock(&priv->ptp_lock); + + now = priv->tstamp_cc.read(&priv->tstamp_cc); + + while ((skb = skb_dequeue(&data->skb_rxtstamp_queue)) != NULL) { + struct skb_shared_hwtstamps *shwt = skb_hwtstamps(skb); + u64 ts; + + *shwt = (struct skb_shared_hwtstamps) {0}; + + ts = SJA1105_SKB_CB(skb)->meta_tstamp; + ts = sja1105_tstamp_reconstruct(priv, now, ts); + ts = timecounter_cyc2time(&priv->tstamp_tc, ts); + + shwt->hwtstamp = ns_to_ktime(ts); + netif_rx_ni(skb); + } + + mutex_unlock(&priv->ptp_lock); +} + +/* Called from dsa_skb_defer_rx_timestamp */ +static bool sja1105_port_rxtstamp(struct dsa_switch *ds, int port, + struct sk_buff *skb, unsigned int type) +{ + struct sja1105_private *priv = ds->priv; + struct sja1105_tagger_data *data = &priv->tagger_data; + + if (!data->hwts_rx_en) + return false; + + /* We need to read the full PTP clock to reconstruct the Rx + * timestamp. For that we need a sleepable context. + */ + skb_queue_tail(&data->skb_rxtstamp_queue, skb); + schedule_work(&data->rxtstamp_work); + return true; +} + +/* Called from dsa_skb_tx_timestamp. This callback is just to make DSA clone + * the skb and have it available in DSA_SKB_CB in the .port_deferred_xmit + * callback, where we will timestamp it synchronously. + */ +static bool sja1105_port_txtstamp(struct dsa_switch *ds, int port, + struct sk_buff *skb, unsigned int type) +{ + struct sja1105_private *priv = ds->priv; + struct sja1105_port *sp = &priv->ports[port]; + + if (!sp->hwts_tx_en) + return false; + + return true; +} + static const struct dsa_switch_ops sja1105_switch_ops = { .get_tag_protocol = sja1105_get_tag_protocol, .setup = sja1105_setup, - .adjust_link = sja1105_adjust_link, + .teardown = sja1105_teardown, .set_ageing_time = sja1105_set_ageing_time, .phylink_validate = sja1105_phylink_validate, + .phylink_mac_config = sja1105_mac_config, + .phylink_mac_link_up = sja1105_mac_link_up, + .phylink_mac_link_down = sja1105_mac_link_down, .get_strings = sja1105_get_strings, .get_ethtool_stats = sja1105_get_ethtool_stats, .get_sset_count = sja1105_get_sset_count, + .get_ts_info = sja1105_get_ts_info, .port_fdb_dump = sja1105_fdb_dump, .port_fdb_add = sja1105_fdb_add, .port_fdb_del = sja1105_fdb_del, @@ -1535,6 +2088,10 @@ static const struct dsa_switch_ops sja1105_switch_ops = { .port_mdb_add = sja1105_mdb_add, .port_mdb_del = sja1105_mdb_del, .port_deferred_xmit = sja1105_port_deferred_xmit, + .port_hwtstamp_get = sja1105_hwtstamp_get, + .port_hwtstamp_set = sja1105_hwtstamp_set, + .port_rxtstamp = sja1105_port_rxtstamp, + .port_txtstamp = sja1105_port_txtstamp, }; static int sja1105_check_device_id(struct sja1105_private *priv) @@ -1575,6 +2132,7 @@ static int sja1105_check_device_id(struct sja1105_private *priv) static int sja1105_probe(struct spi_device *spi) { + struct sja1105_tagger_data *tagger_data; struct device *dev = &spi->dev; struct sja1105_private *priv; struct dsa_switch *ds; @@ -1629,12 +2187,17 @@ static int sja1105_probe(struct spi_device *spi) ds->priv = priv; priv->ds = ds; + tagger_data = &priv->tagger_data; + skb_queue_head_init(&tagger_data->skb_rxtstamp_queue); + INIT_WORK(&tagger_data->rxtstamp_work, sja1105_rxtstamp_work); + /* Connections between dsa_port and sja1105_port */ for (i = 0; i < SJA1105_NUM_PORTS; i++) { struct sja1105_port *sp = &priv->ports[i]; ds->ports[i].priv = sp; sp->dp = &ds->ports[i]; + sp->data = tagger_data; } mutex_init(&priv->mgmt_lock); @@ -1645,6 +2208,7 @@ static int sja1105_remove(struct spi_device *spi) { struct sja1105_private *priv = spi_get_drvdata(spi); + sja1105_ptp_clock_unregister(priv); dsa_unregister_switch(priv->ds); sja1105_static_config_free(&priv->static_config); return 0; diff --git a/drivers/net/dsa/sja1105/sja1105_ptp.c b/drivers/net/dsa/sja1105/sja1105_ptp.c new file mode 100644 index 000000000000..d19cfdf681af --- /dev/null +++ b/drivers/net/dsa/sja1105/sja1105_ptp.c @@ -0,0 +1,393 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Copyright (c) 2019, Vladimir Oltean <olteanv@gmail.com> + */ +#include "sja1105.h" + +/* The adjfine API clamps ppb between [-32,768,000, 32,768,000], and + * therefore scaled_ppm between [-2,147,483,648, 2,147,483,647]. + * Set the maximum supported ppb to a round value smaller than the maximum. + * + * Percentually speaking, this is a +/- 0.032x adjustment of the + * free-running counter (0.968x to 1.032x). + */ +#define SJA1105_MAX_ADJ_PPB 32000000 +#define SJA1105_SIZE_PTP_CMD 4 + +/* Timestamps are in units of 8 ns clock ticks (equivalent to a fixed + * 125 MHz clock) so the scale factor (MULT / SHIFT) needs to be 8. + * Furthermore, wisely pick SHIFT as 28 bits, which translates + * MULT into 2^31 (0x80000000). This is the same value around which + * the hardware PTPCLKRATE is centered, so the same ppb conversion + * arithmetic can be reused. + */ +#define SJA1105_CC_SHIFT 28 +#define SJA1105_CC_MULT (8 << SJA1105_CC_SHIFT) + +/* Having 33 bits of cycle counter left until a 64-bit overflow during delta + * conversion, we multiply this by the 8 ns counter resolution and arrive at + * a comfortable 68.71 second refresh interval until the delta would cause + * an integer overflow, in absence of any other readout. + * Approximate to 1 minute. + */ +#define SJA1105_REFRESH_INTERVAL (HZ * 60) + +/* This range is actually +/- SJA1105_MAX_ADJ_PPB + * divided by 1000 (ppb -> ppm) and with a 16-bit + * "fractional" part (actually fixed point). + * | + * v + * Convert scaled_ppm from the +/- ((10^6) << 16) range + * into the +/- (1 << 31) range. + * + * This forgoes a "ppb" numeric representation (up to NSEC_PER_SEC) + * and defines the scaling factor between scaled_ppm and the actual + * frequency adjustments (both cycle counter and hardware). + * + * ptpclkrate = scaled_ppm * 2^31 / (10^6 * 2^16) + * simplifies to + * ptpclkrate = scaled_ppm * 2^9 / 5^6 + */ +#define SJA1105_CC_MULT_NUM (1 << 9) +#define SJA1105_CC_MULT_DEM 15625 + +#define ptp_to_sja1105(d) container_of((d), struct sja1105_private, ptp_caps) +#define cc_to_sja1105(d) container_of((d), struct sja1105_private, tstamp_cc) +#define dw_to_sja1105(d) container_of((d), struct sja1105_private, refresh_work) + +struct sja1105_ptp_cmd { + u64 resptp; /* reset */ +}; + +int sja1105_get_ts_info(struct dsa_switch *ds, int port, + struct ethtool_ts_info *info) +{ + struct sja1105_private *priv = ds->priv; + + /* Called during cleanup */ + if (!priv->clock) + return -ENODEV; + + info->so_timestamping = SOF_TIMESTAMPING_TX_HARDWARE | + SOF_TIMESTAMPING_RX_HARDWARE | + SOF_TIMESTAMPING_RAW_HARDWARE; + info->tx_types = (1 << HWTSTAMP_TX_OFF) | + (1 << HWTSTAMP_TX_ON); + info->rx_filters = (1 << HWTSTAMP_FILTER_NONE) | + (1 << HWTSTAMP_FILTER_PTP_V2_L2_EVENT); + info->phc_index = ptp_clock_index(priv->clock); + return 0; +} + +int sja1105et_ptp_cmd(const void *ctx, const void *data) +{ + const struct sja1105_ptp_cmd *cmd = data; + const struct sja1105_private *priv = ctx; + const struct sja1105_regs *regs = priv->info->regs; + const int size = SJA1105_SIZE_PTP_CMD; + u8 buf[SJA1105_SIZE_PTP_CMD] = {0}; + /* No need to keep this as part of the structure */ + u64 valid = 1; + + sja1105_pack(buf, &valid, 31, 31, size); + sja1105_pack(buf, &cmd->resptp, 2, 2, size); + + return sja1105_spi_send_packed_buf(priv, SPI_WRITE, regs->ptp_control, + buf, SJA1105_SIZE_PTP_CMD); +} + +int sja1105pqrs_ptp_cmd(const void *ctx, const void *data) +{ + const struct sja1105_ptp_cmd *cmd = data; + const struct sja1105_private *priv = ctx; + const struct sja1105_regs *regs = priv->info->regs; + const int size = SJA1105_SIZE_PTP_CMD; + u8 buf[SJA1105_SIZE_PTP_CMD] = {0}; + /* No need to keep this as part of the structure */ + u64 valid = 1; + + sja1105_pack(buf, &valid, 31, 31, size); + sja1105_pack(buf, &cmd->resptp, 3, 3, size); + + return sja1105_spi_send_packed_buf(priv, SPI_WRITE, regs->ptp_control, + buf, SJA1105_SIZE_PTP_CMD); +} + +/* The switch returns partial timestamps (24 bits for SJA1105 E/T, which wrap + * around in 0.135 seconds, and 32 bits for P/Q/R/S, wrapping around in 34.35 + * seconds). + * + * This receives the RX or TX MAC timestamps, provided by hardware as + * the lower bits of the cycle counter, sampled at the time the timestamp was + * collected. + * + * To reconstruct into a full 64-bit-wide timestamp, the cycle counter is + * read and the high-order bits are filled in. + * + * Must be called within one wraparound period of the partial timestamp since + * it was generated by the MAC. + */ +u64 sja1105_tstamp_reconstruct(struct sja1105_private *priv, u64 now, + u64 ts_partial) +{ + u64 partial_tstamp_mask = CYCLECOUNTER_MASK(priv->info->ptp_ts_bits); + u64 ts_reconstructed; + + ts_reconstructed = (now & ~partial_tstamp_mask) | ts_partial; + + /* Check lower bits of current cycle counter against the timestamp. + * If the current cycle counter is lower than the partial timestamp, + * then wraparound surely occurred and must be accounted for. + */ + if ((now & partial_tstamp_mask) <= ts_partial) + ts_reconstructed -= (partial_tstamp_mask + 1); + + return ts_reconstructed; +} + +/* Reads the SPI interface for an egress timestamp generated by the switch + * for frames sent using management routes. + * + * SJA1105 E/T layout of the 4-byte SPI payload: + * + * 31 23 15 7 0 + * | | | | | + * +-----+-----+-----+ ^ + * ^ | + * | | + * 24-bit timestamp Update bit + * + * + * SJA1105 P/Q/R/S layout of the 8-byte SPI payload: + * + * 31 23 15 7 0 63 55 47 39 32 + * | | | | | | | | | | + * ^ +-----+-----+-----+-----+ + * | ^ + * | | + * Update bit 32-bit timestamp + * + * Notice that the update bit is in the same place. + * To have common code for E/T and P/Q/R/S for reading the timestamp, + * we need to juggle with the offset and the bit indices. + */ +int sja1105_ptpegr_ts_poll(struct sja1105_private *priv, int port, u64 *ts) +{ + const struct sja1105_regs *regs = priv->info->regs; + int tstamp_bit_start, tstamp_bit_end; + int timeout = 10; + u8 packed_buf[8]; + u64 update; + int rc; + + do { + rc = sja1105_spi_send_packed_buf(priv, SPI_READ, + regs->ptpegr_ts[port], + packed_buf, + priv->info->ptpegr_ts_bytes); + if (rc < 0) + return rc; + + sja1105_unpack(packed_buf, &update, 0, 0, + priv->info->ptpegr_ts_bytes); + if (update) + break; + + usleep_range(10, 50); + } while (--timeout); + + if (!timeout) + return -ETIMEDOUT; + + /* Point the end bit to the second 32-bit word on P/Q/R/S, + * no-op on E/T. + */ + tstamp_bit_end = (priv->info->ptpegr_ts_bytes - 4) * 8; + /* Shift the 24-bit timestamp on E/T to be collected from 31:8. + * No-op on P/Q/R/S. + */ + tstamp_bit_end += 32 - priv->info->ptp_ts_bits; + tstamp_bit_start = tstamp_bit_end + priv->info->ptp_ts_bits - 1; + + *ts = 0; + + sja1105_unpack(packed_buf, ts, tstamp_bit_start, tstamp_bit_end, + priv->info->ptpegr_ts_bytes); + + return 0; +} + +int sja1105_ptp_reset(struct sja1105_private *priv) +{ + struct dsa_switch *ds = priv->ds; + struct sja1105_ptp_cmd cmd = {0}; + int rc; + + mutex_lock(&priv->ptp_lock); + + cmd.resptp = 1; + dev_dbg(ds->dev, "Resetting PTP clock\n"); + rc = priv->info->ptp_cmd(priv, &cmd); + + timecounter_init(&priv->tstamp_tc, &priv->tstamp_cc, + ktime_to_ns(ktime_get_real())); + + mutex_unlock(&priv->ptp_lock); + + return rc; +} + +static int sja1105_ptp_gettime(struct ptp_clock_info *ptp, + struct timespec64 *ts) +{ + struct sja1105_private *priv = ptp_to_sja1105(ptp); + u64 ns; + + mutex_lock(&priv->ptp_lock); + ns = timecounter_read(&priv->tstamp_tc); + mutex_unlock(&priv->ptp_lock); + + *ts = ns_to_timespec64(ns); + + return 0; +} + +static int sja1105_ptp_settime(struct ptp_clock_info *ptp, + const struct timespec64 *ts) +{ + struct sja1105_private *priv = ptp_to_sja1105(ptp); + u64 ns = timespec64_to_ns(ts); + + mutex_lock(&priv->ptp_lock); + timecounter_init(&priv->tstamp_tc, &priv->tstamp_cc, ns); + mutex_unlock(&priv->ptp_lock); + + return 0; +} + +static int sja1105_ptp_adjfine(struct ptp_clock_info *ptp, long scaled_ppm) +{ + struct sja1105_private *priv = ptp_to_sja1105(ptp); + s64 clkrate; + + clkrate = (s64)scaled_ppm * SJA1105_CC_MULT_NUM; + clkrate = div_s64(clkrate, SJA1105_CC_MULT_DEM); + + mutex_lock(&priv->ptp_lock); + + /* Force a readout to update the timer *before* changing its frequency. + * + * This way, its corrected time curve can at all times be modeled + * as a linear "A * x + B" function, where: + * + * - B are past frequency adjustments and offset shifts, all + * accumulated into the cycle_last variable. + * + * - A is the new frequency adjustments we're just about to set. + * + * Reading now makes B accumulate the correct amount of time, + * corrected at the old rate, before changing it. + * + * Hardware timestamps then become simple points on the curve and + * are approximated using the above function. This is still better + * than letting the switch take the timestamps using the hardware + * rate-corrected clock (PTPCLKVAL) - the comparison in this case would + * be that we're shifting the ruler at the same time as we're taking + * measurements with it. + * + * The disadvantage is that it's possible to receive timestamps when + * a frequency adjustment took place in the near past. + * In this case they will be approximated using the new ppb value + * instead of a compound function made of two segments (one at the old + * and the other at the new rate) - introducing some inaccuracy. + */ + timecounter_read(&priv->tstamp_tc); + + priv->tstamp_cc.mult = SJA1105_CC_MULT + clkrate; + + mutex_unlock(&priv->ptp_lock); + + return 0; +} + +static int sja1105_ptp_adjtime(struct ptp_clock_info *ptp, s64 delta) +{ + struct sja1105_private *priv = ptp_to_sja1105(ptp); + + mutex_lock(&priv->ptp_lock); + timecounter_adjtime(&priv->tstamp_tc, delta); + mutex_unlock(&priv->ptp_lock); + + return 0; +} + +static u64 sja1105_ptptsclk_read(const struct cyclecounter *cc) +{ + struct sja1105_private *priv = cc_to_sja1105(cc); + const struct sja1105_regs *regs = priv->info->regs; + u64 ptptsclk = 0; + int rc; + + rc = sja1105_spi_send_int(priv, SPI_READ, regs->ptptsclk, + &ptptsclk, 8); + if (rc < 0) + dev_err_ratelimited(priv->ds->dev, + "failed to read ptp cycle counter: %d\n", + rc); + return ptptsclk; +} + +static void sja1105_ptp_overflow_check(struct work_struct *work) +{ + struct delayed_work *dw = to_delayed_work(work); + struct sja1105_private *priv = dw_to_sja1105(dw); + struct timespec64 ts; + + sja1105_ptp_gettime(&priv->ptp_caps, &ts); + + schedule_delayed_work(&priv->refresh_work, SJA1105_REFRESH_INTERVAL); +} + +static const struct ptp_clock_info sja1105_ptp_caps = { + .owner = THIS_MODULE, + .name = "SJA1105 PHC", + .adjfine = sja1105_ptp_adjfine, + .adjtime = sja1105_ptp_adjtime, + .gettime64 = sja1105_ptp_gettime, + .settime64 = sja1105_ptp_settime, + .max_adj = SJA1105_MAX_ADJ_PPB, +}; + +int sja1105_ptp_clock_register(struct sja1105_private *priv) +{ + struct dsa_switch *ds = priv->ds; + + /* Set up the cycle counter */ + priv->tstamp_cc = (struct cyclecounter) { + .read = sja1105_ptptsclk_read, + .mask = CYCLECOUNTER_MASK(64), + .shift = SJA1105_CC_SHIFT, + .mult = SJA1105_CC_MULT, + }; + mutex_init(&priv->ptp_lock); + INIT_DELAYED_WORK(&priv->refresh_work, sja1105_ptp_overflow_check); + + schedule_delayed_work(&priv->refresh_work, SJA1105_REFRESH_INTERVAL); + + priv->ptp_caps = sja1105_ptp_caps; + + priv->clock = ptp_clock_register(&priv->ptp_caps, ds->dev); + if (IS_ERR_OR_NULL(priv->clock)) + return PTR_ERR(priv->clock); + + return sja1105_ptp_reset(priv); +} + +void sja1105_ptp_clock_unregister(struct sja1105_private *priv) +{ + if (IS_ERR_OR_NULL(priv->clock)) + return; + + cancel_delayed_work_sync(&priv->refresh_work); + ptp_clock_unregister(priv->clock); + priv->clock = NULL; +} diff --git a/drivers/net/dsa/sja1105/sja1105_ptp.h b/drivers/net/dsa/sja1105/sja1105_ptp.h new file mode 100644 index 000000000000..af456b0a4d27 --- /dev/null +++ b/drivers/net/dsa/sja1105/sja1105_ptp.h @@ -0,0 +1,64 @@ +/* SPDX-License-Identifier: GPL-2.0 + * Copyright (c) 2019, Vladimir Oltean <olteanv@gmail.com> + */ +#ifndef _SJA1105_PTP_H +#define _SJA1105_PTP_H + +#if IS_ENABLED(CONFIG_NET_DSA_SJA1105_PTP) + +int sja1105_ptp_clock_register(struct sja1105_private *priv); + +void sja1105_ptp_clock_unregister(struct sja1105_private *priv); + +int sja1105_ptpegr_ts_poll(struct sja1105_private *priv, int port, u64 *ts); + +int sja1105et_ptp_cmd(const void *ctx, const void *data); + +int sja1105pqrs_ptp_cmd(const void *ctx, const void *data); + +int sja1105_get_ts_info(struct dsa_switch *ds, int port, + struct ethtool_ts_info *ts); + +u64 sja1105_tstamp_reconstruct(struct sja1105_private *priv, u64 now, + u64 ts_partial); + +int sja1105_ptp_reset(struct sja1105_private *priv); + +#else + +static inline int sja1105_ptp_clock_register(struct sja1105_private *priv) +{ + return 0; +} + +static inline void sja1105_ptp_clock_unregister(struct sja1105_private *priv) +{ + return; +} + +static inline int +sja1105_ptpegr_ts_poll(struct sja1105_private *priv, int port, u64 *ts) +{ + return 0; +} + +static inline u64 sja1105_tstamp_reconstruct(struct sja1105_private *priv, + u64 now, u64 ts_partial) +{ + return 0; +} + +static inline int sja1105_ptp_reset(struct sja1105_private *priv) +{ + return 0; +} + +#define sja1105et_ptp_cmd NULL + +#define sja1105pqrs_ptp_cmd NULL + +#define sja1105_get_ts_info NULL + +#endif /* IS_ENABLED(CONFIG_NET_DSA_SJA1105_PTP) */ + +#endif /* _SJA1105_PTP_H */ diff --git a/drivers/net/dsa/sja1105/sja1105_spi.c b/drivers/net/dsa/sja1105/sja1105_spi.c index 2eb70b8acfc3..84dc603138cf 100644 --- a/drivers/net/dsa/sja1105/sja1105_spi.c +++ b/drivers/net/dsa/sja1105/sja1105_spi.c @@ -283,20 +283,22 @@ static int sja1105_cold_reset(const struct sja1105_private *priv) return priv->info->reset_cmd(priv, &reset); } -static int sja1105_inhibit_tx(const struct sja1105_private *priv, - const unsigned long *port_bitmap) +int sja1105_inhibit_tx(const struct sja1105_private *priv, + unsigned long port_bitmap, bool tx_inhibited) { const struct sja1105_regs *regs = priv->info->regs; u64 inhibit_cmd; - int port, rc; + int rc; rc = sja1105_spi_send_int(priv, SPI_READ, regs->port_control, &inhibit_cmd, SJA1105_SIZE_PORT_CTRL); if (rc < 0) return rc; - for_each_set_bit(port, port_bitmap, SJA1105_NUM_PORTS) - inhibit_cmd |= BIT(port); + if (tx_inhibited) + inhibit_cmd |= port_bitmap; + else + inhibit_cmd &= ~port_bitmap; return sja1105_spi_send_int(priv, SPI_WRITE, regs->port_control, &inhibit_cmd, SJA1105_SIZE_PORT_CTRL); @@ -413,7 +415,7 @@ int sja1105_static_config_upload(struct sja1105_private *priv) * Tx on all ports and waiting for current packet to drain. * Otherwise, the PHY will see an unterminated Ethernet packet. */ - rc = sja1105_inhibit_tx(priv, &port_bitmap); + rc = sja1105_inhibit_tx(priv, port_bitmap, true); if (rc < 0) { dev_err(dev, "Failed to inhibit Tx on ports\n"); return -ENXIO; @@ -478,7 +480,12 @@ int sja1105_static_config_upload(struct sja1105_private *priv) dev_info(dev, "Succeeded after %d tried\n", RETRIES - retries); } + rc = sja1105_ptp_reset(priv); + if (rc < 0) + dev_err(dev, "Failed to reset PTP clock: %d\n", rc); + dev_info(dev, "Reset switch and programmed static config\n"); + out: kfree(config_buf); return rc; @@ -491,11 +498,10 @@ static struct sja1105_regs sja1105et_regs = { .port_control = 0x11, .config = 0x020000, .rgu = 0x100440, + /* UM10944.pdf, Table 86, ACU Register overview */ .pad_mii_tx = {0x100800, 0x100802, 0x100804, 0x100806, 0x100808}, .rmii_pll1 = 0x10000A, .cgu_idiv = {0x10000B, 0x10000C, 0x10000D, 0x10000E, 0x10000F}, - /* UM10944.pdf, Table 86, ACU Register overview */ - .rgmii_pad_mii_tx = {0x100800, 0x100802, 0x100804, 0x100806, 0x100808}, .mac = {0x200, 0x202, 0x204, 0x206, 0x208}, .mac_hl1 = {0x400, 0x410, 0x420, 0x430, 0x440}, .mac_hl2 = {0x600, 0x610, 0x620, 0x630, 0x640}, @@ -507,6 +513,11 @@ static struct sja1105_regs sja1105et_regs = { .rgmii_tx_clk = {0x100016, 0x10001D, 0x100024, 0x10002B, 0x100032}, .rmii_ref_clk = {0x100015, 0x10001C, 0x100023, 0x10002A, 0x100031}, .rmii_ext_tx_clk = {0x100018, 0x10001F, 0x100026, 0x10002D, 0x100034}, + .ptpegr_ts = {0xC0, 0xC2, 0xC4, 0xC6, 0xC8}, + .ptp_control = 0x17, + .ptpclk = 0x18, /* Spans 0x18 to 0x19 */ + .ptpclkrate = 0x1A, + .ptptsclk = 0x1B, /* Spans 0x1B to 0x1C */ }; static struct sja1105_regs sja1105pqrs_regs = { @@ -516,11 +527,11 @@ static struct sja1105_regs sja1105pqrs_regs = { .port_control = 0x12, .config = 0x020000, .rgu = 0x100440, + /* UM10944.pdf, Table 86, ACU Register overview */ .pad_mii_tx = {0x100800, 0x100802, 0x100804, 0x100806, 0x100808}, + .pad_mii_id = {0x100810, 0x100811, 0x100812, 0x100813, 0x100814}, .rmii_pll1 = 0x10000A, .cgu_idiv = {0x10000B, 0x10000C, 0x10000D, 0x10000E, 0x10000F}, - /* UM10944.pdf, Table 86, ACU Register overview */ - .rgmii_pad_mii_tx = {0x100800, 0x100802, 0x100804, 0x100806, 0x100808}, .mac = {0x200, 0x202, 0x204, 0x206, 0x208}, .mac_hl1 = {0x400, 0x410, 0x420, 0x430, 0x440}, .mac_hl2 = {0x600, 0x610, 0x620, 0x630, 0x640}, @@ -533,6 +544,11 @@ static struct sja1105_regs sja1105pqrs_regs = { .rmii_ref_clk = {0x100015, 0x10001B, 0x100021, 0x100027, 0x10002D}, .rmii_ext_tx_clk = {0x100017, 0x10001D, 0x100023, 0x100029, 0x10002F}, .qlevel = {0x604, 0x614, 0x624, 0x634, 0x644}, + .ptpegr_ts = {0xC0, 0xC4, 0xC8, 0xCC, 0xD0}, + .ptp_control = 0x18, + .ptpclk = 0x19, + .ptpclkrate = 0x1B, + .ptptsclk = 0x1C, }; struct sja1105_info sja1105e_info = { @@ -540,7 +556,12 @@ struct sja1105_info sja1105e_info = { .part_no = SJA1105ET_PART_NO, .static_ops = sja1105e_table_ops, .dyn_ops = sja1105et_dyn_ops, + .ptp_ts_bits = 24, + .ptpegr_ts_bytes = 4, .reset_cmd = sja1105et_reset_cmd, + .fdb_add_cmd = sja1105et_fdb_add, + .fdb_del_cmd = sja1105et_fdb_del, + .ptp_cmd = sja1105et_ptp_cmd, .regs = &sja1105et_regs, .name = "SJA1105E", }; @@ -549,7 +570,12 @@ struct sja1105_info sja1105t_info = { .part_no = SJA1105ET_PART_NO, .static_ops = sja1105t_table_ops, .dyn_ops = sja1105et_dyn_ops, + .ptp_ts_bits = 24, + .ptpegr_ts_bytes = 4, .reset_cmd = sja1105et_reset_cmd, + .fdb_add_cmd = sja1105et_fdb_add, + .fdb_del_cmd = sja1105et_fdb_del, + .ptp_cmd = sja1105et_ptp_cmd, .regs = &sja1105et_regs, .name = "SJA1105T", }; @@ -558,7 +584,13 @@ struct sja1105_info sja1105p_info = { .part_no = SJA1105P_PART_NO, .static_ops = sja1105p_table_ops, .dyn_ops = sja1105pqrs_dyn_ops, + .ptp_ts_bits = 32, + .ptpegr_ts_bytes = 8, + .setup_rgmii_delay = sja1105pqrs_setup_rgmii_delay, .reset_cmd = sja1105pqrs_reset_cmd, + .fdb_add_cmd = sja1105pqrs_fdb_add, + .fdb_del_cmd = sja1105pqrs_fdb_del, + .ptp_cmd = sja1105pqrs_ptp_cmd, .regs = &sja1105pqrs_regs, .name = "SJA1105P", }; @@ -567,7 +599,13 @@ struct sja1105_info sja1105q_info = { .part_no = SJA1105Q_PART_NO, .static_ops = sja1105q_table_ops, .dyn_ops = sja1105pqrs_dyn_ops, + .ptp_ts_bits = 32, + .ptpegr_ts_bytes = 8, + .setup_rgmii_delay = sja1105pqrs_setup_rgmii_delay, .reset_cmd = sja1105pqrs_reset_cmd, + .fdb_add_cmd = sja1105pqrs_fdb_add, + .fdb_del_cmd = sja1105pqrs_fdb_del, + .ptp_cmd = sja1105pqrs_ptp_cmd, .regs = &sja1105pqrs_regs, .name = "SJA1105Q", }; @@ -576,7 +614,13 @@ struct sja1105_info sja1105r_info = { .part_no = SJA1105R_PART_NO, .static_ops = sja1105r_table_ops, .dyn_ops = sja1105pqrs_dyn_ops, + .ptp_ts_bits = 32, + .ptpegr_ts_bytes = 8, + .setup_rgmii_delay = sja1105pqrs_setup_rgmii_delay, .reset_cmd = sja1105pqrs_reset_cmd, + .fdb_add_cmd = sja1105pqrs_fdb_add, + .fdb_del_cmd = sja1105pqrs_fdb_del, + .ptp_cmd = sja1105pqrs_ptp_cmd, .regs = &sja1105pqrs_regs, .name = "SJA1105R", }; @@ -586,6 +630,12 @@ struct sja1105_info sja1105s_info = { .static_ops = sja1105s_table_ops, .dyn_ops = sja1105pqrs_dyn_ops, .regs = &sja1105pqrs_regs, + .ptp_ts_bits = 32, + .ptpegr_ts_bytes = 8, + .setup_rgmii_delay = sja1105pqrs_setup_rgmii_delay, .reset_cmd = sja1105pqrs_reset_cmd, + .fdb_add_cmd = sja1105pqrs_fdb_add, + .fdb_del_cmd = sja1105pqrs_fdb_del, + .ptp_cmd = sja1105pqrs_ptp_cmd, .name = "SJA1105S", }; diff --git a/drivers/net/dsa/sja1105/sja1105_static_config.c b/drivers/net/dsa/sja1105/sja1105_static_config.c index b3c992b0abb0..b31c737dc560 100644 --- a/drivers/net/dsa/sja1105/sja1105_static_config.c +++ b/drivers/net/dsa/sja1105/sja1105_static_config.c @@ -91,6 +91,28 @@ u32 sja1105_crc32(const void *buf, size_t len) return ~crc; } +static size_t sja1105et_avb_params_entry_packing(void *buf, void *entry_ptr, + enum packing_op op) +{ + const size_t size = SJA1105ET_SIZE_AVB_PARAMS_ENTRY; + struct sja1105_avb_params_entry *entry = entry_ptr; + + sja1105_packing(buf, &entry->destmeta, 95, 48, size, op); + sja1105_packing(buf, &entry->srcmeta, 47, 0, size, op); + return size; +} + +static size_t sja1105pqrs_avb_params_entry_packing(void *buf, void *entry_ptr, + enum packing_op op) +{ + const size_t size = SJA1105PQRS_SIZE_AVB_PARAMS_ENTRY; + struct sja1105_avb_params_entry *entry = entry_ptr; + + sja1105_packing(buf, &entry->destmeta, 125, 78, size, op); + sja1105_packing(buf, &entry->srcmeta, 77, 30, size, op); + return size; +} + static size_t sja1105et_general_params_entry_packing(void *buf, void *entry_ptr, enum packing_op op) { @@ -208,11 +230,20 @@ sja1105pqrs_l2_lookup_params_entry_packing(void *buf, void *entry_ptr, { const size_t size = SJA1105PQRS_SIZE_L2_LOOKUP_PARAMS_ENTRY; struct sja1105_l2_lookup_params_entry *entry = entry_ptr; + int offset, i; + for (i = 0, offset = 58; i < 5; i++, offset += 11) + sja1105_packing(buf, &entry->maxaddrp[i], + offset + 10, offset + 0, size, op); sja1105_packing(buf, &entry->maxage, 57, 43, size, op); + sja1105_packing(buf, &entry->start_dynspc, 42, 33, size, op); + sja1105_packing(buf, &entry->drpnolearn, 32, 28, size, op); sja1105_packing(buf, &entry->shared_learn, 27, 27, size, op); sja1105_packing(buf, &entry->no_enf_hostprt, 26, 26, size, op); sja1105_packing(buf, &entry->no_mgmt_learn, 25, 25, size, op); + sja1105_packing(buf, &entry->use_static, 24, 24, size, op); + sja1105_packing(buf, &entry->owr_dyn, 23, 23, size, op); + sja1105_packing(buf, &entry->learn_once, 22, 22, size, op); return size; } @@ -236,10 +267,20 @@ size_t sja1105pqrs_l2_lookup_entry_packing(void *buf, void *entry_ptr, const size_t size = SJA1105PQRS_SIZE_L2_LOOKUP_ENTRY; struct sja1105_l2_lookup_entry *entry = entry_ptr; - /* These are static L2 lookup entries, so the structure - * should match UM11040 Table 16/17 definitions when - * LOCKEDS is 1. - */ + if (entry->lockeds) { + sja1105_packing(buf, &entry->tsreg, 159, 159, size, op); + sja1105_packing(buf, &entry->mirrvlan, 158, 147, size, op); + sja1105_packing(buf, &entry->takets, 146, 146, size, op); + sja1105_packing(buf, &entry->mirr, 145, 145, size, op); + sja1105_packing(buf, &entry->retag, 144, 144, size, op); + } else { + sja1105_packing(buf, &entry->touched, 159, 159, size, op); + sja1105_packing(buf, &entry->age, 158, 144, size, op); + } + sja1105_packing(buf, &entry->mask_iotag, 143, 143, size, op); + sja1105_packing(buf, &entry->mask_vlanid, 142, 131, size, op); + sja1105_packing(buf, &entry->mask_macaddr, 130, 83, size, op); + sja1105_packing(buf, &entry->iotag, 82, 82, size, op); sja1105_packing(buf, &entry->vlanid, 81, 70, size, op); sja1105_packing(buf, &entry->macaddr, 69, 22, size, op); sja1105_packing(buf, &entry->destports, 21, 17, size, op); @@ -413,6 +454,7 @@ static u64 blk_id_map[BLK_IDX_MAX] = { [BLK_IDX_MAC_CONFIG] = BLKID_MAC_CONFIG, [BLK_IDX_L2_LOOKUP_PARAMS] = BLKID_L2_LOOKUP_PARAMS, [BLK_IDX_L2_FORWARDING_PARAMS] = BLKID_L2_FORWARDING_PARAMS, + [BLK_IDX_AVB_PARAMS] = BLKID_AVB_PARAMS, [BLK_IDX_GENERAL_PARAMS] = BLKID_GENERAL_PARAMS, [BLK_IDX_XMII_PARAMS] = BLKID_XMII_PARAMS, }; @@ -442,7 +484,7 @@ const char *sja1105_static_config_error_msg[] = { "vl-forwarding-parameters-table.partspc.", }; -sja1105_config_valid_t +static sja1105_config_valid_t static_config_check_memory_size(const struct sja1105_table *tables) { const struct sja1105_l2_forwarding_params_entry *l2_fwd_params; @@ -614,6 +656,12 @@ struct sja1105_table_ops sja1105e_table_ops[BLK_IDX_MAX] = { .packed_entry_size = SJA1105_SIZE_L2_FORWARDING_PARAMS_ENTRY, .max_entry_count = SJA1105_MAX_L2_FORWARDING_PARAMS_COUNT, }, + [BLK_IDX_AVB_PARAMS] = { + .packing = sja1105et_avb_params_entry_packing, + .unpacked_entry_size = sizeof(struct sja1105_avb_params_entry), + .packed_entry_size = SJA1105ET_SIZE_AVB_PARAMS_ENTRY, + .max_entry_count = SJA1105_MAX_AVB_PARAMS_COUNT, + }, [BLK_IDX_GENERAL_PARAMS] = { .packing = sja1105et_general_params_entry_packing, .unpacked_entry_size = sizeof(struct sja1105_general_params_entry), @@ -672,6 +720,12 @@ struct sja1105_table_ops sja1105t_table_ops[BLK_IDX_MAX] = { .packed_entry_size = SJA1105_SIZE_L2_FORWARDING_PARAMS_ENTRY, .max_entry_count = SJA1105_MAX_L2_FORWARDING_PARAMS_COUNT, }, + [BLK_IDX_AVB_PARAMS] = { + .packing = sja1105et_avb_params_entry_packing, + .unpacked_entry_size = sizeof(struct sja1105_avb_params_entry), + .packed_entry_size = SJA1105ET_SIZE_AVB_PARAMS_ENTRY, + .max_entry_count = SJA1105_MAX_AVB_PARAMS_COUNT, + }, [BLK_IDX_GENERAL_PARAMS] = { .packing = sja1105et_general_params_entry_packing, .unpacked_entry_size = sizeof(struct sja1105_general_params_entry), @@ -730,6 +784,12 @@ struct sja1105_table_ops sja1105p_table_ops[BLK_IDX_MAX] = { .packed_entry_size = SJA1105_SIZE_L2_FORWARDING_PARAMS_ENTRY, .max_entry_count = SJA1105_MAX_L2_FORWARDING_PARAMS_COUNT, }, + [BLK_IDX_AVB_PARAMS] = { + .packing = sja1105pqrs_avb_params_entry_packing, + .unpacked_entry_size = sizeof(struct sja1105_avb_params_entry), + .packed_entry_size = SJA1105PQRS_SIZE_AVB_PARAMS_ENTRY, + .max_entry_count = SJA1105_MAX_AVB_PARAMS_COUNT, + }, [BLK_IDX_GENERAL_PARAMS] = { .packing = sja1105pqrs_general_params_entry_packing, .unpacked_entry_size = sizeof(struct sja1105_general_params_entry), @@ -788,6 +848,12 @@ struct sja1105_table_ops sja1105q_table_ops[BLK_IDX_MAX] = { .packed_entry_size = SJA1105_SIZE_L2_FORWARDING_PARAMS_ENTRY, .max_entry_count = SJA1105_MAX_L2_FORWARDING_PARAMS_COUNT, }, + [BLK_IDX_AVB_PARAMS] = { + .packing = sja1105pqrs_avb_params_entry_packing, + .unpacked_entry_size = sizeof(struct sja1105_avb_params_entry), + .packed_entry_size = SJA1105PQRS_SIZE_AVB_PARAMS_ENTRY, + .max_entry_count = SJA1105_MAX_AVB_PARAMS_COUNT, + }, [BLK_IDX_GENERAL_PARAMS] = { .packing = sja1105pqrs_general_params_entry_packing, .unpacked_entry_size = sizeof(struct sja1105_general_params_entry), @@ -846,6 +912,12 @@ struct sja1105_table_ops sja1105r_table_ops[BLK_IDX_MAX] = { .packed_entry_size = SJA1105_SIZE_L2_FORWARDING_PARAMS_ENTRY, .max_entry_count = SJA1105_MAX_L2_FORWARDING_PARAMS_COUNT, }, + [BLK_IDX_AVB_PARAMS] = { + .packing = sja1105pqrs_avb_params_entry_packing, + .unpacked_entry_size = sizeof(struct sja1105_avb_params_entry), + .packed_entry_size = SJA1105PQRS_SIZE_AVB_PARAMS_ENTRY, + .max_entry_count = SJA1105_MAX_AVB_PARAMS_COUNT, + }, [BLK_IDX_GENERAL_PARAMS] = { .packing = sja1105pqrs_general_params_entry_packing, .unpacked_entry_size = sizeof(struct sja1105_general_params_entry), @@ -904,6 +976,12 @@ struct sja1105_table_ops sja1105s_table_ops[BLK_IDX_MAX] = { .packed_entry_size = SJA1105_SIZE_L2_FORWARDING_PARAMS_ENTRY, .max_entry_count = SJA1105_MAX_L2_FORWARDING_PARAMS_COUNT, }, + [BLK_IDX_AVB_PARAMS] = { + .packing = sja1105pqrs_avb_params_entry_packing, + .unpacked_entry_size = sizeof(struct sja1105_avb_params_entry), + .packed_entry_size = SJA1105PQRS_SIZE_AVB_PARAMS_ENTRY, + .max_entry_count = SJA1105_MAX_AVB_PARAMS_COUNT, + }, [BLK_IDX_GENERAL_PARAMS] = { .packing = sja1105pqrs_general_params_entry_packing, .unpacked_entry_size = sizeof(struct sja1105_general_params_entry), diff --git a/drivers/net/dsa/sja1105/sja1105_static_config.h b/drivers/net/dsa/sja1105/sja1105_static_config.h index 069ca8fd059c..684465fc0882 100644 --- a/drivers/net/dsa/sja1105/sja1105_static_config.h +++ b/drivers/net/dsa/sja1105/sja1105_static_config.h @@ -20,10 +20,12 @@ #define SJA1105ET_SIZE_MAC_CONFIG_ENTRY 28 #define SJA1105ET_SIZE_L2_LOOKUP_PARAMS_ENTRY 4 #define SJA1105ET_SIZE_GENERAL_PARAMS_ENTRY 40 +#define SJA1105ET_SIZE_AVB_PARAMS_ENTRY 12 #define SJA1105PQRS_SIZE_L2_LOOKUP_ENTRY 20 #define SJA1105PQRS_SIZE_MAC_CONFIG_ENTRY 32 #define SJA1105PQRS_SIZE_L2_LOOKUP_PARAMS_ENTRY 16 #define SJA1105PQRS_SIZE_GENERAL_PARAMS_ENTRY 44 +#define SJA1105PQRS_SIZE_AVB_PARAMS_ENTRY 16 /* UM10944.pdf Page 11, Table 2. Configuration Blocks */ enum { @@ -34,6 +36,7 @@ enum { BLKID_MAC_CONFIG = 0x09, BLKID_L2_LOOKUP_PARAMS = 0x0D, BLKID_L2_FORWARDING_PARAMS = 0x0E, + BLKID_AVB_PARAMS = 0x10, BLKID_GENERAL_PARAMS = 0x11, BLKID_XMII_PARAMS = 0x4E, }; @@ -46,6 +49,7 @@ enum sja1105_blk_idx { BLK_IDX_MAC_CONFIG, BLK_IDX_L2_LOOKUP_PARAMS, BLK_IDX_L2_FORWARDING_PARAMS, + BLK_IDX_AVB_PARAMS, BLK_IDX_GENERAL_PARAMS, BLK_IDX_XMII_PARAMS, BLK_IDX_MAX, @@ -64,6 +68,7 @@ enum sja1105_blk_idx { #define SJA1105_MAX_L2_FORWARDING_PARAMS_COUNT 1 #define SJA1105_MAX_GENERAL_PARAMS_COUNT 1 #define SJA1105_MAX_XMII_PARAMS_COUNT 1 +#define SJA1105_MAX_AVB_PARAMS_COUNT 1 #define SJA1105_MAX_FRAME_MEMORY 929 @@ -122,9 +127,36 @@ struct sja1105_l2_lookup_entry { u64 destports; u64 enfport; u64 index; + /* P/Q/R/S only */ + u64 mask_iotag; + u64 mask_vlanid; + u64 mask_macaddr; + u64 iotag; + u64 lockeds; + union { + /* LOCKEDS=1: Static FDB entries */ + struct { + u64 tsreg; + u64 mirrvlan; + u64 takets; + u64 mirr; + u64 retag; + }; + /* LOCKEDS=0: Dynamically learned FDB entries */ + struct { + u64 touched; + u64 age; + }; + }; }; struct sja1105_l2_lookup_params_entry { + u64 maxaddrp[5]; /* P/Q/R/S only */ + u64 start_dynspc; /* P/Q/R/S only */ + u64 drpnolearn; /* P/Q/R/S only */ + u64 use_static; /* P/Q/R/S only */ + u64 owr_dyn; /* P/Q/R/S only */ + u64 learn_once; /* P/Q/R/S only */ u64 maxage; /* Shared */ u64 dyn_tbsz; /* E/T only */ u64 poly; /* E/T only */ @@ -153,6 +185,11 @@ struct sja1105_l2_policing_entry { u64 partition; }; +struct sja1105_avb_params_entry { + u64 destmeta; + u64 srcmeta; +}; + struct sja1105_mac_config_entry { u64 top[8]; u64 base[8]; diff --git a/drivers/net/dsa/vitesse-vsc73xx.c b/drivers/net/dsa/vitesse-vsc73xx-core.c index d4780610ea8a..614377ef7956 100644 --- a/drivers/net/dsa/vitesse-vsc73xx.c +++ b/drivers/net/dsa/vitesse-vsc73xx-core.c @@ -10,10 +10,6 @@ * handling the switch in a memory-mapped manner by connecting to that external * CPU's memory bus. * - * This driver (currently) only takes control of the switch chip over SPI and - * configures it to route packages around when connected to a CPU port. The - * chip has embedded PHYs and VLAN support so we model it using DSA. - * * Copyright (C) 2018 Linus Wallej <linus.walleij@linaro.org> * Includes portions of code from the firmware uploader by: * Copyright (C) 2009 Gabor Juhos <juhosg@openwrt.org> @@ -24,8 +20,6 @@ #include <linux/of.h> #include <linux/of_device.h> #include <linux/of_mdio.h> -#include <linux/platform_device.h> -#include <linux/spi/spi.h> #include <linux/bitops.h> #include <linux/if_bridge.h> #include <linux/etherdevice.h> @@ -34,6 +28,8 @@ #include <linux/random.h> #include <net/dsa.h> +#include "vitesse-vsc73xx.h" + #define VSC73XX_BLOCK_MAC 0x1 /* Subblocks 0-4, 6 (CPU port) */ #define VSC73XX_BLOCK_ANALYZER 0x2 /* Only subblock 0 */ #define VSC73XX_BLOCK_MII 0x3 /* Subblocks 0 and 1 */ @@ -255,13 +251,6 @@ #define VSC73XX_GLORESET_PHY_RESET BIT(1) #define VSC73XX_GLORESET_MASTER_RESET BIT(0) -#define VSC73XX_CMD_MODE_READ 0 -#define VSC73XX_CMD_MODE_WRITE 1 -#define VSC73XX_CMD_MODE_SHIFT 4 -#define VSC73XX_CMD_BLOCK_SHIFT 5 -#define VSC73XX_CMD_BLOCK_MASK 0x7 -#define VSC73XX_CMD_SUBBLOCK_MASK 0xf - #define VSC7385_CLOCK_DELAY ((3 << 4) | 3) #define VSC7385_CLOCK_DELAY_MASK ((3 << 4) | 3) @@ -274,20 +263,6 @@ VSC73XX_ICPU_CTRL_CLK_EN | \ VSC73XX_ICPU_CTRL_SRST) -/** - * struct vsc73xx - VSC73xx state container - */ -struct vsc73xx { - struct device *dev; - struct gpio_desc *reset; - struct spi_device *spi; - struct dsa_switch *ds; - struct gpio_chip gc; - u16 chipid; - u8 addr[ETH_ALEN]; - struct mutex lock; /* Protects SPI traffic */ -}; - #define IS_7385(a) ((a)->chipid == VSC73XX_CHIPID_ID_7385) #define IS_7388(a) ((a)->chipid == VSC73XX_CHIPID_ID_7388) #define IS_7395(a) ((a)->chipid == VSC73XX_CHIPID_ID_7395) @@ -365,7 +340,7 @@ static const struct vsc73xx_counter vsc73xx_tx_counters[] = { { 29, "TxQoSClass3" }, /* non-standard counter */ }; -static int vsc73xx_is_addr_valid(u8 block, u8 subblock) +int vsc73xx_is_addr_valid(u8 block, u8 subblock) { switch (block) { case VSC73XX_BLOCK_MAC: @@ -396,96 +371,18 @@ static int vsc73xx_is_addr_valid(u8 block, u8 subblock) return 0; } - -static u8 vsc73xx_make_addr(u8 mode, u8 block, u8 subblock) -{ - u8 ret; - - ret = (block & VSC73XX_CMD_BLOCK_MASK) << VSC73XX_CMD_BLOCK_SHIFT; - ret |= (mode & 1) << VSC73XX_CMD_MODE_SHIFT; - ret |= subblock & VSC73XX_CMD_SUBBLOCK_MASK; - - return ret; -} +EXPORT_SYMBOL(vsc73xx_is_addr_valid); static int vsc73xx_read(struct vsc73xx *vsc, u8 block, u8 subblock, u8 reg, u32 *val) { - struct spi_transfer t[2]; - struct spi_message m; - u8 cmd[4]; - u8 buf[4]; - int ret; - - if (!vsc73xx_is_addr_valid(block, subblock)) - return -EINVAL; - - spi_message_init(&m); - - memset(&t, 0, sizeof(t)); - - t[0].tx_buf = cmd; - t[0].len = sizeof(cmd); - spi_message_add_tail(&t[0], &m); - - t[1].rx_buf = buf; - t[1].len = sizeof(buf); - spi_message_add_tail(&t[1], &m); - - cmd[0] = vsc73xx_make_addr(VSC73XX_CMD_MODE_READ, block, subblock); - cmd[1] = reg; - cmd[2] = 0; - cmd[3] = 0; - - mutex_lock(&vsc->lock); - ret = spi_sync(vsc->spi, &m); - mutex_unlock(&vsc->lock); - - if (ret) - return ret; - - *val = (buf[0] << 24) | (buf[1] << 16) | (buf[2] << 8) | buf[3]; - - return 0; + return vsc->ops->read(vsc, block, subblock, reg, val); } static int vsc73xx_write(struct vsc73xx *vsc, u8 block, u8 subblock, u8 reg, u32 val) { - struct spi_transfer t[2]; - struct spi_message m; - u8 cmd[2]; - u8 buf[4]; - int ret; - - if (!vsc73xx_is_addr_valid(block, subblock)) - return -EINVAL; - - spi_message_init(&m); - - memset(&t, 0, sizeof(t)); - - t[0].tx_buf = cmd; - t[0].len = sizeof(cmd); - spi_message_add_tail(&t[0], &m); - - t[1].tx_buf = buf; - t[1].len = sizeof(buf); - spi_message_add_tail(&t[1], &m); - - cmd[0] = vsc73xx_make_addr(VSC73XX_CMD_MODE_WRITE, block, subblock); - cmd[1] = reg; - - buf[0] = (val >> 24) & 0xff; - buf[1] = (val >> 16) & 0xff; - buf[2] = (val >> 8) & 0xff; - buf[3] = val & 0xff; - - mutex_lock(&vsc->lock); - ret = spi_sync(vsc->spi, &m); - mutex_unlock(&vsc->lock); - - return ret; + return vsc->ops->write(vsc, block, subblock, reg, val); } static int vsc73xx_update_bits(struct vsc73xx *vsc, u8 block, u8 subblock, @@ -520,22 +417,8 @@ static int vsc73xx_detect(struct vsc73xx *vsc) } if (val == 0xffffffff) { - dev_info(vsc->dev, "chip seems dead, assert reset\n"); - gpiod_set_value_cansleep(vsc->reset, 1); - /* Reset pulse should be 20ns minimum, according to datasheet - * table 245, so 10us should be fine - */ - usleep_range(10, 100); - gpiod_set_value_cansleep(vsc->reset, 0); - /* Wait 20ms according to datasheet table 245 */ - msleep(20); - - ret = vsc73xx_read(vsc, VSC73XX_BLOCK_SYSTEM, 0, - VSC73XX_ICPU_MBOX_VAL, &val); - if (val == 0xffffffff) { - dev_err(vsc->dev, "seems not to help, giving up\n"); - return -ENODEV; - } + dev_info(vsc->dev, "chip seems dead.\n"); + return -EAGAIN; } ret = vsc73xx_read(vsc, VSC73XX_BLOCK_SYSTEM, 0, @@ -586,9 +469,8 @@ static int vsc73xx_detect(struct vsc73xx *vsc) } if (icpu_si_boot_en && !icpu_pi_en) { dev_err(vsc->dev, - "iCPU enabled boots from SI, no external memory\n"); - dev_err(vsc->dev, "no idea how to deal with this\n"); - return -ENODEV; + "iCPU enabled boots from PI/SI, no external memory\n"); + return -EAGAIN; } if (!icpu_si_boot_en && icpu_pi_en) { dev_err(vsc->dev, @@ -1245,21 +1127,11 @@ static int vsc73xx_gpio_probe(struct vsc73xx *vsc) return 0; } -static int vsc73xx_probe(struct spi_device *spi) +int vsc73xx_probe(struct vsc73xx *vsc) { - struct device *dev = &spi->dev; - struct vsc73xx *vsc; + struct device *dev = vsc->dev; int ret; - vsc = devm_kzalloc(dev, sizeof(*vsc), GFP_KERNEL); - if (!vsc) - return -ENOMEM; - - spi_set_drvdata(spi, vsc); - vsc->spi = spi_dev_get(spi); - vsc->dev = dev; - mutex_init(&vsc->lock); - /* Release reset, if any */ vsc->reset = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_LOW); if (IS_ERR(vsc->reset)) { @@ -1270,15 +1142,20 @@ static int vsc73xx_probe(struct spi_device *spi) /* Wait 20ms according to datasheet table 245 */ msleep(20); - spi->mode = SPI_MODE_0; - spi->bits_per_word = 8; - ret = spi_setup(spi); - if (ret < 0) { - dev_err(dev, "spi setup failed.\n"); - return ret; - } - ret = vsc73xx_detect(vsc); + if (ret == -EAGAIN) { + dev_err(vsc->dev, + "Chip seems to be out of control. Assert reset and try again.\n"); + gpiod_set_value_cansleep(vsc->reset, 1); + /* Reset pulse should be 20ns minimum, according to datasheet + * table 245, so 10us should be fine + */ + usleep_range(10, 100); + gpiod_set_value_cansleep(vsc->reset, 0); + /* Wait 20ms according to datasheet table 245 */ + msleep(20); + ret = vsc73xx_detect(vsc); + } if (ret) { dev_err(dev, "no chip found (%d)\n", ret); return -ENODEV; @@ -1321,43 +1198,16 @@ static int vsc73xx_probe(struct spi_device *spi) return 0; } +EXPORT_SYMBOL(vsc73xx_probe); -static int vsc73xx_remove(struct spi_device *spi) +int vsc73xx_remove(struct vsc73xx *vsc) { - struct vsc73xx *vsc = spi_get_drvdata(spi); - dsa_unregister_switch(vsc->ds); gpiod_set_value(vsc->reset, 1); return 0; } - -static const struct of_device_id vsc73xx_of_match[] = { - { - .compatible = "vitesse,vsc7385", - }, - { - .compatible = "vitesse,vsc7388", - }, - { - .compatible = "vitesse,vsc7395", - }, - { - .compatible = "vitesse,vsc7398", - }, - { }, -}; -MODULE_DEVICE_TABLE(of, vsc73xx_of_match); - -static struct spi_driver vsc73xx_driver = { - .probe = vsc73xx_probe, - .remove = vsc73xx_remove, - .driver = { - .name = "vsc73xx", - .of_match_table = vsc73xx_of_match, - }, -}; -module_spi_driver(vsc73xx_driver); +EXPORT_SYMBOL(vsc73xx_remove); MODULE_AUTHOR("Linus Walleij <linus.walleij@linaro.org>"); MODULE_DESCRIPTION("Vitesse VSC7385/7388/7395/7398 driver"); diff --git a/drivers/net/dsa/vitesse-vsc73xx-platform.c b/drivers/net/dsa/vitesse-vsc73xx-platform.c new file mode 100644 index 000000000000..0541785f9fee --- /dev/null +++ b/drivers/net/dsa/vitesse-vsc73xx-platform.c @@ -0,0 +1,164 @@ +// SPDX-License-Identifier: GPL-2.0 +/* DSA driver for: + * Vitesse VSC7385 SparX-G5 5+1-port Integrated Gigabit Ethernet Switch + * Vitesse VSC7388 SparX-G8 8-port Integrated Gigabit Ethernet Switch + * Vitesse VSC7395 SparX-G5e 5+1-port Integrated Gigabit Ethernet Switch + * Vitesse VSC7398 SparX-G8e 8-port Integrated Gigabit Ethernet Switch + * + * This driver takes control of the switch chip connected over CPU-attached + * address bus and configures it to route packages around when connected to + * a CPU port. + * + * Copyright (C) 2019 Pawel Dembicki <paweldembicki@gmail.com> + * Based on vitesse-vsc-spi.c by: + * Copyright (C) 2018 Linus Wallej <linus.walleij@linaro.org> + * Includes portions of code from the firmware uploader by: + * Copyright (C) 2009 Gabor Juhos <juhosg@openwrt.org> + */ +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> + +#include "vitesse-vsc73xx.h" + +#define VSC73XX_CMD_PLATFORM_BLOCK_SHIFT 14 +#define VSC73XX_CMD_PLATFORM_BLOCK_MASK 0x7 +#define VSC73XX_CMD_PLATFORM_SUBBLOCK_SHIFT 10 +#define VSC73XX_CMD_PLATFORM_SUBBLOCK_MASK 0xf +#define VSC73XX_CMD_PLATFORM_REGISTER_SHIFT 2 + +/** + * struct vsc73xx_platform - VSC73xx Platform state container + */ +struct vsc73xx_platform { + struct platform_device *pdev; + void __iomem *base_addr; + struct vsc73xx vsc; +}; + +static const struct vsc73xx_ops vsc73xx_platform_ops; + +static u32 vsc73xx_make_addr(u8 block, u8 subblock, u8 reg) +{ + u32 ret; + + ret = (block & VSC73XX_CMD_PLATFORM_BLOCK_MASK) + << VSC73XX_CMD_PLATFORM_BLOCK_SHIFT; + ret |= (subblock & VSC73XX_CMD_PLATFORM_SUBBLOCK_MASK) + << VSC73XX_CMD_PLATFORM_SUBBLOCK_SHIFT; + ret |= reg << VSC73XX_CMD_PLATFORM_REGISTER_SHIFT; + + return ret; +} + +static int vsc73xx_platform_read(struct vsc73xx *vsc, u8 block, u8 subblock, + u8 reg, u32 *val) +{ + struct vsc73xx_platform *vsc_platform = vsc->priv; + u32 offset; + + if (!vsc73xx_is_addr_valid(block, subblock)) + return -EINVAL; + + offset = vsc73xx_make_addr(block, subblock, reg); + /* By default vsc73xx running in big-endian mode. + * (See "Register Addressing" section 5.5.3 in the VSC7385 manual.) + */ + *val = ioread32be(vsc_platform->base_addr + offset); + + return 0; +} + +static int vsc73xx_platform_write(struct vsc73xx *vsc, u8 block, u8 subblock, + u8 reg, u32 val) +{ + struct vsc73xx_platform *vsc_platform = vsc->priv; + u32 offset; + + if (!vsc73xx_is_addr_valid(block, subblock)) + return -EINVAL; + + offset = vsc73xx_make_addr(block, subblock, reg); + iowrite32be(val, vsc_platform->base_addr + offset); + + return 0; +} + +static int vsc73xx_platform_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct vsc73xx_platform *vsc_platform; + struct resource *res = NULL; + int ret; + + vsc_platform = devm_kzalloc(dev, sizeof(*vsc_platform), GFP_KERNEL); + if (!vsc_platform) + return -ENOMEM; + + platform_set_drvdata(pdev, vsc_platform); + vsc_platform->pdev = pdev; + vsc_platform->vsc.dev = dev; + vsc_platform->vsc.priv = vsc_platform; + vsc_platform->vsc.ops = &vsc73xx_platform_ops; + + /* obtain I/O memory space */ + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + dev_err(&pdev->dev, "cannot obtain I/O memory space\n"); + ret = -ENXIO; + return ret; + } + + vsc_platform->base_addr = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(vsc_platform->base_addr)) { + dev_err(&pdev->dev, "cannot request I/O memory space\n"); + ret = -ENXIO; + return ret; + } + + return vsc73xx_probe(&vsc_platform->vsc); +} + +static int vsc73xx_platform_remove(struct platform_device *pdev) +{ + struct vsc73xx_platform *vsc_platform = platform_get_drvdata(pdev); + + return vsc73xx_remove(&vsc_platform->vsc); +} + +static const struct vsc73xx_ops vsc73xx_platform_ops = { + .read = vsc73xx_platform_read, + .write = vsc73xx_platform_write, +}; + +static const struct of_device_id vsc73xx_of_match[] = { + { + .compatible = "vitesse,vsc7385", + }, + { + .compatible = "vitesse,vsc7388", + }, + { + .compatible = "vitesse,vsc7395", + }, + { + .compatible = "vitesse,vsc7398", + }, + { }, +}; +MODULE_DEVICE_TABLE(of, vsc73xx_of_match); + +static struct platform_driver vsc73xx_platform_driver = { + .probe = vsc73xx_platform_probe, + .remove = vsc73xx_platform_remove, + .driver = { + .name = "vsc73xx-platform", + .of_match_table = vsc73xx_of_match, + }, +}; +module_platform_driver(vsc73xx_platform_driver); + +MODULE_AUTHOR("Pawel Dembicki <paweldembicki@gmail.com>"); +MODULE_DESCRIPTION("Vitesse VSC7385/7388/7395/7398 Platform driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/net/dsa/vitesse-vsc73xx-spi.c b/drivers/net/dsa/vitesse-vsc73xx-spi.c new file mode 100644 index 000000000000..e73c8fcddc9f --- /dev/null +++ b/drivers/net/dsa/vitesse-vsc73xx-spi.c @@ -0,0 +1,203 @@ +// SPDX-License-Identifier: GPL-2.0 +/* DSA driver for: + * Vitesse VSC7385 SparX-G5 5+1-port Integrated Gigabit Ethernet Switch + * Vitesse VSC7388 SparX-G8 8-port Integrated Gigabit Ethernet Switch + * Vitesse VSC7395 SparX-G5e 5+1-port Integrated Gigabit Ethernet Switch + * Vitesse VSC7398 SparX-G8e 8-port Integrated Gigabit Ethernet Switch + * + * This driver takes control of the switch chip over SPI and + * configures it to route packages around when connected to a CPU port. + * + * Copyright (C) 2018 Linus Wallej <linus.walleij@linaro.org> + * Includes portions of code from the firmware uploader by: + * Copyright (C) 2009 Gabor Juhos <juhosg@openwrt.org> + */ +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/spi/spi.h> + +#include "vitesse-vsc73xx.h" + +#define VSC73XX_CMD_SPI_MODE_READ 0 +#define VSC73XX_CMD_SPI_MODE_WRITE 1 +#define VSC73XX_CMD_SPI_MODE_SHIFT 4 +#define VSC73XX_CMD_SPI_BLOCK_SHIFT 5 +#define VSC73XX_CMD_SPI_BLOCK_MASK 0x7 +#define VSC73XX_CMD_SPI_SUBBLOCK_MASK 0xf + +/** + * struct vsc73xx_spi - VSC73xx SPI state container + */ +struct vsc73xx_spi { + struct spi_device *spi; + struct mutex lock; /* Protects SPI traffic */ + struct vsc73xx vsc; +}; + +static const struct vsc73xx_ops vsc73xx_spi_ops; + +static u8 vsc73xx_make_addr(u8 mode, u8 block, u8 subblock) +{ + u8 ret; + + ret = + (block & VSC73XX_CMD_SPI_BLOCK_MASK) << VSC73XX_CMD_SPI_BLOCK_SHIFT; + ret |= (mode & 1) << VSC73XX_CMD_SPI_MODE_SHIFT; + ret |= subblock & VSC73XX_CMD_SPI_SUBBLOCK_MASK; + + return ret; +} + +static int vsc73xx_spi_read(struct vsc73xx *vsc, u8 block, u8 subblock, u8 reg, + u32 *val) +{ + struct vsc73xx_spi *vsc_spi = vsc->priv; + struct spi_transfer t[2]; + struct spi_message m; + u8 cmd[4]; + u8 buf[4]; + int ret; + + if (!vsc73xx_is_addr_valid(block, subblock)) + return -EINVAL; + + spi_message_init(&m); + + memset(&t, 0, sizeof(t)); + + t[0].tx_buf = cmd; + t[0].len = sizeof(cmd); + spi_message_add_tail(&t[0], &m); + + t[1].rx_buf = buf; + t[1].len = sizeof(buf); + spi_message_add_tail(&t[1], &m); + + cmd[0] = vsc73xx_make_addr(VSC73XX_CMD_SPI_MODE_READ, block, subblock); + cmd[1] = reg; + cmd[2] = 0; + cmd[3] = 0; + + mutex_lock(&vsc_spi->lock); + ret = spi_sync(vsc_spi->spi, &m); + mutex_unlock(&vsc_spi->lock); + + if (ret) + return ret; + + *val = (buf[0] << 24) | (buf[1] << 16) | (buf[2] << 8) | buf[3]; + + return 0; +} + +static int vsc73xx_spi_write(struct vsc73xx *vsc, u8 block, u8 subblock, u8 reg, + u32 val) +{ + struct vsc73xx_spi *vsc_spi = vsc->priv; + struct spi_transfer t[2]; + struct spi_message m; + u8 cmd[2]; + u8 buf[4]; + int ret; + + if (!vsc73xx_is_addr_valid(block, subblock)) + return -EINVAL; + + spi_message_init(&m); + + memset(&t, 0, sizeof(t)); + + t[0].tx_buf = cmd; + t[0].len = sizeof(cmd); + spi_message_add_tail(&t[0], &m); + + t[1].tx_buf = buf; + t[1].len = sizeof(buf); + spi_message_add_tail(&t[1], &m); + + cmd[0] = vsc73xx_make_addr(VSC73XX_CMD_SPI_MODE_WRITE, block, subblock); + cmd[1] = reg; + + buf[0] = (val >> 24) & 0xff; + buf[1] = (val >> 16) & 0xff; + buf[2] = (val >> 8) & 0xff; + buf[3] = val & 0xff; + + mutex_lock(&vsc_spi->lock); + ret = spi_sync(vsc_spi->spi, &m); + mutex_unlock(&vsc_spi->lock); + + return ret; +} + +static int vsc73xx_spi_probe(struct spi_device *spi) +{ + struct device *dev = &spi->dev; + struct vsc73xx_spi *vsc_spi; + int ret; + + vsc_spi = devm_kzalloc(dev, sizeof(*vsc_spi), GFP_KERNEL); + if (!vsc_spi) + return -ENOMEM; + + spi_set_drvdata(spi, vsc_spi); + vsc_spi->spi = spi_dev_get(spi); + vsc_spi->vsc.dev = dev; + vsc_spi->vsc.priv = vsc_spi; + vsc_spi->vsc.ops = &vsc73xx_spi_ops; + mutex_init(&vsc_spi->lock); + + spi->mode = SPI_MODE_0; + spi->bits_per_word = 8; + ret = spi_setup(spi); + if (ret < 0) { + dev_err(dev, "spi setup failed.\n"); + return ret; + } + + return vsc73xx_probe(&vsc_spi->vsc); +} + +static int vsc73xx_spi_remove(struct spi_device *spi) +{ + struct vsc73xx_spi *vsc_spi = spi_get_drvdata(spi); + + return vsc73xx_remove(&vsc_spi->vsc); +} + +static const struct vsc73xx_ops vsc73xx_spi_ops = { + .read = vsc73xx_spi_read, + .write = vsc73xx_spi_write, +}; + +static const struct of_device_id vsc73xx_of_match[] = { + { + .compatible = "vitesse,vsc7385", + }, + { + .compatible = "vitesse,vsc7388", + }, + { + .compatible = "vitesse,vsc7395", + }, + { + .compatible = "vitesse,vsc7398", + }, + { }, +}; +MODULE_DEVICE_TABLE(of, vsc73xx_of_match); + +static struct spi_driver vsc73xx_spi_driver = { + .probe = vsc73xx_spi_probe, + .remove = vsc73xx_spi_remove, + .driver = { + .name = "vsc73xx-spi", + .of_match_table = vsc73xx_of_match, + }, +}; +module_spi_driver(vsc73xx_spi_driver); + +MODULE_AUTHOR("Linus Walleij <linus.walleij@linaro.org>"); +MODULE_DESCRIPTION("Vitesse VSC7385/7388/7395/7398 SPI driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/net/dsa/vitesse-vsc73xx.h b/drivers/net/dsa/vitesse-vsc73xx.h new file mode 100644 index 000000000000..7478f8d4e0a9 --- /dev/null +++ b/drivers/net/dsa/vitesse-vsc73xx.h @@ -0,0 +1,29 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#include <linux/device.h> +#include <linux/etherdevice.h> +#include <linux/gpio/driver.h> + +/** + * struct vsc73xx - VSC73xx state container + */ +struct vsc73xx { + struct device *dev; + struct gpio_desc *reset; + struct dsa_switch *ds; + struct gpio_chip gc; + u16 chipid; + u8 addr[ETH_ALEN]; + const struct vsc73xx_ops *ops; + void *priv; +}; + +struct vsc73xx_ops { + int (*read)(struct vsc73xx *vsc, u8 block, u8 subblock, u8 reg, + u32 *val); + int (*write)(struct vsc73xx *vsc, u8 block, u8 subblock, u8 reg, + u32 val); +}; + +int vsc73xx_is_addr_valid(u8 block, u8 subblock); +int vsc73xx_probe(struct vsc73xx *vsc); +int vsc73xx_remove(struct vsc73xx *vsc); |