diff options
Diffstat (limited to 'drivers/net/dsa')
37 files changed, 3377 insertions, 127 deletions
| diff --git a/drivers/net/dsa/Kconfig b/drivers/net/dsa/Kconfig index 2451f61a38e4..f6a0488589fc 100644 --- a/drivers/net/dsa/Kconfig +++ b/drivers/net/dsa/Kconfig @@ -24,6 +24,8 @@ config NET_DSA_LOOP  	  This enables support for a fake mock-up switch chip which  	  exercises the DSA APIs. +source "drivers/net/dsa/hirschmann/Kconfig" +  config NET_DSA_LANTIQ_GSWIP  	tristate "Lantiq / Intel GSWIP"  	depends on HAS_IOMEM && NET_DSA diff --git a/drivers/net/dsa/Makefile b/drivers/net/dsa/Makefile index 4a943ccc2ca4..a84adb140a04 100644 --- a/drivers/net/dsa/Makefile +++ b/drivers/net/dsa/Makefile @@ -18,6 +18,7 @@ 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				+= hirschmann/  obj-y				+= microchip/  obj-y				+= mv88e6xxx/  obj-y				+= ocelot/ diff --git a/drivers/net/dsa/hirschmann/Kconfig b/drivers/net/dsa/hirschmann/Kconfig new file mode 100644 index 000000000000..222dd35e2c9d --- /dev/null +++ b/drivers/net/dsa/hirschmann/Kconfig @@ -0,0 +1,9 @@ +# SPDX-License-Identifier: GPL-2.0 +config NET_DSA_HIRSCHMANN_HELLCREEK +	tristate "Hirschmann Hellcreek TSN Switch support" +	depends on HAS_IOMEM +	depends on NET_DSA +	depends on PTP_1588_CLOCK +	select NET_DSA_TAG_HELLCREEK +	help +	  This driver adds support for Hirschmann Hellcreek TSN switches. diff --git a/drivers/net/dsa/hirschmann/Makefile b/drivers/net/dsa/hirschmann/Makefile new file mode 100644 index 000000000000..f4075c2998b5 --- /dev/null +++ b/drivers/net/dsa/hirschmann/Makefile @@ -0,0 +1,5 @@ +# SPDX-License-Identifier: GPL-2.0 +obj-$(CONFIG_NET_DSA_HIRSCHMANN_HELLCREEK)	+= hellcreek_sw.o +hellcreek_sw-objs := hellcreek.o +hellcreek_sw-objs += hellcreek_ptp.o +hellcreek_sw-objs += hellcreek_hwtstamp.o diff --git a/drivers/net/dsa/hirschmann/hellcreek.c b/drivers/net/dsa/hirschmann/hellcreek.c new file mode 100644 index 000000000000..6420b76ea37c --- /dev/null +++ b/drivers/net/dsa/hirschmann/hellcreek.c @@ -0,0 +1,1339 @@ +// SPDX-License-Identifier: (GPL-2.0 or MIT) +/* + * DSA driver for: + * Hirschmann Hellcreek TSN switch. + * + * Copyright (C) 2019,2020 Linutronix GmbH + * Author Kurt Kanzenbach <kurt@linutronix.de> + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/device.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/of_mdio.h> +#include <linux/platform_device.h> +#include <linux/bitops.h> +#include <linux/if_bridge.h> +#include <linux/if_vlan.h> +#include <linux/etherdevice.h> +#include <linux/random.h> +#include <linux/iopoll.h> +#include <linux/mutex.h> +#include <linux/delay.h> +#include <net/dsa.h> + +#include "hellcreek.h" +#include "hellcreek_ptp.h" +#include "hellcreek_hwtstamp.h" + +static const struct hellcreek_counter hellcreek_counter[] = { +	{ 0x00, "RxFiltered", }, +	{ 0x01, "RxOctets1k", }, +	{ 0x02, "RxVTAG", }, +	{ 0x03, "RxL2BAD", }, +	{ 0x04, "RxOverloadDrop", }, +	{ 0x05, "RxUC", }, +	{ 0x06, "RxMC", }, +	{ 0x07, "RxBC", }, +	{ 0x08, "RxRS<64", }, +	{ 0x09, "RxRS64", }, +	{ 0x0a, "RxRS65_127", }, +	{ 0x0b, "RxRS128_255", }, +	{ 0x0c, "RxRS256_511", }, +	{ 0x0d, "RxRS512_1023", }, +	{ 0x0e, "RxRS1024_1518", }, +	{ 0x0f, "RxRS>1518", }, +	{ 0x10, "TxTailDropQueue0", }, +	{ 0x11, "TxTailDropQueue1", }, +	{ 0x12, "TxTailDropQueue2", }, +	{ 0x13, "TxTailDropQueue3", }, +	{ 0x14, "TxTailDropQueue4", }, +	{ 0x15, "TxTailDropQueue5", }, +	{ 0x16, "TxTailDropQueue6", }, +	{ 0x17, "TxTailDropQueue7", }, +	{ 0x18, "RxTrafficClass0", }, +	{ 0x19, "RxTrafficClass1", }, +	{ 0x1a, "RxTrafficClass2", }, +	{ 0x1b, "RxTrafficClass3", }, +	{ 0x1c, "RxTrafficClass4", }, +	{ 0x1d, "RxTrafficClass5", }, +	{ 0x1e, "RxTrafficClass6", }, +	{ 0x1f, "RxTrafficClass7", }, +	{ 0x21, "TxOctets1k", }, +	{ 0x22, "TxVTAG", }, +	{ 0x23, "TxL2BAD", }, +	{ 0x25, "TxUC", }, +	{ 0x26, "TxMC", }, +	{ 0x27, "TxBC", }, +	{ 0x28, "TxTS<64", }, +	{ 0x29, "TxTS64", }, +	{ 0x2a, "TxTS65_127", }, +	{ 0x2b, "TxTS128_255", }, +	{ 0x2c, "TxTS256_511", }, +	{ 0x2d, "TxTS512_1023", }, +	{ 0x2e, "TxTS1024_1518", }, +	{ 0x2f, "TxTS>1518", }, +	{ 0x30, "TxTrafficClassOverrun0", }, +	{ 0x31, "TxTrafficClassOverrun1", }, +	{ 0x32, "TxTrafficClassOverrun2", }, +	{ 0x33, "TxTrafficClassOverrun3", }, +	{ 0x34, "TxTrafficClassOverrun4", }, +	{ 0x35, "TxTrafficClassOverrun5", }, +	{ 0x36, "TxTrafficClassOverrun6", }, +	{ 0x37, "TxTrafficClassOverrun7", }, +	{ 0x38, "TxTrafficClass0", }, +	{ 0x39, "TxTrafficClass1", }, +	{ 0x3a, "TxTrafficClass2", }, +	{ 0x3b, "TxTrafficClass3", }, +	{ 0x3c, "TxTrafficClass4", }, +	{ 0x3d, "TxTrafficClass5", }, +	{ 0x3e, "TxTrafficClass6", }, +	{ 0x3f, "TxTrafficClass7", }, +}; + +static u16 hellcreek_read(struct hellcreek *hellcreek, unsigned int offset) +{ +	return readw(hellcreek->base + offset); +} + +static u16 hellcreek_read_ctrl(struct hellcreek *hellcreek) +{ +	return readw(hellcreek->base + HR_CTRL_C); +} + +static u16 hellcreek_read_stat(struct hellcreek *hellcreek) +{ +	return readw(hellcreek->base + HR_SWSTAT); +} + +static void hellcreek_write(struct hellcreek *hellcreek, u16 data, +			    unsigned int offset) +{ +	writew(data, hellcreek->base + offset); +} + +static void hellcreek_select_port(struct hellcreek *hellcreek, int port) +{ +	u16 val = port << HR_PSEL_PTWSEL_SHIFT; + +	hellcreek_write(hellcreek, val, HR_PSEL); +} + +static void hellcreek_select_prio(struct hellcreek *hellcreek, int prio) +{ +	u16 val = prio << HR_PSEL_PRTCWSEL_SHIFT; + +	hellcreek_write(hellcreek, val, HR_PSEL); +} + +static void hellcreek_select_counter(struct hellcreek *hellcreek, int counter) +{ +	u16 val = counter << HR_CSEL_SHIFT; + +	hellcreek_write(hellcreek, val, HR_CSEL); + +	/* Data sheet states to wait at least 20 internal clock cycles */ +	ndelay(200); +} + +static void hellcreek_select_vlan(struct hellcreek *hellcreek, int vid, +				  bool pvid) +{ +	u16 val = 0; + +	/* Set pvid bit first */ +	if (pvid) +		val |= HR_VIDCFG_PVID; +	hellcreek_write(hellcreek, val, HR_VIDCFG); + +	/* Set vlan */ +	val |= vid << HR_VIDCFG_VID_SHIFT; +	hellcreek_write(hellcreek, val, HR_VIDCFG); +} + +static int hellcreek_wait_until_ready(struct hellcreek *hellcreek) +{ +	u16 val; + +	/* Wait up to 1ms, although 3 us should be enough */ +	return readx_poll_timeout(hellcreek_read_ctrl, hellcreek, +				  val, val & HR_CTRL_C_READY, +				  3, 1000); +} + +static int hellcreek_wait_until_transitioned(struct hellcreek *hellcreek) +{ +	u16 val; + +	return readx_poll_timeout_atomic(hellcreek_read_ctrl, hellcreek, +					 val, !(val & HR_CTRL_C_TRANSITION), +					 1, 1000); +} + +static int hellcreek_wait_fdb_ready(struct hellcreek *hellcreek) +{ +	u16 val; + +	return readx_poll_timeout_atomic(hellcreek_read_stat, hellcreek, +					 val, !(val & HR_SWSTAT_BUSY), +					 1, 1000); +} + +static int hellcreek_detect(struct hellcreek *hellcreek) +{ +	u16 id, rel_low, rel_high, date_low, date_high, tgd_ver; +	u8 tgd_maj, tgd_min; +	u32 rel, date; + +	id	  = hellcreek_read(hellcreek, HR_MODID_C); +	rel_low	  = hellcreek_read(hellcreek, HR_REL_L_C); +	rel_high  = hellcreek_read(hellcreek, HR_REL_H_C); +	date_low  = hellcreek_read(hellcreek, HR_BLD_L_C); +	date_high = hellcreek_read(hellcreek, HR_BLD_H_C); +	tgd_ver   = hellcreek_read(hellcreek, TR_TGDVER); + +	if (id != hellcreek->pdata->module_id) +		return -ENODEV; + +	rel	= rel_low | (rel_high << 16); +	date	= date_low | (date_high << 16); +	tgd_maj = (tgd_ver & TR_TGDVER_REV_MAJ_MASK) >> TR_TGDVER_REV_MAJ_SHIFT; +	tgd_min = (tgd_ver & TR_TGDVER_REV_MIN_MASK) >> TR_TGDVER_REV_MIN_SHIFT; + +	dev_info(hellcreek->dev, "Module ID=%02x Release=%04x Date=%04x TGD Version=%02x.%02x\n", +		 id, rel, date, tgd_maj, tgd_min); + +	return 0; +} + +static void hellcreek_feature_detect(struct hellcreek *hellcreek) +{ +	u16 features; + +	features = hellcreek_read(hellcreek, HR_FEABITS0); + +	/* Currently we only detect the size of the FDB table */ +	hellcreek->fdb_entries = ((features & HR_FEABITS0_FDBBINS_MASK) >> +			       HR_FEABITS0_FDBBINS_SHIFT) * 32; + +	dev_info(hellcreek->dev, "Feature detect: FDB entries=%zu\n", +		 hellcreek->fdb_entries); +} + +static enum dsa_tag_protocol hellcreek_get_tag_protocol(struct dsa_switch *ds, +							int port, +							enum dsa_tag_protocol mp) +{ +	return DSA_TAG_PROTO_HELLCREEK; +} + +static int hellcreek_port_enable(struct dsa_switch *ds, int port, +				 struct phy_device *phy) +{ +	struct hellcreek *hellcreek = ds->priv; +	struct hellcreek_port *hellcreek_port; +	u16 val; + +	hellcreek_port = &hellcreek->ports[port]; + +	dev_dbg(hellcreek->dev, "Enable port %d\n", port); + +	mutex_lock(&hellcreek->reg_lock); + +	hellcreek_select_port(hellcreek, port); +	val = hellcreek_port->ptcfg; +	val |= HR_PTCFG_ADMIN_EN; +	hellcreek_write(hellcreek, val, HR_PTCFG); +	hellcreek_port->ptcfg = val; + +	mutex_unlock(&hellcreek->reg_lock); + +	return 0; +} + +static void hellcreek_port_disable(struct dsa_switch *ds, int port) +{ +	struct hellcreek *hellcreek = ds->priv; +	struct hellcreek_port *hellcreek_port; +	u16 val; + +	hellcreek_port = &hellcreek->ports[port]; + +	dev_dbg(hellcreek->dev, "Disable port %d\n", port); + +	mutex_lock(&hellcreek->reg_lock); + +	hellcreek_select_port(hellcreek, port); +	val = hellcreek_port->ptcfg; +	val &= ~HR_PTCFG_ADMIN_EN; +	hellcreek_write(hellcreek, val, HR_PTCFG); +	hellcreek_port->ptcfg = val; + +	mutex_unlock(&hellcreek->reg_lock); +} + +static void hellcreek_get_strings(struct dsa_switch *ds, int port, +				  u32 stringset, uint8_t *data) +{ +	int i; + +	for (i = 0; i < ARRAY_SIZE(hellcreek_counter); ++i) { +		const struct hellcreek_counter *counter = &hellcreek_counter[i]; + +		strlcpy(data + i * ETH_GSTRING_LEN, +			counter->name, ETH_GSTRING_LEN); +	} +} + +static int hellcreek_get_sset_count(struct dsa_switch *ds, int port, int sset) +{ +	if (sset != ETH_SS_STATS) +		return 0; + +	return ARRAY_SIZE(hellcreek_counter); +} + +static void hellcreek_get_ethtool_stats(struct dsa_switch *ds, int port, +					uint64_t *data) +{ +	struct hellcreek *hellcreek = ds->priv; +	struct hellcreek_port *hellcreek_port; +	int i; + +	hellcreek_port = &hellcreek->ports[port]; + +	for (i = 0; i < ARRAY_SIZE(hellcreek_counter); ++i) { +		const struct hellcreek_counter *counter = &hellcreek_counter[i]; +		u8 offset = counter->offset + port * 64; +		u16 high, low; +		u64 value; + +		mutex_lock(&hellcreek->reg_lock); + +		hellcreek_select_counter(hellcreek, offset); + +		/* The registers are locked internally by selecting the +		 * counter. So low and high can be read without reading high +		 * again. +		 */ +		high  = hellcreek_read(hellcreek, HR_CRDH); +		low   = hellcreek_read(hellcreek, HR_CRDL); +		value = ((u64)high << 16) | low; + +		hellcreek_port->counter_values[i] += value; +		data[i] = hellcreek_port->counter_values[i]; + +		mutex_unlock(&hellcreek->reg_lock); +	} +} + +static u16 hellcreek_private_vid(int port) +{ +	return VLAN_N_VID - port + 1; +} + +static int hellcreek_vlan_prepare(struct dsa_switch *ds, int port, +				  const struct switchdev_obj_port_vlan *vlan) +{ +	struct hellcreek *hellcreek = ds->priv; +	int i; + +	dev_dbg(hellcreek->dev, "VLAN prepare for port %d\n", port); + +	/* Restriction: Make sure that nobody uses the "private" VLANs. These +	 * VLANs are internally used by the driver to ensure port +	 * separation. Thus, they cannot be used by someone else. +	 */ +	for (i = 0; i < hellcreek->pdata->num_ports; ++i) { +		const u16 restricted_vid = hellcreek_private_vid(i); +		u16 vid; + +		if (!dsa_is_user_port(ds, i)) +			continue; + +		for (vid = vlan->vid_begin; vid <= vlan->vid_end; ++vid) +			if (vid == restricted_vid) +				return -EBUSY; +	} + +	return 0; +} + +static void hellcreek_select_vlan_params(struct hellcreek *hellcreek, int port, +					 int *shift, int *mask) +{ +	switch (port) { +	case 0: +		*shift = HR_VIDMBRCFG_P0MBR_SHIFT; +		*mask  = HR_VIDMBRCFG_P0MBR_MASK; +		break; +	case 1: +		*shift = HR_VIDMBRCFG_P1MBR_SHIFT; +		*mask  = HR_VIDMBRCFG_P1MBR_MASK; +		break; +	case 2: +		*shift = HR_VIDMBRCFG_P2MBR_SHIFT; +		*mask  = HR_VIDMBRCFG_P2MBR_MASK; +		break; +	case 3: +		*shift = HR_VIDMBRCFG_P3MBR_SHIFT; +		*mask  = HR_VIDMBRCFG_P3MBR_MASK; +		break; +	default: +		*shift = *mask = 0; +		dev_err(hellcreek->dev, "Unknown port %d selected!\n", port); +	} +} + +static void hellcreek_apply_vlan(struct hellcreek *hellcreek, int port, u16 vid, +				 bool pvid, bool untagged) +{ +	int shift, mask; +	u16 val; + +	dev_dbg(hellcreek->dev, "Apply VLAN: port=%d vid=%u pvid=%d untagged=%d", +		port, vid, pvid, untagged); + +	mutex_lock(&hellcreek->reg_lock); + +	hellcreek_select_port(hellcreek, port); +	hellcreek_select_vlan(hellcreek, vid, pvid); + +	/* Setup port vlan membership */ +	hellcreek_select_vlan_params(hellcreek, port, &shift, &mask); +	val = hellcreek->vidmbrcfg[vid]; +	val &= ~mask; +	if (untagged) +		val |= HELLCREEK_VLAN_UNTAGGED_MEMBER << shift; +	else +		val |= HELLCREEK_VLAN_TAGGED_MEMBER << shift; + +	hellcreek_write(hellcreek, val, HR_VIDMBRCFG); +	hellcreek->vidmbrcfg[vid] = val; + +	mutex_unlock(&hellcreek->reg_lock); +} + +static void hellcreek_unapply_vlan(struct hellcreek *hellcreek, int port, +				   u16 vid) +{ +	int shift, mask; +	u16 val; + +	dev_dbg(hellcreek->dev, "Unapply VLAN: port=%d vid=%u\n", port, vid); + +	mutex_lock(&hellcreek->reg_lock); + +	hellcreek_select_vlan(hellcreek, vid, 0); + +	/* Setup port vlan membership */ +	hellcreek_select_vlan_params(hellcreek, port, &shift, &mask); +	val = hellcreek->vidmbrcfg[vid]; +	val &= ~mask; +	val |= HELLCREEK_VLAN_NO_MEMBER << shift; + +	hellcreek_write(hellcreek, val, HR_VIDMBRCFG); +	hellcreek->vidmbrcfg[vid] = val; + +	mutex_unlock(&hellcreek->reg_lock); +} + +static void hellcreek_vlan_add(struct dsa_switch *ds, int port, +			       const struct switchdev_obj_port_vlan *vlan) +{ +	bool untagged = vlan->flags & BRIDGE_VLAN_INFO_UNTAGGED; +	bool pvid = vlan->flags & BRIDGE_VLAN_INFO_PVID; +	struct hellcreek *hellcreek = ds->priv; +	u16 vid; + +	dev_dbg(hellcreek->dev, "Add VLANs (%d -- %d) on port %d, %s, %s\n", +		vlan->vid_begin, vlan->vid_end, port, +		untagged ? "untagged" : "tagged", +		pvid ? "PVID" : "no PVID"); + +	for (vid = vlan->vid_begin; vid <= vlan->vid_end; ++vid) +		hellcreek_apply_vlan(hellcreek, port, vid, pvid, untagged); +} + +static int hellcreek_vlan_del(struct dsa_switch *ds, int port, +			      const struct switchdev_obj_port_vlan *vlan) +{ +	struct hellcreek *hellcreek = ds->priv; +	u16 vid; + +	dev_dbg(hellcreek->dev, "Remove VLANs (%d -- %d) on port %d\n", +		vlan->vid_begin, vlan->vid_end, port); + +	for (vid = vlan->vid_begin; vid <= vlan->vid_end; ++vid) +		hellcreek_unapply_vlan(hellcreek, port, vid); + +	return 0; +} + +static void hellcreek_port_stp_state_set(struct dsa_switch *ds, int port, +					 u8 state) +{ +	struct hellcreek *hellcreek = ds->priv; +	struct hellcreek_port *hellcreek_port; +	const char *new_state; +	u16 val; + +	mutex_lock(&hellcreek->reg_lock); + +	hellcreek_port = &hellcreek->ports[port]; +	val = hellcreek_port->ptcfg; + +	switch (state) { +	case BR_STATE_DISABLED: +		new_state = "DISABLED"; +		val |= HR_PTCFG_BLOCKED; +		val &= ~HR_PTCFG_LEARNING_EN; +		break; +	case BR_STATE_BLOCKING: +		new_state = "BLOCKING"; +		val |= HR_PTCFG_BLOCKED; +		val &= ~HR_PTCFG_LEARNING_EN; +		break; +	case BR_STATE_LISTENING: +		new_state = "LISTENING"; +		val |= HR_PTCFG_BLOCKED; +		val &= ~HR_PTCFG_LEARNING_EN; +		break; +	case BR_STATE_LEARNING: +		new_state = "LEARNING"; +		val |= HR_PTCFG_BLOCKED; +		val |= HR_PTCFG_LEARNING_EN; +		break; +	case BR_STATE_FORWARDING: +		new_state = "FORWARDING"; +		val &= ~HR_PTCFG_BLOCKED; +		val |= HR_PTCFG_LEARNING_EN; +		break; +	default: +		new_state = "UNKNOWN"; +	} + +	hellcreek_select_port(hellcreek, port); +	hellcreek_write(hellcreek, val, HR_PTCFG); +	hellcreek_port->ptcfg = val; + +	mutex_unlock(&hellcreek->reg_lock); + +	dev_dbg(hellcreek->dev, "Configured STP state for port %d: %s\n", +		port, new_state); +} + +static void hellcreek_setup_ingressflt(struct hellcreek *hellcreek, int port, +				       bool enable) +{ +	struct hellcreek_port *hellcreek_port = &hellcreek->ports[port]; +	u16 ptcfg; + +	mutex_lock(&hellcreek->reg_lock); + +	ptcfg = hellcreek_port->ptcfg; + +	if (enable) +		ptcfg |= HR_PTCFG_INGRESSFLT; +	else +		ptcfg &= ~HR_PTCFG_INGRESSFLT; + +	hellcreek_select_port(hellcreek, port); +	hellcreek_write(hellcreek, ptcfg, HR_PTCFG); +	hellcreek_port->ptcfg = ptcfg; + +	mutex_unlock(&hellcreek->reg_lock); +} + +static void hellcreek_setup_vlan_awareness(struct hellcreek *hellcreek, +					   bool enable) +{ +	u16 swcfg; + +	mutex_lock(&hellcreek->reg_lock); + +	swcfg = hellcreek->swcfg; + +	if (enable) +		swcfg |= HR_SWCFG_VLAN_UNAWARE; +	else +		swcfg &= ~HR_SWCFG_VLAN_UNAWARE; + +	hellcreek_write(hellcreek, swcfg, HR_SWCFG); + +	mutex_unlock(&hellcreek->reg_lock); +} + +/* Default setup for DSA: VLAN <X>: CPU and Port <X> egress untagged. */ +static void hellcreek_setup_vlan_membership(struct dsa_switch *ds, int port, +					    bool enabled) +{ +	const u16 vid = hellcreek_private_vid(port); +	int upstream = dsa_upstream_port(ds, port); +	struct hellcreek *hellcreek = ds->priv; + +	/* Apply vid to port as egress untagged and port vlan id */ +	if (enabled) +		hellcreek_apply_vlan(hellcreek, port, vid, true, true); +	else +		hellcreek_unapply_vlan(hellcreek, port, vid); + +	/* Apply vid to cpu port as well */ +	if (enabled) +		hellcreek_apply_vlan(hellcreek, upstream, vid, false, true); +	else +		hellcreek_unapply_vlan(hellcreek, upstream, vid); +} + +static int hellcreek_port_bridge_join(struct dsa_switch *ds, int port, +				      struct net_device *br) +{ +	struct hellcreek *hellcreek = ds->priv; + +	dev_dbg(hellcreek->dev, "Port %d joins a bridge\n", port); + +	/* When joining a vlan_filtering bridge, keep the switch VLAN aware */ +	if (!ds->vlan_filtering) +		hellcreek_setup_vlan_awareness(hellcreek, false); + +	/* Drop private vlans */ +	hellcreek_setup_vlan_membership(ds, port, false); + +	return 0; +} + +static void hellcreek_port_bridge_leave(struct dsa_switch *ds, int port, +					struct net_device *br) +{ +	struct hellcreek *hellcreek = ds->priv; + +	dev_dbg(hellcreek->dev, "Port %d leaves a bridge\n", port); + +	/* Enable VLAN awareness */ +	hellcreek_setup_vlan_awareness(hellcreek, true); + +	/* Enable private vlans */ +	hellcreek_setup_vlan_membership(ds, port, true); +} + +static int __hellcreek_fdb_add(struct hellcreek *hellcreek, +			       const struct hellcreek_fdb_entry *entry) +{ +	u16 meta = 0; + +	dev_dbg(hellcreek->dev, "Add static FDB entry: MAC=%pM, MASK=0x%02x, " +		"OBT=%d, REPRIO_EN=%d, PRIO=%d\n", entry->mac, entry->portmask, +		entry->is_obt, entry->reprio_en, entry->reprio_tc); + +	/* Add mac address */ +	hellcreek_write(hellcreek, entry->mac[1] | (entry->mac[0] << 8), HR_FDBWDH); +	hellcreek_write(hellcreek, entry->mac[3] | (entry->mac[2] << 8), HR_FDBWDM); +	hellcreek_write(hellcreek, entry->mac[5] | (entry->mac[4] << 8), HR_FDBWDL); + +	/* Meta data */ +	meta |= entry->portmask << HR_FDBWRM0_PORTMASK_SHIFT; +	if (entry->is_obt) +		meta |= HR_FDBWRM0_OBT; +	if (entry->reprio_en) { +		meta |= HR_FDBWRM0_REPRIO_EN; +		meta |= entry->reprio_tc << HR_FDBWRM0_REPRIO_TC_SHIFT; +	} +	hellcreek_write(hellcreek, meta, HR_FDBWRM0); + +	/* Commit */ +	hellcreek_write(hellcreek, 0x00, HR_FDBWRCMD); + +	/* Wait until done */ +	return hellcreek_wait_fdb_ready(hellcreek); +} + +static int __hellcreek_fdb_del(struct hellcreek *hellcreek, +			       const struct hellcreek_fdb_entry *entry) +{ +	dev_dbg(hellcreek->dev, "Delete FDB entry: MAC=%pM!\n", entry->mac); + +	/* Delete by matching idx */ +	hellcreek_write(hellcreek, entry->idx | HR_FDBWRCMD_FDBDEL, HR_FDBWRCMD); + +	/* Wait until done */ +	return hellcreek_wait_fdb_ready(hellcreek); +} + +/* Retrieve the index of a FDB entry by mac address. Currently we search through + * the complete table in hardware. If that's too slow, we might have to cache + * the complete FDB table in software. + */ +static int hellcreek_fdb_get(struct hellcreek *hellcreek, +			     const unsigned char *dest, +			     struct hellcreek_fdb_entry *entry) +{ +	size_t i; + +	/* Set read pointer to zero: The read of HR_FDBMAX (read-only register) +	 * should reset the internal pointer. But, that doesn't work. The vendor +	 * suggested a subsequent write as workaround. Same for HR_FDBRDH below. +	 */ +	hellcreek_read(hellcreek, HR_FDBMAX); +	hellcreek_write(hellcreek, 0x00, HR_FDBMAX); + +	/* We have to read the complete table, because the switch/driver might +	 * enter new entries anywhere. +	 */ +	for (i = 0; i < hellcreek->fdb_entries; ++i) { +		unsigned char addr[ETH_ALEN]; +		u16 meta, mac; + +		meta	= hellcreek_read(hellcreek, HR_FDBMDRD); +		mac	= hellcreek_read(hellcreek, HR_FDBRDL); +		addr[5] = mac & 0xff; +		addr[4] = (mac & 0xff00) >> 8; +		mac	= hellcreek_read(hellcreek, HR_FDBRDM); +		addr[3] = mac & 0xff; +		addr[2] = (mac & 0xff00) >> 8; +		mac	= hellcreek_read(hellcreek, HR_FDBRDH); +		addr[1] = mac & 0xff; +		addr[0] = (mac & 0xff00) >> 8; + +		/* Force next entry */ +		hellcreek_write(hellcreek, 0x00, HR_FDBRDH); + +		if (memcmp(addr, dest, ETH_ALEN)) +			continue; + +		/* Match found */ +		entry->idx	    = i; +		entry->portmask	    = (meta & HR_FDBMDRD_PORTMASK_MASK) >> +			HR_FDBMDRD_PORTMASK_SHIFT; +		entry->age	    = (meta & HR_FDBMDRD_AGE_MASK) >> +			HR_FDBMDRD_AGE_SHIFT; +		entry->is_obt	    = !!(meta & HR_FDBMDRD_OBT); +		entry->pass_blocked = !!(meta & HR_FDBMDRD_PASS_BLOCKED); +		entry->is_static    = !!(meta & HR_FDBMDRD_STATIC); +		entry->reprio_tc    = (meta & HR_FDBMDRD_REPRIO_TC_MASK) >> +			HR_FDBMDRD_REPRIO_TC_SHIFT; +		entry->reprio_en    = !!(meta & HR_FDBMDRD_REPRIO_EN); +		memcpy(entry->mac, addr, sizeof(addr)); + +		return 0; +	} + +	return -ENOENT; +} + +static int hellcreek_fdb_add(struct dsa_switch *ds, int port, +			     const unsigned char *addr, u16 vid) +{ +	struct hellcreek_fdb_entry entry = { 0 }; +	struct hellcreek *hellcreek = ds->priv; +	int ret; + +	dev_dbg(hellcreek->dev, "Add FDB entry for MAC=%pM\n", addr); + +	mutex_lock(&hellcreek->reg_lock); + +	ret = hellcreek_fdb_get(hellcreek, addr, &entry); +	if (ret) { +		/* Not found */ +		memcpy(entry.mac, addr, sizeof(entry.mac)); +		entry.portmask = BIT(port); + +		ret = __hellcreek_fdb_add(hellcreek, &entry); +		if (ret) { +			dev_err(hellcreek->dev, "Failed to add FDB entry!\n"); +			goto out; +		} +	} else { +		/* Found */ +		ret = __hellcreek_fdb_del(hellcreek, &entry); +		if (ret) { +			dev_err(hellcreek->dev, "Failed to delete FDB entry!\n"); +			goto out; +		} + +		entry.portmask |= BIT(port); + +		ret = __hellcreek_fdb_add(hellcreek, &entry); +		if (ret) { +			dev_err(hellcreek->dev, "Failed to add FDB entry!\n"); +			goto out; +		} +	} + +out: +	mutex_unlock(&hellcreek->reg_lock); + +	return ret; +} + +static int hellcreek_fdb_del(struct dsa_switch *ds, int port, +			     const unsigned char *addr, u16 vid) +{ +	struct hellcreek_fdb_entry entry = { 0 }; +	struct hellcreek *hellcreek = ds->priv; +	int ret; + +	dev_dbg(hellcreek->dev, "Delete FDB entry for MAC=%pM\n", addr); + +	mutex_lock(&hellcreek->reg_lock); + +	ret = hellcreek_fdb_get(hellcreek, addr, &entry); +	if (ret) { +		/* Not found */ +		dev_err(hellcreek->dev, "FDB entry for deletion not found!\n"); +	} else { +		/* Found */ +		ret = __hellcreek_fdb_del(hellcreek, &entry); +		if (ret) { +			dev_err(hellcreek->dev, "Failed to delete FDB entry!\n"); +			goto out; +		} + +		entry.portmask &= ~BIT(port); + +		if (entry.portmask != 0x00) { +			ret = __hellcreek_fdb_add(hellcreek, &entry); +			if (ret) { +				dev_err(hellcreek->dev, "Failed to add FDB entry!\n"); +				goto out; +			} +		} +	} + +out: +	mutex_unlock(&hellcreek->reg_lock); + +	return ret; +} + +static int hellcreek_fdb_dump(struct dsa_switch *ds, int port, +			      dsa_fdb_dump_cb_t *cb, void *data) +{ +	struct hellcreek *hellcreek = ds->priv; +	u16 entries; +	size_t i; + +	mutex_lock(&hellcreek->reg_lock); + +	/* Set read pointer to zero: The read of HR_FDBMAX (read-only register) +	 * should reset the internal pointer. But, that doesn't work. The vendor +	 * suggested a subsequent write as workaround. Same for HR_FDBRDH below. +	 */ +	entries = hellcreek_read(hellcreek, HR_FDBMAX); +	hellcreek_write(hellcreek, 0x00, HR_FDBMAX); + +	dev_dbg(hellcreek->dev, "FDB dump for port %d, entries=%d!\n", port, entries); + +	/* Read table */ +	for (i = 0; i < hellcreek->fdb_entries; ++i) { +		unsigned char null_addr[ETH_ALEN] = { 0 }; +		struct hellcreek_fdb_entry entry = { 0 }; +		u16 meta, mac; + +		meta	= hellcreek_read(hellcreek, HR_FDBMDRD); +		mac	= hellcreek_read(hellcreek, HR_FDBRDL); +		entry.mac[5] = mac & 0xff; +		entry.mac[4] = (mac & 0xff00) >> 8; +		mac	= hellcreek_read(hellcreek, HR_FDBRDM); +		entry.mac[3] = mac & 0xff; +		entry.mac[2] = (mac & 0xff00) >> 8; +		mac	= hellcreek_read(hellcreek, HR_FDBRDH); +		entry.mac[1] = mac & 0xff; +		entry.mac[0] = (mac & 0xff00) >> 8; + +		/* Force next entry */ +		hellcreek_write(hellcreek, 0x00, HR_FDBRDH); + +		/* Check valid */ +		if (!memcmp(entry.mac, null_addr, ETH_ALEN)) +			continue; + +		entry.portmask	= (meta & HR_FDBMDRD_PORTMASK_MASK) >> +			HR_FDBMDRD_PORTMASK_SHIFT; +		entry.is_static	= !!(meta & HR_FDBMDRD_STATIC); + +		/* Check port mask */ +		if (!(entry.portmask & BIT(port))) +			continue; + +		cb(entry.mac, 0, entry.is_static, data); +	} + +	mutex_unlock(&hellcreek->reg_lock); + +	return 0; +} + +static int hellcreek_vlan_filtering(struct dsa_switch *ds, int port, +				    bool vlan_filtering, +				    struct switchdev_trans *trans) +{ +	struct hellcreek *hellcreek = ds->priv; + +	if (switchdev_trans_ph_prepare(trans)) +		return 0; + +	dev_dbg(hellcreek->dev, "%s VLAN filtering on port %d\n", +		vlan_filtering ? "Enable" : "Disable", port); + +	/* Configure port to drop packages with not known vids */ +	hellcreek_setup_ingressflt(hellcreek, port, vlan_filtering); + +	/* Enable VLAN awareness on the switch. This save due to +	 * ds->vlan_filtering_is_global. +	 */ +	hellcreek_setup_vlan_awareness(hellcreek, vlan_filtering); + +	return 0; +} + +static int hellcreek_enable_ip_core(struct hellcreek *hellcreek) +{ +	int ret; +	u16 val; + +	mutex_lock(&hellcreek->reg_lock); + +	val = hellcreek_read(hellcreek, HR_CTRL_C); +	val |= HR_CTRL_C_ENABLE; +	hellcreek_write(hellcreek, val, HR_CTRL_C); +	ret = hellcreek_wait_until_transitioned(hellcreek); + +	mutex_unlock(&hellcreek->reg_lock); + +	return ret; +} + +static void hellcreek_setup_cpu_and_tunnel_port(struct hellcreek *hellcreek) +{ +	struct hellcreek_port *tunnel_port = &hellcreek->ports[TUNNEL_PORT]; +	struct hellcreek_port *cpu_port = &hellcreek->ports[CPU_PORT]; +	u16 ptcfg = 0; + +	ptcfg |= HR_PTCFG_LEARNING_EN | HR_PTCFG_ADMIN_EN; + +	mutex_lock(&hellcreek->reg_lock); + +	hellcreek_select_port(hellcreek, CPU_PORT); +	hellcreek_write(hellcreek, ptcfg, HR_PTCFG); + +	hellcreek_select_port(hellcreek, TUNNEL_PORT); +	hellcreek_write(hellcreek, ptcfg, HR_PTCFG); + +	cpu_port->ptcfg	   = ptcfg; +	tunnel_port->ptcfg = ptcfg; + +	mutex_unlock(&hellcreek->reg_lock); +} + +static void hellcreek_setup_tc_identity_mapping(struct hellcreek *hellcreek) +{ +	int i; + +	/* The switch has multiple egress queues per port. The queue is selected +	 * via the PCP field in the VLAN header. The switch internally deals +	 * with traffic classes instead of PCP values and this mapping is +	 * configurable. +	 * +	 * The default mapping is (PCP - TC): +	 *  7 - 7 +	 *  6 - 6 +	 *  5 - 5 +	 *  4 - 4 +	 *  3 - 3 +	 *  2 - 1 +	 *  1 - 0 +	 *  0 - 2 +	 * +	 * The default should be an identity mapping. +	 */ + +	for (i = 0; i < 8; ++i) { +		mutex_lock(&hellcreek->reg_lock); + +		hellcreek_select_prio(hellcreek, i); +		hellcreek_write(hellcreek, +				i << HR_PRTCCFG_PCP_TC_MAP_SHIFT, +				HR_PRTCCFG); + +		mutex_unlock(&hellcreek->reg_lock); +	} +} + +static int hellcreek_setup_fdb(struct hellcreek *hellcreek) +{ +	static struct hellcreek_fdb_entry ptp = { +		/* MAC: 01-1B-19-00-00-00 */ +		.mac	      = { 0x01, 0x1b, 0x19, 0x00, 0x00, 0x00 }, +		.portmask     = 0x03,	/* Management ports */ +		.age	      = 0, +		.is_obt	      = 0, +		.pass_blocked = 0, +		.is_static    = 1, +		.reprio_tc    = 6,	/* TC: 6 as per IEEE 802.1AS */ +		.reprio_en    = 1, +	}; +	static struct hellcreek_fdb_entry p2p = { +		/* MAC: 01-80-C2-00-00-0E */ +		.mac	      = { 0x01, 0x80, 0xc2, 0x00, 0x00, 0x0e }, +		.portmask     = 0x03,	/* Management ports */ +		.age	      = 0, +		.is_obt	      = 0, +		.pass_blocked = 0, +		.is_static    = 1, +		.reprio_tc    = 6,	/* TC: 6 as per IEEE 802.1AS */ +		.reprio_en    = 1, +	}; +	int ret; + +	mutex_lock(&hellcreek->reg_lock); +	ret = __hellcreek_fdb_add(hellcreek, &ptp); +	if (ret) +		goto out; +	ret = __hellcreek_fdb_add(hellcreek, &p2p); +out: +	mutex_unlock(&hellcreek->reg_lock); + +	return ret; +} + +static int hellcreek_setup(struct dsa_switch *ds) +{ +	struct hellcreek *hellcreek = ds->priv; +	u16 swcfg = 0; +	int ret, i; + +	dev_dbg(hellcreek->dev, "Set up the switch\n"); + +	/* Let's go */ +	ret = hellcreek_enable_ip_core(hellcreek); +	if (ret) { +		dev_err(hellcreek->dev, "Failed to enable IP core!\n"); +		return ret; +	} + +	/* Enable CPU/Tunnel ports */ +	hellcreek_setup_cpu_and_tunnel_port(hellcreek); + +	/* Switch config: Keep defaults, enable FDB aging and learning and tag +	 * each frame from/to cpu port for DSA tagging.  Also enable the length +	 * aware shaping mode. This eliminates the need for Qbv guard bands. +	 */ +	swcfg |= HR_SWCFG_FDBAGE_EN | +		HR_SWCFG_FDBLRN_EN  | +		HR_SWCFG_ALWAYS_OBT | +		(HR_SWCFG_LAS_ON << HR_SWCFG_LAS_MODE_SHIFT); +	hellcreek->swcfg = swcfg; +	hellcreek_write(hellcreek, swcfg, HR_SWCFG); + +	/* Initial vlan membership to reflect port separation */ +	for (i = 0; i < ds->num_ports; ++i) { +		if (!dsa_is_user_port(ds, i)) +			continue; + +		hellcreek_setup_vlan_membership(ds, i, true); +	} + +	/* Configure PCP <-> TC mapping */ +	hellcreek_setup_tc_identity_mapping(hellcreek); + +	/* Allow VLAN configurations while not filtering which is the default +	 * for new DSA drivers. +	 */ +	ds->configure_vlan_while_not_filtering = true; + +	/* The VLAN awareness is a global switch setting. Therefore, mixed vlan +	 * filtering setups are not supported. +	 */ +	ds->vlan_filtering_is_global = true; + +	/* Intercept _all_ PTP multicast traffic */ +	ret = hellcreek_setup_fdb(hellcreek); +	if (ret) { +		dev_err(hellcreek->dev, +			"Failed to insert static PTP FDB entries\n"); +		return ret; +	} + +	return 0; +} + +static void hellcreek_phylink_validate(struct dsa_switch *ds, int port, +				       unsigned long *supported, +				       struct phylink_link_state *state) +{ +	__ETHTOOL_DECLARE_LINK_MODE_MASK(mask) = { 0, }; +	struct hellcreek *hellcreek = ds->priv; + +	dev_dbg(hellcreek->dev, "Phylink validate for port %d\n", port); + +	/* The MAC settings are a hardware configuration option and cannot be +	 * changed at run time or by strapping. Therefore the attached PHYs +	 * should be programmed to only advertise settings which are supported +	 * by the hardware. +	 */ +	if (hellcreek->pdata->is_100_mbits) +		phylink_set(mask, 100baseT_Full); +	else +		phylink_set(mask, 1000baseT_Full); + +	bitmap_and(supported, supported, mask, +		   __ETHTOOL_LINK_MODE_MASK_NBITS); +	bitmap_and(state->advertising, state->advertising, mask, +		   __ETHTOOL_LINK_MODE_MASK_NBITS); +} + +static int +hellcreek_port_prechangeupper(struct dsa_switch *ds, int port, +			      struct netdev_notifier_changeupper_info *info) +{ +	struct hellcreek *hellcreek = ds->priv; +	bool used = true; +	int ret = -EBUSY; +	u16 vid; +	int i; + +	dev_dbg(hellcreek->dev, "Pre change upper for port %d\n", port); + +	/* +	 * Deny VLAN devices on top of lan ports with the same VLAN ids, because +	 * it breaks the port separation due to the private VLANs. Example: +	 * +	 * lan0.100 *and* lan1.100 cannot be used in parallel. However, lan0.99 +	 * and lan1.100 works. +	 */ + +	if (!is_vlan_dev(info->upper_dev)) +		return 0; + +	vid = vlan_dev_vlan_id(info->upper_dev); + +	/* For all ports, check bitmaps */ +	mutex_lock(&hellcreek->vlan_lock); +	for (i = 0; i < hellcreek->pdata->num_ports; ++i) { +		if (!dsa_is_user_port(ds, i)) +			continue; + +		if (port == i) +			continue; + +		used = used && test_bit(vid, hellcreek->ports[i].vlan_dev_bitmap); +	} + +	if (used) +		goto out; + +	/* Update bitmap */ +	set_bit(vid, hellcreek->ports[port].vlan_dev_bitmap); + +	ret = 0; + +out: +	mutex_unlock(&hellcreek->vlan_lock); + +	return ret; +} + +static const struct dsa_switch_ops hellcreek_ds_ops = { +	.get_ethtool_stats   = hellcreek_get_ethtool_stats, +	.get_sset_count	     = hellcreek_get_sset_count, +	.get_strings	     = hellcreek_get_strings, +	.get_tag_protocol    = hellcreek_get_tag_protocol, +	.get_ts_info	     = hellcreek_get_ts_info, +	.phylink_validate    = hellcreek_phylink_validate, +	.port_bridge_join    = hellcreek_port_bridge_join, +	.port_bridge_leave   = hellcreek_port_bridge_leave, +	.port_disable	     = hellcreek_port_disable, +	.port_enable	     = hellcreek_port_enable, +	.port_fdb_add	     = hellcreek_fdb_add, +	.port_fdb_del	     = hellcreek_fdb_del, +	.port_fdb_dump	     = hellcreek_fdb_dump, +	.port_hwtstamp_set   = hellcreek_port_hwtstamp_set, +	.port_hwtstamp_get   = hellcreek_port_hwtstamp_get, +	.port_prechangeupper = hellcreek_port_prechangeupper, +	.port_rxtstamp	     = hellcreek_port_rxtstamp, +	.port_stp_state_set  = hellcreek_port_stp_state_set, +	.port_txtstamp	     = hellcreek_port_txtstamp, +	.port_vlan_add	     = hellcreek_vlan_add, +	.port_vlan_del	     = hellcreek_vlan_del, +	.port_vlan_filtering = hellcreek_vlan_filtering, +	.port_vlan_prepare   = hellcreek_vlan_prepare, +	.setup		     = hellcreek_setup, +}; + +static int hellcreek_probe(struct platform_device *pdev) +{ +	struct device *dev = &pdev->dev; +	struct hellcreek *hellcreek; +	struct resource *res; +	int ret, i; + +	hellcreek = devm_kzalloc(dev, sizeof(*hellcreek), GFP_KERNEL); +	if (!hellcreek) +		return -ENOMEM; + +	hellcreek->vidmbrcfg = devm_kcalloc(dev, VLAN_N_VID, +					    sizeof(*hellcreek->vidmbrcfg), +					    GFP_KERNEL); +	if (!hellcreek->vidmbrcfg) +		return -ENOMEM; + +	hellcreek->pdata = of_device_get_match_data(dev); + +	hellcreek->ports = devm_kcalloc(dev, hellcreek->pdata->num_ports, +					sizeof(*hellcreek->ports), +					GFP_KERNEL); +	if (!hellcreek->ports) +		return -ENOMEM; + +	for (i = 0; i < hellcreek->pdata->num_ports; ++i) { +		struct hellcreek_port *port = &hellcreek->ports[i]; + +		port->counter_values = +			devm_kcalloc(dev, +				     ARRAY_SIZE(hellcreek_counter), +				     sizeof(*port->counter_values), +				     GFP_KERNEL); +		if (!port->counter_values) +			return -ENOMEM; + +		port->vlan_dev_bitmap = +			devm_kcalloc(dev, +				     BITS_TO_LONGS(VLAN_N_VID), +				     sizeof(unsigned long), +				     GFP_KERNEL); +		if (!port->vlan_dev_bitmap) +			return -ENOMEM; + +		port->hellcreek	= hellcreek; +		port->port	= i; +	} + +	mutex_init(&hellcreek->reg_lock); +	mutex_init(&hellcreek->vlan_lock); +	mutex_init(&hellcreek->ptp_lock); + +	hellcreek->dev = dev; + +	res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "tsn"); +	if (!res) { +		dev_err(dev, "No memory region provided!\n"); +		return -ENODEV; +	} + +	hellcreek->base = devm_ioremap_resource(dev, res); +	if (IS_ERR(hellcreek->base)) { +		dev_err(dev, "No memory available!\n"); +		return PTR_ERR(hellcreek->base); +	} + +	res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "ptp"); +	if (!res) { +		dev_err(dev, "No PTP memory region provided!\n"); +		return -ENODEV; +	} + +	hellcreek->ptp_base = devm_ioremap_resource(dev, res); +	if (IS_ERR(hellcreek->ptp_base)) { +		dev_err(dev, "No memory available!\n"); +		return PTR_ERR(hellcreek->ptp_base); +	} + +	ret = hellcreek_detect(hellcreek); +	if (ret) { +		dev_err(dev, "No (known) chip found!\n"); +		return ret; +	} + +	ret = hellcreek_wait_until_ready(hellcreek); +	if (ret) { +		dev_err(dev, "Switch didn't become ready!\n"); +		return ret; +	} + +	hellcreek_feature_detect(hellcreek); + +	hellcreek->ds = devm_kzalloc(dev, sizeof(*hellcreek->ds), GFP_KERNEL); +	if (!hellcreek->ds) +		return -ENOMEM; + +	hellcreek->ds->dev	     = dev; +	hellcreek->ds->priv	     = hellcreek; +	hellcreek->ds->ops	     = &hellcreek_ds_ops; +	hellcreek->ds->num_ports     = hellcreek->pdata->num_ports; +	hellcreek->ds->num_tx_queues = HELLCREEK_NUM_EGRESS_QUEUES; + +	ret = dsa_register_switch(hellcreek->ds); +	if (ret) { +		dev_err_probe(dev, ret, "Unable to register switch\n"); +		return ret; +	} + +	ret = hellcreek_ptp_setup(hellcreek); +	if (ret) { +		dev_err(dev, "Failed to setup PTP!\n"); +		goto err_ptp_setup; +	} + +	ret = hellcreek_hwtstamp_setup(hellcreek); +	if (ret) { +		dev_err(dev, "Failed to setup hardware timestamping!\n"); +		goto err_tstamp_setup; +	} + +	platform_set_drvdata(pdev, hellcreek); + +	return 0; + +err_tstamp_setup: +	hellcreek_ptp_free(hellcreek); +err_ptp_setup: +	dsa_unregister_switch(hellcreek->ds); + +	return ret; +} + +static int hellcreek_remove(struct platform_device *pdev) +{ +	struct hellcreek *hellcreek = platform_get_drvdata(pdev); + +	hellcreek_hwtstamp_free(hellcreek); +	hellcreek_ptp_free(hellcreek); +	dsa_unregister_switch(hellcreek->ds); +	platform_set_drvdata(pdev, NULL); + +	return 0; +} + +static const struct hellcreek_platform_data de1soc_r1_pdata = { +	.num_ports	 = 4, +	.is_100_mbits	 = 1, +	.qbv_support	 = 1, +	.qbv_on_cpu_port = 1, +	.qbu_support	 = 0, +	.module_id	 = 0x4c30, +}; + +static const struct of_device_id hellcreek_of_match[] = { +	{ +		.compatible = "hirschmann,hellcreek-de1soc-r1", +		.data	    = &de1soc_r1_pdata, +	}, +	{ /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(of, hellcreek_of_match); + +static struct platform_driver hellcreek_driver = { +	.probe	= hellcreek_probe, +	.remove = hellcreek_remove, +	.driver = { +		.name = "hellcreek", +		.of_match_table = hellcreek_of_match, +	}, +}; +module_platform_driver(hellcreek_driver); + +MODULE_AUTHOR("Kurt Kanzenbach <kurt@linutronix.de>"); +MODULE_DESCRIPTION("Hirschmann Hellcreek driver"); +MODULE_LICENSE("Dual MIT/GPL"); diff --git a/drivers/net/dsa/hirschmann/hellcreek.h b/drivers/net/dsa/hirschmann/hellcreek.h new file mode 100644 index 000000000000..e81781ebc31c --- /dev/null +++ b/drivers/net/dsa/hirschmann/hellcreek.h @@ -0,0 +1,286 @@ +/* SPDX-License-Identifier: (GPL-2.0 or MIT) */ +/* + * DSA driver for: + * Hirschmann Hellcreek TSN switch. + * + * Copyright (C) 2019,2020 Linutronix GmbH + * Author Kurt Kanzenbach <kurt@linutronix.de> + */ + +#ifndef _HELLCREEK_H_ +#define _HELLCREEK_H_ + +#include <linux/bitmap.h> +#include <linux/bitops.h> +#include <linux/device.h> +#include <linux/kernel.h> +#include <linux/mutex.h> +#include <linux/workqueue.h> +#include <linux/leds.h> +#include <linux/platform_data/hirschmann-hellcreek.h> +#include <linux/ptp_clock_kernel.h> +#include <linux/timecounter.h> +#include <net/dsa.h> + +/* Ports: + *  - 0: CPU + *  - 1: Tunnel + *  - 2: TSN front port 1 + *  - 3: TSN front port 2 + *  - ... + */ +#define CPU_PORT			0 +#define TUNNEL_PORT			1 + +#define HELLCREEK_VLAN_NO_MEMBER	0x0 +#define HELLCREEK_VLAN_UNTAGGED_MEMBER	0x1 +#define HELLCREEK_VLAN_TAGGED_MEMBER	0x3 +#define HELLCREEK_NUM_EGRESS_QUEUES	8 + +/* Register definitions */ +#define HR_MODID_C			(0 * 2) +#define HR_REL_L_C			(1 * 2) +#define HR_REL_H_C			(2 * 2) +#define HR_BLD_L_C			(3 * 2) +#define HR_BLD_H_C			(4 * 2) +#define HR_CTRL_C			(5 * 2) +#define HR_CTRL_C_READY			BIT(14) +#define HR_CTRL_C_TRANSITION		BIT(13) +#define HR_CTRL_C_ENABLE		BIT(0) + +#define HR_PSEL				(0xa6 * 2) +#define HR_PSEL_PTWSEL_SHIFT		4 +#define HR_PSEL_PTWSEL_MASK		GENMASK(5, 4) +#define HR_PSEL_PRTCWSEL_SHIFT		0 +#define HR_PSEL_PRTCWSEL_MASK		GENMASK(2, 0) + +#define HR_PTCFG			(0xa7 * 2) +#define HR_PTCFG_MLIMIT_EN		BIT(13) +#define HR_PTCFG_UMC_FLT		BIT(10) +#define HR_PTCFG_UUC_FLT		BIT(9) +#define HR_PTCFG_UNTRUST		BIT(8) +#define HR_PTCFG_TAG_REQUIRED		BIT(7) +#define HR_PTCFG_PPRIO_SHIFT		4 +#define HR_PTCFG_PPRIO_MASK		GENMASK(6, 4) +#define HR_PTCFG_INGRESSFLT		BIT(3) +#define HR_PTCFG_BLOCKED		BIT(2) +#define HR_PTCFG_LEARNING_EN		BIT(1) +#define HR_PTCFG_ADMIN_EN		BIT(0) + +#define HR_PRTCCFG			(0xa8 * 2) +#define HR_PRTCCFG_PCP_TC_MAP_SHIFT	0 +#define HR_PRTCCFG_PCP_TC_MAP_MASK	GENMASK(2, 0) + +#define HR_CSEL				(0x8d * 2) +#define HR_CSEL_SHIFT			0 +#define HR_CSEL_MASK			GENMASK(7, 0) +#define HR_CRDL				(0x8e * 2) +#define HR_CRDH				(0x8f * 2) + +#define HR_SWTRC_CFG			(0x90 * 2) +#define HR_SWTRC0			(0x91 * 2) +#define HR_SWTRC1			(0x92 * 2) +#define HR_PFREE			(0x93 * 2) +#define HR_MFREE			(0x94 * 2) + +#define HR_FDBAGE			(0x97 * 2) +#define HR_FDBMAX			(0x98 * 2) +#define HR_FDBRDL			(0x99 * 2) +#define HR_FDBRDM			(0x9a * 2) +#define HR_FDBRDH			(0x9b * 2) + +#define HR_FDBMDRD			(0x9c * 2) +#define HR_FDBMDRD_PORTMASK_SHIFT	0 +#define HR_FDBMDRD_PORTMASK_MASK	GENMASK(3, 0) +#define HR_FDBMDRD_AGE_SHIFT		4 +#define HR_FDBMDRD_AGE_MASK		GENMASK(7, 4) +#define HR_FDBMDRD_OBT			BIT(8) +#define HR_FDBMDRD_PASS_BLOCKED		BIT(9) +#define HR_FDBMDRD_STATIC		BIT(11) +#define HR_FDBMDRD_REPRIO_TC_SHIFT	12 +#define HR_FDBMDRD_REPRIO_TC_MASK	GENMASK(14, 12) +#define HR_FDBMDRD_REPRIO_EN		BIT(15) + +#define HR_FDBWDL			(0x9d * 2) +#define HR_FDBWDM			(0x9e * 2) +#define HR_FDBWDH			(0x9f * 2) +#define HR_FDBWRM0			(0xa0 * 2) +#define HR_FDBWRM0_PORTMASK_SHIFT	0 +#define HR_FDBWRM0_PORTMASK_MASK	GENMASK(3, 0) +#define HR_FDBWRM0_OBT			BIT(8) +#define HR_FDBWRM0_PASS_BLOCKED		BIT(9) +#define HR_FDBWRM0_REPRIO_TC_SHIFT	12 +#define HR_FDBWRM0_REPRIO_TC_MASK	GENMASK(14, 12) +#define HR_FDBWRM0_REPRIO_EN		BIT(15) +#define HR_FDBWRM1			(0xa1 * 2) + +#define HR_FDBWRCMD			(0xa2 * 2) +#define HR_FDBWRCMD_FDBDEL		BIT(9) + +#define HR_SWCFG			(0xa3 * 2) +#define HR_SWCFG_GM_STATEMD		BIT(15) +#define HR_SWCFG_LAS_MODE_SHIFT		12 +#define HR_SWCFG_LAS_MODE_MASK		GENMASK(13, 12) +#define HR_SWCFG_LAS_OFF		(0x00) +#define HR_SWCFG_LAS_ON			(0x01) +#define HR_SWCFG_LAS_STATIC		(0x10) +#define HR_SWCFG_CT_EN			BIT(11) +#define HR_SWCFG_VLAN_UNAWARE		BIT(10) +#define HR_SWCFG_ALWAYS_OBT		BIT(9) +#define HR_SWCFG_FDBAGE_EN		BIT(5) +#define HR_SWCFG_FDBLRN_EN		BIT(4) + +#define HR_SWSTAT			(0xa4 * 2) +#define HR_SWSTAT_FAIL			BIT(4) +#define HR_SWSTAT_BUSY			BIT(0) + +#define HR_SWCMD			(0xa5 * 2) +#define HW_SWCMD_FLUSH			BIT(0) + +#define HR_VIDCFG			(0xaa * 2) +#define HR_VIDCFG_VID_SHIFT		0 +#define HR_VIDCFG_VID_MASK		GENMASK(11, 0) +#define HR_VIDCFG_PVID			BIT(12) + +#define HR_VIDMBRCFG			(0xab * 2) +#define HR_VIDMBRCFG_P0MBR_SHIFT	0 +#define HR_VIDMBRCFG_P0MBR_MASK		GENMASK(1, 0) +#define HR_VIDMBRCFG_P1MBR_SHIFT	2 +#define HR_VIDMBRCFG_P1MBR_MASK		GENMASK(3, 2) +#define HR_VIDMBRCFG_P2MBR_SHIFT	4 +#define HR_VIDMBRCFG_P2MBR_MASK		GENMASK(5, 4) +#define HR_VIDMBRCFG_P3MBR_SHIFT	6 +#define HR_VIDMBRCFG_P3MBR_MASK		GENMASK(7, 6) + +#define HR_FEABITS0			(0xac * 2) +#define HR_FEABITS0_FDBBINS_SHIFT	4 +#define HR_FEABITS0_FDBBINS_MASK	GENMASK(7, 4) +#define HR_FEABITS0_PCNT_SHIFT		8 +#define HR_FEABITS0_PCNT_MASK		GENMASK(11, 8) +#define HR_FEABITS0_MCNT_SHIFT		12 +#define HR_FEABITS0_MCNT_MASK		GENMASK(15, 12) + +#define TR_QTRACK			(0xb1 * 2) +#define TR_TGDVER			(0xb3 * 2) +#define TR_TGDVER_REV_MIN_MASK		GENMASK(7, 0) +#define TR_TGDVER_REV_MIN_SHIFT		0 +#define TR_TGDVER_REV_MAJ_MASK		GENMASK(15, 8) +#define TR_TGDVER_REV_MAJ_SHIFT		8 +#define TR_TGDSEL			(0xb4 * 2) +#define TR_TGDSEL_TDGSEL_MASK		GENMASK(1, 0) +#define TR_TGDSEL_TDGSEL_SHIFT		0 +#define TR_TGDCTRL			(0xb5 * 2) +#define TR_TGDCTRL_GATE_EN		BIT(0) +#define TR_TGDCTRL_CYC_SNAP		BIT(4) +#define TR_TGDCTRL_SNAP_EST		BIT(5) +#define TR_TGDCTRL_ADMINGATESTATES_MASK	GENMASK(15, 8) +#define TR_TGDCTRL_ADMINGATESTATES_SHIFT	8 +#define TR_TGDSTAT0			(0xb6 * 2) +#define TR_TGDSTAT1			(0xb7 * 2) +#define TR_ESTWRL			(0xb8 * 2) +#define TR_ESTWRH			(0xb9 * 2) +#define TR_ESTCMD			(0xba * 2) +#define TR_ESTCMD_ESTSEC_MASK		GENMASK(2, 0) +#define TR_ESTCMD_ESTSEC_SHIFT		0 +#define TR_ESTCMD_ESTARM		BIT(4) +#define TR_ESTCMD_ESTSWCFG		BIT(5) +#define TR_EETWRL			(0xbb * 2) +#define TR_EETWRH			(0xbc * 2) +#define TR_EETCMD			(0xbd * 2) +#define TR_EETCMD_EETSEC_MASK		GEMASK(2, 0) +#define TR_EETCMD_EETSEC_SHIFT		0 +#define TR_EETCMD_EETARM		BIT(4) +#define TR_CTWRL			(0xbe * 2) +#define TR_CTWRH			(0xbf * 2) +#define TR_LCNSL			(0xc1 * 2) +#define TR_LCNSH			(0xc2 * 2) +#define TR_LCS				(0xc3 * 2) +#define TR_GCLDAT			(0xc4 * 2) +#define TR_GCLDAT_GCLWRGATES_MASK	GENMASK(7, 0) +#define TR_GCLDAT_GCLWRGATES_SHIFT	0 +#define TR_GCLDAT_GCLWRLAST		BIT(8) +#define TR_GCLDAT_GCLOVRI		BIT(9) +#define TR_GCLTIL			(0xc5 * 2) +#define TR_GCLTIH			(0xc6 * 2) +#define TR_GCLCMD			(0xc7 * 2) +#define TR_GCLCMD_GCLWRADR_MASK		GENMASK(7, 0) +#define TR_GCLCMD_GCLWRADR_SHIFT	0 +#define TR_GCLCMD_INIT_GATE_STATES_MASK	GENMASK(15, 8) +#define TR_GCLCMD_INIT_GATE_STATES_SHIFT	8 + +struct hellcreek_counter { +	u8 offset; +	const char *name; +}; + +struct hellcreek; + +/* State flags for hellcreek_port_hwtstamp::state */ +enum { +	HELLCREEK_HWTSTAMP_ENABLED, +	HELLCREEK_HWTSTAMP_TX_IN_PROGRESS, +}; + +/* A structure to hold hardware timestamping information per port */ +struct hellcreek_port_hwtstamp { +	/* Timestamping state */ +	unsigned long state; + +	/* Resources for receive timestamping */ +	struct sk_buff_head rx_queue; /* For synchronization messages */ + +	/* Resources for transmit timestamping */ +	unsigned long tx_tstamp_start; +	struct sk_buff *tx_skb; + +	/* Current timestamp configuration */ +	struct hwtstamp_config tstamp_config; +}; + +struct hellcreek_port { +	struct hellcreek *hellcreek; +	unsigned long *vlan_dev_bitmap; +	int port; +	u16 ptcfg;		/* ptcfg shadow */ +	u64 *counter_values; + +	/* Per-port timestamping resources */ +	struct hellcreek_port_hwtstamp port_hwtstamp; +}; + +struct hellcreek_fdb_entry { +	size_t idx; +	unsigned char mac[ETH_ALEN]; +	u8 portmask; +	u8 age; +	u8 is_obt; +	u8 pass_blocked; +	u8 is_static; +	u8 reprio_tc; +	u8 reprio_en; +}; + +struct hellcreek { +	const struct hellcreek_platform_data *pdata; +	struct device *dev; +	struct dsa_switch *ds; +	struct ptp_clock *ptp_clock; +	struct ptp_clock_info ptp_clock_info; +	struct hellcreek_port *ports; +	struct delayed_work overflow_work; +	struct led_classdev led_is_gm; +	struct led_classdev led_sync_good; +	struct mutex reg_lock;	/* Switch IP register lock */ +	struct mutex vlan_lock;	/* VLAN bitmaps lock */ +	struct mutex ptp_lock;	/* PTP IP register lock */ +	void __iomem *base; +	void __iomem *ptp_base; +	u16 swcfg;		/* swcfg shadow */ +	u8 *vidmbrcfg;		/* vidmbrcfg shadow */ +	u64 seconds;		/* PTP seconds */ +	u64 last_ts;		/* Used for overflow detection */ +	u16 status_out;		/* ptp.status_out shadow */ +	size_t fdb_entries; +}; + +#endif /* _HELLCREEK_H_ */ diff --git a/drivers/net/dsa/hirschmann/hellcreek_hwtstamp.c b/drivers/net/dsa/hirschmann/hellcreek_hwtstamp.c new file mode 100644 index 000000000000..69dd9a2e8bb6 --- /dev/null +++ b/drivers/net/dsa/hirschmann/hellcreek_hwtstamp.c @@ -0,0 +1,479 @@ +// SPDX-License-Identifier: (GPL-2.0 OR MIT) +/* + * DSA driver for: + * Hirschmann Hellcreek TSN switch. + * + * Copyright (C) 2019,2020 Hochschule Offenburg + * Copyright (C) 2019,2020 Linutronix GmbH + * Authors: Kamil Alkhouri <kamil.alkhouri@hs-offenburg.de> + *	    Kurt Kanzenbach <kurt@linutronix.de> + */ + +#include <linux/ptp_classify.h> + +#include "hellcreek.h" +#include "hellcreek_hwtstamp.h" +#include "hellcreek_ptp.h" + +int hellcreek_get_ts_info(struct dsa_switch *ds, int port, +			  struct ethtool_ts_info *info) +{ +	struct hellcreek *hellcreek = ds->priv; + +	info->phc_index = hellcreek->ptp_clock ? +		ptp_clock_index(hellcreek->ptp_clock) : -1; +	info->so_timestamping = SOF_TIMESTAMPING_TX_HARDWARE | +		SOF_TIMESTAMPING_RX_HARDWARE | +		SOF_TIMESTAMPING_RAW_HARDWARE; + +	/* enabled tx timestamping */ +	info->tx_types = BIT(HWTSTAMP_TX_ON); + +	/* L2 & L4 PTPv2 event rx messages are timestamped */ +	info->rx_filters = BIT(HWTSTAMP_FILTER_PTP_V2_EVENT); + +	return 0; +} + +/* Enabling/disabling TX and RX HW timestamping for different PTP messages is + * not available in the switch. Thus, this function only serves as a check if + * the user requested what is actually available or not + */ +static int hellcreek_set_hwtstamp_config(struct hellcreek *hellcreek, int port, +					 struct hwtstamp_config *config) +{ +	struct hellcreek_port_hwtstamp *ps = +		&hellcreek->ports[port].port_hwtstamp; +	bool tx_tstamp_enable = false; +	bool rx_tstamp_enable = false; + +	/* Interaction with the timestamp hardware is prevented here.  It is +	 * enabled when this config function ends successfully +	 */ +	clear_bit_unlock(HELLCREEK_HWTSTAMP_ENABLED, &ps->state); + +	/* Reserved for future extensions */ +	if (config->flags) +		return -EINVAL; + +	switch (config->tx_type) { +	case HWTSTAMP_TX_ON: +		tx_tstamp_enable = true; +		break; + +	/* TX HW timestamping can't be disabled on the switch */ +	case HWTSTAMP_TX_OFF: +		config->tx_type = HWTSTAMP_TX_ON; +		break; + +	default: +		return -ERANGE; +	} + +	switch (config->rx_filter) { +	/* RX HW timestamping can't be disabled on the switch */ +	case HWTSTAMP_FILTER_NONE: +		config->rx_filter = HWTSTAMP_FILTER_PTP_V2_EVENT; +		break; + +	case HWTSTAMP_FILTER_PTP_V2_L4_EVENT: +	case HWTSTAMP_FILTER_PTP_V2_L4_SYNC: +	case HWTSTAMP_FILTER_PTP_V2_L4_DELAY_REQ: +	case HWTSTAMP_FILTER_PTP_V2_L2_EVENT: +	case HWTSTAMP_FILTER_PTP_V2_L2_SYNC: +	case HWTSTAMP_FILTER_PTP_V2_L2_DELAY_REQ: +	case HWTSTAMP_FILTER_PTP_V2_EVENT: +	case HWTSTAMP_FILTER_PTP_V2_SYNC: +	case HWTSTAMP_FILTER_PTP_V2_DELAY_REQ: +		config->rx_filter = HWTSTAMP_FILTER_PTP_V2_EVENT; +		rx_tstamp_enable = true; +		break; + +	/* RX HW timestamping can't be enabled for all messages on the switch */ +	case HWTSTAMP_FILTER_ALL: +		config->rx_filter = HWTSTAMP_FILTER_PTP_V2_EVENT; +		break; + +	default: +		return -ERANGE; +	} + +	if (!tx_tstamp_enable) +		return -ERANGE; + +	if (!rx_tstamp_enable) +		return -ERANGE; + +	/* If this point is reached, then the requested hwtstamp config is +	 * compatible with the hwtstamp offered by the switch.  Therefore, +	 * enable the interaction with the HW timestamping +	 */ +	set_bit(HELLCREEK_HWTSTAMP_ENABLED, &ps->state); + +	return 0; +} + +int hellcreek_port_hwtstamp_set(struct dsa_switch *ds, int port, +				struct ifreq *ifr) +{ +	struct hellcreek *hellcreek = ds->priv; +	struct hellcreek_port_hwtstamp *ps; +	struct hwtstamp_config config; +	int err; + +	ps = &hellcreek->ports[port].port_hwtstamp; + +	if (copy_from_user(&config, ifr->ifr_data, sizeof(config))) +		return -EFAULT; + +	err = hellcreek_set_hwtstamp_config(hellcreek, port, &config); +	if (err) +		return err; + +	/* Save the chosen configuration to be returned later */ +	memcpy(&ps->tstamp_config, &config, sizeof(config)); + +	return copy_to_user(ifr->ifr_data, &config, sizeof(config)) ? +		-EFAULT : 0; +} + +int hellcreek_port_hwtstamp_get(struct dsa_switch *ds, int port, +				struct ifreq *ifr) +{ +	struct hellcreek *hellcreek = ds->priv; +	struct hellcreek_port_hwtstamp *ps; +	struct hwtstamp_config *config; + +	ps = &hellcreek->ports[port].port_hwtstamp; +	config = &ps->tstamp_config; + +	return copy_to_user(ifr->ifr_data, config, sizeof(*config)) ? +		-EFAULT : 0; +} + +/* Returns a pointer to the PTP header if the caller should time stamp, or NULL + * if the caller should not. + */ +static struct ptp_header *hellcreek_should_tstamp(struct hellcreek *hellcreek, +						  int port, struct sk_buff *skb, +						  unsigned int type) +{ +	struct hellcreek_port_hwtstamp *ps = +		&hellcreek->ports[port].port_hwtstamp; +	struct ptp_header *hdr; + +	hdr = ptp_parse_header(skb, type); +	if (!hdr) +		return NULL; + +	if (!test_bit(HELLCREEK_HWTSTAMP_ENABLED, &ps->state)) +		return NULL; + +	return hdr; +} + +static u64 hellcreek_get_reserved_field(const struct ptp_header *hdr) +{ +	return be32_to_cpu(hdr->reserved2); +} + +static void hellcreek_clear_reserved_field(struct ptp_header *hdr) +{ +	hdr->reserved2 = 0; +} + +static int hellcreek_ptp_hwtstamp_available(struct hellcreek *hellcreek, +					    unsigned int ts_reg) +{ +	u16 status; + +	status = hellcreek_ptp_read(hellcreek, ts_reg); + +	if (status & PR_TS_STATUS_TS_LOST) +		dev_err(hellcreek->dev, +			"Tx time stamp lost! This should never happen!\n"); + +	/* If hwtstamp is not available, this means the previous hwtstamp was +	 * successfully read, and the one we need is not yet available +	 */ +	return (status & PR_TS_STATUS_TS_AVAIL) ? 1 : 0; +} + +/* Get nanoseconds timestamp from timestamping unit */ +static u64 hellcreek_ptp_hwtstamp_read(struct hellcreek *hellcreek, +				       unsigned int ts_reg) +{ +	u16 nsl, nsh; + +	nsh = hellcreek_ptp_read(hellcreek, ts_reg); +	nsh = hellcreek_ptp_read(hellcreek, ts_reg); +	nsh = hellcreek_ptp_read(hellcreek, ts_reg); +	nsh = hellcreek_ptp_read(hellcreek, ts_reg); +	nsl = hellcreek_ptp_read(hellcreek, ts_reg); + +	return (u64)nsl | ((u64)nsh << 16); +} + +static int hellcreek_txtstamp_work(struct hellcreek *hellcreek, +				   struct hellcreek_port_hwtstamp *ps, int port) +{ +	struct skb_shared_hwtstamps shhwtstamps; +	unsigned int status_reg, data_reg; +	struct sk_buff *tmp_skb; +	int ts_status; +	u64 ns = 0; + +	if (!ps->tx_skb) +		return 0; + +	switch (port) { +	case 2: +		status_reg = PR_TS_TX_P1_STATUS_C; +		data_reg   = PR_TS_TX_P1_DATA_C; +		break; +	case 3: +		status_reg = PR_TS_TX_P2_STATUS_C; +		data_reg   = PR_TS_TX_P2_DATA_C; +		break; +	default: +		dev_err(hellcreek->dev, "Wrong port for timestamping!\n"); +		return 0; +	} + +	ts_status = hellcreek_ptp_hwtstamp_available(hellcreek, status_reg); + +	/* Not available yet? */ +	if (ts_status == 0) { +		/* Check whether the operation of reading the tx timestamp has +		 * exceeded its allowed period +		 */ +		if (time_is_before_jiffies(ps->tx_tstamp_start + +					   TX_TSTAMP_TIMEOUT)) { +			dev_err(hellcreek->dev, +				"Timeout while waiting for Tx timestamp!\n"); +			goto free_and_clear_skb; +		} + +		/* The timestamp should be available quickly, while getting it +		 * in high priority. Restart the work +		 */ +		return 1; +	} + +	mutex_lock(&hellcreek->ptp_lock); +	ns  = hellcreek_ptp_hwtstamp_read(hellcreek, data_reg); +	ns += hellcreek_ptp_gettime_seconds(hellcreek, ns); +	mutex_unlock(&hellcreek->ptp_lock); + +	/* Now we have the timestamp in nanoseconds, store it in the correct +	 * structure in order to send it to the user +	 */ +	memset(&shhwtstamps, 0, sizeof(shhwtstamps)); +	shhwtstamps.hwtstamp = ns_to_ktime(ns); + +	tmp_skb = ps->tx_skb; +	ps->tx_skb = NULL; + +	/* skb_complete_tx_timestamp() frees up the client to make another +	 * timestampable transmit.  We have to be ready for it by clearing the +	 * ps->tx_skb "flag" beforehand +	 */ +	clear_bit_unlock(HELLCREEK_HWTSTAMP_TX_IN_PROGRESS, &ps->state); + +	/* Deliver a clone of the original outgoing tx_skb with tx hwtstamp */ +	skb_complete_tx_timestamp(tmp_skb, &shhwtstamps); + +	return 0; + +free_and_clear_skb: +	dev_kfree_skb_any(ps->tx_skb); +	ps->tx_skb = NULL; +	clear_bit_unlock(HELLCREEK_HWTSTAMP_TX_IN_PROGRESS, &ps->state); + +	return 0; +} + +static void hellcreek_get_rxts(struct hellcreek *hellcreek, +			       struct hellcreek_port_hwtstamp *ps, +			       struct sk_buff *skb, struct sk_buff_head *rxq, +			       int port) +{ +	struct skb_shared_hwtstamps *shwt; +	struct sk_buff_head received; +	unsigned long flags; + +	/* The latched timestamp belongs to one of the received frames. */ +	__skb_queue_head_init(&received); + +	/* Lock & disable interrupts */ +	spin_lock_irqsave(&rxq->lock, flags); + +	/* Add the reception queue "rxq" to the "received" queue an reintialize +	 * "rxq".  From now on, we deal with "received" not with "rxq" +	 */ +	skb_queue_splice_tail_init(rxq, &received); + +	spin_unlock_irqrestore(&rxq->lock, flags); + +	for (; skb; skb = __skb_dequeue(&received)) { +		struct ptp_header *hdr; +		unsigned int type; +		u64 ns; + +		/* Get nanoseconds from ptp packet */ +		type = SKB_PTP_TYPE(skb); +		hdr  = ptp_parse_header(skb, type); +		ns   = hellcreek_get_reserved_field(hdr); +		hellcreek_clear_reserved_field(hdr); + +		/* Add seconds part */ +		mutex_lock(&hellcreek->ptp_lock); +		ns += hellcreek_ptp_gettime_seconds(hellcreek, ns); +		mutex_unlock(&hellcreek->ptp_lock); + +		/* Save time stamp */ +		shwt = skb_hwtstamps(skb); +		memset(shwt, 0, sizeof(*shwt)); +		shwt->hwtstamp = ns_to_ktime(ns); +		netif_rx_ni(skb); +	} +} + +static void hellcreek_rxtstamp_work(struct hellcreek *hellcreek, +				    struct hellcreek_port_hwtstamp *ps, +				    int port) +{ +	struct sk_buff *skb; + +	skb = skb_dequeue(&ps->rx_queue); +	if (skb) +		hellcreek_get_rxts(hellcreek, ps, skb, &ps->rx_queue, port); +} + +long hellcreek_hwtstamp_work(struct ptp_clock_info *ptp) +{ +	struct hellcreek *hellcreek = ptp_to_hellcreek(ptp); +	struct dsa_switch *ds = hellcreek->ds; +	int i, restart = 0; + +	for (i = 0; i < ds->num_ports; i++) { +		struct hellcreek_port_hwtstamp *ps; + +		if (!dsa_is_user_port(ds, i)) +			continue; + +		ps = &hellcreek->ports[i].port_hwtstamp; + +		if (test_bit(HELLCREEK_HWTSTAMP_TX_IN_PROGRESS, &ps->state)) +			restart |= hellcreek_txtstamp_work(hellcreek, ps, i); + +		hellcreek_rxtstamp_work(hellcreek, ps, i); +	} + +	return restart ? 1 : -1; +} + +bool hellcreek_port_txtstamp(struct dsa_switch *ds, int port, +			     struct sk_buff *clone, unsigned int type) +{ +	struct hellcreek *hellcreek = ds->priv; +	struct hellcreek_port_hwtstamp *ps; +	struct ptp_header *hdr; + +	ps = &hellcreek->ports[port].port_hwtstamp; + +	/* Check if the driver is expected to do HW timestamping */ +	if (!(skb_shinfo(clone)->tx_flags & SKBTX_HW_TSTAMP)) +		return false; + +	/* Make sure the message is a PTP message that needs to be timestamped +	 * and the interaction with the HW timestamping is enabled. If not, stop +	 * here +	 */ +	hdr = hellcreek_should_tstamp(hellcreek, port, clone, type); +	if (!hdr) +		return false; + +	if (test_and_set_bit_lock(HELLCREEK_HWTSTAMP_TX_IN_PROGRESS, +				  &ps->state)) +		return false; + +	ps->tx_skb = clone; + +	/* store the number of ticks occurred since system start-up till this +	 * moment +	 */ +	ps->tx_tstamp_start = jiffies; + +	ptp_schedule_worker(hellcreek->ptp_clock, 0); + +	return true; +} + +bool hellcreek_port_rxtstamp(struct dsa_switch *ds, int port, +			     struct sk_buff *skb, unsigned int type) +{ +	struct hellcreek *hellcreek = ds->priv; +	struct hellcreek_port_hwtstamp *ps; +	struct ptp_header *hdr; + +	ps = &hellcreek->ports[port].port_hwtstamp; + +	/* This check only fails if the user did not initialize hardware +	 * timestamping beforehand. +	 */ +	if (ps->tstamp_config.rx_filter != HWTSTAMP_FILTER_PTP_V2_EVENT) +		return false; + +	/* Make sure the message is a PTP message that needs to be timestamped +	 * and the interaction with the HW timestamping is enabled. If not, stop +	 * here +	 */ +	hdr = hellcreek_should_tstamp(hellcreek, port, skb, type); +	if (!hdr) +		return false; + +	SKB_PTP_TYPE(skb) = type; + +	skb_queue_tail(&ps->rx_queue, skb); + +	ptp_schedule_worker(hellcreek->ptp_clock, 0); + +	return true; +} + +static void hellcreek_hwtstamp_port_setup(struct hellcreek *hellcreek, int port) +{ +	struct hellcreek_port_hwtstamp *ps = +		&hellcreek->ports[port].port_hwtstamp; + +	skb_queue_head_init(&ps->rx_queue); +} + +int hellcreek_hwtstamp_setup(struct hellcreek *hellcreek) +{ +	struct dsa_switch *ds = hellcreek->ds; +	int i; + +	/* Initialize timestamping ports. */ +	for (i = 0; i < ds->num_ports; ++i) { +		if (!dsa_is_user_port(ds, i)) +			continue; + +		hellcreek_hwtstamp_port_setup(hellcreek, i); +	} + +	/* Select the synchronized clock as the source timekeeper for the +	 * timestamps and enable inline timestamping. +	 */ +	hellcreek_ptp_write(hellcreek, PR_SETTINGS_C_TS_SRC_TK_MASK | +			    PR_SETTINGS_C_RES3TS, +			    PR_SETTINGS_C); + +	return 0; +} + +void hellcreek_hwtstamp_free(struct hellcreek *hellcreek) +{ +	/* Nothing todo */ +} diff --git a/drivers/net/dsa/hirschmann/hellcreek_hwtstamp.h b/drivers/net/dsa/hirschmann/hellcreek_hwtstamp.h new file mode 100644 index 000000000000..c0745ffa1ebb --- /dev/null +++ b/drivers/net/dsa/hirschmann/hellcreek_hwtstamp.h @@ -0,0 +1,58 @@ +/* SPDX-License-Identifier: (GPL-2.0 OR MIT) */ +/* + * DSA driver for: + * Hirschmann Hellcreek TSN switch. + * + * Copyright (C) 2019,2020 Hochschule Offenburg + * Copyright (C) 2019,2020 Linutronix GmbH + * Authors: Kurt Kanzenbach <kurt@linutronix.de> + *	    Kamil Alkhouri <kamil.alkhouri@hs-offenburg.de> + */ + +#ifndef _HELLCREEK_HWTSTAMP_H_ +#define _HELLCREEK_HWTSTAMP_H_ + +#include <net/dsa.h> +#include "hellcreek.h" + +/* Timestamp Register */ +#define PR_TS_RX_P1_STATUS_C	(0x1d * 2) +#define PR_TS_RX_P1_DATA_C	(0x1e * 2) +#define PR_TS_TX_P1_STATUS_C	(0x1f * 2) +#define PR_TS_TX_P1_DATA_C	(0x20 * 2) +#define PR_TS_RX_P2_STATUS_C	(0x25 * 2) +#define PR_TS_RX_P2_DATA_C	(0x26 * 2) +#define PR_TS_TX_P2_STATUS_C	(0x27 * 2) +#define PR_TS_TX_P2_DATA_C	(0x28 * 2) + +#define PR_TS_STATUS_TS_AVAIL	BIT(2) +#define PR_TS_STATUS_TS_LOST	BIT(3) + +#define SKB_PTP_TYPE(__skb) (*(unsigned int *)((__skb)->cb)) + +/* TX_TSTAMP_TIMEOUT: This limits the time spent polling for a TX + * timestamp. When working properly, hardware will produce a timestamp + * within 1ms. Software may enounter delays, so the timeout is set + * accordingly. + */ +#define TX_TSTAMP_TIMEOUT	msecs_to_jiffies(40) + +int hellcreek_port_hwtstamp_set(struct dsa_switch *ds, int port, +				struct ifreq *ifr); +int hellcreek_port_hwtstamp_get(struct dsa_switch *ds, int port, +				struct ifreq *ifr); + +bool hellcreek_port_rxtstamp(struct dsa_switch *ds, int port, +			     struct sk_buff *clone, unsigned int type); +bool hellcreek_port_txtstamp(struct dsa_switch *ds, int port, +			     struct sk_buff *clone, unsigned int type); + +int hellcreek_get_ts_info(struct dsa_switch *ds, int port, +			  struct ethtool_ts_info *info); + +long hellcreek_hwtstamp_work(struct ptp_clock_info *ptp); + +int hellcreek_hwtstamp_setup(struct hellcreek *chip); +void hellcreek_hwtstamp_free(struct hellcreek *chip); + +#endif /* _HELLCREEK_HWTSTAMP_H_ */ diff --git a/drivers/net/dsa/hirschmann/hellcreek_ptp.c b/drivers/net/dsa/hirschmann/hellcreek_ptp.c new file mode 100644 index 000000000000..2572c6087bb5 --- /dev/null +++ b/drivers/net/dsa/hirschmann/hellcreek_ptp.c @@ -0,0 +1,452 @@ +// SPDX-License-Identifier: (GPL-2.0 OR MIT) +/* + * DSA driver for: + * Hirschmann Hellcreek TSN switch. + * + * Copyright (C) 2019,2020 Hochschule Offenburg + * Copyright (C) 2019,2020 Linutronix GmbH + * Authors: Kamil Alkhouri <kamil.alkhouri@hs-offenburg.de> + *	    Kurt Kanzenbach <kurt@linutronix.de> + */ + +#include <linux/ptp_clock_kernel.h> +#include "hellcreek.h" +#include "hellcreek_ptp.h" +#include "hellcreek_hwtstamp.h" + +u16 hellcreek_ptp_read(struct hellcreek *hellcreek, unsigned int offset) +{ +	return readw(hellcreek->ptp_base + offset); +} + +void hellcreek_ptp_write(struct hellcreek *hellcreek, u16 data, +			 unsigned int offset) +{ +	writew(data, hellcreek->ptp_base + offset); +} + +/* Get nanoseconds from PTP clock */ +static u64 hellcreek_ptp_clock_read(struct hellcreek *hellcreek) +{ +	u16 nsl, nsh; + +	/* Take a snapshot */ +	hellcreek_ptp_write(hellcreek, PR_COMMAND_C_SS, PR_COMMAND_C); + +	/* The time of the day is saved as 96 bits. However, due to hardware +	 * limitations the seconds are not or only partly kept in the PTP +	 * core. Currently only three bits for the seconds are available. That's +	 * why only the nanoseconds are used and the seconds are tracked in +	 * software. Anyway due to internal locking all five registers should be +	 * read. +	 */ +	nsh = hellcreek_ptp_read(hellcreek, PR_SS_SYNC_DATA_C); +	nsh = hellcreek_ptp_read(hellcreek, PR_SS_SYNC_DATA_C); +	nsh = hellcreek_ptp_read(hellcreek, PR_SS_SYNC_DATA_C); +	nsh = hellcreek_ptp_read(hellcreek, PR_SS_SYNC_DATA_C); +	nsl = hellcreek_ptp_read(hellcreek, PR_SS_SYNC_DATA_C); + +	return (u64)nsl | ((u64)nsh << 16); +} + +static u64 __hellcreek_ptp_gettime(struct hellcreek *hellcreek) +{ +	u64 ns; + +	ns = hellcreek_ptp_clock_read(hellcreek); +	if (ns < hellcreek->last_ts) +		hellcreek->seconds++; +	hellcreek->last_ts = ns; +	ns += hellcreek->seconds * NSEC_PER_SEC; + +	return ns; +} + +/* Retrieve the seconds parts in nanoseconds for a packet timestamped with @ns. + * There has to be a check whether an overflow occurred between the packet + * arrival and now. If so use the correct seconds (-1) for calculating the + * packet arrival time. + */ +u64 hellcreek_ptp_gettime_seconds(struct hellcreek *hellcreek, u64 ns) +{ +	u64 s; + +	__hellcreek_ptp_gettime(hellcreek); +	if (hellcreek->last_ts > ns) +		s = hellcreek->seconds * NSEC_PER_SEC; +	else +		s = (hellcreek->seconds - 1) * NSEC_PER_SEC; + +	return s; +} + +static int hellcreek_ptp_gettime(struct ptp_clock_info *ptp, +				 struct timespec64 *ts) +{ +	struct hellcreek *hellcreek = ptp_to_hellcreek(ptp); +	u64 ns; + +	mutex_lock(&hellcreek->ptp_lock); +	ns = __hellcreek_ptp_gettime(hellcreek); +	mutex_unlock(&hellcreek->ptp_lock); + +	*ts = ns_to_timespec64(ns); + +	return 0; +} + +static int hellcreek_ptp_settime(struct ptp_clock_info *ptp, +				 const struct timespec64 *ts) +{ +	struct hellcreek *hellcreek = ptp_to_hellcreek(ptp); +	u16 secl, nsh, nsl; + +	secl = ts->tv_sec & 0xffff; +	nsh  = ((u32)ts->tv_nsec & 0xffff0000) >> 16; +	nsl  = ts->tv_nsec & 0xffff; + +	mutex_lock(&hellcreek->ptp_lock); + +	/* Update overflow data structure */ +	hellcreek->seconds = ts->tv_sec; +	hellcreek->last_ts = ts->tv_nsec; + +	/* Set time in clock */ +	hellcreek_ptp_write(hellcreek, 0x00, PR_CLOCK_WRITE_C); +	hellcreek_ptp_write(hellcreek, 0x00, PR_CLOCK_WRITE_C); +	hellcreek_ptp_write(hellcreek, secl, PR_CLOCK_WRITE_C); +	hellcreek_ptp_write(hellcreek, nsh,  PR_CLOCK_WRITE_C); +	hellcreek_ptp_write(hellcreek, nsl,  PR_CLOCK_WRITE_C); + +	mutex_unlock(&hellcreek->ptp_lock); + +	return 0; +} + +static int hellcreek_ptp_adjfine(struct ptp_clock_info *ptp, long scaled_ppm) +{ +	struct hellcreek *hellcreek = ptp_to_hellcreek(ptp); +	u16 negative = 0, addendh, addendl; +	u32 addend; +	u64 adj; + +	if (scaled_ppm < 0) { +		negative = 1; +		scaled_ppm = -scaled_ppm; +	} + +	/* IP-Core adjusts the nominal frequency by adding or subtracting 1 ns +	 * from the 8 ns (period of the oscillator) every time the accumulator +	 * register overflows. The value stored in the addend register is added +	 * to the accumulator register every 8 ns. +	 * +	 * addend value = (2^30 * accumulator_overflow_rate) / +	 *                oscillator_frequency +	 * where: +	 * +	 * oscillator_frequency = 125 MHz +	 * accumulator_overflow_rate = 125 MHz * scaled_ppm * 2^-16 * 10^-6 * 8 +	 */ +	adj = scaled_ppm; +	adj <<= 11; +	addend = (u32)div_u64(adj, 15625); + +	addendh = (addend & 0xffff0000) >> 16; +	addendl = addend & 0xffff; + +	negative = (negative << 15) & 0x8000; + +	mutex_lock(&hellcreek->ptp_lock); + +	/* Set drift register */ +	hellcreek_ptp_write(hellcreek, negative, PR_CLOCK_DRIFT_C); +	hellcreek_ptp_write(hellcreek, 0x00, PR_CLOCK_DRIFT_C); +	hellcreek_ptp_write(hellcreek, 0x00, PR_CLOCK_DRIFT_C); +	hellcreek_ptp_write(hellcreek, addendh,  PR_CLOCK_DRIFT_C); +	hellcreek_ptp_write(hellcreek, addendl,  PR_CLOCK_DRIFT_C); + +	mutex_unlock(&hellcreek->ptp_lock); + +	return 0; +} + +static int hellcreek_ptp_adjtime(struct ptp_clock_info *ptp, s64 delta) +{ +	struct hellcreek *hellcreek = ptp_to_hellcreek(ptp); +	u16 negative = 0, counth, countl; +	u32 count_val; + +	/* If the offset is larger than IP-Core slow offset resources. Don't +	 * consider slow adjustment. Rather, add the offset directly to the +	 * current time +	 */ +	if (abs(delta) > MAX_SLOW_OFFSET_ADJ) { +		struct timespec64 now, then = ns_to_timespec64(delta); + +		hellcreek_ptp_gettime(ptp, &now); +		now = timespec64_add(now, then); +		hellcreek_ptp_settime(ptp, &now); + +		return 0; +	} + +	if (delta < 0) { +		negative = 1; +		delta = -delta; +	} + +	/* 'count_val' does not exceed the maximum register size (2^30) */ +	count_val = div_s64(delta, MAX_NS_PER_STEP); + +	counth = (count_val & 0xffff0000) >> 16; +	countl = count_val & 0xffff; + +	negative = (negative << 15) & 0x8000; + +	mutex_lock(&hellcreek->ptp_lock); + +	/* Set offset write register */ +	hellcreek_ptp_write(hellcreek, negative, PR_CLOCK_OFFSET_C); +	hellcreek_ptp_write(hellcreek, MAX_NS_PER_STEP, PR_CLOCK_OFFSET_C); +	hellcreek_ptp_write(hellcreek, MIN_CLK_CYCLES_BETWEEN_STEPS, +			    PR_CLOCK_OFFSET_C); +	hellcreek_ptp_write(hellcreek, countl,  PR_CLOCK_OFFSET_C); +	hellcreek_ptp_write(hellcreek, counth,  PR_CLOCK_OFFSET_C); + +	mutex_unlock(&hellcreek->ptp_lock); + +	return 0; +} + +static int hellcreek_ptp_enable(struct ptp_clock_info *ptp, +				struct ptp_clock_request *rq, int on) +{ +	return -EOPNOTSUPP; +} + +static void hellcreek_ptp_overflow_check(struct work_struct *work) +{ +	struct delayed_work *dw = to_delayed_work(work); +	struct hellcreek *hellcreek; + +	hellcreek = dw_overflow_to_hellcreek(dw); + +	mutex_lock(&hellcreek->ptp_lock); +	__hellcreek_ptp_gettime(hellcreek); +	mutex_unlock(&hellcreek->ptp_lock); + +	schedule_delayed_work(&hellcreek->overflow_work, +			      HELLCREEK_OVERFLOW_PERIOD); +} + +static enum led_brightness hellcreek_get_brightness(struct hellcreek *hellcreek, +						    int led) +{ +	return (hellcreek->status_out & led) ? 1 : 0; +} + +static void hellcreek_set_brightness(struct hellcreek *hellcreek, int led, +				     enum led_brightness b) +{ +	mutex_lock(&hellcreek->ptp_lock); + +	if (b) +		hellcreek->status_out |= led; +	else +		hellcreek->status_out &= ~led; + +	hellcreek_ptp_write(hellcreek, hellcreek->status_out, STATUS_OUT); + +	mutex_unlock(&hellcreek->ptp_lock); +} + +static void hellcreek_led_sync_good_set(struct led_classdev *ldev, +					enum led_brightness b) +{ +	struct hellcreek *hellcreek = led_to_hellcreek(ldev, led_sync_good); + +	hellcreek_set_brightness(hellcreek, STATUS_OUT_SYNC_GOOD, b); +} + +static enum led_brightness hellcreek_led_sync_good_get(struct led_classdev *ldev) +{ +	struct hellcreek *hellcreek = led_to_hellcreek(ldev, led_sync_good); + +	return hellcreek_get_brightness(hellcreek, STATUS_OUT_SYNC_GOOD); +} + +static void hellcreek_led_is_gm_set(struct led_classdev *ldev, +				    enum led_brightness b) +{ +	struct hellcreek *hellcreek = led_to_hellcreek(ldev, led_is_gm); + +	hellcreek_set_brightness(hellcreek, STATUS_OUT_IS_GM, b); +} + +static enum led_brightness hellcreek_led_is_gm_get(struct led_classdev *ldev) +{ +	struct hellcreek *hellcreek = led_to_hellcreek(ldev, led_is_gm); + +	return hellcreek_get_brightness(hellcreek, STATUS_OUT_IS_GM); +} + +/* There two available LEDs internally called sync_good and is_gm. However, the + * user might want to use a different label and specify the default state. Take + * those properties from device tree. + */ +static int hellcreek_led_setup(struct hellcreek *hellcreek) +{ +	struct device_node *leds, *led = NULL; +	const char *label, *state; +	int ret = -EINVAL; + +	leds = of_find_node_by_name(hellcreek->dev->of_node, "leds"); +	if (!leds) { +		dev_err(hellcreek->dev, "No LEDs specified in device tree!\n"); +		return ret; +	} + +	hellcreek->status_out = 0; + +	led = of_get_next_available_child(leds, led); +	if (!led) { +		dev_err(hellcreek->dev, "First LED not specified!\n"); +		goto out; +	} + +	ret = of_property_read_string(led, "label", &label); +	hellcreek->led_sync_good.name = ret ? "sync_good" : label; + +	ret = of_property_read_string(led, "default-state", &state); +	if (!ret) { +		if (!strcmp(state, "on")) +			hellcreek->led_sync_good.brightness = 1; +		else if (!strcmp(state, "off")) +			hellcreek->led_sync_good.brightness = 0; +		else if (!strcmp(state, "keep")) +			hellcreek->led_sync_good.brightness = +				hellcreek_get_brightness(hellcreek, +							 STATUS_OUT_SYNC_GOOD); +	} + +	hellcreek->led_sync_good.max_brightness = 1; +	hellcreek->led_sync_good.brightness_set = hellcreek_led_sync_good_set; +	hellcreek->led_sync_good.brightness_get = hellcreek_led_sync_good_get; + +	led = of_get_next_available_child(leds, led); +	if (!led) { +		dev_err(hellcreek->dev, "Second LED not specified!\n"); +		ret = -EINVAL; +		goto out; +	} + +	ret = of_property_read_string(led, "label", &label); +	hellcreek->led_is_gm.name = ret ? "is_gm" : label; + +	ret = of_property_read_string(led, "default-state", &state); +	if (!ret) { +		if (!strcmp(state, "on")) +			hellcreek->led_is_gm.brightness = 1; +		else if (!strcmp(state, "off")) +			hellcreek->led_is_gm.brightness = 0; +		else if (!strcmp(state, "keep")) +			hellcreek->led_is_gm.brightness = +				hellcreek_get_brightness(hellcreek, +							 STATUS_OUT_IS_GM); +	} + +	hellcreek->led_is_gm.max_brightness = 1; +	hellcreek->led_is_gm.brightness_set = hellcreek_led_is_gm_set; +	hellcreek->led_is_gm.brightness_get = hellcreek_led_is_gm_get; + +	/* Set initial state */ +	if (hellcreek->led_sync_good.brightness == 1) +		hellcreek_set_brightness(hellcreek, STATUS_OUT_SYNC_GOOD, 1); +	if (hellcreek->led_is_gm.brightness == 1) +		hellcreek_set_brightness(hellcreek, STATUS_OUT_IS_GM, 1); + +	/* Register both leds */ +	led_classdev_register(hellcreek->dev, &hellcreek->led_sync_good); +	led_classdev_register(hellcreek->dev, &hellcreek->led_is_gm); + +	ret = 0; + +out: +	of_node_put(leds); + +	return ret; +} + +int hellcreek_ptp_setup(struct hellcreek *hellcreek) +{ +	u16 status; +	int ret; + +	/* Set up the overflow work */ +	INIT_DELAYED_WORK(&hellcreek->overflow_work, +			  hellcreek_ptp_overflow_check); + +	/* Setup PTP clock */ +	hellcreek->ptp_clock_info.owner = THIS_MODULE; +	snprintf(hellcreek->ptp_clock_info.name, +		 sizeof(hellcreek->ptp_clock_info.name), +		 dev_name(hellcreek->dev)); + +	/* IP-Core can add up to 0.5 ns per 8 ns cycle, which means +	 * accumulator_overflow_rate shall not exceed 62.5 MHz (which adjusts +	 * the nominal frequency by 6.25%) +	 */ +	hellcreek->ptp_clock_info.max_adj     = 62500000; +	hellcreek->ptp_clock_info.n_alarm     = 0; +	hellcreek->ptp_clock_info.n_pins      = 0; +	hellcreek->ptp_clock_info.n_ext_ts    = 0; +	hellcreek->ptp_clock_info.n_per_out   = 0; +	hellcreek->ptp_clock_info.pps	      = 0; +	hellcreek->ptp_clock_info.adjfine     = hellcreek_ptp_adjfine; +	hellcreek->ptp_clock_info.adjtime     = hellcreek_ptp_adjtime; +	hellcreek->ptp_clock_info.gettime64   = hellcreek_ptp_gettime; +	hellcreek->ptp_clock_info.settime64   = hellcreek_ptp_settime; +	hellcreek->ptp_clock_info.enable      = hellcreek_ptp_enable; +	hellcreek->ptp_clock_info.do_aux_work = hellcreek_hwtstamp_work; + +	hellcreek->ptp_clock = ptp_clock_register(&hellcreek->ptp_clock_info, +						  hellcreek->dev); +	if (IS_ERR(hellcreek->ptp_clock)) +		return PTR_ERR(hellcreek->ptp_clock); + +	/* Enable the offset correction process, if no offset correction is +	 * already taking place +	 */ +	status = hellcreek_ptp_read(hellcreek, PR_CLOCK_STATUS_C); +	if (!(status & PR_CLOCK_STATUS_C_OFS_ACT)) +		hellcreek_ptp_write(hellcreek, +				    status | PR_CLOCK_STATUS_C_ENA_OFS, +				    PR_CLOCK_STATUS_C); + +	/* Enable the drift correction process */ +	hellcreek_ptp_write(hellcreek, status | PR_CLOCK_STATUS_C_ENA_DRIFT, +			    PR_CLOCK_STATUS_C); + +	/* LED setup */ +	ret = hellcreek_led_setup(hellcreek); +	if (ret) { +		if (hellcreek->ptp_clock) +			ptp_clock_unregister(hellcreek->ptp_clock); +		return ret; +	} + +	schedule_delayed_work(&hellcreek->overflow_work, +			      HELLCREEK_OVERFLOW_PERIOD); + +	return 0; +} + +void hellcreek_ptp_free(struct hellcreek *hellcreek) +{ +	led_classdev_unregister(&hellcreek->led_is_gm); +	led_classdev_unregister(&hellcreek->led_sync_good); +	cancel_delayed_work_sync(&hellcreek->overflow_work); +	if (hellcreek->ptp_clock) +		ptp_clock_unregister(hellcreek->ptp_clock); +	hellcreek->ptp_clock = NULL; +} diff --git a/drivers/net/dsa/hirschmann/hellcreek_ptp.h b/drivers/net/dsa/hirschmann/hellcreek_ptp.h new file mode 100644 index 000000000000..0b51392c7e56 --- /dev/null +++ b/drivers/net/dsa/hirschmann/hellcreek_ptp.h @@ -0,0 +1,76 @@ +/* SPDX-License-Identifier: (GPL-2.0 OR MIT) */ +/* + * DSA driver for: + * Hirschmann Hellcreek TSN switch. + * + * Copyright (C) 2019,2020 Hochschule Offenburg + * Copyright (C) 2019,2020 Linutronix GmbH + * Authors: Kurt Kanzenbach <kurt@linutronix.de> + *	    Kamil Alkhouri <kamil.alkhouri@hs-offenburg.de> + */ + +#ifndef _HELLCREEK_PTP_H_ +#define _HELLCREEK_PTP_H_ + +#include <linux/bitops.h> +#include <linux/ptp_clock_kernel.h> + +#include "hellcreek.h" + +/* Every jump in time is 7 ns */ +#define MAX_NS_PER_STEP			7L + +/* Correct offset at every clock cycle */ +#define MIN_CLK_CYCLES_BETWEEN_STEPS	0 + +/* Maximum available slow offset resources */ +#define MAX_SLOW_OFFSET_ADJ					\ +	((unsigned long long)((1 << 30) - 1) * MAX_NS_PER_STEP) + +/* four times a second overflow check */ +#define HELLCREEK_OVERFLOW_PERIOD	(HZ / 4) + +/* PTP Register */ +#define PR_SETTINGS_C			(0x09 * 2) +#define PR_SETTINGS_C_RES3TS		BIT(4) +#define PR_SETTINGS_C_TS_SRC_TK_SHIFT	8 +#define PR_SETTINGS_C_TS_SRC_TK_MASK	GENMASK(9, 8) +#define PR_COMMAND_C			(0x0a * 2) +#define PR_COMMAND_C_SS			BIT(0) + +#define PR_CLOCK_STATUS_C		(0x0c * 2) +#define PR_CLOCK_STATUS_C_ENA_DRIFT	BIT(12) +#define PR_CLOCK_STATUS_C_OFS_ACT	BIT(13) +#define PR_CLOCK_STATUS_C_ENA_OFS	BIT(14) + +#define PR_CLOCK_READ_C			(0x0d * 2) +#define PR_CLOCK_WRITE_C		(0x0e * 2) +#define PR_CLOCK_OFFSET_C		(0x0f * 2) +#define PR_CLOCK_DRIFT_C		(0x10 * 2) + +#define PR_SS_FREE_DATA_C		(0x12 * 2) +#define PR_SS_SYNT_DATA_C		(0x14 * 2) +#define PR_SS_SYNC_DATA_C		(0x16 * 2) +#define PR_SS_DRAC_DATA_C		(0x18 * 2) + +#define STATUS_OUT			(0x60 * 2) +#define STATUS_OUT_SYNC_GOOD		BIT(0) +#define STATUS_OUT_IS_GM		BIT(1) + +int hellcreek_ptp_setup(struct hellcreek *hellcreek); +void hellcreek_ptp_free(struct hellcreek *hellcreek); +u16 hellcreek_ptp_read(struct hellcreek *hellcreek, unsigned int offset); +void hellcreek_ptp_write(struct hellcreek *hellcreek, u16 data, +			 unsigned int offset); +u64 hellcreek_ptp_gettime_seconds(struct hellcreek *hellcreek, u64 ns); + +#define ptp_to_hellcreek(ptp)					\ +	container_of(ptp, struct hellcreek, ptp_clock_info) + +#define dw_overflow_to_hellcreek(dw)				\ +	container_of(dw, struct hellcreek, overflow_work) + +#define led_to_hellcreek(ldev, led)				\ +	container_of(ldev, struct hellcreek, led) + +#endif /* _HELLCREEK_PTP_H_ */ diff --git a/drivers/net/dsa/lantiq_gswip.c b/drivers/net/dsa/lantiq_gswip.c index 74db81dafee3..09701c17f3f6 100644 --- a/drivers/net/dsa/lantiq_gswip.c +++ b/drivers/net/dsa/lantiq_gswip.c @@ -26,6 +26,7 @@   */  #include <linux/clk.h> +#include <linux/delay.h>  #include <linux/etherdevice.h>  #include <linux/firmware.h>  #include <linux/if_bridge.h> @@ -1837,6 +1838,16 @@ static int gswip_gphy_fw_list(struct gswip_priv *priv,  		i++;  	} +	/* The standalone PHY11G requires 300ms to be fully +	 * initialized and ready for any MDIO communication after being +	 * taken out of reset. For the SoC-internal GPHY variant there +	 * is no (known) documentation for the minimum time after a +	 * reset. Use the same value as for the standalone variant as +	 * some users have reported internal PHYs not being detected +	 * without any delay. +	 */ +	msleep(300); +  	return 0;  remove_gphy: diff --git a/drivers/net/dsa/microchip/ksz8795.c b/drivers/net/dsa/microchip/ksz8795.c index 1e101ab56cea..c973db101b72 100644 --- a/drivers/net/dsa/microchip/ksz8795.c +++ b/drivers/net/dsa/microchip/ksz8795.c @@ -23,7 +23,7 @@  static const struct {  	char string[ETH_GSTRING_LEN]; -} mib_names[TOTAL_SWITCH_COUNTER_NUM] = { +} mib_names[] = {  	{ "rx_hi" },  	{ "rx_undersize" },  	{ "rx_fragments" }, @@ -125,7 +125,7 @@ static void ksz8795_r_mib_cnt(struct ksz_device *dev, int port, u16 addr,  	u8 check;  	int loop; -	ctrl_addr = addr + SWITCH_COUNTER_NUM * port; +	ctrl_addr = addr + dev->reg_mib_cnt * port;  	ctrl_addr |= IND_ACC_TABLE(TABLE_MIB | TABLE_READ);  	mutex_lock(&dev->alu_mutex); @@ -156,7 +156,7 @@ static void ksz8795_r_mib_pkt(struct ksz_device *dev, int port, u16 addr,  	u8 check;  	int loop; -	addr -= SWITCH_COUNTER_NUM; +	addr -= dev->reg_mib_cnt;  	ctrl_addr = (KS_MIB_TOTAL_RX_1 - KS_MIB_TOTAL_RX_0) * port;  	ctrl_addr += addr + KS_MIB_TOTAL_RX_0;  	ctrl_addr |= IND_ACC_TABLE(TABLE_MIB | TABLE_READ); @@ -418,8 +418,8 @@ static void ksz8795_r_vlan_entries(struct ksz_device *dev, u16 addr)  	int i;  	ksz8795_r_table(dev, TABLE_VLAN, addr, &data); -	addr *= 4; -	for (i = 0; i < 4; i++) { +	addr *= dev->phy_port_cnt; +	for (i = 0; i < dev->phy_port_cnt; i++) {  		dev->vlan_cache[addr + i].table[0] = (u16)data;  		data >>= VLAN_TABLE_S;  	} @@ -433,7 +433,7 @@ static void ksz8795_r_vlan_table(struct ksz_device *dev, u16 vid, u16 *vlan)  	u64 buf;  	data = (u16 *)&buf; -	addr = vid / 4; +	addr = vid / dev->phy_port_cnt;  	index = vid & 3;  	ksz8795_r_table(dev, TABLE_VLAN, addr, &buf);  	*vlan = data[index]; @@ -447,7 +447,7 @@ static void ksz8795_w_vlan_table(struct ksz_device *dev, u16 vid, u16 vlan)  	u64 buf;  	data = (u16 *)&buf; -	addr = vid / 4; +	addr = vid / dev->phy_port_cnt;  	index = vid & 3;  	ksz8795_r_table(dev, TABLE_VLAN, addr, &buf);  	data[index] = vlan; @@ -654,9 +654,10 @@ static enum dsa_tag_protocol ksz8795_get_tag_protocol(struct dsa_switch *ds,  static void ksz8795_get_strings(struct dsa_switch *ds, int port,  				u32 stringset, uint8_t *buf)  { +	struct ksz_device *dev = ds->priv;  	int i; -	for (i = 0; i < TOTAL_SWITCH_COUNTER_NUM; i++) { +	for (i = 0; i < dev->mib_cnt; i++) {  		memcpy(buf + i * ETH_GSTRING_LEN, mib_names[i].string,  		       ETH_GSTRING_LEN);  	} @@ -691,12 +692,12 @@ static void ksz8795_port_stp_state_set(struct dsa_switch *ds, int port,  	switch (state) {  	case BR_STATE_DISABLED:  		data |= PORT_LEARN_DISABLE; -		if (port < SWITCH_PORT_NUM) +		if (port < dev->phy_port_cnt)  			member = 0;  		break;  	case BR_STATE_LISTENING:  		data |= (PORT_RX_ENABLE | PORT_LEARN_DISABLE); -		if (port < SWITCH_PORT_NUM && +		if (port < dev->phy_port_cnt &&  		    p->stp_state == BR_STATE_DISABLED)  			member = dev->host_mask | p->vid_member;  		break; @@ -720,7 +721,7 @@ static void ksz8795_port_stp_state_set(struct dsa_switch *ds, int port,  		break;  	case BR_STATE_BLOCKING:  		data |= PORT_LEARN_DISABLE; -		if (port < SWITCH_PORT_NUM && +		if (port < dev->phy_port_cnt &&  		    p->stp_state == BR_STATE_DISABLED)  			member = dev->host_mask | p->vid_member;  		break; @@ -750,17 +751,17 @@ static void ksz8795_port_stp_state_set(struct dsa_switch *ds, int port,  static void ksz8795_flush_dyn_mac_table(struct ksz_device *dev, int port)  { -	u8 learn[TOTAL_PORT_NUM]; +	u8 learn[DSA_MAX_PORTS];  	int first, index, cnt;  	struct ksz_port *p; -	if ((uint)port < TOTAL_PORT_NUM) { +	if ((uint)port < dev->port_cnt) {  		first = port;  		cnt = port + 1;  	} else {  		/* Flush all ports. */  		first = 0; -		cnt = dev->mib_port_cnt; +		cnt = dev->port_cnt;  	}  	for (index = first; index < cnt; index++) {  		p = &dev->ports[index]; @@ -992,8 +993,6 @@ static void ksz8795_config_cpu_port(struct dsa_switch *ds)  	u8 remote;  	int i; -	ds->num_ports = dev->port_cnt + 1; -  	/* Switch marks the maximum frame with extra byte as oversize. */  	ksz_cfg(dev, REG_SW_CTRL_2, SW_LEGAL_PACKET_DISABLE, true);  	ksz_cfg(dev, S_TAIL_TAG_CTRL, SW_TAIL_TAG_ENABLE, true); @@ -1005,7 +1004,7 @@ static void ksz8795_config_cpu_port(struct dsa_switch *ds)  	ksz8795_port_setup(dev, dev->cpu_port, true);  	dev->member = dev->host_mask; -	for (i = 0; i < SWITCH_PORT_NUM; i++) { +	for (i = 0; i < dev->phy_port_cnt; i++) {  		p = &dev->ports[i];  		/* Initialize to non-zero so that ksz_cfg_port_member() will @@ -1016,7 +1015,7 @@ static void ksz8795_config_cpu_port(struct dsa_switch *ds)  		ksz8795_port_stp_state_set(ds, i, BR_STATE_DISABLED);  		/* Last port may be disabled. */ -		if (i == dev->port_cnt) +		if (i == dev->phy_port_cnt)  			break;  		p->on = 1;  		p->phy = 1; @@ -1085,7 +1084,7 @@ static int ksz8795_setup(struct dsa_switch *ds)  			   (BROADCAST_STORM_VALUE *  			   BROADCAST_STORM_PROT_RATE) / 100); -	for (i = 0; i < VLAN_TABLE_ENTRIES; i++) +	for (i = 0; i < (dev->num_vlans / 4); i++)  		ksz8795_r_vlan_entries(dev, i);  	/* Setup STP address for STP operation. */ @@ -1150,10 +1149,6 @@ static int ksz8795_switch_detect(struct ksz_device *dev)  	    (id2 != CHIP_ID_94 && id2 != CHIP_ID_95))  		return -ENODEV; -	dev->mib_port_cnt = TOTAL_PORT_NUM; -	dev->phy_port_cnt = SWITCH_PORT_NUM; -	dev->port_cnt = SWITCH_PORT_NUM; -  	if (id2 == CHIP_ID_95) {  		u8 val; @@ -1162,17 +1157,12 @@ static int ksz8795_switch_detect(struct ksz_device *dev)  		if (val & PORT_FIBER_MODE)  			id2 = 0x65;  	} else if (id2 == CHIP_ID_94) { -		dev->port_cnt--; -		dev->last_port = dev->port_cnt;  		id2 = 0x94;  	}  	id16 &= ~0xff;  	id16 |= id2;  	dev->chip_id = id16; -	dev->cpu_port = dev->mib_port_cnt - 1; -	dev->host_mask = BIT(dev->cpu_port); -  	return 0;  } @@ -1194,7 +1184,7 @@ static const struct ksz_chip_data ksz8795_switch_chips[] = {  		.num_alus = 0,  		.num_statics = 8,  		.cpu_ports = 0x10,	/* can be configured as cpu port */ -		.port_cnt = 4,		/* total physical port count */ +		.port_cnt = 5,		/* total cpu and user ports */  	},  	{  		.chip_id = 0x8794, @@ -1203,7 +1193,7 @@ static const struct ksz_chip_data ksz8795_switch_chips[] = {  		.num_alus = 0,  		.num_statics = 8,  		.cpu_ports = 0x10,	/* can be configured as cpu port */ -		.port_cnt = 3,		/* total physical port count */ +		.port_cnt = 4,		/* total cpu and user ports */  	},  	{  		.chip_id = 0x8765, @@ -1212,7 +1202,7 @@ static const struct ksz_chip_data ksz8795_switch_chips[] = {  		.num_alus = 0,  		.num_statics = 8,  		.cpu_ports = 0x10,	/* can be configured as cpu port */ -		.port_cnt = 4,		/* total physical port count */ +		.port_cnt = 5,		/* total cpu and user ports */  	},  }; @@ -1244,27 +1234,32 @@ static int ksz8795_switch_init(struct ksz_device *dev)  	dev->port_mask = BIT(dev->port_cnt) - 1;  	dev->port_mask |= dev->host_mask; -	dev->reg_mib_cnt = SWITCH_COUNTER_NUM; -	dev->mib_cnt = TOTAL_SWITCH_COUNTER_NUM; +	dev->reg_mib_cnt = KSZ8795_COUNTER_NUM; +	dev->mib_cnt = ARRAY_SIZE(mib_names); + +	dev->phy_port_cnt = dev->port_cnt - 1; + +	dev->cpu_port = dev->port_cnt - 1; +	dev->host_mask = BIT(dev->cpu_port); -	i = dev->mib_port_cnt; -	dev->ports = devm_kzalloc(dev->dev, sizeof(struct ksz_port) * i, +	dev->ports = devm_kzalloc(dev->dev, +				  dev->port_cnt * sizeof(struct ksz_port),  				  GFP_KERNEL);  	if (!dev->ports)  		return -ENOMEM; -	for (i = 0; i < dev->mib_port_cnt; i++) { +	for (i = 0; i < dev->port_cnt; i++) {  		mutex_init(&dev->ports[i].mib.cnt_mutex);  		dev->ports[i].mib.counters =  			devm_kzalloc(dev->dev,  				     sizeof(u64) * -				     (TOTAL_SWITCH_COUNTER_NUM + 1), +				     (dev->mib_cnt + 1),  				     GFP_KERNEL);  		if (!dev->ports[i].mib.counters)  			return -ENOMEM;  	}  	/* set the real number of ports */ -	dev->ds->num_ports = dev->port_cnt + 1; +	dev->ds->num_ports = dev->port_cnt;  	return 0;  } diff --git a/drivers/net/dsa/microchip/ksz8795_reg.h b/drivers/net/dsa/microchip/ksz8795_reg.h index 3a50462df8fa..40372047d40d 100644 --- a/drivers/net/dsa/microchip/ksz8795_reg.h +++ b/drivers/net/dsa/microchip/ksz8795_reg.h @@ -846,16 +846,7 @@  #define KS_PRIO_IN_REG			4 -#define TOTAL_PORT_NUM			5 - -/* Host port can only be last of them. */ -#define SWITCH_PORT_NUM			(TOTAL_PORT_NUM - 1) -  #define KSZ8795_COUNTER_NUM		0x20 -#define TOTAL_KSZ8795_COUNTER_NUM	(KSZ8795_COUNTER_NUM + 4) - -#define SWITCH_COUNTER_NUM		KSZ8795_COUNTER_NUM -#define TOTAL_SWITCH_COUNTER_NUM	TOTAL_KSZ8795_COUNTER_NUM  /* Common names used by other drivers */ @@ -998,7 +989,6 @@  #define TAIL_TAG_OVERRIDE		BIT(6)  #define TAIL_TAG_LOOKUP			BIT(7) -#define VLAN_TABLE_ENTRIES		(4096 / 4)  #define FID_ENTRIES			128  #endif diff --git a/drivers/net/dsa/microchip/ksz8795_spi.c b/drivers/net/dsa/microchip/ksz8795_spi.c index 8b00f8e6c02f..f98432a3e2b5 100644 --- a/drivers/net/dsa/microchip/ksz8795_spi.c +++ b/drivers/net/dsa/microchip/ksz8795_spi.c @@ -49,6 +49,12 @@ static int ksz8795_spi_probe(struct spi_device *spi)  	if (spi->dev.platform_data)  		dev->pdata = spi->dev.platform_data; +	/* setup spi */ +	spi->mode = SPI_MODE_3; +	ret = spi_setup(spi); +	if (ret) +		return ret; +  	ret = ksz8795_switch_register(dev);  	/* Main DSA driver may not be started yet. */ diff --git a/drivers/net/dsa/microchip/ksz9477.c b/drivers/net/dsa/microchip/ksz9477.c index abfd3802bb51..42e647b67abd 100644 --- a/drivers/net/dsa/microchip/ksz9477.c +++ b/drivers/net/dsa/microchip/ksz9477.c @@ -478,7 +478,7 @@ static void ksz9477_flush_dyn_mac_table(struct ksz_device *dev, int port)  			   SW_FLUSH_OPTION_M << SW_FLUSH_OPTION_S,  			   SW_FLUSH_OPTION_DYN_MAC << SW_FLUSH_OPTION_S); -	if (port < dev->mib_port_cnt) { +	if (port < dev->port_cnt) {  		/* flush individual port */  		ksz_pread8(dev, port, P_STP_CTRL, &data);  		if (!(data & PORT_LEARN_DISABLE)) @@ -1267,8 +1267,6 @@ static void ksz9477_config_cpu_port(struct dsa_switch *ds)  	struct ksz_port *p;  	int i; -	ds->num_ports = dev->port_cnt; -  	for (i = 0; i < dev->port_cnt; i++) {  		if (dsa_is_cpu_port(ds, i) && (dev->cpu_ports & (1 << i))) {  			phy_interface_t interface; @@ -1319,7 +1317,7 @@ static void ksz9477_config_cpu_port(struct dsa_switch *ds)  	dev->member = dev->host_mask; -	for (i = 0; i < dev->mib_port_cnt; i++) { +	for (i = 0; i < dev->port_cnt; i++) {  		if (i == dev->cpu_port)  			continue;  		p = &dev->ports[i]; @@ -1446,7 +1444,6 @@ static int ksz9477_switch_detect(struct ksz_device *dev)  		return ret;  	/* Number of ports can be reduced depending on chip. */ -	dev->mib_port_cnt = TOTAL_PORT_NUM;  	dev->phy_port_cnt = 5;  	/* Default capability is gigabit capable. */ @@ -1463,7 +1460,6 @@ static int ksz9477_switch_detect(struct ksz_device *dev)  		/* Chip does not support gigabit. */  		if (data8 & SW_QW_ABLE)  			dev->features &= ~GBIT_SUPPORT; -		dev->mib_port_cnt = 3;  		dev->phy_port_cnt = 2;  	} else {  		dev_info(dev->dev, "Found KSZ9477 or compatible\n"); @@ -1566,12 +1562,12 @@ static int ksz9477_switch_init(struct ksz_device *dev)  	dev->reg_mib_cnt = SWITCH_COUNTER_NUM;  	dev->mib_cnt = TOTAL_SWITCH_COUNTER_NUM; -	i = dev->mib_port_cnt; -	dev->ports = devm_kzalloc(dev->dev, sizeof(struct ksz_port) * i, +	dev->ports = devm_kzalloc(dev->dev, +				  dev->port_cnt * sizeof(struct ksz_port),  				  GFP_KERNEL);  	if (!dev->ports)  		return -ENOMEM; -	for (i = 0; i < dev->mib_port_cnt; i++) { +	for (i = 0; i < dev->port_cnt; i++) {  		mutex_init(&dev->ports[i].mib.cnt_mutex);  		dev->ports[i].mib.counters =  			devm_kzalloc(dev->dev, diff --git a/drivers/net/dsa/microchip/ksz9477_spi.c b/drivers/net/dsa/microchip/ksz9477_spi.c index 1142768969c2..15bc11b3cda4 100644 --- a/drivers/net/dsa/microchip/ksz9477_spi.c +++ b/drivers/net/dsa/microchip/ksz9477_spi.c @@ -48,6 +48,12 @@ static int ksz9477_spi_probe(struct spi_device *spi)  	if (spi->dev.platform_data)  		dev->pdata = spi->dev.platform_data; +	/* setup spi */ +	spi->mode = SPI_MODE_3; +	ret = spi_setup(spi); +	if (ret) +		return ret; +  	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 0ef854911f21..cf743133b0b9 100644 --- a/drivers/net/dsa/microchip/ksz_common.c +++ b/drivers/net/dsa/microchip/ksz_common.c @@ -72,7 +72,7 @@ static void ksz_mib_read_work(struct work_struct *work)  	struct ksz_port *p;  	int i; -	for (i = 0; i < dev->mib_port_cnt; i++) { +	for (i = 0; i < dev->port_cnt; i++) {  		if (dsa_is_unused_port(dev->ds, i))  			continue; @@ -103,7 +103,7 @@ void ksz_init_mib_timer(struct ksz_device *dev)  	INIT_DELAYED_WORK(&dev->mib_read, ksz_mib_read_work); -	for (i = 0; i < dev->mib_port_cnt; i++) +	for (i = 0; i < dev->port_cnt; i++)  		dev->dev_ops->port_init_cnt(dev, i);  }  EXPORT_SYMBOL_GPL(ksz_init_mib_timer); @@ -426,7 +426,9 @@ int ksz_switch_register(struct ksz_device *dev,  		ret = of_get_phy_mode(dev->dev->of_node, &interface);  		if (ret == 0)  			dev->compat_interface = interface; -		ports = of_get_child_by_name(dev->dev->of_node, "ports"); +		ports = of_get_child_by_name(dev->dev->of_node, "ethernet-ports"); +		if (!ports) +			ports = of_get_child_by_name(dev->dev->of_node, "ports");  		if (ports)  			for_each_available_child_of_node(ports, port) {  				if (of_property_read_u32(port, "reg", diff --git a/drivers/net/dsa/microchip/ksz_common.h b/drivers/net/dsa/microchip/ksz_common.h index cf866e48ff66..720f22275c84 100644 --- a/drivers/net/dsa/microchip/ksz_common.h +++ b/drivers/net/dsa/microchip/ksz_common.h @@ -71,8 +71,6 @@ struct ksz_device {  	int port_cnt;  	int reg_mib_cnt;  	int mib_cnt; -	int mib_port_cnt; -	int last_port;			/* ports after that not used */  	phy_interface_t compat_interface;  	u32 regs_size;  	bool phy_errata_9477; diff --git a/drivers/net/dsa/mt7530.c b/drivers/net/dsa/mt7530.c index de7692b763d8..a67cac15a724 100644 --- a/drivers/net/dsa/mt7530.c +++ b/drivers/net/dsa/mt7530.c @@ -558,7 +558,7 @@ mt7531_pad_setup(struct dsa_switch *ds, phy_interface_t interface)  		val |= 0x190000 << RG_COREPLL_SDM_PCW_S;  		mt7530_write(priv, MT7531_PLLGP_CR0, val);  		break; -	}; +	}  	/* Set feedback divide ratio update signal to high */  	val = mt7530_read(priv, MT7531_PLLGP_CR0); @@ -870,6 +870,46 @@ mt7530_get_sset_count(struct dsa_switch *ds, int port, int sset)  	return ARRAY_SIZE(mt7530_mib);  } +static int +mt7530_set_ageing_time(struct dsa_switch *ds, unsigned int msecs) +{ +	struct mt7530_priv *priv = ds->priv; +	unsigned int secs = msecs / 1000; +	unsigned int tmp_age_count; +	unsigned int error = -1; +	unsigned int age_count; +	unsigned int age_unit; + +	/* Applied timer is (AGE_CNT + 1) * (AGE_UNIT + 1) seconds */ +	if (secs < 1 || secs > (AGE_CNT_MAX + 1) * (AGE_UNIT_MAX + 1)) +		return -ERANGE; + +	/* iterate through all possible age_count to find the closest pair */ +	for (tmp_age_count = 0; tmp_age_count <= AGE_CNT_MAX; ++tmp_age_count) { +		unsigned int tmp_age_unit = secs / (tmp_age_count + 1) - 1; + +		if (tmp_age_unit <= AGE_UNIT_MAX) { +			unsigned int tmp_error = secs - +				(tmp_age_count + 1) * (tmp_age_unit + 1); + +			/* found a closer pair */ +			if (error > tmp_error) { +				error = tmp_error; +				age_count = tmp_age_count; +				age_unit = tmp_age_unit; +			} + +			/* found the exact match, so break the loop */ +			if (!error) +				break; +		} +	} + +	mt7530_write(priv, MT7530_AAC, AGE_CNT(age_count) | AGE_UNIT(age_unit)); + +	return 0; +} +  static void mt7530_setup_port5(struct dsa_switch *ds, phy_interface_t interface)  {  	struct mt7530_priv *priv = ds->priv; @@ -1021,6 +1061,53 @@ mt7530_port_disable(struct dsa_switch *ds, int port)  	mutex_unlock(&priv->reg_mutex);  } +static int +mt7530_port_change_mtu(struct dsa_switch *ds, int port, int new_mtu) +{ +	struct mt7530_priv *priv = ds->priv; +	struct mii_bus *bus = priv->bus; +	int length; +	u32 val; + +	/* When a new MTU is set, DSA always set the CPU port's MTU to the +	 * largest MTU of the slave ports. Because the switch only has a global +	 * RX length register, only allowing CPU port here is enough. +	 */ +	if (!dsa_is_cpu_port(ds, port)) +		return 0; + +	mutex_lock_nested(&bus->mdio_lock, MDIO_MUTEX_NESTED); + +	val = mt7530_mii_read(priv, MT7530_GMACCR); +	val &= ~MAX_RX_PKT_LEN_MASK; + +	/* RX length also includes Ethernet header, MTK tag, and FCS length */ +	length = new_mtu + ETH_HLEN + MTK_HDR_LEN + ETH_FCS_LEN; +	if (length <= 1522) { +		val |= MAX_RX_PKT_LEN_1522; +	} else if (length <= 1536) { +		val |= MAX_RX_PKT_LEN_1536; +	} else if (length <= 1552) { +		val |= MAX_RX_PKT_LEN_1552; +	} else { +		val &= ~MAX_RX_JUMBO_MASK; +		val |= MAX_RX_JUMBO(DIV_ROUND_UP(length, 1024)); +		val |= MAX_RX_PKT_LEN_JUMBO; +	} + +	mt7530_mii_write(priv, MT7530_GMACCR, val); + +	mutex_unlock(&bus->mdio_lock); + +	return 0; +} + +static int +mt7530_port_max_mtu(struct dsa_switch *ds, int port) +{ +	return MT7530_MAX_MTU; +} +  static void  mt7530_stp_state_set(struct dsa_switch *ds, int port, u8 state)  { @@ -1570,6 +1657,7 @@ mt7530_setup(struct dsa_switch *ds)  	 */  	dn = dsa_to_port(ds, MT7530_CPU_PORT)->master->dev.of_node->parent;  	ds->configure_vlan_while_not_filtering = true; +	ds->mtu_enforcement_ingress = true;  	if (priv->id == ID_MT7530) {  		regulator_set_voltage(priv->core_pwr, 1000000, 1000000); @@ -1808,6 +1896,7 @@ mt7531_setup(struct dsa_switch *ds)  	}  	ds->configure_vlan_while_not_filtering = true; +	ds->mtu_enforcement_ingress = true;  	/* Flush the FDB table */  	ret = mt7530_fdb_cmd(priv, MT7530_FDB_FLUSH, NULL); @@ -2517,8 +2606,11 @@ static const struct dsa_switch_ops mt7530_switch_ops = {  	.phy_write		= mt753x_phy_write,  	.get_ethtool_stats	= mt7530_get_ethtool_stats,  	.get_sset_count		= mt7530_get_sset_count, +	.set_ageing_time	= mt7530_set_ageing_time,  	.port_enable		= mt7530_port_enable,  	.port_disable		= mt7530_port_disable, +	.port_change_mtu	= mt7530_port_change_mtu, +	.port_max_mtu		= mt7530_port_max_mtu,  	.port_stp_state_set	= mt7530_stp_state_set,  	.port_bridge_join	= mt7530_port_bridge_join,  	.port_bridge_leave	= mt7530_port_bridge_leave, diff --git a/drivers/net/dsa/mt7530.h b/drivers/net/dsa/mt7530.h index 9278a8e3d04e..32d8969b3ace 100644 --- a/drivers/net/dsa/mt7530.h +++ b/drivers/net/dsa/mt7530.h @@ -11,6 +11,9 @@  #define MT7530_NUM_FDB_RECORDS		2048  #define MT7530_ALL_MEMBERS		0xff +#define MTK_HDR_LEN	4 +#define MT7530_MAX_MTU	(15 * 1024 - ETH_HLEN - ETH_FCS_LEN - MTK_HDR_LEN) +  enum mt753x_id {  	ID_MT7530 = 0,  	ID_MT7621 = 1, @@ -158,6 +161,19 @@ enum mt7530_vlan_egress_attr {  	MT7530_VLAN_EGRESS_STACK = 3,  }; +/* Register for address age control */ +#define MT7530_AAC			0xa0 +/* Disable ageing */ +#define  AGE_DIS			BIT(20) +/* Age count */ +#define  AGE_CNT_MASK			GENMASK(19, 12) +#define  AGE_CNT_MAX			0xff +#define  AGE_CNT(x)			(AGE_CNT_MASK & ((x) << 12)) +/* Age unit */ +#define  AGE_UNIT_MASK			GENMASK(11, 0) +#define  AGE_UNIT_MAX			0xfff +#define  AGE_UNIT(x)			(AGE_UNIT_MASK & (x)) +  /* Register for port STP state control */  #define MT7530_SSP_P(x)			(0x2000 + ((x) * 0x100))  #define  FID_PST(x)			((x) & 0x3) @@ -289,6 +305,15 @@ enum mt7530_vlan_port_attr {  #define MT7531_DBG_CNT(x)		(0x3018 + (x) * 0x100)  #define  MT7531_DIS_CLR			BIT(31) +#define MT7530_GMACCR			0x30e0 +#define  MAX_RX_JUMBO(x)		((x) << 2) +#define  MAX_RX_JUMBO_MASK		GENMASK(5, 2) +#define  MAX_RX_PKT_LEN_MASK		GENMASK(1, 0) +#define  MAX_RX_PKT_LEN_1522		0x0 +#define  MAX_RX_PKT_LEN_1536		0x1 +#define  MAX_RX_PKT_LEN_1552		0x2 +#define  MAX_RX_PKT_LEN_JUMBO		0x3 +  /* Register for MIB */  #define MT7530_PORT_MIB_COUNTER(x)	(0x4000 + (x) * 0x100)  #define MT7530_MIB_CCR			0x4fe0 diff --git a/drivers/net/dsa/mv88e6xxx/chip.c b/drivers/net/dsa/mv88e6xxx/chip.c index bd297ae7cf9e..eafe6bedc692 100644 --- a/drivers/net/dsa/mv88e6xxx/chip.c +++ b/drivers/net/dsa/mv88e6xxx/chip.c @@ -727,8 +727,8 @@ static void mv88e6xxx_mac_link_down(struct dsa_switch *ds, int port,  	mv88e6xxx_reg_lock(chip);  	if ((!mv88e6xxx_port_ppu_updates(chip, port) || -	     mode == MLO_AN_FIXED) && ops->port_set_link) -		err = ops->port_set_link(chip, port, LINK_FORCED_DOWN); +	     mode == MLO_AN_FIXED) && ops->port_sync_link) +		err = ops->port_sync_link(chip, port, mode, false);  	mv88e6xxx_reg_unlock(chip);  	if (err) @@ -768,8 +768,8 @@ static void mv88e6xxx_mac_link_up(struct dsa_switch *ds, int port,  				goto error;  		} -		if (ops->port_set_link) -			err = ops->port_set_link(chip, port, LINK_FORCED_UP); +		if (ops->port_sync_link) +			err = ops->port_sync_link(chip, port, mode, true);  	}  error:  	mv88e6xxx_reg_unlock(chip); @@ -1347,9 +1347,16 @@ static int mv88e6xxx_atu_setup(struct mv88e6xxx_chip *chip)  	if (err)  		return err; -	err = mv88e6xxx_g1_atu_set_learn2all(chip, true); -	if (err) -		return err; +	/* The chips that have a "learn2all" bit in Global1, ATU +	 * Control are precisely those whose port registers have a +	 * Message Port bit in Port Control 1 and hence implement +	 * ->port_setup_message_port. +	 */ +	if (chip->info->ops->port_setup_message_port) { +		err = mv88e6xxx_g1_atu_set_learn2all(chip, true); +		if (err) +			return err; +	}  	return mv88e6xxx_g1_atu_set_age_time(chip, 300000);  } @@ -1442,7 +1449,7 @@ static void mv88e6xxx_port_fast_age(struct dsa_switch *ds, int port)  static int mv88e6xxx_vtu_setup(struct mv88e6xxx_chip *chip)  { -	if (!chip->info->max_vid) +	if (!mv88e6xxx_max_vid(chip))  		return 0;  	return mv88e6xxx_g1_vtu_flush(chip); @@ -1484,7 +1491,7 @@ int mv88e6xxx_fid_map(struct mv88e6xxx_chip *chip, unsigned long *fid_bitmap)  	}  	/* Set every FID bit used by the VLAN entries */ -	vlan.vid = chip->info->max_vid; +	vlan.vid = mv88e6xxx_max_vid(chip);  	vlan.valid = false;  	do { @@ -1496,7 +1503,7 @@ int mv88e6xxx_fid_map(struct mv88e6xxx_chip *chip, unsigned long *fid_bitmap)  			break;  		set_bit(vlan.fid, fid_bitmap); -	} while (vlan.vid < chip->info->max_vid); +	} while (vlan.vid < mv88e6xxx_max_vid(chip));  	return 0;  } @@ -1587,7 +1594,7 @@ static int mv88e6xxx_port_vlan_filtering(struct dsa_switch *ds, int port,  	int err;  	if (switchdev_trans_ph_prepare(trans)) -		return chip->info->max_vid ? 0 : -EOPNOTSUPP; +		return mv88e6xxx_max_vid(chip) ? 0 : -EOPNOTSUPP;  	mv88e6xxx_reg_lock(chip);  	err = mv88e6xxx_port_set_8021q_mode(chip, port, mode); @@ -1603,7 +1610,7 @@ mv88e6xxx_port_vlan_prepare(struct dsa_switch *ds, int port,  	struct mv88e6xxx_chip *chip = ds->priv;  	int err; -	if (!chip->info->max_vid) +	if (!mv88e6xxx_max_vid(chip))  		return -EOPNOTSUPP;  	/* If the requested port doesn't belong to the same bridge as the VLAN @@ -1973,7 +1980,7 @@ static void mv88e6xxx_port_vlan_add(struct dsa_switch *ds, int port,  	u8 member;  	u16 vid; -	if (!chip->info->max_vid) +	if (!mv88e6xxx_max_vid(chip))  		return;  	if (dsa_is_dsa_port(ds, port) || dsa_is_cpu_port(ds, port)) @@ -2051,7 +2058,7 @@ static int mv88e6xxx_port_vlan_del(struct dsa_switch *ds, int port,  	u16 pvid, vid;  	int err = 0; -	if (!chip->info->max_vid) +	if (!mv88e6xxx_max_vid(chip))  		return -EOPNOTSUPP;  	mv88e6xxx_reg_lock(chip); @@ -2157,7 +2164,7 @@ static int mv88e6xxx_port_db_dump(struct mv88e6xxx_chip *chip, int port,  		return err;  	/* Dump VLANs' Filtering Information Databases */ -	vlan.vid = chip->info->max_vid; +	vlan.vid = mv88e6xxx_max_vid(chip);  	vlan.valid = false;  	do { @@ -2172,7 +2179,7 @@ static int mv88e6xxx_port_db_dump(struct mv88e6xxx_chip *chip, int port,  						 cb, data);  		if (err)  			return err; -	} while (vlan.vid < chip->info->max_vid); +	} while (vlan.vid < mv88e6xxx_max_vid(chip));  	return err;  } @@ -2297,6 +2304,8 @@ static void mv88e6xxx_hardware_reset(struct mv88e6xxx_chip *chip)  		usleep_range(10000, 20000);  		gpiod_set_value_cansleep(gpiod, 0);  		usleep_range(10000, 20000); + +		mv88e6xxx_g1_wait_eeprom_done(chip);  	}  } @@ -2851,6 +2860,7 @@ static int mv88e6xxx_setup(struct dsa_switch *ds)  	chip->ds = ds;  	ds->slave_mii_bus = mv88e6xxx_default_mdio_bus(chip); +	ds->configure_vlan_while_not_filtering = true;  	mv88e6xxx_reg_lock(chip); @@ -3207,6 +3217,7 @@ static const struct mv88e6xxx_ops mv88e6085_ops = {  	.phy_read = mv88e6185_phy_ppu_read,  	.phy_write = mv88e6185_phy_ppu_write,  	.port_set_link = mv88e6xxx_port_set_link, +	.port_sync_link = mv88e6xxx_port_sync_link,  	.port_set_speed_duplex = mv88e6185_port_set_speed_duplex,  	.port_tag_remap = mv88e6095_port_tag_remap,  	.port_set_frame_mode = mv88e6351_port_set_frame_mode, @@ -3246,6 +3257,7 @@ static const struct mv88e6xxx_ops mv88e6095_ops = {  	.phy_read = mv88e6185_phy_ppu_read,  	.phy_write = mv88e6185_phy_ppu_write,  	.port_set_link = mv88e6xxx_port_set_link, +	.port_sync_link = mv88e6185_port_sync_link,  	.port_set_speed_duplex = mv88e6185_port_set_speed_duplex,  	.port_set_frame_mode = mv88e6085_port_set_frame_mode,  	.port_set_egress_floods = mv88e6185_port_set_egress_floods, @@ -3258,6 +3270,9 @@ static const struct mv88e6xxx_ops mv88e6095_ops = {  	.stats_get_strings = mv88e6095_stats_get_strings,  	.stats_get_stats = mv88e6095_stats_get_stats,  	.mgmt_rsvd2cpu = mv88e6185_g2_mgmt_rsvd2cpu, +	.serdes_power = mv88e6185_serdes_power, +	.serdes_get_lane = mv88e6185_serdes_get_lane, +	.serdes_pcs_get_state = mv88e6185_serdes_pcs_get_state,  	.ppu_enable = mv88e6185_g1_ppu_enable,  	.ppu_disable = mv88e6185_g1_ppu_disable,  	.reset = mv88e6185_g1_reset, @@ -3276,6 +3291,7 @@ static const struct mv88e6xxx_ops mv88e6097_ops = {  	.phy_read = mv88e6xxx_g2_smi_phy_read,  	.phy_write = mv88e6xxx_g2_smi_phy_write,  	.port_set_link = mv88e6xxx_port_set_link, +	.port_sync_link = mv88e6185_port_sync_link,  	.port_set_speed_duplex = mv88e6185_port_set_speed_duplex,  	.port_tag_remap = mv88e6095_port_tag_remap,  	.port_set_frame_mode = mv88e6351_port_set_frame_mode, @@ -3296,6 +3312,12 @@ static const struct mv88e6xxx_ops mv88e6097_ops = {  	.set_egress_port = mv88e6095_g1_set_egress_port,  	.watchdog_ops = &mv88e6097_watchdog_ops,  	.mgmt_rsvd2cpu = mv88e6352_g2_mgmt_rsvd2cpu, +	.serdes_power = mv88e6185_serdes_power, +	.serdes_get_lane = mv88e6185_serdes_get_lane, +	.serdes_pcs_get_state = mv88e6185_serdes_pcs_get_state, +	.serdes_irq_mapping = mv88e6390_serdes_irq_mapping, +	.serdes_irq_enable = mv88e6097_serdes_irq_enable, +	.serdes_irq_status = mv88e6097_serdes_irq_status,  	.pot_clear = mv88e6xxx_g2_pot_clear,  	.reset = mv88e6352_g1_reset,  	.rmu_disable = mv88e6085_g1_rmu_disable, @@ -3314,6 +3336,7 @@ static const struct mv88e6xxx_ops mv88e6123_ops = {  	.phy_read = mv88e6xxx_g2_smi_phy_read,  	.phy_write = mv88e6xxx_g2_smi_phy_write,  	.port_set_link = mv88e6xxx_port_set_link, +	.port_sync_link = mv88e6xxx_port_sync_link,  	.port_set_speed_duplex = mv88e6185_port_set_speed_duplex,  	.port_set_frame_mode = mv88e6085_port_set_frame_mode,  	.port_set_egress_floods = mv88e6352_port_set_egress_floods, @@ -3348,6 +3371,7 @@ static const struct mv88e6xxx_ops mv88e6131_ops = {  	.phy_read = mv88e6185_phy_ppu_read,  	.phy_write = mv88e6185_phy_ppu_write,  	.port_set_link = mv88e6xxx_port_set_link, +	.port_sync_link = mv88e6xxx_port_sync_link,  	.port_set_speed_duplex = mv88e6185_port_set_speed_duplex,  	.port_tag_remap = mv88e6095_port_tag_remap,  	.port_set_frame_mode = mv88e6351_port_set_frame_mode, @@ -3389,6 +3413,7 @@ static const struct mv88e6xxx_ops mv88e6141_ops = {  	.phy_read = mv88e6xxx_g2_smi_phy_read,  	.phy_write = mv88e6xxx_g2_smi_phy_write,  	.port_set_link = mv88e6xxx_port_set_link, +	.port_sync_link = mv88e6xxx_port_sync_link,  	.port_set_rgmii_delay = mv88e6390_port_set_rgmii_delay,  	.port_set_speed_duplex = mv88e6341_port_set_speed_duplex,  	.port_max_speed_mode = mv88e6341_port_max_speed_mode, @@ -3440,6 +3465,7 @@ static const struct mv88e6xxx_ops mv88e6161_ops = {  	.phy_read = mv88e6xxx_g2_smi_phy_read,  	.phy_write = mv88e6xxx_g2_smi_phy_write,  	.port_set_link = mv88e6xxx_port_set_link, +	.port_sync_link = mv88e6xxx_port_sync_link,  	.port_set_speed_duplex = mv88e6185_port_set_speed_duplex,  	.port_tag_remap = mv88e6095_port_tag_remap,  	.port_set_frame_mode = mv88e6351_port_set_frame_mode, @@ -3481,6 +3507,7 @@ static const struct mv88e6xxx_ops mv88e6165_ops = {  	.phy_read = mv88e6165_phy_read,  	.phy_write = mv88e6165_phy_write,  	.port_set_link = mv88e6xxx_port_set_link, +	.port_sync_link = mv88e6xxx_port_sync_link,  	.port_set_speed_duplex = mv88e6185_port_set_speed_duplex,  	.port_disable_learn_limit = mv88e6xxx_port_disable_learn_limit,  	.port_disable_pri_override = mv88e6xxx_port_disable_pri_override, @@ -3515,6 +3542,7 @@ static const struct mv88e6xxx_ops mv88e6171_ops = {  	.phy_read = mv88e6xxx_g2_smi_phy_read,  	.phy_write = mv88e6xxx_g2_smi_phy_write,  	.port_set_link = mv88e6xxx_port_set_link, +	.port_sync_link = mv88e6xxx_port_sync_link,  	.port_set_rgmii_delay = mv88e6352_port_set_rgmii_delay,  	.port_set_speed_duplex = mv88e6185_port_set_speed_duplex,  	.port_tag_remap = mv88e6095_port_tag_remap, @@ -3557,6 +3585,7 @@ static const struct mv88e6xxx_ops mv88e6172_ops = {  	.phy_read = mv88e6xxx_g2_smi_phy_read,  	.phy_write = mv88e6xxx_g2_smi_phy_write,  	.port_set_link = mv88e6xxx_port_set_link, +	.port_sync_link = mv88e6xxx_port_sync_link,  	.port_set_rgmii_delay = mv88e6352_port_set_rgmii_delay,  	.port_set_speed_duplex = mv88e6352_port_set_speed_duplex,  	.port_tag_remap = mv88e6095_port_tag_remap, @@ -3608,6 +3637,7 @@ static const struct mv88e6xxx_ops mv88e6175_ops = {  	.phy_read = mv88e6xxx_g2_smi_phy_read,  	.phy_write = mv88e6xxx_g2_smi_phy_write,  	.port_set_link = mv88e6xxx_port_set_link, +	.port_sync_link = mv88e6xxx_port_sync_link,  	.port_set_rgmii_delay = mv88e6352_port_set_rgmii_delay,  	.port_set_speed_duplex = mv88e6185_port_set_speed_duplex,  	.port_tag_remap = mv88e6095_port_tag_remap, @@ -3650,6 +3680,7 @@ static const struct mv88e6xxx_ops mv88e6176_ops = {  	.phy_read = mv88e6xxx_g2_smi_phy_read,  	.phy_write = mv88e6xxx_g2_smi_phy_write,  	.port_set_link = mv88e6xxx_port_set_link, +	.port_sync_link = mv88e6xxx_port_sync_link,  	.port_set_rgmii_delay = mv88e6352_port_set_rgmii_delay,  	.port_set_speed_duplex = mv88e6352_port_set_speed_duplex,  	.port_tag_remap = mv88e6095_port_tag_remap, @@ -3703,6 +3734,7 @@ static const struct mv88e6xxx_ops mv88e6185_ops = {  	.phy_read = mv88e6185_phy_ppu_read,  	.phy_write = mv88e6185_phy_ppu_write,  	.port_set_link = mv88e6xxx_port_set_link, +	.port_sync_link = mv88e6185_port_sync_link,  	.port_set_speed_duplex = mv88e6185_port_set_speed_duplex,  	.port_set_frame_mode = mv88e6085_port_set_frame_mode,  	.port_set_egress_floods = mv88e6185_port_set_egress_floods, @@ -3720,6 +3752,9 @@ static const struct mv88e6xxx_ops mv88e6185_ops = {  	.set_egress_port = mv88e6095_g1_set_egress_port,  	.watchdog_ops = &mv88e6097_watchdog_ops,  	.mgmt_rsvd2cpu = mv88e6185_g2_mgmt_rsvd2cpu, +	.serdes_power = mv88e6185_serdes_power, +	.serdes_get_lane = mv88e6185_serdes_get_lane, +	.serdes_pcs_get_state = mv88e6185_serdes_pcs_get_state,  	.set_cascade_port = mv88e6185_g1_set_cascade_port,  	.ppu_enable = mv88e6185_g1_ppu_enable,  	.ppu_disable = mv88e6185_g1_ppu_disable, @@ -3740,6 +3775,7 @@ static const struct mv88e6xxx_ops mv88e6190_ops = {  	.phy_read = mv88e6xxx_g2_smi_phy_read,  	.phy_write = mv88e6xxx_g2_smi_phy_write,  	.port_set_link = mv88e6xxx_port_set_link, +	.port_sync_link = mv88e6xxx_port_sync_link,  	.port_set_rgmii_delay = mv88e6390_port_set_rgmii_delay,  	.port_set_speed_duplex = mv88e6390_port_set_speed_duplex,  	.port_max_speed_mode = mv88e6390_port_max_speed_mode, @@ -3799,6 +3835,7 @@ static const struct mv88e6xxx_ops mv88e6190x_ops = {  	.phy_read = mv88e6xxx_g2_smi_phy_read,  	.phy_write = mv88e6xxx_g2_smi_phy_write,  	.port_set_link = mv88e6xxx_port_set_link, +	.port_sync_link = mv88e6xxx_port_sync_link,  	.port_set_rgmii_delay = mv88e6390_port_set_rgmii_delay,  	.port_set_speed_duplex = mv88e6390x_port_set_speed_duplex,  	.port_max_speed_mode = mv88e6390x_port_max_speed_mode, @@ -3858,6 +3895,7 @@ static const struct mv88e6xxx_ops mv88e6191_ops = {  	.phy_read = mv88e6xxx_g2_smi_phy_read,  	.phy_write = mv88e6xxx_g2_smi_phy_write,  	.port_set_link = mv88e6xxx_port_set_link, +	.port_sync_link = mv88e6xxx_port_sync_link,  	.port_set_rgmii_delay = mv88e6390_port_set_rgmii_delay,  	.port_set_speed_duplex = mv88e6390_port_set_speed_duplex,  	.port_max_speed_mode = mv88e6390_port_max_speed_mode, @@ -3917,6 +3955,7 @@ static const struct mv88e6xxx_ops mv88e6240_ops = {  	.phy_read = mv88e6xxx_g2_smi_phy_read,  	.phy_write = mv88e6xxx_g2_smi_phy_write,  	.port_set_link = mv88e6xxx_port_set_link, +	.port_sync_link = mv88e6xxx_port_sync_link,  	.port_set_rgmii_delay = mv88e6352_port_set_rgmii_delay,  	.port_set_speed_duplex = mv88e6352_port_set_speed_duplex,  	.port_tag_remap = mv88e6095_port_tag_remap, @@ -3975,6 +4014,7 @@ static const struct mv88e6xxx_ops mv88e6250_ops = {  	.phy_read = mv88e6xxx_g2_smi_phy_read,  	.phy_write = mv88e6xxx_g2_smi_phy_write,  	.port_set_link = mv88e6xxx_port_set_link, +	.port_sync_link = mv88e6xxx_port_sync_link,  	.port_set_rgmii_delay = mv88e6352_port_set_rgmii_delay,  	.port_set_speed_duplex = mv88e6250_port_set_speed_duplex,  	.port_tag_remap = mv88e6095_port_tag_remap, @@ -4012,6 +4052,7 @@ static const struct mv88e6xxx_ops mv88e6290_ops = {  	.phy_read = mv88e6xxx_g2_smi_phy_read,  	.phy_write = mv88e6xxx_g2_smi_phy_write,  	.port_set_link = mv88e6xxx_port_set_link, +	.port_sync_link = mv88e6xxx_port_sync_link,  	.port_set_rgmii_delay = mv88e6390_port_set_rgmii_delay,  	.port_set_speed_duplex = mv88e6390_port_set_speed_duplex,  	.port_max_speed_mode = mv88e6390_port_max_speed_mode, @@ -4073,6 +4114,7 @@ static const struct mv88e6xxx_ops mv88e6320_ops = {  	.phy_read = mv88e6xxx_g2_smi_phy_read,  	.phy_write = mv88e6xxx_g2_smi_phy_write,  	.port_set_link = mv88e6xxx_port_set_link, +	.port_sync_link = mv88e6xxx_port_sync_link,  	.port_set_speed_duplex = mv88e6185_port_set_speed_duplex,  	.port_tag_remap = mv88e6095_port_tag_remap,  	.port_set_frame_mode = mv88e6351_port_set_frame_mode, @@ -4115,6 +4157,7 @@ static const struct mv88e6xxx_ops mv88e6321_ops = {  	.phy_read = mv88e6xxx_g2_smi_phy_read,  	.phy_write = mv88e6xxx_g2_smi_phy_write,  	.port_set_link = mv88e6xxx_port_set_link, +	.port_sync_link = mv88e6xxx_port_sync_link,  	.port_set_speed_duplex = mv88e6185_port_set_speed_duplex,  	.port_tag_remap = mv88e6095_port_tag_remap,  	.port_set_frame_mode = mv88e6351_port_set_frame_mode, @@ -4155,6 +4198,7 @@ static const struct mv88e6xxx_ops mv88e6341_ops = {  	.phy_read = mv88e6xxx_g2_smi_phy_read,  	.phy_write = mv88e6xxx_g2_smi_phy_write,  	.port_set_link = mv88e6xxx_port_set_link, +	.port_sync_link = mv88e6xxx_port_sync_link,  	.port_set_rgmii_delay = mv88e6390_port_set_rgmii_delay,  	.port_set_speed_duplex = mv88e6341_port_set_speed_duplex,  	.port_max_speed_mode = mv88e6341_port_max_speed_mode, @@ -4208,6 +4252,7 @@ static const struct mv88e6xxx_ops mv88e6350_ops = {  	.phy_read = mv88e6xxx_g2_smi_phy_read,  	.phy_write = mv88e6xxx_g2_smi_phy_write,  	.port_set_link = mv88e6xxx_port_set_link, +	.port_sync_link = mv88e6xxx_port_sync_link,  	.port_set_rgmii_delay = mv88e6352_port_set_rgmii_delay,  	.port_set_speed_duplex = mv88e6185_port_set_speed_duplex,  	.port_tag_remap = mv88e6095_port_tag_remap, @@ -4248,6 +4293,7 @@ static const struct mv88e6xxx_ops mv88e6351_ops = {  	.phy_read = mv88e6xxx_g2_smi_phy_read,  	.phy_write = mv88e6xxx_g2_smi_phy_write,  	.port_set_link = mv88e6xxx_port_set_link, +	.port_sync_link = mv88e6xxx_port_sync_link,  	.port_set_rgmii_delay = mv88e6352_port_set_rgmii_delay,  	.port_set_speed_duplex = mv88e6185_port_set_speed_duplex,  	.port_tag_remap = mv88e6095_port_tag_remap, @@ -4292,6 +4338,7 @@ static const struct mv88e6xxx_ops mv88e6352_ops = {  	.phy_read = mv88e6xxx_g2_smi_phy_read,  	.phy_write = mv88e6xxx_g2_smi_phy_write,  	.port_set_link = mv88e6xxx_port_set_link, +	.port_sync_link = mv88e6xxx_port_sync_link,  	.port_set_rgmii_delay = mv88e6352_port_set_rgmii_delay,  	.port_set_speed_duplex = mv88e6352_port_set_speed_duplex,  	.port_tag_remap = mv88e6095_port_tag_remap, @@ -4352,6 +4399,7 @@ static const struct mv88e6xxx_ops mv88e6390_ops = {  	.phy_read = mv88e6xxx_g2_smi_phy_read,  	.phy_write = mv88e6xxx_g2_smi_phy_write,  	.port_set_link = mv88e6xxx_port_set_link, +	.port_sync_link = mv88e6xxx_port_sync_link,  	.port_set_rgmii_delay = mv88e6390_port_set_rgmii_delay,  	.port_set_speed_duplex = mv88e6390_port_set_speed_duplex,  	.port_max_speed_mode = mv88e6390_port_max_speed_mode, @@ -4415,6 +4463,7 @@ static const struct mv88e6xxx_ops mv88e6390x_ops = {  	.phy_read = mv88e6xxx_g2_smi_phy_read,  	.phy_write = mv88e6xxx_g2_smi_phy_write,  	.port_set_link = mv88e6xxx_port_set_link, +	.port_sync_link = mv88e6xxx_port_sync_link,  	.port_set_rgmii_delay = mv88e6390_port_set_rgmii_delay,  	.port_set_speed_duplex = mv88e6390x_port_set_speed_duplex,  	.port_max_speed_mode = mv88e6390x_port_max_speed_mode, diff --git a/drivers/net/dsa/mv88e6xxx/chip.h b/drivers/net/dsa/mv88e6xxx/chip.h index 81c244fc0419..3543055bcb51 100644 --- a/drivers/net/dsa/mv88e6xxx/chip.h +++ b/drivers/net/dsa/mv88e6xxx/chip.h @@ -245,6 +245,7 @@ enum mv88e6xxx_region_id {  	MV88E6XXX_REGION_GLOBAL1 = 0,  	MV88E6XXX_REGION_GLOBAL2,  	MV88E6XXX_REGION_ATU, +	MV88E6XXX_REGION_VTU,  	_MV88E6XXX_REGION_MAX,  }; @@ -416,6 +417,10 @@ struct mv88e6xxx_ops {  	 */  	int (*port_set_link)(struct mv88e6xxx_chip *chip, int port, int link); +	/* Synchronise the port link state with that of the SERDES +	 */ +	int (*port_sync_link)(struct mv88e6xxx_chip *chip, int port, unsigned int mode, bool isup); +  #define PAUSE_ON		1  #define PAUSE_OFF		0 @@ -672,6 +677,11 @@ static inline unsigned int mv88e6xxx_num_ports(struct mv88e6xxx_chip *chip)  	return chip->info->num_ports;  } +static inline unsigned int mv88e6xxx_max_vid(struct mv88e6xxx_chip *chip) +{ +	return chip->info->max_vid; +} +  static inline u16 mv88e6xxx_port_mask(struct mv88e6xxx_chip *chip)  {  	return GENMASK((s32)mv88e6xxx_num_ports(chip) - 1, 0); diff --git a/drivers/net/dsa/mv88e6xxx/devlink.c b/drivers/net/dsa/mv88e6xxx/devlink.c index 10cd1bfd81a0..21953d6d484c 100644 --- a/drivers/net/dsa/mv88e6xxx/devlink.c +++ b/drivers/net/dsa/mv88e6xxx/devlink.c @@ -393,8 +393,10 @@ static int mv88e6xxx_region_atu_snapshot(struct devlink *dl,  	mv88e6xxx_reg_lock(chip);  	err = mv88e6xxx_fid_map(chip, fid_bitmap); -	if (err) +	if (err) { +		kfree(table);  		goto out; +	}  	while (1) {  		fid = find_next_bit(fid_bitmap, MV88E6XXX_N_FID, fid + 1); @@ -415,6 +417,92 @@ out:  	return err;  } +/** + * struct mv88e6xxx_devlink_vtu_entry - Devlink VTU entry + * @fid:   Global1/2:   FID and VLAN policy. + * @sid:   Global1/3:   SID, unknown filters and learning. + * @op:    Global1/5:   FID (old chipsets). + * @vid:   Global1/6:   VID, valid, and page. + * @data:  Global1/7-9: Membership data and priority override. + * @resvd: Reserved. Also happens to align the size to 16B. + * + * The VTU entry format varies between chipset generations, the + * descriptions above represent the superset of all possible + * information, not all fields are valid on all devices. Since this is + * a low-level debug interface, copy all data verbatim and defer + * parsing to the consumer. + */ +struct mv88e6xxx_devlink_vtu_entry { +	u16 fid; +	u16 sid; +	u16 op; +	u16 vid; +	u16 data[3]; +	u16 resvd; +}; + +static int mv88e6xxx_region_vtu_snapshot(struct devlink *dl, +					 const struct devlink_region_ops *ops, +					 struct netlink_ext_ack *extack, +					 u8 **data) +{ +	struct mv88e6xxx_devlink_vtu_entry *table, *entry; +	struct dsa_switch *ds = dsa_devlink_to_ds(dl); +	struct mv88e6xxx_chip *chip = ds->priv; +	struct mv88e6xxx_vtu_entry vlan; +	int err; + +	table = kcalloc(mv88e6xxx_max_vid(chip) + 1, +			sizeof(struct mv88e6xxx_devlink_vtu_entry), +			GFP_KERNEL); +	if (!table) +		return -ENOMEM; + +	entry = table; +	vlan.vid = mv88e6xxx_max_vid(chip); +	vlan.valid = false; + +	mv88e6xxx_reg_lock(chip); + +	do { +		err = mv88e6xxx_g1_vtu_getnext(chip, &vlan); +		if (err) +			break; + +		if (!vlan.valid) +			break; + +		err = err ? : mv88e6xxx_g1_read(chip, MV88E6352_G1_VTU_FID, +						&entry->fid); +		err = err ? : mv88e6xxx_g1_read(chip, MV88E6352_G1_VTU_SID, +						&entry->sid); +		err = err ? : mv88e6xxx_g1_read(chip, MV88E6XXX_G1_VTU_OP, +						&entry->op); +		err = err ? : mv88e6xxx_g1_read(chip, MV88E6XXX_G1_VTU_VID, +						&entry->vid); +		err = err ? : mv88e6xxx_g1_read(chip, MV88E6XXX_G1_VTU_DATA1, +						&entry->data[0]); +		err = err ? : mv88e6xxx_g1_read(chip, MV88E6XXX_G1_VTU_DATA2, +						&entry->data[1]); +		err = err ? : mv88e6xxx_g1_read(chip, MV88E6XXX_G1_VTU_DATA3, +						&entry->data[2]); +		if (err) +			break; + +		entry++; +	} while (vlan.vid < mv88e6xxx_max_vid(chip)); + +	mv88e6xxx_reg_unlock(chip); + +	if (err) { +		kfree(table); +		return err; +	} + +	*data = (u8 *)table; +	return 0; +} +  static int mv88e6xxx_region_port_snapshot(struct devlink_port *devlink_port,  					  const struct devlink_port_region_ops *ops,  					  struct netlink_ext_ack *extack, @@ -473,6 +561,12 @@ static struct devlink_region_ops mv88e6xxx_region_atu_ops = {  	.destructor = kfree,  }; +static struct devlink_region_ops mv88e6xxx_region_vtu_ops = { +	.name = "vtu", +	.snapshot = mv88e6xxx_region_vtu_snapshot, +	.destructor = kfree, +}; +  static const struct devlink_port_region_ops mv88e6xxx_region_port_ops = {  	.name = "port",  	.snapshot = mv88e6xxx_region_port_snapshot, @@ -496,6 +590,10 @@ static struct mv88e6xxx_region mv88e6xxx_regions[] = {  		.ops = &mv88e6xxx_region_atu_ops  	  /* calculated at runtime */  	}, +	[MV88E6XXX_REGION_VTU] = { +		.ops = &mv88e6xxx_region_vtu_ops +	  /* calculated at runtime */ +	},  };  static void @@ -574,9 +672,16 @@ static int mv88e6xxx_setup_devlink_regions_global(struct dsa_switch *ds,  		ops = mv88e6xxx_regions[i].ops;  		size = mv88e6xxx_regions[i].size; -		if (i == MV88E6XXX_REGION_ATU) +		switch (i) { +		case MV88E6XXX_REGION_ATU:  			size = mv88e6xxx_num_databases(chip) *  				sizeof(struct mv88e6xxx_devlink_atu_entry); +			break; +		case MV88E6XXX_REGION_VTU: +			size = mv88e6xxx_max_vid(chip) * +				sizeof(struct mv88e6xxx_devlink_vtu_entry); +			break; +		}  		region = dsa_devlink_region_create(ds, ops, 1, size);  		if (IS_ERR(region)) diff --git a/drivers/net/dsa/mv88e6xxx/global1.c b/drivers/net/dsa/mv88e6xxx/global1.c index f62aa83ca08d..33d443a37efc 100644 --- a/drivers/net/dsa/mv88e6xxx/global1.c +++ b/drivers/net/dsa/mv88e6xxx/global1.c @@ -75,6 +75,37 @@ static int mv88e6xxx_g1_wait_init_ready(struct mv88e6xxx_chip *chip)  	return mv88e6xxx_g1_wait_bit(chip, MV88E6XXX_G1_STS, bit, 1);  } +void mv88e6xxx_g1_wait_eeprom_done(struct mv88e6xxx_chip *chip) +{ +	const unsigned long timeout = jiffies + 1 * HZ; +	u16 val; +	int err; + +	/* Wait up to 1 second for the switch to finish reading the +	 * EEPROM. +	 */ +	while (time_before(jiffies, timeout)) { +		err = mv88e6xxx_g1_read(chip, MV88E6XXX_G1_STS, &val); +		if (err) { +			dev_err(chip->dev, "Error reading status"); +			return; +		} + +		/* If the switch is still resetting, it may not +		 * respond on the bus, and so MDIO read returns +		 * 0xffff. Differentiate between that, and waiting for +		 * the EEPROM to be done by bit 0 being set. +		 */ +		if (val != 0xffff && +		    val & BIT(MV88E6XXX_G1_STS_IRQ_EEPROM_DONE)) +			return; + +		usleep_range(1000, 2000); +	} + +	dev_err(chip->dev, "Timeout waiting for EEPROM done"); +} +  /* Offset 0x01: Switch MAC Address Register Bytes 0 & 1   * Offset 0x02: Switch MAC Address Register Bytes 2 & 3   * Offset 0x03: Switch MAC Address Register Bytes 4 & 5 diff --git a/drivers/net/dsa/mv88e6xxx/global1.h b/drivers/net/dsa/mv88e6xxx/global1.h index 1e3546f8b072..80a182c5b98a 100644 --- a/drivers/net/dsa/mv88e6xxx/global1.h +++ b/drivers/net/dsa/mv88e6xxx/global1.h @@ -278,6 +278,7 @@ 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); +void mv88e6xxx_g1_wait_eeprom_done(struct mv88e6xxx_chip *chip);  int mv88e6185_g1_ppu_enable(struct mv88e6xxx_chip *chip);  int mv88e6185_g1_ppu_disable(struct mv88e6xxx_chip *chip); @@ -329,6 +330,8 @@ void mv88e6xxx_g1_atu_prob_irq_free(struct mv88e6xxx_chip *chip);  int mv88e6165_g1_atu_get_hash(struct mv88e6xxx_chip *chip, u8 *hash);  int mv88e6165_g1_atu_set_hash(struct mv88e6xxx_chip *chip, u8 hash); +int mv88e6xxx_g1_vtu_getnext(struct mv88e6xxx_chip *chip, +			     struct mv88e6xxx_vtu_entry *entry);  int mv88e6185_g1_vtu_getnext(struct mv88e6xxx_chip *chip,  			     struct mv88e6xxx_vtu_entry *entry);  int mv88e6185_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 bac9a8a68e50..40bd67a5c8e9 100644 --- a/drivers/net/dsa/mv88e6xxx/global1_atu.c +++ b/drivers/net/dsa/mv88e6xxx/global1_atu.c @@ -333,7 +333,7 @@ static int mv88e6xxx_g1_atu_move(struct mv88e6xxx_chip *chip, u16 fid,  	mask = chip->info->atu_move_port_mask;  	shift = bitmap_weight(&mask, 16); -	entry.state = 0xf, /* Full EntryState means Move */ +	entry.state = 0xf; /* Full EntryState means Move */  	entry.portvec = from_port & mask;  	entry.portvec |= (to_port & mask) << shift; diff --git a/drivers/net/dsa/mv88e6xxx/global1_vtu.c b/drivers/net/dsa/mv88e6xxx/global1_vtu.c index 48390b7b18ad..66ddf67b8737 100644 --- a/drivers/net/dsa/mv88e6xxx/global1_vtu.c +++ b/drivers/net/dsa/mv88e6xxx/global1_vtu.c @@ -125,11 +125,9 @@ static int mv88e6xxx_g1_vtu_vid_write(struct mv88e6xxx_chip *chip,   * Offset 0x08: VTU/STU Data Register 2   * Offset 0x09: VTU/STU Data Register 3   */ - -static int mv88e6185_g1_vtu_data_read(struct mv88e6xxx_chip *chip, -				      struct mv88e6xxx_vtu_entry *entry) +static int mv88e6185_g1_vtu_stu_data_read(struct mv88e6xxx_chip *chip, +					  u16 *regs)  { -	u16 regs[3];  	int i;  	/* Read all 3 VTU/STU Data registers */ @@ -142,12 +140,45 @@ static int mv88e6185_g1_vtu_data_read(struct mv88e6xxx_chip *chip,  			return err;  	} -	/* Extract MemberTag and PortState data */ +	return 0; +} + +static int mv88e6185_g1_vtu_data_read(struct mv88e6xxx_chip *chip, +				      struct mv88e6xxx_vtu_entry *entry) +{ +	u16 regs[3]; +	int err; +	int i; + +	err = mv88e6185_g1_vtu_stu_data_read(chip, regs); +	if (err) +		return err; + +	/* Extract MemberTag data */  	for (i = 0; i < mv88e6xxx_num_ports(chip); ++i) {  		unsigned int member_offset = (i % 4) * 4; -		unsigned int state_offset = member_offset + 2;  		entry->member[i] = (regs[i / 4] >> member_offset) & 0x3; +	} + +	return 0; +} + +static int mv88e6185_g1_stu_data_read(struct mv88e6xxx_chip *chip, +				      struct mv88e6xxx_vtu_entry *entry) +{ +	u16 regs[3]; +	int err; +	int i; + +	err = mv88e6185_g1_vtu_stu_data_read(chip, regs); +	if (err) +		return err; + +	/* Extract PortState data */ +	for (i = 0; i < mv88e6xxx_num_ports(chip); ++i) { +		unsigned int state_offset = (i % 4) * 4 + 2; +  		entry->state[i] = (regs[i / 4] >> state_offset) & 0x3;  	} @@ -276,8 +307,8 @@ static int mv88e6xxx_g1_vtu_stu_get(struct mv88e6xxx_chip *chip,  	return 0;  } -static int mv88e6xxx_g1_vtu_getnext(struct mv88e6xxx_chip *chip, -				    struct mv88e6xxx_vtu_entry *entry) +int mv88e6xxx_g1_vtu_getnext(struct mv88e6xxx_chip *chip, +			     struct mv88e6xxx_vtu_entry *entry)  {  	int err; @@ -349,6 +380,10 @@ int mv88e6185_g1_vtu_getnext(struct mv88e6xxx_chip *chip,  		if (err)  			return err; +		err = mv88e6185_g1_stu_data_read(chip, entry); +		if (err) +			return err; +  		/* VTU DBNum[3:0] are located in VTU Operation 3:0  		 * VTU DBNum[7:4] are located in VTU Operation 11:8  		 */ @@ -374,16 +409,20 @@ int mv88e6352_g1_vtu_getnext(struct mv88e6xxx_chip *chip,  		return err;  	if (entry->valid) { -		/* Fetch (and mask) VLAN PortState data from the STU */ -		err = mv88e6xxx_g1_vtu_stu_get(chip, entry); +		err = mv88e6185_g1_vtu_data_read(chip, entry);  		if (err)  			return err; -		err = mv88e6185_g1_vtu_data_read(chip, entry); +		err = mv88e6xxx_g1_vtu_fid_read(chip, entry);  		if (err)  			return err; -		err = mv88e6xxx_g1_vtu_fid_read(chip, entry); +		/* Fetch VLAN PortState data from the STU */ +		err = mv88e6xxx_g1_vtu_stu_get(chip, entry); +		if (err) +			return err; + +		err = mv88e6185_g1_stu_data_read(chip, entry);  		if (err)  			return err;  	} diff --git a/drivers/net/dsa/mv88e6xxx/port.c b/drivers/net/dsa/mv88e6xxx/port.c index 8128dc607cf4..77a5fd1798cd 100644 --- a/drivers/net/dsa/mv88e6xxx/port.c +++ b/drivers/net/dsa/mv88e6xxx/port.c @@ -162,6 +162,42 @@ int mv88e6xxx_port_set_link(struct mv88e6xxx_chip *chip, int port, int link)  	return 0;  } +int mv88e6xxx_port_sync_link(struct mv88e6xxx_chip *chip, int port, unsigned int mode, bool isup) +{ +	const struct mv88e6xxx_ops *ops = chip->info->ops; +	int err = 0; +	int link; + +	if (isup) +		link = LINK_FORCED_UP; +	else +		link = LINK_FORCED_DOWN; + +	if (ops->port_set_link) +		err = ops->port_set_link(chip, port, link); + +	return err; +} + +int mv88e6185_port_sync_link(struct mv88e6xxx_chip *chip, int port, unsigned int mode, bool isup) +{ +	const struct mv88e6xxx_ops *ops = chip->info->ops; +	int err = 0; +	int link; + +	if (mode == MLO_AN_INBAND) +		link = LINK_UNFORCED; +	else if (isup) +		link = LINK_FORCED_UP; +	else +		link = LINK_FORCED_DOWN; + +	if (ops->port_set_link) +		err = ops->port_set_link(chip, port, link); + +	return err; +} +  static int mv88e6xxx_port_set_speed_duplex(struct mv88e6xxx_chip *chip,  					   int port, int speed, bool alt_bit,  					   bool force_bit, int duplex) diff --git a/drivers/net/dsa/mv88e6xxx/port.h b/drivers/net/dsa/mv88e6xxx/port.h index 44d76ac973f6..500e1d4896ff 100644 --- a/drivers/net/dsa/mv88e6xxx/port.h +++ b/drivers/net/dsa/mv88e6xxx/port.h @@ -298,6 +298,9 @@ int mv88e6390_port_set_rgmii_delay(struct mv88e6xxx_chip *chip, int port,  int mv88e6xxx_port_set_link(struct mv88e6xxx_chip *chip, int port, int link); +int mv88e6xxx_port_sync_link(struct mv88e6xxx_chip *chip, int port, unsigned int mode, bool isup); +int mv88e6185_port_sync_link(struct mv88e6xxx_chip *chip, int port, unsigned int mode, bool isup); +  int mv88e6065_port_set_speed_duplex(struct mv88e6xxx_chip *chip, int port,  				    int speed, int duplex);  int mv88e6185_port_set_speed_duplex(struct mv88e6xxx_chip *chip, int port, diff --git a/drivers/net/dsa/mv88e6xxx/serdes.c b/drivers/net/dsa/mv88e6xxx/serdes.c index 9c07b4f3d345..3195936dc5be 100644 --- a/drivers/net/dsa/mv88e6xxx/serdes.c +++ b/drivers/net/dsa/mv88e6xxx/serdes.c @@ -400,14 +400,16 @@ void mv88e6352_serdes_get_regs(struct mv88e6xxx_chip *chip, int port, void *_p)  {  	u16 *p = _p;  	u16 reg; +	int err;  	int i;  	if (!mv88e6352_port_has_serdes(chip, port))  		return;  	for (i = 0 ; i < 32; i++) { -		mv88e6352_serdes_read(chip, i, ®); -		p[i] = reg; +		err = mv88e6352_serdes_read(chip, i, ®); +		if (!err) +			p[i] = reg;  	}  } @@ -428,6 +430,115 @@ u8 mv88e6341_serdes_get_lane(struct mv88e6xxx_chip *chip, int port)  	return lane;  } +int mv88e6185_serdes_power(struct mv88e6xxx_chip *chip, int port, u8 lane, +			   bool up) +{ +	/* The serdes power can't be controlled on this switch chip but we need +	 * to supply this function to avoid returning -EOPNOTSUPP in +	 * mv88e6xxx_serdes_power_up/mv88e6xxx_serdes_power_down +	 */ +	return 0; +} + +u8 mv88e6185_serdes_get_lane(struct mv88e6xxx_chip *chip, int port) +{ +	/* There are no configurable serdes lanes on this switch chip but we +	 * need to return non-zero so that callers of +	 * mv88e6xxx_serdes_get_lane() know this is a serdes port. +	 */ +	switch (chip->ports[port].cmode) { +	case MV88E6185_PORT_STS_CMODE_SERDES: +	case MV88E6185_PORT_STS_CMODE_1000BASE_X: +		return 0xff; +	default: +		return 0; +	} +} + +int mv88e6185_serdes_pcs_get_state(struct mv88e6xxx_chip *chip, int port, +				   u8 lane, struct phylink_link_state *state) +{ +	int err; +	u16 status; + +	err = mv88e6xxx_port_read(chip, port, MV88E6XXX_PORT_STS, &status); +	if (err) +		return err; + +	state->link = !!(status & MV88E6XXX_PORT_STS_LINK); + +	if (state->link) { +		state->duplex = status & MV88E6XXX_PORT_STS_DUPLEX ? DUPLEX_FULL : DUPLEX_HALF; + +		switch (status &  MV88E6XXX_PORT_STS_SPEED_MASK) { +		case MV88E6XXX_PORT_STS_SPEED_1000: +			state->speed = SPEED_1000; +			break; +		case MV88E6XXX_PORT_STS_SPEED_100: +			state->speed = SPEED_100; +			break; +		case MV88E6XXX_PORT_STS_SPEED_10: +			state->speed = SPEED_10; +			break; +		default: +			dev_err(chip->dev, "invalid PHY speed\n"); +			return -EINVAL; +		} +	} else { +		state->duplex = DUPLEX_UNKNOWN; +		state->speed = SPEED_UNKNOWN; +	} + +	return 0; +} + +int mv88e6097_serdes_irq_enable(struct mv88e6xxx_chip *chip, int port, u8 lane, +				bool enable) +{ +	u8 cmode = chip->ports[port].cmode; + +	/* The serdes interrupts are enabled in the G2_INT_MASK register. We +	 * need to return 0 to avoid returning -EOPNOTSUPP in +	 * mv88e6xxx_serdes_irq_enable/mv88e6xxx_serdes_irq_disable +	 */ +	switch (cmode) { +	case MV88E6185_PORT_STS_CMODE_SERDES: +	case MV88E6185_PORT_STS_CMODE_1000BASE_X: +		return 0; +	} + +	return -EOPNOTSUPP; +} + +static void mv88e6097_serdes_irq_link(struct mv88e6xxx_chip *chip, int port) +{ +	u16 status; +	int err; + +	err = mv88e6xxx_port_read(chip, port, MV88E6XXX_PORT_STS, &status); +	if (err) { +		dev_err(chip->dev, "can't read port status: %d\n", err); +		return; +	} + +	dsa_port_phylink_mac_change(chip->ds, port, !!(status & MV88E6XXX_PORT_STS_LINK)); +} + +irqreturn_t mv88e6097_serdes_irq_status(struct mv88e6xxx_chip *chip, int port, +					u8 lane) +{ +	u8 cmode = chip->ports[port].cmode; + +	switch (cmode) { +	case MV88E6185_PORT_STS_CMODE_SERDES: +	case MV88E6185_PORT_STS_CMODE_1000BASE_X: +		mv88e6097_serdes_irq_link(chip, port); +		return IRQ_HANDLED; +	} + +	return IRQ_NONE; +} +  u8 mv88e6390_serdes_get_lane(struct mv88e6xxx_chip *chip, int port)  {  	u8 cmode = chip->ports[port].cmode; @@ -987,6 +1098,7 @@ void mv88e6390_serdes_get_regs(struct mv88e6xxx_chip *chip, int port, void *_p)  	u16 *p = _p;  	int lane;  	u16 reg; +	int err;  	int i;  	lane = mv88e6xxx_serdes_get_lane(chip, port); @@ -994,8 +1106,9 @@ void mv88e6390_serdes_get_regs(struct mv88e6xxx_chip *chip, int port, void *_p)  		return;  	for (i = 0 ; i < ARRAY_SIZE(mv88e6390_serdes_regs); i++) { -		mv88e6390_serdes_read(chip, lane, MDIO_MMD_PHYXS, -				      mv88e6390_serdes_regs[i], ®); -		p[i] = reg; +		err = mv88e6390_serdes_read(chip, lane, MDIO_MMD_PHYXS, +					    mv88e6390_serdes_regs[i], ®); +		if (!err) +			p[i] = reg;  	}  } diff --git a/drivers/net/dsa/mv88e6xxx/serdes.h b/drivers/net/dsa/mv88e6xxx/serdes.h index 14315f26228a..93822ef9bab8 100644 --- a/drivers/net/dsa/mv88e6xxx/serdes.h +++ b/drivers/net/dsa/mv88e6xxx/serdes.h @@ -73,6 +73,7 @@  #define MV88E6390_PG_CONTROL		0xf010  #define MV88E6390_PG_CONTROL_ENABLE_PC		BIT(0) +u8 mv88e6185_serdes_get_lane(struct mv88e6xxx_chip *chip, int port);  u8 mv88e6341_serdes_get_lane(struct mv88e6xxx_chip *chip, int port);  u8 mv88e6352_serdes_get_lane(struct mv88e6xxx_chip *chip, int port);  u8 mv88e6390_serdes_get_lane(struct mv88e6xxx_chip *chip, int port); @@ -85,6 +86,8 @@ int mv88e6390_serdes_pcs_config(struct mv88e6xxx_chip *chip, int port,  				u8 lane, unsigned int mode,  				phy_interface_t interface,  				const unsigned long *advertise); +int mv88e6185_serdes_pcs_get_state(struct mv88e6xxx_chip *chip, int port, +				   u8 lane, struct phylink_link_state *state);  int mv88e6352_serdes_pcs_get_state(struct mv88e6xxx_chip *chip, int port,  				   u8 lane, struct phylink_link_state *state);  int mv88e6390_serdes_pcs_get_state(struct mv88e6xxx_chip *chip, int port, @@ -101,14 +104,20 @@ unsigned int mv88e6352_serdes_irq_mapping(struct mv88e6xxx_chip *chip,  					  int port);  unsigned int mv88e6390_serdes_irq_mapping(struct mv88e6xxx_chip *chip,  					  int port); +int mv88e6185_serdes_power(struct mv88e6xxx_chip *chip, int port, u8 lane, +			   bool up);  int mv88e6352_serdes_power(struct mv88e6xxx_chip *chip, int port, u8 lane,  			   bool on);  int mv88e6390_serdes_power(struct mv88e6xxx_chip *chip, int port, u8 lane,  			   bool on); +int mv88e6097_serdes_irq_enable(struct mv88e6xxx_chip *chip, int port, u8 lane, +				bool enable);  int mv88e6352_serdes_irq_enable(struct mv88e6xxx_chip *chip, int port, u8 lane,  				bool enable);  int mv88e6390_serdes_irq_enable(struct mv88e6xxx_chip *chip, int port, u8 lane,  				bool enable); +irqreturn_t mv88e6097_serdes_irq_status(struct mv88e6xxx_chip *chip, int port, +					u8 lane);  irqreturn_t mv88e6352_serdes_irq_status(struct mv88e6xxx_chip *chip, int port,  					u8 lane);  irqreturn_t mv88e6390_serdes_irq_status(struct mv88e6xxx_chip *chip, int port, diff --git a/drivers/net/dsa/ocelot/felix.c b/drivers/net/dsa/ocelot/felix.c index f791860d495f..7dc230677b78 100644 --- a/drivers/net/dsa/ocelot/felix.c +++ b/drivers/net/dsa/ocelot/felix.c @@ -112,10 +112,32 @@ static void felix_bridge_leave(struct dsa_switch *ds, int port,  	ocelot_port_bridge_leave(ocelot, port, br);  } -/* This callback needs to be present */  static int felix_vlan_prepare(struct dsa_switch *ds, int port,  			      const struct switchdev_obj_port_vlan *vlan)  { +	struct ocelot *ocelot = ds->priv; +	u16 vid, flags = vlan->flags; +	int err; + +	/* Ocelot switches copy frames as-is to the CPU, so the flags: +	 * egress-untagged or not, pvid or not, make no difference. This +	 * behavior is already better than what DSA just tries to approximate +	 * when it installs the VLAN with the same flags on the CPU port. +	 * Just accept any configuration, and don't let ocelot deny installing +	 * multiple native VLANs on the NPI port, because the switch doesn't +	 * look at the port tag settings towards the NPI interface anyway. +	 */ +	if (port == ocelot->npi) +		return 0; + +	for (vid = vlan->vid_begin; vid <= vlan->vid_end; vid++) { +		err = ocelot_vlan_prepare(ocelot, port, vid, +					  flags & BRIDGE_VLAN_INFO_PVID, +					  flags & BRIDGE_VLAN_INFO_UNTAGGED); +		if (err) +			return err; +	} +  	return 0;  } @@ -135,9 +157,6 @@ static void felix_vlan_add(struct dsa_switch *ds, int port,  	u16 vid;  	int err; -	if (dsa_is_cpu_port(ds, port)) -		flags &= ~BRIDGE_VLAN_INFO_UNTAGGED; -  	for (vid = vlan->vid_begin; vid <= vlan->vid_end; vid++) {  		err = ocelot_vlan_add(ocelot, port, vid,  				      flags & BRIDGE_VLAN_INFO_PVID, @@ -569,7 +588,6 @@ static int felix_setup(struct dsa_switch *ds)  	struct ocelot *ocelot = ds->priv;  	struct felix *felix = ocelot_to_felix(ocelot);  	int port, err; -	int tc;  	err = felix_init_structs(felix, ds->num_ports);  	if (err) @@ -608,12 +626,6 @@ static int felix_setup(struct dsa_switch *ds)  	ocelot_write_rix(ocelot,  			 ANA_PGID_PGID_PGID(GENMASK(ocelot->num_phys_ports, 0)),  			 ANA_PGID_PGID, PGID_UC); -	/* Setup the per-traffic class flooding PGIDs */ -	for (tc = 0; tc < FELIX_NUM_TC; tc++) -		ocelot_write_rix(ocelot, ANA_FLOODING_FLD_MULTICAST(PGID_MC) | -				 ANA_FLOODING_FLD_BROADCAST(PGID_MC) | -				 ANA_FLOODING_FLD_UNICAST(PGID_UC), -				 ANA_FLOODING, tc);  	ds->mtu_enforcement_ingress = true;  	ds->configure_vlan_while_not_filtering = true; diff --git a/drivers/net/dsa/ocelot/felix_vsc9959.c b/drivers/net/dsa/ocelot/felix_vsc9959.c index 3e925b8d5306..2e5bbdca5ea4 100644 --- a/drivers/net/dsa/ocelot/felix_vsc9959.c +++ b/drivers/net/dsa/ocelot/felix_vsc9959.c @@ -1429,6 +1429,7 @@ static int felix_pci_probe(struct pci_dev *pdev,  	pci_set_drvdata(pdev, felix);  	ocelot = &felix->ocelot;  	ocelot->dev = &pdev->dev; +	ocelot->num_flooding_pgids = FELIX_NUM_TC;  	felix->info = &felix_info_vsc9959;  	felix->switch_base = pci_resource_start(pdev,  						felix->info->switch_pci_bar); diff --git a/drivers/net/dsa/ocelot/seville_vsc9953.c b/drivers/net/dsa/ocelot/seville_vsc9953.c index 1d420c4a2f0f..ebbaf6817ec8 100644 --- a/drivers/net/dsa/ocelot/seville_vsc9953.c +++ b/drivers/net/dsa/ocelot/seville_vsc9953.c @@ -1210,6 +1210,7 @@ static int seville_probe(struct platform_device *pdev)  	ocelot = &felix->ocelot;  	ocelot->dev = &pdev->dev; +	ocelot->num_flooding_pgids = 1;  	felix->info = &seville_info_vsc9953;  	res = platform_get_resource(pdev, IORESOURCE_MEM, 0); diff --git a/drivers/net/dsa/qca/ar9331.c b/drivers/net/dsa/qca/ar9331.c index e24a99031b80..4d49c5f2b790 100644 --- a/drivers/net/dsa/qca/ar9331.c +++ b/drivers/net/dsa/qca/ar9331.c @@ -159,6 +159,8 @@ struct ar9331_sw_priv {  	struct dsa_switch ds;  	struct dsa_switch_ops ops;  	struct irq_domain *irqdomain; +	u32 irq_mask; +	struct mutex lock_irq;  	struct mii_bus *mbus; /* mdio master */  	struct mii_bus *sbus; /* mdio slave */  	struct regmap *regmap; @@ -520,32 +522,44 @@ static irqreturn_t ar9331_sw_irq(int irq, void *data)  static void ar9331_sw_mask_irq(struct irq_data *d)  {  	struct ar9331_sw_priv *priv = irq_data_get_irq_chip_data(d); -	struct regmap *regmap = priv->regmap; -	int ret; -	ret = regmap_update_bits(regmap, AR9331_SW_REG_GINT_MASK, -				 AR9331_SW_GINT_PHY_INT, 0); -	if (ret) -		dev_err(priv->dev, "could not mask IRQ\n"); +	priv->irq_mask = 0;  }  static void ar9331_sw_unmask_irq(struct irq_data *d)  {  	struct ar9331_sw_priv *priv = irq_data_get_irq_chip_data(d); + +	priv->irq_mask = AR9331_SW_GINT_PHY_INT; +} + +static void ar9331_sw_irq_bus_lock(struct irq_data *d) +{ +	struct ar9331_sw_priv *priv = irq_data_get_irq_chip_data(d); + +	mutex_lock(&priv->lock_irq); +} + +static void ar9331_sw_irq_bus_sync_unlock(struct irq_data *d) +{ +	struct ar9331_sw_priv *priv = irq_data_get_irq_chip_data(d);  	struct regmap *regmap = priv->regmap;  	int ret;  	ret = regmap_update_bits(regmap, AR9331_SW_REG_GINT_MASK, -				 AR9331_SW_GINT_PHY_INT, -				 AR9331_SW_GINT_PHY_INT); +				 AR9331_SW_GINT_PHY_INT, priv->irq_mask);  	if (ret) -		dev_err(priv->dev, "could not unmask IRQ\n"); +		dev_err(priv->dev, "failed to change IRQ mask\n"); + +	mutex_unlock(&priv->lock_irq);  }  static struct irq_chip ar9331_sw_irq_chip = {  	.name = AR9331_SW_NAME,  	.irq_mask = ar9331_sw_mask_irq,  	.irq_unmask = ar9331_sw_unmask_irq, +	.irq_bus_lock = ar9331_sw_irq_bus_lock, +	.irq_bus_sync_unlock = ar9331_sw_irq_bus_sync_unlock,  };  static int ar9331_sw_irq_map(struct irq_domain *domain, unsigned int irq, @@ -584,6 +598,7 @@ static int ar9331_sw_irq_init(struct ar9331_sw_priv *priv)  		return irq ? irq : -EINVAL;  	} +	mutex_init(&priv->lock_irq);  	ret = devm_request_threaded_irq(dev, irq, NULL, ar9331_sw_irq,  					IRQF_ONESHOT, AR9331_SW_NAME, priv);  	if (ret) { diff --git a/drivers/net/dsa/qca8k.c b/drivers/net/dsa/qca8k.c index 53064e0e1618..5bdac669a339 100644 --- a/drivers/net/dsa/qca8k.c +++ b/drivers/net/dsa/qca8k.c @@ -1219,8 +1219,8 @@ qca8k_port_change_mtu(struct dsa_switch *ds, int port, int new_mtu)  	priv->port_mtu[port] = new_mtu;  	for (i = 0; i < QCA8K_NUM_PORTS; i++) -		if (priv->port_mtu[port] > mtu) -			mtu = priv->port_mtu[port]; +		if (priv->port_mtu[i] > mtu) +			mtu = priv->port_mtu[i];  	/* Include L2 header / FCS length */  	qca8k_write(priv, QCA8K_MAX_FRAME_SIZE, mtu + ETH_HLEN + ETH_FCS_LEN); diff --git a/drivers/net/dsa/rtl8366.c b/drivers/net/dsa/rtl8366.c index 307466b90489..83d481ef9273 100644 --- a/drivers/net/dsa/rtl8366.c +++ b/drivers/net/dsa/rtl8366.c @@ -384,7 +384,6 @@ int rtl8366_vlan_prepare(struct dsa_switch *ds, int port,  {  	struct realtek_smi *smi = ds->priv;  	u16 vid; -	int ret;  	for (vid = vlan->vid_begin; vid < vlan->vid_end; vid++)  		if (!smi->ops->is_vlan_valid(smi, vid)) @@ -397,11 +396,7 @@ int rtl8366_vlan_prepare(struct dsa_switch *ds, int port,  	 * FIXME: what's with this 4k business?  	 * Just rtl8366_enable_vlan() seems inconclusive.  	 */ -	ret = rtl8366_enable_vlan4k(smi, true); -	if (ret) -		return ret; - -	return 0; +	return rtl8366_enable_vlan4k(smi, true);  }  EXPORT_SYMBOL_GPL(rtl8366_vlan_prepare); | 
