[PATCH 0/9] net: ksz9477: add support for KSZ GbE switches using SPI bus

Hello,
We are using a custom board where an ethernet switch device KSZ9896 is available. This family of devices can use several types of serial bus as management interface: mdio, i2c or SPI. Due to board design constraints and because we initially planned to use this device only from Linux, the SPI bus was used.
Luckily we are using a recent enough u-boot release where KSZ9477 driver is available... but only for the i2c interface. Indeed, unlike the kernel driver, the KSZ9477 driver doesn't use the regmap API to access the underlying bus since the regmap API is limited to direct memory access [1].
Until regmap API with bus support is available in U-boot, we introduced struct ksz_phy_ops to store low-level ksz bus operations (I2C or SPI).
This series has been tested on the current master branch (after v2024.10 release).
[1] https://source.denx.de/u-boot/u-boot/-/blob/v2024.10-rc5/drivers/core/Kconfi...
Best regards, Romain
Romain Naour (9): net: ksz9477: move struct ksz_dsa_priv *priv declaration net: ksz9477: remove dev_set_parent_priv() call net: ksz9477: add KSZ9896 switch support net: ksz9477: rename udevice_id tab to ksz_ids net: ksz9477: set i2c bus offset length only when needed net: ksz9477: store ksz bus operations functions net: ksz9477: rename ksz_i2c_probe() to ksz_probe() net: ksz9477: prepare ksz9477 without I2C support net: ksz9477: add support for KSZ GbE switches using SPI bus
drivers/net/Kconfig | 6 +- drivers/net/ksz9477.c | 241 ++++++++++++++++++++++++++++++++++++++---- 2 files changed, 223 insertions(+), 24 deletions(-)

struct ksz_dsa_priv *priv should be declared before dev_dbg()
Signed-off-by: Romain Naour romain.naour@smile.fr --- drivers/net/ksz9477.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/drivers/net/ksz9477.c b/drivers/net/ksz9477.c index 43baa699619..091e9689487 100644 --- a/drivers/net/ksz9477.c +++ b/drivers/net/ksz9477.c @@ -355,12 +355,12 @@ static int ksz_port_setup(struct udevice *dev, int port, phy_interface_t interface) { struct dsa_pdata *pdata = dev_get_uclass_plat(dev); + struct ksz_dsa_priv *priv = dev_get_priv(dev); u8 data8;
dev_dbg(dev, "%s P%d %s\n", __func__, port + 1, (port == pdata->cpu_port) ? "cpu" : "");
- struct ksz_dsa_priv *priv = dev_get_priv(dev); if (port != pdata->cpu_port) { if (priv->features & NEW_XMII) /* phy port: config errata and leds */

The ksz9477 is currently the only driver using dev_set_parent_priv() outside of the driver model. Also, there was no explanation in the commit adding ksz9477 driver and why dev_set_parent_priv() is required.
Actually there is a typo in ksz_mdio_probe() while retrieving the parent (switch@0) private data:
- priv->ksz = dev_get_parent_priv(dev->parent); + priv->ksz = dev_get_priv(dev->parent);
Printing the address of struct ksz_dsa_priv *priv allows to notice the slight difference:
ksz_i2c_probe: ksz_dsa_priv *priv 0xfdf45768 // address of the saved priv ksz_mdio_bind: ksz_dsa_priv *priv 0xfdf45798 // address returned by dev_get_parent_priv(dev->parent) ksz_mdio_bind: ksz_dsa_priv *priv 0xfdf45768 // address returned by dev_get_priv(dev->parent)
The ksz_mdio driver get the wrong data and without dev_set_parent_priv() the mdio driver fail to access the underlying bus.
While it doesn't cause any issue with I2C bus, it override the per-child data used by the SPI bus (struct spi_slave) and prevent further bus access (even with sspi command).
Signed-off-by: Romain Naour romain.naour@smile.fr --- Tested using the upcoming ksz9477 driver with spi support added later in this patch series. --- drivers/net/ksz9477.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-)
diff --git a/drivers/net/ksz9477.c b/drivers/net/ksz9477.c index 091e9689487..e67012a071a 100644 --- a/drivers/net/ksz9477.c +++ b/drivers/net/ksz9477.c @@ -276,7 +276,7 @@ static int ksz_mdio_probe(struct udevice *dev) struct ksz_mdio_priv *priv = dev_get_priv(dev);
dev_dbg(dev, "%s\n", __func__); - priv->ksz = dev_get_parent_priv(dev->parent); + priv->ksz = dev_get_priv(dev->parent);
return 0; } @@ -514,8 +514,6 @@ static int ksz_i2c_probe(struct udevice *dev) u8 data8; u32 id;
- dev_set_parent_priv(dev, priv); - ret = i2c_set_chip_offset_len(dev, 2); if (ret) { printf("i2c_set_chip_offset_len failed: %d\n", ret);

Add support for the KSZ9896 6-port Gigabit Ethernet Switch to the ksz9477 driver.
The KSZ9896 is similar to KSZ9897 but has only one configurable MII/RMII/RGMII/GMII cpu port.
Signed-off-by: Romain Naour romain.naour@smile.fr --- drivers/net/ksz9477.c | 4 ++++ 1 file changed, 4 insertions(+)
diff --git a/drivers/net/ksz9477.c b/drivers/net/ksz9477.c index e67012a071a..c8c6595d4a2 100644 --- a/drivers/net/ksz9477.c +++ b/drivers/net/ksz9477.c @@ -541,6 +541,9 @@ static int ksz_i2c_probe(struct udevice *dev) case 0x00956700: puts("KSZ9567R: "); break; + case 0x00989600: + puts("KSZ9896C: "); + break; case 0x00989700: puts("KSZ9897S: "); break; @@ -576,6 +579,7 @@ static const struct udevice_id ksz_i2c_ids[] = { { .compatible = "microchip,ksz9477" }, { .compatible = "microchip,ksz9567" }, { .compatible = "microchip,ksz9893" }, + { .compatible = "microchip,ksz9896" }, { } };

The DSA KSZ devicetree binding doesn't specify anything about the underlying bus between the SoC and the DSA switch, so the same "compatible" string can be used wathever the management interface used. The driver must be able to access the underlying bus without any help from the compatible string (like for TPM2 TIS devices).
So, rename udevice_id tab to ksz_ids since it's not specific to i2c bus.
Signed-off-by: Romain Naour romain.naour@smile.fr --- drivers/net/ksz9477.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/drivers/net/ksz9477.c b/drivers/net/ksz9477.c index c8c6595d4a2..3756f48c9ba 100644 --- a/drivers/net/ksz9477.c +++ b/drivers/net/ksz9477.c @@ -574,7 +574,7 @@ static int ksz_i2c_probe(struct udevice *dev) return 0; };
-static const struct udevice_id ksz_i2c_ids[] = { +static const struct udevice_id ksz_ids[] = { { .compatible = "microchip,ksz9897" }, { .compatible = "microchip,ksz9477" }, { .compatible = "microchip,ksz9567" }, @@ -586,7 +586,7 @@ static const struct udevice_id ksz_i2c_ids[] = { U_BOOT_DRIVER(ksz) = { .name = "ksz-switch", .id = UCLASS_DSA, - .of_match = ksz_i2c_ids, + .of_match = ksz_ids, .probe = ksz_i2c_probe, .ops = &ksz_dsa_ops, .priv_auto = sizeof(struct ksz_dsa_priv),

In order to add ksz9477 SPI bus support, check parent bus is an I2C bus before calling i2c_set_offset_len().
Doing so, ksz_i2c_probe() will now return an error (-EINVAL) if the parent bus is not the one expected by the ksz-switch u-boot driver.
Indeed, the DSA KSZ devicetree binding doesn't specify anything about the underlying bus between the SoC and the DSA switch, so the same "compatible" string can be used wathever the management interface used (SPI or I2C).
The ksz-switch u-boot driver currently only support I2C interface but will match a compatible "microchip,ksz9xxx" located under under an SPI bus node.
Signed-off-by: Romain Naour romain.naour@smile.fr --- drivers/net/ksz9477.c | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-)
diff --git a/drivers/net/ksz9477.c b/drivers/net/ksz9477.c index 3756f48c9ba..ce9d4b8753f 100644 --- a/drivers/net/ksz9477.c +++ b/drivers/net/ksz9477.c @@ -510,14 +510,25 @@ static int ksz_i2c_probe(struct udevice *dev) { struct dsa_pdata *pdata = dev_get_uclass_plat(dev); struct ksz_dsa_priv *priv = dev_get_priv(dev); + enum uclass_id parent_id = UCLASS_INVALID; int i, ret; u8 data8; u32 id;
- ret = i2c_set_chip_offset_len(dev, 2); - if (ret) { - printf("i2c_set_chip_offset_len failed: %d\n", ret); - return ret; + parent_id = device_get_uclass_id(dev_get_parent(dev)); + switch (parent_id) { + case UCLASS_I2C: { + ret = i2c_set_chip_offset_len(dev, 2); + if (ret) { + printf("i2c_set_chip_offset_len failed: %d\n", ret); + return ret; + } + break; + } + default: + dev_err(dev, "invalid parent bus (%s)\n", + uclass_get_name(parent_id)); + return -EINVAL; }
/* default config */

The ksz9477 Linux kernel driver is based on regmap API to seamlessly communicate to switch devices connected via different buses like SPI or I2C. The current regmap implementation in U-Boot only supports memory-mapped registers access [1].
Until regmap API with bus support is available in U-boot, introduce struct ksz_phy_ops to store low-level ksz bus operations (I2C for now).
[1] https://lists.denx.de/pipermail/u-boot/2018-May/329392.html
Signed-off-by: Romain Naour romain.naour@smile.fr --- drivers/net/ksz9477.c | 92 ++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 86 insertions(+), 6 deletions(-)
diff --git a/drivers/net/ksz9477.c b/drivers/net/ksz9477.c index ce9d4b8753f..f24bf9943e7 100644 --- a/drivers/net/ksz9477.c +++ b/drivers/net/ksz9477.c @@ -71,15 +71,61 @@ #define MMD_SETUP(mode, dev) (((u16)(mode) << PORT_MMD_OP_MODE_S) | (dev)) #define REG_PORT_PHY_MMD_INDEX_DATA 0x011C
+/** + * struct ksz_phy_ops - low-level KSZ bus operations + */ +struct ksz_phy_ops { + /* read() - Read bytes from the device + * + * @udev: bus device + * @reg: register offset + * @val: data read + * @len: Number of bytes to read + * + * @return: 0 on success, negative on failure + */ + int (*read)(struct udevice *udev, u32 reg, u8 *val, int len); + + /* write() - Write bytes to the device + * + * @udev: bus device + * @reg: register offset + * @val: data to write + * @len: Number of bytes to write + * + * @return: 0 on success, negative on failure + */ + int (*write)(struct udevice *udev, u32 reg, u8 *val, int len); +}; + struct ksz_dsa_priv { struct udevice *dev; + struct ksz_phy_ops *phy_ops;
u32 features; /* chip specific features */ };
+static inline int ksz_i2c_read(struct udevice *dev, u32 reg, u8 *val, int len) +{ + return dm_i2c_read(dev, reg, val, len); +} + +static inline int ksz_i2c_write(struct udevice *dev, u32 reg, u8 *val, int len) +{ + return dm_i2c_write(dev, reg, val, len); +} + +static struct ksz_phy_ops phy_i2c_ops = { + .read = ksz_i2c_read, + .write = ksz_i2c_write, +}; + static inline int ksz_read8(struct udevice *dev, u32 reg, u8 *val) { - int ret = dm_i2c_read(dev, reg, val, 1); + struct ksz_dsa_priv *priv = dev_get_priv(dev); + struct ksz_phy_ops *phy_ops = priv->phy_ops; + + int ret = phy_ops->read(dev, reg, val, 1);
dev_dbg(dev, "%s 0x%04x<<0x%02x\n", __func__, reg, *val);
@@ -93,8 +139,11 @@ static inline int ksz_pread8(struct udevice *dev, int port, int reg, u8 *val)
static inline int ksz_write8(struct udevice *dev, u32 reg, u8 val) { + struct ksz_dsa_priv *priv = dev_get_priv(dev); + struct ksz_phy_ops *phy_ops = priv->phy_ops; + dev_dbg(dev, "%s 0x%04x>>0x%02x\n", __func__, reg, val); - return dm_i2c_write(dev, reg, &val, 1); + return phy_ops->write(dev, reg, &val, 1); }
static inline int ksz_pwrite8(struct udevice *dev, int port, int reg, u8 val) @@ -104,13 +153,15 @@ static inline int ksz_pwrite8(struct udevice *dev, int port, int reg, u8 val)
static inline int ksz_write16(struct udevice *dev, u32 reg, u16 val) { + struct ksz_dsa_priv *priv = dev_get_priv(dev); + struct ksz_phy_ops *phy_ops = priv->phy_ops; u8 buf[2];
buf[1] = val & 0xff; buf[0] = val >> 8; dev_dbg(dev, "%s 0x%04x>>0x%04x\n", __func__, reg, val);
- return dm_i2c_write(dev, reg, buf, 2); + return phy_ops->write(dev, reg, buf, 2); }
static inline int ksz_pwrite16(struct udevice *dev, int port, int reg, u16 val) @@ -120,10 +171,12 @@ static inline int ksz_pwrite16(struct udevice *dev, int port, int reg, u16 val)
static inline int ksz_read16(struct udevice *dev, u32 reg, u16 *val) { + struct ksz_dsa_priv *priv = dev_get_priv(dev); + struct ksz_phy_ops *phy_ops = priv->phy_ops; u8 buf[2]; int ret;
- ret = dm_i2c_read(dev, reg, buf, 2); + ret = phy_ops->read(dev, reg, buf, 2); *val = (buf[0] << 8) | buf[1]; dev_dbg(dev, "%s 0x%04x<<0x%04x\n", __func__, reg, *val);
@@ -137,7 +190,10 @@ static inline int ksz_pread16(struct udevice *dev, int port, int reg, u16 *val)
static inline int ksz_read32(struct udevice *dev, u32 reg, u32 *val) { - return dm_i2c_read(dev, reg, (u8 *)val, 4); + struct ksz_dsa_priv *priv = dev_get_priv(dev); + struct ksz_phy_ops *phy_ops = priv->phy_ops; + + return phy_ops->read(dev, reg, (u8 *)val, 4); }
static inline int ksz_pread32(struct udevice *dev, int port, int reg, u32 *val) @@ -147,6 +203,8 @@ static inline int ksz_pread32(struct udevice *dev, int port, int reg, u32 *val)
static inline int ksz_write32(struct udevice *dev, u32 reg, u32 val) { + struct ksz_dsa_priv *priv = dev_get_priv(dev); + struct ksz_phy_ops *phy_ops = priv->phy_ops; u8 buf[4];
buf[3] = val & 0xff; @@ -155,7 +213,7 @@ static inline int ksz_write32(struct udevice *dev, u32 reg, u32 val) buf[0] = (val >> 8) & 0xff; dev_dbg(dev, "%s 0x%04x>>0x%04x\n", __func__, reg, val);
- return dm_i2c_write(dev, reg, buf, 4); + return phy_ops->write(dev, reg, buf, 4); }
static inline int ksz_pwrite32(struct udevice *dev, int port, int reg, u32 val) @@ -503,6 +561,21 @@ static int ksz_probe_mdio(struct udevice *dev) return 0; }
+static void ksz_ops_register(struct udevice *dev, struct ksz_phy_ops *ops) +{ + struct ksz_dsa_priv *priv = dev_get_priv(dev); + + priv->phy_ops = ops; +} + +static bool dsa_ksz_check_ops(struct ksz_phy_ops *phy_ops) +{ + if (!phy_ops || !phy_ops->read || !phy_ops->write) + return false; + + return true; +} + /* * I2C driver */ @@ -518,6 +591,8 @@ static int ksz_i2c_probe(struct udevice *dev) parent_id = device_get_uclass_id(dev_get_parent(dev)); switch (parent_id) { case UCLASS_I2C: { + ksz_ops_register(dev, &phy_i2c_ops); + ret = i2c_set_chip_offset_len(dev, 2); if (ret) { printf("i2c_set_chip_offset_len failed: %d\n", ret); @@ -531,6 +606,11 @@ static int ksz_i2c_probe(struct udevice *dev) return -EINVAL; }
+ if (!dsa_ksz_check_ops(priv->phy_ops)) { + printf("Driver bug. No bus ops defined\n"); + return -EINVAL; + } + /* default config */ priv->dev = dev;

In order to support management bus other than the I2C, rename ksz_i2c_probe() to ksz_probe() since this function is no longer specific to the I2C bus.
Signed-off-by: Romain Naour romain.naour@smile.fr --- drivers/net/ksz9477.c | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-)
diff --git a/drivers/net/ksz9477.c b/drivers/net/ksz9477.c index f24bf9943e7..82db533c4ec 100644 --- a/drivers/net/ksz9477.c +++ b/drivers/net/ksz9477.c @@ -576,10 +576,7 @@ static bool dsa_ksz_check_ops(struct ksz_phy_ops *phy_ops) return true; }
-/* - * I2C driver - */ -static int ksz_i2c_probe(struct udevice *dev) +static int ksz_probe(struct udevice *dev) { struct dsa_pdata *pdata = dev_get_uclass_plat(dev); struct ksz_dsa_priv *priv = dev_get_priv(dev); @@ -678,7 +675,7 @@ U_BOOT_DRIVER(ksz) = { .name = "ksz-switch", .id = UCLASS_DSA, .of_match = ksz_ids, - .probe = ksz_i2c_probe, + .probe = ksz_probe, .ops = &ksz_dsa_ops, .priv_auto = sizeof(struct ksz_dsa_priv), };

With the upcoming ksz9477 SPI support added, the I2C support will be optional. Either the I2C or the SPI bus will be used.
For now, DM_I2C is still mandatory.
Signed-off-by: Romain Naour romain.naour@smile.fr --- drivers/net/ksz9477.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-)
diff --git a/drivers/net/ksz9477.c b/drivers/net/ksz9477.c index 82db533c4ec..1190b591fcb 100644 --- a/drivers/net/ksz9477.c +++ b/drivers/net/ksz9477.c @@ -11,7 +11,9 @@ #include <eth_phy.h> #include <linux/delay.h> #include <miiphy.h> -#include <i2c.h> +#if CONFIG_IS_ENABLED(DM_I2C) +# include <i2c.h> +#endif #include <net/dsa.h>
#include <asm-generic/gpio.h> @@ -105,6 +107,7 @@ struct ksz_dsa_priv { u32 features; /* chip specific features */ };
+#if CONFIG_IS_ENABLED(DM_I2C) static inline int ksz_i2c_read(struct udevice *dev, u32 reg, u8 *val, int len) { return dm_i2c_read(dev, reg, val, len); @@ -119,6 +122,7 @@ static struct ksz_phy_ops phy_i2c_ops = { .read = ksz_i2c_read, .write = ksz_i2c_write, }; +#endif
static inline int ksz_read8(struct udevice *dev, u32 reg, u8 *val) { @@ -587,6 +591,7 @@ static int ksz_probe(struct udevice *dev)
parent_id = device_get_uclass_id(dev_get_parent(dev)); switch (parent_id) { +#if CONFIG_IS_ENABLED(DM_I2C) case UCLASS_I2C: { ksz_ops_register(dev, &phy_i2c_ops);
@@ -597,6 +602,7 @@ static int ksz_probe(struct udevice *dev) } break; } +#endif default: dev_err(dev, "invalid parent bus (%s)\n", uclass_get_name(parent_id));

The Microchip KSZ Gigabit Ethernet Switches support SGMII/RGMII/MII/RMII with register access via SPI, I2C, or MDIO.
Since this driver is now able to check the underlying bus type, handle the case when the SPI bus is used.
The SPI bus is only used for 8/16/32 wide access of registers.
Reword Kconfig option to include SPI bus support.
Signed-off-by: Romain Naour romain.naour@smile.fr --- drivers/net/Kconfig | 6 +-- drivers/net/ksz9477.c | 103 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 106 insertions(+), 3 deletions(-)
diff --git a/drivers/net/Kconfig b/drivers/net/Kconfig index e7d0ddfe25a..39379428b85 100644 --- a/drivers/net/Kconfig +++ b/drivers/net/Kconfig @@ -511,11 +511,11 @@ config KS8851_MLL The Microchip KS8851 parallel bus external ethernet interface chip.
config KSZ9477 - bool "Microchip KSZ9477 I2C controller driver" - depends on DM_DSA && DM_I2C + bool "Microchip KSZ9477 controller driver" + depends on DM_DSA && (DM_I2C || DM_SPI) help This driver implements a DSA switch driver for the KSZ9477 family - of GbE switches using the I2C interface. + of GbE switches using the I2C or SPI interface.
config LITEETH bool "LiteX LiteEth Ethernet MAC" diff --git a/drivers/net/ksz9477.c b/drivers/net/ksz9477.c index 1190b591fcb..7ebbe197660 100644 --- a/drivers/net/ksz9477.c +++ b/drivers/net/ksz9477.c @@ -14,6 +14,9 @@ #if CONFIG_IS_ENABLED(DM_I2C) # include <i2c.h> #endif +#if CONFIG_IS_ENABLED(DM_SPI) +# include <spi.h> +#endif #include <net/dsa.h>
#include <asm-generic/gpio.h> @@ -73,6 +76,14 @@ #define MMD_SETUP(mode, dev) (((u16)(mode) << PORT_MMD_OP_MODE_S) | (dev)) #define REG_PORT_PHY_MMD_INDEX_DATA 0x011C
+/* SPI specific define (opcodes) */ +#define KSZ_SPI_OP_RD 3 +#define KSZ_SPI_OP_WR 2 + +#define KSZ9477_SPI_ADDR_SHIFT 24 +#define KSZ9477_SPI_ADDR_ALIGN 3 +#define KSZ9477_SPI_TURNAROUND_SHIFT 5 + /** * struct ksz_phy_ops - low-level KSZ bus operations */ @@ -124,6 +135,92 @@ static struct ksz_phy_ops phy_i2c_ops = { }; #endif
+#if CONFIG_IS_ENABLED(DM_SPI) +/** + * ksz_spi_xfer() - only used for 8/16/32 bits bus access + * + * @dev: The SPI slave device which will be sending/receiving the data. + * @reg: register address. + * @out: Pointer to a string of bits to send out. The bits are + * held in a byte array and are sent MSB first. + * @in: Pointer to a string of bits that will be filled in. + * @len: number of bytes to read. + * + * Return: 0 on success, not 0 on failure + */ +static int ksz_spi_xfer(struct udevice *dev, u32 reg, const u8 *out, + u8 *in, u16 len) +{ + int ret; + u32 addr = 0; + u8 opcode; + + if (in && out) { + printf("%s: can't do full duplex\n", __func__); + return -EINVAL; + } + + if (len > 4 || len == 0) { + printf("%s: only 8/16/32 bits bus access supported\n", + __func__); + return -EINVAL; + } + + ret = dm_spi_claim_bus(dev); + if (ret < 0) { + printf("%s: could not claim bus\n", __func__); + return ret; + } + + opcode = (in ? KSZ_SPI_OP_RD : KSZ_SPI_OP_WR); + + /* The actual device address space is 16 bits (A15 - A0), + * so the values of address bits A23 - A16 in the SPI + * command/address phase are “don't care”. + */ + addr |= opcode << (KSZ9477_SPI_ADDR_SHIFT + KSZ9477_SPI_TURNAROUND_SHIFT); + addr |= reg << KSZ9477_SPI_TURNAROUND_SHIFT; + + addr = __swab32(addr); + + ret = dm_spi_xfer(dev, 32, &addr, NULL, SPI_XFER_BEGIN); + if (ret) { + printf("%s ERROR: dm_spi_xfer addr (%u)\n", __func__, ret); + goto release_bus; + } + + ret = dm_spi_xfer(dev, len * 8, out, in, SPI_XFER_END); + if (ret) { + printf("%s ERROR: dm_spi_xfer data (%u)\n", __func__, ret); + goto release_bus; + } + +release_bus: + /* If an error occurred, release the chip by deasserting the CS */ + if (ret < 0) + dm_spi_xfer(dev, 0, NULL, NULL, SPI_XFER_END); + + dm_spi_release_bus(dev); + + return ret; +} + +static inline int ksz_spi_read(struct udevice *dev, u32 reg, u8 *val, int len) +{ + return ksz_spi_xfer(dev, reg, NULL, val, len); +} + +static inline int ksz_spi_write(struct udevice *dev, u32 reg, u8 *val, int len) +{ + return ksz_spi_xfer(dev, reg, val, NULL, len); +} + +static struct ksz_phy_ops phy_spi_ops = { + .read = ksz_spi_read, + .write = ksz_spi_write, +}; +#endif + static inline int ksz_read8(struct udevice *dev, u32 reg, u8 *val) { struct ksz_dsa_priv *priv = dev_get_priv(dev); @@ -602,6 +699,12 @@ static int ksz_probe(struct udevice *dev) } break; } +#endif +#if CONFIG_IS_ENABLED(DM_SPI) + case UCLASS_SPI: { + ksz_ops_register(dev, &phy_spi_ops); + break; + } #endif default: dev_err(dev, "invalid parent bus (%s)\n",

On Tue, 08 Oct 2024 09:54:23 +0200, Romain Naour wrote:
We are using a custom board where an ethernet switch device KSZ9896 is available. This family of devices can use several types of serial bus as management interface: mdio, i2c or SPI. Due to board design constraints and because we initially planned to use this device only from Linux, the SPI bus was used.
Luckily we are using a recent enough u-boot release where KSZ9477 driver is available... but only for the i2c interface. Indeed, unlike the kernel driver, the KSZ9477 driver doesn't use the regmap API to access the underlying bus since the regmap API is limited to direct memory access [1].
[...]
Applied to u-boot/master, thanks!
participants (2)
-
Romain Naour
-
Tom Rini