[PATCH v2 1/3] net: add r9a06g032 A5PSW switch driver

Ralph Siemsen <[email protected]>
Newsgroups org.u-boot-project.lists.u-boot
Message-ID <[email protected]>
Driver for the Advanced 5-port Ethernet switch (A5PSW) on RZ/N1 SoC.

The A5PSW switch is modeled using DSA framework, but without actually
tagging packets. The goal is just to support network booting over a
single Ethernet port.

This driver is loosely based on the Linux driver by Clément Léger
888cdb892b61 ("net: dsa: rzn1-a5psw: add Renesas RZ/N1 advanced 5 port
switch driver") but without any of the VLAN tagging or other features.

Signed-off-by: Ralph Siemsen <[email protected]>
---
 drivers/net/Kconfig      |   6 +
 drivers/net/Makefile     |   1 +
 drivers/net/rzn1_a5psw.c | 377 +++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 384 insertions(+)

diff --git a/drivers/net/Kconfig b/drivers/net/Kconfig
index faa08e9ebba..edc4cb29598 100644
--- a/drivers/net/Kconfig
+++ b/drivers/net/Kconfig
@@ -880,6 +880,12 @@ config RENESAS_RAVB
 	  This driver implements support for the Ethernet AVB block in
 	  several Renesas R-Car and RZ SoCs.
 
+config RZN1_A5PSW
+	bool "Renesas RZ/N1 A5PSW ethernet switch"
+	help
+	  Support the Advanced 5-Port ethernet switch (A5PSW) found in
+	  the Renesas RZ/N1 SoC.
+
 config MPC8XX_FEC
 	bool "Fast Ethernet Controller on MPC8XX"
 	depends on MPC8xx
diff --git a/drivers/net/Makefile b/drivers/net/Makefile
index 4c1f4094ab2..b8f270ae233 100644
--- a/drivers/net/Makefile
+++ b/drivers/net/Makefile
@@ -94,6 +94,7 @@ obj-$(CONFIG_PCNET) += pcnet.o
 obj-$(CONFIG_PIC32_ETH) += pic32_mdio.o pic32_eth.o
 obj-$(CONFIG_RENESAS_ETHER_SWITCH) += rswitch.o
 obj-$(CONFIG_RENESAS_RAVB) += ravb.o
+obj-$(CONFIG_RZN1_A5PSW) += rzn1_a5psw.o
 obj-$(CONFIG_RTL8139) += rtl8139.o
 obj-$(CONFIG_RTL8169) += rtl8169.o
 obj-$(CONFIG_SH_ETHER) += sh_eth.o
diff --git a/drivers/net/rzn1_a5psw.c b/drivers/net/rzn1_a5psw.c
new file mode 100644
index 00000000000..2733ebcdfbb
--- /dev/null
+++ b/drivers/net/rzn1_a5psw.c
@@ -0,0 +1,377 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Adapted from Linux drivers/net/dsa/rzn1_a5psw.c
+ * but without DSA tag support.
+ */
+
+#include <clk.h>
+#include <dm/device.h>
+#include <dm/device_compat.h>
+#include <dm/device-internal.h>
+#include <dm/lists.h>
+#include <dm/of_extra.h>
+#include <linux/bitfield.h>
+#include <linux/delay.h>
+#include <miiphy.h>
+#include <net/dsa.h>
+#include <syscon.h>
+#include <regmap.h>
+#include <reset.h>
+
+struct rzn1_a5psw_plat {
+	struct clk_bulk bulk;
+	struct regmap *regmap;
+	struct udevice *miic;
+	struct regmap *syscon;
+};
+
+/* MoreThanIP 5pt Switch regs */
+#define MT5PT_REVISION		0x0
+#define MT5PT_SCRATCH		0x4
+#define MT5PT_PORT_ENA		0x8
+#define  MT5PT_PORT_ENA_TX(x)		BIT(16 + (x))
+#define  MT5PT_PORT_ENA_RX(x)		BIT(x)
+#define  MT5PT_PORT_ENA_TXRX(x)		((MT5PT_PORT_ENA_TX(0) | MT5PT_PORT_ENA_RX(0)) << (x))
+#define MT5PT_AUTH_PORT(x)	(0x240 + (x) * 4)
+#define  MT5PT_AUTH_PORT_AUTHORIZED	BIT(0)
+#define  MT5PT_AUTH_PORT_CONTROLLED	BIT(1)
+#define  MT5PT_AUTH_PORT_EAPOL_EN	BIT(2)
+#define  MT5PT_AUTH_PORT_GUEST		BIT(3)
+#define  MT5PT_AUTH_PORT_EAPOL_PORT(x)	((x) << 12)
+#define MT5PT_MDIO_CFG_STATUS	0x700
+#define MT5PT_MDIO_COMMAND	0x704
+#define MT5PT_MDIO_DATA		0x708
+#define MT5PT_MAC_CMD_CFGn(x)	(0x808 + (x) * 0x400)
+#define  MT5PT_TX_ENA			BIT(0)
+#define  MT5PT_RX_ENA			BIT(1)
+#define  MT5PT_MBPS_1000		BIT(3)
+#define  MT5PT_HALF_DUPLEX		BIT(10)
+#define MT5PT_MAC_FRM_LENGTHn(x)	(0x814 + (x) * 0x400)
+#define MT5PT_DUMMY_NOOP	0x5000
+
+static void rzn1_switch_write(struct regmap *regmap, uint offset, uint val)
+{
+	/*
+	 * MTIP switch errata:
+	 * There is a clock-crossing issue within the switch,
+	 * which can cause writes to be corrupted.
+	 * To avoid the problem, perform a dummy write with the
+	 * same data value, prior to the actual write.
+	 */
+	regmap_write(regmap, MT5PT_DUMMY_NOOP, val);
+	regmap_write(regmap, offset, val);
+}
+
+static void rzn1_switch_port_speed_duplex(struct udevice *dev, int port, int speed, int duplex)
+{
+	struct rzn1_a5psw_plat *plat = dev_get_plat(dev);
+	struct regmap *regmap = plat->regmap;
+	u32 val;
+
+	regmap_read(regmap, MT5PT_MAC_CMD_CFGn(port), &val);
+	val &= ~(MT5PT_HALF_DUPLEX | MT5PT_MBPS_1000);
+	if (speed == SPEED_1000)
+		val |= MT5PT_MBPS_1000;
+	if (duplex == DUPLEX_HALF)
+		val |= MT5PT_HALF_DUPLEX;
+
+	rzn1_switch_write(regmap, MT5PT_MAC_CMD_CFGn(port), val);
+}
+
+static void rzn1_switch_port_enable(struct udevice *dev, int port)
+{
+	struct rzn1_a5psw_plat *plat = dev_get_plat(dev);
+	struct regmap *regmap = plat->regmap;
+	u32 val;
+
+	regmap_read(regmap, MT5PT_AUTH_PORT(port), &val);
+	val |= MT5PT_AUTH_PORT_AUTHORIZED;
+	rzn1_switch_write(regmap, MT5PT_AUTH_PORT(port), val);
+
+	regmap_read(regmap, MT5PT_PORT_ENA, &val);
+	val |= MT5PT_PORT_ENA_TXRX(port);
+	rzn1_switch_write(regmap, MT5PT_PORT_ENA, val);
+
+	/* Max frame size */
+	rzn1_switch_write(regmap, MT5PT_MAC_FRM_LENGTHn(port), 9224);
+}
+
+static void rzn1_switch_port_disable(struct udevice *dev, int port)
+{
+	struct rzn1_a5psw_plat *plat = dev_get_plat(dev);
+	struct regmap *regmap = plat->regmap;
+	u32 val;
+
+	regmap_read(regmap, MT5PT_AUTH_PORT(port), &val);
+	val &= ~MT5PT_AUTH_PORT_AUTHORIZED;
+	rzn1_switch_write(regmap, MT5PT_AUTH_PORT(port), val);
+
+	regmap_read(regmap, MT5PT_PORT_ENA, &val);
+	val &= ~MT5PT_PORT_ENA_TXRX(port);
+	rzn1_switch_write(regmap, MT5PT_PORT_ENA, val);
+}
+
+static int rzn1_phy_wait(struct udevice *dev)
+{
+	struct rzn1_a5psw_plat *plat = dev_get_plat(dev->parent);
+	struct regmap *regmap = plat->regmap;
+	u32 timeout = 100;
+	int ret;
+	u32 val;
+
+	do {
+		ret = regmap_read(regmap, MT5PT_MDIO_CFG_STATUS, &val);
+		if (ret == 0 && (val & BIT(0)) == 0)
+			return 0;
+
+		mdelay(1);
+	} while (--timeout);
+
+	return -ETIMEDOUT;
+}
+
+static int rzn1_a5psw_mdio_read(struct udevice *dev, int addr, int devad, int reg)
+{
+	struct rzn1_a5psw_plat *plat = dev_get_plat(dev->parent);
+	struct regmap *regmap = plat->regmap;
+	int ret;
+	u32 val = BIT(15) | ((addr & 0x1f) << 5) | (reg & 0x1f);
+
+	/* Clause 22 only */
+	rzn1_switch_write(regmap, MT5PT_MDIO_COMMAND, val);
+
+	ret = rzn1_phy_wait(dev);
+	if (ret) {
+		dev_warn(dev, "PHY read timeout\n");
+		return ret;
+	}
+
+	ret = regmap_read(regmap, MT5PT_MDIO_DATA, &val);
+	if (ret) {
+		dev_warn(dev, "PHY read error\n");
+		return ret;
+	}
+	val &= 0xffff;
+
+	// TODO: check CFG_STATUS for READERR
+
+	dev_dbg(dev, "addr=0x%x reg=0x%x read 0x%04x\n", addr, reg, val);
+
+	return val;
+}
+
+static int rzn1_a5psw_mdio_write(struct udevice *dev, int addr, int devad,
+				 int reg, u16 val)
+{
+	struct rzn1_a5psw_plat *plat = dev_get_plat(dev->parent);
+	struct regmap *regmap = plat->regmap;
+	int ret;
+	u32 cmd = ((addr & 0x1f) << 5) | (reg & 0x1f);
+
+	dev_dbg(dev, "addr=0x%x reg=0x%x write 0x%04x\n", addr, reg, val);
+
+	/* Clause 22 only */
+	rzn1_switch_write(regmap, MT5PT_MDIO_COMMAND, cmd);
+	rzn1_switch_write(regmap, MT5PT_MDIO_DATA, val);
+
+	ret = rzn1_phy_wait(dev);
+	if (ret) {
+		dev_warn(dev, "PHY write timeout\n");
+		return ret;
+	}
+
+	return 0;
+}
+
+static const struct mdio_ops rzn1_a5psw_mdio_ops = {
+	.read = rzn1_a5psw_mdio_read,
+	.write = rzn1_a5psw_mdio_write,
+};
+
+static int rzn1_a5psw_mdio_bind(struct udevice *dev)
+{
+	char name[32];
+	static int num_devices;
+
+	sprintf(name, "rzn1-a5psw-mdio-%d", num_devices++);
+	device_set_name(dev, name);
+
+	return 0;
+}
+
+U_BOOT_DRIVER(rzn1_a5psw_mdio) = {
+	.name		= "rzn1_a5psw_mdio",
+	.id		= UCLASS_MDIO,
+	.ops		= &rzn1_a5psw_mdio_ops,
+	.bind		= rzn1_a5psw_mdio_bind,
+	.plat_auto	= sizeof(struct mdio_perdev_priv),
+};
+
+static int rzn1_a5psw_port_probe(struct udevice *dev, int port, struct phy_device *phy)
+{
+	/* Called once when switch is probed */
+	return phy_config(phy);
+}
+
+void rzn1_rgmii_rmii_conv_speed(struct udevice *dev, int phy,
+				int full_duplex, int speed);
+
+static int rzn1_a5psw_port_enable(struct udevice *dev, int port, struct phy_device *phy)
+{
+	struct rzn1_a5psw_plat *plat = dev_get_plat(dev);
+
+	/* CPU port is already enabled */
+	if (port == 4)
+		return 0;
+
+	/* Get speed/duplex from PHY */
+	genphy_update_link(phy);
+	if (!phy->link) {
+		dev_err(dev, "PHY %s no ethernet link\n", phy->dev->name);
+		return 0;
+	}
+	genphy_parse_link(phy);
+
+	/* Program the converter accoringly */
+	dev_info(dev, "MIIC = %p\n", plat->miic);
+	rzn1_rgmii_rmii_conv_speed(plat->miic, 4 - port, phy->duplex, phy->speed);
+
+	/* Program the switch port accordingly */
+	dev_info(dev, "%s port=%d using speed=%d\n", __func__, port, phy->speed);
+	rzn1_switch_port_speed_duplex(dev, port, phy->speed, phy->duplex);
+	rzn1_switch_port_enable(dev, port);
+	return 0;
+}
+
+static void rzn1_a5psw_port_disable(struct udevice *dev, int port, struct phy_device *phy)
+{
+	/* CPU port is already enabled */
+	if (port == 4)
+		return;
+
+	rzn1_switch_port_disable(dev, port);
+}
+
+static const struct dsa_ops rzn1_a5psw_dsa_ops = {
+	.port_probe = rzn1_a5psw_port_probe,
+	.port_enable = rzn1_a5psw_port_enable,
+	.port_disable = rzn1_a5psw_port_disable,
+};
+
+static int rzn1_a5psw_probe_mdio(struct udevice *dev)
+{
+	struct udevice *mdev;
+	const char *name;
+	ofnode node;
+	int ret;
+
+	/* bind phy ports of mdio child node to rzn1_a5psw_mdio device */
+	node = dev_read_subnode(dev, "mdio");
+	if (!ofnode_valid(node))
+		return 0;
+
+	name = ofnode_get_name(node);
+	ret = device_bind_driver_to_node(dev,
+					 "rzn1_a5psw_mdio",
+					 name, node, NULL);
+	if (ret) {
+		dev_err(dev, "failed to bind %s: %d\n", name, ret);
+	} else {
+		/* need to probe it as there is no compatible to do so */
+		ret = uclass_get_device_by_ofnode(UCLASS_MDIO, node, &mdev);
+		if (ret)
+			dev_err(dev, "failed to probe %s: %d\n", name, ret);
+	}
+
+	return ret;
+}
+
+static int rzn1_a5psw_probe(struct udevice *dev)
+{
+	struct rzn1_a5psw_plat *plat = dev_get_plat(dev);
+	int ret;
+
+	dev_dbg(dev, "%s:%d\n", __func__, __LINE__);
+
+	/* Enable clocks */
+	ret = clk_get_bulk(dev, &plat->bulk);
+	if (ret) {
+		dev_err(dev, "Failed to get clocks\n");
+		return ret;
+	}
+	ret = clk_enable_bulk(&plat->bulk);
+	if (ret) {
+		dev_err(dev, "Failed to enable clocks\n");
+		return ret;
+	}
+
+	/* Access the registers */
+	ret = regmap_init_mem(dev_ofnode(dev), &plat->regmap);
+	if (ret) {
+		dev_err(dev, "Failed to get regmap\n");
+		return ret;
+	}
+
+	/* Find the MIIC driver */
+	ret = uclass_get_device_by_name(UCLASS_MISC, "eth-miic@44030000", &plat->miic);
+	if (ret) {
+		dev_err(dev, "Failed to get MIIC\n");
+		return ret;
+	}
+	dev_dbg(dev, "got MIIC %p <%s>\n", plat->miic, plat->miic->name);
+
+	/* Upstream port is always 1Gbps */
+	rzn1_switch_port_speed_duplex(dev, 4, SPEED_1000, DUPLEX_FULL);
+	rzn1_switch_port_enable(dev, 4);
+
+	/* Some extra resets are located in system controller */
+	plat->syscon = syscon_regmap_lookup_by_phandle(dev, "syscon");
+	if (IS_ERR(plat->syscon)) {
+		dev_err(dev, "No syscon node found\n");
+		return PTR_ERR(plat->syscon);
+	}
+
+	/* Clear ETH and CLK25 resets */
+#define RZN1_SYSCTRL_PWRCTRL_SWITCH 0x188
+	regmap_update_bits(plat->syscon, RZN1_SYSCTRL_PWRCTRL_SWITCH,
+			   BIT(4) | BIT(3), BIT(4) | BIT(3));
+
+	/* Reset all PHYs */
+	struct gpio_desc phy_reset_gpio;
+	u32 phy_reset_delay;
+
+	ret = gpio_request_by_name(dev, "phy-reset-gpios", 0,
+				   &phy_reset_gpio, GPIOD_IS_OUT | GPIOD_IS_OUT_ACTIVE);
+	if (ret) {
+		dev_err(dev, "failed to get phy-reset-gpios: %d\n", ret);
+		return ret;
+	}
+
+	phy_reset_delay = dev_read_u32_default(dev, "phy-reset-duration", 15);
+
+	dm_gpio_set_value(&phy_reset_gpio, 1);
+	mdelay(phy_reset_delay);
+	dm_gpio_set_value(&phy_reset_gpio, 0);
+
+	ret = rzn1_a5psw_probe_mdio(dev);
+	if (ret) {
+		dev_warn(dev, "mdio probe failed\n");
+		return ret;
+	}
+
+	return 0;
+}
+
+static const struct udevice_id rzn1_a5psw_ids[] = {
+	{ .compatible = "renesas,rzn1-a5psw" },
+	{ }
+};
+
+U_BOOT_DRIVER(rzn1_a5psw) = {
+	.name		= "rzn1_a5psw",
+	.id		= UCLASS_DSA,
+	.of_match	= rzn1_a5psw_ids,
+	.probe		= rzn1_a5psw_probe,
+	.ops		= &rzn1_a5psw_dsa_ops,
+	.plat_auto	= sizeof(struct rzn1_a5psw_plat),
+};

-- 
2.55.0
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.