[PATCH v1 3/3] net: add ksz8873 switch support

Oleksij Rempel o.rempel at pengutronix.de
Tue Sep 20 05:55:33 PDT 2022


Add minimal DSA driver for the KSZ8873 switches

Signed-off-by: Oleksij Rempel <o.rempel at pengutronix.de>
---
 drivers/net/Kconfig   |   6 +
 drivers/net/Makefile  |   1 +
 drivers/net/ksz8873.c | 424 ++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 431 insertions(+)
 create mode 100644 drivers/net/ksz8873.c

diff --git a/drivers/net/Kconfig b/drivers/net/Kconfig
index 84a01c5328..2dafd9c7a8 100644
--- a/drivers/net/Kconfig
+++ b/drivers/net/Kconfig
@@ -300,6 +300,12 @@ menuconfig DSA
 
 if DSA
 
+config DRIVER_NET_KSZ8873
+	bool "KSZ8873 switch driver"
+	help
+	  This option enables support for the Microchip KSZ8873
+	  switch chip.
+
 config DRIVER_NET_KSZ9477
 	bool "KSZ9477 switch driver"
 	depends on SPI
diff --git a/drivers/net/Makefile b/drivers/net/Makefile
index 47ad749943..7ff330a2bf 100644
--- a/drivers/net/Makefile
+++ b/drivers/net/Makefile
@@ -27,6 +27,7 @@ obj-$(CONFIG_DRIVER_NET_FEC_IMX)	+= fec_imx.o
 obj-$(CONFIG_DRIVER_NET_FSL_FMAN)	+= fsl-fman.o
 obj-$(CONFIG_DRIVER_NET_GIANFAR)	+= gianfar.o
 obj-$(CONFIG_DRIVER_NET_KS8851_MLL)	+= ks8851_mll.o
+obj-$(CONFIG_DRIVER_NET_KSZ8873)	+= ksz8873.o
 obj-$(CONFIG_DRIVER_NET_KSZ9477)	+= ksz9477.o
 obj-$(CONFIG_DRIVER_NET_MACB)		+= macb.o
 obj-$(CONFIG_DRIVER_NET_MICREL)		+= ksz8864rmn.o
diff --git a/drivers/net/ksz8873.c b/drivers/net/ksz8873.c
new file mode 100644
index 0000000000..bd8071b872
--- /dev/null
+++ b/drivers/net/ksz8873.c
@@ -0,0 +1,424 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <common.h>
+#include <complete.h>
+#include <dsa.h>
+#include <gpiod.h>
+#include <linux/mii.h>
+#include <linux/mdio.h>
+#include <net.h>
+#include <of_device.h>
+#include <regmap.h>
+
+#define KSZ8873_GLOBAL_CTRL_1		0x03
+#define KSZ8873_PASS_ALL_FRAMES		BIT(7)
+#define KSZ8873_P3_TAIL_TAG_EN		BIT(6)
+
+/*
+ * port specific registers. Should be used with ksz_pwrite/ksz_pread functions
+ */
+#define KSZ8873_PORTx_CTRL_1		0x01
+#define KSZ8873_PORTx_CTRL_12		0x0c
+
+#define PORT_AUTO_NEG_ENABLE		BIT(7)
+#define PORT_FORCE_100_MBIT		BIT(6)
+#define PORT_FORCE_FULL_DUPLEX		BIT(5)
+#define PORT_AUTO_NEG_100BTX_FD		BIT(3)
+#define PORT_AUTO_NEG_100BTX		BIT(2)
+#define PORT_AUTO_NEG_10BT_FD		BIT(1)
+#define PORT_AUTO_NEG_10BT		BIT(0)
+
+#define KSZ8873_PORTx_CTRL_13		0x0d
+
+#define PORT_AUTO_NEG_RESTART		BIT(5)
+#define PORT_POWER_DOWN			BIT(3)
+
+#define KSZ8873_PORTx_STATUS_0		0x0e
+
+#define PORT_AUTO_NEG_COMPLETE		BIT(6)
+#define PORT_STAT_LINK_GOOD		BIT(5)
+#define PORT_REMOTE_100BTX_FD		BIT(3)
+#define PORT_REMOTE_100BTX		BIT(2)
+#define PORT_REMOTE_10BT_FD		BIT(1)
+#define PORT_REMOTE_10BT		BIT(0)
+
+#define KSZ8873_PORTx_STATUS_1		0x0f
+
+#define KSZ8795_ID_HI			0x0022
+#define KSZ8863_ID_LO			0x1430
+
+#define PORT_CTRL_ADDR(port, addr)	((addr) + 0x10 + (port) * 0x10)
+
+struct ksz8873_dcfg {
+	unsigned int num_ports;
+	unsigned int phy_port_cnt;
+};
+
+struct ksz8873_switch {
+	struct phy_device *mdiodev;
+	struct dsa_switch ds;
+	struct device_d *dev;
+	const struct ksz8873_dcfg *dcfg;
+	struct regmap *regmap;
+};
+
+/* Serial Management Interface (SMI) uses the following frame format:
+ *
+ *       preamble|start|Read/Write|  PHY   |  REG  |TA|   Data bits      | Idle
+ *               |frame| OP code  |address |address|  |                  |
+ * read | 32x1´s | 01  |    00    | 1xRRR  | RRRRR |Z0| 00000000DDDDDDDD |  Z
+ * write| 32x1´s | 01  |    00    | 0xRRR  | RRRRR |10| xxxxxxxxDDDDDDDD |  Z
+ *
+ */
+
+#define SMI_KSZ88XX_READ_PHY	BIT(4)
+
+static int ksz8873_mdio_read(void *ctx, unsigned int reg, unsigned int *val)
+{
+	struct ksz8873_switch *priv = ctx;
+	struct phy_device *mdiodev = priv->mdiodev;
+	int ret;
+
+	ret = mdiobus_read(mdiodev->bus, ((reg & 0xE0) >> 5) |
+			     SMI_KSZ88XX_READ_PHY, reg);
+	if (ret < 0)
+		return ret;
+
+	*val = ret;
+
+	return 0;
+}
+
+static ssize_t ksz8873_mdio_write(void *ctx, unsigned int reg, unsigned int val)
+{
+	struct ksz8873_switch *priv = ctx;
+	struct phy_device *mdiodev = priv->mdiodev;
+
+	return mdiobus_write(mdiodev->bus, ((reg & 0xE0) >> 5), reg, val);
+}
+
+static const struct regmap_bus ksz8873_regmap_smi = {
+	.reg_read = ksz8873_mdio_read,
+	.reg_write = ksz8873_mdio_write,
+};
+
+static const struct regmap_config ksz8873_regmap_config = {
+	.name = "#8",
+	.reg_bits = 8,
+	.pad_bits = 24,
+	.val_bits = 8,
+};
+
+static int ksz_read8(struct ksz8873_switch *priv, u32 reg, u8 *val)
+{
+	unsigned int value;
+	int ret = regmap_read(priv->regmap, reg, &value);
+
+	*val = value & 0xff;
+
+	return ret;
+}
+
+static int ksz_write8(struct ksz8873_switch *priv, u32 reg, u8 value)
+{
+	return regmap_write(priv->regmap, reg, value);
+}
+
+static int ksz_pread8(struct ksz8873_switch *priv, int port, int reg, u8 *val)
+{
+	return ksz_read8(priv, PORT_CTRL_ADDR(port, reg), val);
+}
+
+static int ksz_pwrite8(struct ksz8873_switch *priv, int port, int reg, u8 val)
+{
+	return ksz_write8(priv, PORT_CTRL_ADDR(port, reg), val);
+}
+
+static void ksz8_r_phy(struct ksz8873_switch *priv, u16 phy, u16 reg, u16 *val)
+{
+	u8 restart, ctrl, link;
+	int processed = true;
+	u16 data = 0;
+	u8 p = phy;
+
+	switch (reg) {
+	case MII_BMCR:
+		ksz_pread8(priv, p, KSZ8873_PORTx_CTRL_13, &restart);
+		ksz_pread8(priv, p, KSZ8873_PORTx_CTRL_12, &ctrl);
+		if (ctrl & PORT_FORCE_100_MBIT)
+			data |= BMCR_SPEED100;
+		if ((ctrl & PORT_AUTO_NEG_ENABLE))
+			data |= BMCR_ANENABLE;
+		if (restart & PORT_POWER_DOWN)
+			data |= BMCR_PDOWN;
+		if (restart & PORT_AUTO_NEG_RESTART)
+			data |= BMCR_ANRESTART;
+		if (ctrl & PORT_FORCE_FULL_DUPLEX)
+			data |= BMCR_FULLDPLX;
+		break;
+	case MII_BMSR:
+		ksz_pread8(priv, p, KSZ8873_PORTx_STATUS_0, &link);
+		data = BMSR_100FULL |
+		       BMSR_100HALF |
+		       BMSR_10FULL |
+		       BMSR_10HALF |
+		       BMSR_ANEGCAPABLE;
+		if (link & PORT_AUTO_NEG_COMPLETE)
+			data |= BMSR_ANEGCOMPLETE;
+		if (link & PORT_STAT_LINK_GOOD)
+			data |= BMSR_LSTATUS;
+		break;
+	case MII_PHYSID1:
+		data = KSZ8795_ID_HI;
+		break;
+	case MII_PHYSID2:
+		data = KSZ8863_ID_LO;
+		break;
+	case MII_ADVERTISE:
+		ksz_pread8(priv, p, KSZ8873_PORTx_CTRL_12, &ctrl);
+		data = ADVERTISE_CSMA;
+		if (ctrl & PORT_AUTO_NEG_100BTX_FD)
+			data |= ADVERTISE_100FULL;
+		if (ctrl & PORT_AUTO_NEG_100BTX)
+			data |= ADVERTISE_100HALF;
+		if (ctrl & PORT_AUTO_NEG_10BT_FD)
+			data |= ADVERTISE_10FULL;
+		if (ctrl & PORT_AUTO_NEG_10BT)
+			data |= ADVERTISE_10HALF;
+		break;
+	case MII_LPA:
+		ksz_pread8(priv, p, KSZ8873_PORTx_STATUS_0, &link);
+		data = LPA_SLCT;
+		if (link & PORT_REMOTE_100BTX_FD)
+			data |= LPA_100FULL;
+		if (link & PORT_REMOTE_100BTX)
+			data |= LPA_100HALF;
+		if (link & PORT_REMOTE_10BT_FD)
+			data |= LPA_10FULL;
+		if (link & PORT_REMOTE_10BT)
+			data |= LPA_10HALF;
+		if (data & ~LPA_SLCT)
+			data |= LPA_LPACK;
+		break;
+	default:
+		processed = false;
+		break;
+	}
+	if (processed)
+		*val = data;
+}
+
+static void ksz8_w_phy(struct ksz8873_switch *priv, u16 phy, u16 reg, u16 val)
+{
+	u8 restart, ctrl, data;
+	u8 p = phy;
+
+	switch (reg) {
+	case MII_BMCR:
+		ksz_pread8(priv, p, KSZ8873_PORTx_CTRL_12, &ctrl);
+		data = ctrl;
+		if ((val & BMCR_ANENABLE))
+			data |= PORT_AUTO_NEG_ENABLE;
+		else
+			data &= ~PORT_AUTO_NEG_ENABLE;
+
+		if (val & BMCR_SPEED100)
+			data |= PORT_FORCE_100_MBIT;
+		else
+			data &= ~PORT_FORCE_100_MBIT;
+		if (val & BMCR_FULLDPLX)
+			data |= PORT_FORCE_FULL_DUPLEX;
+		else
+			data &= ~PORT_FORCE_FULL_DUPLEX;
+		if (data != ctrl)
+			ksz_pwrite8(priv, p, KSZ8873_PORTx_CTRL_12, data);
+		ksz_pread8(priv, p, KSZ8873_PORTx_CTRL_13, &restart);
+		data = restart;
+		if (val & BMCR_ANRESTART)
+			data |= PORT_AUTO_NEG_RESTART;
+		else
+			data &= ~(PORT_AUTO_NEG_RESTART);
+		if (val & BMCR_PDOWN)
+			data |= PORT_POWER_DOWN;
+		else
+			data &= ~PORT_POWER_DOWN;
+		if (data != restart)
+			ksz_pwrite8(priv, p, KSZ8873_PORTx_CTRL_13, data);
+		break;
+	case MII_ADVERTISE:
+		ksz_pread8(priv, p, KSZ8873_PORTx_CTRL_12, &ctrl);
+		data = ctrl;
+		data &= ~(PORT_AUTO_NEG_100BTX_FD |
+			  PORT_AUTO_NEG_100BTX |
+			  PORT_AUTO_NEG_10BT_FD |
+			  PORT_AUTO_NEG_10BT);
+		if (val & ADVERTISE_100FULL)
+			data |= PORT_AUTO_NEG_100BTX_FD;
+		if (val & ADVERTISE_100HALF)
+			data |= PORT_AUTO_NEG_100BTX;
+		if (val & ADVERTISE_10FULL)
+			data |= PORT_AUTO_NEG_10BT_FD;
+		if (val & ADVERTISE_10HALF)
+			data |= PORT_AUTO_NEG_10BT;
+		if (data != ctrl)
+			ksz_pwrite8(priv, p, KSZ8873_PORTx_CTRL_12, data);
+		break;
+	default:
+		break;
+	}
+}
+
+static int ksz8873_phy_read16(struct dsa_switch *ds, int addr, int reg)
+{
+	struct device_d *dev = ds->dev;
+	struct ksz8873_switch *priv = dev_get_priv(dev);
+	u16 val = 0xffff;
+
+	if (addr >= priv->dcfg->phy_port_cnt)
+		return val;
+
+	ksz8_r_phy(priv, addr, reg, &val);
+
+	return val;
+}
+
+static int ksz8873_phy_write16(struct dsa_switch *ds, int addr, int reg,
+			       u16 val)
+{
+	struct device_d *dev = ds->dev;
+	struct ksz8873_switch *priv = dev_get_priv(dev);
+
+	/* No real PHY after this. */
+	if (addr >= priv->dcfg->phy_port_cnt)
+		return 0;
+
+	ksz8_w_phy(priv, addr, reg, val);
+
+	return 0;
+}
+
+static void ksz8873_cfg_port_member(struct ksz8873_switch *priv, int port,
+				    u8 member)
+{
+	ksz_pwrite8(priv, port, KSZ8873_PORTx_CTRL_1, member);
+}
+
+static int ksz8873_port_enable(struct dsa_port *dp, int port,
+			   struct phy_device *phy)
+{
+	return 0;
+}
+
+static int ksz8873_xmit(struct dsa_port *dp, int port, void *packet, int length)
+{
+	u8 *tag = packet + length - dp->ds->needed_tx_tailroom;
+
+	*tag = BIT(dp->index);
+
+	return 0;
+}
+
+static int ksz8873_recv(struct dsa_switch *ds, int *port, void *packet,
+			int length)
+{
+	u8 *tag = packet + length - ds->needed_rx_tailroom;
+
+	*port = *tag & 7;
+
+	return 0;
+};
+
+static const struct dsa_ops ksz8873_dsa_ops = {
+	.port_enable = ksz8873_port_enable,
+	.xmit = ksz8873_xmit,
+	.rcv = ksz8873_recv,
+	.phy_read = ksz8873_phy_read16,
+	.phy_write = ksz8873_phy_write16,
+};
+
+static int ksz8873_default_setup(struct ksz8873_switch *priv)
+{
+	int i;
+
+	ksz_write8(priv, KSZ8873_GLOBAL_CTRL_1, KSZ8873_PASS_ALL_FRAMES |
+		   KSZ8873_P3_TAIL_TAG_EN);
+
+	for (i = 0; i < priv->ds.num_ports; i++) {
+		u8 member;
+		/* isolate all ports by default */
+		member = BIT(priv->ds.cpu_port);
+		ksz8873_cfg_port_member(priv, i, member);
+
+		member = dsa_user_ports(&priv->ds);
+		ksz8873_cfg_port_member(priv, priv->ds.cpu_port, member);
+	}
+
+	return 0;
+}
+
+static int ksz8873_probe_mdio(struct phy_device *mdiodev)
+{
+	struct device_d *dev = &mdiodev->dev;
+	const struct ksz8873_dcfg *dcfg;
+	struct ksz8873_switch *priv;
+	struct dsa_switch *ds;
+	int ret, gpio;
+
+	priv = xzalloc(sizeof(*priv));
+
+	dcfg = of_device_get_match_data(dev);
+	if (!dcfg)
+		return -EINVAL;
+
+	dev->priv = priv;
+	priv->dev = dev;
+	priv->dcfg = dcfg;
+	priv->mdiodev = mdiodev;
+
+	priv->regmap = regmap_init(dev, &ksz8873_regmap_smi, priv,
+				   &ksz8873_regmap_config);
+	if (IS_ERR(priv->regmap))
+		return dev_err_probe(dev, PTR_ERR(priv->regmap),
+				     "Failed to initialize regmap.\n");
+
+	gpio = gpiod_get(dev, "reset", GPIOF_OUT_INIT_ACTIVE);
+	if (gpio_is_valid(gpio)) {
+		mdelay(1);
+		gpio_set_active(gpio, false);
+	}
+
+	ds = &priv->ds;
+	ds->dev = dev;
+	ds->num_ports = dcfg->num_ports;
+	ds->ops = &ksz8873_dsa_ops;
+	ds->needed_rx_tailroom = 1;
+	ds->needed_tx_tailroom = 1;
+	ds->phys_mii_mask = 0x3;
+
+	ret = dsa_register_switch(ds);
+	if (ret)
+		return ret;
+
+	ksz8873_default_setup(priv);
+
+	return 0;
+}
+
+static const struct ksz8873_dcfg ksz8873_dcfg = {
+	.num_ports = 3,
+	.phy_port_cnt = 2,
+};
+
+static const struct of_device_id ksz8873_dt_ids[] = {
+	{ .compatible = "microchip,ksz8873", .data = &ksz8873_dcfg },
+	{ }
+};
+
+static struct phy_driver ksz8873_driver_mdio = {
+	.drv = {
+		.name = "KSZ8873 MDIO",
+		.of_compatible = DRV_OF_COMPAT(ksz8873_dt_ids),
+	},
+	.probe = ksz8873_probe_mdio,
+};
+device_mdio_driver(ksz8873_driver_mdio);
-- 
2.30.2




More information about the barebox mailing list