Hi, Many DSA switches (drivers/net/dsa/) are in fact complex SoCs with more embedded peripherals than just the Ethernet switching IP. For example, I was trying to add interrupt support for the internal PHYs of the NXP SJA1110 switch. For context, one SJA1110 switch as seen in fsl-lx2160a-bluebox3.dts currently has bindings which look like this: sw2: ethernet-switch@2 { compatible = "nxp,sja1110a"; reg = <2>; spi-max-frequency = <4000000>; spi-cpol; dsa,member = <0 1>; ethernet-ports { #address-cells = <1>; #size-cells = <0>; /* Microcontroller port */ port@0 { reg = <0>; status = "disabled"; }; sw2p1: port@1 { reg = <1>; link = <&sw1p4>; phy-mode = "sgmii"; fixed-link { speed = <1000>; full-duplex; }; }; port@2 { reg = <2>; ethernet = <&dpmac18>; phy-mode = "rgmii-id"; rx-internal-delay-ps = <2000>; tx-internal-delay-ps = <2000>; fixed-link { speed = <1000>; full-duplex; }; }; port@3 { reg = <3>; label = "1ge_p2"; phy-mode = "rgmii-id"; phy-handle = <&sw2_mii3_phy>; }; port@4 { reg = <4>; label = "to_sw3"; phy-mode = "2500base-x"; fixed-link { speed = <2500>; full-duplex; }; }; port@5 { reg = <5>; label = "trx7"; phy-mode = "internal"; phy-handle = <&sw2_port5_base_t1_phy>; }; port@6 { reg = <6>; label = "trx8"; phy-mode = "internal"; phy-handle = <&sw2_port6_base_t1_phy>; }; port@7 { reg = <7>; label = "trx9"; phy-mode = "internal"; phy-handle = <&sw2_port7_base_t1_phy>; }; port@8 { reg = <8>; label = "trx10"; phy-mode = "internal"; phy-handle = <&sw2_port8_base_t1_phy>; }; port@9 { reg = <9>; label = "trx11"; phy-mode = "internal"; phy-handle = <&sw2_port9_base_t1_phy>; }; port@a { reg = <10>; label = "trx12"; phy-mode = "internal"; phy-handle = <&sw2_port10_base_t1_phy>; }; }; mdios { #address-cells = <1>; #size-cells = <0>; mdio@0 { compatible = "nxp,sja1110-base-t1-mdio"; #address-cells = <1>; #size-cells = <0>; reg = <0>; sw2_port5_base_t1_phy: ethernet-phy@1 { compatible = "ethernet-phy-ieee802.3-c45"; reg = <0x1>; }; sw2_port6_base_t1_phy: ethernet-phy@2 { compatible = "ethernet-phy-ieee802.3-c45"; reg = <0x2>; }; sw2_port7_base_t1_phy: ethernet-phy@3 { compatible = "ethernet-phy-ieee802.3-c45"; reg = <0x3>; }; sw2_port8_base_t1_phy: ethernet-phy@4 { compatible = "ethernet-phy-ieee802.3-c45"; reg = <0x4>; }; sw2_port9_base_t1_phy: ethernet-phy@5 { compatible = "ethernet-phy-ieee802.3-c45"; reg = <0x5>; }; sw2_port10_base_t1_phy: ethernet-phy@6 { compatible = "ethernet-phy-ieee802.3-c45"; reg = <0x6>; }; }; }; }; To add interrupts in a naive way, similar to how other DSA drivers have done it, it would have to be done like this: #include <dt-bindings/interrupt-controller/nxp-sja1110-acu-slir.h> sw2: ethernet-switch@2 { compatible = "nxp,sja1110a"; reg = <2>; spi-max-frequency = <4000000>; spi-cpol; dsa,member = <0 1>; slir2: interrupt-controller { compatible = "nxp,sja1110-acu-slir"; interrupt-controller; #interrupt-cells = <1>; interrupt-parent = <&gpio 10>; }; ethernet-ports { #address-cells = <1>; #size-cells = <0>; /* Microcontroller port */ port@0 { reg = <0>; status = "disabled"; }; sw2p1: port@1 { reg = <1>; link = <&sw1p4>; phy-mode = "sgmii"; fixed-link { speed = <1000>; full-duplex; }; }; port@2 { reg = <2>; ethernet = <&dpmac18>; phy-mode = "rgmii-id"; rx-internal-delay-ps = <2000>; tx-internal-delay-ps = <2000>; fixed-link { speed = <1000>; full-duplex; }; }; port@3 { reg = <3>; label = "1ge_p2"; phy-mode = "rgmii-id"; phy-handle = <&sw2_mii3_phy>; }; port@4 { reg = <4>; label = "to_sw3"; phy-mode = "2500base-x"; fixed-link { speed = <2500>; full-duplex; }; }; port@5 { reg = <5>; label = "trx7"; phy-mode = "internal"; phy-handle = <&sw2_port5_base_t1_phy>; }; port@6 { reg = <6>; label = "trx8"; phy-mode = "internal"; phy-handle = <&sw2_port6_base_t1_phy>; }; port@7 { reg = <7>; label = "trx9"; phy-mode = "internal"; phy-handle = <&sw2_port7_base_t1_phy>; }; port@8 { reg = <8>; label = "trx10"; phy-mode = "internal"; phy-handle = <&sw2_port8_base_t1_phy>; }; port@9 { reg = <9>; label = "trx11"; phy-mode = "internal"; phy-handle = <&sw2_port9_base_t1_phy>; }; port@a { reg = <10>; label = "trx12"; phy-mode = "internal"; phy-handle = <&sw2_port10_base_t1_phy>; }; }; mdios { #address-cells = <1>; #size-cells = <0>; mdio@0 { compatible = "nxp,sja1110-base-t1-mdio"; #address-cells = <1>; #size-cells = <0>; reg = <0>; sw2_port5_base_t1_phy: ethernet-phy@1 { compatible = "ethernet-phy-ieee802.3-c45"; reg = <0x1>; interrupts-extended = <&slir2 SJA1110_IRQ_CBT1_PHY1>; }; sw2_port6_base_t1_phy: ethernet-phy@2 { compatible = "ethernet-phy-ieee802.3-c45"; reg = <0x2>; interrupts-extended = <&slir2 SJA1110_IRQ_CBT1_PHY2>; }; sw2_port7_base_t1_phy: ethernet-phy@3 { compatible = "ethernet-phy-ieee802.3-c45"; reg = <0x3>; interrupts-extended = <&slir2 SJA1110_IRQ_CBT1_PHY3>; }; sw2_port8_base_t1_phy: ethernet-phy@4 { compatible = "ethernet-phy-ieee802.3-c45"; reg = <0x4>; interrupts-extended = <&slir2 SJA1110_IRQ_CBT1_PHY4>; }; sw2_port9_base_t1_phy: ethernet-phy@5 { compatible = "ethernet-phy-ieee802.3-c45"; reg = <0x5>; interrupts-extended = <&slir2 SJA1110_IRQ_CBT1_PHY5>; }; sw2_port10_base_t1_phy: ethernet-phy@6 { compatible = "ethernet-phy-ieee802.3-c45"; reg = <0x6>; interrupts-extended = <&slir2 SJA1110_IRQ_CBT1_PHY6>; }; }; }; }; However, the irq_domain/irqchip handling code in this case will go to drivers/net/dsa/, and it won't really be a "driver" (there is no struct device of its own). I don't really like that, the drivers/net/dsa/ folder should ideally contain only drivers for the switching IP. It doesn't scale to find here code for GPIO, interrupts, MDIO, hwmon and what have you; not only because the folder gets bloated with irrelevant stuff, but also because DSA maintainers are not the best reviewers of drivers which really belong to (and make use of infrastructure from) other subsystems. Not only that, but "interrupt-controller" cannot even have a unit address with these bindings, because it's on the same level as "ethernet-ports" which requires not having it. But there are multiple interrupt controllers in the SJA1110 block (I could count 3). Not clear how the other 3 would be defined in the device tree in this format. The logical continuation of the existing bindings would be to do what was done for the multiple MDIO controllers: an "interrupt-controllers" container node, with children with #address-cells = <1>. I think that doesn't scale very well either, so I was looking into transitioning the sja1105 bindings to something similar to what Colin Foster has done with vsc7512 (ocelot). For this switch, new-style bindings would look like this: soc@2 { compatible = "nxp,sja1110-soc"; reg = <2>; spi-max-frequency = <4000000>; spi-cpol; #address-cells = <1>; #size-cells = <1>; sw2: ethernet-switch@0 { compatible = "nxp,sja1110a"; reg = <0x000000 0x400000>; resets = <&sw2_rgu SJA1110_RGU_ETHSW_RST>; dsa,member = <0 1>; ethernet-ports { #address-cells = <1>; #size-cells = <0>; /* Microcontroller port */ port@0 { reg = <0>; status = "disabled"; }; sw2p1: port@1 { reg = <1>; link = <&sw1p4>; phy-mode = "sgmii"; fixed-link { speed = <1000>; full-duplex; }; }; port@2 { reg = <2>; ethernet = <&dpmac18>; phy-mode = "rgmii-id"; rx-internal-delay-ps = <2000>; tx-internal-delay-ps = <2000>; fixed-link { speed = <1000>; full-duplex; }; }; port@3 { reg = <3>; label = "1ge_p2"; phy-mode = "rgmii-id"; phy-handle = <&sw2_mii3_phy>; }; port@4 { reg = <4>; label = "to_sw3"; phy-mode = "2500base-x"; fixed-link { speed = <2500>; full-duplex; }; }; port@5 { reg = <5>; label = "trx7"; phy-mode = "internal"; phy-handle = <&sw2_port5_base_t1_phy>; }; port@6 { reg = <6>; label = "trx8"; phy-mode = "internal"; phy-handle = <&sw2_port6_base_t1_phy>; }; port@7 { reg = <7>; label = "trx9"; phy-mode = "internal"; phy-handle = <&sw2_port7_base_t1_phy>; }; port@8 { reg = <8>; label = "trx10"; phy-mode = "internal"; phy-handle = <&sw2_port8_base_t1_phy>; }; port@9 { reg = <9>; label = "trx11"; phy-mode = "internal"; phy-handle = <&sw2_port9_base_t1_phy>; }; port@a { reg = <10>; label = "trx12"; phy-mode = "internal"; phy-handle = <&sw2_port10_base_t1_phy>; }; }; }; mdio@704000 { compatible = "nxp,sja1110-base-t1-mdio"; #address-cells = <1>; #size-cells = <0>; reg = <0x704000 0x1000>; sw2_port5_base_t1_phy: ethernet-phy@1 { compatible = "ethernet-phy-ieee802.3-c45"; reg = <0x1>; interrupts-extended = <&slir2 SJA1110_IRQ_CBT1_PHY1>; }; sw2_port6_base_t1_phy: ethernet-phy@2 { compatible = "ethernet-phy-ieee802.3-c45"; reg = <0x2>; interrupts-extended = <&slir2 SJA1110_IRQ_CBT1_PHY2>; }; sw2_port7_base_t1_phy: ethernet-phy@3 { compatible = "ethernet-phy-ieee802.3-c45"; reg = <0x3>; interrupts-extended = <&slir2 SJA1110_IRQ_CBT1_PHY3>; }; sw2_port8_base_t1_phy: ethernet-phy@4 { compatible = "ethernet-phy-ieee802.3-c45"; reg = <0x4>; interrupts-extended = <&slir2 SJA1110_IRQ_CBT1_PHY4>; }; sw2_port9_base_t1_phy: ethernet-phy@5 { compatible = "ethernet-phy-ieee802.3-c45"; reg = <0x5>; interrupts-extended = <&slir2 SJA1110_IRQ_CBT1_PHY5>; }; sw2_port10_base_t1_phy: ethernet-phy@6 { compatible = "ethernet-phy-ieee802.3-c45"; reg = <0x6>; interrupts-extended = <&slir2 SJA1110_IRQ_CBT1_PHY6>; }; }; mdio@709000 { compatible = "nxp,sja1110-base-tx-mdio"; #address-cells = <1>; #size-cells = <0>; reg = <0x709000 0x1000>; }; slir2: interrupt-controller@711fe0 { compatible = "nxp,sja1110-acu-slir"; reg = <0x711fe0 0x10>; interrupt-controller; #interrupt-cells = <1>; interrupt-parent = <&gpio 10>; }; gpio@712000 { compatible = "nxp,sja1110-gpio"; reg = <0x712000 0x1000>; gpio-controller; }; slir2_gpio: interrupt-controller@712fe0 { compatible = "nxp,sja1110-gpio-slir"; reg = <0x712fe0 0x10>; interrupt-controller; #interrupt-cells = <1>; }; sw2_rgu: reset@718000 { compatible = "nxp,sja1110-rgu"; reg = <0x718000 0x1000>; #reset-cells = <1>; }; sw2_cgu: clock-controller@719000 { compatible = "nxp,sja1110-cgu"; reg = <0x719000 0x1000>; #clock-cells = <1>; }; slir2_pmu: interrupt-controller@71afe0 { compatible = "nxp,sja1110-pmu-slir"; reg = <0x71afe0 0x10>; interrupt-controller; #interrupt-cells = <1>; }; }; In this model, the Ethernet switch is just one of the children of the SoC driver (the one owning the spi_device, and creating regmaps for its children). The DSA driver doesn't have to concern itself with the other drivers, which can live in their own folders. It seems like mfd is a tool which could help with the driver for "nxp,sja1110-soc", but mfd_add_devices() requires an array of struct mfd_cell, and I don't really like that. I want a driver which just creates IORESOURCE_REG arrays of resources for each child OF node according to their "reg" properties, creates regmaps for each resource using a regmap_config that I give it, associates the regmaps with the parent using devres, and allocates/probes a platform device driver for each of these child OF nodes. I don't want to spell out a different mfd_cell for each driver that I'd like the SoC to probe. It looks like of_platform_populate() would be an alternative option for this task, but that doesn't live up to the task either. It will assume that the addresses of the SoC children are in the CPU's address space (IORESOURCE_MEM), and attempt to translate them. It simply doesn't have the concept of IORESOURCE_REG. The MFD drivers which call of_platform_populate() (simple-mfd-i2c.c) simply don't have unit addresses for their children, and this is why address translation isn't a problem for them. In fact, this seems to be a rather large limitation of include/linux/of_address.h. Even something as simple as of_address_count() will end up trying to translate the address into the CPU memory space, so not even open-coding the resource creation in the SoC driver is as simple as it appears. Is there a better way than completely open-coding the parsing of the OF addresses when turning them into IORESOURCE_REG resources (or open-coding mfd_cells for each child)? Would there be a desire in creating a generic set of helpers which create platform devices with IORESOURCE_REG resources, based solely on OF addresses of children? What would be the correct scope for these helpers?