diff options
Diffstat (limited to 'drivers/net/dsa')
| -rw-r--r-- | drivers/net/dsa/Kconfig | 17 | ||||
| -rw-r--r-- | drivers/net/dsa/Makefile | 3 | ||||
| -rw-r--r-- | drivers/net/dsa/bcm_sf2.c | 81 | ||||
| -rw-r--r-- | drivers/net/dsa/mv88e6060.c | 5 | ||||
| -rw-r--r-- | drivers/net/dsa/mv88e6123_61_65.c | 25 | ||||
| -rw-r--r-- | drivers/net/dsa/mv88e6131.c | 12 | ||||
| -rw-r--r-- | drivers/net/dsa/mv88e6171.c | 26 | ||||
| -rw-r--r-- | drivers/net/dsa/mv88e6352.c | 788 | ||||
| -rw-r--r-- | drivers/net/dsa/mv88e6xxx.c | 101 | ||||
| -rw-r--r-- | drivers/net/dsa/mv88e6xxx.h | 16 | 
10 files changed, 1020 insertions, 54 deletions
diff --git a/drivers/net/dsa/Kconfig b/drivers/net/dsa/Kconfig index 9234d808cbb3..48e62a34f7f2 100644 --- a/drivers/net/dsa/Kconfig +++ b/drivers/net/dsa/Kconfig @@ -37,20 +37,29 @@ config NET_DSA_MV88E6123_61_65  	  ethernet switch chips.  config NET_DSA_MV88E6171 -	tristate "Marvell 88E6171 ethernet switch chip support" +	tristate "Marvell 88E6171/6172 ethernet switch chip support"  	select NET_DSA  	select NET_DSA_MV88E6XXX  	select NET_DSA_TAG_EDSA  	---help--- -	  This enables support for the Marvell 88E6171 ethernet switch -	  chip. +	  This enables support for the Marvell 88E6171/6172 ethernet switch +	  chips. + +config NET_DSA_MV88E6352 +	tristate "Marvell 88E6176/88E6352 ethernet switch chip support" +	select NET_DSA +	select NET_DSA_MV88E6XXX +	select NET_DSA_TAG_EDSA +	---help--- +	  This enables support for the Marvell 88E6176 and 88E6352 ethernet +	  switch chips.  config NET_DSA_BCM_SF2  	tristate "Broadcom Starfighter 2 Ethernet switch support"  	depends on HAS_IOMEM  	select NET_DSA  	select NET_DSA_TAG_BRCM -	select FIXED_PHY if NET_DSA_BCM_SF2=y +	select FIXED_PHY  	select BCM7XXX_PHY  	select MDIO_BCM_UNIMAC  	---help--- diff --git a/drivers/net/dsa/Makefile b/drivers/net/dsa/Makefile index 23a90de9830e..e2d51c4b9382 100644 --- a/drivers/net/dsa/Makefile +++ b/drivers/net/dsa/Makefile @@ -7,6 +7,9 @@ endif  ifdef CONFIG_NET_DSA_MV88E6131  mv88e6xxx_drv-y += mv88e6131.o  endif +ifdef CONFIG_NET_DSA_MV88E6352 +mv88e6xxx_drv-y += mv88e6352.o +endif  ifdef CONFIG_NET_DSA_MV88E6171  mv88e6xxx_drv-y += mv88e6171.o  endif diff --git a/drivers/net/dsa/bcm_sf2.c b/drivers/net/dsa/bcm_sf2.c index b9625968daac..feb29c4526f7 100644 --- a/drivers/net/dsa/bcm_sf2.c +++ b/drivers/net/dsa/bcm_sf2.c @@ -377,6 +377,29 @@ static irqreturn_t bcm_sf2_switch_1_isr(int irq, void *dev_id)  	return IRQ_HANDLED;  } +static int bcm_sf2_sw_rst(struct bcm_sf2_priv *priv) +{ +	unsigned int timeout = 1000; +	u32 reg; + +	reg = core_readl(priv, CORE_WATCHDOG_CTRL); +	reg |= SOFTWARE_RESET | EN_CHIP_RST | EN_SW_RESET; +	core_writel(priv, reg, CORE_WATCHDOG_CTRL); + +	do { +		reg = core_readl(priv, CORE_WATCHDOG_CTRL); +		if (!(reg & SOFTWARE_RESET)) +			break; + +		usleep_range(1000, 2000); +	} while (timeout-- > 0); + +	if (timeout == 0) +		return -ETIMEDOUT; + +	return 0; +} +  static int bcm_sf2_sw_setup(struct dsa_switch *ds)  {  	const char *reg_names[BCM_SF2_REGS_NUM] = BCM_SF2_REGS_NAME; @@ -404,11 +427,18 @@ static int bcm_sf2_sw_setup(struct dsa_switch *ds)  		*base = of_iomap(dn, i);  		if (*base == NULL) {  			pr_err("unable to find register: %s\n", reg_names[i]); -			return -ENODEV; +			ret = -ENOMEM; +			goto out_unmap;  		}  		base++;  	} +	ret = bcm_sf2_sw_rst(priv); +	if (ret) { +		pr_err("unable to software reset switch: %d\n", ret); +		goto out_unmap; +	} +  	/* Disable all interrupts and request them */  	intrl2_0_writel(priv, 0xffffffff, INTRL2_CPU_MASK_SET);  	intrl2_0_writel(priv, 0xffffffff, INTRL2_CPU_CLEAR); @@ -484,7 +514,8 @@ out_free_irq0:  out_unmap:  	base = &priv->core;  	for (i = 0; i < BCM_SF2_REGS_NUM; i++) { -		iounmap(*base); +		if (*base) +			iounmap(*base);  		base++;  	}  	return ret; @@ -653,10 +684,9 @@ static void bcm_sf2_sw_fixed_link_update(struct dsa_switch *ds, int port,  					 struct fixed_phy_status *status)  {  	struct bcm_sf2_priv *priv = ds_to_priv(ds); -	u32 link, duplex, pause, speed; +	u32 duplex, pause, speed;  	u32 reg; -	link = core_readl(priv, CORE_LNKSTS);  	duplex = core_readl(priv, CORE_DUPSTS);  	pause = core_readl(priv, CORE_PAUSESTS);  	speed = core_readl(priv, CORE_SPDSTS); @@ -670,22 +700,26 @@ static void bcm_sf2_sw_fixed_link_update(struct dsa_switch *ds, int port,  	 * which means that we need to force the link at the port override  	 * level to get the data to flow. We do use what the interrupt handler  	 * did determine before. +	 * +	 * For the other ports, we just force the link status, since this is +	 * a fixed PHY device.  	 */  	if (port == 7) {  		status->link = priv->port_sts[port].link; -		reg = core_readl(priv, CORE_STS_OVERRIDE_GMIIP_PORT(7)); -		reg |= SW_OVERRIDE; -		if (status->link) -			reg |= LINK_STS; -		else -			reg &= ~LINK_STS; -		core_writel(priv, reg, CORE_STS_OVERRIDE_GMIIP_PORT(7));  		status->duplex = 1;  	} else { -		status->link = !!(link & (1 << port)); +		status->link = 1;  		status->duplex = !!(duplex & (1 << port));  	} +	reg = core_readl(priv, CORE_STS_OVERRIDE_GMIIP_PORT(port)); +	reg |= SW_OVERRIDE; +	if (status->link) +		reg |= LINK_STS; +	else +		reg &= ~LINK_STS; +	core_writel(priv, reg, CORE_STS_OVERRIDE_GMIIP_PORT(port)); +  	switch (speed) {  	case SPDSTS_10:  		status->speed = SPEED_10; @@ -733,29 +767,6 @@ static int bcm_sf2_sw_suspend(struct dsa_switch *ds)  	return 0;  } -static int bcm_sf2_sw_rst(struct bcm_sf2_priv *priv) -{ -	unsigned int timeout = 1000; -	u32 reg; - -	reg = core_readl(priv, CORE_WATCHDOG_CTRL); -	reg |= SOFTWARE_RESET | EN_CHIP_RST | EN_SW_RESET; -	core_writel(priv, reg, CORE_WATCHDOG_CTRL); - -	do { -		reg = core_readl(priv, CORE_WATCHDOG_CTRL); -		if (!(reg & SOFTWARE_RESET)) -			break; - -		usleep_range(1000, 2000); -	} while (timeout-- > 0); - -	if (timeout == 0) -		return -ETIMEDOUT; - -	return 0; -} -  static int bcm_sf2_sw_resume(struct dsa_switch *ds)  {  	struct bcm_sf2_priv *priv = ds_to_priv(ds); diff --git a/drivers/net/dsa/mv88e6060.c b/drivers/net/dsa/mv88e6060.c index 05b0ca3bf71d..c29aebe1e62b 100644 --- a/drivers/net/dsa/mv88e6060.c +++ b/drivers/net/dsa/mv88e6060.c @@ -69,8 +69,11 @@ static char *mv88e6060_probe(struct device *host_dev, int sw_addr)  	ret = mdiobus_read(bus, sw_addr + REG_PORT(0), 0x03);  	if (ret >= 0) { -		ret &= 0xfff0;  		if (ret == 0x0600) +			return "Marvell 88E6060 (A0)"; +		if (ret == 0x0601 || ret == 0x0602) +			return "Marvell 88E6060 (B0)"; +		if ((ret & 0xfff0) == 0x0600)  			return "Marvell 88E6060";  	} diff --git a/drivers/net/dsa/mv88e6123_61_65.c b/drivers/net/dsa/mv88e6123_61_65.c index a332c53ff955..e9c736e1cef3 100644 --- a/drivers/net/dsa/mv88e6123_61_65.c +++ b/drivers/net/dsa/mv88e6123_61_65.c @@ -299,6 +299,7 @@ static int mv88e6123_61_65_setup(struct dsa_switch *ds)  	mutex_init(&ps->smi_mutex);  	mutex_init(&ps->stats_mutex); +	mutex_init(&ps->phy_mutex);  	ret = mv88e6123_61_65_switch_reset(ds);  	if (ret < 0) @@ -329,16 +330,28 @@ static int mv88e6123_61_65_port_to_phy_addr(int port)  static int  mv88e6123_61_65_phy_read(struct dsa_switch *ds, int port, int regnum)  { +	struct mv88e6xxx_priv_state *ps = ds_to_priv(ds);  	int addr = mv88e6123_61_65_port_to_phy_addr(port); -	return mv88e6xxx_phy_read(ds, addr, regnum); +	int ret; + +	mutex_lock(&ps->phy_mutex); +	ret = mv88e6xxx_phy_read(ds, addr, regnum); +	mutex_unlock(&ps->phy_mutex); +	return ret;  }  static int  mv88e6123_61_65_phy_write(struct dsa_switch *ds,  			      int port, int regnum, u16 val)  { +	struct mv88e6xxx_priv_state *ps = ds_to_priv(ds);  	int addr = mv88e6123_61_65_port_to_phy_addr(port); -	return mv88e6xxx_phy_write(ds, addr, regnum, val); +	int ret; + +	mutex_lock(&ps->phy_mutex); +	ret = mv88e6xxx_phy_write(ds, addr, regnum, val); +	mutex_unlock(&ps->phy_mutex); +	return ret;  }  static struct mv88e6xxx_hw_stat mv88e6123_61_65_hw_stats[] = { @@ -372,6 +385,9 @@ static struct mv88e6xxx_hw_stat mv88e6123_61_65_hw_stats[] = {  	{ "hist_256_511bytes", 4, 0x0b, },  	{ "hist_512_1023bytes", 4, 0x0c, },  	{ "hist_1024_max_bytes", 4, 0x0d, }, +	{ "sw_in_discards", 4, 0x110, }, +	{ "sw_in_filtered", 2, 0x112, }, +	{ "sw_out_filtered", 2, 0x113, },  };  static void @@ -406,6 +422,11 @@ struct dsa_switch_driver mv88e6123_61_65_switch_driver = {  	.get_strings		= mv88e6123_61_65_get_strings,  	.get_ethtool_stats	= mv88e6123_61_65_get_ethtool_stats,  	.get_sset_count		= mv88e6123_61_65_get_sset_count, +#ifdef CONFIG_NET_DSA_HWMON +	.get_temp		= mv88e6xxx_get_temp, +#endif +	.get_regs_len		= mv88e6xxx_get_regs_len, +	.get_regs		= mv88e6xxx_get_regs,  };  MODULE_ALIAS("platform:mv88e6123"); diff --git a/drivers/net/dsa/mv88e6131.c b/drivers/net/dsa/mv88e6131.c index 244c735014fa..1230f52aa70e 100644 --- a/drivers/net/dsa/mv88e6131.c +++ b/drivers/net/dsa/mv88e6131.c @@ -21,6 +21,7 @@  #define ID_6085		0x04a0  #define ID_6095		0x0950  #define ID_6131		0x1060 +#define ID_6131_B2	0x1066  static char *mv88e6131_probe(struct device *host_dev, int sw_addr)  { @@ -32,12 +33,15 @@ static char *mv88e6131_probe(struct device *host_dev, int sw_addr)  	ret = __mv88e6xxx_reg_read(bus, sw_addr, REG_PORT(0), 0x03);  	if (ret >= 0) { -		ret &= 0xfff0; -		if (ret == ID_6085) +		int ret_masked = ret & 0xfff0; + +		if (ret_masked == ID_6085)  			return "Marvell 88E6085"; -		if (ret == ID_6095) +		if (ret_masked == ID_6095)  			return "Marvell 88E6095/88E6095F"; -		if (ret == ID_6131) +		if (ret == ID_6131_B2) +			return "Marvell 88E6131 (B2)"; +		if (ret_masked == ID_6131)  			return "Marvell 88E6131";  	} diff --git a/drivers/net/dsa/mv88e6171.c b/drivers/net/dsa/mv88e6171.c index 78d8e876f3aa..aa33d16f2e22 100644 --- a/drivers/net/dsa/mv88e6171.c +++ b/drivers/net/dsa/mv88e6171.c @@ -1,4 +1,4 @@ -/* net/dsa/mv88e6171.c - Marvell 88e6171 switch chip support +/* net/dsa/mv88e6171.c - Marvell 88e6171/8826172 switch chip support   * Copyright (c) 2008-2009 Marvell Semiconductor   * Copyright (c) 2014 Claudio Leite <[email protected]>   * @@ -29,6 +29,8 @@ static char *mv88e6171_probe(struct device *host_dev, int sw_addr)  	if (ret >= 0) {  		if ((ret & 0xfff0) == 0x1710)  			return "Marvell 88E6171"; +		if ((ret & 0xfff0) == 0x1720) +			return "Marvell 88E6172";  	}  	return NULL; @@ -314,6 +316,8 @@ static int mv88e6171_setup(struct dsa_switch *ds)  			return ret;  	} +	mutex_init(&ps->phy_mutex); +  	return 0;  } @@ -327,18 +331,28 @@ static int mv88e6171_port_to_phy_addr(int port)  static int  mv88e6171_phy_read(struct dsa_switch *ds, int port, int regnum)  { +	struct mv88e6xxx_priv_state *ps = ds_to_priv(ds);  	int addr = mv88e6171_port_to_phy_addr(port); +	int ret; -	return mv88e6xxx_phy_read(ds, addr, regnum); +	mutex_lock(&ps->phy_mutex); +	ret = mv88e6xxx_phy_read(ds, addr, regnum); +	mutex_unlock(&ps->phy_mutex); +	return ret;  }  static int  mv88e6171_phy_write(struct dsa_switch *ds,  		    int port, int regnum, u16 val)  { +	struct mv88e6xxx_priv_state *ps = ds_to_priv(ds);  	int addr = mv88e6171_port_to_phy_addr(port); +	int ret; -	return mv88e6xxx_phy_write(ds, addr, regnum, val); +	mutex_lock(&ps->phy_mutex); +	ret = mv88e6xxx_phy_write(ds, addr, regnum, val); +	mutex_unlock(&ps->phy_mutex); +	return ret;  }  static struct mv88e6xxx_hw_stat mv88e6171_hw_stats[] = { @@ -406,6 +420,12 @@ struct dsa_switch_driver mv88e6171_switch_driver = {  	.get_strings		= mv88e6171_get_strings,  	.get_ethtool_stats	= mv88e6171_get_ethtool_stats,  	.get_sset_count		= mv88e6171_get_sset_count, +#ifdef CONFIG_NET_DSA_HWMON +	.get_temp               = mv88e6xxx_get_temp, +#endif +	.get_regs_len		= mv88e6xxx_get_regs_len, +	.get_regs		= mv88e6xxx_get_regs,  };  MODULE_ALIAS("platform:mv88e6171"); +MODULE_ALIAS("platform:mv88e6172"); diff --git a/drivers/net/dsa/mv88e6352.c b/drivers/net/dsa/mv88e6352.c new file mode 100644 index 000000000000..258d9ef5ef25 --- /dev/null +++ b/drivers/net/dsa/mv88e6352.c @@ -0,0 +1,788 @@ +/* + * net/dsa/mv88e6352.c - Marvell 88e6352 switch chip support + * + * Copyright (c) 2014 Guenter Roeck + * + * Derived from mv88e6123_61_65.c + * Copyright (c) 2008-2009 Marvell Semiconductor + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#include <linux/delay.h> +#include <linux/jiffies.h> +#include <linux/list.h> +#include <linux/module.h> +#include <linux/netdevice.h> +#include <linux/platform_device.h> +#include <linux/phy.h> +#include <net/dsa.h> +#include "mv88e6xxx.h" + +static int mv88e6352_wait(struct dsa_switch *ds, int reg, u16 mask) +{ +	unsigned long timeout = jiffies + HZ / 10; + +	while (time_before(jiffies, timeout)) { +		int ret; + +		ret = REG_READ(REG_GLOBAL2, reg); +		if (ret < 0) +			return ret; + +		if (!(ret & mask)) +			return 0; + +		usleep_range(1000, 2000); +	} +	return -ETIMEDOUT; +} + +static inline int mv88e6352_phy_wait(struct dsa_switch *ds) +{ +	return mv88e6352_wait(ds, 0x18, 0x8000); +} + +static inline int mv88e6352_eeprom_load_wait(struct dsa_switch *ds) +{ +	return mv88e6352_wait(ds, 0x14, 0x0800); +} + +static inline int mv88e6352_eeprom_busy_wait(struct dsa_switch *ds) +{ +	return mv88e6352_wait(ds, 0x14, 0x8000); +} + +static int __mv88e6352_phy_read(struct dsa_switch *ds, int addr, int regnum) +{ +	int ret; + +	REG_WRITE(REG_GLOBAL2, 0x18, 0x9800 | (addr << 5) | regnum); + +	ret = mv88e6352_phy_wait(ds); +	if (ret < 0) +		return ret; + +	return REG_READ(REG_GLOBAL2, 0x19); +} + +static int __mv88e6352_phy_write(struct dsa_switch *ds, int addr, int regnum, +				 u16 val) +{ +	REG_WRITE(REG_GLOBAL2, 0x19, val); +	REG_WRITE(REG_GLOBAL2, 0x18, 0x9400 | (addr << 5) | regnum); + +	return mv88e6352_phy_wait(ds); +} + +static char *mv88e6352_probe(struct device *host_dev, int sw_addr) +{ +	struct mii_bus *bus = dsa_host_dev_to_mii_bus(host_dev); +	int ret; + +	if (bus == NULL) +		return NULL; + +	ret = __mv88e6xxx_reg_read(bus, sw_addr, REG_PORT(0), 0x03); +	if (ret >= 0) { +		if ((ret & 0xfff0) == 0x1760) +			return "Marvell 88E6176"; +		if (ret == 0x3521) +			return "Marvell 88E6352 (A0)"; +		if (ret == 0x3522) +			return "Marvell 88E6352 (A1)"; +		if ((ret & 0xfff0) == 0x3520) +			return "Marvell 88E6352"; +	} + +	return NULL; +} + +static int mv88e6352_switch_reset(struct dsa_switch *ds) +{ +	unsigned long timeout; +	int ret; +	int i; + +	/* Set all ports to the disabled state. */ +	for (i = 0; i < 7; i++) { +		ret = REG_READ(REG_PORT(i), 0x04); +		REG_WRITE(REG_PORT(i), 0x04, ret & 0xfffc); +	} + +	/* Wait for transmit queues to drain. */ +	usleep_range(2000, 4000); + +	/* Reset the switch. Keep PPU active (bit 14, undocumented). +	 * The PPU needs to be active to support indirect phy register +	 * accesses through global registers 0x18 and 0x19. +	 */ +	REG_WRITE(REG_GLOBAL, 0x04, 0xc000); + +	/* Wait up to one second for reset to complete. */ +	timeout = jiffies + 1 * HZ; +	while (time_before(jiffies, timeout)) { +		ret = REG_READ(REG_GLOBAL, 0x00); +		if ((ret & 0x8800) == 0x8800) +			break; +		usleep_range(1000, 2000); +	} +	if (time_after(jiffies, timeout)) +		return -ETIMEDOUT; + +	return 0; +} + +static int mv88e6352_setup_global(struct dsa_switch *ds) +{ +	int ret; +	int i; + +	/* Discard packets with excessive collisions, +	 * mask all interrupt sources, enable PPU (bit 14, undocumented). +	 */ +	REG_WRITE(REG_GLOBAL, 0x04, 0x6000); + +	/* Set the default address aging time to 5 minutes, and +	 * enable address learn messages to be sent to all message +	 * ports. +	 */ +	REG_WRITE(REG_GLOBAL, 0x0a, 0x0148); + +	/* Configure the priority mapping registers. */ +	ret = mv88e6xxx_config_prio(ds); +	if (ret < 0) +		return ret; + +	/* Configure the upstream port, and configure the upstream +	 * port as the port to which ingress and egress monitor frames +	 * are to be sent. +	 */ +	REG_WRITE(REG_GLOBAL, 0x1a, (dsa_upstream_port(ds) * 0x1110)); + +	/* Disable remote management for now, and set the switch's +	 * DSA device number. +	 */ +	REG_WRITE(REG_GLOBAL, 0x1c, ds->index & 0x1f); + +	/* Send all frames with destination addresses matching +	 * 01:80:c2:00:00:2x to the CPU port. +	 */ +	REG_WRITE(REG_GLOBAL2, 0x02, 0xffff); + +	/* Send all frames with destination addresses matching +	 * 01:80:c2:00:00:0x to the CPU port. +	 */ +	REG_WRITE(REG_GLOBAL2, 0x03, 0xffff); + +	/* Disable the loopback filter, disable flow control +	 * messages, disable flood broadcast override, disable +	 * removing of provider tags, disable ATU age violation +	 * interrupts, disable tag flow control, force flow +	 * control priority to the highest, and send all special +	 * multicast frames to the CPU at the highest priority. +	 */ +	REG_WRITE(REG_GLOBAL2, 0x05, 0x00ff); + +	/* Program the DSA routing table. */ +	for (i = 0; i < 32; i++) { +		int nexthop = 0x1f; + +		if (i != ds->index && i < ds->dst->pd->nr_chips) +			nexthop = ds->pd->rtable[i] & 0x1f; + +		REG_WRITE(REG_GLOBAL2, 0x06, 0x8000 | (i << 8) | nexthop); +	} + +	/* Clear all trunk masks. */ +	for (i = 0; i < 8; i++) +		REG_WRITE(REG_GLOBAL2, 0x07, 0x8000 | (i << 12) | 0x7f); + +	/* Clear all trunk mappings. */ +	for (i = 0; i < 16; i++) +		REG_WRITE(REG_GLOBAL2, 0x08, 0x8000 | (i << 11)); + +	/* Disable ingress rate limiting by resetting all ingress +	 * rate limit registers to their initial state. +	 */ +	for (i = 0; i < 7; i++) +		REG_WRITE(REG_GLOBAL2, 0x09, 0x9000 | (i << 8)); + +	/* Initialise cross-chip port VLAN table to reset defaults. */ +	REG_WRITE(REG_GLOBAL2, 0x0b, 0x9000); + +	/* Clear the priority override table. */ +	for (i = 0; i < 16; i++) +		REG_WRITE(REG_GLOBAL2, 0x0f, 0x8000 | (i << 8)); + +	/* @@@ initialise AVB (22/23) watchdog (27) sdet (29) registers */ + +	return 0; +} + +static int mv88e6352_setup_port(struct dsa_switch *ds, int p) +{ +	int addr = REG_PORT(p); +	u16 val; + +	/* MAC Forcing register: don't force link, speed, duplex +	 * or flow control state to any particular values on physical +	 * ports, but force the CPU port and all DSA ports to 1000 Mb/s +	 * full duplex. +	 */ +	if (dsa_is_cpu_port(ds, p) || ds->dsa_port_mask & (1 << p)) +		REG_WRITE(addr, 0x01, 0x003e); +	else +		REG_WRITE(addr, 0x01, 0x0003); + +	/* Do not limit the period of time that this port can be +	 * paused for by the remote end or the period of time that +	 * this port can pause the remote end. +	 */ +	REG_WRITE(addr, 0x02, 0x0000); + +	/* Port Control: disable Drop-on-Unlock, disable Drop-on-Lock, +	 * disable Header mode, enable IGMP/MLD snooping, disable VLAN +	 * tunneling, determine priority by looking at 802.1p and IP +	 * priority fields (IP prio has precedence), and set STP state +	 * to Forwarding. +	 * +	 * If this is the CPU link, use DSA or EDSA tagging depending +	 * on which tagging mode was configured. +	 * +	 * If this is a link to another switch, use DSA tagging mode. +	 * +	 * If this is the upstream port for this switch, enable +	 * forwarding of unknown unicasts and multicasts. +	 */ +	val = 0x0433; +	if (dsa_is_cpu_port(ds, p)) { +		if (ds->dst->tag_protocol == DSA_TAG_PROTO_EDSA) +			val |= 0x3300; +		else +			val |= 0x0100; +	} +	if (ds->dsa_port_mask & (1 << p)) +		val |= 0x0100; +	if (p == dsa_upstream_port(ds)) +		val |= 0x000c; +	REG_WRITE(addr, 0x04, val); + +	/* Port Control 1: disable trunking.  Also, if this is the +	 * CPU port, enable learn messages to be sent to this port. +	 */ +	REG_WRITE(addr, 0x05, dsa_is_cpu_port(ds, p) ? 0x8000 : 0x0000); + +	/* Port based VLAN map: give each port its own address +	 * database, allow the CPU port to talk to each of the 'real' +	 * ports, and allow each of the 'real' ports to only talk to +	 * the upstream port. +	 */ +	val = (p & 0xf) << 12; +	if (dsa_is_cpu_port(ds, p)) +		val |= ds->phys_port_mask; +	else +		val |= 1 << dsa_upstream_port(ds); +	REG_WRITE(addr, 0x06, val); + +	/* Default VLAN ID and priority: don't set a default VLAN +	 * ID, and set the default packet priority to zero. +	 */ +	REG_WRITE(addr, 0x07, 0x0000); + +	/* Port Control 2: don't force a good FCS, set the maximum +	 * frame size to 10240 bytes, don't let the switch add or +	 * strip 802.1q tags, don't discard tagged or untagged frames +	 * on this port, do a destination address lookup on all +	 * received packets as usual, disable ARP mirroring and don't +	 * send a copy of all transmitted/received frames on this port +	 * to the CPU. +	 */ +	REG_WRITE(addr, 0x08, 0x2080); + +	/* Egress rate control: disable egress rate control. */ +	REG_WRITE(addr, 0x09, 0x0001); + +	/* Egress rate control 2: disable egress rate control. */ +	REG_WRITE(addr, 0x0a, 0x0000); + +	/* Port Association Vector: when learning source addresses +	 * of packets, add the address to the address database using +	 * a port bitmap that has only the bit for this port set and +	 * the other bits clear. +	 */ +	REG_WRITE(addr, 0x0b, 1 << p); + +	/* Port ATU control: disable limiting the number of address +	 * database entries that this port is allowed to use. +	 */ +	REG_WRITE(addr, 0x0c, 0x0000); + +	/* Priority Override: disable DA, SA and VTU priority override. */ +	REG_WRITE(addr, 0x0d, 0x0000); + +	/* Port Ethertype: use the Ethertype DSA Ethertype value. */ +	REG_WRITE(addr, 0x0f, ETH_P_EDSA); + +	/* Tag Remap: use an identity 802.1p prio -> switch prio +	 * mapping. +	 */ +	REG_WRITE(addr, 0x18, 0x3210); + +	/* Tag Remap 2: use an identity 802.1p prio -> switch prio +	 * mapping. +	 */ +	REG_WRITE(addr, 0x19, 0x7654); + +	return 0; +} + +#ifdef CONFIG_NET_DSA_HWMON + +static int mv88e6352_phy_page_read(struct dsa_switch *ds, +				   int port, int page, int reg) +{ +	struct mv88e6xxx_priv_state *ps = ds_to_priv(ds); +	int ret; + +	mutex_lock(&ps->phy_mutex); +	ret = __mv88e6352_phy_write(ds, port, 0x16, page); +	if (ret < 0) +		goto error; +	ret = __mv88e6352_phy_read(ds, port, reg); +error: +	__mv88e6352_phy_write(ds, port, 0x16, 0x0); +	mutex_unlock(&ps->phy_mutex); +	return ret; +} + +static int mv88e6352_phy_page_write(struct dsa_switch *ds, +				    int port, int page, int reg, int val) +{ +	struct mv88e6xxx_priv_state *ps = ds_to_priv(ds); +	int ret; + +	mutex_lock(&ps->phy_mutex); +	ret = __mv88e6352_phy_write(ds, port, 0x16, page); +	if (ret < 0) +		goto error; + +	ret = __mv88e6352_phy_write(ds, port, reg, val); +error: +	__mv88e6352_phy_write(ds, port, 0x16, 0x0); +	mutex_unlock(&ps->phy_mutex); +	return ret; +} + +static int mv88e6352_get_temp(struct dsa_switch *ds, int *temp) +{ +	int ret; + +	*temp = 0; + +	ret = mv88e6352_phy_page_read(ds, 0, 6, 27); +	if (ret < 0) +		return ret; + +	*temp = (ret & 0xff) - 25; + +	return 0; +} + +static int mv88e6352_get_temp_limit(struct dsa_switch *ds, int *temp) +{ +	int ret; + +	*temp = 0; + +	ret = mv88e6352_phy_page_read(ds, 0, 6, 26); +	if (ret < 0) +		return ret; + +	*temp = (((ret >> 8) & 0x1f) * 5) - 25; + +	return 0; +} + +static int mv88e6352_set_temp_limit(struct dsa_switch *ds, int temp) +{ +	int ret; + +	ret = mv88e6352_phy_page_read(ds, 0, 6, 26); +	if (ret < 0) +		return ret; +	temp = clamp_val(DIV_ROUND_CLOSEST(temp, 5) + 5, 0, 0x1f); +	return mv88e6352_phy_page_write(ds, 0, 6, 26, +					(ret & 0xe0ff) | (temp << 8)); +} + +static int mv88e6352_get_temp_alarm(struct dsa_switch *ds, bool *alarm) +{ +	int ret; + +	*alarm = false; + +	ret = mv88e6352_phy_page_read(ds, 0, 6, 26); +	if (ret < 0) +		return ret; + +	*alarm = !!(ret & 0x40); + +	return 0; +} +#endif /* CONFIG_NET_DSA_HWMON */ + +static int mv88e6352_setup(struct dsa_switch *ds) +{ +	struct mv88e6xxx_priv_state *ps = ds_to_priv(ds); +	int ret; +	int i; + +	mutex_init(&ps->smi_mutex); +	mutex_init(&ps->stats_mutex); +	mutex_init(&ps->phy_mutex); +	mutex_init(&ps->eeprom_mutex); + +	ps->id = REG_READ(REG_PORT(0), 0x03) & 0xfff0; + +	ret = mv88e6352_switch_reset(ds); +	if (ret < 0) +		return ret; + +	/* @@@ initialise vtu and atu */ + +	ret = mv88e6352_setup_global(ds); +	if (ret < 0) +		return ret; + +	for (i = 0; i < 7; i++) { +		ret = mv88e6352_setup_port(ds, i); +		if (ret < 0) +			return ret; +	} + +	return 0; +} + +static int mv88e6352_port_to_phy_addr(int port) +{ +	if (port >= 0 && port <= 4) +		return port; +	return -EINVAL; +} + +static int +mv88e6352_phy_read(struct dsa_switch *ds, int port, int regnum) +{ +	struct mv88e6xxx_priv_state *ps = ds_to_priv(ds); +	int addr = mv88e6352_port_to_phy_addr(port); +	int ret; + +	if (addr < 0) +		return addr; + +	mutex_lock(&ps->phy_mutex); +	ret = __mv88e6352_phy_read(ds, addr, regnum); +	mutex_unlock(&ps->phy_mutex); + +	return ret; +} + +static int +mv88e6352_phy_write(struct dsa_switch *ds, int port, int regnum, u16 val) +{ +	struct mv88e6xxx_priv_state *ps = ds_to_priv(ds); +	int addr = mv88e6352_port_to_phy_addr(port); +	int ret; + +	if (addr < 0) +		return addr; + +	mutex_lock(&ps->phy_mutex); +	ret = __mv88e6352_phy_write(ds, addr, regnum, val); +	mutex_unlock(&ps->phy_mutex); + +	return ret; +} + +static struct mv88e6xxx_hw_stat mv88e6352_hw_stats[] = { +	{ "in_good_octets", 8, 0x00, }, +	{ "in_bad_octets", 4, 0x02, }, +	{ "in_unicast", 4, 0x04, }, +	{ "in_broadcasts", 4, 0x06, }, +	{ "in_multicasts", 4, 0x07, }, +	{ "in_pause", 4, 0x16, }, +	{ "in_undersize", 4, 0x18, }, +	{ "in_fragments", 4, 0x19, }, +	{ "in_oversize", 4, 0x1a, }, +	{ "in_jabber", 4, 0x1b, }, +	{ "in_rx_error", 4, 0x1c, }, +	{ "in_fcs_error", 4, 0x1d, }, +	{ "out_octets", 8, 0x0e, }, +	{ "out_unicast", 4, 0x10, }, +	{ "out_broadcasts", 4, 0x13, }, +	{ "out_multicasts", 4, 0x12, }, +	{ "out_pause", 4, 0x15, }, +	{ "excessive", 4, 0x11, }, +	{ "collisions", 4, 0x1e, }, +	{ "deferred", 4, 0x05, }, +	{ "single", 4, 0x14, }, +	{ "multiple", 4, 0x17, }, +	{ "out_fcs_error", 4, 0x03, }, +	{ "late", 4, 0x1f, }, +	{ "hist_64bytes", 4, 0x08, }, +	{ "hist_65_127bytes", 4, 0x09, }, +	{ "hist_128_255bytes", 4, 0x0a, }, +	{ "hist_256_511bytes", 4, 0x0b, }, +	{ "hist_512_1023bytes", 4, 0x0c, }, +	{ "hist_1024_max_bytes", 4, 0x0d, }, +	{ "sw_in_discards", 4, 0x110, }, +	{ "sw_in_filtered", 2, 0x112, }, +	{ "sw_out_filtered", 2, 0x113, }, +}; + +static int mv88e6352_read_eeprom_word(struct dsa_switch *ds, int addr) +{ +	struct mv88e6xxx_priv_state *ps = ds_to_priv(ds); +	int ret; + +	mutex_lock(&ps->eeprom_mutex); + +	ret = mv88e6xxx_reg_write(ds, REG_GLOBAL2, 0x14, +				  0xc000 | (addr & 0xff)); +	if (ret < 0) +		goto error; + +	ret = mv88e6352_eeprom_busy_wait(ds); +	if (ret < 0) +		goto error; + +	ret = mv88e6xxx_reg_read(ds, REG_GLOBAL2, 0x15); +error: +	mutex_unlock(&ps->eeprom_mutex); +	return ret; +} + +static int mv88e6352_get_eeprom(struct dsa_switch *ds, +				struct ethtool_eeprom *eeprom, u8 *data) +{ +	int offset; +	int len; +	int ret; + +	offset = eeprom->offset; +	len = eeprom->len; +	eeprom->len = 0; + +	eeprom->magic = 0xc3ec4951; + +	ret = mv88e6352_eeprom_load_wait(ds); +	if (ret < 0) +		return ret; + +	if (offset & 1) { +		int word; + +		word = mv88e6352_read_eeprom_word(ds, offset >> 1); +		if (word < 0) +			return word; + +		*data++ = (word >> 8) & 0xff; + +		offset++; +		len--; +		eeprom->len++; +	} + +	while (len >= 2) { +		int word; + +		word = mv88e6352_read_eeprom_word(ds, offset >> 1); +		if (word < 0) +			return word; + +		*data++ = word & 0xff; +		*data++ = (word >> 8) & 0xff; + +		offset += 2; +		len -= 2; +		eeprom->len += 2; +	} + +	if (len) { +		int word; + +		word = mv88e6352_read_eeprom_word(ds, offset >> 1); +		if (word < 0) +			return word; + +		*data++ = word & 0xff; + +		offset++; +		len--; +		eeprom->len++; +	} + +	return 0; +} + +static int mv88e6352_eeprom_is_readonly(struct dsa_switch *ds) +{ +	int ret; + +	ret = mv88e6xxx_reg_read(ds, REG_GLOBAL2, 0x14); +	if (ret < 0) +		return ret; + +	if (!(ret & 0x0400)) +		return -EROFS; + +	return 0; +} + +static int mv88e6352_write_eeprom_word(struct dsa_switch *ds, int addr, +				       u16 data) +{ +	struct mv88e6xxx_priv_state *ps = ds_to_priv(ds); +	int ret; + +	mutex_lock(&ps->eeprom_mutex); + +	ret = mv88e6xxx_reg_write(ds, REG_GLOBAL2, 0x15, data); +	if (ret < 0) +		goto error; + +	ret = mv88e6xxx_reg_write(ds, REG_GLOBAL2, 0x14, +				  0xb000 | (addr & 0xff)); +	if (ret < 0) +		goto error; + +	ret = mv88e6352_eeprom_busy_wait(ds); +error: +	mutex_unlock(&ps->eeprom_mutex); +	return ret; +} + +static int mv88e6352_set_eeprom(struct dsa_switch *ds, +				struct ethtool_eeprom *eeprom, u8 *data) +{ +	int offset; +	int ret; +	int len; + +	if (eeprom->magic != 0xc3ec4951) +		return -EINVAL; + +	ret = mv88e6352_eeprom_is_readonly(ds); +	if (ret) +		return ret; + +	offset = eeprom->offset; +	len = eeprom->len; +	eeprom->len = 0; + +	ret = mv88e6352_eeprom_load_wait(ds); +	if (ret < 0) +		return ret; + +	if (offset & 1) { +		int word; + +		word = mv88e6352_read_eeprom_word(ds, offset >> 1); +		if (word < 0) +			return word; + +		word = (*data++ << 8) | (word & 0xff); + +		ret = mv88e6352_write_eeprom_word(ds, offset >> 1, word); +		if (ret < 0) +			return ret; + +		offset++; +		len--; +		eeprom->len++; +	} + +	while (len >= 2) { +		int word; + +		word = *data++; +		word |= *data++ << 8; + +		ret = mv88e6352_write_eeprom_word(ds, offset >> 1, word); +		if (ret < 0) +			return ret; + +		offset += 2; +		len -= 2; +		eeprom->len += 2; +	} + +	if (len) { +		int word; + +		word = mv88e6352_read_eeprom_word(ds, offset >> 1); +		if (word < 0) +			return word; + +		word = (word & 0xff00) | *data++; + +		ret = mv88e6352_write_eeprom_word(ds, offset >> 1, word); +		if (ret < 0) +			return ret; + +		offset++; +		len--; +		eeprom->len++; +	} + +	return 0; +} + +static void +mv88e6352_get_strings(struct dsa_switch *ds, int port, uint8_t *data) +{ +	mv88e6xxx_get_strings(ds, ARRAY_SIZE(mv88e6352_hw_stats), +			      mv88e6352_hw_stats, port, data); +} + +static void +mv88e6352_get_ethtool_stats(struct dsa_switch *ds, int port, uint64_t *data) +{ +	mv88e6xxx_get_ethtool_stats(ds, ARRAY_SIZE(mv88e6352_hw_stats), +				    mv88e6352_hw_stats, port, data); +} + +static int mv88e6352_get_sset_count(struct dsa_switch *ds) +{ +	return ARRAY_SIZE(mv88e6352_hw_stats); +} + +struct dsa_switch_driver mv88e6352_switch_driver = { +	.tag_protocol		= DSA_TAG_PROTO_EDSA, +	.priv_size		= sizeof(struct mv88e6xxx_priv_state), +	.probe			= mv88e6352_probe, +	.setup			= mv88e6352_setup, +	.set_addr		= mv88e6xxx_set_addr_indirect, +	.phy_read		= mv88e6352_phy_read, +	.phy_write		= mv88e6352_phy_write, +	.poll_link		= mv88e6xxx_poll_link, +	.get_strings		= mv88e6352_get_strings, +	.get_ethtool_stats	= mv88e6352_get_ethtool_stats, +	.get_sset_count		= mv88e6352_get_sset_count, +#ifdef CONFIG_NET_DSA_HWMON +	.get_temp		= mv88e6352_get_temp, +	.get_temp_limit		= mv88e6352_get_temp_limit, +	.set_temp_limit		= mv88e6352_set_temp_limit, +	.get_temp_alarm		= mv88e6352_get_temp_alarm, +#endif +	.get_eeprom		= mv88e6352_get_eeprom, +	.set_eeprom		= mv88e6352_set_eeprom, +	.get_regs_len		= mv88e6xxx_get_regs_len, +	.get_regs		= mv88e6xxx_get_regs, +}; + +MODULE_ALIAS("platform:mv88e6352"); diff --git a/drivers/net/dsa/mv88e6xxx.c b/drivers/net/dsa/mv88e6xxx.c index a6c90cf5634d..cd6807c6b4ed 100644 --- a/drivers/net/dsa/mv88e6xxx.c +++ b/drivers/net/dsa/mv88e6xxx.c @@ -485,20 +485,108 @@ void mv88e6xxx_get_ethtool_stats(struct dsa_switch *ds,  	for (i = 0; i < nr_stats; i++) {  		struct mv88e6xxx_hw_stat *s = stats + i;  		u32 low; -		u32 high; - +		u32 high = 0; + +		if (s->reg >= 0x100) { +			int ret; + +			ret = mv88e6xxx_reg_read(ds, REG_PORT(port), +						 s->reg - 0x100); +			if (ret < 0) +				goto error; +			low = ret; +			if (s->sizeof_stat == 4) { +				ret = mv88e6xxx_reg_read(ds, REG_PORT(port), +							 s->reg - 0x100 + 1); +				if (ret < 0) +					goto error; +				high = ret; +			} +			data[i] = (((u64)high) << 16) | low; +			continue; +		}  		mv88e6xxx_stats_read(ds, s->reg, &low);  		if (s->sizeof_stat == 8)  			mv88e6xxx_stats_read(ds, s->reg + 1, &high); -		else -			high = 0;  		data[i] = (((u64)high) << 32) | low;  	} - +error:  	mutex_unlock(&ps->stats_mutex);  } +int mv88e6xxx_get_regs_len(struct dsa_switch *ds, int port) +{ +	return 32 * sizeof(u16); +} + +void mv88e6xxx_get_regs(struct dsa_switch *ds, int port, +			struct ethtool_regs *regs, void *_p) +{ +	u16 *p = _p; +	int i; + +	regs->version = 0; + +	memset(p, 0xff, 32 * sizeof(u16)); + +	for (i = 0; i < 32; i++) { +		int ret; + +		ret = mv88e6xxx_reg_read(ds, REG_PORT(port), i); +		if (ret >= 0) +			p[i] = ret; +	} +} + +#ifdef CONFIG_NET_DSA_HWMON + +int  mv88e6xxx_get_temp(struct dsa_switch *ds, int *temp) +{ +	struct mv88e6xxx_priv_state *ps = ds_to_priv(ds); +	int ret; +	int val; + +	*temp = 0; + +	mutex_lock(&ps->phy_mutex); + +	ret = mv88e6xxx_phy_write(ds, 0x0, 0x16, 0x6); +	if (ret < 0) +		goto error; + +	/* Enable temperature sensor */ +	ret = mv88e6xxx_phy_read(ds, 0x0, 0x1a); +	if (ret < 0) +		goto error; + +	ret = mv88e6xxx_phy_write(ds, 0x0, 0x1a, ret | (1 << 5)); +	if (ret < 0) +		goto error; + +	/* Wait for temperature to stabilize */ +	usleep_range(10000, 12000); + +	val = mv88e6xxx_phy_read(ds, 0x0, 0x1a); +	if (val < 0) { +		ret = val; +		goto error; +	} + +	/* Disable temperature sensor */ +	ret = mv88e6xxx_phy_write(ds, 0x0, 0x1a, ret & ~(1 << 5)); +	if (ret < 0) +		goto error; + +	*temp = ((val & 0x1f) - 5) * 5; + +error: +	mv88e6xxx_phy_write(ds, 0x0, 0x16, 0x0); +	mutex_unlock(&ps->phy_mutex); +	return ret; +} +#endif /* CONFIG_NET_DSA_HWMON */ +  static int __init mv88e6xxx_init(void)  {  #if IS_ENABLED(CONFIG_NET_DSA_MV88E6131) @@ -507,6 +595,9 @@ static int __init mv88e6xxx_init(void)  #if IS_ENABLED(CONFIG_NET_DSA_MV88E6123_61_65)  	register_switch_driver(&mv88e6123_61_65_switch_driver);  #endif +#if IS_ENABLED(CONFIG_NET_DSA_MV88E6352) +	register_switch_driver(&mv88e6352_switch_driver); +#endif  #if IS_ENABLED(CONFIG_NET_DSA_MV88E6171)  	register_switch_driver(&mv88e6171_switch_driver);  #endif diff --git a/drivers/net/dsa/mv88e6xxx.h b/drivers/net/dsa/mv88e6xxx.h index 5e5145ad9525..03e397efde36 100644 --- a/drivers/net/dsa/mv88e6xxx.h +++ b/drivers/net/dsa/mv88e6xxx.h @@ -37,6 +37,17 @@ struct mv88e6xxx_priv_state {  	 */  	struct mutex	stats_mutex; +	/* This mutex serializes phy access for chips with +	 * indirect phy addressing. It is unused for chips +	 * with direct phy access. +	 */ +	struct mutex	phy_mutex; + +	/* This mutex serializes eeprom access for chips with +	 * eeprom support. +	 */ +	struct mutex eeprom_mutex; +  	int		id; /* switch product id */  }; @@ -67,9 +78,14 @@ void mv88e6xxx_get_strings(struct dsa_switch *ds,  void mv88e6xxx_get_ethtool_stats(struct dsa_switch *ds,  				 int nr_stats, struct mv88e6xxx_hw_stat *stats,  				 int port, uint64_t *data); +int mv88e6xxx_get_regs_len(struct dsa_switch *ds, int port); +void mv88e6xxx_get_regs(struct dsa_switch *ds, int port, +			struct ethtool_regs *regs, void *_p); +int  mv88e6xxx_get_temp(struct dsa_switch *ds, int *temp);  extern struct dsa_switch_driver mv88e6131_switch_driver;  extern struct dsa_switch_driver mv88e6123_61_65_switch_driver; +extern struct dsa_switch_driver mv88e6352_switch_driver;  extern struct dsa_switch_driver mv88e6171_switch_driver;  #define REG_READ(addr, reg)						\  |