Re: [PATCH v2 net-next 4/9] net: dsa: microchip: add DSA support for microchip lan937x

From: Prasanna Vengateshan
Date: Tue Apr 27 2021 - 03:43:57 EST


On Thu, 2021-04-22 at 22:59 +0300, Vladimir Oltean wrote:
> EXTERNAL EMAIL: Do not click links or open attachments unless you know the
> content is safe
>
> On Thu, Apr 22, 2021 at 03:12:52PM +0530, Prasanna Vengateshan wrote:
> > Basic DSA driver support for lan937x and the device will be
> > configured through SPI interface.
> >
> > drivers/net/dsa/microchip/ path is already part of MAINTAINERS &
> > the new files come under this path. Hence no update needed to the
> > MAINTAINERS
> >
> > Reused KSZ APIs for port_bridge_join() & port_bridge_leave() and
> > added support for port_stp_state_set() & port_fast_age().
> >
> > lan937x_flush_dyn_mac_table() which gets called from
> > port_fast_age() of KSZ common layer, hence added support for it.
> >
> > currently port_bridge_flags returns -EOPNOTSUPP, this support
> > will be added later
> >
> > Signed-off-by: Prasanna Vengateshan <prasanna.vengateshan@xxxxxxxxxxxxx>
> > ---
> (...)
> > +int lan937x_reset_switch(struct ksz_device *dev)
> > +{
> > +     u32 data32;
> > +     u8 data8;
> > +     int rc;
> > +
> > +     /* reset switch */
> > +     rc = lan937x_cfg(dev, REG_SW_OPERATION, SW_RESET, true);
> > +     if (rc < 0)
> > +             return rc;
> > +
> > +     /* default configuration */
> > +     rc = ksz_read8(dev, REG_SW_LUE_CTRL_1, &data8);
> > +     if (rc < 0)
> > +             return rc;
> > +
> > +     data8 = SW_AGING_ENABLE | SW_LINK_AUTO_AGING |
> > +           SW_SRC_ADDR_FILTER;
> > +
> > +     rc = ksz_write8(dev, REG_SW_LUE_CTRL_1, data8);
> > +     if (rc < 0)
> > +             return rc;
> > +
> > +     /* disable interrupts */
> > +     rc = ksz_write32(dev, REG_SW_INT_MASK__4, SWITCH_INT_MASK);
> > +     if (rc < 0)
> > +             return rc;
> > +
> > +     rc = ksz_write32(dev, REG_SW_PORT_INT_MASK__4, 0xFF);
> > +     if (rc < 0)
> > +             return rc;
> > +
> > +     rc = ksz_read32(dev, REG_SW_PORT_INT_STATUS__4, &data32);
> > +     if (rc < 0)
> > +             return rc;
> > +
> > +     /* set broadcast storm protection 10% rate */
> > +     rc = regmap_update_bits(dev->regmap[1], REG_SW_MAC_CTRL_2,
> > +                             BROADCAST_STORM_RATE,
> > +                        (BROADCAST_STORM_VALUE *
> > +                        BROADCAST_STORM_PROT_RATE) / 100);
>
> Why do you think this is a sane enough configuration to enable by
> default? We have tc-flower policers for this kind of stuff. If the
> broadcast policer is global to the switch and not per port, you can
> model it using:
>
> tc qdisc add dev lan1 ingress_block 1 clsact
> tc qdisc add dev lan2 ingress_block 1 clsact
> tc qdisc add dev lan3 ingress_block 1 clsact
> tc filter add block 1 flower skip_sw dst_mac ff:ff:ff:ff:ff:ff \
>         action police \
>         rate 43Mbit \
>         burst 10000
Noted, What i understand is FLOW_ACTION_POLICE to be implemented later as part
of cls_flower_add(). Will remove this config in the next version.

>
> > +
> > +     return rc;
> > +}
> > +
> (...)
> > +int lan937x_internal_phy_write(struct ksz_device *dev, int addr,
> > +                            int reg, u16 val)
> > +{
> > +     u16 temp, addr_base;
> > +     unsigned int value;
> > +     int rc;
> > +
> > +     /* Check for internal phy port */
> > +     if (!lan937x_is_internal_phy_port(dev, addr))
> > +             return -EOPNOTSUPP;
> > +
> > +     if (lan937x_is_internal_100BTX_phy_port(dev, addr))
> > +             addr_base = REG_PORT_TX_PHY_CTRL_BASE;
> > +     else
> > +             addr_base = REG_PORT_T1_PHY_CTRL_BASE;
> > +
> > +     temp = PORT_CTRL_ADDR(addr, (addr_base + (reg << 2)));
> > +
> > +     rc = ksz_write16(dev, REG_VPHY_IND_ADDR__2, temp);
> > +     if (rc < 0)
> > +             return rc;
> > +
> > +     /* Write the data to be written to the VPHY reg */
> > +     rc = ksz_write16(dev, REG_VPHY_IND_DATA__2, val);
> > +     if (rc < 0)
> > +             return rc;
> > +
> > +     /* Write the Write En and Busy bit */
> > +     rc = ksz_write16(dev, REG_VPHY_IND_CTRL__2, (VPHY_IND_WRITE
> > +                             | VPHY_IND_BUSY));
>
> This isn't quite the coding style that the kernel community is used to
> seeing. This looks more adequate:
My apologies on the incorrect indentation in some places. I will find out how
did i miss and fix from my side.

> > +
> > +     /* failed to write phy register. get out of loop */
>
> What loop? The regmap_read_poll_timeout? If you're here you're already
> out of it, aren't you?
"get out of loop" should be removed.


> > +
> > +            
> >
> > +static int lan937x_mdio_register(struct dsa_switch *ds)
> > +{
> > +     struct ksz_device *dev = ds->priv;
> > +     int ret;
> > +
> > +     dev->mdio_np = of_get_compatible_child(ds->dev->of_node,
> > "microchip,lan937x-mdio");
>
> I think it is strange to have a node with a compatible string but no
> dedicated driver? I think the most popular option is to set:
>
>         dev->mdio_np = of_get_child_by_name(node, "mdio");
>
> and just create an "mdio" subnode with no compatible.
Sure

>
> > +
> > +     if (!dev->mdio_np) {
> > +             dev_err(ds->dev, "no MDIO bus node\n");
> > +             return -ENODEV;
> > +     }
> > +
> > +     ds->slave_mii_bus = devm_mdiobus_alloc(ds->dev);
> > +
> > +     if (!ds->slave_mii_bus)
> > +             return -ENOMEM;
> > +
> > +     ds->slave_mii_bus->priv = ds->priv;
> > +     ds->slave_mii_bus->read = lan937x_sw_mdio_read;
> > +     ds->slave_mii_bus->write = lan937x_sw_mdio_write;
> > +     ds->slave_mii_bus->name = "lan937x slave smi";
> > +     snprintf(ds->slave_mii_bus->id, MII_BUS_ID_SIZE, "SMI-%d",
> > +              ds->index);
> > +     ds->slave_mii_bus->parent = ds->dev;
> > +     ds->slave_mii_bus->phy_mask = ~ds->phys_mii_mask;
> > +
> > +     ret = of_mdiobus_register(ds->slave_mii_bus, dev->mdio_np);
> > +
> > +     if (ret) {
> > +             dev_err(ds->dev, "unable to register MDIO bus %s\n",
> > +                     ds->slave_mii_bus->id);
> > +             of_node_put(dev->mdio_np);
> > +             return ret;
> > +     }
> > +
> > +     return 0;
> > +}
> > +
> >
> > +
> > +static phy_interface_t lan937x_get_interface(struct ksz_device *dev, int
> > port)
> > +{
> > +     phy_interface_t interface;
> > +     u8 data8;
> > +     int rc;
> > +
> > +     if (lan937x_is_internal_phy_port(dev, port))
> > +             return PHY_INTERFACE_MODE_NA;
>
> I think conventional wisdom is to use PHY_INTERFACE_MODE_INTERNAL for
> internal ports, as the name would suggest.
Sure.

>
> >
> > +static void lan937x_config_cpu_port(struct dsa_switch *ds)
> > +{
> > +     struct ksz_device *dev = ds->priv;
> > +     struct ksz_port *p;
> > +     int i;
> > +
> > +     ds->num_ports = dev->port_cnt;
> > +
> > +     for (i = 0; i < dev->port_cnt; i++) {
> > +             if (dsa_is_cpu_port(ds, i) && (dev->cpu_ports & (1 << i))) {
> > +                     phy_interface_t interface;
> > +                     const char *prev_msg;
> > +                     const char *prev_mode;
> > +
> > +                     dev->cpu_port = i;
> > +                     dev->host_mask = (1 << dev->cpu_port);
> > +                     dev->port_mask |= dev->host_mask;
> > +                     p = &dev->ports[i];
> > +
> > +                     /* Read from XMII register to determine host port
> > +                      * interface.  If set specifically in device tree
> > +                      * note the difference to help debugging.
> > +                      */
> > +                     interface = lan937x_get_interface(dev, i);
> > +                     if (!p->interface) {
> > +                             if (dev->compat_interface)
> > +                                     p->interface = dev->compat_interface;
>
> Compatibility with what? This is a new driver.
Should be removed.

>
> > +                             else
> > +                                     p->interface = interface;
> > +                     }
> > +
> > +                     if (interface && interface != p->interface) {
> > +                             prev_msg = " instead of ";
> > +                             prev_mode = phy_modes(interface);
> > +                     } else {
> > +                             prev_msg = "";
> > +                             prev_mode = "";
> > +                     }
> > +
> > +                     dev_info(dev->dev,
> > +                              "Port%d: using phy mode %s%s%s\n",
> > +                              i,
> > +                              phy_modes(p->interface),
> > +                              prev_msg,
> > +                              prev_mode);
>
> It's unlikely that anyone is going to be able to find such a composite
> error message string using grep.
Since the compatibility is going to be removed, it is expected to configure from
DTS. I will retain the dev_info with out the string 'instead of' for just to
inform what interface is currently configured.

>
> > +
> > +                     /* enable cpu port */
> > +                     lan937x_port_setup(dev, i, true);
> > +                     p->vid_member = dev->port_mask;
> > +             }
> > +     }
> > +
> > +     dev->member = dev->host_mask;
> > +
> > +     for (i = 0; i < dev->port_cnt; i++) {
> > +             if (i == dev->cpu_port)
> > +                     continue;
> > +             p = &dev->ports[i];
> > +
> > +             /* Initialize to non-zero so that lan937x_cfg_port_member()
> > will
> > +              * be called.
> > +              */
> > +             p->vid_member = (1 << i);
> > +             p->member = dev->port_mask;
> > +             lan937x_port_stp_state_set(ds, i, BR_STATE_DISABLED);
> > +     }
> > +}
> > +
> > +static int lan937x_setup(struct dsa_switch *ds)
> > +{
> > +     struct ksz_device *dev = ds->priv;
> > +     int rc;
> > +
> > +     dev->vlan_cache = devm_kcalloc(dev->dev, sizeof(struct vlan_table),
> > +                                    dev->num_vlans, GFP_KERNEL);
> > +     if (!dev->vlan_cache)
> > +             return -ENOMEM;
> > +
> > +     rc = lan937x_reset_switch(dev);
> > +     if (rc < 0) {
> > +             dev_err(ds->dev, "failed to reset switch\n");
> > +             return rc;
> > +     }
> > +
> > +     /* Required for port partitioning. */
> > +     lan937x_cfg32(dev, REG_SW_QM_CTRL__4, UNICAST_VLAN_BOUNDARY,
> > +                   true);
> > +
> > +     lan937x_config_cpu_port(ds);
> > +
> > +     ds->configure_vlan_while_not_filtering = true;
>
> You can delete this line, it's implicitly true now.
Sure.

>

> > +
> > +     lan937x_enable_spi_indirect_access(dev);
> > +
> > +     /* start switch */
> > +     lan937x_cfg(dev, REG_SW_OPERATION, SW_START, true);
> > +
> > +     ksz_init_mib_timer(dev);
> > +
> > +     return 0;
> > +}
> > +
> > +static int lan937x_change_mtu(struct dsa_switch *ds, int port, int mtu)
> > +{
> > +     struct ksz_device *dev = ds->priv;
> > +     u16 max_size;
> > +     int rc;
> > +
> > +     if (mtu >= FR_MIN_SIZE) {
> > +             rc = lan937x_port_cfg(dev, port, REG_PORT_MAC_CTRL_0,
> > PORT_JUMBO_EN, true);
> > +             max_size = FR_MAX_SIZE;
> > +     } else {
> > +             rc = lan937x_port_cfg(dev, port, REG_PORT_MAC_CTRL_0,
> > PORT_JUMBO_EN, false);
> > +             max_size = FR_MIN_SIZE;
> > +     }
> > +
> > +     if (rc < 0) {
> > +             dev_err(ds->dev, "failed to enable jumbo\n");
> > +             return rc;
> > +     }
> > +
> > +     /* Write the frame size in PORT_MAX_FR_SIZE register */
> > +     rc = lan937x_pwrite16(dev, port, PORT_MAX_FR_SIZE, max_size);
> > +
> > +     if (rc < 0) {
> > +             dev_err(ds->dev, "failed to change the mtu\n");
> > +             return rc;
> > +     }
> > +
> > +     return 0;
> > +}
> > +
> > +static int lan937x_get_max_mtu(struct dsa_switch *ds, int port)
> > +{
> > +     /* Frame size is 9000 (= 0x2328) if
> > +      * jumbo frame support is enabled, PORT_JUMBO_EN bit will be enabled
> > +      * based on mtu in lan937x_change_mtu() API
> > +      */
> > +     return FR_MAX_SIZE;
>
> Frame size is one thing. But MTU is L2 payload, which excludes MAC DA,
> MAC SA, EtherType and VLAN ID. So does the switch really accept a packet
> with an L2 payload length of 9000 bytes and a VLAN tag?
(FR_MAX_SIZE - VLAN_ETH_HLEN - ETH_FCS_LEN) is supposed to be returned and i
will add the test case from my side. will make the changes in next rev.

>
> > +}
> > +
> > +static int   lan937x_port_pre_bridge_flags(struct dsa_switch *ds, int port,
> > +                                           struct switchdev_brport_flags
> > flags,
> > +                                      struct netlink_ext_ack *extack)
> > +{
> > +     return -EOPNOTSUPP;
> > +}
> > +
> > +static int   lan937x_port_bridge_flags(struct dsa_switch *ds, int port,
> > +                                       struct switchdev_brport_flags flags,
> > +                                      struct netlink_ext_ack *extack)
> > +{
> > +     return -EOPNOTSUPP;
> > +}
>
> This shouldn't have been implemented but is necessary due to a bug, see
> commit 70a7c484c7c3 ("net: dsa: fix bridge support for drivers without
> port_bridge_flags callback").
Thanks for pointing out the commit ID. I will remove it.

>
> > +
> > +const struct dsa_switch_ops lan937x_switch_ops = {
> > +     .get_tag_protocol       = lan937x_get_tag_protocol,
> > +     .setup                  = lan937x_setup,
> > +     .phy_read               = lan937x_phy_read16,
> > +     .phy_write              = lan937x_phy_write16,
> > +     .port_enable            = ksz_enable_port,
> > +     .port_bridge_join       = ksz_port_bridge_join,
> > +     .port_bridge_leave      = ksz_port_bridge_leave,
> > +     .port_pre_bridge_flags  = lan937x_port_pre_bridge_flags,
> > +     .port_bridge_flags      = lan937x_port_bridge_flags,
> > +     .port_stp_state_set     = lan937x_port_stp_state_set,
> > +     .port_fast_age          = ksz_port_fast_age,
> > +     .port_max_mtu           = lan937x_get_max_mtu,
> > +     .port_change_mtu        = lan937x_change_mtu,
> > +};
> > +
> > +int lan937x_switch_register(struct ksz_device *dev)
> > +{
> > +     int ret;
> > +
> > +     ret = ksz_switch_register(dev, &lan937x_dev_ops);
> > +
> > +     if (ret) {
> > +             if (dev->mdio_np) {
> > +                     mdiobus_unregister(dev->ds->slave_mii_bus);
>
> I don't see any mdiobus_unregister when the driver is removed?
Oops, i will add unregister in ".remove" as well.

>
> > +                     of_node_put(dev->mdio_np);
>
> Also, why keep mdio_np inside ksz_device, therefore for the entire
> lifetime of the driver? Why do you need it? 
ksz_switch_register common function is used here. Inside ksz_switch_register
function, mdio is being registered through init(). After the mdio bus
registration, there is dsa_register_switch() in ksz common layer. Hence made
that as part of ksz_device to unregister the mdio if there were any failures and
unregistered along with of_node_put. Do you think this is a bad approach?


> Not to mention you don't
> appear to be ever calling of_node_put on unbind, unless I'm missing
> something.
of_node_put is called as above. And then the same is called, if
of_mdiobus_register() is failed in lan937x_dev.c. Then the next action is to
call during driver removal. Did i miss any other place?


>
> > +             }
> > +     }
> > +
> > +     return ret;
> > +}
> > +EXPORT_SYMBOL(lan937x_switch_register);
> > +
> > +MODULE_AUTHOR("Prasanna Vengateshan Varadharajan <
> > Prasanna.Vengateshan@xxxxxxxxxxxxx>");
> > +MODULE_DESCRIPTION("Microchip LAN937x Series Switch DSA Driver");
> > +MODULE_LICENSE("GPL");
> > diff --git a/drivers/net/dsa/microchip/lan937x_spi.c
> > b/drivers/net/dsa/microchip/lan937x_spi.c
> > new file mode 100644
> > index 000000000000..d9731d6afb96
> > --- /dev/null
> > +++ b/drivers/net/dsa/microchip/lan937x_spi.c
> > @@ -0,0 +1,226 @@
> > +// SPDX-License-Identifier: GPL-2.0
> > +/* Microchip LAN937X switch driver register access through SPI
> > + * Copyright (C) 2019-2021 Microchip Technology Inc.
> > + */
> > +#include <asm/unaligned.h>
>
> Why do you need this?
>
> > +
> > +#include <linux/delay.h>
>
> Or this?
Will remove both of them

>
> > +#include <linux/kernel.h>
> > +#include <linux/module.h>
> > +#include <linux/regmap.h>
> > +#include <linux/spi/spi.h>
> > +#include <linux/of_device.h>
> > +
> > +#include "ksz_common.h"
> > +
> > +#define SPI_ADDR_SHIFT                               24
> > +#define SPI_ADDR_ALIGN                               3
> > +#define SPI_TURNAROUND_SHIFT         5
> > +
> > +KSZ_REGMAP_TABLE(lan937x, 32, SPI_ADDR_SHIFT,
> > +              SPI_TURNAROUND_SHIFT, SPI_ADDR_ALIGN);
> > +
> > +struct lan937x_chip_data {
> > +     u32 chip_id;
> > +     const char *dev_name;
> > +     int num_vlans;
> > +     int num_alus;
> > +     int num_statics;
> > +     int cpu_ports;
> > +     int port_cnt;
> > +};
> > +
> > +static const struct of_device_id lan937x_dt_ids[];
> > +
> > +static const struct lan937x_chip_data lan937x_switch_chips[] = {
> > +     {
> > +             .chip_id = 0x00937010,
> > +             .dev_name = "LAN9370",
> > +             .num_vlans = 4096,
> > +             .num_alus = 1024,
> > +             .num_statics = 256,
> > +             /* can be configured as cpu port */
> > +             .cpu_ports = 0x10,
> > +             /* total port count */
> > +             .port_cnt = 5,
> > +     },
> > +     {
> > +             .chip_id = 0x00937110,
> > +             .dev_name = "LAN9371",
> > +             .num_vlans = 4096,
> > +             .num_alus = 1024,
> > +             .num_statics = 256,
> > +             /* can be configured as cpu port */
> > +             .cpu_ports = 0x30,
> > +             /* total port count */
> > +             .port_cnt = 6,
> > +     },
> > +     {
> > +             .chip_id = 0x00937210,
> > +             .dev_name = "LAN9372",
> > +             .num_vlans = 4096,
> > +             .num_alus = 1024,
> > +             .num_statics = 256,
> > +             /* can be configured as cpu port */
> > +             .cpu_ports = 0x30,
> > +             /* total port count */
> > +             .port_cnt = 8,
> > +     },
> > +     {
> > +             .chip_id = 0x00937310,
> > +             .dev_name = "LAN9373",
> > +             .num_vlans = 4096,
> > +             .num_alus = 1024,
> > +             .num_statics = 256,
> > +             /* can be configured as cpu port */
> > +             .cpu_ports = 0x38,
> > +             /* total port count */
> > +             .port_cnt = 5,
> > +     },
> > +     {
> > +             .chip_id = 0x00937410,
> > +             .dev_name = "LAN9374",
> > +             .num_vlans = 4096,
> > +             .num_alus = 1024,
> > +             .num_statics = 256,
> > +             /* can be configured as cpu port */
> > +             .cpu_ports = 0x30,
> > +             /* total port count */
> > +             .port_cnt = 8,
> > +     },
> > +
>
> The new line shouldn't be here?
Noted, will remove it.

>
> > +};
> > +