diff mbox series

[RFC,net-next,2/2] net: dsa: add Realtek RTL8366S switch driver

Message ID 20210217062139.7893-3-dqfext@gmail.com (mailing list archive)
State RFC
Delegated to: Netdev Maintainers
Headers show
Series DSA driver for Realtek RTL8366S/SR | expand

Checks

Context Check Description
netdev/cover_letter success Link
netdev/fixes_present success Link
netdev/patch_count success Link
netdev/tree_selection success Clearly marked for net-next
netdev/subject_prefix success Link
netdev/cc_maintainers warning 1 maintainers not CCed: linux@armlinux.org.uk
netdev/source_inline success Was 0 now: 0
netdev/verify_signedoff success Link
netdev/module_param success Was 0 now: 0
netdev/build_32bit fail Errors and warnings before: 1 this patch: 14
netdev/kdoc success Errors and warnings before: 21 this patch: 21
netdev/verify_fixes success Link
netdev/checkpatch fail CHECK: Alignment should match open parenthesis CHECK: Macro argument '_x' may be better as '(_x)' to avoid precedence issues CHECK: Please don't use multiple blank lines ERROR: open brace '{' following enum go on the same line WARNING: 'retrived' may be misspelled - perhaps 'retrieved'? WARNING: Statements should start on a tabstop WARNING: added, moved or deleted file(s), does MAINTAINERS need updating? WARNING: line length of 81 exceeds 80 columns WARNING: line length of 82 exceeds 80 columns WARNING: networking block comments don't use an empty /* line, use /* Comment... WARNING: suspect code indent for conditional statements (8, 12)
netdev/build_allmodconfig_warn fail Errors and warnings before: 0 this patch: 14
netdev/header_inline success Link
netdev/stable success Stable not CCed

Commit Message

Qingfang Deng Feb. 17, 2021, 6:21 a.m. UTC
Support Realtek RTL8366S/SR switch

Signed-off-by: DENG Qingfang <dqfext@gmail.com>
---
 drivers/net/dsa/Kconfig            |    1 +
 drivers/net/dsa/Makefile           |    2 +-
 drivers/net/dsa/realtek-smi-core.c |    3 +-
 drivers/net/dsa/realtek-smi-core.h |    1 +
 drivers/net/dsa/rtl8366s.c         | 1165 ++++++++++++++++++++++++++++
 5 files changed, 1169 insertions(+), 3 deletions(-)
 create mode 100644 drivers/net/dsa/rtl8366s.c

Comments

Linus Walleij Feb. 17, 2021, 11:12 a.m. UTC | #1
Hi Qingfang,

thanks for your patch! Overall it looks good and I will not
nitpick details since this is RFC.

On Wed, Feb 17, 2021 at 7:21 AM DENG Qingfang <dqfext@gmail.com> wrote:
>
> Support Realtek RTL8366S/SR switch
>
> Signed-off-by: DENG Qingfang <dqfext@gmail.com>

I would mention that the DT bindings for the switch are already in
Documentation/devicetree/bindings/net/dsa/realtek-smi.txt
(Hmmm we should convert these bindings to YAML too.)

>         select NET_DSA_TAG_RTL4_A
> +       select NET_DSA_TAG_RTL8366S

Hopefully we can use NET_DSA_TAG_RTL4_A for this
switch as mentioned.

> +/* bits 0..7 = port 0, bits 8..15 = port 1 */
> +#define RTL8366S_PAACR0                        0x0011
> +/* bits 0..7 = port 2, bits 8..15 = port 3 */
> +#define RTL8366S_PAACR1                        0x0012
> +/* bits 0..7 = port 4, bits 8..15 = port 5 */
> +#define RTL8366S_PAACR2                        0x0013

Is this all? I was under the impression that RTL8366S
supports up to 8 ports (7+1).

> +#define RTL8366S_CHIP_ID_REG                   0x0105
> +#define RTL8366S_CHIP_ID_8366                  0x8366

Curiously RTL8366RB presents ID 0x5937. Oh well. Interesting
engineering process I suppose.

> +/* LED control registers */
> +#define RTL8366S_LED_BLINKRATE_REG             0x0420
> +#define RTL8366S_LED_BLINKRATE_BIT             0
> +#define RTL8366S_LED_BLINKRATE_MASK            0x0007
> +
> +#define RTL8366S_LED_CTRL_REG                  0x0421
> +#define RTL8366S_LED_0_1_CTRL_REG              0x0422
> +#define RTL8366S_LED_2_3_CTRL_REG              0x0423

Again I wonder if there are more registers here for 8 ports?

> +/* PHY registers control */
> +#define RTL8366S_PHY_ACCESS_CTRL_REG           0x8028
> +#define RTL8366S_PHY_ACCESS_DATA_REG           0x8029
> +
> +#define RTL8366S_PHY_CTRL_READ                 1
> +#define RTL8366S_PHY_CTRL_WRITE                        0
> +
> +#define RTL8366S_PORT_NUM_CPU          5
> +#define RTL8366S_NUM_PORTS             6

Hm 5+1? Isn't RTL8366S 7+1?

> +#define RTL8366S_PORT_1                        BIT(0) /* In userspace port 0 */
> +#define RTL8366S_PORT_2                        BIT(1) /* In userspace port 1 */
> +#define RTL8366S_PORT_3                        BIT(2) /* In userspace port 2 */
> +#define RTL8366S_PORT_4                        BIT(3) /* In userspace port 3 */
> +#define RTL8366S_PORT_5                        BIT(4) /* In userspace port 4 */
> +#define RTL8366S_PORT_CPU              BIT(5) /* CPU port */

Same here.

Overall the question about whether the switch is 5+1 or 7+1 is my
big design remark.

Maybe it is only 5+1 who knows...

Yours,
Linus Walleij
Vladimir Oltean Feb. 17, 2021, 12:25 p.m. UTC | #2
On Wed, Feb 17, 2021 at 02:21:39PM +0800, DENG Qingfang wrote:
> Support Realtek RTL8366S/SR switch
> 
> Signed-off-by: DENG Qingfang <dqfext@gmail.com>
> ---
>  drivers/net/dsa/Kconfig            |    1 +
>  drivers/net/dsa/Makefile           |    2 +-
>  drivers/net/dsa/realtek-smi-core.c |    3 +-
>  drivers/net/dsa/realtek-smi-core.h |    1 +
>  drivers/net/dsa/rtl8366s.c         | 1165 ++++++++++++++++++++++++++++
>  5 files changed, 1169 insertions(+), 3 deletions(-)
>  create mode 100644 drivers/net/dsa/rtl8366s.c
> 
> diff --git a/drivers/net/dsa/Kconfig b/drivers/net/dsa/Kconfig
> index 3af373e90806..52f1df6ef53a 100644
> --- a/drivers/net/dsa/Kconfig
> +++ b/drivers/net/dsa/Kconfig
> @@ -75,6 +75,7 @@ config NET_DSA_REALTEK_SMI
>  	tristate "Realtek SMI Ethernet switch family support"
>  	depends on NET_DSA
>  	select NET_DSA_TAG_RTL4_A
> +	select NET_DSA_TAG_RTL8366S
>  	select FIXED_PHY
>  	select IRQ_DOMAIN
>  	select REALTEK_PHY
> diff --git a/drivers/net/dsa/Makefile b/drivers/net/dsa/Makefile
> index f3598c040994..8c51c25d8378 100644
> --- a/drivers/net/dsa/Makefile
> +++ b/drivers/net/dsa/Makefile
> @@ -10,7 +10,7 @@ obj-$(CONFIG_NET_DSA_MT7530)	+= mt7530.o
>  obj-$(CONFIG_NET_DSA_MV88E6060) += mv88e6060.o
>  obj-$(CONFIG_NET_DSA_QCA8K)	+= qca8k.o
>  obj-$(CONFIG_NET_DSA_REALTEK_SMI) += realtek-smi.o
> -realtek-smi-objs		:= realtek-smi-core.o rtl8366.o rtl8366rb.o
> +realtek-smi-objs		:= realtek-smi-core.o rtl8366.o rtl8366rb.o rtl8366s.o
>  obj-$(CONFIG_NET_DSA_SMSC_LAN9303) += lan9303-core.o
>  obj-$(CONFIG_NET_DSA_SMSC_LAN9303_I2C) += lan9303_i2c.o
>  obj-$(CONFIG_NET_DSA_SMSC_LAN9303_MDIO) += lan9303_mdio.o
> diff --git a/drivers/net/dsa/realtek-smi-core.c b/drivers/net/dsa/realtek-smi-core.c
> index 8e49d4f85d48..e0ced416c362 100644
> --- a/drivers/net/dsa/realtek-smi-core.c
> +++ b/drivers/net/dsa/realtek-smi-core.c
> @@ -480,9 +480,8 @@ static const struct of_device_id realtek_smi_of_match[] = {
>  		.data = &rtl8366rb_variant,
>  	},
>  	{
> -		/* FIXME: add support for RTL8366S and more */
>  		.compatible = "realtek,rtl8366s",
> -		.data = NULL,
> +		.data = &rtl8366s_variant,
>  	},
>  	{ /* sentinel */ },
>  };
> diff --git a/drivers/net/dsa/realtek-smi-core.h b/drivers/net/dsa/realtek-smi-core.h
> index fcf465f7f922..a5d45b6f6de3 100644
> --- a/drivers/net/dsa/realtek-smi-core.h
> +++ b/drivers/net/dsa/realtek-smi-core.h
> @@ -143,5 +143,6 @@ int rtl8366_get_sset_count(struct dsa_switch *ds, int port, int sset);
>  void rtl8366_get_ethtool_stats(struct dsa_switch *ds, int port, uint64_t *data);
>  
>  extern const struct realtek_smi_variant rtl8366rb_variant;
> +extern const struct realtek_smi_variant rtl8366s_variant;
>  
>  #endif /*  _REALTEK_SMI_H */
> diff --git a/drivers/net/dsa/rtl8366s.c b/drivers/net/dsa/rtl8366s.c
> new file mode 100644
> index 000000000000..718e492f8bbd
> --- /dev/null
> +++ b/drivers/net/dsa/rtl8366s.c
> @@ -0,0 +1,1165 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/* Realtek SMI subdriver for the Realtek RTL8366S ethernet switch
> + *
> + * Copyright (C) 2021 DENG, Qingfang <dqfext@gmail.com>
> + * Copyright (C) 2009-2010 Gabor Juhos <juhosg@openwrt.org>
> + * Copyright (C) 2010 Antti Seppälä <a.seppala@gmail.com>
> + */
> +
> +#include <linux/bitfield.h>
> +#include <linux/etherdevice.h>
> +#include <linux/if_bridge.h>
> +#include <linux/regmap.h>
> +
> +#include "realtek-smi-core.h"
> +
> +#define RTL8366S_PHY_NO_MAX	4
> +#define RTL8366S_PHY_PAGE_MAX	7
> +#define RTL8366S_PHY_ADDR_MAX	31
> +
> +/* Switch Global Configuration register */
> +#define RTL8366S_SGCR				0x0000
> +#define RTL8366S_SGCR_EN_BC_STORM_CTRL		BIT(0)
> +#define RTL8366S_SGCR_MAX_LENGTH(_x)		(_x << 4)
> +#define RTL8366S_SGCR_MAX_LENGTH_MASK		RTL8366S_SGCR_MAX_LENGTH(0x3)
> +#define RTL8366S_SGCR_MAX_LENGTH_1522		RTL8366S_SGCR_MAX_LENGTH(0x0)
> +#define RTL8366S_SGCR_MAX_LENGTH_1536		RTL8366S_SGCR_MAX_LENGTH(0x1)
> +#define RTL8366S_SGCR_MAX_LENGTH_1552		RTL8366S_SGCR_MAX_LENGTH(0x2)
> +#define RTL8366S_SGCR_MAX_LENGTH_16000		RTL8366S_SGCR_MAX_LENGTH(0x3)
> +#define RTL8366S_SGCR_EN_VLAN			BIT(13)
> +
> +/* Port Enable Control register */
> +#define RTL8366S_PECR				0x0001
> +
> +/* Switch Security Control registers */
> +#define RTL8366S_SSCR0				0x0002
> +#define RTL8366S_SSCR1				0x0003
> +#define RTL8366S_SSCR2				0x0004
> +#define RTL8366S_SSCR2_DROP_UNKNOWN_DA		BIT(0)
> +
> +/* Port Mode Control registers */
> +#define RTL8366S_PMC0				0x0005
> +#define RTL8366S_PMC0_SPI			BIT(0)
> +#define RTL8366S_PMC0_EN_AUTOLOAD		BIT(1)
> +#define RTL8366S_PMC0_PROBE			BIT(2)
> +#define RTL8366S_PMC0_DIS_BISR			BIT(3)
> +#define RTL8366S_PMC0_ADCTEST			BIT(4)
> +#define RTL8366S_PMC0_SRAM_DIAG			BIT(5)
> +#define RTL8366S_PMC0_EN_SCAN			BIT(6)
> +#define RTL8366S_PMC0_P4_IOMODE_MASK		GENMASK(9, 7)
> +#define RTL8366S_PMC0_P4_IOMODE_PHY_TO_GMAC	\
> +	FIELD_PREP(RTL8366S_PMC0_P4_IOMODE_MASK, 0)
> +#define RTL8366S_PMC0_P4_IOMODE_GMAC_TO_RGMII	\
> +	FIELD_PREP(RTL8366S_PMC0_P4_IOMODE_MASK, 1)
> +#define RTL8366S_PMC0_P4_IOMODE_PHY_TO_RGMII	\
> +	FIELD_PREP(RTL8366S_PMC0_P4_IOMODE_MASK, 2)
> +#define RTL8366S_PMC0_P5_IOMODE_MASK		GENMASK(12, 10)
> +#define RTL8366S_PMC0_SDSMODE_MASK		GENMASK(15, 13)
> +#define RTL8366S_PMC1				0x0006
> +
> +/* Port Mirror Control Register */
> +#define RTL8366S_PMCR				0x0007
> +#define RTL8366S_PMCR_SOURCE_MASK		GENMASK(3, 0)
> +#define RTL8366S_PMCR_MINITOR_MASK		GENMASK(7, 4)
> +#define RTL8366S_PMCR_MIRROR_RX			BIT(8)
> +#define RTL8366S_PMCR_MIRROR_TX			BIT(9)
> +#define RTL8366S_PMCR_MIRROR_SPC		BIT(10)
> +#define RTL8366S_PMCR_MIRROR_ISO		BIT(11)
> +
> +/* Keep format on egress */
> +#define RTL8366S_EGRESS_KEEP_FORMAT_REG		0x0008
> +#define RTL8366S_EGRESS_KEEP_FORMAT_MASK	GENMASK(15, 8)
> +#define RTL8366S_EGRESS_KEEP_FORMAT_ALL	\
> +	FIELD_PREP(RTL8366S_EGRESS_KEEP_FORMAT_MASK, RTL8366S_PORT_ALL)
> +
> +/* Green Ethernet Feature (based on GPL_BELKIN_F5D8235-4_v1000 v1.01.24) */
> +#define RTL8366S_GREEN_ETHERNET_CTRL_REG	0x000a
> +#define RTL8366S_GREEN_ETHERNET_CTRL_MASK	0x0018
> +#define RTL8366S_GREEN_ETHERNET_TX		BIT(3)
> +#define RTL8366S_GREEN_ETHERNET_RX		BIT(4)
> +
> +/* bits 0..7 = port 0, bits 8..15 = port 1 */
> +#define RTL8366S_PAACR0			0x0011
> +/* bits 0..7 = port 2, bits 8..15 = port 3 */
> +#define RTL8366S_PAACR1			0x0012
> +/* bits 0..7 = port 4, bits 8..15 = port 5 */
> +#define RTL8366S_PAACR2			0x0013
> +#define RTL8366S_PAACR_SPEED_10M	0
> +#define RTL8366S_PAACR_SPEED_100M	1
> +#define RTL8366S_PAACR_SPEED_1000M	2
> +#define RTL8366S_PAACR_FULL_DUPLEX	BIT(2)
> +#define RTL8366S_PAACR_LINK_UP		BIT(4)
> +#define RTL8366S_PAACR_TX_PAUSE		BIT(5)
> +#define RTL8366S_PAACR_RX_PAUSE		BIT(6)
> +#define RTL8366S_PAACR_AN		BIT(7)
> +
> +#define RTL8366S_PAACR_CPU_PORT	(RTL8366S_PAACR_SPEED_1000M | \
> +				 RTL8366S_PAACR_FULL_DUPLEX | \
> +				 RTL8366S_PAACR_LINK_UP | \
> +				 RTL8366S_PAACR_TX_PAUSE | \
> +				 RTL8366S_PAACR_RX_PAUSE)
> +
> +/* Spanning Tree Protocol register */
> +#define RTL8366S_STP_BASE			0x003a
> +#define RTL8366S_STP_REG(_p)	\
> +		(RTL8366S_STP_BASE + (_p))
> +#define RTL8366S_STP_MASK			GENMASK(1, 0)
> +
> +enum RTL8366S_STP_STATE
> +{
> +	RTL8366S_STP_DISABLED = 0,
> +	RTL8366S_STP_BLOCKING,
> +	RTL8366S_STP_LEARNING,
> +	RTL8366S_STP_FORWARDING,
> +};
> +
> +#define RTL8366S_CPU_CTRL_REG			0x004f
> +#define RTL8366S_CPU_DROP_UNKNOWN_DA		BIT(14)
> +#define RTL8366S_CPU_NO_TAG			BIT(15)
> +
> +#define RTL8366S_PORT_VLAN_CTRL_BASE		0x0058
> +#define RTL8366S_PORT_VLAN_CTRL_REG(_p)  \
> +		(RTL8366S_PORT_VLAN_CTRL_BASE + (_p) / 4)
> +#define RTL8366S_PORT_VLAN_CTRL_MASK		0xf
> +#define RTL8366S_PORT_VLAN_CTRL_SHIFT(_p)	(4 * ((_p) % 4))
> +
> +#define RTL8366S_PORT_LINK_STATUS_BASE		0x0060
> +#define RTL8366S_PORT_STATUS_SPEED_MASK		0x0003
> +#define RTL8366S_PORT_STATUS_DUPLEX_MASK	0x0004
> +#define RTL8366S_PORT_STATUS_LINK_MASK		0x0010
> +#define RTL8366S_PORT_STATUS_TXPAUSE_MASK	0x0020
> +#define RTL8366S_PORT_STATUS_RXPAUSE_MASK	0x0040
> +#define RTL8366S_PORT_STATUS_AN_MASK		0x0080
> +
> +#define RTL8366S_RESET_CTRL_REG			0x0100
> +#define RTL8366S_CHIP_CTRL_RESET_HW		BIT(0)
> +#define RTL8366S_CHIP_CTRL_RESET_SW		BIT(1)
> +
> +#define RTL8366S_CHIP_VERSION_CTRL_REG		0x0104
> +#define RTL8366S_CHIP_VERSION_MASK		0xf
> +#define RTL8366S_CHIP_ID_REG			0x0105
> +#define RTL8366S_CHIP_ID_8366			0x8366
> +
> +/* VLAN 4K access registers */
> +#define RTL8366S_VLAN_TB_CTRL_REG		0x010f
> +
> +#define RTL8366S_TABLE_ACCESS_CTRL_REG		0x0180
> +#define RTL8366S_TABLE_VLAN_READ_CTRL		0x0E01
> +#define RTL8366S_TABLE_VLAN_WRITE_CTRL		0x0F01
> +
> +#define RTL8366S_VLAN_TABLE_READ_BASE		0x018B
> +#define RTL8366S_VLAN_TABLE_WRITE_BASE		0x0185
> +
> +/* VLAN filtering registers */
> +#define RTL8366S_VLAN_TAGINGRESS_REG		0x0378
> +#define RTL8366S_VLAN_MEMBERINGRESS_REG		0x0379
> +
> +/* Link aggregation registers */
> +#define RTL8366S_LAGCR				0x0380
> +#define RTL8366S_LAGCR_MODE_DUMP		BIT(0)
> +#define RTL8366S_LAGCR_HASHSEL_MASK		GENMASK(2, 1)
> +#define RTL8366S_LAGCR_HASHSEL_DA_SA	\
> +	FIELD_PREP(RTL8366S_LAGCR_HASHSEL_MASK, 0)
> +#define RTL8366S_LAGCR_HASHSEL_DA	\
> +	FIELD_PREP(RTL8366S_LAGCR_HASHSEL_MASK, 1)
> +#define RTL8366S_LAGCR_HASHSEL_SA	\
> +	FIELD_PREP(RTL8366S_LAGCR_HASHSEL_MASK, 2)
> +#define RTL8366S_LAGCR_PORTMAP_MASK		GENMASK(8, 3)
> +
> +#define RTL8366S_LAG_MAPPING_BASE		0x0381
> +#define RTL8366S_LAG_MAPPING_BIT		3
> +#define RTL8366S_LAG_MAPPING_MASK		GENMASK(2, 0)
> +
> +#define RTL8366S_LAG_FC_CTRL_REG		0x0383
> +#define RTL8366S_LAG_FC_MASK			GENMASK(5, 0)
> +
> +#define RTL8366S_LAG_QEMPTY_REG			0x0384
> +#define RTL8366S_LAG_QEMPTY_MASK		GENMASK(5, 0)
> +
> +/* RMA register address */
> +#define RTL8366S_RMA_CONTROL_REG		0x0391
> +#define RTL8366S_RMA_IGMP			BIT(10)
> +#define RTL8366S_RMA_MLD			BIT(11)
> +#define RTL8366S_RMA_USER_DEFINED_BASE		0x0392
> +
> +/* LED control registers */
> +#define RTL8366S_LED_BLINKRATE_REG		0x0420
> +#define RTL8366S_LED_BLINKRATE_BIT		0
> +#define RTL8366S_LED_BLINKRATE_MASK		0x0007
> +
> +#define RTL8366S_LED_CTRL_REG			0x0421
> +#define RTL8366S_LED_0_1_CTRL_REG		0x0422
> +#define RTL8366S_LED_2_3_CTRL_REG		0x0423
> +
> +#define RTL8366S_MIB_COUNT			33
> +#define RTL8366S_GLOBAL_MIB_COUNT		1
> +#define RTL8366S_MIB_COUNTER_PORT_OFFSET	0x0040
> +#define RTL8366S_MIB_COUNTER_BASE		0x1000
> +#define RTL8366S_MIB_COUNTER_PORT_OFFSET2	0x0008
> +#define RTL8366S_MIB_COUNTER_BASE2		0x1180
> +#define RTL8366S_MIB_CTRL_REG			0x11F0
> +#define RTL8366S_MIB_CTRL_USER_MASK		0x01FF
> +#define RTL8366S_MIB_CTRL_BUSY_MASK		0x0001
> +#define RTL8366S_MIB_CTRL_RESET_MASK		0x0002
> +
> +#define RTL8366S_MIB_CTRL_GLOBAL_RESET_MASK	0x0004
> +#define RTL8366S_MIB_CTRL_PORT_RESET_BIT	0x0003
> +#define RTL8366S_MIB_CTRL_PORT_RESET_MASK	0x01FC
> +
> +
> +#define RTL8366S_VLAN_MC_BASE(_x)		(0x0016 + (_x) * 2)
> +
> +#define RTL8366S_MAC_FORCE_CTRL0_REG		0x0F04
> +#define RTL8366S_MAC_FORCE_CTRL1_REG		0x0F05
> +
> +/* PHY registers control */
> +#define RTL8366S_PHY_ACCESS_CTRL_REG		0x8028
> +#define RTL8366S_PHY_ACCESS_DATA_REG		0x8029
> +
> +#define RTL8366S_PHY_CTRL_READ			1
> +#define RTL8366S_PHY_CTRL_WRITE			0
> +
> +#define RTL8366S_PORT_NUM_CPU		5
> +#define RTL8366S_NUM_PORTS		6
> +#define RTL8366S_NUM_VLANS		16
> +#define RTL8366S_NUM_LEDGROUPS		4
> +#define RTL8366S_NUM_VIDS		4096
> +#define RTL8366S_PRIORITYMAX		7
> +#define RTL8366S_FIDMAX			7
> +
> +
> +#define RTL8366S_PORT_1			BIT(0) /* In userspace port 0 */
> +#define RTL8366S_PORT_2			BIT(1) /* In userspace port 1 */
> +#define RTL8366S_PORT_3			BIT(2) /* In userspace port 2 */
> +#define RTL8366S_PORT_4			BIT(3) /* In userspace port 3 */
> +#define RTL8366S_PORT_5			BIT(4) /* In userspace port 4 */
> +#define RTL8366S_PORT_CPU		BIT(5) /* CPU port */
> +
> +#define RTL8366S_PORT_ALL		(RTL8366S_PORT_1 |	\
> +					 RTL8366S_PORT_2 |	\
> +					 RTL8366S_PORT_3 |	\
> +					 RTL8366S_PORT_4 |	\
> +					 RTL8366S_PORT_5 |	\
> +					 RTL8366S_PORT_CPU)
> +
> +#define RTL8366S_PORT_ALL_BUT_CPU	(RTL8366S_PORT_1 |	\
> +					 RTL8366S_PORT_2 |	\
> +					 RTL8366S_PORT_3 |	\
> +					 RTL8366S_PORT_4 |	\
> +					 RTL8366S_PORT_5)
> +
> +#define RTL8366S_PORT_ALL_EXTERNAL	RTL8366S_PORT_ALL_BUT_CPU
> +
> +#define RTL8366S_PORT_ALL_INTERNAL	RTL8366S_PORT_CPU
> +
> +#define RTL8366S_VLAN_VID_MASK		0xfff
> +#define RTL8366S_VLAN_PRIORITY_SHIFT	12
> +#define RTL8366S_VLAN_PRIORITY_MASK	0x7
> +#define RTL8366S_VLAN_MEMBER_MASK	0x3f
> +#define RTL8366S_VLAN_UNTAG_SHIFT	6
> +#define RTL8366S_VLAN_UNTAG_MASK	0x3f
> +#define RTL8366S_VLAN_FID_SHIFT		12
> +#define RTL8366S_VLAN_FID_MASK		0x7
> +
> +/* Green Ethernet Feature for PHY ports */
> +#define RTL8366S_PHY_POWER_SAVING_CTRL_REG	0x000c
> +#define RTL8366S_PHY_POWER_SAVING_MASK		0x1000
> +
> +#define RTL8366S_PHY_REG_MASK			0x001f
> +#define RTL8366S_PHY_PAGE_OFFSET		5
> +#define RTL8366S_PHY_PAGE_MASK			GENMASK(7, 5)
> +#define RTL8366S_PHY_NO_OFFSET			9
> +#define RTL8366S_PHY_NO_MASK			GENMASK(13, 9)
> +
> +#define RTL8366S_MIB_RXB_ID		0	/* IfInOctets */
> +#define RTL8366S_MIB_TXB_ID		20	/* IfOutOctets */

I'm not going to do a detailed review on a driver that is a 90% copy of
rtl8366rb. You should probably spend some time to avoid duplicating
what is common.

> +
> +static struct rtl8366_mib_counter rtl8366s_mib_counters[] = {
> +	{ 0,  0, 4, "IfInOctets"				},
> +	{ 0,  4, 4, "EtherStatsOctets"				},
> +	{ 0,  8, 2, "EtherStatsUnderSizePkts"			},
> +	{ 0, 10, 2, "EtherFragments"				},
> +	{ 0, 12, 2, "EtherStatsPkts64Octets"			},
> +	{ 0, 14, 2, "EtherStatsPkts65to127Octets"		},
> +	{ 0, 16, 2, "EtherStatsPkts128to255Octets"		},
> +	{ 0, 18, 2, "EtherStatsPkts256to511Octets"		},
> +	{ 0, 20, 2, "EtherStatsPkts512to1023Octets"		},
> +	{ 0, 22, 2, "EtherStatsPkts1024to1518Octets"		},
> +	{ 0, 24, 2, "EtherOversizeStats"			},
> +	{ 0, 26, 2, "EtherStatsJabbers"				},
> +	{ 0, 28, 2, "IfInUcastPkts"				},
> +	{ 0, 30, 2, "EtherStatsMulticastPkts"			},
> +	{ 0, 32, 2, "EtherStatsBroadcastPkts"			},
> +	{ 0, 34, 2, "EtherStatsDropEvents"			},
> +	{ 0, 36, 2, "Dot3StatsFCSErrors"			},
> +	{ 0, 38, 2, "Dot3StatsSymbolErrors"			},
> +	{ 0, 40, 2, "Dot3InPauseFrames"				},
> +	{ 0, 42, 2, "Dot3ControlInUnknownOpcodes"		},
> +	{ 0, 44, 4, "IfOutOctets"				},
> +	{ 0, 48, 2, "Dot3StatsSingleCollisionFrames"		},
> +	{ 0, 50, 2, "Dot3StatMultipleCollisionFrames"		},
> +	{ 0, 52, 2, "Dot3sDeferredTransmissions"		},
> +	{ 0, 54, 2, "Dot3StatsLateCollisions"			},
> +	{ 0, 56, 2, "EtherStatsCollisions"			},
> +	{ 0, 58, 2, "Dot3StatsExcessiveCollisions"		},
> +	{ 0, 60, 2, "Dot3OutPauseFrames"			},
> +	{ 0, 62, 2, "Dot1dBasePortDelayExceededDiscards"	},
> +
> +	/*
> +	 * The following counters are accessible at a different
> +	 * base address.
> +	 */
> +	{ 1,  0, 2, "Dot1dTpPortInDiscards"			},
> +	{ 1,  2, 2, "IfOutUcastPkts"				},
> +	{ 1,  4, 2, "IfOutMulticastPkts"			},
> +	{ 1,  6, 2, "IfOutBroadcastPkts"			},
> +};
> +
> +static int rtl8366s_get_mib_counter(struct realtek_smi *smi,
> +				    int port,
> +				    struct rtl8366_mib_counter *mib,
> +				    u64 *mibvalue)
> +{
> +	u32 addr, val;
> +	int ret;
> +	int i;
> +
> +	switch (mib->base) {
> +	case 0:
> +		addr = RTL8366S_MIB_COUNTER_BASE +
> +		       RTL8366S_MIB_COUNTER_PORT_OFFSET * port;
> +		break;
> +	case 1:
> +		addr = RTL8366S_MIB_COUNTER_BASE2 +
> +		       RTL8366S_MIB_COUNTER_PORT_OFFSET2 * port;
> +		break;
> +	default:
> +		return -EINVAL;
> +	}
> +
> +	addr += mib->offset;
> +
> +	/*
> +	 * Writing access counter address first
> +	 * then ASIC will prepare 64bits counter wait for being retrived
> +	 */
> +	ret = regmap_write(smi->map, addr, 0);
> +	if (ret)
> +		return ret;
> +
> +	/* read MIB control register */
> +	ret =  regmap_read(smi->map, RTL8366S_MIB_CTRL_REG, &val);
> +	if (ret)
> +		return -EIO;
> +
> +	if (val & RTL8366S_MIB_CTRL_BUSY_MASK)
> +		return -EBUSY;
> +
> +	if (val & RTL8366S_MIB_CTRL_RESET_MASK)
> +		return -EIO;
> +
> +	/* Read each individual MIB 16 bits at the time */
> +	*mibvalue = 0;
> +	for (i = mib->length; i > 0; i--) {
> +		ret = regmap_read(smi->map, addr + (i - 1), &val);
> +		if (ret)
> +			return ret;
> +		*mibvalue = (*mibvalue << 16) | (val & 0xFFFF);
> +	}
> +	return 0;
> +}
> +
> +static int rtl8366s_phy_read(struct realtek_smi *smi, int phy, int regnum)
> +{
> +	u32 val;
> +	u32 reg;
> +	int ret;
> +
> +	if (phy > RTL8366S_PHY_NO_MAX)
> +		return -EINVAL;
> +
> +	ret = regmap_write(smi->map, RTL8366S_PHY_ACCESS_CTRL_REG,
> +			   RTL8366S_PHY_CTRL_READ);
> +	if (ret)
> +		return ret;
> +
> +	reg = 0x8000 | (1 << (phy + RTL8366S_PHY_NO_OFFSET)) | regnum;
> +
> +	ret = regmap_write(smi->map, reg, 0);
> +	if (ret)
> +		return ret;
> +
> +	ret = regmap_read(smi->map, RTL8366S_PHY_ACCESS_DATA_REG, &val);
> +	if (ret)
> +		return ret;
> +
> +	return val;
> +}
> +
> +static int rtl8366s_phy_write(struct realtek_smi *smi, int phy, int regnum,
> +			       u16 val)
> +{
> +	u32 reg;
> +	int ret;
> +
> +	if (phy > RTL8366S_PHY_NO_MAX)
> +		return -EINVAL;
> +
> +	ret = regmap_write(smi->map, RTL8366S_PHY_ACCESS_CTRL_REG,
> +			   RTL8366S_PHY_CTRL_WRITE);
> +	if (ret)
> +		return ret;
> +
> +	reg = 0x8000 | (1 << (phy + RTL8366S_PHY_NO_OFFSET)) | regnum;
> +
> +	ret = regmap_write(smi->map, reg, val);
> +	if (ret)
> +		return ret;
> +
> +	return 0;
> +}
> +
> +static int rtl8366s_reset_chip(struct realtek_smi *smi)
> +{
> +	int timeout = 10;
> +	u32 val;
> +	int ret;
> +
> +	realtek_smi_write_reg_noack(smi, RTL8366S_RESET_CTRL_REG,
> +				    RTL8366S_CHIP_CTRL_RESET_HW);
> +	do {
> +		usleep_range(20000, 25000);
> +		ret = regmap_read(smi->map, RTL8366S_RESET_CTRL_REG, &val);
> +		if (ret)
> +			return ret;
> +
> +		if (!(val & RTL8366S_CHIP_CTRL_RESET_HW))
> +			break;
> +	} while (--timeout);
> +
> +	if (!timeout) {
> +		dev_err(smi->dev, "timeout waiting for the switch to reset\n");
> +		return -EIO;
> +	}
> +
> +	return 0;
> +}
> +
> +static enum dsa_tag_protocol rtl8366s_get_tag_protocol(struct dsa_switch *ds,
> +						       int port,
> +						       enum dsa_tag_protocol mp)
> +{
> +	return DSA_TAG_PROTO_RTL8366S;
> +}
> +
> +static void
> +rtl8366s_mac_link_up(struct dsa_switch *ds, int port, unsigned int mode,
> +		     phy_interface_t interface, struct phy_device *phydev,
> +		     int speed, int duplex, bool tx_pause, bool rx_pause)
> +{
> +	struct realtek_smi *smi = ds->priv;
> +	int ret;
> +
> +	if (port != RTL8366S_PORT_NUM_CPU)
> +		return;
> +
> +	/* Force the fixed CPU port into 1Gbit mode, no autonegotiation */
> +	ret = regmap_update_bits(smi->map, RTL8366S_MAC_FORCE_CTRL1_REG,
> +				 BIT(port), BIT(port));
> +	if (ret) {
> +		dev_err(smi->dev, "failed to force 1Gbit on CPU port\n");
> +		return;
> +	}
> +
> +	ret = regmap_update_bits(smi->map, RTL8366S_PAACR2,
> +				 0xFF00U,
> +				 RTL8366S_PAACR_CPU_PORT << 8);
> +	if (ret) {
> +		dev_err(smi->dev, "failed to set PAACR on CPU port\n");
> +		return;
> +	}
> +
> +	/* Enable the CPU port */
> +	ret = regmap_update_bits(smi->map, RTL8366S_PECR, BIT(port),
> +				 0);
> +	if (ret) {
> +		dev_err(smi->dev, "failed to enable the CPU port\n");
> +		return;
> +	}
> +}
> +
> +static void
> +rtl8366s_mac_link_down(struct dsa_switch *ds, int port, unsigned int mode,
> +		       phy_interface_t interface)
> +{
> +	struct realtek_smi *smi = ds->priv;
> +	int ret;
> +
> +	if (port != RTL8366S_PORT_NUM_CPU)
> +		return;
> +
> +	/* Disable the CPU port */
> +	ret = regmap_update_bits(smi->map, RTL8366S_PECR, BIT(port),
> +				 BIT(port));
> +	if (ret) {
> +		dev_err(smi->dev, "failed to disable the CPU port\n");
> +		return;
> +	}
> +}
> +
> +static int rtl8366s_setup(struct dsa_switch *ds)
> +{
> +	struct realtek_smi *smi = ds->priv;
> +	int ret;
> +
> +	/* Reset chip */
> +	ret = rtl8366s_reset_chip(smi);
> +	if (ret)
> +		return ret;
> +
> +	/* Set up the "green ethernet" feature */
> +	ret = regmap_update_bits(smi->map, RTL8366S_GREEN_ETHERNET_CTRL_REG,
> +				 RTL8366S_GREEN_ETHERNET_CTRL_MASK,
> +				 RTL8366S_GREEN_ETHERNET_TX |
> +				 RTL8366S_GREEN_ETHERNET_RX);
> +	if (ret)
> +		return ret;
> +
> +	/* Enable CPU port with custom tag 8899 */
> +	ret = regmap_write(smi->map, RTL8366S_CPU_CTRL_REG,
> +			   RTL8366S_PORT_CPU);
> +	if (ret)
> +		return ret;
> +
> +	/* Make sure we default-enable the fixed CPU port */
> +	ret = regmap_update_bits(smi->map, RTL8366S_PECR,
> +				 RTL8366S_PORT_CPU, 0);
> +	if (ret)
> +		return ret;
> +
> +	/* Enable learning for all ports */
> +	ret = regmap_write(smi->map, RTL8366S_SSCR0, 0);
> +	if (ret)
> +		return ret;

DSA has a new way of dealing with address learning.
For the user ports, .port_pre_bridge_flags and .port_bridge_flags get
called now. You should start with address learning disabled and implement
those operations.
For the CPU port, DSA does not manage address learning (yet). So if you
support it there, you need to enable it manually.
Qingfang Deng Feb. 17, 2021, 12:45 p.m. UTC | #3
On Wed, Feb 17, 2021 at 7:12 PM Linus Walleij <linus.walleij@linaro.org> wrote:
>
> Overall the question about whether the switch is 5+1 or 7+1 is my
> big design remark.
>
> Maybe it is only 5+1 who knows...

Yes, it's 5+1.
https://cdn.jsdelivr.net/gh/libc0607/Realtek_switch_hacking@files/rtl8366s_8366sr_datasheet_vpre-1.4_20071022.pdf

>
> Yours,
> Linus Walleij
Linus Walleij Feb. 17, 2021, 3:44 p.m. UTC | #4
On Wed, Feb 17, 2021 at 1:25 PM Vladimir Oltean <olteanv@gmail.com> wrote:

> I'm not going to do a detailed review on a driver that is a 90% copy of
> rtl8366rb. You should probably spend some time to avoid duplicating
> what is common.

The split of common and individual code comes from the OpenWrt
driver, maybe it wasn't optimal. Maybe we can move some of the
driver into the shared rtl8366.c file.

Yours,
Linus Walleij
diff mbox series

Patch

diff --git a/drivers/net/dsa/Kconfig b/drivers/net/dsa/Kconfig
index 3af373e90806..52f1df6ef53a 100644
--- a/drivers/net/dsa/Kconfig
+++ b/drivers/net/dsa/Kconfig
@@ -75,6 +75,7 @@  config NET_DSA_REALTEK_SMI
 	tristate "Realtek SMI Ethernet switch family support"
 	depends on NET_DSA
 	select NET_DSA_TAG_RTL4_A
+	select NET_DSA_TAG_RTL8366S
 	select FIXED_PHY
 	select IRQ_DOMAIN
 	select REALTEK_PHY
diff --git a/drivers/net/dsa/Makefile b/drivers/net/dsa/Makefile
index f3598c040994..8c51c25d8378 100644
--- a/drivers/net/dsa/Makefile
+++ b/drivers/net/dsa/Makefile
@@ -10,7 +10,7 @@  obj-$(CONFIG_NET_DSA_MT7530)	+= mt7530.o
 obj-$(CONFIG_NET_DSA_MV88E6060) += mv88e6060.o
 obj-$(CONFIG_NET_DSA_QCA8K)	+= qca8k.o
 obj-$(CONFIG_NET_DSA_REALTEK_SMI) += realtek-smi.o
-realtek-smi-objs		:= realtek-smi-core.o rtl8366.o rtl8366rb.o
+realtek-smi-objs		:= realtek-smi-core.o rtl8366.o rtl8366rb.o rtl8366s.o
 obj-$(CONFIG_NET_DSA_SMSC_LAN9303) += lan9303-core.o
 obj-$(CONFIG_NET_DSA_SMSC_LAN9303_I2C) += lan9303_i2c.o
 obj-$(CONFIG_NET_DSA_SMSC_LAN9303_MDIO) += lan9303_mdio.o
diff --git a/drivers/net/dsa/realtek-smi-core.c b/drivers/net/dsa/realtek-smi-core.c
index 8e49d4f85d48..e0ced416c362 100644
--- a/drivers/net/dsa/realtek-smi-core.c
+++ b/drivers/net/dsa/realtek-smi-core.c
@@ -480,9 +480,8 @@  static const struct of_device_id realtek_smi_of_match[] = {
 		.data = &rtl8366rb_variant,
 	},
 	{
-		/* FIXME: add support for RTL8366S and more */
 		.compatible = "realtek,rtl8366s",
-		.data = NULL,
+		.data = &rtl8366s_variant,
 	},
 	{ /* sentinel */ },
 };
diff --git a/drivers/net/dsa/realtek-smi-core.h b/drivers/net/dsa/realtek-smi-core.h
index fcf465f7f922..a5d45b6f6de3 100644
--- a/drivers/net/dsa/realtek-smi-core.h
+++ b/drivers/net/dsa/realtek-smi-core.h
@@ -143,5 +143,6 @@  int rtl8366_get_sset_count(struct dsa_switch *ds, int port, int sset);
 void rtl8366_get_ethtool_stats(struct dsa_switch *ds, int port, uint64_t *data);
 
 extern const struct realtek_smi_variant rtl8366rb_variant;
+extern const struct realtek_smi_variant rtl8366s_variant;
 
 #endif /*  _REALTEK_SMI_H */
diff --git a/drivers/net/dsa/rtl8366s.c b/drivers/net/dsa/rtl8366s.c
new file mode 100644
index 000000000000..718e492f8bbd
--- /dev/null
+++ b/drivers/net/dsa/rtl8366s.c
@@ -0,0 +1,1165 @@ 
+// SPDX-License-Identifier: GPL-2.0
+/* Realtek SMI subdriver for the Realtek RTL8366S ethernet switch
+ *
+ * Copyright (C) 2021 DENG, Qingfang <dqfext@gmail.com>
+ * Copyright (C) 2009-2010 Gabor Juhos <juhosg@openwrt.org>
+ * Copyright (C) 2010 Antti Seppälä <a.seppala@gmail.com>
+ */
+
+#include <linux/bitfield.h>
+#include <linux/etherdevice.h>
+#include <linux/if_bridge.h>
+#include <linux/regmap.h>
+
+#include "realtek-smi-core.h"
+
+#define RTL8366S_PHY_NO_MAX	4
+#define RTL8366S_PHY_PAGE_MAX	7
+#define RTL8366S_PHY_ADDR_MAX	31
+
+/* Switch Global Configuration register */
+#define RTL8366S_SGCR				0x0000
+#define RTL8366S_SGCR_EN_BC_STORM_CTRL		BIT(0)
+#define RTL8366S_SGCR_MAX_LENGTH(_x)		(_x << 4)
+#define RTL8366S_SGCR_MAX_LENGTH_MASK		RTL8366S_SGCR_MAX_LENGTH(0x3)
+#define RTL8366S_SGCR_MAX_LENGTH_1522		RTL8366S_SGCR_MAX_LENGTH(0x0)
+#define RTL8366S_SGCR_MAX_LENGTH_1536		RTL8366S_SGCR_MAX_LENGTH(0x1)
+#define RTL8366S_SGCR_MAX_LENGTH_1552		RTL8366S_SGCR_MAX_LENGTH(0x2)
+#define RTL8366S_SGCR_MAX_LENGTH_16000		RTL8366S_SGCR_MAX_LENGTH(0x3)
+#define RTL8366S_SGCR_EN_VLAN			BIT(13)
+
+/* Port Enable Control register */
+#define RTL8366S_PECR				0x0001
+
+/* Switch Security Control registers */
+#define RTL8366S_SSCR0				0x0002
+#define RTL8366S_SSCR1				0x0003
+#define RTL8366S_SSCR2				0x0004
+#define RTL8366S_SSCR2_DROP_UNKNOWN_DA		BIT(0)
+
+/* Port Mode Control registers */
+#define RTL8366S_PMC0				0x0005
+#define RTL8366S_PMC0_SPI			BIT(0)
+#define RTL8366S_PMC0_EN_AUTOLOAD		BIT(1)
+#define RTL8366S_PMC0_PROBE			BIT(2)
+#define RTL8366S_PMC0_DIS_BISR			BIT(3)
+#define RTL8366S_PMC0_ADCTEST			BIT(4)
+#define RTL8366S_PMC0_SRAM_DIAG			BIT(5)
+#define RTL8366S_PMC0_EN_SCAN			BIT(6)
+#define RTL8366S_PMC0_P4_IOMODE_MASK		GENMASK(9, 7)
+#define RTL8366S_PMC0_P4_IOMODE_PHY_TO_GMAC	\
+	FIELD_PREP(RTL8366S_PMC0_P4_IOMODE_MASK, 0)
+#define RTL8366S_PMC0_P4_IOMODE_GMAC_TO_RGMII	\
+	FIELD_PREP(RTL8366S_PMC0_P4_IOMODE_MASK, 1)
+#define RTL8366S_PMC0_P4_IOMODE_PHY_TO_RGMII	\
+	FIELD_PREP(RTL8366S_PMC0_P4_IOMODE_MASK, 2)
+#define RTL8366S_PMC0_P5_IOMODE_MASK		GENMASK(12, 10)
+#define RTL8366S_PMC0_SDSMODE_MASK		GENMASK(15, 13)
+#define RTL8366S_PMC1				0x0006
+
+/* Port Mirror Control Register */
+#define RTL8366S_PMCR				0x0007
+#define RTL8366S_PMCR_SOURCE_MASK		GENMASK(3, 0)
+#define RTL8366S_PMCR_MINITOR_MASK		GENMASK(7, 4)
+#define RTL8366S_PMCR_MIRROR_RX			BIT(8)
+#define RTL8366S_PMCR_MIRROR_TX			BIT(9)
+#define RTL8366S_PMCR_MIRROR_SPC		BIT(10)
+#define RTL8366S_PMCR_MIRROR_ISO		BIT(11)
+
+/* Keep format on egress */
+#define RTL8366S_EGRESS_KEEP_FORMAT_REG		0x0008
+#define RTL8366S_EGRESS_KEEP_FORMAT_MASK	GENMASK(15, 8)
+#define RTL8366S_EGRESS_KEEP_FORMAT_ALL	\
+	FIELD_PREP(RTL8366S_EGRESS_KEEP_FORMAT_MASK, RTL8366S_PORT_ALL)
+
+/* Green Ethernet Feature (based on GPL_BELKIN_F5D8235-4_v1000 v1.01.24) */
+#define RTL8366S_GREEN_ETHERNET_CTRL_REG	0x000a
+#define RTL8366S_GREEN_ETHERNET_CTRL_MASK	0x0018
+#define RTL8366S_GREEN_ETHERNET_TX		BIT(3)
+#define RTL8366S_GREEN_ETHERNET_RX		BIT(4)
+
+/* bits 0..7 = port 0, bits 8..15 = port 1 */
+#define RTL8366S_PAACR0			0x0011
+/* bits 0..7 = port 2, bits 8..15 = port 3 */
+#define RTL8366S_PAACR1			0x0012
+/* bits 0..7 = port 4, bits 8..15 = port 5 */
+#define RTL8366S_PAACR2			0x0013
+#define RTL8366S_PAACR_SPEED_10M	0
+#define RTL8366S_PAACR_SPEED_100M	1
+#define RTL8366S_PAACR_SPEED_1000M	2
+#define RTL8366S_PAACR_FULL_DUPLEX	BIT(2)
+#define RTL8366S_PAACR_LINK_UP		BIT(4)
+#define RTL8366S_PAACR_TX_PAUSE		BIT(5)
+#define RTL8366S_PAACR_RX_PAUSE		BIT(6)
+#define RTL8366S_PAACR_AN		BIT(7)
+
+#define RTL8366S_PAACR_CPU_PORT	(RTL8366S_PAACR_SPEED_1000M | \
+				 RTL8366S_PAACR_FULL_DUPLEX | \
+				 RTL8366S_PAACR_LINK_UP | \
+				 RTL8366S_PAACR_TX_PAUSE | \
+				 RTL8366S_PAACR_RX_PAUSE)
+
+/* Spanning Tree Protocol register */
+#define RTL8366S_STP_BASE			0x003a
+#define RTL8366S_STP_REG(_p)	\
+		(RTL8366S_STP_BASE + (_p))
+#define RTL8366S_STP_MASK			GENMASK(1, 0)
+
+enum RTL8366S_STP_STATE
+{
+	RTL8366S_STP_DISABLED = 0,
+	RTL8366S_STP_BLOCKING,
+	RTL8366S_STP_LEARNING,
+	RTL8366S_STP_FORWARDING,
+};
+
+#define RTL8366S_CPU_CTRL_REG			0x004f
+#define RTL8366S_CPU_DROP_UNKNOWN_DA		BIT(14)
+#define RTL8366S_CPU_NO_TAG			BIT(15)
+
+#define RTL8366S_PORT_VLAN_CTRL_BASE		0x0058
+#define RTL8366S_PORT_VLAN_CTRL_REG(_p)  \
+		(RTL8366S_PORT_VLAN_CTRL_BASE + (_p) / 4)
+#define RTL8366S_PORT_VLAN_CTRL_MASK		0xf
+#define RTL8366S_PORT_VLAN_CTRL_SHIFT(_p)	(4 * ((_p) % 4))
+
+#define RTL8366S_PORT_LINK_STATUS_BASE		0x0060
+#define RTL8366S_PORT_STATUS_SPEED_MASK		0x0003
+#define RTL8366S_PORT_STATUS_DUPLEX_MASK	0x0004
+#define RTL8366S_PORT_STATUS_LINK_MASK		0x0010
+#define RTL8366S_PORT_STATUS_TXPAUSE_MASK	0x0020
+#define RTL8366S_PORT_STATUS_RXPAUSE_MASK	0x0040
+#define RTL8366S_PORT_STATUS_AN_MASK		0x0080
+
+#define RTL8366S_RESET_CTRL_REG			0x0100
+#define RTL8366S_CHIP_CTRL_RESET_HW		BIT(0)
+#define RTL8366S_CHIP_CTRL_RESET_SW		BIT(1)
+
+#define RTL8366S_CHIP_VERSION_CTRL_REG		0x0104
+#define RTL8366S_CHIP_VERSION_MASK		0xf
+#define RTL8366S_CHIP_ID_REG			0x0105
+#define RTL8366S_CHIP_ID_8366			0x8366
+
+/* VLAN 4K access registers */
+#define RTL8366S_VLAN_TB_CTRL_REG		0x010f
+
+#define RTL8366S_TABLE_ACCESS_CTRL_REG		0x0180
+#define RTL8366S_TABLE_VLAN_READ_CTRL		0x0E01
+#define RTL8366S_TABLE_VLAN_WRITE_CTRL		0x0F01
+
+#define RTL8366S_VLAN_TABLE_READ_BASE		0x018B
+#define RTL8366S_VLAN_TABLE_WRITE_BASE		0x0185
+
+/* VLAN filtering registers */
+#define RTL8366S_VLAN_TAGINGRESS_REG		0x0378
+#define RTL8366S_VLAN_MEMBERINGRESS_REG		0x0379
+
+/* Link aggregation registers */
+#define RTL8366S_LAGCR				0x0380
+#define RTL8366S_LAGCR_MODE_DUMP		BIT(0)
+#define RTL8366S_LAGCR_HASHSEL_MASK		GENMASK(2, 1)
+#define RTL8366S_LAGCR_HASHSEL_DA_SA	\
+	FIELD_PREP(RTL8366S_LAGCR_HASHSEL_MASK, 0)
+#define RTL8366S_LAGCR_HASHSEL_DA	\
+	FIELD_PREP(RTL8366S_LAGCR_HASHSEL_MASK, 1)
+#define RTL8366S_LAGCR_HASHSEL_SA	\
+	FIELD_PREP(RTL8366S_LAGCR_HASHSEL_MASK, 2)
+#define RTL8366S_LAGCR_PORTMAP_MASK		GENMASK(8, 3)
+
+#define RTL8366S_LAG_MAPPING_BASE		0x0381
+#define RTL8366S_LAG_MAPPING_BIT		3
+#define RTL8366S_LAG_MAPPING_MASK		GENMASK(2, 0)
+
+#define RTL8366S_LAG_FC_CTRL_REG		0x0383
+#define RTL8366S_LAG_FC_MASK			GENMASK(5, 0)
+
+#define RTL8366S_LAG_QEMPTY_REG			0x0384
+#define RTL8366S_LAG_QEMPTY_MASK		GENMASK(5, 0)
+
+/* RMA register address */
+#define RTL8366S_RMA_CONTROL_REG		0x0391
+#define RTL8366S_RMA_IGMP			BIT(10)
+#define RTL8366S_RMA_MLD			BIT(11)
+#define RTL8366S_RMA_USER_DEFINED_BASE		0x0392
+
+/* LED control registers */
+#define RTL8366S_LED_BLINKRATE_REG		0x0420
+#define RTL8366S_LED_BLINKRATE_BIT		0
+#define RTL8366S_LED_BLINKRATE_MASK		0x0007
+
+#define RTL8366S_LED_CTRL_REG			0x0421
+#define RTL8366S_LED_0_1_CTRL_REG		0x0422
+#define RTL8366S_LED_2_3_CTRL_REG		0x0423
+
+#define RTL8366S_MIB_COUNT			33
+#define RTL8366S_GLOBAL_MIB_COUNT		1
+#define RTL8366S_MIB_COUNTER_PORT_OFFSET	0x0040
+#define RTL8366S_MIB_COUNTER_BASE		0x1000
+#define RTL8366S_MIB_COUNTER_PORT_OFFSET2	0x0008
+#define RTL8366S_MIB_COUNTER_BASE2		0x1180
+#define RTL8366S_MIB_CTRL_REG			0x11F0
+#define RTL8366S_MIB_CTRL_USER_MASK		0x01FF
+#define RTL8366S_MIB_CTRL_BUSY_MASK		0x0001
+#define RTL8366S_MIB_CTRL_RESET_MASK		0x0002
+
+#define RTL8366S_MIB_CTRL_GLOBAL_RESET_MASK	0x0004
+#define RTL8366S_MIB_CTRL_PORT_RESET_BIT	0x0003
+#define RTL8366S_MIB_CTRL_PORT_RESET_MASK	0x01FC
+
+
+#define RTL8366S_VLAN_MC_BASE(_x)		(0x0016 + (_x) * 2)
+
+#define RTL8366S_MAC_FORCE_CTRL0_REG		0x0F04
+#define RTL8366S_MAC_FORCE_CTRL1_REG		0x0F05
+
+/* PHY registers control */
+#define RTL8366S_PHY_ACCESS_CTRL_REG		0x8028
+#define RTL8366S_PHY_ACCESS_DATA_REG		0x8029
+
+#define RTL8366S_PHY_CTRL_READ			1
+#define RTL8366S_PHY_CTRL_WRITE			0
+
+#define RTL8366S_PORT_NUM_CPU		5
+#define RTL8366S_NUM_PORTS		6
+#define RTL8366S_NUM_VLANS		16
+#define RTL8366S_NUM_LEDGROUPS		4
+#define RTL8366S_NUM_VIDS		4096
+#define RTL8366S_PRIORITYMAX		7
+#define RTL8366S_FIDMAX			7
+
+
+#define RTL8366S_PORT_1			BIT(0) /* In userspace port 0 */
+#define RTL8366S_PORT_2			BIT(1) /* In userspace port 1 */
+#define RTL8366S_PORT_3			BIT(2) /* In userspace port 2 */
+#define RTL8366S_PORT_4			BIT(3) /* In userspace port 3 */
+#define RTL8366S_PORT_5			BIT(4) /* In userspace port 4 */
+#define RTL8366S_PORT_CPU		BIT(5) /* CPU port */
+
+#define RTL8366S_PORT_ALL		(RTL8366S_PORT_1 |	\
+					 RTL8366S_PORT_2 |	\
+					 RTL8366S_PORT_3 |	\
+					 RTL8366S_PORT_4 |	\
+					 RTL8366S_PORT_5 |	\
+					 RTL8366S_PORT_CPU)
+
+#define RTL8366S_PORT_ALL_BUT_CPU	(RTL8366S_PORT_1 |	\
+					 RTL8366S_PORT_2 |	\
+					 RTL8366S_PORT_3 |	\
+					 RTL8366S_PORT_4 |	\
+					 RTL8366S_PORT_5)
+
+#define RTL8366S_PORT_ALL_EXTERNAL	RTL8366S_PORT_ALL_BUT_CPU
+
+#define RTL8366S_PORT_ALL_INTERNAL	RTL8366S_PORT_CPU
+
+#define RTL8366S_VLAN_VID_MASK		0xfff
+#define RTL8366S_VLAN_PRIORITY_SHIFT	12
+#define RTL8366S_VLAN_PRIORITY_MASK	0x7
+#define RTL8366S_VLAN_MEMBER_MASK	0x3f
+#define RTL8366S_VLAN_UNTAG_SHIFT	6
+#define RTL8366S_VLAN_UNTAG_MASK	0x3f
+#define RTL8366S_VLAN_FID_SHIFT		12
+#define RTL8366S_VLAN_FID_MASK		0x7
+
+/* Green Ethernet Feature for PHY ports */
+#define RTL8366S_PHY_POWER_SAVING_CTRL_REG	0x000c
+#define RTL8366S_PHY_POWER_SAVING_MASK		0x1000
+
+#define RTL8366S_PHY_REG_MASK			0x001f
+#define RTL8366S_PHY_PAGE_OFFSET		5
+#define RTL8366S_PHY_PAGE_MASK			GENMASK(7, 5)
+#define RTL8366S_PHY_NO_OFFSET			9
+#define RTL8366S_PHY_NO_MASK			GENMASK(13, 9)
+
+#define RTL8366S_MIB_RXB_ID		0	/* IfInOctets */
+#define RTL8366S_MIB_TXB_ID		20	/* IfOutOctets */
+
+static struct rtl8366_mib_counter rtl8366s_mib_counters[] = {
+	{ 0,  0, 4, "IfInOctets"				},
+	{ 0,  4, 4, "EtherStatsOctets"				},
+	{ 0,  8, 2, "EtherStatsUnderSizePkts"			},
+	{ 0, 10, 2, "EtherFragments"				},
+	{ 0, 12, 2, "EtherStatsPkts64Octets"			},
+	{ 0, 14, 2, "EtherStatsPkts65to127Octets"		},
+	{ 0, 16, 2, "EtherStatsPkts128to255Octets"		},
+	{ 0, 18, 2, "EtherStatsPkts256to511Octets"		},
+	{ 0, 20, 2, "EtherStatsPkts512to1023Octets"		},
+	{ 0, 22, 2, "EtherStatsPkts1024to1518Octets"		},
+	{ 0, 24, 2, "EtherOversizeStats"			},
+	{ 0, 26, 2, "EtherStatsJabbers"				},
+	{ 0, 28, 2, "IfInUcastPkts"				},
+	{ 0, 30, 2, "EtherStatsMulticastPkts"			},
+	{ 0, 32, 2, "EtherStatsBroadcastPkts"			},
+	{ 0, 34, 2, "EtherStatsDropEvents"			},
+	{ 0, 36, 2, "Dot3StatsFCSErrors"			},
+	{ 0, 38, 2, "Dot3StatsSymbolErrors"			},
+	{ 0, 40, 2, "Dot3InPauseFrames"				},
+	{ 0, 42, 2, "Dot3ControlInUnknownOpcodes"		},
+	{ 0, 44, 4, "IfOutOctets"				},
+	{ 0, 48, 2, "Dot3StatsSingleCollisionFrames"		},
+	{ 0, 50, 2, "Dot3StatMultipleCollisionFrames"		},
+	{ 0, 52, 2, "Dot3sDeferredTransmissions"		},
+	{ 0, 54, 2, "Dot3StatsLateCollisions"			},
+	{ 0, 56, 2, "EtherStatsCollisions"			},
+	{ 0, 58, 2, "Dot3StatsExcessiveCollisions"		},
+	{ 0, 60, 2, "Dot3OutPauseFrames"			},
+	{ 0, 62, 2, "Dot1dBasePortDelayExceededDiscards"	},
+
+	/*
+	 * The following counters are accessible at a different
+	 * base address.
+	 */
+	{ 1,  0, 2, "Dot1dTpPortInDiscards"			},
+	{ 1,  2, 2, "IfOutUcastPkts"				},
+	{ 1,  4, 2, "IfOutMulticastPkts"			},
+	{ 1,  6, 2, "IfOutBroadcastPkts"			},
+};
+
+static int rtl8366s_get_mib_counter(struct realtek_smi *smi,
+				    int port,
+				    struct rtl8366_mib_counter *mib,
+				    u64 *mibvalue)
+{
+	u32 addr, val;
+	int ret;
+	int i;
+
+	switch (mib->base) {
+	case 0:
+		addr = RTL8366S_MIB_COUNTER_BASE +
+		       RTL8366S_MIB_COUNTER_PORT_OFFSET * port;
+		break;
+	case 1:
+		addr = RTL8366S_MIB_COUNTER_BASE2 +
+		       RTL8366S_MIB_COUNTER_PORT_OFFSET2 * port;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	addr += mib->offset;
+
+	/*
+	 * Writing access counter address first
+	 * then ASIC will prepare 64bits counter wait for being retrived
+	 */
+	ret = regmap_write(smi->map, addr, 0);
+	if (ret)
+		return ret;
+
+	/* read MIB control register */
+	ret =  regmap_read(smi->map, RTL8366S_MIB_CTRL_REG, &val);
+	if (ret)
+		return -EIO;
+
+	if (val & RTL8366S_MIB_CTRL_BUSY_MASK)
+		return -EBUSY;
+
+	if (val & RTL8366S_MIB_CTRL_RESET_MASK)
+		return -EIO;
+
+	/* Read each individual MIB 16 bits at the time */
+	*mibvalue = 0;
+	for (i = mib->length; i > 0; i--) {
+		ret = regmap_read(smi->map, addr + (i - 1), &val);
+		if (ret)
+			return ret;
+		*mibvalue = (*mibvalue << 16) | (val & 0xFFFF);
+	}
+	return 0;
+}
+
+static int rtl8366s_phy_read(struct realtek_smi *smi, int phy, int regnum)
+{
+	u32 val;
+	u32 reg;
+	int ret;
+
+	if (phy > RTL8366S_PHY_NO_MAX)
+		return -EINVAL;
+
+	ret = regmap_write(smi->map, RTL8366S_PHY_ACCESS_CTRL_REG,
+			   RTL8366S_PHY_CTRL_READ);
+	if (ret)
+		return ret;
+
+	reg = 0x8000 | (1 << (phy + RTL8366S_PHY_NO_OFFSET)) | regnum;
+
+	ret = regmap_write(smi->map, reg, 0);
+	if (ret)
+		return ret;
+
+	ret = regmap_read(smi->map, RTL8366S_PHY_ACCESS_DATA_REG, &val);
+	if (ret)
+		return ret;
+
+	return val;
+}
+
+static int rtl8366s_phy_write(struct realtek_smi *smi, int phy, int regnum,
+			       u16 val)
+{
+	u32 reg;
+	int ret;
+
+	if (phy > RTL8366S_PHY_NO_MAX)
+		return -EINVAL;
+
+	ret = regmap_write(smi->map, RTL8366S_PHY_ACCESS_CTRL_REG,
+			   RTL8366S_PHY_CTRL_WRITE);
+	if (ret)
+		return ret;
+
+	reg = 0x8000 | (1 << (phy + RTL8366S_PHY_NO_OFFSET)) | regnum;
+
+	ret = regmap_write(smi->map, reg, val);
+	if (ret)
+		return ret;
+
+	return 0;
+}
+
+static int rtl8366s_reset_chip(struct realtek_smi *smi)
+{
+	int timeout = 10;
+	u32 val;
+	int ret;
+
+	realtek_smi_write_reg_noack(smi, RTL8366S_RESET_CTRL_REG,
+				    RTL8366S_CHIP_CTRL_RESET_HW);
+	do {
+		usleep_range(20000, 25000);
+		ret = regmap_read(smi->map, RTL8366S_RESET_CTRL_REG, &val);
+		if (ret)
+			return ret;
+
+		if (!(val & RTL8366S_CHIP_CTRL_RESET_HW))
+			break;
+	} while (--timeout);
+
+	if (!timeout) {
+		dev_err(smi->dev, "timeout waiting for the switch to reset\n");
+		return -EIO;
+	}
+
+	return 0;
+}
+
+static enum dsa_tag_protocol rtl8366s_get_tag_protocol(struct dsa_switch *ds,
+						       int port,
+						       enum dsa_tag_protocol mp)
+{
+	return DSA_TAG_PROTO_RTL8366S;
+}
+
+static void
+rtl8366s_mac_link_up(struct dsa_switch *ds, int port, unsigned int mode,
+		     phy_interface_t interface, struct phy_device *phydev,
+		     int speed, int duplex, bool tx_pause, bool rx_pause)
+{
+	struct realtek_smi *smi = ds->priv;
+	int ret;
+
+	if (port != RTL8366S_PORT_NUM_CPU)
+		return;
+
+	/* Force the fixed CPU port into 1Gbit mode, no autonegotiation */
+	ret = regmap_update_bits(smi->map, RTL8366S_MAC_FORCE_CTRL1_REG,
+				 BIT(port), BIT(port));
+	if (ret) {
+		dev_err(smi->dev, "failed to force 1Gbit on CPU port\n");
+		return;
+	}
+
+	ret = regmap_update_bits(smi->map, RTL8366S_PAACR2,
+				 0xFF00U,
+				 RTL8366S_PAACR_CPU_PORT << 8);
+	if (ret) {
+		dev_err(smi->dev, "failed to set PAACR on CPU port\n");
+		return;
+	}
+
+	/* Enable the CPU port */
+	ret = regmap_update_bits(smi->map, RTL8366S_PECR, BIT(port),
+				 0);
+	if (ret) {
+		dev_err(smi->dev, "failed to enable the CPU port\n");
+		return;
+	}
+}
+
+static void
+rtl8366s_mac_link_down(struct dsa_switch *ds, int port, unsigned int mode,
+		       phy_interface_t interface)
+{
+	struct realtek_smi *smi = ds->priv;
+	int ret;
+
+	if (port != RTL8366S_PORT_NUM_CPU)
+		return;
+
+	/* Disable the CPU port */
+	ret = regmap_update_bits(smi->map, RTL8366S_PECR, BIT(port),
+				 BIT(port));
+	if (ret) {
+		dev_err(smi->dev, "failed to disable the CPU port\n");
+		return;
+	}
+}
+
+static int rtl8366s_setup(struct dsa_switch *ds)
+{
+	struct realtek_smi *smi = ds->priv;
+	int ret;
+
+	/* Reset chip */
+	ret = rtl8366s_reset_chip(smi);
+	if (ret)
+		return ret;
+
+	/* Set up the "green ethernet" feature */
+	ret = regmap_update_bits(smi->map, RTL8366S_GREEN_ETHERNET_CTRL_REG,
+				 RTL8366S_GREEN_ETHERNET_CTRL_MASK,
+				 RTL8366S_GREEN_ETHERNET_TX |
+				 RTL8366S_GREEN_ETHERNET_RX);
+	if (ret)
+		return ret;
+
+	/* Enable CPU port with custom tag 8899 */
+	ret = regmap_write(smi->map, RTL8366S_CPU_CTRL_REG,
+			   RTL8366S_PORT_CPU);
+	if (ret)
+		return ret;
+
+	/* Make sure we default-enable the fixed CPU port */
+	ret = regmap_update_bits(smi->map, RTL8366S_PECR,
+				 RTL8366S_PORT_CPU, 0);
+	if (ret)
+		return ret;
+
+	/* Enable learning for all ports */
+	ret = regmap_write(smi->map, RTL8366S_SSCR0, 0);
+	if (ret)
+		return ret;
+
+	/* Enable auto ageing for all ports */
+	ret = regmap_write(smi->map, RTL8366S_SSCR1, 0);
+	if (ret)
+		return ret;
+
+	/* Don't drop packets whose DA has not been learned */
+	ret = regmap_update_bits(smi->map, RTL8366S_SSCR2,
+				 RTL8366S_SSCR2_DROP_UNKNOWN_DA, 0);
+	if (ret)
+		return ret;
+
+	/* Connect Port 4 PHY to RGMII
+	 * TODO: Make it configurable in DTS
+	 */
+	ret = regmap_update_bits(smi->map, RTL8366S_PMC0,
+				 RTL8366S_PMC0_P4_IOMODE_MASK,
+				 RTL8366S_PMC0_P4_IOMODE_PHY_TO_RGMII);
+	if (ret)
+		return ret;
+
+	/* Set up port-based VLAN */
+	ret = rtl8366_init_vlan(smi);
+	if (ret)
+		return ret;
+
+	/* Keep original tagged/untagged on egress */
+	ret = regmap_update_bits(smi->map,
+				 RTL8366S_EGRESS_KEEP_FORMAT_REG,
+				 RTL8366S_EGRESS_KEEP_FORMAT_MASK,
+				 RTL8366S_EGRESS_KEEP_FORMAT_ALL);
+	if (ret)
+		return ret;
+
+	/* Enable Link Aggregation "Dump" mode. The switch will
+	 * automatically set hash value mapping to LAG ports.
+	 */
+	ret = regmap_write(smi->map, RTL8366S_LAGCR,
+			   RTL8366S_LAGCR_MODE_DUMP);
+	if (ret)
+		return ret;
+
+	ret = realtek_smi_setup_mdio(smi);
+	if (ret) {
+		dev_info(smi->dev, "could not set up MDIO bus\n");
+		return -ENODEV;
+	}
+
+	ds->mtu_enforcement_ingress = true;
+
+	return 0;
+}
+
+static int rtl8366s_get_vlan_4k(struct realtek_smi *smi, u32 vid,
+				struct rtl8366_vlan_4k *vlan4k)
+{
+	u32 data[2];
+	int ret;
+	int i;
+
+	memset(vlan4k, 0, sizeof(struct rtl8366_vlan_4k));
+
+	if (vid >= RTL8366S_NUM_VIDS)
+		return -EINVAL;
+
+	/* write VID */
+	ret = regmap_write(smi->map, RTL8366S_VLAN_TABLE_WRITE_BASE,
+			   vid & RTL8366S_VLAN_VID_MASK);
+	if (ret)
+		return ret;
+
+	/* write table access control word */
+	ret = regmap_write(smi->map, RTL8366S_TABLE_ACCESS_CTRL_REG,
+			   RTL8366S_TABLE_VLAN_READ_CTRL);
+	if (ret)
+		return ret;
+
+	for (i = 0; i < 2; i++) {
+		ret = regmap_read(smi->map,
+				  RTL8366S_VLAN_TABLE_READ_BASE + i,
+				  &data[i]);
+		if (ret)
+			return ret;
+	}
+
+	vlan4k->vid = vid;
+	vlan4k->untag = (data[1] >> RTL8366S_VLAN_UNTAG_SHIFT) &
+			RTL8366S_VLAN_UNTAG_MASK;
+	vlan4k->member = data[1] & RTL8366S_VLAN_MEMBER_MASK;
+	vlan4k->fid = (data[1] >> RTL8366S_VLAN_FID_SHIFT) &
+			RTL8366S_VLAN_FID_MASK;
+
+	return 0;
+}
+
+static int rtl8366s_set_vlan_4k(struct realtek_smi *smi,
+				const struct rtl8366_vlan_4k *vlan4k)
+{
+	u32 data[2];
+	int ret;
+	int i;
+
+	if (vlan4k->vid >= RTL8366S_NUM_VIDS ||
+	    vlan4k->member > RTL8366S_VLAN_MEMBER_MASK ||
+	    vlan4k->untag > RTL8366S_VLAN_UNTAG_MASK ||
+	    vlan4k->fid > RTL8366S_FIDMAX)
+		return -EINVAL;
+
+	data[0] = vlan4k->vid & RTL8366S_VLAN_VID_MASK;
+	data[1] = (vlan4k->member & RTL8366S_VLAN_MEMBER_MASK) |
+		  ((vlan4k->untag & RTL8366S_VLAN_UNTAG_MASK) <<
+			RTL8366S_VLAN_UNTAG_SHIFT) |
+		  ((vlan4k->fid & RTL8366S_VLAN_FID_MASK) <<
+			RTL8366S_VLAN_FID_SHIFT);
+
+	for (i = 0; i < 2; i++) {
+		ret = regmap_write(smi->map,
+					    RTL8366S_VLAN_TABLE_WRITE_BASE + i,
+					    data[i]);
+		if (ret)
+			return ret;
+	}
+
+	/* write table access control word */
+	ret = regmap_write(smi->map, RTL8366S_TABLE_ACCESS_CTRL_REG,
+				    RTL8366S_TABLE_VLAN_WRITE_CTRL);
+
+	return ret;
+}
+
+static int rtl8366s_get_vlan_mc(struct realtek_smi *smi, u32 index,
+				struct rtl8366_vlan_mc *vlanmc)
+{
+	u32 data[2];
+	int ret;
+	int i;
+
+	memset(vlanmc, 0, sizeof(struct rtl8366_vlan_mc));
+
+	if (index >= RTL8366S_NUM_VLANS)
+		return -EINVAL;
+
+	for (i = 0; i < 2; i++) {
+		ret = regmap_read(smi->map,
+				  RTL8366S_VLAN_MC_BASE(index) + i,
+				  &data[i]);
+		if (ret)
+			return ret;
+	}
+
+	vlanmc->vid = data[0] & RTL8366S_VLAN_VID_MASK;
+	vlanmc->priority = (data[0] >> RTL8366S_VLAN_PRIORITY_SHIFT) &
+			   RTL8366S_VLAN_PRIORITY_MASK;
+	vlanmc->untag = (data[1] >> RTL8366S_VLAN_UNTAG_SHIFT) &
+			RTL8366S_VLAN_UNTAG_MASK;
+	vlanmc->member = data[1] & RTL8366S_VLAN_MEMBER_MASK;
+	vlanmc->fid = (data[1] >> RTL8366S_VLAN_FID_SHIFT) &
+		      RTL8366S_VLAN_FID_MASK;
+
+	return 0;
+}
+
+static int rtl8366s_set_vlan_mc(struct realtek_smi *smi, u32 index,
+				const struct rtl8366_vlan_mc *vlanmc)
+{
+	u32 data[2];
+	int ret;
+	int i;
+
+	if (index >= RTL8366S_NUM_VLANS ||
+	    vlanmc->vid >= RTL8366S_NUM_VIDS ||
+	    vlanmc->priority > RTL8366S_PRIORITYMAX ||
+	    vlanmc->member > RTL8366S_VLAN_MEMBER_MASK ||
+	    vlanmc->untag > RTL8366S_VLAN_UNTAG_MASK ||
+	    vlanmc->fid > RTL8366S_FIDMAX)
+		return -EINVAL;
+
+	data[0] = (vlanmc->vid & RTL8366S_VLAN_VID_MASK) |
+		  ((vlanmc->priority & RTL8366S_VLAN_PRIORITY_MASK) <<
+			RTL8366S_VLAN_PRIORITY_SHIFT);
+	data[1] = (vlanmc->member & RTL8366S_VLAN_MEMBER_MASK) |
+		  ((vlanmc->untag & RTL8366S_VLAN_UNTAG_MASK) <<
+			RTL8366S_VLAN_UNTAG_SHIFT) |
+		  ((vlanmc->fid & RTL8366S_VLAN_FID_MASK) <<
+			RTL8366S_VLAN_FID_SHIFT);
+
+	for (i = 0; i < 2; i++) {
+		ret = regmap_write(smi->map,
+				   RTL8366S_VLAN_MC_BASE(index) + i,
+				   data[i]);
+		if (ret)
+			return ret;
+	}
+
+	return 0;
+}
+
+static int rtl8366s_get_mc_index(struct realtek_smi *smi, int port, int *val)
+{
+	u32 data;
+	int ret;
+
+	if (port >= RTL8366S_NUM_PORTS)
+		return -EINVAL;
+
+	ret = regmap_read(smi->map, RTL8366S_PORT_VLAN_CTRL_REG(port),
+				   &data);
+	if (ret)
+		return ret;
+
+	*val = (data >> RTL8366S_PORT_VLAN_CTRL_SHIFT(port)) &
+	       RTL8366S_PORT_VLAN_CTRL_MASK;
+
+	return 0;
+}
+
+static int rtl8366s_set_mc_index(struct realtek_smi *smi, int port, int index)
+{
+	if (port >= RTL8366S_NUM_PORTS || index >= RTL8366S_NUM_VLANS)
+		return -EINVAL;
+
+	return regmap_update_bits(smi->map, RTL8366S_PORT_VLAN_CTRL_REG(port),
+				RTL8366S_PORT_VLAN_CTRL_MASK <<
+					RTL8366S_PORT_VLAN_CTRL_SHIFT(port),
+				(index & RTL8366S_PORT_VLAN_CTRL_MASK) <<
+					RTL8366S_PORT_VLAN_CTRL_SHIFT(port));
+}
+
+static int rtl8366s_enable_vlan(struct realtek_smi *smi, bool enable)
+{
+	return regmap_update_bits(smi->map, RTL8366S_SGCR, RTL8366S_SGCR_EN_VLAN,
+				  enable ? RTL8366S_SGCR_EN_VLAN : 0);
+}
+
+static int rtl8366s_enable_vlan4k(struct realtek_smi *smi, bool enable)
+{
+	return regmap_update_bits(smi->map, RTL8366S_VLAN_TB_CTRL_REG,
+				  1, enable);
+}
+
+static bool rtl8366s_is_vlan_valid(struct realtek_smi *smi, unsigned int vlan)
+{
+	unsigned int max = RTL8366S_NUM_VLANS;
+
+	if (smi->vlan4k_enabled)
+		max = RTL8366S_NUM_VIDS - 1;
+
+	return vlan && vlan < max;
+}
+
+static int
+rtl8366s_port_enable(struct dsa_switch *ds, int port,
+		     struct phy_device *phy)
+{
+	struct realtek_smi *smi = ds->priv;
+
+	return regmap_update_bits(smi->map, RTL8366S_PECR, BIT(port), 0);
+}
+
+static void
+rtl8366s_port_disable(struct dsa_switch *ds, int port)
+{
+	struct realtek_smi *smi = ds->priv;
+
+	regmap_update_bits(smi->map, RTL8366S_PECR, BIT(port), BIT(port));
+}
+
+static int
+rtl8366s_port_bridge_join(struct dsa_switch *ds, int port,
+			  struct net_device *bridge)
+{
+	struct realtek_smi *smi = ds->priv;
+	unsigned int port_mask = 0;
+	int i;
+
+	for (i = 0; i < RTL8366S_NUM_PORTS; i++) {
+		unsigned int mask;
+		int ret;
+
+		if (dsa_to_port(ds, i)->bridge_dev != bridge)
+			continue;
+
+		/* Add this port to the portvlan mask of the other ports
+		 * in the bridge
+		 */
+		mask = BIT(port);
+		mask |= mask << RTL8366S_VLAN_UNTAG_SHIFT;
+		ret = regmap_update_bits(smi->map,
+					 RTL8366S_VLAN_MC_BASE(i) + 1,
+					 mask, mask);
+		if (ret)
+			return ret;
+
+		port_mask |= BIT(i);
+	}
+
+	/* Add all other ports to this ports portvlan mask */
+	port_mask |= port_mask << RTL8366S_VLAN_UNTAG_SHIFT;
+	return regmap_update_bits(smi->map, RTL8366S_VLAN_MC_BASE(port) + 1,
+				  port_mask, port_mask);
+}
+
+static void
+rtl8366s_port_bridge_leave(struct dsa_switch *ds, int port,
+			   struct net_device *bridge)
+{
+	struct realtek_smi *smi = ds->priv;
+	unsigned int port_mask;
+	int i;
+
+	for (i = 0; i < RTL8366S_NUM_PORTS; i++) {
+		unsigned int mask;
+
+		if (dsa_to_port(ds, i)->bridge_dev != bridge)
+			continue;
+
+		/* Remove this port from the portvlan mask of the other
+		 * ports in the bridge
+		 */
+		mask = BIT(port);
+		mask |= mask << RTL8366S_VLAN_UNTAG_SHIFT;
+		regmap_update_bits(smi->map,
+				   RTL8366S_VLAN_MC_BASE(i) + 1,
+				   mask, 0);
+	}
+
+	/* Set the cpu port to be the only one else in the portvlan mask
+	 * of this port
+	 */
+	port_mask = BIT(port) | BIT(RTL8366S_PORT_NUM_CPU);
+	port_mask |= port_mask << RTL8366S_VLAN_UNTAG_SHIFT;
+	regmap_write(smi->map, RTL8366S_VLAN_MC_BASE(port) + 1,
+		     port_mask);
+}
+
+static void
+rtl8366s_port_stp_state_set(struct dsa_switch *ds, int port, u8 state)
+{
+	struct realtek_smi *smi = ds->priv;
+	enum RTL8366S_STP_STATE val;
+
+	switch (state) {
+	case BR_STATE_DISABLED:
+		val = RTL8366S_STP_DISABLED;
+		break;
+	case BR_STATE_BLOCKING:
+	case BR_STATE_LISTENING:
+		val = RTL8366S_STP_BLOCKING;
+		break;
+	case BR_STATE_LEARNING:
+		val = RTL8366S_STP_LEARNING;
+		break;
+	case BR_STATE_FORWARDING:
+	default:
+		val = RTL8366S_STP_FORWARDING;
+		break;
+	}
+
+	/* The HW supports 8 MSTIs but we only use 0 */
+	regmap_update_bits(smi->map, RTL8366S_STP_REG(port),
+			   RTL8366S_STP_MASK, val);
+}
+
+static int rtl8366s_port_vlan_filtering(struct dsa_switch *ds, int port,
+					bool vlan_filtering)
+{
+	struct realtek_smi *smi = ds->priv;
+	unsigned int val;
+	int ret;
+
+	/* Enable/Disable VLAN ingress filtering */
+	val = BIT(port);
+	ret = regmap_update_bits(smi->map, RTL8366S_VLAN_MEMBERINGRESS_REG,
+				 val, vlan_filtering ? val : 0);
+	if (ret)
+		return ret;
+
+	/* Disable/Enable keep original tagged/untagged */
+	val = FIELD_PREP(RTL8366S_EGRESS_KEEP_FORMAT_MASK, val);
+	ret = regmap_update_bits(smi->map, RTL8366S_EGRESS_KEEP_FORMAT_REG,
+				 val, vlan_filtering ? 0 : val);
+	if (ret)
+		return ret;
+
+	return rtl8366_vlan_filtering(ds, port, vlan_filtering);
+}
+
+static int rtl8366s_port_mirror_add(struct dsa_switch *ds, int port,
+				    struct dsa_mall_mirror_tc_entry *mirror,
+				    bool ingress)
+{
+	struct realtek_smi *smi = ds->priv;
+	unsigned int val, dir;
+	int ret;
+
+	/* The source and the monitor port cannot be the same */
+	if (port == mirror->to_local_port)
+		return -EOPNOTSUPP;
+
+	ret = regmap_read(smi->map, RTL8366S_PMCR, &val);
+	if (ret)
+		return ret;
+
+	dir = ingress ? RTL8366S_PMCR_MIRROR_RX : RTL8366S_PMCR_MIRROR_TX;
+	/* RTL8366S only supports one port mirror set */
+	if (val & dir)
+	    return -EEXIST;
+
+	/* If the other direction is active, allow setting up both
+	 * directions for the same source and monitor ports
+	 */
+	if (val & (RTL8366S_PMCR_MIRROR_RX | RTL8366S_PMCR_MIRROR_TX)) {
+		int source, monitor;
+
+		source = FIELD_GET(RTL8366S_PMCR_SOURCE_MASK, val);
+		monitor = FIELD_GET(RTL8366S_PMCR_MINITOR_MASK, val);
+
+		if (source != port || monitor != mirror->to_local_port)
+			return -EEXIST;
+	} else {
+		val &= ~RTL8366S_PMCR_SOURCE_MASK;
+		val |= FIELD_PREP(RTL8366S_PMCR_SOURCE_MASK, port);
+		val &= ~RTL8366S_PMCR_MINITOR_MASK;
+		val |= FIELD_PREP(RTL8366S_PMCR_MINITOR_MASK,
+		       mirror->to_local_port);
+	}
+
+	val |= dir;
+
+	return regmap_write(smi->map, RTL8366S_PMCR, val);
+}
+
+static void rtl8366s_port_mirror_del(struct dsa_switch *ds, int port,
+				     struct dsa_mall_mirror_tc_entry *mirror)
+{
+	struct realtek_smi *smi = ds->priv;
+	unsigned int dir;
+
+	dir = mirror->ingress ? RTL8366S_PMCR_MIRROR_RX : RTL8366S_PMCR_MIRROR_TX;
+	regmap_update_bits(smi->map, RTL8366S_PMCR, dir, 0);
+}
+
+static int rtl8366s_port_change_mtu(struct dsa_switch *ds, int port, int new_mtu)
+{
+	struct realtek_smi *smi = ds->priv;
+	u32 len;
+
+	/* 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
+	 * max length register, only allowing CPU port here is enough.
+	 */
+	if (port != RTL8366S_PORT_NUM_CPU)
+		return 0;
+
+	/* Includes Ethernet header and FCS length.
+	 *
+	 * Note that the CPU tag does not count towards its length, the
+	 * same reason why the frame must be padded _before_ inserting
+	 * the CPU tag on xmit.
+	 */
+	new_mtu += ETH_HLEN + ETH_FCS_LEN;
+	if (new_mtu <= 1522)
+		len = RTL8366S_SGCR_MAX_LENGTH_1522;
+	else if (new_mtu <= 1536)
+		len = RTL8366S_SGCR_MAX_LENGTH_1536;
+	else if (new_mtu <= 1552)
+		len = RTL8366S_SGCR_MAX_LENGTH_1552;
+	else
+		len = RTL8366S_SGCR_MAX_LENGTH_16000;
+
+	return regmap_update_bits(smi->map, RTL8366S_SGCR,
+				  RTL8366S_SGCR_MAX_LENGTH_MASK,
+				  len);
+}
+
+static int rtl8366s_port_max_mtu(struct dsa_switch *ds, int port)
+{
+	return 16000 - ETH_HLEN - ETH_FCS_LEN;
+}
+
+static int rtl8366s_port_lag_change(struct dsa_switch *ds, int port)
+{
+	const struct dsa_port *dp = dsa_to_port(ds, port);
+	struct realtek_smi *smi = ds->priv;
+	unsigned int val;
+
+	val = FIELD_PREP(RTL8366S_LAGCR_PORTMAP_MASK, BIT(port));
+
+	return regmap_update_bits(smi->map, RTL8366S_LAGCR, val,
+				  dp->lag_tx_enabled ? val : 0);
+}
+
+static int rtl8366s_port_lag_join(struct dsa_switch *ds, int port,
+				  struct net_device *lag,
+				  struct netdev_lag_upper_info *info)
+{
+	struct realtek_smi *smi = ds->priv;
+	const struct dsa_port *dp;
+	unsigned int val;
+	int count = 0;
+
+	/* Only supports hash type */
+	if (info->tx_type != NETDEV_LAG_TX_TYPE_HASH)
+		return -EOPNOTSUPP;
+
+	list_for_each_entry(dp, dp->dst->ports, list) {
+		if (dp->lag_dev == lag)
+			count++;
+		/* Only supports 1 LAG set */
+		else if (dp->lag_dev)
+			return -EBUSY;
+	}
+
+	/* Only supports maximum LAG member of 4 */
+	if (count >= 4)
+		return -ENOSPC;
+
+	val = FIELD_PREP(RTL8366S_LAGCR_PORTMAP_MASK, BIT(port));
+
+	return regmap_update_bits(smi->map, RTL8366S_LAGCR, val, val);
+}
+
+static int rtl8366s_port_lag_leave(struct dsa_switch *ds, int port,
+				   struct net_device *lag)
+{
+	struct realtek_smi *smi = ds->priv;
+	unsigned int val;
+
+	val = FIELD_PREP(RTL8366S_LAGCR_PORTMAP_MASK, BIT(port));
+
+	return regmap_update_bits(smi->map, RTL8366S_LAGCR, val, 0);
+}
+
+static int rtl8366s_detect(struct realtek_smi *smi)
+{
+	struct device *dev = smi->dev;
+	unsigned int chip_ver;
+	unsigned int chip_id;
+	int ret;
+
+	ret = regmap_read(smi->map, RTL8366S_CHIP_ID_REG, &chip_id);
+	if (ret) {
+		dev_err(dev, "unable to read chip id (%d)\n", ret);
+		return ret;
+	}
+
+	switch (chip_id) {
+	case RTL8366S_CHIP_ID_8366:
+		break;
+	default:
+		dev_err(dev, "unknown chip id (%04x)\n", chip_id);
+		return -ENODEV;
+	}
+
+	ret = regmap_read(smi->map, RTL8366S_CHIP_VERSION_CTRL_REG,
+			  &chip_ver);
+	if (ret) {
+		dev_err(dev, "unable to read chip version (%d)\n", ret);
+		return ret;
+	}
+
+	chip_ver &= RTL8366S_CHIP_VERSION_MASK;
+	dev_info(dev, "RTL%04x ver. %u chip found\n", chip_id, chip_ver);
+
+	smi->cpu_port = RTL8366S_PORT_NUM_CPU;
+	smi->num_ports = RTL8366S_NUM_PORTS;
+	smi->num_vlan_mc = RTL8366S_NUM_VLANS;
+	smi->mib_counters = rtl8366s_mib_counters;
+	smi->num_mib_counters = ARRAY_SIZE(rtl8366s_mib_counters);
+
+	return 0;
+}
+
+static const struct dsa_switch_ops rtl8366s_switch_ops = {
+	.get_tag_protocol = rtl8366s_get_tag_protocol,
+	.setup = rtl8366s_setup,
+	.phylink_mac_link_down = rtl8366s_mac_link_down,
+	.phylink_mac_link_up = rtl8366s_mac_link_up,
+	.get_strings = rtl8366_get_strings,
+	.get_ethtool_stats = rtl8366_get_ethtool_stats,
+	.get_sset_count = rtl8366_get_sset_count,
+	.port_enable = rtl8366s_port_enable,
+	.port_disable = rtl8366s_port_disable,
+	.port_bridge_join = rtl8366s_port_bridge_join,
+	.port_bridge_leave = rtl8366s_port_bridge_leave,
+	.port_stp_state_set = rtl8366s_port_stp_state_set,
+	.port_vlan_filtering = rtl8366s_port_vlan_filtering,
+	.port_vlan_add = rtl8366_vlan_add,
+	.port_vlan_del = rtl8366_vlan_del,
+	.port_mirror_add = rtl8366s_port_mirror_add,
+	.port_mirror_del = rtl8366s_port_mirror_del,
+	.port_change_mtu = rtl8366s_port_change_mtu,
+	.port_max_mtu = rtl8366s_port_max_mtu,
+	.port_lag_change = rtl8366s_port_lag_change,
+	.port_lag_join = rtl8366s_port_lag_join,
+	.port_lag_leave = rtl8366s_port_lag_leave,
+};
+
+static const struct realtek_smi_ops rtl8366s_smi_ops = {
+	.detect		= rtl8366s_detect,
+	.get_mib_counter = rtl8366s_get_mib_counter,
+	.get_vlan_mc	= rtl8366s_get_vlan_mc,
+	.set_vlan_mc	= rtl8366s_set_vlan_mc,
+	.get_vlan_4k	= rtl8366s_get_vlan_4k,
+	.set_vlan_4k	= rtl8366s_set_vlan_4k,
+	.get_mc_index	= rtl8366s_get_mc_index,
+	.set_mc_index	= rtl8366s_set_mc_index,
+	.is_vlan_valid	= rtl8366s_is_vlan_valid,
+	.enable_vlan	= rtl8366s_enable_vlan,
+	.enable_vlan4k	= rtl8366s_enable_vlan4k,
+	.phy_read	= rtl8366s_phy_read,
+	.phy_write	= rtl8366s_phy_write,
+};
+
+static const struct realtek_smi_variant rtl8366s_variant = {
+	.ds_ops = &rtl8366s_switch_ops,
+	.ops = &rtl8366s_smi_ops,
+	.clk_delay = 10,
+	.cmd_read = 0xa9,
+	.cmd_write = 0xa8,
+};
+EXPORT_SYMBOL_GPL(rtl8366s_variant);