[RFC v5 net-next 00/13] add support for VSC75XX control over SPI

Colin Foster
2021-12-18T21:50:13Z

The patch set in general is to add support for the VSC7511, VSC7512,
VSC7513 and VSC7514 devices controlled over SPI. The driver is
believed to be fully functional for the internal phy ports (0-3)
on the VSC7512. As I'll discuss, it is not yet functional for other
ports yet.

V5 is an overhaul of the framework. Instead of struct spi_device being
called directly into drivers/net/dsa/ocelot/ocelot_spi.c, it is now done
through drivers/net/mfd/ocelot-spi.c. The MFD layer is handled in
drivers/net/mfd/ocelot-core.c. At the end of this patch set, three
optional devices are supported: ocelot-pinctrl to control pins,
mdio-mscc-miim to communicate on the external MDIO bus, and
ocelot-ext to manage the switch.

This structure seems to really clean things up. "ocelot_ext" comes in
around 650 lines now, down from 950 in v4. Much of that has moved to
ocelot-spi.c. All references to SPI have been removed from ocelot_ext. I
know lines of a file isn't a great metric of software design quality,
but I think they are related in this case.

My hope at the end of this RFC is to be able to split this into several
smaller patch sets. One set would add MFD + pinctrl, and possibly MDIO.
To the best of my current knowledge those drivers are functionally
complete and possibly ready for full "PATCH" status instead of RFC.

Of note: the MFD uses hard-coded resources, while the MMIO version uses
DTS resources. If nothing else, I'll update the relevant documentation
accordingly.

A separate patch set would be to add switch capabilities. Since the
external phys are now functional, I can turn my focus to those
additional ports.

That leads to a direct question: Is it acceptable to do switch
capabilities in two patch sets? Set 1 would add internal phy
functionality that currently works, set 2 would add PCS / SGMII support
to ports 4-7? For full transparency: I don't have Fib{re,er}
hardware so I don't plan to develop that support. I simply won't be able
to test it.

As I mentioned in the last RFC:
The hardware setup I'm using for development is a beaglebone black, with
jumpers from SPI0 to the microchip VSC7512 dev board. The microchip dev
board has been modified to not boot from flash, but wait for SPI. An
ethernet cable is connected from the beaglebone ethernet to port 0 of
the dev board.

The device tree I'm using for the VSC7512 is below, and has changed in
architecture significantly since V4:

&spi0 {
#address-cells = <1>;
#size-cells = <0>;
status = "okay";

ocelot-chip@0 {
compatible = "mscc,vsc7512_mfd_spi";
spi-max-frequency = <250000>;
reg = <0>;

ethernet-switch@0 {
compatible = "mscc,vsc7512-ext-switch";
ports {
#address-cells = <1>;
#size-cells = <0>;

port@0 {
reg = <0>;
label = "cpu";
status = "okay";
ethernet = <&mac_sw>;
phy-handle = <&sw_phy0>;
phy-mode = "internal";
};

port@1 {
reg = <1>;
label = "swp1";
status = "okay";
phy-handle = <&sw_phy1>;
phy-mode = "internal";
};

port@2 {
reg = <2>;
label = "swp2";
status = "okay";
phy-handle = <&sw_phy2>;
phy-mode = "internal";
};

port@3 {
reg = <3>;
label = "swp3";
status = "okay";
phy-handle = <&sw_phy3>;
phy-mode = "internal";
};

port@4 {
reg = <4>;
label = "swp4";
status = "okay";
phy-handle = <&sw_phy4>;
phy-mode = "sgmii";
};

port@5 {
reg = <5>;
label = "swp5";
status = "okay";
phy-handle = <&sw_phy5>;
phy-mode = "sgmii";
};

port@6 {
reg = <6>;
label = "swp6";
status = "okay";
phy-handle = <&sw_phy6>;
phy-mode = "sgmii";
};

port@7 {
reg = <7>;
label = "swp7";
status = "okay";
phy-handle = <&sw_phy7>;
phy-mode = "sgmii";
};
};

mdio {
#address-cells = <1>;
#size-cells = <0>;

sw_phy0: ethernet-phy@0 {
reg = <0x0>;
};

sw_phy1: ethernet-phy@1 {
reg = <0x1>;
};

sw_phy2: ethernet-phy@2 {
reg = <0x2>;
};

sw_phy3: ethernet-phy@3 {
reg = <0x3>;
};
};
};

mdio1: mdio1 {
compatible = "mscc,ocelot-miim";
pinctrl-names = "default";
pinctrl-0 = <&miim1>;
#address-cells = <1>;
#size-cells = <0>;

sw_phy4: ethernet-phy@4 {
reg = <0x4>;
};

sw_phy5: ethernet-phy@5 {
reg = <0x5>;
};

sw_phy6: ethernet-phy@6 {
reg = <0x6>;
};

sw_phy7: ethernet-phy@7 {
reg = <0x7>;
};
};

gpio: pinctrl@0 {
compatible = "mscc,ocelot-pinctrl";
gpio-controller;
#gpio_cells = <2>;
gpio-ranges = <&gpio 0 0 22>;

led_shift_reg_pins: led-shift-reg-pins {
pins = "GPIO_0", "GPIO_1", "GPIO_2", "GPIO_3";
function = "sg0";
};

miim1: miim1 {
pins = "GPIO_14", "GPIO_15";
function = "miim";
};
};

/* Non-functional at the moment */
sgpio: sgpio {
compatible = "mscc,ocelot-sgpio";
bus-frequency=<12500000>;
clocks = <&ocelot_clock>;
microchip,sgpio-port-ranges = <0 31>;

sgpio_in0: sgpio@0 {
compatible = "microchip,sparx5-sgpio-bank";
reg = <0>;
gpio-controller;
#gpio-cells = <3>;
ngpios = <32>;
};

sgpio_out1: sgpio@1 {
compatible = "microchip,sparx5-sgpio-bank";
reg = <1>;
gpio-controller;
gpio-cells = <3>;
ngpios = <32>;
};
};
};
};

The relevant boot log for the switch / MDIO bus is here. Of note: ports
4-7 are now properly probed before the switch comes up.

[ 1.357533] SPI driver ocelot_mfd_spi has no spi_device_id for mscc,vsc7514_mfd_spi
[ 1.357561] SPI driver ocelot_mfd_spi has no spi_device_id for mscc,vsc7513_mfd_spi
[ 1.357571] SPI driver ocelot_mfd_spi has no spi_device_id for mscc,vsc7512_mfd_spi
[ 1.357581] SPI driver ocelot_mfd_spi has no spi_device_id for mscc,vsc7511_mfd_spi
[ 3.159000] ocelot_mfd_spi spi0.0: configured SPI bus for speed 250000, rx padding bytes 0
[ 3.167549] ocelot_mfd_spi spi0.0: initializing SPI interface for chip
[ 3.175088] ocelot_mfd_spi spi0.0: resetting ocelot chip
[ 3.301006] ocelot_mfd_spi spi0.0: initializing SPI interface for chip
[ 3.309537] pinctrl-ocelot pinctrl-ocelot: DMA mask not set
[ 3.315694] gpiochip_find_base: found new base at 2026
[ 3.315737] gpio gpiochip4: (ocelot-gpio): created GPIO range 0->21 ==> pinctrl-ocelot PIN 0->21
[ 3.321957] gpio gpiochip4: (ocelot-gpio): added GPIO chardev (254:4)
[ 3.322059] gpio gpiochip4: registered GPIOs 2026 to 2047 on ocelot-gpio
[ 3.322076] pinctrl-ocelot pinctrl-ocelot: driver registered
[ 3.331370] mscc-miim ocelot-miim1: DMA mask not set
[ 3.337044] mdio_bus ocelot-miim1-mii: GPIO lookup for consumer reset
[ 3.337065] mdio_bus ocelot-miim1-mii: using device tree for GPIO lookup
[ 3.337088] of_get_named_gpiod_flags: can't parse 'reset-gpios' property of node '/ocp/interconnect@48000000/segment@0/target-module@30000/spi@0/ocelot-chip@0/mdio1[0]'
[ 3.337141] of_get_named_gpiod_flags: can't parse 'reset-gpio' property of node '/ocp/interconnect@48000000/segment@0/target-module@30000/spi@0/ocelot-chip@0/mdio1[0]'
[ 3.337190] mdio_bus ocelot-miim1-mii: using lookup tables for GPIO lookup
[ 3.337202] mdio_bus ocelot-miim1-mii: No GPIO consumer reset found
[ 3.337215] libphy: mscc_miim: probed
[ 3.343764] mdio_bus ocelot-miim1-mii:04: GPIO lookup for consumer reset
[ 3.343788] mdio_bus ocelot-miim1-mii:04: using device tree for GPIO lookup
[ 3.343809] of_get_named_gpiod_flags: can't parse 'reset-gpios' property of node '/ocp/interconnect@48000000/segment@0/target-module@30000/spi@0/ocelot-chip@0/mdio1/ethernet-phy@4[0]'
[ 3.343873] of_get_named_gpiod_flags: can't parse 'reset-gpio' property of node '/ocp/interconnect@48000000/segment@0/target-module@30000/spi@0/ocelot-chip@0/mdio1/ethernet-phy@4[0]'
[ 3.343928] mdio_bus ocelot-miim1-mii:04: using lookup tables for GPIO lookup
[ 3.343939] mdio_bus ocelot-miim1-mii:04: No GPIO consumer reset found
# Repeated for 5, 6 and 7
[ 3.355921] ocelot-ext-switch ocelot-ext-switch: DMA mask not set
[ 3.864119] mdio_bus ocelot-ext-switch-mii: GPIO lookup for consumer reset
[ 3.864151] mdio_bus ocelot-ext-switch-mii: using device tree for GPIO lookup
[ 3.864176] of_get_named_gpiod_flags: can't parse 'reset-gpios' property of node '/ocp/interconnect@48000000/segment@0/target-module@30000/spi@0/ocelot-chip@0/ethernet-switch@0/mdio[0]'
[ 3.864236] of_get_named_gpiod_flags: can't parse 'reset-gpio' property of node '/ocp/interconnect@48000000/segment@0/target-module@30000/spi@0/ocelot-chip@0/ethernet-switch@0/mdio[0]'
[ 3.864291] mdio_bus ocelot-ext-switch-mii: using lookup tables for GPIO lookup
[ 3.864303] mdio_bus ocelot-ext-switch-mii: No GPIO consumer reset found
[ 4.363826] libphy: ocelot_ext MDIO bus: probed
[ 4.371316] mdio_bus ocelot-ext-switch-mii:00: GPIO lookup for consumer reset
[ 4.371340] mdio_bus ocelot-ext-switch-mii:00: using device tree for GPIO lookup
[ 4.371360] of_get_named_gpiod_flags: can't parse 'reset-gpios' property of node '/ocp/interconnect@48000000/segment@0/target-module@30000/spi@0/ocelot-chip@0/ethernet-switch@0/mdio/ethernet-phy@0[0]'
[ 4.371433] of_get_named_gpiod_flags: can't parse 'reset-gpio' property of node '/ocp/interconnect@48000000/segment@0/target-module@30000/spi@0/ocelot-chip@0/ethernet-switch@0/mdio/ethernet-phy@0[0]'
[ 4.371495] mdio_bus ocelot-ext-switch-mii:00: using lookup tables for GPIO lookup
[ 4.371507] mdio_bus ocelot-ext-switch-mii:00: No GPIO consumer reset found
[ 4.374476] mdio_bus ocelot-ext-switch-mii:01: GPIO lookup for consumer reset
[ 4.374504] mdio_bus ocelot-ext-switch-mii:01: using device tree for GPIO lookup
[ 4.374524] of_get_named_gpiod_flags: can't parse 'reset-gpios' property of node '/ocp/interconnect@48000000/segment@0/target-module@30000/spi@0/ocelot-chip@0/ethernet-switch@0/mdio/ethernet-phy@1[0]'
[ 4.374597] of_get_named_gpiod_flags: can't parse 'reset-gpio' property of node '/ocp/interconnect@48000000/segment@0/target-module@30000/spi@0/ocelot-chip@0/ethernet-switch@0/mdio/ethernet-phy@1[0]'
[ 4.374657] mdio_bus ocelot-ext-switch-mii:01: using lookup tables for GPIO lookup
[ 4.374669] mdio_bus ocelot-ext-switch-mii:01: No GPIO consumer reset found
# Repeated for 2 and 3
[ 8.105647] ocelot-ext-switch ocelot-ext-switch: PHY [ocelot-ext-switch-mii:00] driver [Generic PHY] (irq=POLL)
[ 8.119027] ocelot-ext-switch ocelot-ext-switch: configuring for phy/internal link mode
[ 8.145065] ocelot-ext-switch ocelot-ext-switch swp1 (uninitialized): PHY [ocelot-ext-switch-mii:01] driver [Generic PHY] (irq=POLL)
[ 8.163599] ocelot-ext-switch ocelot-ext-switch swp2 (uninitialized): PHY [ocelot-ext-switch-mii:02] driver [Generic PHY] (irq=POLL)
[ 8.182016] ocelot-ext-switch ocelot-ext-switch swp3 (uninitialized): PHY [ocelot-ext-switch-mii:03] driver [Generic PHY] (irq=POLL)
[ 8.200313] ocelot-ext-switch ocelot-ext-switch swp4 (uninitialized): PHY [ocelot-miim1-mii:04] driver [Generic PHY] (irq=POLL)
[ 8.218238] ocelot-ext-switch ocelot-ext-switch swp5 (uninitialized): PHY [ocelot-miim1-mii:05] driver [Generic PHY] (irq=POLL)
[ 8.236204] ocelot-ext-switch ocelot-ext-switch swp6 (uninitialized): PHY [ocelot-miim1-mii:06] driver [Generic PHY] (irq=POLL)
[ 8.254153] ocelot-ext-switch ocelot-ext-switch swp7 (uninitialized): PHY [ocelot-miim1-mii:07] driver [Generic PHY] (irq=POLL)
[ 8.277362] device eth0 entered promiscuous mode
[ 8.282119] DSA: tree 0 setup
[ 8.285250] ocelot_mfd_spi spi0.0: ocelot mfd core setup complete
[ 8.297532] ocelot_mfd_spi spi0.0: ocelot spi mfd probed
[ 10.259154] ocelot-ext-switch ocelot-ext-switch: Link is Up - 100Mbps/Full - flow control off

Then I enable ports 1-3 on a bridge with STP with an intentional loop of
port 2 plugged directly into port 3:

[ 21.040578] cpsw-switch 4a100000.switch: starting ndev. mode: dual_mac
[ 21.133226] SMSC LAN8710/LAN8720 4a101000.mdio:00: attached PHY driver (mii_bus:phy_addr=4a101000.mdio:00, irq=POLL)
[ 21.150341] 8021q: adding VLAN 0 to HW filter on device eth0
[ 21.175435] ocelot-ext-switch ocelot-ext-switch swp1: configuring for phy/internal link mode
[ 21.210804] ocelot-ext-switch ocelot-ext-switch swp2: configuring for phy/internal link mode
[ 21.245110] ocelot-ext-switch ocelot-ext-switch swp3: configuring for phy/internal link mode
[ 21.310462] br0: port 1(swp1) entered blocking state
[ 21.315670] br0: port 1(swp1) entered disabled state
[ 21.334172] device swp1 entered promiscuous mode
[ 21.365554] br0: port 2(swp2) entered blocking state
[ 21.370594] br0: port 2(swp2) entered disabled state
[ 21.388041] device swp2 entered promiscuous mode
[ 21.410549] br0: port 3(swp3) entered blocking state
[ 21.415726] br0: port 3(swp3) entered disabled state
[ 21.432923] device swp3 entered promiscuous mode
[ 21.713724] ocelot-ext-switch ocelot-ext-switch: Link is Down
[ 22.741120] ocelot-ext-switch ocelot-ext-switch: Link is Up - 100Mbps/Full - flow control off
[ 23.382081] cpsw-switch 4a100000.switch eth0: Link is Up - 100Mbps/Full - flow control off
[ 23.392704] IPv6: ADDRCONF(NETDEV_CHANGE): eth0: link becomes ready
[ 24.339517] ocelot-ext-switch ocelot-ext-switch swp2: Link is Up - 1Gbps/Full - flow control rx/tx
[ 24.348688] IPv6: ADDRCONF(NETDEV_CHANGE): swp2: link becomes ready
[ 24.355215] br0: port 2(swp2) entered blocking state
[ 24.360220] br0: port 2(swp2) entered listening state
[ 24.427317] ocelot-ext-switch ocelot-ext-switch swp3: Link is Up - 1Gbps/Full - flow control rx/tx
[ 24.436649] IPv6: ADDRCONF(NETDEV_CHANGE): swp3: link becomes ready
[ 24.443458] br0: port 3(swp3) entered blocking state
[ 24.448507] br0: port 3(swp3) entered listening state
[ 25.463130] ocelot-ext-switch ocelot-ext-switch swp1: Link is Up - 1Gbps/Full - flow control rx/tx
[ 25.472385] IPv6: ADDRCONF(NETDEV_CHANGE): swp1: link becomes ready
[ 25.479062] br0: port 1(swp1) entered blocking state
[ 25.484241] br0: port 1(swp1) entered listening state
[ 26.091585] br0: port 3(swp3) entered blocking state
[ 39.531066] br0: port 2(swp2) entered learning state
[ 40.811057] br0: port 1(swp1) entered learning state
[ 54.891054] br0: port 2(swp2) entered forwarding state
[ 54.896328] br0: topology change detected, propagating
[ 54.906787] IPv6: ADDRCONF(NETDEV_CHANGE): br0: link becomes ready
[ 55.531415] br0: received packet on swp2 with own address as source address (addr:*, vlan:0)
[ 56.171057] br0: port 1(swp1) entered forwarding state
[ 56.176312] br0: topology change detected, propagating
[ 90.091617] br0: received packet on swp2 with own address as source address (addr:*, vlan:0)

(I'm curious about those swp2 packets that trickle through...)

In order to make this work, I have modified the cpsw driver, and now the
cpsw_new driver, to allow for frames over 1500 bytes. Otherwise the
tagging protocol will not work between the beaglebone and the VSC7512. I
plan to eventually try to get those changes in mainline, but I don't
want to get distracted from my initial goal. I also had to change
bonecommon.dtsi to avoid using VLAN 0.

Lastly, this patch set relies on changes that have been merged in from
linux-pinctrl/next. kernel-test-robot will complain that the last two
patches won't compile and might not even apply. The path forward might
be to get the MFD / pinctrl into linux-pinctrl before the next merge
window. I doubt the ocelot-ext will be fully functional by then, though
it does feel close!

RFC history:
v1 (accidentally named vN)
* Initial architecture. Not functional
* General concepts laid out

v2
* Near functional. No CPU port communication, but control over all
external ports
* Cleaned up regmap implementation from v1

v3
* Functional
* Shared MDIO transactions routed through mdio-mscc-miim
* CPU / NPI port enabled by way of vsc7512_enable_npi_port /
felix->info->enable_npi_port
* NPI port tagging functional - Requires a CPU port driver that supports
frames of 1520 bytes. Verified with a patch to the cpsw driver

v4
* Functional
* Device tree fixes
* Add hooks for pinctrl-ocelot - some functionality by way of sysfs
* Add hooks for pinctrl-microsemi-sgpio - not yet fully functional
* Remove lynx_pcs interface for a generic phylink_pcs. The goal here
is to have an ocelot_pcs that will work for each configuration of
every port.

v5
* Restructured to MFD
* Several commits were split out, submitted, and accepted
* pinctrl-ocelot believed to be fully functional (requires commits
from the linux-pinctrl tree)
* External MDIO bus believed to be fully functional

Colin Foster (13):
mfd: ocelot: add support for external mfd control over SPI for the
VSC7512
mfd: ocelot: offer an interface for MFD children to get regmaps
net: mscc: ocelot: expose ocelot wm functions
net: dsa: felix: add configurable device quirks
net: mdio: mscc-miim: add ability to externally register phy reset
control
net: dsa: ocelot: add external ocelot switch control
mfd: ocelot: enable the external switch interface
mfd: add interface to check whether a device is mfd
net: mdio: mscc-miim: add local dev variable to cleanup probe function
net: mdio: mscc-miim: add MFD functionality through ocelot-core
mfd: ocelot-core: add control for the external mdio interface
pinctrl: ocelot: add MFD functionality through ocelot-core
mfd: ocelot: add ocelot-pinctrl as a supported child interface

drivers/mfd/Kconfig | 15 +
drivers/mfd/Makefile | 3 +
drivers/mfd/mfd-core.c | 5 +
drivers/mfd/ocelot-core.c | 203 +++++++
drivers/mfd/ocelot-mfd.h | 19 +
drivers/mfd/ocelot-spi.c | 374 ++++++++++++
drivers/net/dsa/ocelot/Kconfig | 15 +
drivers/net/dsa/ocelot/Makefile | 5 +
drivers/net/dsa/ocelot/felix.c | 7 +-
drivers/net/dsa/ocelot/felix.h | 1 +
drivers/net/dsa/ocelot/felix_vsc9959.c | 1 +
drivers/net/dsa/ocelot/ocelot_ext.c | 644 +++++++++++++++++++++
drivers/net/dsa/ocelot/seville_vsc9953.c | 4 +-
drivers/net/ethernet/mscc/ocelot_devlink.c | 31 +
drivers/net/ethernet/mscc/ocelot_vsc7514.c | 28 -
drivers/net/mdio/mdio-mscc-miim.c | 50 +-
drivers/pinctrl/pinctrl-ocelot.c | 30 +-
include/linux/mdio/mdio-mscc-miim.h | 3 +-
include/linux/mfd/core.h | 10 +
include/soc/mscc/ocelot.h | 19 +
20 files changed, 1410 insertions(+), 57 deletions(-)
create mode 100644 drivers/mfd/ocelot-core.c
create mode 100644 drivers/mfd/ocelot-mfd.h
create mode 100644 drivers/mfd/ocelot-spi.c
create mode 100644 drivers/net/dsa/ocelot/ocelot_ext.c

--
2.25.1