lists.openwall.net   lists  /  announce  owl-users  owl-dev  john-users  john-dev  passwdqc-users  yescrypt  popa3d-users  /  oss-security  kernel-hardening  musl  sabotage  tlsify  passwords  /  crypt-dev  xvendor  /  Bugtraq  Full-Disclosure  linux-kernel  linux-netdev  linux-ext4  linux-hardening  linux-cve-announce  PHC 
Open Source and information security mailing list archives
 
Hash Suite: Windows password security audit tool. GUI, reports in PDF.
[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Date:   Mon, 31 Jan 2022 18:50:44 +0000
From:   Vladimir Oltean <vladimir.oltean@....com>
To:     Colin Foster <colin.foster@...advantage.com>
CC:     "linux-arm-kernel@...ts.infradead.org" 
        <linux-arm-kernel@...ts.infradead.org>,
        "linux-gpio@...r.kernel.org" <linux-gpio@...r.kernel.org>,
        "netdev@...r.kernel.org" <netdev@...r.kernel.org>,
        "linux-kernel@...r.kernel.org" <linux-kernel@...r.kernel.org>,
        Steen Hegelund <Steen.Hegelund@...rochip.com>,
        Lars Povlsen <lars.povlsen@...rochip.com>,
        Linus Walleij <linus.walleij@...aro.org>,
        Russell King <linux@...linux.org.uk>,
        Heiner Kallweit <hkallweit1@...il.com>,
        Jakub Kicinski <kuba@...nel.org>,
        "David S. Miller" <davem@...emloft.net>,
        Florian Fainelli <f.fainelli@...il.com>,
        Vivien Didelot <vivien.didelot@...il.com>,
        Andrew Lunn <andrew@...n.ch>,
        "UNGLinuxDriver@...rochip.com" <UNGLinuxDriver@...rochip.com>,
        Alexandre Belloni <alexandre.belloni@...tlin.com>,
        Claudiu Manoil <claudiu.manoil@....com>,
        Lee Jones <lee.jones@...aro.org>,
        "katie.morris@...advantage.com" <katie.morris@...advantage.com>
Subject: Re: [RFC v6 net-next 9/9] net: dsa: ocelot: add external ocelot
 switch control

On Sat, Jan 29, 2022 at 02:02:21PM -0800, Colin Foster wrote:
> Add control of an external VSC7512 chip by way of the ocelot-mfd interface.
> 
> Currently the four copper phy ports are fully functional. Communication to
> external phys is also functional, but the SGMII / QSGMII interfaces are
> currently non-functional.
> 
> Signed-off-by: Colin Foster <colin.foster@...advantage.com>
> ---
>  drivers/mfd/ocelot-core.c           |   4 +
>  drivers/net/dsa/ocelot/Kconfig      |  14 +
>  drivers/net/dsa/ocelot/Makefile     |   5 +
>  drivers/net/dsa/ocelot/ocelot_ext.c | 681 ++++++++++++++++++++++++++++
>  include/soc/mscc/ocelot.h           |   2 +
>  5 files changed, 706 insertions(+)
>  create mode 100644 drivers/net/dsa/ocelot/ocelot_ext.c
> 
> diff --git a/drivers/mfd/ocelot-core.c b/drivers/mfd/ocelot-core.c
> index 590489481b8c..17a77d618e92 100644
> --- a/drivers/mfd/ocelot-core.c
> +++ b/drivers/mfd/ocelot-core.c
> @@ -122,6 +122,10 @@ static const struct mfd_cell vsc7512_devs[] = {
>  		.num_resources = ARRAY_SIZE(vsc7512_miim1_resources),
>  		.resources = vsc7512_miim1_resources,
>  	},
> +	{
> +		.name = "ocelot-ext-switch",
> +		.of_compatible = "mscc,vsc7512-ext-switch",
> +	},
>  };
>  
>  int ocelot_core_init(struct ocelot_core *core)
> diff --git a/drivers/net/dsa/ocelot/Kconfig b/drivers/net/dsa/ocelot/Kconfig
> index 220b0b027b55..f40b2c7171ad 100644
> --- a/drivers/net/dsa/ocelot/Kconfig
> +++ b/drivers/net/dsa/ocelot/Kconfig
> @@ -1,4 +1,18 @@
>  # SPDX-License-Identifier: GPL-2.0-only
> +config NET_DSA_MSCC_OCELOT_EXT
> +	tristate "Ocelot External Ethernet switch support"
> +	depends on NET_DSA && SPI
> +	depends on NET_VENDOR_MICROSEMI
> +	select MDIO_MSCC_MIIM
> +	select MFD_OCELOT_CORE
> +	select MSCC_OCELOT_SWITCH_LIB
> +	select NET_DSA_TAG_OCELOT_8021Q
> +	select NET_DSA_TAG_OCELOT
> +	help
> +	  This driver supports the VSC7511, VSC7512, VSC7513 and VSC7514 chips
> +	  when controlled through SPI. It can be used with the Microsemi dev
> +	  boards and an external CPU or custom hardware.
> +
>  config NET_DSA_MSCC_FELIX
>  	tristate "Ocelot / Felix Ethernet switch support"
>  	depends on NET_DSA && PCI
> diff --git a/drivers/net/dsa/ocelot/Makefile b/drivers/net/dsa/ocelot/Makefile
> index f6dd131e7491..d7f3f5a4461c 100644
> --- a/drivers/net/dsa/ocelot/Makefile
> +++ b/drivers/net/dsa/ocelot/Makefile
> @@ -1,11 +1,16 @@
>  # SPDX-License-Identifier: GPL-2.0-only
>  obj-$(CONFIG_NET_DSA_MSCC_FELIX) += mscc_felix.o
> +obj-$(CONFIG_NET_DSA_MSCC_OCELOT_EXT) += mscc_ocelot_ext.o
>  obj-$(CONFIG_NET_DSA_MSCC_SEVILLE) += mscc_seville.o
>  
>  mscc_felix-objs := \
>  	felix.o \
>  	felix_vsc9959.o
>  
> +mscc_ocelot_ext-objs := \
> +	felix.o \
> +	ocelot_ext.o
> +
>  mscc_seville-objs := \
>  	felix.o \
>  	seville_vsc9953.o
> diff --git a/drivers/net/dsa/ocelot/ocelot_ext.c b/drivers/net/dsa/ocelot/ocelot_ext.c
> new file mode 100644
> index 000000000000..6fdff016673e
> --- /dev/null
> +++ b/drivers/net/dsa/ocelot/ocelot_ext.c

How about ocelot_vsc7512.c for a name?

> @@ -0,0 +1,681 @@
> +// SPDX-License-Identifier: (GPL-2.0 OR MIT)
> +/*
> + * Copyright 2021 Innovative Advantage Inc.
> + */
> +
> +#include <asm/byteorder.h>
> +#include <linux/iopoll.h>
> +#include <linux/kconfig.h>
> +#include <linux/mdio/mdio-mscc-miim.h>
> +#include <linux/of_mdio.h>
> +#include <linux/phylink.h>
> +#include <linux/platform_device.h>
> +#include <linux/regmap.h>
> +#include <soc/mscc/ocelot_ana.h>
> +#include <soc/mscc/ocelot_dev.h>
> +#include <soc/mscc/ocelot_qsys.h>
> +#include <soc/mscc/ocelot_vcap.h>
> +#include <soc/mscc/ocelot_ptp.h>
> +#include <soc/mscc/ocelot_sys.h>
> +#include <soc/mscc/ocelot.h>
> +#include <soc/mscc/vsc7514_regs.h>
> +#include "felix.h"
> +
> +#define VSC7512_NUM_PORTS		11
> +
> +#define OCELOT_SPI_PORT_MODE_INTERNAL	(1 << 0)
> +#define OCELOT_SPI_PORT_MODE_SGMII	(1 << 1)
> +#define OCELOT_SPI_PORT_MODE_QSGMII	(1 << 2)
> +
> +const u32 vsc7512_port_modes[VSC7512_NUM_PORTS] = {

missing "static"?

> +	OCELOT_SPI_PORT_MODE_INTERNAL,

Why is "_SPI_" in the name?

> +	OCELOT_SPI_PORT_MODE_INTERNAL,
> +	OCELOT_SPI_PORT_MODE_INTERNAL,
> +	OCELOT_SPI_PORT_MODE_INTERNAL,
> +	OCELOT_SPI_PORT_MODE_SGMII | OCELOT_SPI_PORT_MODE_QSGMII,
> +	OCELOT_SPI_PORT_MODE_SGMII | OCELOT_SPI_PORT_MODE_QSGMII,
> +	OCELOT_SPI_PORT_MODE_SGMII | OCELOT_SPI_PORT_MODE_QSGMII,
> +	OCELOT_SPI_PORT_MODE_SGMII | OCELOT_SPI_PORT_MODE_QSGMII,
> +	OCELOT_SPI_PORT_MODE_SGMII | OCELOT_SPI_PORT_MODE_QSGMII,
> +	OCELOT_SPI_PORT_MODE_SGMII,
> +	OCELOT_SPI_PORT_MODE_SGMII | OCELOT_SPI_PORT_MODE_QSGMII,
> +};
> +
> +struct ocelot_ext_data {
> +	struct felix felix;
> +	const u32 *port_modes;
> +};

I don't mind at all if you extend/generalize the pre-validation to all
Felix drivers and put "port_modes" inside struct felix_info.

felix_vsc9959.c would be:

#define VSC9959_PORT_MODE_SERDES	(OCELOT_PORT_MODE_SGMII | \
					 OCELOT_PORT_MODE_QSGMII | \
					 OCELOT_PORT_MODE_2500BASEX | \
					 OCELOT_PORT_MODE_USXGMII)

static const u32 vsc9959_port_modes[] = {
	VSC9959_PORT_MODE_SERDES,
	VSC9959_PORT_MODE_SERDES,
	VSC9959_PORT_MODE_SERDES,
	VSC9959_PORT_MODE_SERDES,
	OCELOT_PORT_MODE_INTERNAL,
	OCELOT_PORT_MODE_INTERNAL,
};

seville_vsc9953.c would be:

#define VSC9953_PORT_MODE_SERDES	(OCELOT_PORT_MODE_SGMII | \
					 OCELOT_PORT_MODE_QSGMII)

static const u32 vsc9959_port_modes[] = {
	VSC9953_PORT_MODE_SERDES,
	VSC9953_PORT_MODE_SERDES,
	VSC9953_PORT_MODE_SERDES,
	VSC9953_PORT_MODE_SERDES,
	VSC9953_PORT_MODE_SERDES,
	VSC9953_PORT_MODE_SERDES,
	VSC9953_PORT_MODE_SERDES,
	VSC9953_PORT_MODE_SERDES,
	OCELOT_PORT_MODE_INTERNAL,
	OCELOT_PORT_MODE_INTERNAL,
};

and no more felix_info :: prevalidate_phy_mode function pointer.

> +
> +static const u32 vsc7512_gcb_regmap[] = {
> +	REG(GCB_SOFT_RST,			0x0008),
> +	REG(GCB_MIIM_MII_STATUS,		0x009c),
> +	REG(GCB_PHY_PHY_CFG,			0x00f0),
> +	REG(GCB_PHY_PHY_STAT,			0x00f4),
> +};
> +
> +static const u32 *vsc7512_regmap[TARGET_MAX] = {
> +	[ANA] = vsc7514_ana_regmap,
> +	[QS] = vsc7514_qs_regmap,
> +	[QSYS] = vsc7514_qsys_regmap,
> +	[REW] = vsc7514_rew_regmap,
> +	[SYS] = vsc7514_sys_regmap,
> +	[S0] = vsc7514_vcap_regmap,
> +	[S1] = vsc7514_vcap_regmap,
> +	[S2] = vsc7514_vcap_regmap,
> +	[PTP] = vsc7514_ptp_regmap,
> +	[GCB] = vsc7512_gcb_regmap,
> +	[DEV_GMII] = vsc7514_dev_gmii_regmap,
> +};
> +
> +#define VSC7512_BYTE_ORDER_LE 0x00000000
> +#define VSC7512_BYTE_ORDER_BE 0x81818181
> +#define VSC7512_BIT_ORDER_MSB 0x00000000
> +#define VSC7512_BIT_ORDER_LSB 0x42424242

Unused.

> +
> +static struct ocelot_ext_data *felix_to_ocelot_ext(struct felix *felix)
> +{
> +	return container_of(felix, struct ocelot_ext_data, felix);
> +}
> +
> +static struct ocelot_ext_data *ocelot_to_ocelot_ext(struct ocelot *ocelot)
> +{
> +	struct felix *felix = ocelot_to_felix(ocelot);
> +
> +	return felix_to_ocelot_ext(felix);
> +}

I wouldn't mind a "ds_to_felix()" helper, but as mentioned, it would be
good if you could use struct felix instead of introducing yet one more
container.

> +
> +static void ocelot_ext_reset_phys(struct ocelot *ocelot)
> +{
> +	ocelot_write(ocelot, 0, GCB_PHY_PHY_CFG);
> +	ocelot_write(ocelot, 0x1ff, GCB_PHY_PHY_CFG);
> +	mdelay(500);
> +}
> +
> +static int ocelot_ext_reset(struct ocelot *ocelot)
> +{
> +	struct felix *felix = ocelot_to_felix(ocelot);
> +	struct device *dev = ocelot->dev;
> +	struct device_node *mdio_node;
> +	int retries = 100;
> +	int err, val;
> +
> +	ocelot_ext_reset_phys(ocelot);
> +
> +	mdio_node = of_get_child_by_name(dev->of_node, "mdio");

 * Return: A node pointer if found, with refcount incremented, use
 * of_node_put() on it when done.

There's no "of_node_put()" below.

> +	if (!mdio_node)
> +		dev_info(ocelot->dev,
> +			 "mdio children not found in device tree\n");
> +
> +	err = of_mdiobus_register(felix->imdio, mdio_node);
> +	if (err) {
> +		dev_err(ocelot->dev, "error registering MDIO bus\n");
> +		return err;
> +	}
> +
> +	felix->ds->slave_mii_bus = felix->imdio;

A bit surprised to see MDIO bus registration in ocelot_ops :: reset and
not in felix_info :: mdio_bus_alloc.

> +
> +	/* We might need to reset the switch core here, if that is possible */
> +	err = regmap_field_write(ocelot->regfields[SYS_RESET_CFG_MEM_INIT], 1);
> +	if (err)

of_mdiobus_register() needs mdiobus_unregister() on error.

> +		return err;
> +
> +	err = regmap_field_write(ocelot->regfields[SYS_RESET_CFG_MEM_ENA], 1);
> +	if (err)
> +		return err;
> +
> +	do {
> +		msleep(1);
> +		regmap_field_read(ocelot->regfields[SYS_RESET_CFG_MEM_INIT],
> +				  &val);
> +	} while (val && --retries);
> +
> +	if (!retries)
> +		return -ETIMEDOUT;
> +
> +	err = regmap_field_write(ocelot->regfields[SYS_RESET_CFG_CORE_ENA], 1);
> +
> +	return err;

"err = ...; return err" can be turned into "return ..." if it weren't
for error handling. But you need to handle errors.

> +}
> +
> +static u32 ocelot_offset_from_reg_base(struct ocelot *ocelot, u32 target,
> +				       u32 reg)
> +{
> +	return ocelot->map[target][reg & REG_MASK];
> +}
> +
> +static const struct ocelot_ops vsc7512_ops = {
> +	.reset		= ocelot_ext_reset,
> +	.wm_enc		= ocelot_wm_enc,
> +	.wm_dec		= ocelot_wm_dec,
> +	.wm_stat	= ocelot_wm_stat,
> +	.port_to_netdev	= felix_port_to_netdev,
> +	.netdev_to_port	= felix_netdev_to_port,
> +};
> +
> +static const struct resource vsc7512_target_io_res[TARGET_MAX] = {
> +	[ANA] = {
> +		.start	= 0x71880000,
> +		.end	= 0x7188ffff,
> +		.name	= "ana",
> +	},
> +	[QS] = {
> +		.start	= 0x71080000,
> +		.end	= 0x710800ff,
> +		.name	= "qs",
> +	},
> +	[QSYS] = {
> +		.start	= 0x71800000,
> +		.end	= 0x719fffff,
> +		.name	= "qsys",
> +	},
> +	[REW] = {
> +		.start	= 0x71030000,
> +		.end	= 0x7103ffff,
> +		.name	= "rew",
> +	},
> +	[SYS] = {
> +		.start	= 0x71010000,
> +		.end	= 0x7101ffff,
> +		.name	= "sys",
> +	},
> +	[S0] = {
> +		.start	= 0x71040000,
> +		.end	= 0x710403ff,
> +		.name	= "s0",
> +	},
> +	[S1] = {
> +		.start	= 0x71050000,
> +		.end	= 0x710503ff,
> +		.name	= "s1",
> +	},
> +	[S2] = {
> +		.start	= 0x71060000,
> +		.end	= 0x710603ff,
> +		.name	= "s2",
> +	},
> +	[GCB] =	{
> +		.start	= 0x71070000,
> +		.end	= 0x7107022b,
> +		.name	= "devcpu_gcb",
> +	},
> +};
> +
> +static const struct resource vsc7512_port_io_res[] = {
> +	{
> +		.start	= 0x711e0000,
> +		.end	= 0x711effff,
> +		.name	= "port0",
> +	},
> +	{
> +		.start	= 0x711f0000,
> +		.end	= 0x711fffff,
> +		.name	= "port1",
> +	},
> +	{
> +		.start	= 0x71200000,
> +		.end	= 0x7120ffff,
> +		.name	= "port2",
> +	},
> +	{
> +		.start	= 0x71210000,
> +		.end	= 0x7121ffff,
> +		.name	= "port3",
> +	},
> +	{
> +		.start	= 0x71220000,
> +		.end	= 0x7122ffff,
> +		.name	= "port4",
> +	},
> +	{
> +		.start	= 0x71230000,
> +		.end	= 0x7123ffff,
> +		.name	= "port5",
> +	},
> +	{
> +		.start	= 0x71240000,
> +		.end	= 0x7124ffff,
> +		.name	= "port6",
> +	},
> +	{
> +		.start	= 0x71250000,
> +		.end	= 0x7125ffff,
> +		.name	= "port7",
> +	},
> +	{
> +		.start	= 0x71260000,
> +		.end	= 0x7126ffff,
> +		.name	= "port8",
> +	},
> +	{
> +		.start	= 0x71270000,
> +		.end	= 0x7127ffff,
> +		.name	= "port9",
> +	},
> +	{
> +		.start	= 0x71280000,
> +		.end	= 0x7128ffff,
> +		.name	= "port10",
> +	},
> +};
> +
> +static const struct reg_field vsc7512_regfields[REGFIELD_MAX] = {
> +	[ANA_ADVLEARN_VLAN_CHK] = REG_FIELD(ANA_ADVLEARN, 11, 11),
> +	[ANA_ADVLEARN_LEARN_MIRROR] = REG_FIELD(ANA_ADVLEARN, 0, 10),
> +	[ANA_ANEVENTS_MSTI_DROP] = REG_FIELD(ANA_ANEVENTS, 27, 27),
> +	[ANA_ANEVENTS_ACLKILL] = REG_FIELD(ANA_ANEVENTS, 26, 26),
> +	[ANA_ANEVENTS_ACLUSED] = REG_FIELD(ANA_ANEVENTS, 25, 25),
> +	[ANA_ANEVENTS_AUTOAGE] = REG_FIELD(ANA_ANEVENTS, 24, 24),
> +	[ANA_ANEVENTS_VS2TTL1] = REG_FIELD(ANA_ANEVENTS, 23, 23),
> +	[ANA_ANEVENTS_STORM_DROP] = REG_FIELD(ANA_ANEVENTS, 22, 22),
> +	[ANA_ANEVENTS_LEARN_DROP] = REG_FIELD(ANA_ANEVENTS, 21, 21),
> +	[ANA_ANEVENTS_AGED_ENTRY] = REG_FIELD(ANA_ANEVENTS, 20, 20),
> +	[ANA_ANEVENTS_CPU_LEARN_FAILED] = REG_FIELD(ANA_ANEVENTS, 19, 19),
> +	[ANA_ANEVENTS_AUTO_LEARN_FAILED] = REG_FIELD(ANA_ANEVENTS, 18, 18),
> +	[ANA_ANEVENTS_LEARN_REMOVE] = REG_FIELD(ANA_ANEVENTS, 17, 17),
> +	[ANA_ANEVENTS_AUTO_LEARNED] = REG_FIELD(ANA_ANEVENTS, 16, 16),
> +	[ANA_ANEVENTS_AUTO_MOVED] = REG_FIELD(ANA_ANEVENTS, 15, 15),
> +	[ANA_ANEVENTS_DROPPED] = REG_FIELD(ANA_ANEVENTS, 14, 14),
> +	[ANA_ANEVENTS_CLASSIFIED_DROP] = REG_FIELD(ANA_ANEVENTS, 13, 13),
> +	[ANA_ANEVENTS_CLASSIFIED_COPY] = REG_FIELD(ANA_ANEVENTS, 12, 12),
> +	[ANA_ANEVENTS_VLAN_DISCARD] = REG_FIELD(ANA_ANEVENTS, 11, 11),
> +	[ANA_ANEVENTS_FWD_DISCARD] = REG_FIELD(ANA_ANEVENTS, 10, 10),
> +	[ANA_ANEVENTS_MULTICAST_FLOOD] = REG_FIELD(ANA_ANEVENTS, 9, 9),
> +	[ANA_ANEVENTS_UNICAST_FLOOD] = REG_FIELD(ANA_ANEVENTS, 8, 8),
> +	[ANA_ANEVENTS_DEST_KNOWN] = REG_FIELD(ANA_ANEVENTS, 7, 7),
> +	[ANA_ANEVENTS_BUCKET3_MATCH] = REG_FIELD(ANA_ANEVENTS, 6, 6),
> +	[ANA_ANEVENTS_BUCKET2_MATCH] = REG_FIELD(ANA_ANEVENTS, 5, 5),
> +	[ANA_ANEVENTS_BUCKET1_MATCH] = REG_FIELD(ANA_ANEVENTS, 4, 4),
> +	[ANA_ANEVENTS_BUCKET0_MATCH] = REG_FIELD(ANA_ANEVENTS, 3, 3),
> +	[ANA_ANEVENTS_CPU_OPERATION] = REG_FIELD(ANA_ANEVENTS, 2, 2),
> +	[ANA_ANEVENTS_DMAC_LOOKUP] = REG_FIELD(ANA_ANEVENTS, 1, 1),
> +	[ANA_ANEVENTS_SMAC_LOOKUP] = REG_FIELD(ANA_ANEVENTS, 0, 0),
> +	[ANA_TABLES_MACACCESS_B_DOM] = REG_FIELD(ANA_TABLES_MACACCESS, 18, 18),
> +	[ANA_TABLES_MACTINDX_BUCKET] = REG_FIELD(ANA_TABLES_MACTINDX, 10, 11),
> +	[ANA_TABLES_MACTINDX_M_INDEX] = REG_FIELD(ANA_TABLES_MACTINDX, 0, 9),
> +	[GCB_SOFT_RST_SWC_RST] = REG_FIELD(GCB_SOFT_RST, 1, 1),
> +	[QSYS_TIMED_FRAME_ENTRY_TFRM_VLD] = REG_FIELD(QSYS_TIMED_FRAME_ENTRY, 20, 20),
> +	[QSYS_TIMED_FRAME_ENTRY_TFRM_FP] = REG_FIELD(QSYS_TIMED_FRAME_ENTRY, 8, 19),
> +	[QSYS_TIMED_FRAME_ENTRY_TFRM_PORTNO] = REG_FIELD(QSYS_TIMED_FRAME_ENTRY, 4, 7),
> +	[QSYS_TIMED_FRAME_ENTRY_TFRM_TM_SEL] = REG_FIELD(QSYS_TIMED_FRAME_ENTRY, 1, 3),
> +	[QSYS_TIMED_FRAME_ENTRY_TFRM_TM_T] = REG_FIELD(QSYS_TIMED_FRAME_ENTRY, 0, 0),
> +	[SYS_RESET_CFG_CORE_ENA] = REG_FIELD(SYS_RESET_CFG, 2, 2),
> +	[SYS_RESET_CFG_MEM_ENA] = REG_FIELD(SYS_RESET_CFG, 1, 1),
> +	[SYS_RESET_CFG_MEM_INIT] = REG_FIELD(SYS_RESET_CFG, 0, 0),
> +	/* Replicated per number of ports (12), register size 4 per port */
> +	[QSYS_SWITCH_PORT_MODE_PORT_ENA] = REG_FIELD_ID(QSYS_SWITCH_PORT_MODE, 14, 14, 12, 4),
> +	[QSYS_SWITCH_PORT_MODE_SCH_NEXT_CFG] = REG_FIELD_ID(QSYS_SWITCH_PORT_MODE, 11, 13, 12, 4),
> +	[QSYS_SWITCH_PORT_MODE_YEL_RSRVD] = REG_FIELD_ID(QSYS_SWITCH_PORT_MODE, 10, 10, 12, 4),
> +	[QSYS_SWITCH_PORT_MODE_INGRESS_DROP_MODE] = REG_FIELD_ID(QSYS_SWITCH_PORT_MODE, 9, 9, 12, 4),
> +	[QSYS_SWITCH_PORT_MODE_TX_PFC_ENA] = REG_FIELD_ID(QSYS_SWITCH_PORT_MODE, 1, 8, 12, 4),
> +	[QSYS_SWITCH_PORT_MODE_TX_PFC_MODE] = REG_FIELD_ID(QSYS_SWITCH_PORT_MODE, 0, 0, 12, 4),
> +	[SYS_PORT_MODE_DATA_WO_TS] = REG_FIELD_ID(SYS_PORT_MODE, 5, 6, 12, 4),
> +	[SYS_PORT_MODE_INCL_INJ_HDR] = REG_FIELD_ID(SYS_PORT_MODE, 3, 4, 12, 4),
> +	[SYS_PORT_MODE_INCL_XTR_HDR] = REG_FIELD_ID(SYS_PORT_MODE, 1, 2, 12, 4),
> +	[SYS_PORT_MODE_INCL_HDR_ERR] = REG_FIELD_ID(SYS_PORT_MODE, 0, 0, 12, 4),
> +	[SYS_PAUSE_CFG_PAUSE_START] = REG_FIELD_ID(SYS_PAUSE_CFG, 10, 18, 12, 4),
> +	[SYS_PAUSE_CFG_PAUSE_STOP] = REG_FIELD_ID(SYS_PAUSE_CFG, 1, 9, 12, 4),
> +	[SYS_PAUSE_CFG_PAUSE_ENA] = REG_FIELD_ID(SYS_PAUSE_CFG, 0, 1, 12, 4),
> +};
> +
> +static const struct ocelot_stat_layout vsc7512_stats_layout[] = {
> +	{ .offset = 0x00,	.name = "rx_octets", },
> +	{ .offset = 0x01,	.name = "rx_unicast", },
> +	{ .offset = 0x02,	.name = "rx_multicast", },
> +	{ .offset = 0x03,	.name = "rx_broadcast", },
> +	{ .offset = 0x04,	.name = "rx_shorts", },
> +	{ .offset = 0x05,	.name = "rx_fragments", },
> +	{ .offset = 0x06,	.name = "rx_jabbers", },
> +	{ .offset = 0x07,	.name = "rx_crc_align_errs", },
> +	{ .offset = 0x08,	.name = "rx_sym_errs", },
> +	{ .offset = 0x09,	.name = "rx_frames_below_65_octets", },
> +	{ .offset = 0x0A,	.name = "rx_frames_65_to_127_octets", },
> +	{ .offset = 0x0B,	.name = "rx_frames_128_to_255_octets", },
> +	{ .offset = 0x0C,	.name = "rx_frames_256_to_511_octets", },
> +	{ .offset = 0x0D,	.name = "rx_frames_512_to_1023_octets", },
> +	{ .offset = 0x0E,	.name = "rx_frames_1024_to_1526_octets", },
> +	{ .offset = 0x0F,	.name = "rx_frames_over_1526_octets", },
> +	{ .offset = 0x10,	.name = "rx_pause", },
> +	{ .offset = 0x11,	.name = "rx_control", },
> +	{ .offset = 0x12,	.name = "rx_longs", },
> +	{ .offset = 0x13,	.name = "rx_classified_drops", },
> +	{ .offset = 0x14,	.name = "rx_red_prio_0", },
> +	{ .offset = 0x15,	.name = "rx_red_prio_1", },
> +	{ .offset = 0x16,	.name = "rx_red_prio_2", },
> +	{ .offset = 0x17,	.name = "rx_red_prio_3", },
> +	{ .offset = 0x18,	.name = "rx_red_prio_4", },
> +	{ .offset = 0x19,	.name = "rx_red_prio_5", },
> +	{ .offset = 0x1A,	.name = "rx_red_prio_6", },
> +	{ .offset = 0x1B,	.name = "rx_red_prio_7", },
> +	{ .offset = 0x1C,	.name = "rx_yellow_prio_0", },
> +	{ .offset = 0x1D,	.name = "rx_yellow_prio_1", },
> +	{ .offset = 0x1E,	.name = "rx_yellow_prio_2", },
> +	{ .offset = 0x1F,	.name = "rx_yellow_prio_3", },
> +	{ .offset = 0x20,	.name = "rx_yellow_prio_4", },
> +	{ .offset = 0x21,	.name = "rx_yellow_prio_5", },
> +	{ .offset = 0x22,	.name = "rx_yellow_prio_6", },
> +	{ .offset = 0x23,	.name = "rx_yellow_prio_7", },
> +	{ .offset = 0x24,	.name = "rx_green_prio_0", },
> +	{ .offset = 0x25,	.name = "rx_green_prio_1", },
> +	{ .offset = 0x26,	.name = "rx_green_prio_2", },
> +	{ .offset = 0x27,	.name = "rx_green_prio_3", },
> +	{ .offset = 0x28,	.name = "rx_green_prio_4", },
> +	{ .offset = 0x29,	.name = "rx_green_prio_5", },
> +	{ .offset = 0x2A,	.name = "rx_green_prio_6", },
> +	{ .offset = 0x2B,	.name = "rx_green_prio_7", },
> +	{ .offset = 0x40,	.name = "tx_octets", },
> +	{ .offset = 0x41,	.name = "tx_unicast", },
> +	{ .offset = 0x42,	.name = "tx_multicast", },
> +	{ .offset = 0x43,	.name = "tx_broadcast", },
> +	{ .offset = 0x44,	.name = "tx_collision", },
> +	{ .offset = 0x45,	.name = "tx_drops", },
> +	{ .offset = 0x46,	.name = "tx_pause", },
> +	{ .offset = 0x47,	.name = "tx_frames_below_65_octets", },
> +	{ .offset = 0x48,	.name = "tx_frames_65_to_127_octets", },
> +	{ .offset = 0x49,	.name = "tx_frames_128_255_octets", },
> +	{ .offset = 0x4A,	.name = "tx_frames_256_511_octets", },
> +	{ .offset = 0x4B,	.name = "tx_frames_512_1023_octets", },
> +	{ .offset = 0x4C,	.name = "tx_frames_1024_1526_octets", },
> +	{ .offset = 0x4D,	.name = "tx_frames_over_1526_octets", },
> +	{ .offset = 0x4E,	.name = "tx_yellow_prio_0", },
> +	{ .offset = 0x4F,	.name = "tx_yellow_prio_1", },
> +	{ .offset = 0x50,	.name = "tx_yellow_prio_2", },
> +	{ .offset = 0x51,	.name = "tx_yellow_prio_3", },
> +	{ .offset = 0x52,	.name = "tx_yellow_prio_4", },
> +	{ .offset = 0x53,	.name = "tx_yellow_prio_5", },
> +	{ .offset = 0x54,	.name = "tx_yellow_prio_6", },
> +	{ .offset = 0x55,	.name = "tx_yellow_prio_7", },
> +	{ .offset = 0x56,	.name = "tx_green_prio_0", },
> +	{ .offset = 0x57,	.name = "tx_green_prio_1", },
> +	{ .offset = 0x58,	.name = "tx_green_prio_2", },
> +	{ .offset = 0x59,	.name = "tx_green_prio_3", },
> +	{ .offset = 0x5A,	.name = "tx_green_prio_4", },
> +	{ .offset = 0x5B,	.name = "tx_green_prio_5", },
> +	{ .offset = 0x5C,	.name = "tx_green_prio_6", },
> +	{ .offset = 0x5D,	.name = "tx_green_prio_7", },
> +	{ .offset = 0x5E,	.name = "tx_aged", },
> +	{ .offset = 0x80,	.name = "drop_local", },
> +	{ .offset = 0x81,	.name = "drop_tail", },
> +	{ .offset = 0x82,	.name = "drop_yellow_prio_0", },
> +	{ .offset = 0x83,	.name = "drop_yellow_prio_1", },
> +	{ .offset = 0x84,	.name = "drop_yellow_prio_2", },
> +	{ .offset = 0x85,	.name = "drop_yellow_prio_3", },
> +	{ .offset = 0x86,	.name = "drop_yellow_prio_4", },
> +	{ .offset = 0x87,	.name = "drop_yellow_prio_5", },
> +	{ .offset = 0x88,	.name = "drop_yellow_prio_6", },
> +	{ .offset = 0x89,	.name = "drop_yellow_prio_7", },
> +	{ .offset = 0x8A,	.name = "drop_green_prio_0", },
> +	{ .offset = 0x8B,	.name = "drop_green_prio_1", },
> +	{ .offset = 0x8C,	.name = "drop_green_prio_2", },
> +	{ .offset = 0x8D,	.name = "drop_green_prio_3", },
> +	{ .offset = 0x8E,	.name = "drop_green_prio_4", },
> +	{ .offset = 0x8F,	.name = "drop_green_prio_5", },
> +	{ .offset = 0x90,	.name = "drop_green_prio_6", },
> +	{ .offset = 0x91,	.name = "drop_green_prio_7", },
> +};
> +
> +static void vsc7512_phylink_validate(struct ocelot *ocelot, int port,
> +				     unsigned long *supported,
> +				     struct phylink_link_state *state)
> +{
> +	struct ocelot_port *ocelot_port = ocelot->ports[port];
> +
> +	__ETHTOOL_DECLARE_LINK_MODE_MASK(mask) = { 0, };
> +
> +	if (state->interface != PHY_INTERFACE_MODE_NA &&
> +	    state->interface != ocelot_port->phy_mode) {
> +		bitmap_zero(supported, __ETHTOOL_LINK_MODE_MASK_NBITS);
> +		return;
> +	}
> +
> +	phylink_set_port_modes(mask);
> +
> +	phylink_set(mask, Pause);
> +	phylink_set(mask, Autoneg);
> +	phylink_set(mask, Asym_Pause);
> +	phylink_set(mask, 10baseT_Half);
> +	phylink_set(mask, 10baseT_Full);
> +	phylink_set(mask, 100baseT_Half);
> +	phylink_set(mask, 100baseT_Full);
> +	phylink_set(mask, 1000baseT_Half);
> +	phylink_set(mask, 1000baseT_Full);
> +
> +	bitmap_and(supported, supported, mask, __ETHTOOL_LINK_MODE_MASK_NBITS);
> +	bitmap_and(state->advertising, state->advertising, mask,
> +		   __ETHTOOL_LINK_MODE_MASK_NBITS);
> +}
> +
> +static int vsc7512_prevalidate_phy_mode(struct ocelot *ocelot, int port,
> +					phy_interface_t phy_mode)
> +{
> +	struct ocelot_ext_data *ocelot_ext = ocelot_to_ocelot_ext(ocelot);
> +
> +	switch (phy_mode) {
> +	case PHY_INTERFACE_MODE_INTERNAL:
> +		if (ocelot_ext->port_modes[port] &
> +				OCELOT_SPI_PORT_MODE_INTERNAL)
> +			return 0;
> +		return -EOPNOTSUPP;
> +	case PHY_INTERFACE_MODE_SGMII:
> +		if (ocelot_ext->port_modes[port] & OCELOT_SPI_PORT_MODE_SGMII)
> +			return 0;
> +		return -EOPNOTSUPP;
> +	case PHY_INTERFACE_MODE_QSGMII:
> +		if (ocelot_ext->port_modes[port] & OCELOT_SPI_PORT_MODE_QSGMII)
> +			return 0;
> +		return -EOPNOTSUPP;
> +	default:
> +		return -EOPNOTSUPP;
> +	}
> +}
> +
> +static int vsc7512_port_setup_tc(struct dsa_switch *ds, int port,
> +				 enum tc_setup_type type, void *type_data)
> +{
> +	return -EOPNOTSUPP;
> +}

The absence of this callback should return -EOPNOTSUPP as well, from
felix_port_setup_tc().

> +
> +static struct vcap_props vsc7512_vcap_props[] = {
> +	[VCAP_ES0] = {
> +		.action_type_width = 0,
> +		.action_table = {
> +			[ES0_ACTION_TYPE_NORMAL] = {
> +				.width = 73,
> +				.count = 1,
> +			},
> +		},
> +		.target = S0,
> +		.keys = vsc7514_vcap_es0_keys,
> +		.actions = vsc7514_vcap_es0_actions,
> +	},
> +	[VCAP_IS1] = {
> +		.action_type_width = 0,
> +		.action_table = {
> +			[IS1_ACTION_TYPE_NORMAL] = {
> +				.width = 78,
> +				.count = 4,
> +			},
> +		},
> +		.target = S1,
> +		.keys = vsc7514_vcap_is1_keys,
> +		.actions = vsc7514_vcap_is1_actions,
> +	},
> +	[VCAP_IS2] = {
> +		.action_type_width = 1,
> +		.action_table = {
> +			[IS2_ACTION_TYPE_NORMAL] = {
> +				.width = 49,
> +				.count = 2,
> +			},
> +			[IS2_ACTION_TYPE_SMAC_SIP] = {
> +				.width = 6,
> +				.count = 4,
> +			},
> +		},
> +		.target = S2,
> +		.keys = vsc7514_vcap_is2_keys,
> +		.actions = vsc7514_vcap_is2_actions,
> +	},
> +};
> +
> +static struct regmap *vsc7512_regmap_init(struct ocelot *ocelot,
> +					  struct resource *res)
> +{
> +	struct device *dev = ocelot->dev;
> +	struct regmap *regmap;
> +
> +	regmap = ocelot_get_regmap_from_resource(dev->parent, res);
> +	if (IS_ERR(regmap))
> +		return ERR_CAST(regmap);
> +
> +	return regmap;

Seems like a long-winded way of typing "return ocelot_get_regmap_from_resource(...)"?

> +}
> +
> +static int vsc7512_mdio_bus_alloc(struct ocelot *ocelot)
> +{
> +	struct felix *felix = ocelot_to_felix(ocelot);
> +	struct device *dev = ocelot->dev;
> +	u32 mii_offset, phy_offset;
> +	struct mii_bus *bus;
> +	int err;
> +
> +	mii_offset = ocelot_offset_from_reg_base(ocelot, GCB,
> +						 GCB_MIIM_MII_STATUS);
> +
> +	phy_offset = ocelot_offset_from_reg_base(ocelot, GCB, GCB_PHY_PHY_CFG);
> +
> +	err = mscc_miim_setup(dev, &bus, "ocelot_ext MDIO bus",
> +			       ocelot->targets[GCB], mii_offset,
> +			       ocelot->targets[GCB], phy_offset);
> +	if (err) {
> +		dev_err(dev, "failed to setup MDIO bus\n");
> +		return err;
> +	}
> +
> +	felix->imdio = bus;
> +
> +	return err;
> +}
> +
> +
> +static void vsc7512_mdio_bus_free(struct ocelot *ocelot)
> +{
> +	struct felix *felix = ocelot_to_felix(ocelot);
> +
> +	if (felix->imdio)

I don't think the conditional is warranted here? Did you notice a call
path where you were called while felix->imdio was NULL?

> +		mdiobus_unregister(felix->imdio);
> +}
> +
> +static const struct felix_info ocelot_ext_info = {
> +	.target_io_res			= vsc7512_target_io_res,
> +	.port_io_res			= vsc7512_port_io_res,
> +	.regfields			= vsc7512_regfields,
> +	.map				= vsc7512_regmap,
> +	.ops				= &vsc7512_ops,
> +	.stats_layout			= vsc7512_stats_layout,
> +	.num_stats			= ARRAY_SIZE(vsc7512_stats_layout),
> +	.vcap				= vsc7512_vcap_props,
> +	.num_mact_rows			= 1024,
> +	.num_ports			= VSC7512_NUM_PORTS,
> +	.num_tx_queues			= OCELOT_NUM_TC,
> +	.mdio_bus_alloc			= vsc7512_mdio_bus_alloc,
> +	.mdio_bus_free			= vsc7512_mdio_bus_free,
> +	.phylink_validate		= vsc7512_phylink_validate,
> +	.prevalidate_phy_mode		= vsc7512_prevalidate_phy_mode,
> +	.port_setup_tc			= vsc7512_port_setup_tc,
> +	.init_regmap			= vsc7512_regmap_init,
> +};
> +
> +static int ocelot_ext_probe(struct platform_device *pdev)
> +{
> +	struct ocelot_ext_data *ocelot_ext;
> +	struct dsa_switch *ds;
> +	struct ocelot *ocelot;
> +	struct felix *felix;
> +	struct device *dev;
> +	int err;
> +
> +	dev = &pdev->dev;
> +
> +	ocelot_ext = devm_kzalloc(dev, sizeof(struct ocelot_ext_data),
> +				  GFP_KERNEL);
> +
> +	if (!ocelot_ext)

Try to omit blank lines between an assignment and the proceeding sanity
checks. Also, try to stick to either using devres everywhere, or nowhere,
within the same function at least.

> +		return -ENOMEM;
> +
> +	dev_set_drvdata(dev, ocelot_ext);
> +
> +	ocelot_ext->port_modes = vsc7512_port_modes;
> +	felix = &ocelot_ext->felix;
> +
> +	ocelot = &felix->ocelot;
> +	ocelot->dev = dev;
> +
> +	ocelot->num_flooding_pgids = 1;
> +
> +	felix->info = &ocelot_ext_info;
> +
> +	ds = kzalloc(sizeof(*ds), GFP_KERNEL);
> +	if (!ds) {
> +		err = -ENOMEM;
> +		dev_err(dev, "Failed to allocate DSA switch\n");
> +		return err;
> +	}
> +
> +	ds->dev = dev;
> +	ds->num_ports = felix->info->num_ports;
> +	ds->num_tx_queues = felix->info->num_tx_queues;
> +
> +	ds->ops = &felix_switch_ops;
> +	ds->priv = ocelot;
> +	felix->ds = ds;
> +	felix->tag_proto = DSA_TAG_PROTO_OCELOT;
> +
> +	err = dsa_register_switch(ds);
> +
> +	if (err) {
> +		dev_err(dev, "Failed to register DSA switch: %d\n", err);
> +		goto err_register_ds;
> +	}
> +
> +	return 0;
> +
> +err_register_ds:
> +	kfree(ds);
> +	return err;
> +}
> +
> +static int ocelot_ext_remove(struct platform_device *pdev)
> +{
> +	struct ocelot_ext_data *ocelot_ext;
> +	struct felix *felix;
> +
> +	ocelot_ext = dev_get_drvdata(&pdev->dev);
> +	felix = &ocelot_ext->felix;
> +
> +	dsa_unregister_switch(felix->ds);
> +
> +	kfree(felix->ds);
> +
> +	devm_kfree(&pdev->dev, ocelot_ext);

What is the point of devm_kfree?

> +
> +	return 0;
> +}
> +
> +const struct of_device_id ocelot_ext_switch_of_match[] = {
> +	{ .compatible = "mscc,vsc7512-ext-switch" },
> +	{ },
> +};
> +MODULE_DEVICE_TABLE(of, ocelot_ext_switch_of_match);
> +
> +static struct platform_driver ocelot_ext_switch_driver = {
> +	.driver = {
> +		.name = "ocelot-ext-switch",
> +		.of_match_table = of_match_ptr(ocelot_ext_switch_of_match),
> +	},
> +	.probe = ocelot_ext_probe,
> +	.remove = ocelot_ext_remove,

Please blindly follow the pattern of every other DSA driver, with a
->remove and ->shutdown method that run either one, or the other, by
checking whether dev_get_drvdata() has been set to NULL by the other one
or not. And call dsa_switch_shutdown() from ocelot_ext_shutdown() (or
vsc7512_shutdown, or whatever you decide to call it).

> +};
> +module_platform_driver(ocelot_ext_switch_driver);
> +
> +MODULE_DESCRIPTION("External Ocelot Switch driver");
> +MODULE_LICENSE("GPL v2");
> diff --git a/include/soc/mscc/ocelot.h b/include/soc/mscc/ocelot.h
> index 8b8ebede5a01..62cd61d4142e 100644
> --- a/include/soc/mscc/ocelot.h
> +++ b/include/soc/mscc/ocelot.h
> @@ -399,6 +399,8 @@ enum ocelot_reg {
>  	GCB_MIIM_MII_STATUS,
>  	GCB_MIIM_MII_CMD,
>  	GCB_MIIM_MII_DATA,
> +	GCB_PHY_PHY_CFG,
> +	GCB_PHY_PHY_STAT,
>  	DEV_CLOCK_CFG = DEV_GMII << TARGET_OFFSET,
>  	DEV_PORT_MISC,
>  	DEV_EVENTS,
> -- 
> 2.25.1
>

Powered by blists - more mailing lists