The patch set in general is to add support for the VSC7512, and
eventually the VSC7511, 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. It is not yet functional for SGMII,
QSGMII, and SerDes ports.
I have mentioned previously:
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 relevant sections of the device tree I'm using for the VSC7512 is
below. Notably the SGPIO LEDs follow link status and speed from network
triggers.
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, there are things that I have not changed. v6 still uses
device_is_mfd to reason about whether it should use
devm_ioremap_resource or ocelot_get_regmap_from_resource. If necessary,
a different compatible string could be used. That would create some code
duplication, but if device_is_mfd is not desired I completely
understand.
I also still use ocelot_get_regmap_from_resource, and it has been
squashed into the main MFD addition commit. My initial thoughts of it
being able to be done through dev / MFD were probably over-complicating
things.
/ {
model = "TI AM335x BeagleBone Black";
compatible = "ti,am335x-bone-black", "ti,am335x-bone", "ti,am33xx";
// 125 MHz clock on dev board
ocelot_clock: ocelot-clock {
compatible = "fixed-clock";
#clock-cells = <0>;
clock-frequency = <125000000>;
};
vscleds {
compatible = "gpio-leds";
vscled@0 {
label = "port0led";
gpios = <&sgpio_out1 0 0 GPIO_ACTIVE_LOW>;
default-state = "off";
linux,default-trigger = "ocelot-ext-switch-mii:00:link";
};
vscled@1 {
label = "port0led1";
gpios = <&sgpio_out1 0 1 GPIO_ACTIVE_LOW>;
default-state = "off";
linux,default-trigger = "ocelot-ext-switch-mii:00:1Gbps";
};
vscled@10 {
label = "port1led";
gpios = <&sgpio_out1 1 0 GPIO_ACTIVE_LOW>;
default-state = "off";
linux,default-trigger = "ocelot-ext-switch-mii:01:link";
};
vscled@11 {
label = "port1led1";
gpios = <&sgpio_out1 1 1 GPIO_ACTIVE_LOW>;
default-state = "off";
linux,default-trigger = "ocelot-ext-switch-mii:01:1Gbps";
};
vscled@20 {
label = "port2led";
gpios = <&sgpio_out1 2 0 GPIO_ACTIVE_LOW>;
default-state = "off";
linux,default-trigger = "ocelot-ext-switch-mii:02:link";
};
vscled@21 {
label = "port2led1";
gpios = <&sgpio_out1 2 1 GPIO_ACTIVE_LOW>;
default-state = "off";
linux,default-trigger = "ocelot-ext-switch-mii:02:1Gbps";
};
vscled@30 {
label = "port3led";
gpios = <&sgpio_out1 3 0 GPIO_ACTIVE_LOW>;
default-state = "off";
linux,default-trigger = "ocelot-ext-switch-mii:03:link";
};
vscled@31 {
label = "port3led1";
gpios = <&sgpio_out1 3 1 GPIO_ACTIVE_LOW>;
default-state = "off";
linux,default-trigger = "ocelot-ext-switch-mii:03:1Gbps";
};
vscled@40 {
label = "port4led";
gpios = <&sgpio_out1 4 0 GPIO_ACTIVE_LOW>;
default-state = "off";
linux,default-trigger = "ocelot-miim1-mii:04:link";
};
vscled@41 {
label = "port4led1";
gpios = <&sgpio_out1 4 1 GPIO_ACTIVE_LOW>;
default-state = "off";
linux,default-trigger = "ocelot-miim1-mii:04:1Gbps";
};
vscled@50 {
label = "port5led";
gpios = <&sgpio_out1 5 0 GPIO_ACTIVE_LOW>;
default-state = "off";
linux,default-trigger = "ocelot-miim1-mii:05:link";
};
vscled@51 {
label = "port5led1";
gpios = <&sgpio_out1 5 1 GPIO_ACTIVE_LOW>;
default-state = "off";
linux,default-trigger = "ocelot-miim1-mii:05:1Gbps";
};
vscled@60 {
label = "port6led";
gpios = <&sgpio_out1 6 0 GPIO_ACTIVE_LOW>;
default-state = "off";
linux,default-trigger = "ocelot-miim1-mii:06:link";
};
vscled@61 {
label = "port6led1";
gpios = <&sgpio_out1 6 1 GPIO_ACTIVE_LOW>;
default-state = "off";
linux,default-trigger = "ocelot-miim1-mii:06:1Gbps";
};
vscled@70 {
label = "port7led";
gpios = <&sgpio_out1 7 0 GPIO_ACTIVE_LOW>;
default-state = "off";
linux,default-trigger = "ocelot-miim1-mii:07:link";
};
vscled@71 {
label = "port7led1";
gpios = <&sgpio_out1 7 1 GPIO_ACTIVE_LOW>;
default-state = "off";
linux,default-trigger = "ocelot-miim1-mii:07:1Gbps";
};
};
};
&spi0 {
#address-cells = <1>;
#size-cells = <0>;
status = "okay";
ocelot-chip@0 {
compatible = "mscc,vsc7512_mfd_spi";
spi-max-frequency = <2500000>;
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 = "qsgmii";
phys = <&serdes 4 SERDES6G(0)>;
};
port@5 {
reg = <5>;
label = "swp5";
status = "okay";
phy-handle = <&sw_phy5>;
phy-mode = "qsgmii";
phys = <&serdes 5 SERDES6G(0)>;
};
port@6 {
reg = <6>;
label = "swp6";
status = "okay";
phy-handle = <&sw_phy6>;
phy-mode = "qsgmii";
phys = <&serdes 6 SERDES6G(0)>;
};
port@7 {
reg = <7>;
label = "swp7";
status = "okay";
phy-handle = <&sw_phy7>;
phy-mode = "qsgmii";
phys = <&serdes 7 SERDES6G(0)>;
};
};
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";
};
};
sgpio: sgpio {
compatible = "mscc,ocelot-sgpio";
#address-cells = <1>;
#size-cells = <0>;
bus-frequency=<12500000>;
clocks = <&ocelot_clock>;
microchip,sgpio-port-ranges = <0 15>;
pinctrl-names = "default";
pinctrl-0 = <&led_shift_reg_pins>;
sgpio_in0: sgpio@0 {
compatible = "microchip,sparx5-sgpio-bank";
reg = <0>;
gpio-controller;
#gpio-cells = <3>;
ngpios = <64>;
};
sgpio_out1: sgpio@1 {
compatible = "microchip,sparx5-sgpio-bank";
reg = <1>;
gpio-controller;
#gpio-cells = <3>;
ngpios = <64>;
};
};
};
};
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
v6
* Applied several suggestions from the last RFC from Lee Jones. I
hope I didn't miss anything.
* Clean up MFD core - SPI interaction. They no longer use callbacks.
* regmaps get registered to the child device, and don't attempt to
get shared. It seems if a regmap is to be shared, that should be
solved with syscon, not dev or mfd.
Colin Foster (9):
pinctrl: ocelot: allow pinctrl-ocelot to be loaded as a module
pinctrl: microchip-sgpio: allow sgpio driver to be used as a module
net: mdio: mscc-miim: add local dev variable to cleanup probe function
net: mdio: mscc-miim: add ability to externally register phy reset
control
mfd: add interface to check whether a device is mfd
mfd: ocelot: add support for external mfd control over SPI for the
VSC7512
net: mscc: ocelot: expose ocelot wm functions
net: dsa: felix: add configurable device quirks
net: dsa: ocelot: add external ocelot switch control
drivers/mfd/Kconfig | 19 +
drivers/mfd/Makefile | 3 +
drivers/mfd/mfd-core.c | 6 +
drivers/mfd/ocelot-core.c | 169 +++++
drivers/mfd/ocelot-spi.c | 325 ++++++++++
drivers/mfd/ocelot.h | 36 ++
drivers/net/dsa/ocelot/Kconfig | 14 +
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 | 681 +++++++++++++++++++++
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 | 49 +-
drivers/pinctrl/Kconfig | 4 +-
drivers/pinctrl/pinctrl-microchip-sgpio.c | 26 +-
drivers/pinctrl/pinctrl-ocelot.c | 33 +-
include/linux/mdio/mdio-mscc-miim.h | 3 +-
include/linux/mfd/core.h | 10 +
include/soc/mscc/ocelot.h | 18 +
22 files changed, 1409 insertions(+), 64 deletions(-)
create mode 100644 drivers/mfd/ocelot-core.c
create mode 100644 drivers/mfd/ocelot-spi.c
create mode 100644 drivers/mfd/ocelot.h
create mode 100644 drivers/net/dsa/ocelot/ocelot_ext.c
--
2.25.1
Create a local device *dev in order to not dereference the platform_device
several times throughout the probe function.
Signed-off-by: Colin Foster <[email protected]>
---
drivers/net/mdio/mdio-mscc-miim.c | 24 ++++++++++++------------
1 file changed, 12 insertions(+), 12 deletions(-)
diff --git a/drivers/net/mdio/mdio-mscc-miim.c b/drivers/net/mdio/mdio-mscc-miim.c
index 7d2abaf2b2c9..6b14f3cf3891 100644
--- a/drivers/net/mdio/mdio-mscc-miim.c
+++ b/drivers/net/mdio/mdio-mscc-miim.c
@@ -220,6 +220,7 @@ EXPORT_SYMBOL(mscc_miim_setup);
static int mscc_miim_probe(struct platform_device *pdev)
{
struct regmap *mii_regmap, *phy_regmap = NULL;
+ struct device *dev = &pdev->dev;
void __iomem *regs, *phy_regs;
struct mscc_miim_dev *miim;
struct resource *res;
@@ -228,38 +229,37 @@ static int mscc_miim_probe(struct platform_device *pdev)
regs = devm_platform_get_and_ioremap_resource(pdev, 0, NULL);
if (IS_ERR(regs)) {
- dev_err(&pdev->dev, "Unable to map MIIM registers\n");
+ dev_err(dev, "Unable to map MIIM registers\n");
return PTR_ERR(regs);
}
- mii_regmap = devm_regmap_init_mmio(&pdev->dev, regs,
- &mscc_miim_regmap_config);
+ mii_regmap = devm_regmap_init_mmio(dev, regs, &mscc_miim_regmap_config);
if (IS_ERR(mii_regmap)) {
- dev_err(&pdev->dev, "Unable to create MIIM regmap\n");
+ dev_err(dev, "Unable to create MIIM regmap\n");
return PTR_ERR(mii_regmap);
}
/* This resource is optional */
res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
if (res) {
- phy_regs = devm_ioremap_resource(&pdev->dev, res);
+ phy_regs = devm_ioremap_resource(dev, res);
if (IS_ERR(phy_regs)) {
- dev_err(&pdev->dev, "Unable to map internal phy registers\n");
+ dev_err(dev, "Unable to map internal phy registers\n");
return PTR_ERR(phy_regs);
}
- phy_regmap = devm_regmap_init_mmio(&pdev->dev, phy_regs,
+ phy_regmap = devm_regmap_init_mmio(dev, phy_regs,
&mscc_miim_regmap_config);
if (IS_ERR(phy_regmap)) {
- dev_err(&pdev->dev, "Unable to create phy register regmap\n");
+ dev_err(dev, "Unable to create phy register regmap\n");
return PTR_ERR(phy_regmap);
}
}
- ret = mscc_miim_setup(&pdev->dev, &bus, "mscc_miim", mii_regmap, 0);
+ ret = mscc_miim_setup(dev, &bus, "mscc_miim", mii_regmap, 0);
if (ret < 0) {
- dev_err(&pdev->dev, "Unable to setup the MDIO bus\n");
+ dev_err(dev, "Unable to setup the MDIO bus\n");
return ret;
}
@@ -267,9 +267,9 @@ static int mscc_miim_probe(struct platform_device *pdev)
miim->phy_regs = phy_regmap;
miim->phy_reset_offset = 0;
- ret = of_mdiobus_register(bus, pdev->dev.of_node);
+ ret = of_mdiobus_register(bus, dev->of_node);
if (ret < 0) {
- dev_err(&pdev->dev, "Cannot register MDIO bus (%d)\n", ret);
+ dev_err(dev, "Cannot register MDIO bus (%d)\n", ret);
return ret;
}
--
2.25.1
As the commit message suggests, this simply adds the ability to select
SGPIO pinctrl as a module. This becomes more practical when the SGPIO
hardware exists on an external chip, controlled indirectly by I2C or SPI.
This commit enables that level of control.
Signed-off-by: Colin Foster <[email protected]>
---
drivers/pinctrl/Kconfig | 2 +-
drivers/pinctrl/pinctrl-microchip-sgpio.c | 4 ++++
2 files changed, 5 insertions(+), 1 deletion(-)
diff --git a/drivers/pinctrl/Kconfig b/drivers/pinctrl/Kconfig
index 1b367f423ceb..7ff00c560775 100644
--- a/drivers/pinctrl/Kconfig
+++ b/drivers/pinctrl/Kconfig
@@ -292,7 +292,7 @@ config PINCTRL_MCP23S08
corresponding interrupt-controller.
config PINCTRL_MICROCHIP_SGPIO
- bool "Pinctrl driver for Microsemi/Microchip Serial GPIO"
+ tristate "Pinctrl driver for Microsemi/Microchip Serial GPIO"
depends on OF
depends on HAS_IOMEM
select GPIOLIB
diff --git a/drivers/pinctrl/pinctrl-microchip-sgpio.c b/drivers/pinctrl/pinctrl-microchip-sgpio.c
index 8e081c90bdb2..8db3caf15cf2 100644
--- a/drivers/pinctrl/pinctrl-microchip-sgpio.c
+++ b/drivers/pinctrl/pinctrl-microchip-sgpio.c
@@ -912,6 +912,7 @@ static const struct of_device_id microchip_sgpio_gpio_of_match[] = {
/* sentinel */
}
};
+MODULE_DEVICE_TABLE(of, microchip_sgpio_gpio_of_match);
static struct platform_driver microchip_sgpio_pinctrl_driver = {
.driver = {
@@ -922,3 +923,6 @@ static struct platform_driver microchip_sgpio_pinctrl_driver = {
.probe = microchip_sgpio_probe,
};
builtin_platform_driver(microchip_sgpio_pinctrl_driver);
+
+MODULE_DESCRIPTION("Microchip SGPIO Pinctrl Driver");
+MODULE_LICENSE("GPL v2");
--
2.25.1
Expose ocelot_wm functions so they can be shared with other drivers.
Signed-off-by: Colin Foster <[email protected]>
Reviewed-by: Vladimir Oltean <[email protected]>
---
drivers/net/ethernet/mscc/ocelot_devlink.c | 31 ++++++++++++++++++++++
drivers/net/ethernet/mscc/ocelot_vsc7514.c | 28 -------------------
include/soc/mscc/ocelot.h | 5 ++++
3 files changed, 36 insertions(+), 28 deletions(-)
diff --git a/drivers/net/ethernet/mscc/ocelot_devlink.c b/drivers/net/ethernet/mscc/ocelot_devlink.c
index b8737efd2a85..d9ea75a14f2f 100644
--- a/drivers/net/ethernet/mscc/ocelot_devlink.c
+++ b/drivers/net/ethernet/mscc/ocelot_devlink.c
@@ -487,6 +487,37 @@ static void ocelot_watermark_init(struct ocelot *ocelot)
ocelot_setup_sharing_watermarks(ocelot);
}
+/* Watermark encode
+ * Bit 8: Unit; 0:1, 1:16
+ * Bit 7-0: Value to be multiplied with unit
+ */
+u16 ocelot_wm_enc(u16 value)
+{
+ WARN_ON(value >= 16 * BIT(8));
+
+ if (value >= BIT(8))
+ return BIT(8) | (value / 16);
+
+ return value;
+}
+EXPORT_SYMBOL(ocelot_wm_enc);
+
+u16 ocelot_wm_dec(u16 wm)
+{
+ if (wm & BIT(8))
+ return (wm & GENMASK(7, 0)) * 16;
+
+ return wm;
+}
+EXPORT_SYMBOL(ocelot_wm_dec);
+
+void ocelot_wm_stat(u32 val, u32 *inuse, u32 *maxuse)
+{
+ *inuse = (val & GENMASK(23, 12)) >> 12;
+ *maxuse = val & GENMASK(11, 0);
+}
+EXPORT_SYMBOL(ocelot_wm_stat);
+
/* Pool size and type are fixed up at runtime. Keeping this structure to
* look up the cell size multipliers.
*/
diff --git a/drivers/net/ethernet/mscc/ocelot_vsc7514.c b/drivers/net/ethernet/mscc/ocelot_vsc7514.c
index 4f4a495a60ad..5e526545ef67 100644
--- a/drivers/net/ethernet/mscc/ocelot_vsc7514.c
+++ b/drivers/net/ethernet/mscc/ocelot_vsc7514.c
@@ -307,34 +307,6 @@ static int ocelot_reset(struct ocelot *ocelot)
return 0;
}
-/* Watermark encode
- * Bit 8: Unit; 0:1, 1:16
- * Bit 7-0: Value to be multiplied with unit
- */
-static u16 ocelot_wm_enc(u16 value)
-{
- WARN_ON(value >= 16 * BIT(8));
-
- if (value >= BIT(8))
- return BIT(8) | (value / 16);
-
- return value;
-}
-
-static u16 ocelot_wm_dec(u16 wm)
-{
- if (wm & BIT(8))
- return (wm & GENMASK(7, 0)) * 16;
-
- return wm;
-}
-
-static void ocelot_wm_stat(u32 val, u32 *inuse, u32 *maxuse)
-{
- *inuse = (val & GENMASK(23, 12)) >> 12;
- *maxuse = val & GENMASK(11, 0);
-}
-
static const struct ocelot_ops ocelot_ops = {
.reset = ocelot_reset,
.wm_enc = ocelot_wm_enc,
diff --git a/include/soc/mscc/ocelot.h b/include/soc/mscc/ocelot.h
index 70fae9c8b649..8b8ebede5a01 100644
--- a/include/soc/mscc/ocelot.h
+++ b/include/soc/mscc/ocelot.h
@@ -812,6 +812,11 @@ void ocelot_deinit(struct ocelot *ocelot);
void ocelot_init_port(struct ocelot *ocelot, int port);
void ocelot_deinit_port(struct ocelot *ocelot, int port);
+/* Watermark interface */
+u16 ocelot_wm_enc(u16 value);
+u16 ocelot_wm_dec(u16 wm);
+void ocelot_wm_stat(u32 val, u32 *inuse, u32 *maxuse);
+
/* DSA callbacks */
void ocelot_get_strings(struct ocelot *ocelot, int port, u32 sset, u8 *data);
void ocelot_get_ethtool_stats(struct ocelot *ocelot, int port, u64 *data);
--
2.25.1
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 <[email protected]>
---
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
@@ -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] = {
+ OCELOT_SPI_PORT_MODE_INTERNAL,
+ 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;
+};
+
+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
+
+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);
+}
+
+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");
+ 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;
+
+ /* 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)
+ 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;
+}
+
+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;
+}
+
+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;
+}
+
+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)
+ 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)
+ 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);
+
+ 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,
+};
+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
The define FELIX_MAC_QUIRKS was used directly in the felix.c shared driver.
Other devices (VSC7512 for example) don't require the same quirks, so they
need to be configured on a per-device basis.
Signed-off-by: Colin Foster <[email protected]>
---
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/seville_vsc9953.c | 1 +
4 files changed, 8 insertions(+), 2 deletions(-)
diff --git a/drivers/net/dsa/ocelot/felix.c b/drivers/net/dsa/ocelot/felix.c
index 9957772201d5..4c086bcf111b 100644
--- a/drivers/net/dsa/ocelot/felix.c
+++ b/drivers/net/dsa/ocelot/felix.c
@@ -850,9 +850,12 @@ static void felix_phylink_mac_link_down(struct dsa_switch *ds, int port,
phy_interface_t interface)
{
struct ocelot *ocelot = ds->priv;
+ struct felix *felix;
+
+ felix = ocelot_to_felix(ocelot);
ocelot_phylink_mac_link_down(ocelot, port, link_an_mode, interface,
- FELIX_MAC_QUIRKS);
+ felix->info->quirks);
}
static void felix_phylink_mac_link_up(struct dsa_switch *ds, int port,
@@ -867,7 +870,7 @@ static void felix_phylink_mac_link_up(struct dsa_switch *ds, int port,
ocelot_phylink_mac_link_up(ocelot, port, phydev, link_an_mode,
interface, speed, duplex, tx_pause, rx_pause,
- FELIX_MAC_QUIRKS);
+ felix->info->quirks);
if (felix->info->port_sched_speed_set)
felix->info->port_sched_speed_set(ocelot, port, speed);
diff --git a/drivers/net/dsa/ocelot/felix.h b/drivers/net/dsa/ocelot/felix.h
index 9395ac119d33..f35894b06ce5 100644
--- a/drivers/net/dsa/ocelot/felix.h
+++ b/drivers/net/dsa/ocelot/felix.h
@@ -26,6 +26,7 @@ struct felix_info {
u16 vcap_pol_base2;
u16 vcap_pol_max2;
const struct ptp_clock_info *ptp_caps;
+ u32 quirks;
/* Some Ocelot switches are integrated into the SoC without the
* extraction IRQ line connected to the ARM GIC. By enabling this
diff --git a/drivers/net/dsa/ocelot/felix_vsc9959.c b/drivers/net/dsa/ocelot/felix_vsc9959.c
index bf8d38239e7e..7e88480cc103 100644
--- a/drivers/net/dsa/ocelot/felix_vsc9959.c
+++ b/drivers/net/dsa/ocelot/felix_vsc9959.c
@@ -2231,6 +2231,7 @@ static const struct felix_info felix_info_vsc9959 = {
.num_mact_rows = 2048,
.num_ports = 6,
.num_tx_queues = OCELOT_NUM_TC,
+ .quirks = FELIX_MAC_QUIRKS,
.quirk_no_xtr_irq = true,
.ptp_caps = &vsc9959_ptp_caps,
.mdio_bus_alloc = vsc9959_mdio_bus_alloc,
diff --git a/drivers/net/dsa/ocelot/seville_vsc9953.c b/drivers/net/dsa/ocelot/seville_vsc9953.c
index c6264e9f4c37..aa31f741634e 100644
--- a/drivers/net/dsa/ocelot/seville_vsc9953.c
+++ b/drivers/net/dsa/ocelot/seville_vsc9953.c
@@ -1100,6 +1100,7 @@ static const struct felix_info seville_info_vsc9953 = {
.vcap_pol_max = VSC9953_VCAP_POLICER_MAX,
.vcap_pol_base2 = VSC9953_VCAP_POLICER_BASE2,
.vcap_pol_max2 = VSC9953_VCAP_POLICER_MAX2,
+ .quirks = FELIX_MAC_QUIRKS,
.num_mact_rows = 2048,
.num_ports = 10,
.num_tx_queues = OCELOT_NUM_TC,
--
2.25.1
The ocelot-ext driver requires the phys to be externally controlled by an
optional parameter. This commit exposes that variable so it can be
utilized.
Signed-off-by: Colin Foster <[email protected]>
---
drivers/net/dsa/ocelot/seville_vsc9953.c | 3 ++-
drivers/net/mdio/mdio-mscc-miim.c | 10 ++++++----
include/linux/mdio/mdio-mscc-miim.h | 3 ++-
3 files changed, 10 insertions(+), 6 deletions(-)
diff --git a/drivers/net/dsa/ocelot/seville_vsc9953.c b/drivers/net/dsa/ocelot/seville_vsc9953.c
index 8c1c9da61602..c6264e9f4c37 100644
--- a/drivers/net/dsa/ocelot/seville_vsc9953.c
+++ b/drivers/net/dsa/ocelot/seville_vsc9953.c
@@ -1021,7 +1021,8 @@ static int vsc9953_mdio_bus_alloc(struct ocelot *ocelot)
rc = mscc_miim_setup(dev, &bus, "VSC9953 internal MDIO bus",
ocelot->targets[GCB],
- ocelot->map[GCB][GCB_MIIM_MII_STATUS & REG_MASK]);
+ ocelot->map[GCB][GCB_MIIM_MII_STATUS & REG_MASK],
+ NULL, 0);
if (rc) {
dev_err(dev, "failed to setup MDIO bus\n");
diff --git a/drivers/net/mdio/mdio-mscc-miim.c b/drivers/net/mdio/mdio-mscc-miim.c
index 6b14f3cf3891..07baf8390744 100644
--- a/drivers/net/mdio/mdio-mscc-miim.c
+++ b/drivers/net/mdio/mdio-mscc-miim.c
@@ -188,7 +188,8 @@ static const struct regmap_config mscc_miim_regmap_config = {
};
int mscc_miim_setup(struct device *dev, struct mii_bus **pbus, const char *name,
- struct regmap *mii_regmap, int status_offset)
+ struct regmap *mii_regmap, int status_offset,
+ struct regmap *phy_regmap, int phy_offset)
{
struct mscc_miim_dev *miim;
struct mii_bus *bus;
@@ -210,6 +211,8 @@ int mscc_miim_setup(struct device *dev, struct mii_bus **pbus, const char *name,
miim->regs = mii_regmap;
miim->mii_status_offset = status_offset;
+ miim->phy_regs = phy_regmap;
+ miim->phy_reset_offset = phy_offset;
*pbus = bus;
@@ -257,15 +260,14 @@ static int mscc_miim_probe(struct platform_device *pdev)
}
}
- ret = mscc_miim_setup(dev, &bus, "mscc_miim", mii_regmap, 0);
+ ret = mscc_miim_setup(&pdev->dev, &bus, "mscc_miim", mii_regmap, 0,
+ phy_regmap, 0);
if (ret < 0) {
dev_err(dev, "Unable to setup the MDIO bus\n");
return ret;
}
miim = bus->priv;
- miim->phy_regs = phy_regmap;
- miim->phy_reset_offset = 0;
ret = of_mdiobus_register(bus, dev->of_node);
if (ret < 0) {
diff --git a/include/linux/mdio/mdio-mscc-miim.h b/include/linux/mdio/mdio-mscc-miim.h
index 5b4ed2c3cbb9..5a95e43f73f9 100644
--- a/include/linux/mdio/mdio-mscc-miim.h
+++ b/include/linux/mdio/mdio-mscc-miim.h
@@ -14,6 +14,7 @@
int mscc_miim_setup(struct device *device, struct mii_bus **bus,
const char *name, struct regmap *mii_regmap,
- int status_offset);
+ int status_offset, struct regmap *phy_regmap,
+ int phy_offset);
#endif
--
2.25.1
Some drivers will need to create regmaps differently based on whether they
are a child of an MFD or a standalone device. An example of this would be
if a regmap were directly memory-mapped or an external bus. In the
memory-mapped case a call to devm_regmap_init_mmio would return the correct
regmap. In the case of an MFD, the regmap would need to be requested from
the parent device.
This addition allows the driver to correctly reason about these scenarios.
Signed-off-by: Colin Foster <[email protected]>
---
drivers/mfd/mfd-core.c | 6 ++++++
include/linux/mfd/core.h | 10 ++++++++++
2 files changed, 16 insertions(+)
diff --git a/drivers/mfd/mfd-core.c b/drivers/mfd/mfd-core.c
index 684a011a6396..2ba6a692499b 100644
--- a/drivers/mfd/mfd-core.c
+++ b/drivers/mfd/mfd-core.c
@@ -33,6 +33,12 @@ static struct device_type mfd_dev_type = {
.name = "mfd_device",
};
+int device_is_mfd(struct platform_device *pdev)
+{
+ return (!strcmp(pdev->dev.type->name, mfd_dev_type.name));
+}
+EXPORT_SYMBOL(device_is_mfd);
+
int mfd_cell_enable(struct platform_device *pdev)
{
const struct mfd_cell *cell = mfd_get_cell(pdev);
diff --git a/include/linux/mfd/core.h b/include/linux/mfd/core.h
index 0bc7cba798a3..c0719436b652 100644
--- a/include/linux/mfd/core.h
+++ b/include/linux/mfd/core.h
@@ -10,6 +10,7 @@
#ifndef MFD_CORE_H
#define MFD_CORE_H
+#include <generated/autoconf.h>
#include <linux/platform_device.h>
#define MFD_RES_SIZE(arr) (sizeof(arr) / sizeof(struct resource))
@@ -123,6 +124,15 @@ struct mfd_cell {
int num_parent_supplies;
};
+#ifdef CONFIG_MFD_CORE
+int device_is_mfd(struct platform_device *pdev);
+#else
+static inline int device_is_mfd(struct platform_device *pdev)
+{
+ return 0;
+}
+#endif
+
/*
* Convenience functions for clients using shared cells. Refcounting
* happens automatically, with the cell's enable/disable callbacks
--
2.25.1
Create a single SPI MFD ocelot device that manages the SPI bus on the
external chip and can handle requests for regmaps. This should allow any
ocelot driver (pinctrl, miim, etc.) to be used externally, provided they
utilize regmaps.
Signed-off-by: Colin Foster <[email protected]>
---
drivers/mfd/Kconfig | 19 ++
drivers/mfd/Makefile | 3 +
drivers/mfd/ocelot-core.c | 165 +++++++++++
drivers/mfd/ocelot-spi.c | 325 ++++++++++++++++++++++
drivers/mfd/ocelot.h | 36 +++
drivers/net/mdio/mdio-mscc-miim.c | 21 +-
drivers/pinctrl/pinctrl-microchip-sgpio.c | 22 +-
drivers/pinctrl/pinctrl-ocelot.c | 29 +-
include/soc/mscc/ocelot.h | 11 +
9 files changed, 614 insertions(+), 17 deletions(-)
create mode 100644 drivers/mfd/ocelot-core.c
create mode 100644 drivers/mfd/ocelot-spi.c
create mode 100644 drivers/mfd/ocelot.h
diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
index ba0b3eb131f1..57bbf2d11324 100644
--- a/drivers/mfd/Kconfig
+++ b/drivers/mfd/Kconfig
@@ -948,6 +948,25 @@ config MFD_MENF21BMC
This driver can also be built as a module. If so the module
will be called menf21bmc.
+config MFD_OCELOT
+ tristate "Microsemi Ocelot External Control Support"
+ select MFD_CORE
+ help
+ Say yes here to add support for Ocelot chips (VSC7511, VSC7512,
+ VSC7513, VSC7514) controlled externally.
+
+ All four of these chips can be controlled internally (MMIO) or
+ externally via SPI, I2C, PCIe. This enables control of these chips
+ over one or more of these buses.
+
+config MFD_OCELOT_SPI
+ tristate "Microsemi Ocelot SPI interface"
+ depends on MFD_OCELOT
+ depends on SPI_MASTER
+ select REGMAP_SPI
+ help
+ Say yes here to add control to the MFD_OCELOT chips via SPI.
+
config EZX_PCAP
bool "Motorola EZXPCAP Support"
depends on SPI_MASTER
diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile
index df1ecc4a4c95..12513843067a 100644
--- a/drivers/mfd/Makefile
+++ b/drivers/mfd/Makefile
@@ -120,6 +120,9 @@ obj-$(CONFIG_MFD_MC13XXX_I2C) += mc13xxx-i2c.o
obj-$(CONFIG_MFD_CORE) += mfd-core.o
+obj-$(CONFIG_MFD_OCELOT) += ocelot-core.o
+obj-$(CONFIG_MFD_OCELOT_SPI) += ocelot-spi.o
+
obj-$(CONFIG_EZX_PCAP) += ezx-pcap.o
obj-$(CONFIG_MFD_CPCAP) += motorola-cpcap.o
diff --git a/drivers/mfd/ocelot-core.c b/drivers/mfd/ocelot-core.c
new file mode 100644
index 000000000000..590489481b8c
--- /dev/null
+++ b/drivers/mfd/ocelot-core.c
@@ -0,0 +1,165 @@
+// SPDX-License-Identifier: (GPL-2.0 OR MIT)
+/*
+ * MFD core driver for the Ocelot chip family.
+ *
+ * The VSC7511, 7512, 7513, and 7514 can be controlled internally via an
+ * on-chip MIPS processor, or externally via SPI, I2C, PCIe. This core driver is
+ * intended to be the bus-agnostic glue between, for example, the SPI bus and
+ * the MFD children.
+ *
+ * Copyright 2021 Innovative Advantage Inc.
+ *
+ * Author: Colin Foster <[email protected]>
+ */
+
+#include <linux/mfd/core.h>
+#include <linux/module.h>
+#include <linux/regmap.h>
+
+#include <asm/byteorder.h>
+
+#include "ocelot.h"
+
+#define GCB_SOFT_RST (0x0008)
+
+#define SOFT_CHIP_RST (0x1)
+
+static const struct resource vsc7512_gcb_resource = {
+ .start = 0x71070000,
+ .end = 0x7107022b,
+ .name = "devcpu_gcb",
+};
+
+static int ocelot_reset(struct ocelot_core *core)
+{
+ int ret;
+
+ /*
+ * Reset the entire chip here to put it into a completely known state.
+ * Other drivers may want to reset their own subsystems. The register
+ * self-clears, so one write is all that is needed
+ */
+ ret = regmap_write(core->gcb_regmap, GCB_SOFT_RST, SOFT_CHIP_RST);
+ if (ret)
+ return ret;
+
+ msleep(100);
+
+ /*
+ * A chip reset will clear the SPI configuration, so it needs to be done
+ * again before we can access any more registers
+ */
+ ret = ocelot_spi_initialize(core);
+
+ return ret;
+}
+
+static struct regmap *ocelot_devm_regmap_init(struct ocelot_core *core,
+ struct device *dev,
+ const struct resource *res)
+{
+ struct regmap *regmap;
+
+ regmap = dev_get_regmap(dev, res->name);
+ if (!regmap)
+ regmap = ocelot_spi_devm_get_regmap(core, dev, res);
+
+ return regmap;
+}
+
+struct regmap *ocelot_get_regmap_from_resource(struct device *dev,
+ const struct resource *res)
+{
+ struct ocelot_core *core = dev_get_drvdata(dev);
+
+ return ocelot_devm_regmap_init(core, dev, res);
+}
+EXPORT_SYMBOL(ocelot_get_regmap_from_resource);
+
+static const struct resource vsc7512_miim1_resources[] = {
+ {
+ .start = 0x710700c0,
+ .end = 0x710700e3,
+ .name = "gcb_miim1",
+ .flags = IORESOURCE_MEM,
+ },
+};
+
+static const struct resource vsc7512_pinctrl_resources[] = {
+ {
+ .start = 0x71070034,
+ .end = 0x7107009f,
+ .name = "gcb_gpio",
+ .flags = IORESOURCE_MEM,
+ },
+};
+
+static const struct resource vsc7512_sgpio_resources[] = {
+ {
+ .start = 0x710700f8,
+ .end = 0x710701f7,
+ .name = "gcb_sio",
+ .flags = IORESOURCE_MEM,
+ },
+};
+
+static const struct mfd_cell vsc7512_devs[] = {
+ {
+ .name = "pinctrl-ocelot",
+ .of_compatible = "mscc,ocelot-pinctrl",
+ .num_resources = ARRAY_SIZE(vsc7512_pinctrl_resources),
+ .resources = vsc7512_pinctrl_resources,
+ },
+ {
+ .name = "pinctrl-sgpio",
+ .of_compatible = "mscc,ocelot-sgpio",
+ .num_resources = ARRAY_SIZE(vsc7512_sgpio_resources),
+ .resources = vsc7512_sgpio_resources,
+ },
+ {
+ .name = "ocelot-miim1",
+ .of_compatible = "mscc,ocelot-miim",
+ .num_resources = ARRAY_SIZE(vsc7512_miim1_resources),
+ .resources = vsc7512_miim1_resources,
+ },
+};
+
+int ocelot_core_init(struct ocelot_core *core)
+{
+ struct device *dev = core->dev;
+ int ret;
+
+ dev_set_drvdata(dev, core);
+
+ core->gcb_regmap = ocelot_devm_regmap_init(core, dev,
+ &vsc7512_gcb_resource);
+ if (!core->gcb_regmap)
+ return -ENOMEM;
+
+ /* Prepare the chip */
+ ret = ocelot_reset(core);
+ if (ret) {
+ dev_err(dev, "ocelot mfd reset failed with code %d\n", ret);
+ return ret;
+ }
+
+ ret = devm_mfd_add_devices(dev, PLATFORM_DEVID_NONE, vsc7512_devs,
+ ARRAY_SIZE(vsc7512_devs), NULL, 0, NULL);
+ if (ret) {
+ dev_err(dev, "error adding mfd devices\n");
+ return ret;
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL(ocelot_core_init);
+
+int ocelot_remove(struct ocelot_core *core)
+{
+ return 0;
+}
+EXPORT_SYMBOL(ocelot_remove);
+
+MODULE_DESCRIPTION("Ocelot Chip MFD driver");
+MODULE_AUTHOR("Colin Foster <[email protected]>");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/mfd/ocelot-spi.c b/drivers/mfd/ocelot-spi.c
new file mode 100644
index 000000000000..1e268a4dfa17
--- /dev/null
+++ b/drivers/mfd/ocelot-spi.c
@@ -0,0 +1,325 @@
+// SPDX-License-Identifier: (GPL-2.0 OR MIT)
+/*
+ * SPI core driver for the Ocelot chip family.
+ *
+ * This driver will handle everything necessary to allow for communication over
+ * SPI to the VSC7511, VSC7512, VSC7513 and VSC7514 chips. The main functions
+ * are to prepare the chip's SPI interface for a specific bus speed, and a host
+ * processor's endianness. This will create and distribute regmaps for any MFD
+ * children.
+ *
+ * Copyright 2021 Innovative Advantage Inc.
+ *
+ * Author: Colin Foster <[email protected]>
+ */
+
+#include <linux/iopoll.h>
+#include <linux/kconfig.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/regmap.h>
+#include <linux/spi/spi.h>
+
+#include <asm/byteorder.h>
+
+#include "ocelot.h"
+
+struct ocelot_spi {
+ int spi_padding_bytes;
+ struct spi_device *spi;
+ struct ocelot_core core;
+ struct regmap *cpuorg_regmap;
+};
+
+#define DEV_CPUORG_IF_CTRL (0x0000)
+#define DEV_CPUORG_IF_CFGSTAT (0x0004)
+
+static const struct resource vsc7512_dev_cpuorg_resource = {
+ .start = 0x71000000,
+ .end = 0x710002ff,
+ .name = "devcpu_org",
+};
+
+#define VSC7512_BYTE_ORDER_LE 0x00000000
+#define VSC7512_BYTE_ORDER_BE 0x81818181
+#define VSC7512_BIT_ORDER_MSB 0x00000000
+#define VSC7512_BIT_ORDER_LSB 0x42424242
+
+static struct ocelot_spi *core_to_ocelot_spi(struct ocelot_core *core)
+{
+ return container_of(core, struct ocelot_spi, core);
+}
+
+static int ocelot_spi_init_bus(struct ocelot_spi *ocelot_spi)
+{
+ struct spi_device *spi;
+ struct device *dev;
+ u32 val, check;
+ int err;
+
+ spi = ocelot_spi->spi;
+ dev = &spi->dev;
+
+#ifdef __LITTLE_ENDIAN
+ val = VSC7512_BYTE_ORDER_LE;
+#else
+ val = VSC7512_BYTE_ORDER_BE;
+#endif
+
+ err = regmap_write(ocelot_spi->cpuorg_regmap, DEV_CPUORG_IF_CTRL, val);
+ if (err)
+ return err;
+
+ val = ocelot_spi->spi_padding_bytes;
+ err = regmap_write(ocelot_spi->cpuorg_regmap, DEV_CPUORG_IF_CFGSTAT,
+ val);
+ if (err)
+ return err;
+
+ check = val | 0x02000000;
+
+ err = regmap_read(ocelot_spi->cpuorg_regmap, DEV_CPUORG_IF_CFGSTAT,
+ &val);
+ if (err)
+ return err;
+
+ if (check != val)
+ return -ENODEV;
+
+ return 0;
+}
+
+int ocelot_spi_initialize(struct ocelot_core *core)
+{
+ struct ocelot_spi *ocelot_spi = core_to_ocelot_spi(core);
+
+ return ocelot_spi_init_bus(ocelot_spi);
+}
+EXPORT_SYMBOL(ocelot_spi_initialize);
+
+static unsigned int ocelot_spi_translate_address(unsigned int reg)
+{
+ return cpu_to_be32((reg & 0xffffff) >> 2);
+}
+
+struct ocelot_spi_regmap_context {
+ u32 base;
+ struct ocelot_spi *ocelot_spi;
+};
+
+static int ocelot_spi_reg_read(void *context, unsigned int reg,
+ unsigned int *val)
+{
+ struct ocelot_spi_regmap_context *regmap_context = context;
+ struct ocelot_spi *ocelot_spi = regmap_context->ocelot_spi;
+ struct spi_transfer tx, padding, rx;
+ struct spi_message msg;
+ struct spi_device *spi;
+ unsigned int addr;
+ u8 *tx_buf;
+
+ WARN_ON(!val);
+
+ spi = ocelot_spi->spi;
+
+ addr = ocelot_spi_translate_address(reg + regmap_context->base);
+ tx_buf = (u8 *)&addr;
+
+ spi_message_init(&msg);
+
+ memset(&tx, 0, sizeof(struct spi_transfer));
+
+ /* Ignore the first byte for the 24-bit address */
+ tx.tx_buf = &tx_buf[1];
+ tx.len = 3;
+
+ spi_message_add_tail(&tx, &msg);
+
+ if (ocelot_spi->spi_padding_bytes > 0) {
+ u8 dummy_buf[16] = {0};
+
+ memset(&padding, 0, sizeof(struct spi_transfer));
+
+ /* Just toggle the clock for padding bytes */
+ padding.len = ocelot_spi->spi_padding_bytes;
+ padding.tx_buf = dummy_buf;
+ padding.dummy_data = 1;
+
+ spi_message_add_tail(&padding, &msg);
+ }
+
+ memset(&rx, 0, sizeof(struct spi_transfer));
+ rx.rx_buf = val;
+ rx.len = 4;
+
+ spi_message_add_tail(&rx, &msg);
+
+ return spi_sync(spi, &msg);
+}
+
+static int ocelot_spi_reg_write(void *context, unsigned int reg,
+ unsigned int val)
+{
+ struct ocelot_spi_regmap_context *regmap_context = context;
+ struct ocelot_spi *ocelot_spi = regmap_context->ocelot_spi;
+ struct spi_transfer tx[2] = {0};
+ struct spi_message msg;
+ struct spi_device *spi;
+ unsigned int addr;
+ u8 *tx_buf;
+
+ spi = ocelot_spi->spi;
+
+ addr = ocelot_spi_translate_address(reg + regmap_context->base);
+ tx_buf = (u8 *)&addr;
+
+ spi_message_init(&msg);
+
+ /* Ignore the first byte for the 24-bit address and set the write bit */
+ tx_buf[1] |= BIT(7);
+ tx[0].tx_buf = &tx_buf[1];
+ tx[0].len = 3;
+
+ spi_message_add_tail(&tx[0], &msg);
+
+ memset(&tx[1], 0, sizeof(struct spi_transfer));
+ tx[1].tx_buf = &val;
+ tx[1].len = 4;
+
+ spi_message_add_tail(&tx[1], &msg);
+
+ return spi_sync(spi, &msg);
+}
+
+static const struct regmap_config ocelot_spi_regmap_config = {
+ .reg_bits = 24,
+ .reg_stride = 4,
+ .val_bits = 32,
+
+ .reg_read = ocelot_spi_reg_read,
+ .reg_write = ocelot_spi_reg_write,
+
+ .max_register = 0xffffffff,
+ .use_single_write = true,
+ .use_single_read = true,
+ .can_multi_write = false,
+
+ .reg_format_endian = REGMAP_ENDIAN_BIG,
+ .val_format_endian = REGMAP_ENDIAN_NATIVE,
+};
+
+struct regmap *
+ocelot_spi_devm_get_regmap(struct ocelot_core *core, struct device *dev,
+ const struct resource *res)
+{
+ struct ocelot_spi *ocelot_spi = core_to_ocelot_spi(core);
+ struct ocelot_spi_regmap_context *context;
+ struct regmap_config regmap_config;
+ struct regmap *regmap;
+
+ context = devm_kzalloc(dev, sizeof(*context), GFP_KERNEL);
+ if (IS_ERR(context))
+ return ERR_CAST(context);
+
+ context->base = res->start;
+ context->ocelot_spi = ocelot_spi;
+
+ memcpy(®map_config, &ocelot_spi_regmap_config,
+ sizeof(ocelot_spi_regmap_config));
+
+ regmap_config.name = res->name;
+ regmap_config.max_register = res->end - res->start;
+
+ regmap = devm_regmap_init(dev, NULL, context, ®map_config);
+ if (IS_ERR(regmap))
+ return ERR_CAST(regmap);
+
+ return regmap;
+}
+
+static int ocelot_spi_probe(struct spi_device *spi)
+{
+ struct device *dev = &spi->dev;
+ struct ocelot_spi *ocelot_spi;
+ int err;
+
+ ocelot_spi = devm_kzalloc(dev, sizeof(*ocelot_spi), GFP_KERNEL);
+
+ if (!ocelot_spi)
+ return -ENOMEM;
+
+ if (spi->max_speed_hz <= 500000) {
+ ocelot_spi->spi_padding_bytes = 0;
+ } else {
+ /*
+ * Calculation taken from the manual for IF_CFGSTAT:IF_CFG.
+ * Register access time is 1us, so we need to configure and send
+ * out enough padding bytes between the read request and data
+ * transmission that lasts at least 1 microsecond.
+ */
+ ocelot_spi->spi_padding_bytes = 1 +
+ (spi->max_speed_hz / 1000000 + 2) / 8;
+ }
+
+ ocelot_spi->spi = spi;
+
+ spi->bits_per_word = 8;
+
+ err = spi_setup(spi);
+ if (err < 0) {
+ dev_err(&spi->dev, "Error %d initializing SPI\n", err);
+ return err;
+ }
+
+ ocelot_spi->cpuorg_regmap =
+ ocelot_spi_devm_get_regmap(&ocelot_spi->core, dev,
+ &vsc7512_dev_cpuorg_resource);
+ if (!ocelot_spi->cpuorg_regmap)
+ return -ENOMEM;
+
+ ocelot_spi->core.dev = dev;
+
+ /*
+ * The chip must be set up for SPI before it gets initialized and reset.
+ * This must be done before calling init, and after a chip reset is
+ * performed.
+ */
+ err = ocelot_spi_init_bus(ocelot_spi);
+ if (err) {
+ dev_err(dev, "Error %d initializing Ocelot SPI bus\n", err);
+ return err;
+ }
+
+ err = ocelot_core_init(&ocelot_spi->core);
+ if (err < 0) {
+ dev_err(dev, "Error %d initializing Ocelot MFD\n", err);
+ return err;
+ }
+
+ return 0;
+}
+
+static int ocelot_spi_remove(struct spi_device *spi)
+{
+ return 0;
+}
+
+const struct of_device_id ocelot_spi_of_match[] = {
+ { .compatible = "mscc,vsc7512_mfd_spi" },
+ { },
+};
+MODULE_DEVICE_TABLE(of, ocelot_spi_of_match);
+
+static struct spi_driver ocelot_spi_driver = {
+ .driver = {
+ .name = "ocelot_mfd_spi",
+ .of_match_table = of_match_ptr(ocelot_spi_of_match),
+ },
+ .probe = ocelot_spi_probe,
+ .remove = ocelot_spi_remove,
+};
+module_spi_driver(ocelot_spi_driver);
+
+MODULE_DESCRIPTION("Ocelot Chip MFD SPI driver");
+MODULE_AUTHOR("Colin Foster <[email protected]>");
+MODULE_LICENSE("Dual MIT/GPL");
diff --git a/drivers/mfd/ocelot.h b/drivers/mfd/ocelot.h
new file mode 100644
index 000000000000..8bb2b57002be
--- /dev/null
+++ b/drivers/mfd/ocelot.h
@@ -0,0 +1,36 @@
+/* SPDX-License-Identifier: GPL-2.0 OR MIT */
+/*
+ * Copyright 2021 Innovative Advantage Inc.
+ */
+
+#include <linux/kconfig.h>
+#include <linux/regmap.h>
+
+struct ocelot_core {
+ struct device *dev;
+ struct regmap *gcb_regmap;
+};
+
+void ocelot_get_resource_name(char *name, const struct resource *res,
+ int size);
+int ocelot_core_init(struct ocelot_core *core);
+int ocelot_remove(struct ocelot_core *core);
+
+#if IS_ENABLED(CONFIG_MFD_OCELOT_SPI)
+struct regmap *ocelot_spi_devm_get_regmap(struct ocelot_core *core,
+ struct device *dev,
+ const struct resource *res);
+int ocelot_spi_initialize(struct ocelot_core *core);
+#else
+static inline struct regmap *ocelot_spi_devm_get_regmap(
+ struct ocelot_core *core, struct device *dev,
+ const struct resource *res)
+{
+ return NULL;
+}
+
+static inline int ocelot_spi_initialize(struct ocelot_core *core)
+{
+ return -EOPNOTSUPP;
+}
+#endif
diff --git a/drivers/net/mdio/mdio-mscc-miim.c b/drivers/net/mdio/mdio-mscc-miim.c
index 07baf8390744..8e54bde06fd5 100644
--- a/drivers/net/mdio/mdio-mscc-miim.c
+++ b/drivers/net/mdio/mdio-mscc-miim.c
@@ -11,11 +11,13 @@
#include <linux/iopoll.h>
#include <linux/kernel.h>
#include <linux/mdio/mdio-mscc-miim.h>
+#include <linux/mfd/core.h>
#include <linux/module.h>
#include <linux/of_mdio.h>
#include <linux/phy.h>
#include <linux/platform_device.h>
#include <linux/regmap.h>
+#include <soc/mscc/ocelot.h>
#define MSCC_MIIM_REG_STATUS 0x0
#define MSCC_MIIM_STATUS_STAT_PENDING BIT(2)
@@ -230,13 +232,20 @@ static int mscc_miim_probe(struct platform_device *pdev)
struct mii_bus *bus;
int ret;
- regs = devm_platform_get_and_ioremap_resource(pdev, 0, NULL);
- if (IS_ERR(regs)) {
- dev_err(dev, "Unable to map MIIM registers\n");
- return PTR_ERR(regs);
- }
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+
+ if (!device_is_mfd(pdev)) {
+ regs = devm_ioremap_resource(dev, res);
+ if (IS_ERR(regs)) {
+ dev_err(dev, "Unable to map MIIM registers\n");
+ return PTR_ERR(regs);
+ }
- mii_regmap = devm_regmap_init_mmio(dev, regs, &mscc_miim_regmap_config);
+ mii_regmap = devm_regmap_init_mmio(dev, regs,
+ &mscc_miim_regmap_config);
+ } else {
+ mii_regmap = ocelot_get_regmap_from_resource(dev->parent, res);
+ }
if (IS_ERR(mii_regmap)) {
dev_err(dev, "Unable to create MIIM regmap\n");
diff --git a/drivers/pinctrl/pinctrl-microchip-sgpio.c b/drivers/pinctrl/pinctrl-microchip-sgpio.c
index 8db3caf15cf2..53df095b33e0 100644
--- a/drivers/pinctrl/pinctrl-microchip-sgpio.c
+++ b/drivers/pinctrl/pinctrl-microchip-sgpio.c
@@ -12,6 +12,7 @@
#include <linux/clk.h>
#include <linux/gpio/driver.h>
#include <linux/io.h>
+#include <linux/mfd/core.h>
#include <linux/mod_devicetable.h>
#include <linux/module.h>
#include <linux/pinctrl/pinmux.h>
@@ -19,6 +20,7 @@
#include <linux/property.h>
#include <linux/regmap.h>
#include <linux/reset.h>
+#include <soc/mscc/ocelot.h>
#include "core.h"
#include "pinconf.h"
@@ -137,7 +139,9 @@ static inline int sgpio_addr_to_pin(struct sgpio_priv *priv, int port, int bit)
static inline u32 sgpio_get_addr(struct sgpio_priv *priv, u32 rno, u32 off)
{
- return priv->properties->regoff[rno] + off;
+ int stride = regmap_get_reg_stride(priv->regs);
+
+ return (priv->properties->regoff[rno] + off) * stride;
}
static u32 sgpio_readl(struct sgpio_priv *priv, u32 rno, u32 off)
@@ -818,6 +822,7 @@ static int microchip_sgpio_probe(struct platform_device *pdev)
struct fwnode_handle *fwnode;
struct reset_control *reset;
struct sgpio_priv *priv;
+ struct resource *res;
struct clk *clk;
u32 __iomem *regs;
u32 val;
@@ -850,11 +855,18 @@ static int microchip_sgpio_probe(struct platform_device *pdev)
return -EINVAL;
}
- regs = devm_platform_ioremap_resource(pdev, 0);
- if (IS_ERR(regs))
- return PTR_ERR(regs);
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+
+ if (!device_is_mfd(pdev)) {
+ regs = devm_ioremap_resource(dev, res);
+ if (IS_ERR(regs))
+ return PTR_ERR(regs);
+
+ priv->regs = devm_regmap_init_mmio(dev, regs, ®map_config);
+ } else {
+ priv->regs = ocelot_get_regmap_from_resource(dev->parent, res);
+ }
- priv->regs = devm_regmap_init_mmio(dev, regs, ®map_config);
if (IS_ERR(priv->regs))
return PTR_ERR(priv->regs);
diff --git a/drivers/pinctrl/pinctrl-ocelot.c b/drivers/pinctrl/pinctrl-ocelot.c
index b6ad3ffb4596..d5485c6a0e20 100644
--- a/drivers/pinctrl/pinctrl-ocelot.c
+++ b/drivers/pinctrl/pinctrl-ocelot.c
@@ -10,6 +10,7 @@
#include <linux/gpio/driver.h>
#include <linux/interrupt.h>
#include <linux/io.h>
+#include <linux/mfd/core.h>
#include <linux/of_device.h>
#include <linux/of_irq.h>
#include <linux/of_platform.h>
@@ -20,6 +21,7 @@
#include <linux/platform_device.h>
#include <linux/regmap.h>
#include <linux/slab.h>
+#include <soc/mscc/ocelot.h>
#include "core.h"
#include "pinconf.h"
@@ -1123,6 +1125,9 @@ static int lan966x_pinmux_set_mux(struct pinctrl_dev *pctldev,
return 0;
}
+#if defined(REG)
+#undef REG
+#endif
#define REG(r, info, p) ((r) * (info)->stride + (4 * ((p) / 32)))
static int ocelot_gpio_set_direction(struct pinctrl_dev *pctldev,
@@ -1805,6 +1810,7 @@ static int ocelot_pinctrl_probe(struct platform_device *pdev)
struct device *dev = &pdev->dev;
struct ocelot_pinctrl *info;
struct regmap *pincfg;
+ struct resource *res;
void __iomem *base;
int ret;
struct regmap_config regmap_config = {
@@ -1819,16 +1825,27 @@ static int ocelot_pinctrl_probe(struct platform_device *pdev)
info->desc = (struct pinctrl_desc *)device_get_match_data(dev);
- base = devm_ioremap_resource(dev,
- platform_get_resource(pdev, IORESOURCE_MEM, 0));
- if (IS_ERR(base))
- return PTR_ERR(base);
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (IS_ERR(res)) {
+ dev_err(dev, "Failed to get resource\n");
+ return PTR_ERR(res);
+ }
info->stride = 1 + (info->desc->npins - 1) / 32;
- regmap_config.max_register = OCELOT_GPIO_SD_MAP * info->stride + 15 * 4;
+ if (!device_is_mfd(pdev)) {
+ base = devm_ioremap_resource(dev, res);
+ if (IS_ERR(base))
+ return PTR_ERR(base);
+
+ regmap_config.max_register =
+ OCELOT_GPIO_SD_MAP * info->stride + 15 * 4;
+
+ info->map = devm_regmap_init_mmio(dev, base, ®map_config);
+ } else {
+ info->map = ocelot_get_regmap_from_resource(dev->parent, res);
+ }
- info->map = devm_regmap_init_mmio(dev, base, ®map_config);
if (IS_ERR(info->map)) {
dev_err(dev, "Failed to create regmap\n");
return PTR_ERR(info->map);
diff --git a/include/soc/mscc/ocelot.h b/include/soc/mscc/ocelot.h
index 5c3a3597f1d2..70fae9c8b649 100644
--- a/include/soc/mscc/ocelot.h
+++ b/include/soc/mscc/ocelot.h
@@ -969,4 +969,15 @@ ocelot_mrp_del_ring_role(struct ocelot *ocelot, int port,
}
#endif
+#if IS_ENABLED(CONFIG_MFD_OCELOT)
+struct regmap *ocelot_get_regmap_from_resource(struct device *dev,
+ const struct resource *res);
+#else
+static inline struct regmap *
+ocelot_get_regmap_from_resource(struct device *dev, const struct resource *res)
+{
+ return NULL;
+}
+#endif
+
#endif
--
2.25.1
On Sat, Jan 29, 2022 at 11:02 PM Colin Foster
<[email protected]> wrote:
> As the commit message suggests, this simply adds the ability to select
> SGPIO pinctrl as a module. This becomes more practical when the SGPIO
> hardware exists on an external chip, controlled indirectly by I2C or SPI.
> This commit enables that level of control.
>
> Signed-off-by: Colin Foster <[email protected]>
Reviewed-by: Linus Walleij <[email protected]>
Yours,
Linus Walleij
On Sat, 29 Jan 2022, Colin Foster wrote:
> Some drivers will need to create regmaps differently based on whether they
> are a child of an MFD or a standalone device. An example of this would be
> if a regmap were directly memory-mapped or an external bus. In the
> memory-mapped case a call to devm_regmap_init_mmio would return the correct
> regmap. In the case of an MFD, the regmap would need to be requested from
> the parent device.
>
> This addition allows the driver to correctly reason about these scenarios.
>
> Signed-off-by: Colin Foster <[email protected]>
> ---
> drivers/mfd/mfd-core.c | 6 ++++++
> include/linux/mfd/core.h | 10 ++++++++++
> 2 files changed, 16 insertions(+)
>
> diff --git a/drivers/mfd/mfd-core.c b/drivers/mfd/mfd-core.c
> index 684a011a6396..2ba6a692499b 100644
> --- a/drivers/mfd/mfd-core.c
> +++ b/drivers/mfd/mfd-core.c
> @@ -33,6 +33,12 @@ static struct device_type mfd_dev_type = {
> .name = "mfd_device",
> };
>
> +int device_is_mfd(struct platform_device *pdev)
> +{
> + return (!strcmp(pdev->dev.type->name, mfd_dev_type.name));
> +}
> +EXPORT_SYMBOL(device_is_mfd);
As I said before, I really don't want MFDness leaking out into other
parts of the kernel. Please find another way to differentiate between
devices registered via the MFD API and by other means.
I'm happy to help here.
How else could these devices be enumerated?
> int mfd_cell_enable(struct platform_device *pdev)
> {
> const struct mfd_cell *cell = mfd_get_cell(pdev);
> diff --git a/include/linux/mfd/core.h b/include/linux/mfd/core.h
> index 0bc7cba798a3..c0719436b652 100644
> --- a/include/linux/mfd/core.h
> +++ b/include/linux/mfd/core.h
> @@ -10,6 +10,7 @@
> #ifndef MFD_CORE_H
> #define MFD_CORE_H
>
> +#include <generated/autoconf.h>
> #include <linux/platform_device.h>
>
> #define MFD_RES_SIZE(arr) (sizeof(arr) / sizeof(struct resource))
> @@ -123,6 +124,15 @@ struct mfd_cell {
> int num_parent_supplies;
> };
>
> +#ifdef CONFIG_MFD_CORE
> +int device_is_mfd(struct platform_device *pdev);
> +#else
> +static inline int device_is_mfd(struct platform_device *pdev)
> +{
> + return 0;
> +}
> +#endif
> +
> /*
> * Convenience functions for clients using shared cells. Refcounting
> * happens automatically, with the cell's enable/disable callbacks
--
Lee Jones [李琼斯]
Principal Technical Lead - Developer Services
Linaro.org │ Open source software for Arm SoCs
Follow Linaro: Facebook | Twitter | Blog
On Sat, 29 Jan 2022, Colin Foster wrote:
> Create a single SPI MFD ocelot device that manages the SPI bus on the
> external chip and can handle requests for regmaps. This should allow any
> ocelot driver (pinctrl, miim, etc.) to be used externally, provided they
> utilize regmaps.
>
> Signed-off-by: Colin Foster <[email protected]>
> ---
> drivers/mfd/Kconfig | 19 ++
> drivers/mfd/Makefile | 3 +
> drivers/mfd/ocelot-core.c | 165 +++++++++++
> drivers/mfd/ocelot-spi.c | 325 ++++++++++++++++++++++
> drivers/mfd/ocelot.h | 36 +++
> drivers/net/mdio/mdio-mscc-miim.c | 21 +-
> drivers/pinctrl/pinctrl-microchip-sgpio.c | 22 +-
> drivers/pinctrl/pinctrl-ocelot.c | 29 +-
> include/soc/mscc/ocelot.h | 11 +
Please avoid mixing subsystems in patches if at all avoidable.
If there are not build time dependencies/breakages, I'd suggest
firstly applying support for this into MFD *then* utilising that
support in subsequent patches.
> 9 files changed, 614 insertions(+), 17 deletions(-)
> create mode 100644 drivers/mfd/ocelot-core.c
> create mode 100644 drivers/mfd/ocelot-spi.c
> create mode 100644 drivers/mfd/ocelot.h
>
> diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
> index ba0b3eb131f1..57bbf2d11324 100644
> --- a/drivers/mfd/Kconfig
> +++ b/drivers/mfd/Kconfig
> @@ -948,6 +948,25 @@ config MFD_MENF21BMC
> This driver can also be built as a module. If so the module
> will be called menf21bmc.
>
> +config MFD_OCELOT
> + tristate "Microsemi Ocelot External Control Support"
Please explain exactly what an ECS is in the help below.
> + select MFD_CORE
> + help
> + Say yes here to add support for Ocelot chips (VSC7511, VSC7512,
> + VSC7513, VSC7514) controlled externally.
> +
> + All four of these chips can be controlled internally (MMIO) or
> + externally via SPI, I2C, PCIe. This enables control of these chips
> + over one or more of these buses.
> +
> +config MFD_OCELOT_SPI
> + tristate "Microsemi Ocelot SPI interface"
> + depends on MFD_OCELOT
> + depends on SPI_MASTER
> + select REGMAP_SPI
> + help
> + Say yes here to add control to the MFD_OCELOT chips via SPI.
> +
> config EZX_PCAP
> bool "Motorola EZXPCAP Support"
> depends on SPI_MASTER
> diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile
> index df1ecc4a4c95..12513843067a 100644
> --- a/drivers/mfd/Makefile
> +++ b/drivers/mfd/Makefile
> @@ -120,6 +120,9 @@ obj-$(CONFIG_MFD_MC13XXX_I2C) += mc13xxx-i2c.o
>
> obj-$(CONFIG_MFD_CORE) += mfd-core.o
>
> +obj-$(CONFIG_MFD_OCELOT) += ocelot-core.o
> +obj-$(CONFIG_MFD_OCELOT_SPI) += ocelot-spi.o
> +
These do not look lined-up with the remainder of the file.
> obj-$(CONFIG_EZX_PCAP) += ezx-pcap.o
> obj-$(CONFIG_MFD_CPCAP) += motorola-cpcap.o
>
> diff --git a/drivers/mfd/ocelot-core.c b/drivers/mfd/ocelot-core.c
> new file mode 100644
> index 000000000000..590489481b8c
> --- /dev/null
> +++ b/drivers/mfd/ocelot-core.c
> @@ -0,0 +1,165 @@
> +// SPDX-License-Identifier: (GPL-2.0 OR MIT)
> +/*
> + * MFD core driver for the Ocelot chip family.
> + *
> + * The VSC7511, 7512, 7513, and 7514 can be controlled internally via an
> + * on-chip MIPS processor, or externally via SPI, I2C, PCIe. This core driver is
> + * intended to be the bus-agnostic glue between, for example, the SPI bus and
> + * the MFD children.
> + *
> + * Copyright 2021 Innovative Advantage Inc.
> + *
> + * Author: Colin Foster <[email protected]>
> + */
> +
> +#include <linux/mfd/core.h>
> +#include <linux/module.h>
> +#include <linux/regmap.h>
> +
> +#include <asm/byteorder.h>
> +
> +#include "ocelot.h"
> +
> +#define GCB_SOFT_RST (0x0008)
Why the brackets?
> +#define SOFT_CHIP_RST (0x1)
As above.
> +static const struct resource vsc7512_gcb_resource = {
> + .start = 0x71070000,
> + .end = 0x7107022b,
Please define these somewhere.
> + .name = "devcpu_gcb",
> +};
There is a macro you can use for these.
Grep for "DEFINE_RES_"
> +static int ocelot_reset(struct ocelot_core *core)
> +{
> + int ret;
> +
> + /*
> + * Reset the entire chip here to put it into a completely known state.
> + * Other drivers may want to reset their own subsystems. The register
> + * self-clears, so one write is all that is needed
> + */
> + ret = regmap_write(core->gcb_regmap, GCB_SOFT_RST, SOFT_CHIP_RST);
> + if (ret)
> + return ret;
> +
> + msleep(100);
> +
> + /*
> + * A chip reset will clear the SPI configuration, so it needs to be done
> + * again before we can access any more registers
> + */
> + ret = ocelot_spi_initialize(core);
> +
> + return ret;
> +}
> +
> +static struct regmap *ocelot_devm_regmap_init(struct ocelot_core *core,
> + struct device *dev,
> + const struct resource *res)
> +{
> + struct regmap *regmap;
> +
> + regmap = dev_get_regmap(dev, res->name);
> + if (!regmap)
> + regmap = ocelot_spi_devm_get_regmap(core, dev, res);
Why are you making SPI specific calls from the Core driver?
> + return regmap;
> +}
> +
> +struct regmap *ocelot_get_regmap_from_resource(struct device *dev,
> + const struct resource *res)
> +{
> + struct ocelot_core *core = dev_get_drvdata(dev);
> +
> + return ocelot_devm_regmap_init(core, dev, res);
> +}
> +EXPORT_SYMBOL(ocelot_get_regmap_from_resource);
Why don't you always call ocelot_devm_regmap_init() with the 'core'
parameter dropped and just do dev_get_drvdata() inside of there?
You're passing 'dev' anyway.
> +static const struct resource vsc7512_miim1_resources[] = {
> + {
> + .start = 0x710700c0,
> + .end = 0x710700e3,
> + .name = "gcb_miim1",
> + .flags = IORESOURCE_MEM,
> + },
> +};
> +
> +static const struct resource vsc7512_pinctrl_resources[] = {
> + {
> + .start = 0x71070034,
> + .end = 0x7107009f,
> + .name = "gcb_gpio",
> + .flags = IORESOURCE_MEM,
> + },
> +};
> +
> +static const struct resource vsc7512_sgpio_resources[] = {
> + {
> + .start = 0x710700f8,
> + .end = 0x710701f7,
> + .name = "gcb_sio",
> + .flags = IORESOURCE_MEM,
> + },
> +};
> +
> +static const struct mfd_cell vsc7512_devs[] = {
> + {
> + .name = "pinctrl-ocelot",
<device>-<sub-device>
"ocelot-pinctrl"
> + .of_compatible = "mscc,ocelot-pinctrl",
> + .num_resources = ARRAY_SIZE(vsc7512_pinctrl_resources),
> + .resources = vsc7512_pinctrl_resources,
> + },
> + {
Same line please.
> + .name = "pinctrl-sgpio",
"ocelot-sgpio"
> + .of_compatible = "mscc,ocelot-sgpio",
> + .num_resources = ARRAY_SIZE(vsc7512_sgpio_resources),
> + .resources = vsc7512_sgpio_resources,
> + },
> + {
> + .name = "ocelot-miim1",
> + .of_compatible = "mscc,ocelot-miim",
> + .num_resources = ARRAY_SIZE(vsc7512_miim1_resources),
> + .resources = vsc7512_miim1_resources,
> + },
> +};
> +
> +int ocelot_core_init(struct ocelot_core *core)
> +{
> + struct device *dev = core->dev;
> + int ret;
> +
> + dev_set_drvdata(dev, core);
> +
> + core->gcb_regmap = ocelot_devm_regmap_init(core, dev,
> + &vsc7512_gcb_resource);
> + if (!core->gcb_regmap)
And if an error is returned?
> + return -ENOMEM;
> +
> + /* Prepare the chip */
Does it prepare or reset the chip?
If the former, then the following call is misnamed.
if the latter, then there is no need for this comment.
> + ret = ocelot_reset(core);
> + if (ret) {
> + dev_err(dev, "ocelot mfd reset failed with code %d\n", ret);
Isn't the device called 'ocelot'? If so, you just repeated yourself.
"Failed to reset device: %d\n"
> + return ret;
> + }
> +
> + ret = devm_mfd_add_devices(dev, PLATFORM_DEVID_NONE, vsc7512_devs,
Why NONE?
> + ARRAY_SIZE(vsc7512_devs), NULL, 0, NULL);
> + if (ret) {
> + dev_err(dev, "error adding mfd devices\n");
"Failed to add sub-devices"
> + return ret;
> + }
> +
> + return 0;
> +}
> +EXPORT_SYMBOL(ocelot_core_init);
> +
> +int ocelot_remove(struct ocelot_core *core)
> +{
> + return 0;
> +}
> +EXPORT_SYMBOL(ocelot_remove);
What's the propose of this?
> +MODULE_DESCRIPTION("Ocelot Chip MFD driver");
No such thing as an MFD driver.
> +MODULE_AUTHOR("Colin Foster <[email protected]>");
> +MODULE_LICENSE("GPL v2");
> diff --git a/drivers/mfd/ocelot-spi.c b/drivers/mfd/ocelot-spi.c
> new file mode 100644
> index 000000000000..1e268a4dfa17
> --- /dev/null
> +++ b/drivers/mfd/ocelot-spi.c
> @@ -0,0 +1,325 @@
> +// SPDX-License-Identifier: (GPL-2.0 OR MIT)
> +/*
> + * SPI core driver for the Ocelot chip family.
> + *
> + * This driver will handle everything necessary to allow for communication over
> + * SPI to the VSC7511, VSC7512, VSC7513 and VSC7514 chips. The main functions
> + * are to prepare the chip's SPI interface for a specific bus speed, and a host
> + * processor's endianness. This will create and distribute regmaps for any MFD
> + * children.
> + *
> + * Copyright 2021 Innovative Advantage Inc.
> + *
> + * Author: Colin Foster <[email protected]>
> + */
> +
> +#include <linux/iopoll.h>
> +#include <linux/kconfig.h>
> +#include <linux/module.h>
> +#include <linux/of.h>
> +#include <linux/regmap.h>
> +#include <linux/spi/spi.h>
> +
> +#include <asm/byteorder.h>
> +
> +#include "ocelot.h"
> +
> +struct ocelot_spi {
> + int spi_padding_bytes;
> + struct spi_device *spi;
> + struct ocelot_core core;
> + struct regmap *cpuorg_regmap;
> +};
> +
> +#define DEV_CPUORG_IF_CTRL (0x0000)
> +#define DEV_CPUORG_IF_CFGSTAT (0x0004)
> +
> +static const struct resource vsc7512_dev_cpuorg_resource = {
> + .start = 0x71000000,
> + .end = 0x710002ff,
> + .name = "devcpu_org",
> +};
> +
> +#define VSC7512_BYTE_ORDER_LE 0x00000000
> +#define VSC7512_BYTE_ORDER_BE 0x81818181
> +#define VSC7512_BIT_ORDER_MSB 0x00000000
> +#define VSC7512_BIT_ORDER_LSB 0x42424242
> +
> +static struct ocelot_spi *core_to_ocelot_spi(struct ocelot_core *core)
> +{
> + return container_of(core, struct ocelot_spi, core);
> +}
See my comments in the header file.
> +static int ocelot_spi_init_bus(struct ocelot_spi *ocelot_spi)
> +{
> + struct spi_device *spi;
> + struct device *dev;
> + u32 val, check;
> + int err;
> +
> + spi = ocelot_spi->spi;
> + dev = &spi->dev;
> +
> +#ifdef __LITTLE_ENDIAN
> + val = VSC7512_BYTE_ORDER_LE;
> +#else
> + val = VSC7512_BYTE_ORDER_BE;
> +#endif
> +
> + err = regmap_write(ocelot_spi->cpuorg_regmap, DEV_CPUORG_IF_CTRL, val);
> + if (err)
> + return err;
> +
> + val = ocelot_spi->spi_padding_bytes;
> + err = regmap_write(ocelot_spi->cpuorg_regmap, DEV_CPUORG_IF_CFGSTAT,
> + val);
> + if (err)
> + return err;
> +
> + check = val | 0x02000000;
Either define or comment magic numbers (I prefer the former).
> + err = regmap_read(ocelot_spi->cpuorg_regmap, DEV_CPUORG_IF_CFGSTAT,
> + &val);
> + if (err)
> + return err;
Comments needed for what you're actually doing here.
> + if (check != val)
> + return -ENODEV;
> +
> + return 0;
> +}
> +
> +int ocelot_spi_initialize(struct ocelot_core *core)
> +{
> + struct ocelot_spi *ocelot_spi = core_to_ocelot_spi(core);
> +
> + return ocelot_spi_init_bus(ocelot_spi);
> +}
> +EXPORT_SYMBOL(ocelot_spi_initialize);
See my comments in the header file.
> +static unsigned int ocelot_spi_translate_address(unsigned int reg)
> +{
> + return cpu_to_be32((reg & 0xffffff) >> 2);
> +}
Comment.
> +struct ocelot_spi_regmap_context {
> + u32 base;
> + struct ocelot_spi *ocelot_spi;
> +};
See my comments in the header file.
> +static int ocelot_spi_reg_read(void *context, unsigned int reg,
> + unsigned int *val)
> +{
> + struct ocelot_spi_regmap_context *regmap_context = context;
> + struct ocelot_spi *ocelot_spi = regmap_context->ocelot_spi;
> + struct spi_transfer tx, padding, rx;
> + struct spi_message msg;
> + struct spi_device *spi;
> + unsigned int addr;
> + u8 *tx_buf;
> +
> + WARN_ON(!val);
Is this possible?
> + spi = ocelot_spi->spi;
> +
> + addr = ocelot_spi_translate_address(reg + regmap_context->base);
> + tx_buf = (u8 *)&addr;
> +
> + spi_message_init(&msg);
> +
> + memset(&tx, 0, sizeof(struct spi_transfer));
> +
> + /* Ignore the first byte for the 24-bit address */
> + tx.tx_buf = &tx_buf[1];
> + tx.len = 3;
> +
> + spi_message_add_tail(&tx, &msg);
> +
> + if (ocelot_spi->spi_padding_bytes > 0) {
> + u8 dummy_buf[16] = {0};
> +
> + memset(&padding, 0, sizeof(struct spi_transfer));
> +
> + /* Just toggle the clock for padding bytes */
> + padding.len = ocelot_spi->spi_padding_bytes;
> + padding.tx_buf = dummy_buf;
> + padding.dummy_data = 1;
> +
> + spi_message_add_tail(&padding, &msg);
> + }
> +
> + memset(&rx, 0, sizeof(struct spi_transfer));
sizeof(*rx)
> + rx.rx_buf = val;
> + rx.len = 4;
> +
> + spi_message_add_tail(&rx, &msg);
> +
> + return spi_sync(spi, &msg);
> +}
> +
> +static int ocelot_spi_reg_write(void *context, unsigned int reg,
> + unsigned int val)
> +{
> + struct ocelot_spi_regmap_context *regmap_context = context;
> + struct ocelot_spi *ocelot_spi = regmap_context->ocelot_spi;
> + struct spi_transfer tx[2] = {0};
> + struct spi_message msg;
> + struct spi_device *spi;
> + unsigned int addr;
> + u8 *tx_buf;
> +
> + spi = ocelot_spi->spi;
> +
> + addr = ocelot_spi_translate_address(reg + regmap_context->base);
> + tx_buf = (u8 *)&addr;
> +
> + spi_message_init(&msg);
> +
> + /* Ignore the first byte for the 24-bit address and set the write bit */
> + tx_buf[1] |= BIT(7);
> + tx[0].tx_buf = &tx_buf[1];
> + tx[0].len = 3;
> +
> + spi_message_add_tail(&tx[0], &msg);
> +
> + memset(&tx[1], 0, sizeof(struct spi_transfer));
> + tx[1].tx_buf = &val;
> + tx[1].len = 4;
> +
> + spi_message_add_tail(&tx[1], &msg);
> +
> + return spi_sync(spi, &msg);
> +}
> +
> +static const struct regmap_config ocelot_spi_regmap_config = {
> + .reg_bits = 24,
> + .reg_stride = 4,
> + .val_bits = 32,
> +
> + .reg_read = ocelot_spi_reg_read,
> + .reg_write = ocelot_spi_reg_write,
> +
> + .max_register = 0xffffffff,
> + .use_single_write = true,
> + .use_single_read = true,
> + .can_multi_write = false,
> +
> + .reg_format_endian = REGMAP_ENDIAN_BIG,
> + .val_format_endian = REGMAP_ENDIAN_NATIVE,
> +};
> +
> +struct regmap *
> +ocelot_spi_devm_get_regmap(struct ocelot_core *core, struct device *dev,
> + const struct resource *res)
> +{
> + struct ocelot_spi *ocelot_spi = core_to_ocelot_spi(core);
> + struct ocelot_spi_regmap_context *context;
> + struct regmap_config regmap_config;
> + struct regmap *regmap;
> +
> + context = devm_kzalloc(dev, sizeof(*context), GFP_KERNEL);
> + if (IS_ERR(context))
> + return ERR_CAST(context);
> +
> + context->base = res->start;
> + context->ocelot_spi = ocelot_spi;
> +
> + memcpy(®map_config, &ocelot_spi_regmap_config,
> + sizeof(ocelot_spi_regmap_config));
> +
> + regmap_config.name = res->name;
> + regmap_config.max_register = res->end - res->start;
> +
> + regmap = devm_regmap_init(dev, NULL, context, ®map_config);
> + if (IS_ERR(regmap))
> + return ERR_CAST(regmap);
> +
> + return regmap;
> +}
> +
> +static int ocelot_spi_probe(struct spi_device *spi)
> +{
> + struct device *dev = &spi->dev;
> + struct ocelot_spi *ocelot_spi;
> + int err;
> +
> + ocelot_spi = devm_kzalloc(dev, sizeof(*ocelot_spi), GFP_KERNEL);
> +
> + if (!ocelot_spi)
> + return -ENOMEM;
> +
> + if (spi->max_speed_hz <= 500000) {
> + ocelot_spi->spi_padding_bytes = 0;
> + } else {
> + /*
> + * Calculation taken from the manual for IF_CFGSTAT:IF_CFG.
> + * Register access time is 1us, so we need to configure and send
> + * out enough padding bytes between the read request and data
> + * transmission that lasts at least 1 microsecond.
> + */
> + ocelot_spi->spi_padding_bytes = 1 +
> + (spi->max_speed_hz / 1000000 + 2) / 8;
> + }
> +
> + ocelot_spi->spi = spi;
> +
> + spi->bits_per_word = 8;
> +
> + err = spi_setup(spi);
> + if (err < 0) {
> + dev_err(&spi->dev, "Error %d initializing SPI\n", err);
> + return err;
> + }
> +
> + ocelot_spi->cpuorg_regmap =
> + ocelot_spi_devm_get_regmap(&ocelot_spi->core, dev,
> + &vsc7512_dev_cpuorg_resource);
> + if (!ocelot_spi->cpuorg_regmap)
And if an error is returned?
> + return -ENOMEM;
> +
> + ocelot_spi->core.dev = dev;
> +
> + /*
> + * The chip must be set up for SPI before it gets initialized and reset.
> + * This must be done before calling init, and after a chip reset is
> + * performed.
> + */
> + err = ocelot_spi_init_bus(ocelot_spi);
> + if (err) {
> + dev_err(dev, "Error %d initializing Ocelot SPI bus\n", err);
> + return err;
> + }
> +
> + err = ocelot_core_init(&ocelot_spi->core);
> + if (err < 0) {
> + dev_err(dev, "Error %d initializing Ocelot MFD\n", err);
> + return err;
> + }
> +
> + return 0;
> +}
> +
> +static int ocelot_spi_remove(struct spi_device *spi)
> +{
> + return 0;
> +}
> +
> +const struct of_device_id ocelot_spi_of_match[] = {
> + { .compatible = "mscc,vsc7512_mfd_spi" },
> + { },
> +};
> +MODULE_DEVICE_TABLE(of, ocelot_spi_of_match);
> +
> +static struct spi_driver ocelot_spi_driver = {
> + .driver = {
> + .name = "ocelot_mfd_spi",
> + .of_match_table = of_match_ptr(ocelot_spi_of_match),
> + },
> + .probe = ocelot_spi_probe,
> + .remove = ocelot_spi_remove,
> +};
> +module_spi_driver(ocelot_spi_driver);
> +
> +MODULE_DESCRIPTION("Ocelot Chip MFD SPI driver");
> +MODULE_AUTHOR("Colin Foster <[email protected]>");
> +MODULE_LICENSE("Dual MIT/GPL");
> diff --git a/drivers/mfd/ocelot.h b/drivers/mfd/ocelot.h
> new file mode 100644
> index 000000000000..8bb2b57002be
> --- /dev/null
> +++ b/drivers/mfd/ocelot.h
> @@ -0,0 +1,36 @@
> +/* SPDX-License-Identifier: GPL-2.0 OR MIT */
> +/*
> + * Copyright 2021 Innovative Advantage Inc.
> + */
> +
> +#include <linux/kconfig.h>
> +#include <linux/regmap.h>
> +
> +struct ocelot_core {
> + struct device *dev;
> + struct regmap *gcb_regmap;
> +};
Please drop this over-complicated 'core' and 'spi' stuff.
You spend too much effort converting between 'dev', 'core' and 'spi'.
I suggest you just pass 'dev' around as your key parameter.
Any additional attributes you *need" to carry around can do in:
struct ocelot *ddata;
> +void ocelot_get_resource_name(char *name, const struct resource *res,
> + int size);
> +int ocelot_core_init(struct ocelot_core *core);
> +int ocelot_remove(struct ocelot_core *core);
> +
> +#if IS_ENABLED(CONFIG_MFD_OCELOT_SPI)
> +struct regmap *ocelot_spi_devm_get_regmap(struct ocelot_core *core,
> + struct device *dev,
> + const struct resource *res);
> +int ocelot_spi_initialize(struct ocelot_core *core);
> +#else
> +static inline struct regmap *ocelot_spi_devm_get_regmap(
> + struct ocelot_core *core, struct device *dev,
> + const struct resource *res)
> +{
> + return NULL;
> +}
> +
> +static inline int ocelot_spi_initialize(struct ocelot_core *core)
> +{
> + return -EOPNOTSUPP;
> +}
> +#endif
> diff --git a/drivers/net/mdio/mdio-mscc-miim.c b/drivers/net/mdio/mdio-mscc-miim.c
> index 07baf8390744..8e54bde06fd5 100644
> --- a/drivers/net/mdio/mdio-mscc-miim.c
> +++ b/drivers/net/mdio/mdio-mscc-miim.c
> @@ -11,11 +11,13 @@
> #include <linux/iopoll.h>
> #include <linux/kernel.h>
> #include <linux/mdio/mdio-mscc-miim.h>
> +#include <linux/mfd/core.h>
> #include <linux/module.h>
> #include <linux/of_mdio.h>
> #include <linux/phy.h>
> #include <linux/platform_device.h>
> #include <linux/regmap.h>
> +#include <soc/mscc/ocelot.h>
>
> #define MSCC_MIIM_REG_STATUS 0x0
> #define MSCC_MIIM_STATUS_STAT_PENDING BIT(2)
> @@ -230,13 +232,20 @@ static int mscc_miim_probe(struct platform_device *pdev)
> struct mii_bus *bus;
> int ret;
>
> - regs = devm_platform_get_and_ioremap_resource(pdev, 0, NULL);
> - if (IS_ERR(regs)) {
> - dev_err(dev, "Unable to map MIIM registers\n");
> - return PTR_ERR(regs);
> - }
> + res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> +
> + if (!device_is_mfd(pdev)) {
> + regs = devm_ioremap_resource(dev, res);
> + if (IS_ERR(regs)) {
> + dev_err(dev, "Unable to map MIIM registers\n");
> + return PTR_ERR(regs);
> + }
>
> - mii_regmap = devm_regmap_init_mmio(dev, regs, &mscc_miim_regmap_config);
> + mii_regmap = devm_regmap_init_mmio(dev, regs,
> + &mscc_miim_regmap_config);
These tabs look wrong.
Doesn't checkpatch.pl warn about stuff like this?
> + } else {
> + mii_regmap = ocelot_get_regmap_from_resource(dev->parent, res);
> + }
You need a comment to explain why you're calling both of these.
> if (IS_ERR(mii_regmap)) {
> dev_err(dev, "Unable to create MIIM regmap\n");
> diff --git a/drivers/pinctrl/pinctrl-microchip-sgpio.c b/drivers/pinctrl/pinctrl-microchip-sgpio.c
> index 8db3caf15cf2..53df095b33e0 100644
> --- a/drivers/pinctrl/pinctrl-microchip-sgpio.c
> +++ b/drivers/pinctrl/pinctrl-microchip-sgpio.c
> @@ -12,6 +12,7 @@
> #include <linux/clk.h>
> #include <linux/gpio/driver.h>
> #include <linux/io.h>
> +#include <linux/mfd/core.h>
> #include <linux/mod_devicetable.h>
> #include <linux/module.h>
> #include <linux/pinctrl/pinmux.h>
> @@ -19,6 +20,7 @@
> #include <linux/property.h>
> #include <linux/regmap.h>
> #include <linux/reset.h>
> +#include <soc/mscc/ocelot.h>
>
> #include "core.h"
> #include "pinconf.h"
> @@ -137,7 +139,9 @@ static inline int sgpio_addr_to_pin(struct sgpio_priv *priv, int port, int bit)
>
> static inline u32 sgpio_get_addr(struct sgpio_priv *priv, u32 rno, u32 off)
> {
> - return priv->properties->regoff[rno] + off;
> + int stride = regmap_get_reg_stride(priv->regs);
> +
> + return (priv->properties->regoff[rno] + off) * stride;
> }
>
> static u32 sgpio_readl(struct sgpio_priv *priv, u32 rno, u32 off)
> @@ -818,6 +822,7 @@ static int microchip_sgpio_probe(struct platform_device *pdev)
> struct fwnode_handle *fwnode;
> struct reset_control *reset;
> struct sgpio_priv *priv;
> + struct resource *res;
> struct clk *clk;
> u32 __iomem *regs;
> u32 val;
> @@ -850,11 +855,18 @@ static int microchip_sgpio_probe(struct platform_device *pdev)
> return -EINVAL;
> }
>
> - regs = devm_platform_ioremap_resource(pdev, 0);
> - if (IS_ERR(regs))
> - return PTR_ERR(regs);
> + res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> +
> + if (!device_is_mfd(pdev)) {
> + regs = devm_ioremap_resource(dev, res);
What happens if you call this if the device was registered via MFD?
> + if (IS_ERR(regs))
> + return PTR_ERR(regs);
> +
> + priv->regs = devm_regmap_init_mmio(dev, regs, ®map_config);
> + } else {
> + priv->regs = ocelot_get_regmap_from_resource(dev->parent, res);
> + }
>
> - priv->regs = devm_regmap_init_mmio(dev, regs, ®map_config);
> if (IS_ERR(priv->regs))
> return PTR_ERR(priv->regs);
>
> diff --git a/drivers/pinctrl/pinctrl-ocelot.c b/drivers/pinctrl/pinctrl-ocelot.c
> index b6ad3ffb4596..d5485c6a0e20 100644
> --- a/drivers/pinctrl/pinctrl-ocelot.c
> +++ b/drivers/pinctrl/pinctrl-ocelot.c
> @@ -10,6 +10,7 @@
> #include <linux/gpio/driver.h>
> #include <linux/interrupt.h>
> #include <linux/io.h>
> +#include <linux/mfd/core.h>
> #include <linux/of_device.h>
> #include <linux/of_irq.h>
> #include <linux/of_platform.h>
> @@ -20,6 +21,7 @@
> #include <linux/platform_device.h>
> #include <linux/regmap.h>
> #include <linux/slab.h>
> +#include <soc/mscc/ocelot.h>
>
> #include "core.h"
> #include "pinconf.h"
> @@ -1123,6 +1125,9 @@ static int lan966x_pinmux_set_mux(struct pinctrl_dev *pctldev,
> return 0;
> }
>
> +#if defined(REG)
> +#undef REG
> +#endif
> #define REG(r, info, p) ((r) * (info)->stride + (4 * ((p) / 32)))
>
> static int ocelot_gpio_set_direction(struct pinctrl_dev *pctldev,
> @@ -1805,6 +1810,7 @@ static int ocelot_pinctrl_probe(struct platform_device *pdev)
> struct device *dev = &pdev->dev;
> struct ocelot_pinctrl *info;
> struct regmap *pincfg;
> + struct resource *res;
> void __iomem *base;
> int ret;
> struct regmap_config regmap_config = {
> @@ -1819,16 +1825,27 @@ static int ocelot_pinctrl_probe(struct platform_device *pdev)
>
> info->desc = (struct pinctrl_desc *)device_get_match_data(dev);
>
> - base = devm_ioremap_resource(dev,
> - platform_get_resource(pdev, IORESOURCE_MEM, 0));
> - if (IS_ERR(base))
> - return PTR_ERR(base);
> + res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> + if (IS_ERR(res)) {
> + dev_err(dev, "Failed to get resource\n");
> + return PTR_ERR(res);
> + }
>
> info->stride = 1 + (info->desc->npins - 1) / 32;
>
> - regmap_config.max_register = OCELOT_GPIO_SD_MAP * info->stride + 15 * 4;
> + if (!device_is_mfd(pdev)) {
> + base = devm_ioremap_resource(dev, res);
> + if (IS_ERR(base))
> + return PTR_ERR(base);
> +
> + regmap_config.max_register =
> + OCELOT_GPIO_SD_MAP * info->stride + 15 * 4;
> +
> + info->map = devm_regmap_init_mmio(dev, base, ®map_config);
> + } else {
> + info->map = ocelot_get_regmap_from_resource(dev->parent, res);
> + }
>
> - info->map = devm_regmap_init_mmio(dev, base, ®map_config);
> if (IS_ERR(info->map)) {
> dev_err(dev, "Failed to create regmap\n");
> return PTR_ERR(info->map);
> diff --git a/include/soc/mscc/ocelot.h b/include/soc/mscc/ocelot.h
> index 5c3a3597f1d2..70fae9c8b649 100644
> --- a/include/soc/mscc/ocelot.h
> +++ b/include/soc/mscc/ocelot.h
> @@ -969,4 +969,15 @@ ocelot_mrp_del_ring_role(struct ocelot *ocelot, int port,
> }
> #endif
>
> +#if IS_ENABLED(CONFIG_MFD_OCELOT)
> +struct regmap *ocelot_get_regmap_from_resource(struct device *dev,
> + const struct resource *res);
> +#else
> +static inline struct regmap *
> +ocelot_get_regmap_from_resource(struct device *dev, const struct resource *res)
> +{
> + return NULL;
> +}
> +#endif
> +
> #endif
--
Lee Jones [李琼斯]
Principal Technical Lead - Developer Services
Linaro.org │ Open source software for Arm SoCs
Follow Linaro: Facebook | Twitter | Blog
On Sat, Jan 29, 2022 at 02:02:16PM -0800, Colin Foster wrote:
> @@ -257,15 +260,14 @@ static int mscc_miim_probe(struct platform_device *pdev)
> }
> }
>
> - ret = mscc_miim_setup(dev, &bus, "mscc_miim", mii_regmap, 0);
> + ret = mscc_miim_setup(&pdev->dev, &bus, "mscc_miim", mii_regmap, 0,
> + phy_regmap, 0);
> if (ret < 0) {
> dev_err(dev, "Unable to setup the MDIO bus\n");
> return ret;
> }
>
> miim = bus->priv;
You left this variable set but not used. Please delete it.
> - miim->phy_regs = phy_regmap;
> - miim->phy_reset_offset = 0;
>
> ret = of_mdiobus_register(bus, dev->of_node);
> if (ret < 0) {
Hi Lee,
Thank you very much for your time / feedback.
On Mon, Jan 31, 2022 at 09:29:34AM +0000, Lee Jones wrote:
> On Sat, 29 Jan 2022, Colin Foster wrote:
>
> > Create a single SPI MFD ocelot device that manages the SPI bus on the
> > external chip and can handle requests for regmaps. This should allow any
> > ocelot driver (pinctrl, miim, etc.) to be used externally, provided they
> > utilize regmaps.
> >
> > Signed-off-by: Colin Foster <[email protected]>
> > ---
> > drivers/mfd/Kconfig | 19 ++
> > drivers/mfd/Makefile | 3 +
> > drivers/mfd/ocelot-core.c | 165 +++++++++++
> > drivers/mfd/ocelot-spi.c | 325 ++++++++++++++++++++++
> > drivers/mfd/ocelot.h | 36 +++
>
> > drivers/net/mdio/mdio-mscc-miim.c | 21 +-
> > drivers/pinctrl/pinctrl-microchip-sgpio.c | 22 +-
> > drivers/pinctrl/pinctrl-ocelot.c | 29 +-
> > include/soc/mscc/ocelot.h | 11 +
>
> Please avoid mixing subsystems in patches if at all avoidable.
>
> If there are not build time dependencies/breakages, I'd suggest
> firstly applying support for this into MFD *then* utilising that
> support in subsequent patches.
My last RFC did this, and you had suggested to squash the commits. To
clarify, are you suggesting the MFD / Pinctrl get applied in a single
patch, then the MIIM get applied in a separate one? Because I had
started with what sounds like you're describing - an "empty" MFD with
subsequent patches rolling in each subsystem.
Perhaps I misinterpreted your initial feedback.
>
> > 9 files changed, 614 insertions(+), 17 deletions(-)
> > create mode 100644 drivers/mfd/ocelot-core.c
> > create mode 100644 drivers/mfd/ocelot-spi.c
> > create mode 100644 drivers/mfd/ocelot.h
> >
> > diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
> > index ba0b3eb131f1..57bbf2d11324 100644
> > --- a/drivers/mfd/Kconfig
> > +++ b/drivers/mfd/Kconfig
> > @@ -948,6 +948,25 @@ config MFD_MENF21BMC
> > This driver can also be built as a module. If so the module
> > will be called menf21bmc.
> >
> > +config MFD_OCELOT
> > + tristate "Microsemi Ocelot External Control Support"
>
> Please explain exactly what an ECS is in the help below.
I thought I had by way of the second paragraph below. I'm trying to
think of what extra information could be of use at this point...
I could describe how they have internal processors and using this level
of control would basically bypass that functionality.
>
> > + select MFD_CORE
> > + help
> > + Say yes here to add support for Ocelot chips (VSC7511, VSC7512,
> > + VSC7513, VSC7514) controlled externally.
> > +
> > + All four of these chips can be controlled internally (MMIO) or
> > + externally via SPI, I2C, PCIe. This enables control of these chips
> > + over one or more of these buses.
> > +
> > +config MFD_OCELOT_SPI
> > + tristate "Microsemi Ocelot SPI interface"
> > + depends on MFD_OCELOT
> > + depends on SPI_MASTER
> > + select REGMAP_SPI
> > + help
> > + Say yes here to add control to the MFD_OCELOT chips via SPI.
> > +
> > config EZX_PCAP
> > bool "Motorola EZXPCAP Support"
> > depends on SPI_MASTER
> > diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile
> > index df1ecc4a4c95..12513843067a 100644
> > --- a/drivers/mfd/Makefile
> > +++ b/drivers/mfd/Makefile
> > @@ -120,6 +120,9 @@ obj-$(CONFIG_MFD_MC13XXX_I2C) += mc13xxx-i2c.o
> >
> > obj-$(CONFIG_MFD_CORE) += mfd-core.o
> >
> > +obj-$(CONFIG_MFD_OCELOT) += ocelot-core.o
> > +obj-$(CONFIG_MFD_OCELOT_SPI) += ocelot-spi.o
> > +
>
> These do not look lined-up with the remainder of the file.
I'll fix that. Thanks.
>
> > obj-$(CONFIG_EZX_PCAP) += ezx-pcap.o
> > obj-$(CONFIG_MFD_CPCAP) += motorola-cpcap.o
> >
> > diff --git a/drivers/mfd/ocelot-core.c b/drivers/mfd/ocelot-core.c
> > new file mode 100644
> > index 000000000000..590489481b8c
> > --- /dev/null
> > +++ b/drivers/mfd/ocelot-core.c
> > @@ -0,0 +1,165 @@
> > +// SPDX-License-Identifier: (GPL-2.0 OR MIT)
> > +/*
> > + * MFD core driver for the Ocelot chip family.
> > + *
> > + * The VSC7511, 7512, 7513, and 7514 can be controlled internally via an
> > + * on-chip MIPS processor, or externally via SPI, I2C, PCIe. This core driver is
> > + * intended to be the bus-agnostic glue between, for example, the SPI bus and
> > + * the MFD children.
> > + *
> > + * Copyright 2021 Innovative Advantage Inc.
> > + *
> > + * Author: Colin Foster <[email protected]>
> > + */
> > +
> > +#include <linux/mfd/core.h>
> > +#include <linux/module.h>
> > +#include <linux/regmap.h>
> > +
> > +#include <asm/byteorder.h>
> > +
> > +#include "ocelot.h"
> > +
> > +#define GCB_SOFT_RST (0x0008)
>
> Why the brackets?
>
> > +#define SOFT_CHIP_RST (0x1)
>
> As above.
I'll remove them. No reason in particular. Seemingly the convention for
these types of macros are to use parentheses if there's an operation
involved, but not use them otherwise. Makes sense.
>
> > +static const struct resource vsc7512_gcb_resource = {
> > + .start = 0x71070000,
> > + .end = 0x7107022b,
>
> Please define these somewhere.
>
> > + .name = "devcpu_gcb",
> > +};
>
> There is a macro you can use for these.
>
> Grep for "DEFINE_RES_"
Thanks. I didn't know about these. I'll macro these addresses and use
DEFINE_RES.
>
> > +static int ocelot_reset(struct ocelot_core *core)
> > +{
> > + int ret;
> > +
> > + /*
> > + * Reset the entire chip here to put it into a completely known state.
> > + * Other drivers may want to reset their own subsystems. The register
> > + * self-clears, so one write is all that is needed
> > + */
> > + ret = regmap_write(core->gcb_regmap, GCB_SOFT_RST, SOFT_CHIP_RST);
> > + if (ret)
> > + return ret;
> > +
> > + msleep(100);
> > +
> > + /*
> > + * A chip reset will clear the SPI configuration, so it needs to be done
> > + * again before we can access any more registers
> > + */
> > + ret = ocelot_spi_initialize(core);
> > +
> > + return ret;
> > +}
> > +
> > +static struct regmap *ocelot_devm_regmap_init(struct ocelot_core *core,
> > + struct device *dev,
> > + const struct resource *res)
> > +{
> > + struct regmap *regmap;
> > +
> > + regmap = dev_get_regmap(dev, res->name);
> > + if (!regmap)
> > + regmap = ocelot_spi_devm_get_regmap(core, dev, res);
>
> Why are you making SPI specific calls from the Core driver?
This was my interpretation of your initial feedback. It was initially
implemented as a config->get_regmap() function pointer so that core
didn't need to know anything about ocelot_spi.
If function pointers aren't used, it seems like core would have to know
about all possible bus types... Maybe my naming led to some
misunderstandings. Specifically I'd used "init_bus" which was intended
to be "set up the chip to be able to properly communicate via SPI" but
could have been interpreted as "tell the user of this driver that the
bus is being initialized by way of a callback"?
>
> > + return regmap;
> > +}
> > +
> > +struct regmap *ocelot_get_regmap_from_resource(struct device *dev,
> > + const struct resource *res)
> > +{
> > + struct ocelot_core *core = dev_get_drvdata(dev);
> > +
> > + return ocelot_devm_regmap_init(core, dev, res);
> > +}
> > +EXPORT_SYMBOL(ocelot_get_regmap_from_resource);
>
> Why don't you always call ocelot_devm_regmap_init() with the 'core'
> parameter dropped and just do dev_get_drvdata() inside of there?
>
> You're passing 'dev' anyway.
This might be an error. I'll look into this, but I changed the intended
behavior of this between v5 and v6.
In v5 I had intended to attach all regmaps to the spi_device. This way
they could be shared amongst child devices of spi->dev. I think that was
a bad design decision on my part, so I abandoned it. If the child
devices are to share regmaps, they should explicitly do so by way of
syscon, not implicitly by name.
In v6 my intent is to have every regmap be devm-linked to the children.
This way the regmap would be destroyed and recreated by rmmod / insmod,
of the sub-modules, instead of being kept around the MFD module.
So perhaps to clear this up I should rename "dev" to "child" because it
seems that the naming has already gotten too confusing. What I intended
to do was:
struct regmap *ocelot_get_regmap_from_resource(struct device *parent,
struct device *child,
const struct resource *res)
{
struct ocelot_core *core = dev_get_drvdata(parent);
return ocelot_devm_regmap_init(core, child, res);
}
Or maybe even:
struct regmap *ocelot_get_regmap_from_resource(struct device *child,
const struct resource *res)
{
struct ocelot_core *core = dev_get_drvdata(child->parent);
return ocelot_devm_regmap_init(core, child, res);
}
>
> > +static const struct resource vsc7512_miim1_resources[] = {
> > + {
> > + .start = 0x710700c0,
> > + .end = 0x710700e3,
> > + .name = "gcb_miim1",
> > + .flags = IORESOURCE_MEM,
> > + },
> > +};
> > +
> > +static const struct resource vsc7512_pinctrl_resources[] = {
> > + {
> > + .start = 0x71070034,
> > + .end = 0x7107009f,
> > + .name = "gcb_gpio",
> > + .flags = IORESOURCE_MEM,
> > + },
> > +};
> > +
> > +static const struct resource vsc7512_sgpio_resources[] = {
> > + {
> > + .start = 0x710700f8,
> > + .end = 0x710701f7,
> > + .name = "gcb_sio",
> > + .flags = IORESOURCE_MEM,
> > + },
> > +};
> > +
> > +static const struct mfd_cell vsc7512_devs[] = {
> > + {
> > + .name = "pinctrl-ocelot",
>
> <device>-<sub-device>
>
> "ocelot-pinctrl"
>
> > + .of_compatible = "mscc,ocelot-pinctrl",
> > + .num_resources = ARRAY_SIZE(vsc7512_pinctrl_resources),
> > + .resources = vsc7512_pinctrl_resources,
> > + },
> > + {
>
> Same line please.
>
> > + .name = "pinctrl-sgpio",
>
> "ocelot-sgpio"
I'll fix these up. Thanks.
>
> > + .of_compatible = "mscc,ocelot-sgpio",
> > + .num_resources = ARRAY_SIZE(vsc7512_sgpio_resources),
> > + .resources = vsc7512_sgpio_resources,
> > + },
> > + {
> > + .name = "ocelot-miim1",
> > + .of_compatible = "mscc,ocelot-miim",
> > + .num_resources = ARRAY_SIZE(vsc7512_miim1_resources),
> > + .resources = vsc7512_miim1_resources,
> > + },
> > +};
> > +
> > +int ocelot_core_init(struct ocelot_core *core)
> > +{
> > + struct device *dev = core->dev;
> > + int ret;
> > +
> > + dev_set_drvdata(dev, core);
> > +
> > + core->gcb_regmap = ocelot_devm_regmap_init(core, dev,
> > + &vsc7512_gcb_resource);
> > + if (!core->gcb_regmap)
>
> And if an error is returned?
Yes, I should be using IS_ERR here. I'll fix it.
>
> > + return -ENOMEM;
> > +
> > + /* Prepare the chip */
>
> Does it prepare or reset the chip?
>
> If the former, then the following call is misnamed.
>
> if the latter, then there is no need for this comment.
I'll clarify in the source. It resets the chip and sets up the bus so
that registers can be accessed.
I agree the comment is unnecessary.
>
> > + ret = ocelot_reset(core);
> > + if (ret) {
> > + dev_err(dev, "ocelot mfd reset failed with code %d\n", ret);
>
> Isn't the device called 'ocelot'? If so, you just repeated yourself.
>
> "Failed to reset device: %d\n"
Good point. I'll clarify.
>
> > + return ret;
> > + }
> > +
> > + ret = devm_mfd_add_devices(dev, PLATFORM_DEVID_NONE, vsc7512_devs,
>
> Why NONE?
I dont know the implication here. Example taken from
drivers/mfd/madera-core.c. I imagine PLATFORM_DEVID_AUTO is the correct
macro to use here?
>
> > + ARRAY_SIZE(vsc7512_devs), NULL, 0, NULL);
> > + if (ret) {
> > + dev_err(dev, "error adding mfd devices\n");
>
> "Failed to add sub-devices"
>
> > + return ret;
> > + }
> > +
> > + return 0;
> > +}
> > +EXPORT_SYMBOL(ocelot_core_init);
> > +
> > +int ocelot_remove(struct ocelot_core *core)
> > +{
> > + return 0;
> > +}
> > +EXPORT_SYMBOL(ocelot_remove);
>
> What's the propose of this?
It is useless now that I am using devm_mfd_add_devices. I'll remove.
>
> > +MODULE_DESCRIPTION("Ocelot Chip MFD driver");
>
> No such thing as an MFD driver.
Understood. I'll rename and clarify.
>
> > +MODULE_AUTHOR("Colin Foster <[email protected]>");
> > +MODULE_LICENSE("GPL v2");
> > diff --git a/drivers/mfd/ocelot-spi.c b/drivers/mfd/ocelot-spi.c
> > new file mode 100644
> > index 000000000000..1e268a4dfa17
> > --- /dev/null
> > +++ b/drivers/mfd/ocelot-spi.c
> > @@ -0,0 +1,325 @@
> > +// SPDX-License-Identifier: (GPL-2.0 OR MIT)
> > +/*
> > + * SPI core driver for the Ocelot chip family.
> > + *
> > + * This driver will handle everything necessary to allow for communication over
> > + * SPI to the VSC7511, VSC7512, VSC7513 and VSC7514 chips. The main functions
> > + * are to prepare the chip's SPI interface for a specific bus speed, and a host
> > + * processor's endianness. This will create and distribute regmaps for any MFD
> > + * children.
> > + *
> > + * Copyright 2021 Innovative Advantage Inc.
> > + *
> > + * Author: Colin Foster <[email protected]>
> > + */
> > +
> > +#include <linux/iopoll.h>
> > +#include <linux/kconfig.h>
> > +#include <linux/module.h>
> > +#include <linux/of.h>
> > +#include <linux/regmap.h>
> > +#include <linux/spi/spi.h>
> > +
> > +#include <asm/byteorder.h>
> > +
> > +#include "ocelot.h"
> > +
> > +struct ocelot_spi {
> > + int spi_padding_bytes;
> > + struct spi_device *spi;
> > + struct ocelot_core core;
> > + struct regmap *cpuorg_regmap;
> > +};
> > +
> > +#define DEV_CPUORG_IF_CTRL (0x0000)
> > +#define DEV_CPUORG_IF_CFGSTAT (0x0004)
> > +
> > +static const struct resource vsc7512_dev_cpuorg_resource = {
> > + .start = 0x71000000,
> > + .end = 0x710002ff,
> > + .name = "devcpu_org",
> > +};
> > +
> > +#define VSC7512_BYTE_ORDER_LE 0x00000000
> > +#define VSC7512_BYTE_ORDER_BE 0x81818181
> > +#define VSC7512_BIT_ORDER_MSB 0x00000000
> > +#define VSC7512_BIT_ORDER_LSB 0x42424242
> > +
> > +static struct ocelot_spi *core_to_ocelot_spi(struct ocelot_core *core)
> > +{
> > + return container_of(core, struct ocelot_spi, core);
> > +}
>
> See my comments in the header file.
>
> > +static int ocelot_spi_init_bus(struct ocelot_spi *ocelot_spi)
> > +{
> > + struct spi_device *spi;
> > + struct device *dev;
> > + u32 val, check;
> > + int err;
> > +
> > + spi = ocelot_spi->spi;
> > + dev = &spi->dev;
> > +
> > +#ifdef __LITTLE_ENDIAN
> > + val = VSC7512_BYTE_ORDER_LE;
> > +#else
> > + val = VSC7512_BYTE_ORDER_BE;
> > +#endif
> > +
> > + err = regmap_write(ocelot_spi->cpuorg_regmap, DEV_CPUORG_IF_CTRL, val);
> > + if (err)
> > + return err;
> > +
> > + val = ocelot_spi->spi_padding_bytes;
> > + err = regmap_write(ocelot_spi->cpuorg_regmap, DEV_CPUORG_IF_CFGSTAT,
> > + val);
> > + if (err)
> > + return err;
> > +
> > + check = val | 0x02000000;
>
> Either define or comment magic numbers (I prefer the former).
Ahh yes, I missed this one. My apologies.
>
> > + err = regmap_read(ocelot_spi->cpuorg_regmap, DEV_CPUORG_IF_CFGSTAT,
> > + &val);
> > + if (err)
> > + return err;
>
> Comments needed for what you're actually doing here.
Agreed.
>
> > + if (check != val)
> > + return -ENODEV;
> > +
> > + return 0;
> > +}
> > +
> > +int ocelot_spi_initialize(struct ocelot_core *core)
> > +{
> > + struct ocelot_spi *ocelot_spi = core_to_ocelot_spi(core);
> > +
> > + return ocelot_spi_init_bus(ocelot_spi);
> > +}
> > +EXPORT_SYMBOL(ocelot_spi_initialize);
>
> See my comments in the header file.
>
> > +static unsigned int ocelot_spi_translate_address(unsigned int reg)
> > +{
> > + return cpu_to_be32((reg & 0xffffff) >> 2);
> > +}
>
> Comment.
Also agree.
>
> > +struct ocelot_spi_regmap_context {
> > + u32 base;
> > + struct ocelot_spi *ocelot_spi;
> > +};
>
> See my comments in the header file.
>
> > +static int ocelot_spi_reg_read(void *context, unsigned int reg,
> > + unsigned int *val)
> > +{
> > + struct ocelot_spi_regmap_context *regmap_context = context;
> > + struct ocelot_spi *ocelot_spi = regmap_context->ocelot_spi;
> > + struct spi_transfer tx, padding, rx;
> > + struct spi_message msg;
> > + struct spi_device *spi;
> > + unsigned int addr;
> > + u8 *tx_buf;
> > +
> > + WARN_ON(!val);
>
> Is this possible?
Hmm... I don't know if regmap_read guards against val == NULL. It
doesn't look like it does. It is very much a "this should never happen"
moment...
I can remove it, or change this to return an error if !val, which is
what I probably should have done in the first place. Thoughts?
>
> > + spi = ocelot_spi->spi;
> > +
> > + addr = ocelot_spi_translate_address(reg + regmap_context->base);
> > + tx_buf = (u8 *)&addr;
> > +
> > + spi_message_init(&msg);
> > +
> > + memset(&tx, 0, sizeof(struct spi_transfer));
> > +
> > + /* Ignore the first byte for the 24-bit address */
> > + tx.tx_buf = &tx_buf[1];
> > + tx.len = 3;
> > +
> > + spi_message_add_tail(&tx, &msg);
> > +
> > + if (ocelot_spi->spi_padding_bytes > 0) {
> > + u8 dummy_buf[16] = {0};
> > +
> > + memset(&padding, 0, sizeof(struct spi_transfer));
> > +
> > + /* Just toggle the clock for padding bytes */
> > + padding.len = ocelot_spi->spi_padding_bytes;
> > + padding.tx_buf = dummy_buf;
> > + padding.dummy_data = 1;
> > +
> > + spi_message_add_tail(&padding, &msg);
> > + }
> > +
> > + memset(&rx, 0, sizeof(struct spi_transfer));
>
> sizeof(*rx)
Agreed. I'm making this a habit.
>
> > + rx.rx_buf = val;
> > + rx.len = 4;
> > +
> > + spi_message_add_tail(&rx, &msg);
> > +
> > + return spi_sync(spi, &msg);
> > +}
> > +
> > +static int ocelot_spi_reg_write(void *context, unsigned int reg,
> > + unsigned int val)
> > +{
> > + struct ocelot_spi_regmap_context *regmap_context = context;
> > + struct ocelot_spi *ocelot_spi = regmap_context->ocelot_spi;
> > + struct spi_transfer tx[2] = {0};
> > + struct spi_message msg;
> > + struct spi_device *spi;
> > + unsigned int addr;
> > + u8 *tx_buf;
> > +
> > + spi = ocelot_spi->spi;
> > +
> > + addr = ocelot_spi_translate_address(reg + regmap_context->base);
> > + tx_buf = (u8 *)&addr;
> > +
> > + spi_message_init(&msg);
> > +
> > + /* Ignore the first byte for the 24-bit address and set the write bit */
> > + tx_buf[1] |= BIT(7);
> > + tx[0].tx_buf = &tx_buf[1];
> > + tx[0].len = 3;
> > +
> > + spi_message_add_tail(&tx[0], &msg);
> > +
> > + memset(&tx[1], 0, sizeof(struct spi_transfer));
> > + tx[1].tx_buf = &val;
> > + tx[1].len = 4;
> > +
> > + spi_message_add_tail(&tx[1], &msg);
> > +
> > + return spi_sync(spi, &msg);
> > +}
> > +
> > +static const struct regmap_config ocelot_spi_regmap_config = {
> > + .reg_bits = 24,
> > + .reg_stride = 4,
> > + .val_bits = 32,
> > +
> > + .reg_read = ocelot_spi_reg_read,
> > + .reg_write = ocelot_spi_reg_write,
> > +
> > + .max_register = 0xffffffff,
> > + .use_single_write = true,
> > + .use_single_read = true,
> > + .can_multi_write = false,
> > +
> > + .reg_format_endian = REGMAP_ENDIAN_BIG,
> > + .val_format_endian = REGMAP_ENDIAN_NATIVE,
> > +};
> > +
> > +struct regmap *
> > +ocelot_spi_devm_get_regmap(struct ocelot_core *core, struct device *dev,
> > + const struct resource *res)
> > +{
> > + struct ocelot_spi *ocelot_spi = core_to_ocelot_spi(core);
> > + struct ocelot_spi_regmap_context *context;
> > + struct regmap_config regmap_config;
> > + struct regmap *regmap;
> > +
> > + context = devm_kzalloc(dev, sizeof(*context), GFP_KERNEL);
> > + if (IS_ERR(context))
> > + return ERR_CAST(context);
> > +
> > + context->base = res->start;
> > + context->ocelot_spi = ocelot_spi;
> > +
> > + memcpy(®map_config, &ocelot_spi_regmap_config,
> > + sizeof(ocelot_spi_regmap_config));
> > +
> > + regmap_config.name = res->name;
> > + regmap_config.max_register = res->end - res->start;
> > +
> > + regmap = devm_regmap_init(dev, NULL, context, ®map_config);
> > + if (IS_ERR(regmap))
> > + return ERR_CAST(regmap);
> > +
> > + return regmap;
> > +}
> > +
> > +static int ocelot_spi_probe(struct spi_device *spi)
> > +{
> > + struct device *dev = &spi->dev;
> > + struct ocelot_spi *ocelot_spi;
> > + int err;
> > +
> > + ocelot_spi = devm_kzalloc(dev, sizeof(*ocelot_spi), GFP_KERNEL);
> > +
> > + if (!ocelot_spi)
> > + return -ENOMEM;
> > +
> > + if (spi->max_speed_hz <= 500000) {
> > + ocelot_spi->spi_padding_bytes = 0;
> > + } else {
> > + /*
> > + * Calculation taken from the manual for IF_CFGSTAT:IF_CFG.
> > + * Register access time is 1us, so we need to configure and send
> > + * out enough padding bytes between the read request and data
> > + * transmission that lasts at least 1 microsecond.
> > + */
> > + ocelot_spi->spi_padding_bytes = 1 +
> > + (spi->max_speed_hz / 1000000 + 2) / 8;
> > + }
> > +
> > + ocelot_spi->spi = spi;
> > +
> > + spi->bits_per_word = 8;
> > +
> > + err = spi_setup(spi);
> > + if (err < 0) {
> > + dev_err(&spi->dev, "Error %d initializing SPI\n", err);
> > + return err;
> > + }
> > +
> > + ocelot_spi->cpuorg_regmap =
> > + ocelot_spi_devm_get_regmap(&ocelot_spi->core, dev,
> > + &vsc7512_dev_cpuorg_resource);
> > + if (!ocelot_spi->cpuorg_regmap)
>
> And if an error is returned?
As above, IS_ERR should be used. Thanks for pointing these out.
>
> > + return -ENOMEM;
> > +
> > + ocelot_spi->core.dev = dev;
> > +
> > + /*
> > + * The chip must be set up for SPI before it gets initialized and reset.
> > + * This must be done before calling init, and after a chip reset is
> > + * performed.
> > + */
> > + err = ocelot_spi_init_bus(ocelot_spi);
> > + if (err) {
> > + dev_err(dev, "Error %d initializing Ocelot SPI bus\n", err);
> > + return err;
> > + }
> > +
> > + err = ocelot_core_init(&ocelot_spi->core);
> > + if (err < 0) {
> > + dev_err(dev, "Error %d initializing Ocelot MFD\n", err);
> > + return err;
> > + }
> > +
> > + return 0;
> > +}
> > +
> > +static int ocelot_spi_remove(struct spi_device *spi)
> > +{
> > + return 0;
> > +}
> > +
> > +const struct of_device_id ocelot_spi_of_match[] = {
> > + { .compatible = "mscc,vsc7512_mfd_spi" },
> > + { },
> > +};
> > +MODULE_DEVICE_TABLE(of, ocelot_spi_of_match);
> > +
> > +static struct spi_driver ocelot_spi_driver = {
> > + .driver = {
> > + .name = "ocelot_mfd_spi",
> > + .of_match_table = of_match_ptr(ocelot_spi_of_match),
> > + },
> > + .probe = ocelot_spi_probe,
> > + .remove = ocelot_spi_remove,
> > +};
> > +module_spi_driver(ocelot_spi_driver);
> > +
> > +MODULE_DESCRIPTION("Ocelot Chip MFD SPI driver");
> > +MODULE_AUTHOR("Colin Foster <[email protected]>");
> > +MODULE_LICENSE("Dual MIT/GPL");
> > diff --git a/drivers/mfd/ocelot.h b/drivers/mfd/ocelot.h
> > new file mode 100644
> > index 000000000000..8bb2b57002be
> > --- /dev/null
> > +++ b/drivers/mfd/ocelot.h
> > @@ -0,0 +1,36 @@
> > +/* SPDX-License-Identifier: GPL-2.0 OR MIT */
> > +/*
> > + * Copyright 2021 Innovative Advantage Inc.
> > + */
> > +
> > +#include <linux/kconfig.h>
> > +#include <linux/regmap.h>
> > +
> > +struct ocelot_core {
> > + struct device *dev;
> > + struct regmap *gcb_regmap;
> > +};
>
> Please drop this over-complicated 'core' and 'spi' stuff.
>
> You spend too much effort converting between 'dev', 'core' and 'spi'.
>
> I suggest you just pass 'dev' around as your key parameter.
>
> Any additional attributes you *need" to carry around can do in:
>
> struct ocelot *ddata;
Understood. I'll take another look and it'll probably clean things up
quite a bit, as you suggest.
>
> > +void ocelot_get_resource_name(char *name, const struct resource *res,
> > + int size);
> > +int ocelot_core_init(struct ocelot_core *core);
> > +int ocelot_remove(struct ocelot_core *core);
> > +
> > +#if IS_ENABLED(CONFIG_MFD_OCELOT_SPI)
> > +struct regmap *ocelot_spi_devm_get_regmap(struct ocelot_core *core,
> > + struct device *dev,
> > + const struct resource *res);
> > +int ocelot_spi_initialize(struct ocelot_core *core);
> > +#else
> > +static inline struct regmap *ocelot_spi_devm_get_regmap(
> > + struct ocelot_core *core, struct device *dev,
> > + const struct resource *res)
> > +{
> > + return NULL;
> > +}
> > +
> > +static inline int ocelot_spi_initialize(struct ocelot_core *core)
> > +{
> > + return -EOPNOTSUPP;
> > +}
> > +#endif
> > diff --git a/drivers/net/mdio/mdio-mscc-miim.c b/drivers/net/mdio/mdio-mscc-miim.c
> > index 07baf8390744..8e54bde06fd5 100644
> > --- a/drivers/net/mdio/mdio-mscc-miim.c
> > +++ b/drivers/net/mdio/mdio-mscc-miim.c
> > @@ -11,11 +11,13 @@
> > #include <linux/iopoll.h>
> > #include <linux/kernel.h>
> > #include <linux/mdio/mdio-mscc-miim.h>
> > +#include <linux/mfd/core.h>
> > #include <linux/module.h>
> > #include <linux/of_mdio.h>
> > #include <linux/phy.h>
> > #include <linux/platform_device.h>
> > #include <linux/regmap.h>
> > +#include <soc/mscc/ocelot.h>
> >
> > #define MSCC_MIIM_REG_STATUS 0x0
> > #define MSCC_MIIM_STATUS_STAT_PENDING BIT(2)
> > @@ -230,13 +232,20 @@ static int mscc_miim_probe(struct platform_device *pdev)
> > struct mii_bus *bus;
> > int ret;
> >
> > - regs = devm_platform_get_and_ioremap_resource(pdev, 0, NULL);
> > - if (IS_ERR(regs)) {
> > - dev_err(dev, "Unable to map MIIM registers\n");
> > - return PTR_ERR(regs);
> > - }
> > + res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> > +
> > + if (!device_is_mfd(pdev)) {
> > + regs = devm_ioremap_resource(dev, res);
> > + if (IS_ERR(regs)) {
> > + dev_err(dev, "Unable to map MIIM registers\n");
> > + return PTR_ERR(regs);
> > + }
> >
> > - mii_regmap = devm_regmap_init_mmio(dev, regs, &mscc_miim_regmap_config);
> > + mii_regmap = devm_regmap_init_mmio(dev, regs,
> > + &mscc_miim_regmap_config);
>
> These tabs look wrong.
>
> Doesn't checkpatch.pl warn about stuff like this?
It does say to check, yes. I missed that. Apologies.
> > + } else {
> > + mii_regmap = ocelot_get_regmap_from_resource(dev->parent, res);
> > + }
>
> You need a comment to explain why you're calling both of these.
Agreed. Whether we keep this method or not I'll clarify in all places
where this is used.
>
> > if (IS_ERR(mii_regmap)) {
> > dev_err(dev, "Unable to create MIIM regmap\n");
> > diff --git a/drivers/pinctrl/pinctrl-microchip-sgpio.c b/drivers/pinctrl/pinctrl-microchip-sgpio.c
> > index 8db3caf15cf2..53df095b33e0 100644
> > --- a/drivers/pinctrl/pinctrl-microchip-sgpio.c
> > +++ b/drivers/pinctrl/pinctrl-microchip-sgpio.c
> > @@ -12,6 +12,7 @@
> > #include <linux/clk.h>
> > #include <linux/gpio/driver.h>
> > #include <linux/io.h>
> > +#include <linux/mfd/core.h>
> > #include <linux/mod_devicetable.h>
> > #include <linux/module.h>
> > #include <linux/pinctrl/pinmux.h>
> > @@ -19,6 +20,7 @@
> > #include <linux/property.h>
> > #include <linux/regmap.h>
> > #include <linux/reset.h>
> > +#include <soc/mscc/ocelot.h>
> >
> > #include "core.h"
> > #include "pinconf.h"
> > @@ -137,7 +139,9 @@ static inline int sgpio_addr_to_pin(struct sgpio_priv *priv, int port, int bit)
> >
> > static inline u32 sgpio_get_addr(struct sgpio_priv *priv, u32 rno, u32 off)
> > {
> > - return priv->properties->regoff[rno] + off;
> > + int stride = regmap_get_reg_stride(priv->regs);
> > +
> > + return (priv->properties->regoff[rno] + off) * stride;
> > }
> >
> > static u32 sgpio_readl(struct sgpio_priv *priv, u32 rno, u32 off)
> > @@ -818,6 +822,7 @@ static int microchip_sgpio_probe(struct platform_device *pdev)
> > struct fwnode_handle *fwnode;
> > struct reset_control *reset;
> > struct sgpio_priv *priv;
> > + struct resource *res;
> > struct clk *clk;
> > u32 __iomem *regs;
> > u32 val;
> > @@ -850,11 +855,18 @@ static int microchip_sgpio_probe(struct platform_device *pdev)
> > return -EINVAL;
> > }
> >
> > - regs = devm_platform_ioremap_resource(pdev, 0);
> > - if (IS_ERR(regs))
> > - return PTR_ERR(regs);
> > + res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> > +
> > + if (!device_is_mfd(pdev)) {
> > + regs = devm_ioremap_resource(dev, res);
>
> What happens if you call this if the device was registered via MFD?
I don't recall if it was your suggestion, but I tried this.
devm_ioremap_resource on the MFD triggered a kernel crash. I didn't look
much more into things than that, but if trying devm_ioremap_resource and
falling back to ocelot_get_regmap_from_resource is the desired path, I
can investigate further.
>
> > + if (IS_ERR(regs))
> > + return PTR_ERR(regs);
> > +
> > + priv->regs = devm_regmap_init_mmio(dev, regs, ®map_config);
> > + } else {
> > + priv->regs = ocelot_get_regmap_from_resource(dev->parent, res);
> > + }
> >
> > - priv->regs = devm_regmap_init_mmio(dev, regs, ®map_config);
> > if (IS_ERR(priv->regs))
> > return PTR_ERR(priv->regs);
> >
> > diff --git a/drivers/pinctrl/pinctrl-ocelot.c b/drivers/pinctrl/pinctrl-ocelot.c
> > index b6ad3ffb4596..d5485c6a0e20 100644
> > --- a/drivers/pinctrl/pinctrl-ocelot.c
> > +++ b/drivers/pinctrl/pinctrl-ocelot.c
> > @@ -10,6 +10,7 @@
> > #include <linux/gpio/driver.h>
> > #include <linux/interrupt.h>
> > #include <linux/io.h>
> > +#include <linux/mfd/core.h>
> > #include <linux/of_device.h>
> > #include <linux/of_irq.h>
> > #include <linux/of_platform.h>
> > @@ -20,6 +21,7 @@
> > #include <linux/platform_device.h>
> > #include <linux/regmap.h>
> > #include <linux/slab.h>
> > +#include <soc/mscc/ocelot.h>
> >
> > #include "core.h"
> > #include "pinconf.h"
> > @@ -1123,6 +1125,9 @@ static int lan966x_pinmux_set_mux(struct pinctrl_dev *pctldev,
> > return 0;
> > }
> >
> > +#if defined(REG)
> > +#undef REG
> > +#endif
> > #define REG(r, info, p) ((r) * (info)->stride + (4 * ((p) / 32)))
> >
> > static int ocelot_gpio_set_direction(struct pinctrl_dev *pctldev,
> > @@ -1805,6 +1810,7 @@ static int ocelot_pinctrl_probe(struct platform_device *pdev)
> > struct device *dev = &pdev->dev;
> > struct ocelot_pinctrl *info;
> > struct regmap *pincfg;
> > + struct resource *res;
> > void __iomem *base;
> > int ret;
> > struct regmap_config regmap_config = {
> > @@ -1819,16 +1825,27 @@ static int ocelot_pinctrl_probe(struct platform_device *pdev)
> >
> > info->desc = (struct pinctrl_desc *)device_get_match_data(dev);
> >
> > - base = devm_ioremap_resource(dev,
> > - platform_get_resource(pdev, IORESOURCE_MEM, 0));
> > - if (IS_ERR(base))
> > - return PTR_ERR(base);
> > + res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> > + if (IS_ERR(res)) {
> > + dev_err(dev, "Failed to get resource\n");
> > + return PTR_ERR(res);
> > + }
> >
> > info->stride = 1 + (info->desc->npins - 1) / 32;
> >
> > - regmap_config.max_register = OCELOT_GPIO_SD_MAP * info->stride + 15 * 4;
> > + if (!device_is_mfd(pdev)) {
> > + base = devm_ioremap_resource(dev, res);
> > + if (IS_ERR(base))
> > + return PTR_ERR(base);
> > +
> > + regmap_config.max_register =
> > + OCELOT_GPIO_SD_MAP * info->stride + 15 * 4;
> > +
> > + info->map = devm_regmap_init_mmio(dev, base, ®map_config);
> > + } else {
> > + info->map = ocelot_get_regmap_from_resource(dev->parent, res);
> > + }
> >
> > - info->map = devm_regmap_init_mmio(dev, base, ®map_config);
> > if (IS_ERR(info->map)) {
> > dev_err(dev, "Failed to create regmap\n");
> > return PTR_ERR(info->map);
> > diff --git a/include/soc/mscc/ocelot.h b/include/soc/mscc/ocelot.h
> > index 5c3a3597f1d2..70fae9c8b649 100644
> > --- a/include/soc/mscc/ocelot.h
> > +++ b/include/soc/mscc/ocelot.h
> > @@ -969,4 +969,15 @@ ocelot_mrp_del_ring_role(struct ocelot *ocelot, int port,
> > }
> > #endif
> >
> > +#if IS_ENABLED(CONFIG_MFD_OCELOT)
> > +struct regmap *ocelot_get_regmap_from_resource(struct device *dev,
> > + const struct resource *res);
> > +#else
> > +static inline struct regmap *
> > +ocelot_get_regmap_from_resource(struct device *dev, const struct resource *res)
> > +{
> > + return NULL;
> > +}
> > +#endif
> > +
> > #endif
>
> --
> Lee Jones [李琼斯]
> Principal Technical Lead - Developer Services
> Linaro.org │ Open source software for Arm SoCs
> Follow Linaro: Facebook | Twitter | Blog
On Sat, Jan 29, 2022 at 02:02:15PM -0800, Colin Foster wrote:
> Create a local device *dev in order to not dereference the platform_device
> several times throughout the probe function.
>
> Signed-off-by: Colin Foster <[email protected]>
> ---
Reviewed-by: Vladimir Oltean <[email protected]>
Hi Vladimir,
On Mon, Jan 31, 2022 at 05:13:19PM +0000, Vladimir Oltean wrote:
> On Sat, Jan 29, 2022 at 02:02:16PM -0800, Colin Foster wrote:
> > @@ -257,15 +260,14 @@ static int mscc_miim_probe(struct platform_device *pdev)
> > }
> > }
> >
> > - ret = mscc_miim_setup(dev, &bus, "mscc_miim", mii_regmap, 0);
> > + ret = mscc_miim_setup(&pdev->dev, &bus, "mscc_miim", mii_regmap, 0,
> > + phy_regmap, 0);
> > if (ret < 0) {
> > dev_err(dev, "Unable to setup the MDIO bus\n");
> > return ret;
> > }
> >
> > miim = bus->priv;
>
> You left this variable set but not used. Please delete it.
Correct. Good catch. There were a couple of these in v6 that
kernel-test-robot is happy to point out to me :-)
Already fixed in my v7 branch.
>
> > - miim->phy_regs = phy_regmap;
> > - miim->phy_reset_offset = 0;
> >
> > ret = of_mdiobus_register(bus, dev->of_node);
> > if (ret < 0) {
On Sat, Jan 29, 2022 at 02:02:20PM -0800, Colin Foster wrote:
> The define FELIX_MAC_QUIRKS was used directly in the felix.c shared driver.
> Other devices (VSC7512 for example) don't require the same quirks, so they
> need to be configured on a per-device basis.
>
> Signed-off-by: Colin Foster <[email protected]>
> ---
Reviewed-by: Vladimir Oltean <[email protected]>
Just one small comment.
> diff --git a/drivers/net/dsa/ocelot/felix.h b/drivers/net/dsa/ocelot/felix.h
> index 9395ac119d33..f35894b06ce5 100644
> --- a/drivers/net/dsa/ocelot/felix.h
> +++ b/drivers/net/dsa/ocelot/felix.h
> @@ -26,6 +26,7 @@ struct felix_info {
> u16 vcap_pol_base2;
> u16 vcap_pol_max2;
> const struct ptp_clock_info *ptp_caps;
> + u32 quirks;
It's an "unsigned long" when passed to ocelot_phylink_mac_link_{up,down},
so please keep it the same here.
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 <[email protected]>
> ---
> 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
>
On Mon, Jan 31, 2022 at 03:11:55PM -0800, Florian Fainelli wrote:
>
>
> On 1/29/2022 2:02 PM, Colin Foster wrote:
> > As the commit message suggests, this simply adds the ability to select
> > SGPIO pinctrl as a module. This becomes more practical when the SGPIO
> > hardware exists on an external chip, controlled indirectly by I2C or SPI.
> > This commit enables that level of control.
> >
> > Signed-off-by: Colin Foster <[email protected]>
>
> Reviewed-by: Florian Fainelli <[email protected]>
Thanks Florian
> --
> Florian
On 1/29/2022 2:02 PM, Colin Foster wrote:
> Create a local device *dev in order to not dereference the platform_device
> several times throughout the probe function.
>
> Signed-off-by: Colin Foster <[email protected]>
Reviewed-by: Florian Fainelli <[email protected]>
--
Florian
On 1/29/2022 2:02 PM, Colin Foster wrote:
> As the commit message suggests, this simply adds the ability to select
> SGPIO pinctrl as a module. This becomes more practical when the SGPIO
> hardware exists on an external chip, controlled indirectly by I2C or SPI.
> This commit enables that level of control.
>
> Signed-off-by: Colin Foster <[email protected]>
Reviewed-by: Florian Fainelli <[email protected]>
--
Florian
On Mon, 31 Jan 2022, Colin Foster wrote:
> Hi Lee,
>
> Thank you very much for your time / feedback.
>
> On Mon, Jan 31, 2022 at 09:29:34AM +0000, Lee Jones wrote:
> > On Sat, 29 Jan 2022, Colin Foster wrote:
> >
> > > Create a single SPI MFD ocelot device that manages the SPI bus on the
> > > external chip and can handle requests for regmaps. This should allow any
> > > ocelot driver (pinctrl, miim, etc.) to be used externally, provided they
> > > utilize regmaps.
> > >
> > > Signed-off-by: Colin Foster <[email protected]>
> > > ---
> > > drivers/mfd/Kconfig | 19 ++
> > > drivers/mfd/Makefile | 3 +
> > > drivers/mfd/ocelot-core.c | 165 +++++++++++
> > > drivers/mfd/ocelot-spi.c | 325 ++++++++++++++++++++++
> > > drivers/mfd/ocelot.h | 36 +++
> >
> > > drivers/net/mdio/mdio-mscc-miim.c | 21 +-
> > > drivers/pinctrl/pinctrl-microchip-sgpio.c | 22 +-
> > > drivers/pinctrl/pinctrl-ocelot.c | 29 +-
> > > include/soc/mscc/ocelot.h | 11 +
> >
> > Please avoid mixing subsystems in patches if at all avoidable.
> >
> > If there are not build time dependencies/breakages, I'd suggest
> > firstly applying support for this into MFD *then* utilising that
> > support in subsequent patches.
>
> My last RFC did this, and you had suggested to squash the commits. To
> clarify, are you suggesting the MFD / Pinctrl get applied in a single
> patch, then the MIIM get applied in a separate one? Because I had
> started with what sounds like you're describing - an "empty" MFD with
> subsequent patches rolling in each subsystem.
>
> Perhaps I misinterpreted your initial feedback.
I want you to add all device support into the MFD driver at once.
The associated drivers, the ones that live in other subsystems, should
be applied as separate patches. There seldom exist any *build time*
dependencies between the device side and the driver side.
> > > 9 files changed, 614 insertions(+), 17 deletions(-)
> > > create mode 100644 drivers/mfd/ocelot-core.c
> > > create mode 100644 drivers/mfd/ocelot-spi.c
> > > create mode 100644 drivers/mfd/ocelot.h
> > >
> > > diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
> > > index ba0b3eb131f1..57bbf2d11324 100644
> > > --- a/drivers/mfd/Kconfig
> > > +++ b/drivers/mfd/Kconfig
> > > @@ -948,6 +948,25 @@ config MFD_MENF21BMC
> > > This driver can also be built as a module. If so the module
> > > will be called menf21bmc.
> > >
> > > +config MFD_OCELOT
> > > + tristate "Microsemi Ocelot External Control Support"
> >
> > Please explain exactly what an ECS is in the help below.
>
> I thought I had by way of the second paragraph below. I'm trying to
> think of what extra information could be of use at this point...
>
> I could describe how they have internal processors and using this level
> of control would basically bypass that functionality.
Yes please.
Also provide details about what the device actually does.
> > > +static struct regmap *ocelot_devm_regmap_init(struct ocelot_core *core,
> > > + struct device *dev,
> > > + const struct resource *res)
> > > +{
> > > + struct regmap *regmap;
> > > +
> > > + regmap = dev_get_regmap(dev, res->name);
> > > + if (!regmap)
> > > + regmap = ocelot_spi_devm_get_regmap(core, dev, res);
> >
> > Why are you making SPI specific calls from the Core driver?
>
> This was my interpretation of your initial feedback. It was initially
> implemented as a config->get_regmap() function pointer so that core
> didn't need to know anything about ocelot_spi.
>
> If function pointers aren't used, it seems like core would have to know
> about all possible bus types... Maybe my naming led to some
> misunderstandings. Specifically I'd used "init_bus" which was intended
> to be "set up the chip to be able to properly communicate via SPI" but
> could have been interpreted as "tell the user of this driver that the
> bus is being initialized by way of a callback"?
Okay, I see what's happening now.
Please add a comment to describe why you're calling one helper, what
failure means in the first instance and what you hope to achieve by
calling the subsequent one.
> > > + return regmap;
> > > +}
> > > +
> > > +struct regmap *ocelot_get_regmap_from_resource(struct device *dev,
> > > + const struct resource *res)
> > > +{
> > > + struct ocelot_core *core = dev_get_drvdata(dev);
> > > +
> > > + return ocelot_devm_regmap_init(core, dev, res);
> > > +}
> > > +EXPORT_SYMBOL(ocelot_get_regmap_from_resource);
> >
> > Why don't you always call ocelot_devm_regmap_init() with the 'core'
> > parameter dropped and just do dev_get_drvdata() inside of there?
> >
> > You're passing 'dev' anyway.
>
> This might be an error. I'll look into this, but I changed the intended
> behavior of this between v5 and v6.
>
> In v5 I had intended to attach all regmaps to the spi_device. This way
> they could be shared amongst child devices of spi->dev. I think that was
> a bad design decision on my part, so I abandoned it. If the child
> devices are to share regmaps, they should explicitly do so by way of
> syscon, not implicitly by name.
>
> In v6 my intent is to have every regmap be devm-linked to the children.
> This way the regmap would be destroyed and recreated by rmmod / insmod,
> of the sub-modules, instead of being kept around the MFD module.
What's the reason for using an MFD to handle the Regmap(s) if you're
going to have per-device ones anyway? Why not handle them in the
children?
> So perhaps to clear this up I should rename "dev" to "child" because it
> seems that the naming has already gotten too confusing. What I intended
> to do was:
>
> struct regmap *ocelot_get_regmap_from_resource(struct device *parent,
> struct device *child,
> const struct resource *res)
> {
> struct ocelot_core *core = dev_get_drvdata(parent);
>
> return ocelot_devm_regmap_init(core, child, res);
> }
>
> Or maybe even:
> struct regmap *ocelot_get_regmap_from_resource(struct device *child,
> const struct resource *res)
> {
> struct ocelot_core *core = dev_get_drvdata(child->parent);
>
> return ocelot_devm_regmap_init(core, child, res);
> }
Or just call:
ocelot_devm_regmap_init(core, dev->parent, res);
... from the original call-site?
Or, as I previously suggested:
ocelot_devm_regmap_init(dev->parent, res);
[...]
> > > + ret = devm_mfd_add_devices(dev, PLATFORM_DEVID_NONE, vsc7512_devs,
> >
> > Why NONE?
>
> I dont know the implication here. Example taken from
> drivers/mfd/madera-core.c. I imagine PLATFORM_DEVID_AUTO is the correct
> macro to use here?
That's why I asked. Please read-up on the differences and use the
correct one for your device instead of just blindly copy/pasting from
other sources. :)
[...]
> > > + WARN_ON(!val);
> >
> > Is this possible?
>
> Hmm... I don't know if regmap_read guards against val == NULL. It
> doesn't look like it does. It is very much a "this should never happen"
> moment...
>
> I can remove it, or change this to return an error if !val, which is
> what I probably should have done in the first place. Thoughts?
Not really. Just make sure whatever you decide to do is informed.
[...]
> > > - regs = devm_platform_ioremap_resource(pdev, 0);
> > > - if (IS_ERR(regs))
> > > - return PTR_ERR(regs);
> > > + res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> > > +
> > > + if (!device_is_mfd(pdev)) {
> > > + regs = devm_ioremap_resource(dev, res);
> >
> > What happens if you call this if the device was registered via MFD?
>
> I don't recall if it was your suggestion, but I tried this.
> devm_ioremap_resource on the MFD triggered a kernel crash. I didn't look
> much more into things than that, but if trying devm_ioremap_resource and
> falling back to ocelot_get_regmap_from_resource is the desired path, I
> can investigate further.
Yes please. It should never crash. That's probably a bug.
--
Lee Jones [李琼斯]
Principal Technical Lead - Developer Services
Linaro.org │ Open source software for Arm SoCs
Follow Linaro: Facebook | Twitter | Blog
Hello Lee,
On Tue, Feb 01, 2022 at 07:36:21AM +0000, Lee Jones wrote:
> On Mon, 31 Jan 2022, Colin Foster wrote:
>
> > Hi Lee,
> >
> > Thank you very much for your time / feedback.
> >
> > On Mon, Jan 31, 2022 at 09:29:34AM +0000, Lee Jones wrote:
> > > On Sat, 29 Jan 2022, Colin Foster wrote:
> > >
> > > > Create a single SPI MFD ocelot device that manages the SPI bus on the
> > > > external chip and can handle requests for regmaps. This should allow any
> > > > ocelot driver (pinctrl, miim, etc.) to be used externally, provided they
> > > > utilize regmaps.
> > > >
> > > > Signed-off-by: Colin Foster <[email protected]>
> > > > ---
> > > > drivers/mfd/Kconfig | 19 ++
> > > > drivers/mfd/Makefile | 3 +
> > > > drivers/mfd/ocelot-core.c | 165 +++++++++++
> > > > drivers/mfd/ocelot-spi.c | 325 ++++++++++++++++++++++
> > > > drivers/mfd/ocelot.h | 36 +++
> > >
> > > > drivers/net/mdio/mdio-mscc-miim.c | 21 +-
> > > > drivers/pinctrl/pinctrl-microchip-sgpio.c | 22 +-
> > > > drivers/pinctrl/pinctrl-ocelot.c | 29 +-
> > > > include/soc/mscc/ocelot.h | 11 +
> > >
> > > Please avoid mixing subsystems in patches if at all avoidable.
> > >
> > > If there are not build time dependencies/breakages, I'd suggest
> > > firstly applying support for this into MFD *then* utilising that
> > > support in subsequent patches.
> >
> > My last RFC did this, and you had suggested to squash the commits. To
> > clarify, are you suggesting the MFD / Pinctrl get applied in a single
> > patch, then the MIIM get applied in a separate one? Because I had
> > started with what sounds like you're describing - an "empty" MFD with
> > subsequent patches rolling in each subsystem.
> >
> > Perhaps I misinterpreted your initial feedback.
>
> I want you to add all device support into the MFD driver at once.
>
> The associated drivers, the ones that live in other subsystems, should
> be applied as separate patches. There seldom exist any *build time*
> dependencies between the device side and the driver side.
The sub-devices are modified to use ocelot_get_regmap_from_resource. I
suppose I can add the inline stub function in drivers/mfd/ocelot.h,
which wouldn't break functionality. I'll do that in the next RFC.
Thanks for clarifying!
>
> > > > 9 files changed, 614 insertions(+), 17 deletions(-)
> > > > create mode 100644 drivers/mfd/ocelot-core.c
> > > > create mode 100644 drivers/mfd/ocelot-spi.c
> > > > create mode 100644 drivers/mfd/ocelot.h
> > > >
> > > > diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
> > > > index ba0b3eb131f1..57bbf2d11324 100644
> > > > --- a/drivers/mfd/Kconfig
> > > > +++ b/drivers/mfd/Kconfig
> > > > @@ -948,6 +948,25 @@ config MFD_MENF21BMC
> > > > This driver can also be built as a module. If so the module
> > > > will be called menf21bmc.
> > > >
> > > > +config MFD_OCELOT
> > > > + tristate "Microsemi Ocelot External Control Support"
> > >
> > > Please explain exactly what an ECS is in the help below.
> >
> > I thought I had by way of the second paragraph below. I'm trying to
> > think of what extra information could be of use at this point...
> >
> > I could describe how they have internal processors and using this level
> > of control would basically bypass that functionality.
>
> Yes please.
>
> Also provide details about what the device actually does.
Got it.
>
> > > > +static struct regmap *ocelot_devm_regmap_init(struct ocelot_core *core,
> > > > + struct device *dev,
> > > > + const struct resource *res)
> > > > +{
> > > > + struct regmap *regmap;
> > > > +
> > > > + regmap = dev_get_regmap(dev, res->name);
> > > > + if (!regmap)
> > > > + regmap = ocelot_spi_devm_get_regmap(core, dev, res);
> > >
> > > Why are you making SPI specific calls from the Core driver?
> >
> > This was my interpretation of your initial feedback. It was initially
> > implemented as a config->get_regmap() function pointer so that core
> > didn't need to know anything about ocelot_spi.
> >
> > If function pointers aren't used, it seems like core would have to know
> > about all possible bus types... Maybe my naming led to some
> > misunderstandings. Specifically I'd used "init_bus" which was intended
> > to be "set up the chip to be able to properly communicate via SPI" but
> > could have been interpreted as "tell the user of this driver that the
> > bus is being initialized by way of a callback"?
>
> Okay, I see what's happening now.
>
> Please add a comment to describe why you're calling one helper, what
> failure means in the first instance and what you hope to achieve by
> calling the subsequent one.
Will do.
>
> > > > + return regmap;
> > > > +}
> > > > +
> > > > +struct regmap *ocelot_get_regmap_from_resource(struct device *dev,
> > > > + const struct resource *res)
> > > > +{
> > > > + struct ocelot_core *core = dev_get_drvdata(dev);
> > > > +
> > > > + return ocelot_devm_regmap_init(core, dev, res);
> > > > +}
> > > > +EXPORT_SYMBOL(ocelot_get_regmap_from_resource);
> > >
> > > Why don't you always call ocelot_devm_regmap_init() with the 'core'
> > > parameter dropped and just do dev_get_drvdata() inside of there?
> > >
> > > You're passing 'dev' anyway.
> >
> > This might be an error. I'll look into this, but I changed the intended
> > behavior of this between v5 and v6.
> >
> > In v5 I had intended to attach all regmaps to the spi_device. This way
> > they could be shared amongst child devices of spi->dev. I think that was
> > a bad design decision on my part, so I abandoned it. If the child
> > devices are to share regmaps, they should explicitly do so by way of
> > syscon, not implicitly by name.
> >
> > In v6 my intent is to have every regmap be devm-linked to the children.
> > This way the regmap would be destroyed and recreated by rmmod / insmod,
> > of the sub-modules, instead of being kept around the MFD module.
>
> What's the reason for using an MFD to handle the Regmap(s) if you're
> going to have per-device ones anyway? Why not handle them in the
> children?
Also addressing the suggestion below:
ocelot_core is the MFD "regmap-giver". It knows how to get a regmap from
Ocelot SPI and hand it to the child.
In order to do this, ocelot_core needs to know information about
ocelot-spi. As you pointed out, there's a cleaner way to do this without
jumping between core, spi, and dev. I agree.
However, the ocelot-spi priv data is tied to ocelot_core->dev. In order
to recover that information, ocelot-core needs to know about a child
device's "dev->parent".
So in v5, I had only used this "core->dev", which eventually burrowed
down into devm_regmap_init().
What this would mean to the user is if they ran "modprobe
ocelot-pinctrl; rmmod ocelot-pinctrl" there would be a debugfs interface
left at /sys/kernel/debug/regmap/spi0.0-gcb_gpio that was created for
ocelot-pinctrl, but abandoned.
I feel like that is incorrect behavior. "rmmod ocelot-pinctrl" should
destroy the regmap, since it is unused. In fact, it would probably break
upon subsequent "modprobe" commands, since it would try to register a
regmap of the same name but to a different device instance.
In order to achieve this, _two_ devices are required to pass around: the
"core->dev" (the same as child->parent) to get all information needed
about SPI, and the "child->dev" to get attached in devm_regmap_init().
As an example: ocelot_core needs a regmap for resetting the chip. It
would call:
ocelot_devm_regmap_init(dev, dev, res);
The first "dev" is used to get core information, the second is used to
in devm_*
A child device like ocelot-pinctrl would use something like:
ocelot_devm_regmap_init(dev->parent, dev, res);
This second "dev" argument wasn't in v5 because I believe I tried to
implicitly share regmaps. That would've led to the stale debugfs
reference I mentioned above, which I think is pretty undesireable.
>
> > So perhaps to clear this up I should rename "dev" to "child" because it
> > seems that the naming has already gotten too confusing. What I intended
> > to do was:
> >
> > struct regmap *ocelot_get_regmap_from_resource(struct device *parent,
> > struct device *child,
> > const struct resource *res)
> > {
> > struct ocelot_core *core = dev_get_drvdata(parent);
> >
> > return ocelot_devm_regmap_init(core, child, res);
> > }
> >
> > Or maybe even:
> > struct regmap *ocelot_get_regmap_from_resource(struct device *child,
> > const struct resource *res)
> > {
> > struct ocelot_core *core = dev_get_drvdata(child->parent);
> >
> > return ocelot_devm_regmap_init(core, child, res);
> > }
>
> Or just call:
>
> ocelot_devm_regmap_init(core, dev->parent, res);
>
> ... from the original call-site?
>
> Or, as I previously suggested:
>
> ocelot_devm_regmap_init(dev->parent, res);
>
> [...]
>
> > > > + ret = devm_mfd_add_devices(dev, PLATFORM_DEVID_NONE, vsc7512_devs,
> > >
> > > Why NONE?
> >
> > I dont know the implication here. Example taken from
> > drivers/mfd/madera-core.c. I imagine PLATFORM_DEVID_AUTO is the correct
> > macro to use here?
>
> That's why I asked. Please read-up on the differences and use the
> correct one for your device instead of just blindly copy/pasting from
> other sources. :)
Will do! It is easy to miss these details when everything is new, so
thanks for pointing this out.
>
> [...]
>
> > > > + WARN_ON(!val);
> > >
> > > Is this possible?
> >
> > Hmm... I don't know if regmap_read guards against val == NULL. It
> > doesn't look like it does. It is very much a "this should never happen"
> > moment...
> >
> > I can remove it, or change this to return an error if !val, which is
> > what I probably should have done in the first place. Thoughts?
>
> Not really. Just make sure whatever you decide to do is informed.
Understood. I'll look more into it and verify.
>
> [...]
>
> > > > - regs = devm_platform_ioremap_resource(pdev, 0);
> > > > - if (IS_ERR(regs))
> > > > - return PTR_ERR(regs);
> > > > + res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> > > > +
> > > > + if (!device_is_mfd(pdev)) {
> > > > + regs = devm_ioremap_resource(dev, res);
> > >
> > > What happens if you call this if the device was registered via MFD?
> >
> > I don't recall if it was your suggestion, but I tried this.
> > devm_ioremap_resource on the MFD triggered a kernel crash. I didn't look
> > much more into things than that, but if trying devm_ioremap_resource and
> > falling back to ocelot_get_regmap_from_resource is the desired path, I
> > can investigate further.
>
> Yes please. It should never crash. That's probably a bug.
Can do.
And thanks again for the feedback! I do really appreciate it.
>
> --
> Lee Jones [李琼斯]
> Principal Technical Lead - Developer Services
> Linaro.org │ Open source software for Arm SoCs
> Follow Linaro: Facebook | Twitter | Blog
Hi Vladimir,
My apologies for the delay. As I mentioned in another thread, I went
through the "MFD" updates before getting to these. A couple questions
that might be helpful before I go to the next RFC.
On Mon, Jan 31, 2022 at 06:50:44PM +0000, Vladimir Oltean wrote:
> 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 <[email protected]>
> > ---
> > 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?
I'm not crazy about "ocelot_ext" either... but I intend for this to
support VSC7511, 7512, 7513, and 7514. I'm using 7512 as my starting
point, but 7511 will be in quick succession, so I don't think
ocelot_vsc7512 is appropriate.
I'll update everything that is 7512-specific to be appropriately named.
Addresses, features, etc. As you suggest below, there's some function
names that are still around with the vsc7512 name that I'm changing to
the more generic "ocelot_ext" version.
[ ... ]
> > +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.
>
Currently the ocelot_ext struct is unused, and will be removed from v7,
along with these container conversions. I'll keep this in mind if I end
up needing to expand things in the future.
When these were written it was clear that "Felix" had no business
dragging around info about "ocelot_spi," so these conversions seemed
necessary. Now that SPI has been completely removed from this DSA
section, things are a lot cleaner.
> > +
> > +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.
These are both good catches. Thanks! This one in particular was a relic
of the initial spi_device design - no communication could have been
performed at all until after the bus was getting initailized... which
was in reset at the time.
Now it is in the MFD core initialization.
This brings up a question that I think you were getting at when MFD was
first discussed for this driver:
Should Felix know anything about the chip's internal MDIO bus? Or should
the internal bus be a separate entry in the MFD?
Currently my DT is structured as:
&spi0 {
ocelot-chip@0 {
compatible = "mscc,vsc7512_mfd_spi";
ethernet-switch@0 {
compatible = "mscc,vsc7512-ext-switch";
ports {
};
/* Internal MDIO port here */
mdio {
};
};
/* External MDIO port here */
mdio1: mdio1 {
compatible = "mscc,ocelot-miim";
};
/* Additional peripherals here - pinctrl, sgpio, hsio... */
gpio: pinctrl@0 {
compatible = "mscc,ocelot-pinctrl"
};
...
};
};
Should it instead be:
&spi0 {
ocelot-chip@0 {
compatible = "mscc,vsc7512_mfd_spi";
ethernet-switch@0 {
compatible = "mscc,vsc7512-ext-switch";
ports {
};
};
/* Internal MDIO port here */
mdio0: mdio0 {
compatible = "mscc,ocelot-miim"
};
/* External MDIO port here */
mdio1: mdio1 {
compatible = "mscc,ocelot-miim";
};
/* Additional peripherals here - pinctrl, sgpio, hsio... */
gpio: pinctrl@0 {
compatible = "mscc,ocelot-pinctrl"
};
...
};
};
That way I could get rid of mdio_bus_alloc entirely. (I just tried it
and it didn't "just work" but I'll do a little debugging)
The more I think about it the more I think this is the correct path to
go down.
[ ... ]
> > + 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.
With this error handling during a reset... these errors get handled in
the main ocelot switch library by way of ocelot->ops->reset().
I can add additional dev_err messages on all these calls if that would
be useful.
[ ... ]
> > +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?
>
You're right. It was probably necessary for me to get off the ground,
but not anymore. Removed.
[ ... ]
> > +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.
I switched both calls to not use devres and free both of these in remove
now. However... (comments below)
>
> > + 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).
... I assume there's no worry that kfree gets called in each driver's
remove routine but not in their shutdown? I'll read through commit
0650bf52b31f (net: dsa: be compatible with masters which unregister on shutdown)
to get a more thorough understanding of what's going on... but will
blindly follow for now. :-)
>
> > +};
> > +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
> >
On Sat, Mar 05, 2022 at 04:28:49PM -0800, Colin Foster wrote:
> Hi Vladimir,
>
> My apologies for the delay. As I mentioned in another thread, I went
> through the "MFD" updates before getting to these. A couple questions
> that might be helpful before I go to the next RFC.
>
> On Mon, Jan 31, 2022 at 06:50:44PM +0000, Vladimir Oltean wrote:
> > 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 <[email protected]>
> > > ---
> > > 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?
>
> I'm not crazy about "ocelot_ext" either... but I intend for this to
> support VSC7511, 7512, 7513, and 7514. I'm using 7512 as my starting
> point, but 7511 will be in quick succession, so I don't think
> ocelot_vsc7512 is appropriate.
>
> I'll update everything that is 7512-specific to be appropriately named.
> Addresses, features, etc. As you suggest below, there's some function
> names that are still around with the vsc7512 name that I'm changing to
> the more generic "ocelot_ext" version.
>
> [ ... ]
> > > +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.
> >
>
> Currently the ocelot_ext struct is unused, and will be removed from v7,
> along with these container conversions. I'll keep this in mind if I end
> up needing to expand things in the future.
>
> When these were written it was clear that "Felix" had no business
> dragging around info about "ocelot_spi," so these conversions seemed
> necessary. Now that SPI has been completely removed from this DSA
> section, things are a lot cleaner.
>
> > > +
> > > +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.
>
> These are both good catches. Thanks! This one in particular was a relic
> of the initial spi_device design - no communication could have been
> performed at all until after the bus was getting initailized... which
> was in reset at the time.
>
> Now it is in the MFD core initialization.
>
> This brings up a question that I think you were getting at when MFD was
> first discussed for this driver:
>
> Should Felix know anything about the chip's internal MDIO bus? Or should
> the internal bus be a separate entry in the MFD?
>
> Currently my DT is structured as:
>
> &spi0 {
> ocelot-chip@0 {
> compatible = "mscc,vsc7512_mfd_spi";
> ethernet-switch@0 {
> compatible = "mscc,vsc7512-ext-switch";
> ports {
> };
>
> /* Internal MDIO port here */
> mdio {
> };
> };
> /* External MDIO port here */
> mdio1: mdio1 {
> compatible = "mscc,ocelot-miim";
> };
> /* Additional peripherals here - pinctrl, sgpio, hsio... */
> gpio: pinctrl@0 {
> compatible = "mscc,ocelot-pinctrl"
> };
> ...
> };
> };
>
>
> Should it instead be:
>
> &spi0 {
> ocelot-chip@0 {
> compatible = "mscc,vsc7512_mfd_spi";
> ethernet-switch@0 {
> compatible = "mscc,vsc7512-ext-switch";
> ports {
> };
> };
> /* Internal MDIO port here */
> mdio0: mdio0 {
> compatible = "mscc,ocelot-miim"
> };
> /* External MDIO port here */
> mdio1: mdio1 {
> compatible = "mscc,ocelot-miim";
> };
> /* Additional peripherals here - pinctrl, sgpio, hsio... */
> gpio: pinctrl@0 {
> compatible = "mscc,ocelot-pinctrl"
> };
> ...
> };
> };
>
> That way I could get rid of mdio_bus_alloc entirely. (I just tried it
> and it didn't "just work" but I'll do a little debugging)
>
> The more I think about it the more I think this is the correct path to
> go down.
As I've mentioned in the past, on NXP switches (felix/seville), there
was a different justification. There, the internal MDIO bus is used to
access the SGMII PCS, not any internal PHY as in the ocelot-ext case.
As opposed to the 'phy-handle' that describes the relationship between a
MAC and its (internal) PHY, no such equivalent 'pcs-handle' property
exists in a standardized form. So I wanted to avoid a dependency on OF
where the drivers would not learn any actual information from it.
It is also possible to have a non-OF based connection to the internal
PHY, but that has some limitations, because DSA has a lot of legacy in
this area. 'Non OF-based' means that there is a port which lacks both
'phy-handle' and 'fixed-link'. We have said that a user port with such
an OF node should be interpreted as having an internal PHY located on
the ds->slave_mii_bus at a PHY address equal to the port index.
Whereas the same conditions (no 'phy-handle', no 'fixed-link') on a CPU
port mean that the port is a fixed-link that operates at the largest
supported link speed.
Since you have a PHY on the CPU port, I'd tend to avoid any ambiguity
and explicitly specify the 'phy-handle', 'fixed-link' properties in the
device tree.
What I'm not completely sure about is whether you really have 2 MDIO
buses. I don't have a VSC7512, and I haven't checked the datasheet
(traveling right now) but this would be surprising to me.
Anyway, if you do, then at least try to match the $nodename pattern from
Documentation/devicetree/bindings/net/mdio.yaml. I don't think "mdio0"
matches "^mdio(@.*)?".
> [ ... ]
> > > + 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.
>
> With this error handling during a reset... these errors get handled in
> the main ocelot switch library by way of ocelot->ops->reset().
>
> I can add additional dev_err messages on all these calls if that would
> be useful.
Please interpret this in context. Your ocelot_ext_reset() function calls
of_mdiobus_register(), then does other work which may fail, then returns
that error code while leaving the MDIO bus dangling. When I said "you
need to handle errors" I meant "you need to unwind whatever work is done
in the function in the case of an error". If you are going to remove the
of_mdiobus_register(), there is probably not much left.
> [ ... ]
> > > +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?
> >
>
> You're right. It was probably necessary for me to get off the ground,
> but not anymore. Removed.
>
> [ ... ]
> > > +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.
>
> I switched both calls to not use devres and free both of these in remove
> now. However... (comments below)
>
> >
> > > + 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).
>
> ... I assume there's no worry that kfree gets called in each driver's
> remove routine but not in their shutdown? I'll read through commit
> 0650bf52b31f (net: dsa: be compatible with masters which unregister on shutdown)
> to get a more thorough understanding of what's going on... but will
> blindly follow for now. :-)
The remove method is called when you unbind the driver from the
device. The shutdown method is called when you reboot. The latter can be
leaky w.r.t. memory allocation.
My request here was to provide a shutdown method implementation, and
hook it in the same way as other DSA drivers do.
> >
> > > +};
> > > +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
> > >
Hi Vladimir,
On Mon, Mar 07, 2022 at 09:51:38PM +0000, Vladimir Oltean wrote:
> On Sat, Mar 05, 2022 at 04:28:49PM -0800, Colin Foster wrote:
> > Hi Vladimir,
> >
> > My apologies for the delay. As I mentioned in another thread, I went
> > through the "MFD" updates before getting to these. A couple questions
> > that might be helpful before I go to the next RFC.
> >
> > On Mon, Jan 31, 2022 at 06:50:44PM +0000, Vladimir Oltean wrote:
> > > 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 <[email protected]>
> > > > ---
> > > > 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?
> >
> > I'm not crazy about "ocelot_ext" either... but I intend for this to
> > support VSC7511, 7512, 7513, and 7514. I'm using 7512 as my starting
> > point, but 7511 will be in quick succession, so I don't think
> > ocelot_vsc7512 is appropriate.
> >
> > I'll update everything that is 7512-specific to be appropriately named.
> > Addresses, features, etc. As you suggest below, there's some function
> > names that are still around with the vsc7512 name that I'm changing to
> > the more generic "ocelot_ext" version.
> >
> > [ ... ]
> > > > +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.
> > >
> >
> > Currently the ocelot_ext struct is unused, and will be removed from v7,
> > along with these container conversions. I'll keep this in mind if I end
> > up needing to expand things in the future.
> >
> > When these were written it was clear that "Felix" had no business
> > dragging around info about "ocelot_spi," so these conversions seemed
> > necessary. Now that SPI has been completely removed from this DSA
> > section, things are a lot cleaner.
> >
> > > > +
> > > > +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.
> >
> > These are both good catches. Thanks! This one in particular was a relic
> > of the initial spi_device design - no communication could have been
> > performed at all until after the bus was getting initailized... which
> > was in reset at the time.
> >
> > Now it is in the MFD core initialization.
> >
> > This brings up a question that I think you were getting at when MFD was
> > first discussed for this driver:
> >
> > Should Felix know anything about the chip's internal MDIO bus? Or should
> > the internal bus be a separate entry in the MFD?
> >
> > Currently my DT is structured as:
> >
> > &spi0 {
> > ocelot-chip@0 {
> > compatible = "mscc,vsc7512_mfd_spi";
> > ethernet-switch@0 {
> > compatible = "mscc,vsc7512-ext-switch";
> > ports {
> > };
> >
> > /* Internal MDIO port here */
> > mdio {
> > };
> > };
> > /* External MDIO port here */
> > mdio1: mdio1 {
> > compatible = "mscc,ocelot-miim";
> > };
> > /* Additional peripherals here - pinctrl, sgpio, hsio... */
> > gpio: pinctrl@0 {
> > compatible = "mscc,ocelot-pinctrl"
> > };
> > ...
> > };
> > };
> >
> >
> > Should it instead be:
> >
> > &spi0 {
> > ocelot-chip@0 {
> > compatible = "mscc,vsc7512_mfd_spi";
> > ethernet-switch@0 {
> > compatible = "mscc,vsc7512-ext-switch";
> > ports {
> > };
> > };
> > /* Internal MDIO port here */
> > mdio0: mdio0 {
> > compatible = "mscc,ocelot-miim"
> > };
> > /* External MDIO port here */
> > mdio1: mdio1 {
> > compatible = "mscc,ocelot-miim";
> > };
> > /* Additional peripherals here - pinctrl, sgpio, hsio... */
> > gpio: pinctrl@0 {
> > compatible = "mscc,ocelot-pinctrl"
> > };
> > ...
> > };
> > };
> >
> > That way I could get rid of mdio_bus_alloc entirely. (I just tried it
> > and it didn't "just work" but I'll do a little debugging)
> >
> > The more I think about it the more I think this is the correct path to
> > go down.
>
> As I've mentioned in the past, on NXP switches (felix/seville), there
> was a different justification. There, the internal MDIO bus is used to
> access the SGMII PCS, not any internal PHY as in the ocelot-ext case.
> As opposed to the 'phy-handle' that describes the relationship between a
> MAC and its (internal) PHY, no such equivalent 'pcs-handle' property
> exists in a standardized form. So I wanted to avoid a dependency on OF
> where the drivers would not learn any actual information from it.
>
> It is also possible to have a non-OF based connection to the internal
> PHY, but that has some limitations, because DSA has a lot of legacy in
> this area. 'Non OF-based' means that there is a port which lacks both
> 'phy-handle' and 'fixed-link'. We have said that a user port with such
> an OF node should be interpreted as having an internal PHY located on
> the ds->slave_mii_bus at a PHY address equal to the port index.
> Whereas the same conditions (no 'phy-handle', no 'fixed-link') on a CPU
> port mean that the port is a fixed-link that operates at the largest
> supported link speed.
I see. And there was a comment a while back... I believe it was
Alexandre suggested there was some of consideration in the design to
support the non-OF-based cases. I hope I'm getting a better idea of the
big picture... one piece at a time.
>
> Since you have a PHY on the CPU port, I'd tend to avoid any ambiguity
> and explicitly specify the 'phy-handle', 'fixed-link' properties in the
> device tree.
Yes, you suggested this early on. Thank you for guiding me down the
right path.
>
> What I'm not completely sure about is whether you really have 2 MDIO
> buses. I don't have a VSC7512, and I haven't checked the datasheet
> (traveling right now) but this would be surprising to me.
> Anyway, if you do, then at least try to match the $nodename pattern from
> Documentation/devicetree/bindings/net/mdio.yaml. I don't think "mdio0"
> matches "^mdio(@.*)?".
Safe travels!
I was really surprised about the two MDIO buses as well. My coworker
pointed this out to me right before I decided to start looking into the
external phys and probably saved me a week of datasheet shuffling / scope
probing. Especially since the MDIO bus 2 addresses are pin-strapped to
start at 4, seemingly to not overlap the internal MDIO addresses 0-3.
And the two MDIO buses also exist in
arch/mips/boot/dts/mscc/ocelot.dtsi, so I know I'm not crazy.
I'll update the node names in my tree per your suggestion. I figured
there'd be no desire for me sharing a .dtsi for my boot-pin-modified dev
board configuration. Maybe I'm wrong, and sharing the relevant portions
in cover letters is not the right thing to do.
>
> > [ ... ]
> > > > + 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.
> >
> > With this error handling during a reset... these errors get handled in
> > the main ocelot switch library by way of ocelot->ops->reset().
> >
> > I can add additional dev_err messages on all these calls if that would
> > be useful.
>
> Please interpret this in context. Your ocelot_ext_reset() function calls
> of_mdiobus_register(), then does other work which may fail, then returns
> that error code while leaving the MDIO bus dangling. When I said "you
> need to handle errors" I meant "you need to unwind whatever work is done
> in the function in the case of an error". If you are going to remove the
> of_mdiobus_register(), there is probably not much left.
Thanks for explaining this. Understood.
>
> > [ ... ]
> > > > +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?
> > >
> >
> > You're right. It was probably necessary for me to get off the ground,
> > but not anymore. Removed.
> >
> > [ ... ]
> > > > +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.
> >
> > I switched both calls to not use devres and free both of these in remove
> > now. However... (comments below)
> >
> > >
> > > > + 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).
> >
> > ... I assume there's no worry that kfree gets called in each driver's
> > remove routine but not in their shutdown? I'll read through commit
> > 0650bf52b31f (net: dsa: be compatible with masters which unregister on shutdown)
> > to get a more thorough understanding of what's going on... but will
> > blindly follow for now. :-)
>
> The remove method is called when you unbind the driver from the
> device. The shutdown method is called when you reboot. The latter can be
> leaky w.r.t. memory allocation.
Interesting concept. Makes sense though. Thanks again for explaining!
>
> My request here was to provide a shutdown method implementation, and
> hook it in the same way as other DSA drivers do.
>
> > >
> > > > +};
> > > > +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
> > > >