* [PATCH net-next 01/12] net: pcs: pcs-xpcs-regmap: support XPCS memory-mapped MDIO bus via regmap
2026-05-01 15:54 [PATCH net-next 00/12] net: enable TC956x support Alex Elder
@ 2026-05-01 15:54 ` Alex Elder
2026-05-01 15:54 ` [PATCH net-next 02/12] net: pcs: pcs-xpcs: select operating mode for 10G-baseR capable PCS Alex Elder
` (11 subsequent siblings)
12 siblings, 0 replies; 37+ messages in thread
From: Alex Elder @ 2026-05-01 15:54 UTC (permalink / raw)
To: andrew+netdev, davem, edumazet, kuba, pabeni, maxime.chevallier,
rmk+kernel, andersson, konradybcio, robh, krzk+dt, conor+dt,
linusw, brgl, arnd, gregkh
Cc: Daniel Thompson, elder, mohd.anwar, a0987203069, alexandre.torgue,
ast, boon.khai.ng, chenchuangyu, chenhuacai, daniel, hawk,
hkallweit1, inochiama, john.fastabend, julianbraha,
livelycarpet87, matthew.gerlach, mcoquelin.stm32, me,
prabhakar.mahadev-lad.rj, richardcochran, rohan.g.thomas, sdf,
siyanteng, weishangjuan, wens, netdev, bpf, linux-arm-msm,
devicetree, linux-gpio, linux-stm32, linux-arm-kernel,
linux-kernel
From: Daniel Thompson <daniel@riscstar.com>
In some DesignWare XPCS implementatons the memory-mapped MDIO bus is
allocated to a register window that does not align to a page boundary.
This makes iomapping the registers problematic.
For example the Toshiba TC9564 (a PCIe Ethernet-AVB/TSN bridge) provides
an "eMAC" subsystem with the XPCS base address cuddled up to XGMAC
registers.
Let's introduce helpers to allow the driver that owns the eMAC to register
an XPCS using is regmap for the memory-mapped MDIO bus.
Signed-off-by: Daniel Thompson <daniel@riscstar.com>
Signed-off-by: Alex Elder <elder@riscstar.com>
---
drivers/net/pcs/Makefile | 4 +-
drivers/net/pcs/pcs-xpcs-regmap.c | 203 ++++++++++++++++++++++++++++
include/linux/pcs/pcs-xpcs-regmap.h | 20 +++
3 files changed, 225 insertions(+), 2 deletions(-)
create mode 100644 drivers/net/pcs/pcs-xpcs-regmap.c
create mode 100644 include/linux/pcs/pcs-xpcs-regmap.h
diff --git a/drivers/net/pcs/Makefile b/drivers/net/pcs/Makefile
index 4f7920618b900..565f1b63fce0b 100644
--- a/drivers/net/pcs/Makefile
+++ b/drivers/net/pcs/Makefile
@@ -1,8 +1,8 @@
# SPDX-License-Identifier: GPL-2.0
# Makefile for Linux PCS drivers
-pcs_xpcs-$(CONFIG_PCS_XPCS) := pcs-xpcs.o pcs-xpcs-plat.o \
- pcs-xpcs-nxp.o pcs-xpcs-wx.o
+pcs_xpcs-$(CONFIG_PCS_XPCS) := pcs-xpcs.o pcs-xpcs-nxp.o pcs-xpcs-regmap.o \
+ pcs-xpcs-plat.o pcs-xpcs-wx.o
obj-$(CONFIG_PCS_XPCS) += pcs_xpcs.o
obj-$(CONFIG_PCS_LYNX) += pcs-lynx.o
diff --git a/drivers/net/pcs/pcs-xpcs-regmap.c b/drivers/net/pcs/pcs-xpcs-regmap.c
new file mode 100644
index 0000000000000..20a54a3605951
--- /dev/null
+++ b/drivers/net/pcs/pcs-xpcs-regmap.c
@@ -0,0 +1,203 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Synopsys DesignWare XPCS regmap helpers
+ *
+ * Copyright (C) 2026 RISCstar Solutions.
+ * Copyright (C) 2024 Serge Semin
+ */
+
+#include <linux/device.h>
+#include <linux/kernel.h>
+#include <linux/mdio.h>
+#include <linux/pcs/pcs-xpcs.h>
+#include <linux/pcs/pcs-xpcs-regmap.h>
+#include <linux/regmap.h>
+
+#include "pcs-xpcs.h"
+
+/* Page select register for the indirect MMIO CSRs access */
+#define DW_VR_CSR_VIEWPORT 0xff
+
+struct dw_xpcs_regmap {
+ struct device *dev;
+ struct mii_bus *bus;
+ struct regmap *regmap;
+ bool reg_indir;
+};
+
+static ptrdiff_t xpcs_regmap_addr_format(int dev, int reg)
+{
+ return FIELD_PREP(0x1f0000, dev) | FIELD_PREP(0xffff, reg);
+}
+
+static u16 xpcs_regmap_addr_page(ptrdiff_t csr)
+{
+ return FIELD_GET(0x1fff00, csr);
+}
+
+static ptrdiff_t xpcs_regmap_addr_offset(ptrdiff_t csr)
+{
+ return FIELD_GET(0xff, csr);
+}
+
+static int xpcs_regmap_read_reg_indirect(struct dw_xpcs_regmap *pxpcs, int dev,
+ int reg)
+{
+ ptrdiff_t csr, ofs;
+ unsigned int val;
+ u16 page;
+ int res;
+
+ csr = xpcs_regmap_addr_format(dev, reg);
+ page = xpcs_regmap_addr_page(csr);
+ ofs = xpcs_regmap_addr_offset(csr);
+
+ res = regmap_write(pxpcs->regmap, DW_VR_CSR_VIEWPORT, page);
+ if (res < 0)
+ return res;
+
+ res = regmap_read(pxpcs->regmap, ofs, &val);
+ if (res < 0)
+ return res;
+
+ return val & 0xffff;
+}
+
+static int xpcs_regmap_write_reg_indirect(struct dw_xpcs_regmap *pxpcs, int dev,
+ int reg, u16 val)
+{
+ ptrdiff_t csr, ofs;
+ u16 page;
+ int res;
+
+ csr = xpcs_regmap_addr_format(dev, reg);
+ page = xpcs_regmap_addr_page(csr);
+ ofs = xpcs_regmap_addr_offset(csr);
+
+ res = regmap_write(pxpcs->regmap, DW_VR_CSR_VIEWPORT, page);
+ if (res < 0)
+ return res;
+
+ return regmap_write(pxpcs->regmap, ofs, val);
+}
+
+static int xpcs_regmap_read_reg_direct(struct dw_xpcs_regmap *pxpcs, int dev,
+ int reg)
+{
+ unsigned int val;
+ ptrdiff_t csr;
+ int res;
+
+ csr = xpcs_regmap_addr_format(dev, reg);
+ res = regmap_read(pxpcs->regmap, csr, &val);
+ if (res < 0)
+ return res;
+
+ return val & 0xffff;
+}
+
+static int xpcs_regmap_write_reg_direct(struct dw_xpcs_regmap *pxpcs, int dev,
+ int reg, u16 val)
+{
+ ptrdiff_t csr = xpcs_regmap_addr_format(dev, reg);
+
+ return regmap_write(pxpcs->regmap, csr, val);
+}
+
+static int xpcs_regmap_read_c22(struct mii_bus *bus, int addr, int reg)
+{
+ struct dw_xpcs_regmap *pxpcs = bus->priv;
+
+ if (addr != 0)
+ return -ENODEV;
+
+ if (pxpcs->reg_indir)
+ return xpcs_regmap_read_reg_indirect(pxpcs, MDIO_MMD_VEND2, reg);
+ else
+ return xpcs_regmap_read_reg_direct(pxpcs, MDIO_MMD_VEND2, reg);
+}
+
+static int xpcs_regmap_write_c22(struct mii_bus *bus, int addr, int reg, u16 val)
+{
+ struct dw_xpcs_regmap *pxpcs = bus->priv;
+
+ if (addr != 0)
+ return -ENODEV;
+
+ if (pxpcs->reg_indir)
+ return xpcs_regmap_write_reg_indirect(pxpcs, MDIO_MMD_VEND2, reg, val);
+ else
+ return xpcs_regmap_write_reg_direct(pxpcs, MDIO_MMD_VEND2, reg, val);
+}
+
+static int xpcs_regmap_read_c45(struct mii_bus *bus, int addr, int dev, int reg)
+{
+ struct dw_xpcs_regmap *pxpcs = bus->priv;
+
+ if (addr != 0)
+ return -ENODEV;
+
+ if (pxpcs->reg_indir)
+ return xpcs_regmap_read_reg_indirect(pxpcs, dev, reg);
+ else
+ return xpcs_regmap_read_reg_direct(pxpcs, dev, reg);
+}
+
+static int xpcs_regmap_write_c45(struct mii_bus *bus, int addr, int dev,
+ int reg, u16 val)
+{
+ struct dw_xpcs_regmap *pxpcs = bus->priv;
+
+ if (addr != 0)
+ return -ENODEV;
+
+ if (pxpcs->reg_indir)
+ return xpcs_regmap_write_reg_indirect(pxpcs, dev, reg, val);
+ else
+ return xpcs_regmap_write_reg_direct(pxpcs, dev, reg, val);
+}
+
+struct dw_xpcs *devm_xpcs_regmap_register(struct device *dev,
+ const struct xpcs_regmap_config *config)
+{
+ static atomic_t id = ATOMIC_INIT(-1);
+ struct dw_xpcs_regmap *pxpcs;
+ int ret;
+
+ pxpcs = devm_kzalloc(dev, sizeof(*pxpcs), GFP_KERNEL);
+ if (!pxpcs)
+ return ERR_PTR(-ENOMEM);
+
+ pxpcs->dev = dev;
+ pxpcs->regmap = config->regmap;
+ pxpcs->reg_indir = config->reg_indir;
+
+ pxpcs->bus = devm_mdiobus_alloc_size(dev, 0);
+ if (!pxpcs->bus)
+ return ERR_PTR(-ENOMEM);
+
+ pxpcs->bus->name = "DW XPCS MCI/APB3";
+ pxpcs->bus->read = xpcs_regmap_read_c22;
+ pxpcs->bus->write = xpcs_regmap_write_c22;
+ pxpcs->bus->read_c45 = xpcs_regmap_read_c45;
+ pxpcs->bus->write_c45 = xpcs_regmap_write_c45;
+ pxpcs->bus->phy_mask = ~0;
+ pxpcs->bus->parent = dev;
+ pxpcs->bus->priv = pxpcs;
+
+ snprintf(pxpcs->bus->id, MII_BUS_ID_SIZE,
+ "dwxpcs-%x", atomic_inc_return(&id));
+
+ /* MDIO-bus here serves as just a back-end engine abstracting out
+ * the MDIO and MCI/APB3 IO interfaces utilized for the DW XPCS CSRs
+ * access.
+ */
+ ret = devm_mdiobus_register(dev, pxpcs->bus);
+ if (ret) {
+ dev_err(dev, "Failed to create MDIO bus\n");
+ return ERR_PTR(ret);
+ }
+
+ return xpcs_create_mdiodev(pxpcs->bus, 0);
+}
+EXPORT_SYMBOL_GPL(devm_xpcs_regmap_register);
diff --git a/include/linux/pcs/pcs-xpcs-regmap.h b/include/linux/pcs/pcs-xpcs-regmap.h
new file mode 100644
index 0000000000000..19c99d4160365
--- /dev/null
+++ b/include/linux/pcs/pcs-xpcs-regmap.h
@@ -0,0 +1,20 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+#ifndef __LINUX_PCS_XPCS_REGMAP_H
+#define __LINUX_PCS_XPCS_REGMAP_H
+
+#include <linux/types.h>
+
+struct device;
+struct regmap;
+struct dw_xpcs;
+
+struct xpcs_regmap_config {
+ struct regmap *regmap;
+ bool reg_indir;
+};
+
+struct dw_xpcs *devm_xpcs_regmap_register(
+ struct device *dev, const struct xpcs_regmap_config *config);
+
+#endif /* __LINUX_PCS_XPCS_REGMAP_H */
--
2.51.0
^ permalink raw reply related [flat|nested] 37+ messages in thread* [PATCH net-next 02/12] net: pcs: pcs-xpcs: select operating mode for 10G-baseR capable PCS
2026-05-01 15:54 [PATCH net-next 00/12] net: enable TC956x support Alex Elder
2026-05-01 15:54 ` [PATCH net-next 01/12] net: pcs: pcs-xpcs-regmap: support XPCS memory-mapped MDIO bus via regmap Alex Elder
@ 2026-05-01 15:54 ` Alex Elder
2026-05-01 16:50 ` Andrew Lunn
2026-05-01 15:54 ` [PATCH net-next 03/12] net: pcs: pcs-xpcs: Preserve BMCR_ANENBLE during link up Alex Elder
` (10 subsequent siblings)
12 siblings, 1 reply; 37+ messages in thread
From: Alex Elder @ 2026-05-01 15:54 UTC (permalink / raw)
To: andrew+netdev, davem, edumazet, kuba, pabeni, maxime.chevallier,
rmk+kernel, andersson, konradybcio, robh, krzk+dt, conor+dt,
linusw, brgl, arnd, gregkh
Cc: Daniel Thompson, elder, mohd.anwar, a0987203069, alexandre.torgue,
ast, boon.khai.ng, chenchuangyu, chenhuacai, daniel, hawk,
hkallweit1, inochiama, john.fastabend, julianbraha,
livelycarpet87, matthew.gerlach, mcoquelin.stm32, me,
prabhakar.mahadev-lad.rj, richardcochran, rohan.g.thomas, sdf,
siyanteng, weishangjuan, wens, netdev, bpf, linux-arm-msm,
devicetree, linux-gpio, linux-stm32, linux-arm-kernel,
linux-kernel
From: Daniel Thompson <daniel@riscstar.com>
Currently the XPCS found on Toshiba TC9564 (a.k.a. Qualcomm QPS615)
is unable to operate at 1000base-X and slower with a PHY connected
using SGMII/2500base-X (in our case a Qualcomm QCA8081).
The problem arises when the XPCS supports 10Gbase-R. That means that
the reset value of SR_XS_PCS_CTRL2:PCS_TYPE_SEL (0) is valid and this
suppresses the modal switching based on bit 13 of SR_PMA_CTRL1 or
SR_XS_PCS_CTRL1.
The reported XPCS dev ID on a TC9564 is exactly the same as every other
XPCS supported by the kernel so we can't use the dev ID to automatically
determine what operating mode to select. However we can use the feature
bits in SR_XS_PCS_STS2 to detect 10Gbase-R support.
Rather than introduce a quirk let's attempt to solve this generically by
setting SR_XS_PCS_CTRL2:PCS_TYPE_SEL to a reserved value when we detect
the right we detect the right combination of phy interface and XPCS
feature support.
Signed-off-by: Daniel Thompson <daniel@riscstar.com>
Signed-off-by: Alex Elder <elder@riscstar.com>
---
drivers/net/pcs/pcs-xpcs.c | 38 ++++++++++++++++++++++++++++++++++++++
1 file changed, 38 insertions(+)
diff --git a/drivers/net/pcs/pcs-xpcs.c b/drivers/net/pcs/pcs-xpcs.c
index e69fa2f0a0e8d..b2c84b7e1e113 100644
--- a/drivers/net/pcs/pcs-xpcs.c
+++ b/drivers/net/pcs/pcs-xpcs.c
@@ -747,6 +747,40 @@ static void xpcs_pre_config(struct phylink_pcs *pcs, phy_interface_t interface)
xpcs->need_reset = false;
}
+static int xpcs_config_operating_mode(struct dw_xpcs *xpcs, int an_mode)
+{
+ int mdio_stat2, ret;
+
+ switch (an_mode) {
+ case DW_AN_C37_SGMII:
+ case DW_AN_C37_1000BASEX:
+ case DW_2500BASEX:
+ mdio_stat2 = xpcs_read(xpcs, MDIO_MMD_PCS, MDIO_STAT2);
+ if (mdio_stat2 < 0)
+ return mdio_stat2;
+
+ /*
+ * If this XPCS supports 10Gbase-R then it will be the default
+ * which prevents 1000base-X and slower from working correctly.
+ *
+ * Why are we writing MDIO_PCS_CTRL2_TYPE + 1? We want the modal
+ * behaviour that comes when we pick a reserved value. XPCS
+ * allocates extra bits to this field and allocates values from
+ * 15 down so MDIO_PCS_CTRL2_TYPE + 1 is the value likely to
+ * be allocated last (and hopefully never).
+ */
+ if (mdio_stat2 & MDIO_PCS_STAT2_10GBR) {
+ ret = xpcs_write(xpcs, MDIO_MMD_PCS, MDIO_CTRL2,
+ MDIO_PCS_CTRL2_TYPE + 1);
+ if (ret < 0)
+ return ret;
+ }
+ break;
+ }
+
+ return 0;
+}
+
static int xpcs_config_aneg_c37_sgmii(struct dw_xpcs *xpcs,
unsigned int neg_mode)
{
@@ -919,6 +953,10 @@ static int xpcs_do_config(struct dw_xpcs *xpcs, phy_interface_t interface,
if (!compat)
return -ENODEV;
+ ret = xpcs_config_operating_mode(xpcs, compat->an_mode);
+ if (ret < 0)
+ return ret;
+
if (xpcs->info.pma == WX_TXGBE_XPCS_PMA_10G_ID) {
/* Wangxun devices need backplane CL37 AN enabled for
* SGMII and 1000base-X
--
2.51.0
^ permalink raw reply related [flat|nested] 37+ messages in thread* Re: [PATCH net-next 02/12] net: pcs: pcs-xpcs: select operating mode for 10G-baseR capable PCS
2026-05-01 15:54 ` [PATCH net-next 02/12] net: pcs: pcs-xpcs: select operating mode for 10G-baseR capable PCS Alex Elder
@ 2026-05-01 16:50 ` Andrew Lunn
2026-05-01 18:07 ` Alex Elder
0 siblings, 1 reply; 37+ messages in thread
From: Andrew Lunn @ 2026-05-01 16:50 UTC (permalink / raw)
To: Alex Elder
Cc: andrew+netdev, davem, edumazet, kuba, pabeni, maxime.chevallier,
rmk+kernel, andersson, konradybcio, robh, krzk+dt, conor+dt,
linusw, brgl, arnd, gregkh, Daniel Thompson, mohd.anwar,
a0987203069, alexandre.torgue, ast, boon.khai.ng, chenchuangyu,
chenhuacai, daniel, hawk, hkallweit1, inochiama, john.fastabend,
julianbraha, livelycarpet87, matthew.gerlach, mcoquelin.stm32, me,
prabhakar.mahadev-lad.rj, richardcochran, rohan.g.thomas, sdf,
siyanteng, weishangjuan, wens, netdev, bpf, linux-arm-msm,
devicetree, linux-gpio, linux-stm32, linux-arm-kernel,
linux-kernel
> +static int xpcs_config_operating_mode(struct dw_xpcs *xpcs, int an_mode)
> +{
> + int mdio_stat2, ret;
> +
> + switch (an_mode) {
> + case DW_AN_C37_SGMII:
> + case DW_AN_C37_1000BASEX:
> + case DW_2500BASEX:
> + mdio_stat2 = xpcs_read(xpcs, MDIO_MMD_PCS, MDIO_STAT2);
> + if (mdio_stat2 < 0)
> + return mdio_stat2;
> +
> + /*
> + * If this XPCS supports 10Gbase-R then it will be the default
> + * which prevents 1000base-X and slower from working correctly.
It would be interesting to know if Toshiba messed up the integration
of the PCS, or there is an errata for the licensed IP.
Anybody got access to the databook and erratas?
Andrew
^ permalink raw reply [flat|nested] 37+ messages in thread* Re: [PATCH net-next 02/12] net: pcs: pcs-xpcs: select operating mode for 10G-baseR capable PCS
2026-05-01 16:50 ` Andrew Lunn
@ 2026-05-01 18:07 ` Alex Elder
0 siblings, 0 replies; 37+ messages in thread
From: Alex Elder @ 2026-05-01 18:07 UTC (permalink / raw)
To: Andrew Lunn
Cc: andrew+netdev, davem, edumazet, kuba, pabeni, maxime.chevallier,
rmk+kernel, andersson, konradybcio, robh, krzk+dt, conor+dt,
linusw, brgl, arnd, gregkh, Daniel Thompson, mohd.anwar,
a0987203069, alexandre.torgue, ast, boon.khai.ng, chenchuangyu,
chenhuacai, daniel, hawk, hkallweit1, inochiama, john.fastabend,
julianbraha, livelycarpet87, matthew.gerlach, mcoquelin.stm32, me,
prabhakar.mahadev-lad.rj, richardcochran, rohan.g.thomas, sdf,
siyanteng, weishangjuan, wens, netdev, bpf, linux-arm-msm,
devicetree, linux-gpio, linux-stm32, linux-arm-kernel,
linux-kernel
On 5/1/26 11:50 AM, Andrew Lunn wrote:
>> +static int xpcs_config_operating_mode(struct dw_xpcs *xpcs, int an_mode)
>> +{
>> + int mdio_stat2, ret;
>> +
>> + switch (an_mode) {
>> + case DW_AN_C37_SGMII:
>> + case DW_AN_C37_1000BASEX:
>> + case DW_2500BASEX:
>> + mdio_stat2 = xpcs_read(xpcs, MDIO_MMD_PCS, MDIO_STAT2);
>> + if (mdio_stat2 < 0)
>> + return mdio_stat2;
>> +
>> + /*
>> + * If this XPCS supports 10Gbase-R then it will be the default
>> + * which prevents 1000base-X and slower from working correctly.
>
> It would be interesting to know if Toshiba messed up the integration
> of the PCS, or there is an errata for the licensed IP.
>
> Anybody got access to the databook and erratas?
>
> Andrew
I have the same question. We do not (currently/yet?) have access
to this information.
-Alex
^ permalink raw reply [flat|nested] 37+ messages in thread
* [PATCH net-next 03/12] net: pcs: pcs-xpcs: Preserve BMCR_ANENBLE during link up
2026-05-01 15:54 [PATCH net-next 00/12] net: enable TC956x support Alex Elder
2026-05-01 15:54 ` [PATCH net-next 01/12] net: pcs: pcs-xpcs-regmap: support XPCS memory-mapped MDIO bus via regmap Alex Elder
2026-05-01 15:54 ` [PATCH net-next 02/12] net: pcs: pcs-xpcs: select operating mode for 10G-baseR capable PCS Alex Elder
@ 2026-05-01 15:54 ` Alex Elder
2026-05-01 17:06 ` Andrew Lunn
2026-05-01 15:54 ` [PATCH net-next 04/12] net: stmmac: dma: create a separate dma_device pointer Alex Elder
` (9 subsequent siblings)
12 siblings, 1 reply; 37+ messages in thread
From: Alex Elder @ 2026-05-01 15:54 UTC (permalink / raw)
To: andrew+netdev, davem, edumazet, kuba, pabeni, maxime.chevallier,
rmk+kernel, andersson, konradybcio, robh, krzk+dt, conor+dt,
linusw, brgl, arnd, gregkh
Cc: Daniel Thompson, elder, mohd.anwar, a0987203069, alexandre.torgue,
ast, boon.khai.ng, chenchuangyu, chenhuacai, daniel, hawk,
hkallweit1, inochiama, john.fastabend, julianbraha,
livelycarpet87, matthew.gerlach, mcoquelin.stm32, me,
prabhakar.mahadev-lad.rj, richardcochran, rohan.g.thomas, sdf,
siyanteng, weishangjuan, wens, netdev, bpf, linux-arm-msm,
devicetree, linux-gpio, linux-stm32, linux-arm-kernel,
linux-kernel
From: Daniel Thompson <daniel@riscstar.com>
Currently the XCPS found on Toshiba TC9564 (a.k.a. Qualcomm QPS615)
is unable to operate at 1000base-X and slower with a PHY connected
using SGMII/2500base-X (in our case a Qualcomm QCA8081). The link
negotiates speed correctly but the MAC can't get any packets out.
This attracted attention to the ANENABLE bit and we observed that the
bit is currently set during config and cleared during link up.
Preserving the bit during link up allows the system to work as expected.
Perhaps I lack the imagination but I couldn't come up with any reason
why keeping the ANENABLE bit set would break things for other XPCS
implementations. Let's ensure link up sets the bit for SGMII interfaces.
Signed-off-by: Daniel Thompson <daniel@riscstar.com>
Signed-off-by: Alex Elder <elder@riscstar.com>
---
drivers/net/pcs/pcs-xpcs.c | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/drivers/net/pcs/pcs-xpcs.c b/drivers/net/pcs/pcs-xpcs.c
index b2c84b7e1e113..1d62d5b31c61c 100644
--- a/drivers/net/pcs/pcs-xpcs.c
+++ b/drivers/net/pcs/pcs-xpcs.c
@@ -1263,11 +1263,14 @@ static void xpcs_link_up_sgmii_1000basex(struct dw_xpcs *xpcs,
phy_interface_t interface,
int speed, int duplex)
{
+ u16 an_enable;
int ret;
if (neg_mode == PHYLINK_PCS_NEG_INBAND_ENABLED)
return;
+ an_enable = (interface == PHY_INTERFACE_MODE_SGMII ? BMCR_ANENABLE : 0);
+
if (interface == PHY_INTERFACE_MODE_1000BASEX) {
if (speed != SPEED_1000) {
dev_err(&xpcs->mdiodev->dev,
@@ -1283,7 +1286,7 @@ static void xpcs_link_up_sgmii_1000basex(struct dw_xpcs *xpcs,
}
ret = xpcs_write(xpcs, MDIO_MMD_VEND2, MII_BMCR,
- mii_bmcr_encode_fixed(speed, duplex));
+ mii_bmcr_encode_fixed(speed, duplex) | an_enable);
if (ret)
dev_err(&xpcs->mdiodev->dev, "%s: xpcs_write returned %pe\n",
__func__, ERR_PTR(ret));
--
2.51.0
^ permalink raw reply related [flat|nested] 37+ messages in thread* Re: [PATCH net-next 03/12] net: pcs: pcs-xpcs: Preserve BMCR_ANENBLE during link up
2026-05-01 15:54 ` [PATCH net-next 03/12] net: pcs: pcs-xpcs: Preserve BMCR_ANENBLE during link up Alex Elder
@ 2026-05-01 17:06 ` Andrew Lunn
0 siblings, 0 replies; 37+ messages in thread
From: Andrew Lunn @ 2026-05-01 17:06 UTC (permalink / raw)
To: Alex Elder
Cc: andrew+netdev, davem, edumazet, kuba, pabeni, maxime.chevallier,
rmk+kernel, andersson, konradybcio, robh, krzk+dt, conor+dt,
linusw, brgl, arnd, gregkh, Daniel Thompson, mohd.anwar,
a0987203069, alexandre.torgue, ast, boon.khai.ng, chenchuangyu,
chenhuacai, daniel, hawk, hkallweit1, inochiama, john.fastabend,
julianbraha, livelycarpet87, matthew.gerlach, mcoquelin.stm32, me,
prabhakar.mahadev-lad.rj, richardcochran, rohan.g.thomas, sdf,
siyanteng, weishangjuan, wens, netdev, bpf, linux-arm-msm,
devicetree, linux-gpio, linux-stm32, linux-arm-kernel,
linux-kernel
On Fri, May 01, 2026 at 10:54:11AM -0500, Alex Elder wrote:
> From: Daniel Thompson <daniel@riscstar.com>
>
> Currently the XCPS found on Toshiba TC9564 (a.k.a. Qualcomm QPS615)
> is unable to operate at 1000base-X and slower with a PHY connected
> using SGMII/2500base-X (in our case a Qualcomm QCA8081). The link
> negotiates speed correctly but the MAC can't get any packets out.
We need to break this down into its components.
I assume you are saying the PHY negotiates the media speed with the
link partner. That is PHY talking to PHY.
But we also have the PCS talking to the PHY. There can be inband
signalling here, for SGMII and 2500BaseX. But not for over clocked
SGMII, which is how some vendors implement 2500BaseX. SGMII signalling
does not work when overclocked to 2.5G.
> This attracted attention to the ANENABLE bit and we observed that the
> bit is currently set during config and cleared during link up.
Here we are talking about the PCS ANEBNABLE, not the PHY ANEBNABLE. So
this is negotiation between the PCS and the PHY.
> Preserving the bit during link up allows the system to work as expected.
> int ret;
>
> if (neg_mode == PHYLINK_PCS_NEG_INBAND_ENABLED)
> return;
Think about this.
> ret = xpcs_write(xpcs, MDIO_MMD_VEND2, MII_BMCR,
> - mii_bmcr_encode_fixed(speed, duplex));
> + mii_bmcr_encode_fixed(speed, duplex) | an_enable);
And mii_bmcr_encode_fixed().
Andrew
^ permalink raw reply [flat|nested] 37+ messages in thread
* [PATCH net-next 04/12] net: stmmac: dma: create a separate dma_device pointer
2026-05-01 15:54 [PATCH net-next 00/12] net: enable TC956x support Alex Elder
` (2 preceding siblings ...)
2026-05-01 15:54 ` [PATCH net-next 03/12] net: pcs: pcs-xpcs: Preserve BMCR_ANENBLE during link up Alex Elder
@ 2026-05-01 15:54 ` Alex Elder
2026-05-01 17:13 ` Andrew Lunn
2026-05-01 15:54 ` [PATCH net-next 05/12] net: stmmac: dwxgmac2: Add multi MSI interrupt mode Alex Elder
` (8 subsequent siblings)
12 siblings, 1 reply; 37+ messages in thread
From: Alex Elder @ 2026-05-01 15:54 UTC (permalink / raw)
To: andrew+netdev, davem, edumazet, kuba, pabeni, maxime.chevallier,
rmk+kernel, andersson, konradybcio, robh, krzk+dt, conor+dt,
linusw, brgl, arnd, gregkh
Cc: daniel, elder, mohd.anwar, a0987203069, alexandre.torgue, ast,
boon.khai.ng, chenchuangyu, chenhuacai, daniel, hawk, hkallweit1,
inochiama, john.fastabend, julianbraha, livelycarpet87,
matthew.gerlach, mcoquelin.stm32, me, prabhakar.mahadev-lad.rj,
richardcochran, rohan.g.thomas, sdf, siyanteng, weishangjuan,
wens, netdev, bpf, linux-arm-msm, devicetree, linux-gpio,
linux-stm32, linux-arm-kernel, linux-kernel
The Toshiba TC956x Ethernet bridge chip is an Ethernet AVN/TSN bridge
that is essentially a small but specialized SoC. It provides two XGMAC
Ethernet interfaces along with a number of internal IP blocks, some of
which are used by both eMACs.
The chip implements two internal PCIe functions, and one of these is
used to manage the common internal IPs. Both of the PCIe functions
use an auxiliary bus device to represent an XGMAC Ethernet interface.
Separating the PCIe function from the XGMAC IP this way helps in
managing the life cycle for various objects (common and per-MAC).
However this separation means that the MAC device is no longer the
proper device to use for DMA. To address this, we add support for
a second "DMA device" pointer in the stmmac_priv structure. The DMA
device pointer is used for all DMA operations, while the "normal"
device pointer is used for log messages, memory allocation, runtime
power management, and a few other things.
To set up the DMA device pointer, we add a new device structure pointer
to the plat_stmmacenet_data structure. If set, it will be assigned as
the (new) dma_device pointer field in the stmmac_priv structure. If
the plat_stmmacenet_data field is NULL, the "normal" device pointer is
assigned as the dma_device pointer instead (preserving existing behavior).
Signed-off-by: Alex Elder <elder@riscstar.com>
---
.../net/ethernet/stmicro/stmmac/chain_mode.c | 12 ++--
.../net/ethernet/stmicro/stmmac/ring_mode.c | 12 ++--
drivers/net/ethernet/stmicro/stmmac/stmmac.h | 1 +
.../net/ethernet/stmicro/stmmac/stmmac_main.c | 59 ++++++++++---------
.../net/ethernet/stmicro/stmmac/stmmac_xdp.c | 2 +-
include/linux/stmmac.h | 1 +
6 files changed, 46 insertions(+), 41 deletions(-)
diff --git a/drivers/net/ethernet/stmicro/stmmac/chain_mode.c b/drivers/net/ethernet/stmicro/stmmac/chain_mode.c
index fc04a23342cfc..331e6523ee018 100644
--- a/drivers/net/ethernet/stmicro/stmmac/chain_mode.c
+++ b/drivers/net/ethernet/stmicro/stmmac/chain_mode.c
@@ -34,10 +34,10 @@ static int jumbo_frm(struct stmmac_tx_queue *tx_q, struct sk_buff *skb,
buf_len = min_t(unsigned int, nopaged_len, bmax);
len = nopaged_len - buf_len;
- des2 = dma_map_single(priv->device, skb->data,
+ des2 = dma_map_single(priv->dma_device, skb->data,
buf_len, DMA_TO_DEVICE);
desc->des2 = cpu_to_le32(des2);
- if (dma_mapping_error(priv->device, des2))
+ if (dma_mapping_error(priv->dma_device, des2))
return -1;
tx_q->tx_skbuff_dma[entry].buf = des2;
tx_q->tx_skbuff_dma[entry].len = buf_len;
@@ -51,11 +51,11 @@ static int jumbo_frm(struct stmmac_tx_queue *tx_q, struct sk_buff *skb,
desc = tx_q->dma_tx + entry;
if (len > bmax) {
- des2 = dma_map_single(priv->device,
+ des2 = dma_map_single(priv->dma_device,
(skb->data + bmax * i),
bmax, DMA_TO_DEVICE);
desc->des2 = cpu_to_le32(des2);
- if (dma_mapping_error(priv->device, des2))
+ if (dma_mapping_error(priv->dma_device, des2))
return -1;
tx_q->tx_skbuff_dma[entry].buf = des2;
tx_q->tx_skbuff_dma[entry].len = bmax;
@@ -64,11 +64,11 @@ static int jumbo_frm(struct stmmac_tx_queue *tx_q, struct sk_buff *skb,
len -= bmax;
i++;
} else {
- des2 = dma_map_single(priv->device,
+ des2 = dma_map_single(priv->dma_device,
(skb->data + bmax * i), len,
DMA_TO_DEVICE);
desc->des2 = cpu_to_le32(des2);
- if (dma_mapping_error(priv->device, des2))
+ if (dma_mapping_error(priv->dma_device, des2))
return -1;
tx_q->tx_skbuff_dma[entry].buf = des2;
tx_q->tx_skbuff_dma[entry].len = len;
diff --git a/drivers/net/ethernet/stmicro/stmmac/ring_mode.c b/drivers/net/ethernet/stmicro/stmmac/ring_mode.c
index 78fc6aa5bbe95..0d334c51fc1c2 100644
--- a/drivers/net/ethernet/stmicro/stmmac/ring_mode.c
+++ b/drivers/net/ethernet/stmicro/stmmac/ring_mode.c
@@ -37,10 +37,10 @@ static int jumbo_frm(struct stmmac_tx_queue *tx_q, struct sk_buff *skb,
if (nopaged_len > BUF_SIZE_8KiB) {
- des2 = dma_map_single(priv->device, skb->data, bmax,
+ des2 = dma_map_single(priv->dma_device, skb->data, bmax,
DMA_TO_DEVICE);
desc->des2 = cpu_to_le32(des2);
- if (dma_mapping_error(priv->device, des2))
+ if (dma_mapping_error(priv->dma_device, des2))
return -1;
tx_q->tx_skbuff_dma[entry].buf = des2;
@@ -58,10 +58,10 @@ static int jumbo_frm(struct stmmac_tx_queue *tx_q, struct sk_buff *skb,
else
desc = tx_q->dma_tx + entry;
- des2 = dma_map_single(priv->device, skb->data + bmax, len,
+ des2 = dma_map_single(priv->dma_device, skb->data + bmax, len,
DMA_TO_DEVICE);
desc->des2 = cpu_to_le32(des2);
- if (dma_mapping_error(priv->device, des2))
+ if (dma_mapping_error(priv->dma_device, des2))
return -1;
tx_q->tx_skbuff_dma[entry].buf = des2;
tx_q->tx_skbuff_dma[entry].len = len;
@@ -72,10 +72,10 @@ static int jumbo_frm(struct stmmac_tx_queue *tx_q, struct sk_buff *skb,
STMMAC_RING_MODE, 1, !skb_is_nonlinear(skb),
skb->len);
} else {
- des2 = dma_map_single(priv->device, skb->data,
+ des2 = dma_map_single(priv->dma_device, skb->data,
nopaged_len, DMA_TO_DEVICE);
desc->des2 = cpu_to_le32(des2);
- if (dma_mapping_error(priv->device, des2))
+ if (dma_mapping_error(priv->dma_device, des2))
return -1;
tx_q->tx_skbuff_dma[entry].buf = des2;
tx_q->tx_skbuff_dma[entry].len = nopaged_len;
diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac.h b/drivers/net/ethernet/stmicro/stmmac/stmmac.h
index 8ba8f03e1ce03..76c8551687998 100644
--- a/drivers/net/ethernet/stmicro/stmmac/stmmac.h
+++ b/drivers/net/ethernet/stmicro/stmmac/stmmac.h
@@ -278,6 +278,7 @@ struct stmmac_priv {
void __iomem *ioaddr;
struct net_device *dev;
struct device *device;
+ struct device *dma_device;
struct mac_device_info *hw;
int (*hwif_quirks)(struct stmmac_priv *priv);
struct mutex lock;
diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c
index ca68248dbc781..1104cf750295b 100644
--- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c
+++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c
@@ -1730,12 +1730,12 @@ static void stmmac_free_tx_buffer(struct stmmac_priv *priv,
if (tx_q->tx_skbuff_dma[i].buf &&
tx_q->tx_skbuff_dma[i].buf_type != STMMAC_TXBUF_T_XDP_TX) {
if (tx_q->tx_skbuff_dma[i].map_as_page)
- dma_unmap_page(priv->device,
+ dma_unmap_page(priv->dma_device,
tx_q->tx_skbuff_dma[i].buf,
tx_q->tx_skbuff_dma[i].len,
DMA_TO_DEVICE);
else
- dma_unmap_single(priv->device,
+ dma_unmap_single(priv->dma_device,
tx_q->tx_skbuff_dma[i].buf,
tx_q->tx_skbuff_dma[i].len,
DMA_TO_DEVICE);
@@ -2166,7 +2166,7 @@ static void __free_dma_rx_desc_resources(struct stmmac_priv *priv,
size = stmmac_get_rx_desc_size(priv) * dma_conf->dma_rx_size;
- dma_free_coherent(priv->device, size, addr, rx_q->dma_rx_phy);
+ dma_free_coherent(priv->dma_device, size, addr, rx_q->dma_rx_phy);
if (xdp_rxq_info_is_reg(&rx_q->xdp_rxq))
xdp_rxq_info_unreg(&rx_q->xdp_rxq);
@@ -2214,7 +2214,7 @@ static void __free_dma_tx_desc_resources(struct stmmac_priv *priv,
size = stmmac_get_tx_desc_size(priv, tx_q) * dma_conf->dma_tx_size;
- dma_free_coherent(priv->device, size, addr, tx_q->dma_tx_phy);
+ dma_free_coherent(priv->dma_device, size, addr, tx_q->dma_tx_phy);
kfree(tx_q->tx_skbuff_dma);
kfree(tx_q->tx_skbuff);
@@ -2266,8 +2266,8 @@ static int __alloc_dma_rx_desc_resources(struct stmmac_priv *priv,
pp_params.flags = PP_FLAG_DMA_MAP | PP_FLAG_DMA_SYNC_DEV;
pp_params.pool_size = dma_conf->dma_rx_size;
pp_params.order = order_base_2(num_pages);
- pp_params.nid = dev_to_node(priv->device);
- pp_params.dev = priv->device;
+ pp_params.nid = dev_to_node(priv->dma_device);
+ pp_params.dev = priv->dma_device;
pp_params.dma_dir = xdp_prog ? DMA_BIDIRECTIONAL : DMA_FROM_DEVICE;
pp_params.offset = stmmac_rx_offset(priv);
pp_params.max_len = dma_conf->dma_buf_sz;
@@ -2290,7 +2290,7 @@ static int __alloc_dma_rx_desc_resources(struct stmmac_priv *priv,
size = stmmac_get_rx_desc_size(priv) * dma_conf->dma_rx_size;
- addr = dma_alloc_coherent(priv->device, size, &rx_q->dma_rx_phy,
+ addr = dma_alloc_coherent(priv->dma_device, size, &rx_q->dma_rx_phy,
GFP_KERNEL);
if (!addr)
return -ENOMEM;
@@ -2369,7 +2369,7 @@ static int __alloc_dma_tx_desc_resources(struct stmmac_priv *priv,
size = stmmac_get_tx_desc_size(priv, tx_q) * dma_conf->dma_tx_size;
- addr = dma_alloc_coherent(priv->device, size,
+ addr = dma_alloc_coherent(priv->dma_device, size,
&tx_q->dma_tx_phy, GFP_KERNEL);
if (!addr)
return -ENOMEM;
@@ -2898,12 +2898,12 @@ static int stmmac_tx_clean(struct stmmac_priv *priv, int budget, u32 queue,
if (likely(tx_q->tx_skbuff_dma[entry].buf &&
tx_q->tx_skbuff_dma[entry].buf_type != STMMAC_TXBUF_T_XDP_TX)) {
if (tx_q->tx_skbuff_dma[entry].map_as_page)
- dma_unmap_page(priv->device,
+ dma_unmap_page(priv->dma_device,
tx_q->tx_skbuff_dma[entry].buf,
tx_q->tx_skbuff_dma[entry].len,
DMA_TO_DEVICE);
else
- dma_unmap_single(priv->device,
+ dma_unmap_single(priv->dma_device,
tx_q->tx_skbuff_dma[entry].buf,
tx_q->tx_skbuff_dma[entry].len,
DMA_TO_DEVICE);
@@ -4569,9 +4569,9 @@ static netdev_tx_t stmmac_tso_xmit(struct sk_buff *skb, struct net_device *dev)
first = desc;
/* first descriptor: fill Headers on Buf1 */
- des = dma_map_single(priv->device, skb->data, skb_headlen(skb),
+ des = dma_map_single(priv->dma_device, skb->data, skb_headlen(skb),
DMA_TO_DEVICE);
- if (dma_mapping_error(priv->device, des))
+ if (dma_mapping_error(priv->dma_device, des))
goto dma_map_err;
stmmac_set_desc_addr(priv, first, des);
@@ -4597,10 +4597,10 @@ static netdev_tx_t stmmac_tso_xmit(struct sk_buff *skb, struct net_device *dev)
for (i = 0; i < nfrags; i++) {
const skb_frag_t *frag = &skb_shinfo(skb)->frags[i];
- des = skb_frag_dma_map(priv->device, frag, 0,
+ des = skb_frag_dma_map(priv->dma_device, frag, 0,
skb_frag_size(frag),
DMA_TO_DEVICE);
- if (dma_mapping_error(priv->device, des))
+ if (dma_mapping_error(priv->dma_device, des))
goto dma_map_err;
stmmac_tso_allocator(priv, des, skb_frag_size(frag),
@@ -4825,9 +4825,9 @@ static netdev_tx_t stmmac_xmit(struct sk_buff *skb, struct net_device *dev)
} else {
bool last_segment = (nfrags == 0);
- dma_addr = dma_map_single(priv->device, skb->data,
+ dma_addr = dma_map_single(priv->dma_device, skb->data,
nopaged_len, DMA_TO_DEVICE);
- if (dma_mapping_error(priv->device, dma_addr))
+ if (dma_mapping_error(priv->dma_device, dma_addr))
goto dma_map_err;
stmmac_set_tx_skb_dma_entry(tx_q, first_entry, dma_addr,
@@ -4876,9 +4876,9 @@ static netdev_tx_t stmmac_xmit(struct sk_buff *skb, struct net_device *dev)
desc = stmmac_get_tx_desc(priv, tx_q, entry);
- dma_addr = skb_frag_dma_map(priv->device, frag, 0, frag_size,
- DMA_TO_DEVICE);
- if (dma_mapping_error(priv->device, dma_addr))
+ dma_addr = skb_frag_dma_map(priv->dma_device, frag, 0,
+ frag_size, DMA_TO_DEVICE);
+ if (dma_mapping_error(priv->dma_device, dma_addr))
goto dma_map_err; /* should reuse desc w/o issues */
stmmac_set_tx_skb_dma_entry(tx_q, entry, dma_addr, frag_size,
@@ -5188,9 +5188,9 @@ static int stmmac_xdp_xmit_xdpf(struct stmmac_priv *priv, int queue,
tx_desc = stmmac_get_tx_desc(priv, tx_q, entry);
if (dma_map) {
- dma_addr = dma_map_single(priv->device, xdpf->data,
+ dma_addr = dma_map_single(priv->dma_device, xdpf->data,
xdpf->len, DMA_TO_DEVICE);
- if (dma_mapping_error(priv->device, dma_addr))
+ if (dma_mapping_error(priv->dma_device, dma_addr))
return STMMAC_XDP_CONSUMED;
buf_type = STMMAC_TXBUF_T_XDP_NDO;
@@ -5199,7 +5199,7 @@ static int stmmac_xdp_xmit_xdpf(struct stmmac_priv *priv, int queue,
dma_addr = page_pool_get_dma_addr(page) + sizeof(*xdpf) +
xdpf->headroom;
- dma_sync_single_for_device(priv->device, dma_addr,
+ dma_sync_single_for_device(priv->dma_device, dma_addr,
xdpf->len, DMA_BIDIRECTIONAL);
buf_type = STMMAC_TXBUF_T_XDP_TX;
@@ -5781,7 +5781,7 @@ static int stmmac_rx(struct stmmac_priv *priv, int limit, u32 queue)
if (!skb) {
unsigned int pre_len, sync_len;
- dma_sync_single_for_cpu(priv->device, buf->addr,
+ dma_sync_single_for_cpu(priv->dma_device, buf->addr,
buf1_len, dma_dir);
net_prefetch(page_address(buf->page) +
buf->page_offset);
@@ -5860,7 +5860,7 @@ static int stmmac_rx(struct stmmac_priv *priv, int limit, u32 queue)
skb_mark_for_recycle(skb);
buf->page = NULL;
} else if (buf1_len) {
- dma_sync_single_for_cpu(priv->device, buf->addr,
+ dma_sync_single_for_cpu(priv->dma_device, buf->addr,
buf1_len, dma_dir);
skb_add_rx_frag(skb, skb_shinfo(skb)->nr_frags,
buf->page, buf->page_offset, buf1_len,
@@ -5869,7 +5869,7 @@ static int stmmac_rx(struct stmmac_priv *priv, int limit, u32 queue)
}
if (buf2_len) {
- dma_sync_single_for_cpu(priv->device, buf->sec_addr,
+ dma_sync_single_for_cpu(priv->dma_device, buf->sec_addr,
buf2_len, dma_dir);
skb_add_rx_frag(skb, skb_shinfo(skb)->nr_frags,
buf->sec_page, 0, buf2_len,
@@ -7810,6 +7810,7 @@ static int __stmmac_dvr_probe(struct device *device,
priv = netdev_priv(ndev);
priv->device = device;
+ priv->dma_device = plat_dat->dma_device ? : device;
priv->dev = ndev;
for (i = 0; i < MTL_MAX_RX_QUEUES; i++)
@@ -7938,8 +7939,9 @@ static int __stmmac_dvr_probe(struct device *device,
priv->dma_cap.host_dma_width = priv->dma_cap.addr64;
if (priv->dma_cap.host_dma_width) {
- ret = dma_set_mask_and_coherent(device,
- DMA_BIT_MASK(priv->dma_cap.host_dma_width));
+ u64 mask = DMA_BIT_MASK(priv->dma_cap.host_dma_width);
+
+ ret = dma_set_mask_and_coherent(priv->dma_device, mask);
if (!ret) {
dev_info(priv->device, "Using %d/%d bits DMA host/device width\n",
priv->dma_cap.host_dma_width, priv->dma_cap.addr64);
@@ -7951,7 +7953,8 @@ static int __stmmac_dvr_probe(struct device *device,
if (IS_ENABLED(CONFIG_ARCH_DMA_ADDR_T_64BIT))
priv->plat->dma_cfg->eame = true;
} else {
- ret = dma_set_mask_and_coherent(device, DMA_BIT_MASK(32));
+ ret = dma_set_mask_and_coherent(priv->dma_device,
+ DMA_BIT_MASK(32));
if (ret) {
dev_err(priv->device, "Failed to set DMA Mask\n");
goto error_hw_init;
diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_xdp.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_xdp.c
index d7e4db7224b0c..7ba068f1ca88d 100644
--- a/drivers/net/ethernet/stmicro/stmmac/stmmac_xdp.c
+++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_xdp.c
@@ -25,7 +25,7 @@ static int stmmac_xdp_enable_pool(struct stmmac_priv *priv,
if (frame_size < ETH_FRAME_LEN + VLAN_HLEN * 2)
return -EOPNOTSUPP;
- err = xsk_pool_dma_map(pool, priv->device, STMMAC_RX_DMA_ATTR);
+ err = xsk_pool_dma_map(pool, priv->dma_device, STMMAC_RX_DMA_ATTR);
if (err) {
netdev_err(priv->dev, "Failed to map xsk pool\n");
return err;
diff --git a/include/linux/stmmac.h b/include/linux/stmmac.h
index 4430b967abdeb..02ae177d5c27d 100644
--- a/include/linux/stmmac.h
+++ b/include/linux/stmmac.h
@@ -245,6 +245,7 @@ struct plat_stmmacenet_data {
struct stmmac_mdio_bus_data *mdio_bus_data;
struct device_node *phy_node;
struct device_node *mdio_node;
+ struct device *dma_device;
struct stmmac_dma_cfg *dma_cfg;
struct stmmac_safety_feature_cfg *safety_feat_cfg;
int clk_csr;
--
2.51.0
^ permalink raw reply related [flat|nested] 37+ messages in thread* Re: [PATCH net-next 04/12] net: stmmac: dma: create a separate dma_device pointer
2026-05-01 15:54 ` [PATCH net-next 04/12] net: stmmac: dma: create a separate dma_device pointer Alex Elder
@ 2026-05-01 17:13 ` Andrew Lunn
2026-05-01 18:06 ` Alex Elder
0 siblings, 1 reply; 37+ messages in thread
From: Andrew Lunn @ 2026-05-01 17:13 UTC (permalink / raw)
To: Alex Elder
Cc: andrew+netdev, davem, edumazet, kuba, pabeni, maxime.chevallier,
rmk+kernel, andersson, konradybcio, robh, krzk+dt, conor+dt,
linusw, brgl, arnd, gregkh, daniel, mohd.anwar, a0987203069,
alexandre.torgue, ast, boon.khai.ng, chenchuangyu, chenhuacai,
daniel, hawk, hkallweit1, inochiama, john.fastabend, julianbraha,
livelycarpet87, matthew.gerlach, mcoquelin.stm32, me,
prabhakar.mahadev-lad.rj, richardcochran, rohan.g.thomas, sdf,
siyanteng, weishangjuan, wens, netdev, bpf, linux-arm-msm,
devicetree, linux-gpio, linux-stm32, linux-arm-kernel,
linux-kernel
> if (priv->dma_cap.host_dma_width) {
> - ret = dma_set_mask_and_coherent(device,
> - DMA_BIT_MASK(priv->dma_cap.host_dma_width));
> + u64 mask = DMA_BIT_MASK(priv->dma_cap.host_dma_width);
> +
> + ret = dma_set_mask_and_coherent(priv->dma_device, mask);
I'm nitpicking, but i don't think you need to introduce mask.
DMA_BIT_MASK... is already on a line of its own, and is within the 80
limit. Nothing changes here with s/device/priv->dma_device/
Andrew
^ permalink raw reply [flat|nested] 37+ messages in thread* Re: [PATCH net-next 04/12] net: stmmac: dma: create a separate dma_device pointer
2026-05-01 17:13 ` Andrew Lunn
@ 2026-05-01 18:06 ` Alex Elder
2026-05-01 20:55 ` Andrew Lunn
0 siblings, 1 reply; 37+ messages in thread
From: Alex Elder @ 2026-05-01 18:06 UTC (permalink / raw)
To: Andrew Lunn
Cc: andrew+netdev, davem, edumazet, kuba, pabeni, maxime.chevallier,
rmk+kernel, andersson, konradybcio, robh, krzk+dt, conor+dt,
linusw, brgl, arnd, gregkh, daniel, mohd.anwar, a0987203069,
alexandre.torgue, ast, boon.khai.ng, chenchuangyu, chenhuacai,
daniel, hawk, hkallweit1, inochiama, john.fastabend, julianbraha,
livelycarpet87, matthew.gerlach, mcoquelin.stm32, me,
prabhakar.mahadev-lad.rj, richardcochran, rohan.g.thomas, sdf,
siyanteng, weishangjuan, wens, netdev, bpf, linux-arm-msm,
devicetree, linux-gpio, linux-stm32, linux-arm-kernel,
linux-kernel
On 5/1/26 12:13 PM, Andrew Lunn wrote:
>> if (priv->dma_cap.host_dma_width) {
>> - ret = dma_set_mask_and_coherent(device,
>> - DMA_BIT_MASK(priv->dma_cap.host_dma_width));
>> + u64 mask = DMA_BIT_MASK(priv->dma_cap.host_dma_width);
>> +
>> + ret = dma_set_mask_and_coherent(priv->dma_device, mask);
>
> I'm nitpicking, but i don't think you need to introduce mask.
> DMA_BIT_MASK... is already on a line of its own, and is within the 80
> limit. Nothing changes here with s/device/priv->dma_device/
>
> Andrew
I did this. It was simply to silence a checkpatch.pl warning
about a long line.
I don't care either way, I'll gladly put it back the way it was.
-Alex
^ permalink raw reply [flat|nested] 37+ messages in thread* Re: [PATCH net-next 04/12] net: stmmac: dma: create a separate dma_device pointer
2026-05-01 18:06 ` Alex Elder
@ 2026-05-01 20:55 ` Andrew Lunn
0 siblings, 0 replies; 37+ messages in thread
From: Andrew Lunn @ 2026-05-01 20:55 UTC (permalink / raw)
To: Alex Elder
Cc: andrew+netdev, davem, edumazet, kuba, pabeni, maxime.chevallier,
rmk+kernel, andersson, konradybcio, robh, krzk+dt, conor+dt,
linusw, brgl, arnd, gregkh, daniel, mohd.anwar, a0987203069,
alexandre.torgue, ast, boon.khai.ng, chenchuangyu, chenhuacai,
daniel, hawk, hkallweit1, inochiama, john.fastabend, julianbraha,
livelycarpet87, matthew.gerlach, mcoquelin.stm32, me,
prabhakar.mahadev-lad.rj, richardcochran, rohan.g.thomas, sdf,
siyanteng, weishangjuan, wens, netdev, bpf, linux-arm-msm,
devicetree, linux-gpio, linux-stm32, linux-arm-kernel,
linux-kernel
On Fri, May 01, 2026 at 01:06:23PM -0500, Alex Elder wrote:
> On 5/1/26 12:13 PM, Andrew Lunn wrote:
> > > if (priv->dma_cap.host_dma_width) {
> > > - ret = dma_set_mask_and_coherent(device,
> > > - DMA_BIT_MASK(priv->dma_cap.host_dma_width));
> > > + u64 mask = DMA_BIT_MASK(priv->dma_cap.host_dma_width);
> > > +
> > > + ret = dma_set_mask_and_coherent(priv->dma_device, mask);
> >
> > I'm nitpicking, but i don't think you need to introduce mask.
> > DMA_BIT_MASK... is already on a line of its own, and is within the 80
> > limit. Nothing changes here with s/device/priv->dma_device/
> >
> > Andrew
>
> I did this. It was simply to silence a checkpatch.pl warning
> about a long line.
>
> I don't care either way, I'll gladly put it back the way it was.
Please pull checkpatch fixes out into a patch of their own.
Andrew
^ permalink raw reply [flat|nested] 37+ messages in thread
* [PATCH net-next 05/12] net: stmmac: dwxgmac2: Add multi MSI interrupt mode
2026-05-01 15:54 [PATCH net-next 00/12] net: enable TC956x support Alex Elder
` (3 preceding siblings ...)
2026-05-01 15:54 ` [PATCH net-next 04/12] net: stmmac: dma: create a separate dma_device pointer Alex Elder
@ 2026-05-01 15:54 ` Alex Elder
2026-05-01 17:21 ` Andrew Lunn
2026-05-01 15:54 ` [PATCH net-next 06/12] net: stmmac: dwxgmac2: Add XGMAC 3.01a support Alex Elder
` (7 subsequent siblings)
12 siblings, 1 reply; 37+ messages in thread
From: Alex Elder @ 2026-05-01 15:54 UTC (permalink / raw)
To: andrew+netdev, davem, edumazet, kuba, pabeni, maxime.chevallier,
rmk+kernel, andersson, konradybcio, robh, krzk+dt, conor+dt,
linusw, brgl, arnd, gregkh
Cc: Daniel Thompson, elder, mohd.anwar, a0987203069, alexandre.torgue,
ast, boon.khai.ng, chenchuangyu, chenhuacai, daniel, hawk,
hkallweit1, inochiama, john.fastabend, julianbraha,
livelycarpet87, matthew.gerlach, mcoquelin.stm32, me,
prabhakar.mahadev-lad.rj, richardcochran, rohan.g.thomas, sdf,
siyanteng, weishangjuan, wens, netdev, bpf, linux-arm-msm,
devicetree, linux-gpio, linux-stm32, linux-arm-kernel,
linux-kernel
From: Daniel Thompson <daniel@riscstar.com>
Currently there are no XGMAC platforms integrated using the multi MSI
interrupt mode. In other words no existing driver sets both
DWMAC_CORE_XGMAC and STMMAC_FLAG_MULTI_MSI_EN.
In order to support systems that do enable both options (such as the
Toshiba TC9564 whose driver is currently being developed) we need to
add logic to the XGMAC DMA callbacks. Happily we can simply
replicate similar code from GMAC4. Let's do that!
Signed-off-by: Daniel Thompson <daniel@riscstar.com>
Signed-off-by: Alex Elder <elder@riscstar.com>
---
drivers/net/ethernet/stmicro/stmmac/dwxgmac2.h | 2 ++
drivers/net/ethernet/stmicro/stmmac/dwxgmac2_dma.c | 8 ++++++++
2 files changed, 10 insertions(+)
diff --git a/drivers/net/ethernet/stmicro/stmmac/dwxgmac2.h b/drivers/net/ethernet/stmicro/stmmac/dwxgmac2.h
index 51943705a2b03..9b0b5cc619556 100644
--- a/drivers/net/ethernet/stmicro/stmmac/dwxgmac2.h
+++ b/drivers/net/ethernet/stmicro/stmmac/dwxgmac2.h
@@ -320,6 +320,8 @@
/* DMA Registers */
#define XGMAC_DMA_MODE 0x00003000
#define XGMAC_SWR BIT(0)
+#define XGMAC_INTM_MASK GENMASK(13, 12)
+#define XGMAC_INTM_MODE1 0x1
#define XGMAC_DMA_SYSBUS_MODE 0x00003004
#define XGMAC_WR_OSR_LMT GENMASK(29, 24)
#define XGMAC_RD_OSR_LMT GENMASK(21, 16)
diff --git a/drivers/net/ethernet/stmicro/stmmac/dwxgmac2_dma.c b/drivers/net/ethernet/stmicro/stmmac/dwxgmac2_dma.c
index 03437f1cf3df3..a84601ac32153 100644
--- a/drivers/net/ethernet/stmicro/stmmac/dwxgmac2_dma.c
+++ b/drivers/net/ethernet/stmicro/stmmac/dwxgmac2_dma.c
@@ -31,6 +31,14 @@ static void dwxgmac2_dma_init(void __iomem *ioaddr,
value |= XGMAC_EAME;
writel(value, ioaddr + XGMAC_DMA_SYSBUS_MODE);
+
+ value = readl(ioaddr + XGMAC_DMA_MODE);
+
+ if (dma_cfg->multi_msi_en)
+ value = u32_replace_bits(value, XGMAC_INTM_MODE1,
+ XGMAC_INTM_MASK);
+
+ writel(value, ioaddr + XGMAC_DMA_MODE);
}
static void dwxgmac2_dma_init_chan(struct stmmac_priv *priv,
--
2.51.0
^ permalink raw reply related [flat|nested] 37+ messages in thread* Re: [PATCH net-next 05/12] net: stmmac: dwxgmac2: Add multi MSI interrupt mode
2026-05-01 15:54 ` [PATCH net-next 05/12] net: stmmac: dwxgmac2: Add multi MSI interrupt mode Alex Elder
@ 2026-05-01 17:21 ` Andrew Lunn
0 siblings, 0 replies; 37+ messages in thread
From: Andrew Lunn @ 2026-05-01 17:21 UTC (permalink / raw)
To: Alex Elder
Cc: andrew+netdev, davem, edumazet, kuba, pabeni, maxime.chevallier,
rmk+kernel, andersson, konradybcio, robh, krzk+dt, conor+dt,
linusw, brgl, arnd, gregkh, Daniel Thompson, mohd.anwar,
a0987203069, alexandre.torgue, ast, boon.khai.ng, chenchuangyu,
chenhuacai, daniel, hawk, hkallweit1, inochiama, john.fastabend,
julianbraha, livelycarpet87, matthew.gerlach, mcoquelin.stm32, me,
prabhakar.mahadev-lad.rj, richardcochran, rohan.g.thomas, sdf,
siyanteng, weishangjuan, wens, netdev, bpf, linux-arm-msm,
devicetree, linux-gpio, linux-stm32, linux-arm-kernel,
linux-kernel
On Fri, May 01, 2026 at 10:54:13AM -0500, Alex Elder wrote:
> From: Daniel Thompson <daniel@riscstar.com>
>
> Currently there are no XGMAC platforms integrated using the multi MSI
> interrupt mode. In other words no existing driver sets both
> DWMAC_CORE_XGMAC and STMMAC_FLAG_MULTI_MSI_EN.
>
> In order to support systems that do enable both options (such as the
> Toshiba TC9564 whose driver is currently being developed) we need to
> add logic to the XGMAC DMA callbacks. Happily we can simply
> replicate similar code from GMAC4. Let's do that!
The word replicate made me think it has been cut/paste, rather than
being refactored into a helper. However,
> +#define XGMAC_INTM_MASK GENMASK(13, 12)
#define DMA_BUS_MODE_INTM_MASK GENMASK(17, 16)
Different bits in the register, so the code structure is the same, but
the code cannot be shared in a meaningful way. So this is O.K.
Andrew
^ permalink raw reply [flat|nested] 37+ messages in thread
* [PATCH net-next 06/12] net: stmmac: dwxgmac2: Add XGMAC 3.01a support
2026-05-01 15:54 [PATCH net-next 00/12] net: enable TC956x support Alex Elder
` (4 preceding siblings ...)
2026-05-01 15:54 ` [PATCH net-next 05/12] net: stmmac: dwxgmac2: Add multi MSI interrupt mode Alex Elder
@ 2026-05-01 15:54 ` Alex Elder
2026-05-01 15:54 ` [PATCH net-next 07/12] net: stmmac: dwxgmac2: export symbols for XGMAC 3.01a DMA Alex Elder
` (6 subsequent siblings)
12 siblings, 0 replies; 37+ messages in thread
From: Alex Elder @ 2026-05-01 15:54 UTC (permalink / raw)
To: andrew+netdev, davem, edumazet, kuba, pabeni, maxime.chevallier,
rmk+kernel, andersson, konradybcio, robh, krzk+dt, conor+dt,
linusw, brgl, arnd, gregkh
Cc: Daniel Thompson, elder, mohd.anwar, a0987203069, alexandre.torgue,
ast, boon.khai.ng, chenchuangyu, chenhuacai, daniel, hawk,
hkallweit1, inochiama, john.fastabend, julianbraha,
livelycarpet87, matthew.gerlach, mcoquelin.stm32, me,
prabhakar.mahadev-lad.rj, richardcochran, rohan.g.thomas, sdf,
siyanteng, weishangjuan, wens, netdev, bpf, linux-arm-msm,
devicetree, linux-gpio, linux-stm32, linux-arm-kernel,
linux-kernel
From: Daniel Thompson <daniel@riscstar.com>
XGMAC 2.x and 3.x are architecturally very similar. That means that
for everything except one erratum we can simply use the XGMAC 2.x
callback functions in the stmmac_dma_ops structure.
Only the set_rx_ring_len callback is specific to XGMAC 3.01. It
limits the number of outstanding write requests that can be serviced
per DMA.
The other erratum addressed in this patch is simply a comment to
ensure that a feature that stmmac doesn't currently use is not enabled
without contemplating the errata.
Signed-off-by: Daniel Thompson <daniel@riscstar.com>
Signed-off-by: Alex Elder <elder@riscstar.com>
---
.../net/ethernet/stmicro/stmmac/dwxgmac2.h | 3 ++
.../ethernet/stmicro/stmmac/dwxgmac2_dma.c | 52 +++++++++++++++++++
2 files changed, 55 insertions(+)
diff --git a/drivers/net/ethernet/stmicro/stmmac/dwxgmac2.h b/drivers/net/ethernet/stmicro/stmmac/dwxgmac2.h
index 9b0b5cc619556..bcf59ad8a1939 100644
--- a/drivers/net/ethernet/stmicro/stmmac/dwxgmac2.h
+++ b/drivers/net/ethernet/stmicro/stmmac/dwxgmac2.h
@@ -374,6 +374,8 @@
#define XGMAC_DMA_CH_RxDESC_TAIL_LPTR(x) (0x0000312c + (0x80 * (x)))
#define XGMAC_DMA_CH_TxDESC_RING_LEN(x) (0x00003130 + (0x80 * (x)))
#define XGMAC_DMA_CH_RxDESC_RING_LEN(x) (0x00003134 + (0x80 * (x)))
+#define XGMAC_OWRQ GENMASK(25, 24)
+#define XGMAC_RDRL GENMASK(15, 0)
#define XGMAC_DMA_CH_INT_EN(x) (0x00003138 + (0x80 * (x)))
#define XGMAC_NIE BIT(15)
#define XGMAC_AIE BIT(14)
@@ -463,6 +465,7 @@
extern const struct stmmac_ops dwxgmac210_ops;
extern const struct stmmac_ops dwxlgmac2_ops;
extern const struct stmmac_dma_ops dwxgmac210_dma_ops;
+extern const struct stmmac_dma_ops dwxgmac301_dma_ops;
extern const struct stmmac_desc_ops dwxgmac210_desc_ops;
#endif /* __STMMAC_DWXGMAC2_H__ */
diff --git a/drivers/net/ethernet/stmicro/stmmac/dwxgmac2_dma.c b/drivers/net/ethernet/stmicro/stmmac/dwxgmac2_dma.c
index a84601ac32153..dc2897e9931d1 100644
--- a/drivers/net/ethernet/stmicro/stmmac/dwxgmac2_dma.c
+++ b/drivers/net/ethernet/stmicro/stmmac/dwxgmac2_dma.c
@@ -38,6 +38,14 @@ static void dwxgmac2_dma_init(void __iomem *ioaddr,
value = u32_replace_bits(value, XGMAC_INTM_MODE1,
XGMAC_INTM_MASK);
+ /*
+ * A friendly warning to future adventurers. If Descriptor Posted
+ * Write support, which is off by default, is ever enabled then be sure
+ * to make it optional. This is required by errata for at least XGMAC
+ * 3.01A... and the XGMAC 2.x and 3.x are architecturally similar so we
+ * use dwxgmac2 support for the 3.x family as well.
+ */
+
writel(value, ioaddr + XGMAC_DMA_MODE);
}
@@ -490,6 +498,20 @@ static void dwxgmac2_set_rx_ring_len(struct stmmac_priv *priv,
writel(len, ioaddr + XGMAC_DMA_CH_RxDESC_RING_LEN(chan));
}
+static void dwxgmac301_set_rx_ring_len(struct stmmac_priv *priv,
+ void __iomem *ioaddr, u32 len, u32 chan)
+{
+ u32 val = FIELD_PREP(XGMAC_RDRL, len);
+
+ /*
+ * Reduce the number of outstanding write requests to 3 (from default
+ * of 4). This is an errata workaround for XGMAC 3.01a.
+ */
+ val |= FIELD_PREP(XGMAC_OWRQ, 3);
+
+ writel(val, ioaddr + XGMAC_DMA_CH_RxDESC_RING_LEN(chan));
+}
+
static void dwxgmac2_set_tx_ring_len(struct stmmac_priv *priv,
void __iomem *ioaddr, u32 len, u32 chan)
{
@@ -619,3 +641,33 @@ const struct stmmac_dma_ops dwxgmac210_dma_ops = {
.enable_sph = dwxgmac2_enable_sph,
.enable_tbs = dwxgmac2_enable_tbs,
};
+
+const struct stmmac_dma_ops dwxgmac301_dma_ops = {
+ .reset = dwxgmac2_dma_reset,
+ .init = dwxgmac2_dma_init,
+ .init_chan = dwxgmac2_dma_init_chan,
+ .init_rx_chan = dwxgmac2_dma_init_rx_chan,
+ .init_tx_chan = dwxgmac2_dma_init_tx_chan,
+ .axi = dwxgmac2_dma_axi,
+ .dump_regs = dwxgmac2_dma_dump_regs,
+ .dma_rx_mode = dwxgmac2_dma_rx_mode,
+ .dma_tx_mode = dwxgmac2_dma_tx_mode,
+ .enable_dma_irq = dwxgmac2_enable_dma_irq,
+ .disable_dma_irq = dwxgmac2_disable_dma_irq,
+ .start_tx = dwxgmac2_dma_start_tx,
+ .stop_tx = dwxgmac2_dma_stop_tx,
+ .start_rx = dwxgmac2_dma_start_rx,
+ .stop_rx = dwxgmac2_dma_stop_rx,
+ .dma_interrupt = dwxgmac2_dma_interrupt,
+ .get_hw_feature = dwxgmac2_get_hw_feature,
+ .rx_watchdog = dwxgmac2_rx_watchdog,
+ .set_rx_ring_len = dwxgmac301_set_rx_ring_len,
+ .set_tx_ring_len = dwxgmac2_set_tx_ring_len,
+ .set_rx_tail_ptr = dwxgmac2_set_rx_tail_ptr,
+ .set_tx_tail_ptr = dwxgmac2_set_tx_tail_ptr,
+ .enable_tso = dwxgmac2_enable_tso,
+ .qmode = dwxgmac2_qmode,
+ .set_bfsize = dwxgmac2_set_bfsize,
+ .enable_sph = dwxgmac2_enable_sph,
+ .enable_tbs = dwxgmac2_enable_tbs,
+};
--
2.51.0
^ permalink raw reply related [flat|nested] 37+ messages in thread* [PATCH net-next 07/12] net: stmmac: dwxgmac2: export symbols for XGMAC 3.01a DMA
2026-05-01 15:54 [PATCH net-next 00/12] net: enable TC956x support Alex Elder
` (5 preceding siblings ...)
2026-05-01 15:54 ` [PATCH net-next 06/12] net: stmmac: dwxgmac2: Add XGMAC 3.01a support Alex Elder
@ 2026-05-01 15:54 ` Alex Elder
2026-05-01 15:54 ` [PATCH net-next 08/12] dt-bindings: net: toshiba,tc965x-dwmac: add TC956x Ethernet bridge Alex Elder
` (5 subsequent siblings)
12 siblings, 0 replies; 37+ messages in thread
From: Alex Elder @ 2026-05-01 15:54 UTC (permalink / raw)
To: andrew+netdev, davem, edumazet, kuba, pabeni, maxime.chevallier,
rmk+kernel, andersson, konradybcio, robh, krzk+dt, conor+dt,
linusw, brgl, arnd, gregkh
Cc: Daniel Thompson, elder, mohd.anwar, a0987203069, alexandre.torgue,
ast, boon.khai.ng, chenchuangyu, chenhuacai, daniel, hawk,
hkallweit1, inochiama, john.fastabend, julianbraha,
livelycarpet87, matthew.gerlach, mcoquelin.stm32, me,
prabhakar.mahadev-lad.rj, richardcochran, rohan.g.thomas, sdf,
siyanteng, weishangjuan, wens, netdev, bpf, linux-arm-msm,
devicetree, linux-gpio, linux-stm32, linux-arm-kernel,
linux-kernel
From: Daniel Thompson <daniel@riscstar.com>
Toshiba TC956x is an Ethernet-AVB/TSN bridge and is essentially a
small-and-highly-specialised SoC. Ethernet on this chip is provided
by a DesignWare XGMAC.
One consequence of the SoC-like design is that the internal AXI bus
(used by the XGMAC for DMA) maps the PCI DMA space with a non-zero base
address. This requires a translation step (happily just simple addition)
to convert the PCI DMA address to the hardware DMA address.
This is pretty funky so rather than push that translation logic into
the core driver we intend to keep that logic inside the TC956x
platform code. In order to do that we need to export a few symbols
to allow us to override some of the DMA and descriptor op tables.
FWIW this approach to overriding the ops tables is similar to the
mechanism currently found in dwmac-loongson.c (with the exception
that we have also exported a couple of functions so we don't
have to replicate their content in the TC956x platform code).
Signed-off-by: Daniel Thompson <daniel@riscstar.com>
Signed-off-by: Alex Elder <elder@riscstar.com>
---
drivers/net/ethernet/stmicro/stmmac/dwxgmac2.h | 7 +++++++
.../ethernet/stmicro/stmmac/dwxgmac2_core.c | 1 +
.../ethernet/stmicro/stmmac/dwxgmac2_descs.c | 1 +
.../net/ethernet/stmicro/stmmac/dwxgmac2_dma.c | 18 ++++++++++--------
4 files changed, 19 insertions(+), 8 deletions(-)
diff --git a/drivers/net/ethernet/stmicro/stmmac/dwxgmac2.h b/drivers/net/ethernet/stmicro/stmmac/dwxgmac2.h
index bcf59ad8a1939..8cecde1bef8a1 100644
--- a/drivers/net/ethernet/stmicro/stmmac/dwxgmac2.h
+++ b/drivers/net/ethernet/stmicro/stmmac/dwxgmac2.h
@@ -468,4 +468,11 @@ extern const struct stmmac_dma_ops dwxgmac210_dma_ops;
extern const struct stmmac_dma_ops dwxgmac301_dma_ops;
extern const struct stmmac_desc_ops dwxgmac210_desc_ops;
+void dwxgmac2_dma_init_rx_chan(struct stmmac_priv *priv, void __iomem *ioaddr,
+ struct stmmac_dma_cfg *dma_cfg, dma_addr_t phy,
+ u32 chan);
+void dwxgmac2_dma_init_tx_chan(struct stmmac_priv *priv, void __iomem *ioaddr,
+ struct stmmac_dma_cfg *dma_cfg, dma_addr_t phy,
+ u32 chan);
+
#endif /* __STMMAC_DWXGMAC2_H__ */
diff --git a/drivers/net/ethernet/stmicro/stmmac/dwxgmac2_core.c b/drivers/net/ethernet/stmicro/stmmac/dwxgmac2_core.c
index f02b434bbd505..c9547dc6912a3 100644
--- a/drivers/net/ethernet/stmicro/stmmac/dwxgmac2_core.c
+++ b/drivers/net/ethernet/stmicro/stmmac/dwxgmac2_core.c
@@ -1556,6 +1556,7 @@ int dwxgmac2_setup(struct stmmac_priv *priv)
return 0;
}
+EXPORT_SYMBOL_GPL(dwxgmac2_setup);
int dwxlgmac2_setup(struct stmmac_priv *priv)
{
diff --git a/drivers/net/ethernet/stmicro/stmmac/dwxgmac2_descs.c b/drivers/net/ethernet/stmicro/stmmac/dwxgmac2_descs.c
index b5f200a874840..cc67d8e1a920a 100644
--- a/drivers/net/ethernet/stmicro/stmmac/dwxgmac2_descs.c
+++ b/drivers/net/ethernet/stmicro/stmmac/dwxgmac2_descs.c
@@ -368,3 +368,4 @@ const struct stmmac_desc_ops dwxgmac210_desc_ops = {
.set_vlan = dwxgmac2_set_vlan,
.set_tbs = dwxgmac2_set_tbs,
};
+EXPORT_SYMBOL_GPL(dwxgmac210_desc_ops);
diff --git a/drivers/net/ethernet/stmicro/stmmac/dwxgmac2_dma.c b/drivers/net/ethernet/stmicro/stmmac/dwxgmac2_dma.c
index dc2897e9931d1..ec365e66276f1 100644
--- a/drivers/net/ethernet/stmicro/stmmac/dwxgmac2_dma.c
+++ b/drivers/net/ethernet/stmicro/stmmac/dwxgmac2_dma.c
@@ -62,10 +62,10 @@ static void dwxgmac2_dma_init_chan(struct stmmac_priv *priv,
writel(XGMAC_DMA_INT_DEFAULT_EN, ioaddr + XGMAC_DMA_CH_INT_EN(chan));
}
-static void dwxgmac2_dma_init_rx_chan(struct stmmac_priv *priv,
- void __iomem *ioaddr,
- struct stmmac_dma_cfg *dma_cfg,
- dma_addr_t phy, u32 chan)
+void dwxgmac2_dma_init_rx_chan(struct stmmac_priv *priv,
+ void __iomem *ioaddr,
+ struct stmmac_dma_cfg *dma_cfg,
+ dma_addr_t phy, u32 chan)
{
u32 rxpbl = dma_cfg->rxpbl ?: dma_cfg->pbl;
u32 value;
@@ -77,11 +77,11 @@ static void dwxgmac2_dma_init_rx_chan(struct stmmac_priv *priv,
writel(upper_32_bits(phy), ioaddr + XGMAC_DMA_CH_RxDESC_HADDR(chan));
writel(lower_32_bits(phy), ioaddr + XGMAC_DMA_CH_RxDESC_LADDR(chan));
}
+EXPORT_SYMBOL_GPL(dwxgmac2_dma_init_rx_chan);
-static void dwxgmac2_dma_init_tx_chan(struct stmmac_priv *priv,
- void __iomem *ioaddr,
- struct stmmac_dma_cfg *dma_cfg,
- dma_addr_t phy, u32 chan)
+void dwxgmac2_dma_init_tx_chan(struct stmmac_priv *priv, void __iomem *ioaddr,
+ struct stmmac_dma_cfg *dma_cfg, dma_addr_t phy,
+ u32 chan)
{
u32 txpbl = dma_cfg->txpbl ?: dma_cfg->pbl;
u32 value;
@@ -93,6 +93,7 @@ static void dwxgmac2_dma_init_tx_chan(struct stmmac_priv *priv,
writel(upper_32_bits(phy), ioaddr + XGMAC_DMA_CH_TxDESC_HADDR(chan));
writel(lower_32_bits(phy), ioaddr + XGMAC_DMA_CH_TxDESC_LADDR(chan));
}
+EXPORT_SYMBOL_GPL(dwxgmac2_dma_init_tx_chan);
static void dwxgmac2_dma_axi(void __iomem *ioaddr, struct stmmac_axi *axi)
{
@@ -671,3 +672,4 @@ const struct stmmac_dma_ops dwxgmac301_dma_ops = {
.enable_sph = dwxgmac2_enable_sph,
.enable_tbs = dwxgmac2_enable_tbs,
};
+EXPORT_SYMBOL_GPL(dwxgmac301_dma_ops);
--
2.51.0
^ permalink raw reply related [flat|nested] 37+ messages in thread* [PATCH net-next 08/12] dt-bindings: net: toshiba,tc965x-dwmac: add TC956x Ethernet bridge
2026-05-01 15:54 [PATCH net-next 00/12] net: enable TC956x support Alex Elder
` (6 preceding siblings ...)
2026-05-01 15:54 ` [PATCH net-next 07/12] net: stmmac: dwxgmac2: export symbols for XGMAC 3.01a DMA Alex Elder
@ 2026-05-01 15:54 ` Alex Elder
2026-05-01 17:38 ` Andrew Lunn
2026-05-01 15:54 ` [PATCH net-next 09/12] gpio: tc956x: add TC956x/QPS615 support Alex Elder
` (4 subsequent siblings)
12 siblings, 1 reply; 37+ messages in thread
From: Alex Elder @ 2026-05-01 15:54 UTC (permalink / raw)
To: andrew+netdev, davem, edumazet, kuba, pabeni, maxime.chevallier,
rmk+kernel, andersson, konradybcio, robh, krzk+dt, conor+dt,
linusw, brgl, arnd, gregkh
Cc: Daniel Thompson, elder, mohd.anwar, a0987203069, alexandre.torgue,
ast, boon.khai.ng, chenchuangyu, chenhuacai, daniel, hawk,
hkallweit1, inochiama, john.fastabend, julianbraha,
livelycarpet87, matthew.gerlach, mcoquelin.stm32, me,
prabhakar.mahadev-lad.rj, richardcochran, rohan.g.thomas, sdf,
siyanteng, weishangjuan, wens, netdev, bpf, linux-arm-msm,
devicetree, linux-gpio, linux-stm32, linux-arm-kernel,
linux-kernel
From: Daniel Thompson <daniel@riscstar.com>
Add devicetree bindings for the Toshiba TC956x family of Ethernet-AVB/TSN
bridges.
Signed-off-by: Daniel Thompson <daniel@riscstar.com>
Signed-off-by: Alex Elder <elder@riscstar.com>
---
.../bindings/net/toshiba,tc956x-dwmac.yaml | 111 ++++++++++++++++++
1 file changed, 111 insertions(+)
create mode 100644 Documentation/devicetree/bindings/net/toshiba,tc956x-dwmac.yaml
diff --git a/Documentation/devicetree/bindings/net/toshiba,tc956x-dwmac.yaml b/Documentation/devicetree/bindings/net/toshiba,tc956x-dwmac.yaml
new file mode 100644
index 0000000000000..d95d22a3761da
--- /dev/null
+++ b/Documentation/devicetree/bindings/net/toshiba,tc956x-dwmac.yaml
@@ -0,0 +1,111 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/net/toshiba,tc956x-dwmac.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Toshiba TC956x Ethernet-AVB/TSN Controller
+
+maintainers:
+ - Alex Elder <elder@riscstar.com>
+ - Daniel Thompson <daniel@riscstar.com>
+
+description: |
+ This node provides properties for configuring the Ethernet PCI functions
+ that are attached to the internal downstream port of the TC956x's PCIe
+ switch.
+
+ TC956x are a family of Ethernet-AVB/TSN bridge chips that combine a PCIe
+ switch together with a number of Ethernet controllers. These bindings
+ cover only the Ethernet functions of these devices.
+
+allOf:
+ - $ref: /schemas/pci/pci-bus-common.yaml#
+ - $ref: /schemas/pci/pci-device.yaml#
+
+unevaluatedProperties: false
+
+properties:
+ compatible:
+ enum:
+ - pci1179,0220 # Toshiba TC9564 (a.k.a. Qualcomm QPS615)
+
+ "#gpio-cells":
+ const: 2
+
+ gpio-controller: true
+
+ # We can't allOf reference Ethernet-controller.yaml because we end up with
+ # contradictory $nodename rules (`ethernet@` versus `pci@`). Happily only a
+ # small number of the properties are useful on TC956x so we can just reference
+ # what we need.
+ phy-connection-type:
+ $ref: ethernet-controller.yaml#/properties/phy-connection-type
+
+ phy-handle:
+ $ref: ethernet-controller.yaml#/properties/phy-handle
+
+ phy-mode:
+ $ref: ethernet-controller.yaml#/properties/phy-mode
+
+ mdio:
+ $ref: snps,dwmac.yaml#/properties/mdio
+
+required:
+ - compatible
+ - reg
+
+examples:
+ - |
+ pcie {
+ #address-cells = <3>;
+ #size-cells = <2>;
+
+ tc956x_emac0: pci@0,0 {
+ compatible = "pci1179,0220";
+ reg = <0x50000 0x0 0x0 0x0 0x0>;
+ #address-cells = <3>;
+ #size-cells = <2>;
+ device_type = "pci";
+ ranges;
+
+ gpio-controller;
+ #gpio-cells = <2>;
+
+ phy-mode = "10gbase-r";
+ phy-handle = <&tc956x_emac0_phy>;
+
+ mdio {
+ compatible = "snps,dwmac-mdio";
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ tc956x_emac0_phy: ethernet-phy@1c {
+ compatible = "ethernet-phy-id311c.1c12";
+ reg = <0x1c>;
+ };
+ };
+ };
+ pci@0,1 {
+ compatible = "pci1179,0220";
+ reg = <0x50100 0x0 0x0 0x0 0x0>;
+ #address-cells = <3>;
+ #size-cells = <2>;
+ device_type = "pci";
+ ranges;
+
+ phy-mode = "sgmii";
+ phy-handle = <&tc956x_emac1_phy>;
+
+ mdio {
+ compatible = "snps,dwmac-mdio";
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ tc956x_emac1_phy: ethernet-phy@1c {
+ compatible = "ethernet-phy-id004d.d101";
+ reg = <0x1c>;
+ };
+ };
+ };
+ };
--
2.51.0
^ permalink raw reply related [flat|nested] 37+ messages in thread* Re: [PATCH net-next 08/12] dt-bindings: net: toshiba,tc965x-dwmac: add TC956x Ethernet bridge
2026-05-01 15:54 ` [PATCH net-next 08/12] dt-bindings: net: toshiba,tc965x-dwmac: add TC956x Ethernet bridge Alex Elder
@ 2026-05-01 17:38 ` Andrew Lunn
2026-05-03 2:22 ` Alex Elder
0 siblings, 1 reply; 37+ messages in thread
From: Andrew Lunn @ 2026-05-01 17:38 UTC (permalink / raw)
To: Alex Elder
Cc: andrew+netdev, davem, edumazet, kuba, pabeni, maxime.chevallier,
rmk+kernel, andersson, konradybcio, robh, krzk+dt, conor+dt,
linusw, brgl, arnd, gregkh, Daniel Thompson, mohd.anwar,
a0987203069, alexandre.torgue, ast, boon.khai.ng, chenchuangyu,
chenhuacai, daniel, hawk, hkallweit1, inochiama, john.fastabend,
julianbraha, livelycarpet87, matthew.gerlach, mcoquelin.stm32, me,
prabhakar.mahadev-lad.rj, richardcochran, rohan.g.thomas, sdf,
siyanteng, weishangjuan, wens, netdev, bpf, linux-arm-msm,
devicetree, linux-gpio, linux-stm32, linux-arm-kernel,
linux-kernel
Your ASCII art of the chip might be useful here as documentation.
> + # We can't allOf reference Ethernet-controller.yaml because we end up with
> + # contradictory $nodename rules (`ethernet@` versus `pci@`). Happily only a
> + # small number of the properties are useful on TC956x so we can just reference
> + # what we need.
Why not add an subnodes for the ethernet interfaces?
> +examples:
> + - |
> + pcie {
> + #address-cells = <3>;
> + #size-cells = <2>;
> +
> + tc956x_emac0: pci@0,0 {
> + compatible = "pci1179,0220";
> + reg = <0x50000 0x0 0x0 0x0 0x0>;
> + #address-cells = <3>;
> + #size-cells = <2>;
> + device_type = "pci";
> + ranges;
> +
> + gpio-controller;
> + #gpio-cells = <2>;
I've not got to the GPIO driver patch yet...
Is the GPIO part of the ethernet device, or part of the chip? The
hierarchy here should match the hierarchy of the hardware.
> + phy-mode = "10gbase-r";
> + phy-handle = <&tc956x_emac0_phy>;
> +
> + mdio {
> + compatible = "snps,dwmac-mdio";
> + #address-cells = <1>;
> + #size-cells = <0>;
> +
> + tc956x_emac0_phy: ethernet-phy@1c {
> + compatible = "ethernet-phy-id311c.1c12";
> + reg = <0x1c>;
> + };
> + };
> + };
> + pci@0,1 {
> + compatible = "pci1179,0220";
> + reg = <0x50100 0x0 0x0 0x0 0x0>;
> + #address-cells = <3>;
> + #size-cells = <2>;
> + device_type = "pci";
> + ranges;
> +
You second ethernet does not have a gpio controller?
Andrew
^ permalink raw reply [flat|nested] 37+ messages in thread* Re: [PATCH net-next 08/12] dt-bindings: net: toshiba,tc965x-dwmac: add TC956x Ethernet bridge
2026-05-01 17:38 ` Andrew Lunn
@ 2026-05-03 2:22 ` Alex Elder
0 siblings, 0 replies; 37+ messages in thread
From: Alex Elder @ 2026-05-03 2:22 UTC (permalink / raw)
To: Andrew Lunn
Cc: andrew+netdev, davem, edumazet, kuba, pabeni, maxime.chevallier,
rmk+kernel, andersson, konradybcio, robh, krzk+dt, conor+dt,
linusw, brgl, arnd, gregkh, Daniel Thompson, mohd.anwar,
a0987203069, alexandre.torgue, ast, boon.khai.ng, chenchuangyu,
chenhuacai, daniel, hawk, hkallweit1, inochiama, john.fastabend,
julianbraha, livelycarpet87, matthew.gerlach, mcoquelin.stm32, me,
prabhakar.mahadev-lad.rj, richardcochran, rohan.g.thomas, sdf,
siyanteng, weishangjuan, wens, netdev, bpf, linux-arm-msm,
devicetree, linux-gpio, linux-stm32, linux-arm-kernel,
linux-kernel
On 5/1/26 12:38 PM, Andrew Lunn wrote:
> Your ASCII art of the chip might be useful here as documentation.
>
>> + # We can't allOf reference Ethernet-controller.yaml because we end up with
>> + # contradictory $nodename rules (`ethernet@` versus `pci@`). Happily only a
>> + # small number of the properties are useful on TC956x so we can just reference
>> + # what we need.
>
> Why not add an subnodes for the ethernet interfaces?
I'm going to wait to respond to this until I've had a chance
to discuss it with Daniel. (It might be Tuesday.)
>
>> +examples:
>> + - |
>> + pcie {
>> + #address-cells = <3>;
>> + #size-cells = <2>;
>> +
>> + tc956x_emac0: pci@0,0 {
>> + compatible = "pci1179,0220";
>> + reg = <0x50000 0x0 0x0 0x0 0x0>;
>> + #address-cells = <3>;
>> + #size-cells = <2>;
>> + device_type = "pci";
>> + ranges;
>> +
>> + gpio-controller;
>> + #gpio-cells = <2>;
>
> I've not got to the GPIO driver patch yet...
>
> Is the GPIO part of the ethernet device, or part of the chip? The
> hierarchy here should match the hierarchy of the hardware.
The GPIO is part of the TC9564 chip, and is a separate IP
within it. Within this chip there is one GPIO controller
(as well as a UART and so on), independent of the eMACs.
>> + phy-mode = "10gbase-r";
>> + phy-handle = <&tc956x_emac0_phy>;
>> +
>> + mdio {
>> + compatible = "snps,dwmac-mdio";
>> + #address-cells = <1>;
>> + #size-cells = <0>;
>> +
>> + tc956x_emac0_phy: ethernet-phy@1c {
>> + compatible = "ethernet-phy-id311c.1c12";
>> + reg = <0x1c>;
>> + };
>> + };
>> + };
>> + pci@0,1 {
>> + compatible = "pci1179,0220";
>> + reg = <0x50100 0x0 0x0 0x0 0x0>;
>> + #address-cells = <3>;
>> + #size-cells = <2>;
>> + device_type = "pci";
>> + ranges;
>> +
>
> You second ethernet does not have a gpio controller?
Basically, no. We made the decision to create a "chip"
abstraction that is responsible for managing these other
shared IP blocks (of which only the GPIO controller has
a separate driver).
Both of the PCIe endpoints are able to manipulate the
registers for the GPIO, but we made it the responsibility
of function 0--not function 1--to handle that.
It's possible that some platforms won't use the built-in
GPIO controller to manage PHY resets. So we used this
property to signal that it was required.
So *if* the gpio-controller (and #gpio-cells) property is
present, then function 0 creates an auxiliary device for
the GPIO controller. Otherwise something else supplies the
GPIO lines managing PHY resets.
-Alex
>
> Andrew
^ permalink raw reply [flat|nested] 37+ messages in thread
* [PATCH net-next 09/12] gpio: tc956x: add TC956x/QPS615 support
2026-05-01 15:54 [PATCH net-next 00/12] net: enable TC956x support Alex Elder
` (7 preceding siblings ...)
2026-05-01 15:54 ` [PATCH net-next 08/12] dt-bindings: net: toshiba,tc965x-dwmac: add TC956x Ethernet bridge Alex Elder
@ 2026-05-01 15:54 ` Alex Elder
2026-05-01 18:36 ` Andrew Lunn
2026-05-03 3:42 ` Julian Braha
2026-05-01 15:54 ` [PATCH net-next 10/12] net: stmmac: " Alex Elder
` (3 subsequent siblings)
12 siblings, 2 replies; 37+ messages in thread
From: Alex Elder @ 2026-05-01 15:54 UTC (permalink / raw)
To: andrew+netdev, davem, edumazet, kuba, pabeni, maxime.chevallier,
rmk+kernel, andersson, konradybcio, robh, krzk+dt, conor+dt,
linusw, brgl, arnd, gregkh
Cc: daniel, elder, mohd.anwar, a0987203069, alexandre.torgue, ast,
boon.khai.ng, chenchuangyu, chenhuacai, daniel, hawk, hkallweit1,
inochiama, john.fastabend, julianbraha, livelycarpet87,
matthew.gerlach, mcoquelin.stm32, me, prabhakar.mahadev-lad.rj,
richardcochran, rohan.g.thomas, sdf, siyanteng, weishangjuan,
wens, netdev, bpf, linux-arm-msm, devicetree, linux-gpio,
linux-stm32, linux-arm-kernel, linux-kernel
Toshiba TC956x is an Ethernet-AVB/TSN bridge and is essentially
a small and highly-specialized SoC. TC956x includes a GPIO block that
can be accessed, alongside several other peripherals, via two PCIe
endpoint functions. The PCIe function driver creates an auxiliary
device for the GPIO block, and that device gets bound to this auxiliary
device driver.
Co-developed-by: Daniel Thompson <daniel@riscstar.com>
Signed-off-by: Daniel Thompson <daniel@riscstar.com>
Signed-off-by: Alex Elder <elder@riscstar.com>
---
drivers/gpio/Kconfig | 11 ++
drivers/gpio/Makefile | 1 +
drivers/gpio/gpio-tc956x.c | 209 +++++++++++++++++++++++++++++++++++++
3 files changed, 221 insertions(+)
create mode 100644 drivers/gpio/gpio-tc956x.c
diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig
index 020e51e30317a..746cedea7e91d 100644
--- a/drivers/gpio/Kconfig
+++ b/drivers/gpio/Kconfig
@@ -1646,6 +1646,17 @@ config GPIO_TC3589X
This enables support for the GPIOs found on the TC3589X
I/O Expander.
+config GPIO_TC956X
+ tristate "Toshiba TC956X GPIO support"
+ depends on TOSHIBA_TC956X_PCI
+ default m if TOSHIBA_TC956X_PCI
+ help
+ This enables support for the GPIO controller embedded in the Toshiba
+ TC956X (and Qualcomm QPS615). This device connects to the host
+ via PCIe port, which is the upstream port on an internal PCIe
+ switch. On some platforms, a few of the GPIO lines are used to
+ manage external resets.
+
config GPIO_TIMBERDALE
bool "Support for timberdale GPIO IP"
depends on MFD_TIMBERDALE
diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile
index b267598b517de..c3584e7cba9b4 100644
--- a/drivers/gpio/Makefile
+++ b/drivers/gpio/Makefile
@@ -178,6 +178,7 @@ obj-$(CONFIG_GPIO_SYSCON) += gpio-syscon.o
obj-$(CONFIG_GPIO_TANGIER) += gpio-tangier.o
obj-$(CONFIG_GPIO_TB10X) += gpio-tb10x.o
obj-$(CONFIG_GPIO_TC3589X) += gpio-tc3589x.o
+obj-$(CONFIG_GPIO_TC956X) += gpio-tc956x.o
obj-$(CONFIG_GPIO_TEGRA186) += gpio-tegra186.o
obj-$(CONFIG_GPIO_TEGRA) += gpio-tegra.o
obj-$(CONFIG_GPIO_THUNDERX) += gpio-thunderx.o
diff --git a/drivers/gpio/gpio-tc956x.c b/drivers/gpio/gpio-tc956x.c
new file mode 100644
index 0000000000000..12221d8f812d9
--- /dev/null
+++ b/drivers/gpio/gpio-tc956x.c
@@ -0,0 +1,209 @@
+// SPDX-License-Identifier: GPL-2.0
+
+/*
+ * Copyright (C) 2026 by RISCstar Solutions Corporation. All rights reserved.
+ */
+
+/*
+ * The Toshiba TC956X implements a PCIe Gen 3 switch that connects an
+ * upstream x4 port to two downstream PCIe x2 ports. It incorporates
+ * an internal endpoint on a internal PCIe port that implements two
+ * Synopsys XGMAC Ethernet interfaces.
+ *
+ * 35 GPIOs are also implemented by an embedded GPIO controller. Three
+ * registers control the first 32 GPIOs (other than 20 and 21, which are
+ * reserved). Three other registers control GPIOs 32 through 36. GPIOs
+ * 22-24, 27-28, 31, and 34 are treated as "input only".
+ *
+ * There is a TC956X PCI power controller driver that accesses the
+ * direction and output value registers for GPIOs 2 and 3. These
+ * GPIOs control the reset signal for the two downstream PCIe ports.
+ * Their values will never change during operation of this driver, and
+ * this driver reserves these two GPIOS.
+ */
+
+#include <linux/auxiliary_bus.h>
+#include <linux/dev_printk.h>
+#include <linux/gpio/driver.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+
+#define DRIVER_NAME "tc956x-gpio"
+
+#define TC956X_GPIO_COUNT 37 /* Number of GPIOs (20-21 reserved) */
+
+/* The GPIO offsets are relative to 0x1200 in TC956X SFR space */
+#define GPIO_IN0_OFFSET 0x00 /* Input value (0-31) */
+#define GPIO_EN0_OFFSET 0x08 /* 0: out; 1: in (0-31) */
+#define GPIO_OUT0_OFFSET 0x10 /* Output value (0-31) */
+
+#define GPIO_IN1_OFFSET 0x04 /* Input value (32-36) */
+#define GPIO_EN1_OFFSET 0x0c /* 0: out; 1: in (32-36) */
+#define GPIO_OUT1_OFFSET 0x14 /* Output value (32-36) */
+
+/*
+ * struct tc956x_gpio - Information related to the embedded GPIO controller
+ * @chip: GPIO chip structure
+ * @regmap: MMIO register map for SFR GPIO region access
+ * @input_only: Bitmap indicating which GPIOs are input-only
+ */
+struct tc956x_gpio {
+ struct gpio_chip chip;
+ struct regmap *regmap;
+ DECLARE_BITMAP(input_only, TC956X_GPIO_COUNT);
+};
+
+static int tc956x_gpio_get_direction(struct gpio_chip *gc, unsigned int offset)
+{
+ struct tc956x_gpio *gpio = gpiochip_get_data(gc);
+ u32 reg;
+ u32 val;
+
+ if (test_bit(offset, gpio->input_only))
+ return GPIO_LINE_DIRECTION_IN;
+
+ reg = offset < 32 ? GPIO_EN0_OFFSET : GPIO_EN1_OFFSET;
+
+ regmap_read(gpio->regmap, reg, &val);
+ if (val & BIT(offset % 32))
+ return GPIO_LINE_DIRECTION_IN;
+
+ return GPIO_LINE_DIRECTION_OUT;
+}
+
+static int tc956x_gpio_direction_input(struct gpio_chip *gc,
+ unsigned int offset)
+{
+ u32 reg = offset < 32 ? GPIO_EN0_OFFSET : GPIO_EN1_OFFSET;
+ struct tc956x_gpio *gpio = gpiochip_get_data(gc);
+ u32 mask = BIT(offset % 32);
+
+ return regmap_update_bits(gpio->regmap, reg, mask, mask);
+}
+
+static int tc956x_gpio_direction_output(struct gpio_chip *gc,
+ unsigned int offset, int value)
+{
+ struct tc956x_gpio *gpio = gpiochip_get_data(gc);
+ u32 vreg;
+ u32 dreg;
+ u32 mask;
+
+ if (test_bit(offset, gpio->input_only))
+ return -EINVAL;
+
+ if (offset < 32) {
+ vreg = GPIO_OUT0_OFFSET;
+ dreg = GPIO_EN0_OFFSET;
+ } else {
+ vreg = GPIO_OUT1_OFFSET;
+ dreg = GPIO_EN1_OFFSET;
+ }
+ mask = BIT(offset % 32);
+
+ /* Set output value first, then direction */
+ regmap_update_bits(gpio->regmap, vreg, mask, value ? mask : 0);
+
+ return regmap_update_bits(gpio->regmap, dreg, mask, 0);
+}
+
+static int tc956x_gpio_get(struct gpio_chip *gc, unsigned int offset)
+{
+ u32 reg = offset < 32 ? GPIO_IN0_OFFSET : GPIO_IN1_OFFSET;
+ struct tc956x_gpio *gpio = gpiochip_get_data(gc);
+ u32 val;
+
+ regmap_read(gpio->regmap, reg, &val);
+
+ return val & BIT(offset % 32) ? 1 : 0;
+}
+
+static int tc956x_gpio_set(struct gpio_chip *gc, unsigned int offset, int value)
+{
+ u32 reg = offset < 32 ? GPIO_OUT0_OFFSET : GPIO_OUT1_OFFSET;
+ struct tc956x_gpio *gpio = gpiochip_get_data(gc);
+ u32 mask = BIT(offset % 32);
+
+ return regmap_update_bits(gpio->regmap, reg, mask, value ? mask : 0);
+}
+
+static int tc956x_gpio_init_valid_mask(struct gpio_chip *gc,
+ unsigned long *valid_mask,
+ unsigned int ngpios)
+{
+ /*
+ * GPIOs 2 and 3 are used by the PCI power control driver, and
+ * we don't allow them to be used. GPIOs 20 and 21 are reserved
+ * (and not usable).
+ */
+ bitmap_fill(valid_mask, ngpios);
+ bitmap_clear(valid_mask, 2, 2);
+ bitmap_clear(valid_mask, 20, 2);
+
+ return 0;
+}
+
+static int tc956x_gpio_probe(struct auxiliary_device *adev,
+ const struct auxiliary_device_id *id)
+{
+ struct device *dev = &adev->dev;
+ struct tc956x_gpio *gpio;
+ struct gpio_chip *gc;
+
+ if (!dev->platform_data)
+ return -EINVAL;
+
+ gpio = devm_kzalloc(dev, sizeof(*gpio), GFP_KERNEL);
+ if (!gpio)
+ return -ENOMEM;
+ gpio->regmap = dev->platform_data;
+
+ /* Mark GPIOs 22, 23, 24, 27, 28, 31, and 34 as input only */
+ bitmap_set(gpio->input_only, 22, 3);
+ bitmap_set(gpio->input_only, 27, 2);
+ set_bit(31, gpio->input_only);
+ set_bit(34, gpio->input_only);
+
+ gc = &gpio->chip;
+
+ gc->label = DRIVER_NAME;
+ gc->parent = dev->parent;
+
+ gc->get_direction = tc956x_gpio_get_direction;
+ gc->direction_input = tc956x_gpio_direction_input;
+ gc->direction_output = tc956x_gpio_direction_output;
+ gc->get = tc956x_gpio_get;
+ gc->set = tc956x_gpio_set;
+ gc->init_valid_mask = tc956x_gpio_init_valid_mask;
+
+ gc->base = -1;
+ gc->ngpio = TC956X_GPIO_COUNT;
+ gc->can_sleep = false;
+
+ dev_set_drvdata(dev, gpio);
+
+ return devm_gpiochip_add_data(dev, gc, gpio);
+}
+
+static const struct auxiliary_device_id tc956x_gpio_ids[] = {
+ { .name = "tc956x_pci.tc9564-gpio", },
+ { }
+};
+MODULE_DEVICE_TABLE(auxiliary, tc956x_gpio_ids);
+
+static struct auxiliary_driver tc956x_gpio_driver = {
+ .name = DRIVER_NAME,
+ .probe = tc956x_gpio_probe,
+ .id_table = tc956x_gpio_ids,
+ .driver = {
+ .name = DRIVER_NAME,
+ .owner = THIS_MODULE,
+ .probe_type = PROBE_PREFER_ASYNCHRONOUS,
+ },
+};
+module_auxiliary_driver(tc956x_gpio_driver);
+
+MODULE_DESCRIPTION("Toshiba TC956X PCIe GPIO Driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("auxiliary:" DRIVER_NAME);
--
2.51.0
^ permalink raw reply related [flat|nested] 37+ messages in thread* Re: [PATCH net-next 09/12] gpio: tc956x: add TC956x/QPS615 support
2026-05-01 15:54 ` [PATCH net-next 09/12] gpio: tc956x: add TC956x/QPS615 support Alex Elder
@ 2026-05-01 18:36 ` Andrew Lunn
2026-05-03 1:45 ` Alex Elder
2026-05-03 3:42 ` Julian Braha
1 sibling, 1 reply; 37+ messages in thread
From: Andrew Lunn @ 2026-05-01 18:36 UTC (permalink / raw)
To: Alex Elder
Cc: andrew+netdev, davem, edumazet, kuba, pabeni, maxime.chevallier,
rmk+kernel, andersson, konradybcio, robh, krzk+dt, conor+dt,
linusw, brgl, arnd, gregkh, daniel, mohd.anwar, a0987203069,
alexandre.torgue, ast, boon.khai.ng, chenchuangyu, chenhuacai,
daniel, hawk, hkallweit1, inochiama, john.fastabend, julianbraha,
livelycarpet87, matthew.gerlach, mcoquelin.stm32, me,
prabhakar.mahadev-lad.rj, richardcochran, rohan.g.thomas, sdf,
siyanteng, weishangjuan, wens, netdev, bpf, linux-arm-msm,
devicetree, linux-gpio, linux-stm32, linux-arm-kernel,
linux-kernel
> + * There is a TC956X PCI power controller driver that accesses the
> + * direction and output value registers for GPIOs 2 and 3. These
> + * GPIOs control the reset signal for the two downstream PCIe ports.
> + * Their values will never change during operation of this driver, and
> + * this driver reserves these two GPIOS.
Why doesn't this power controller driver actually use this driver to
control the GPIOs? Chicken/egg?
Maybe add a comment why gpio-regmap.c cannot be used. You probably
need to instantiate it twice, but i still think you will end up with
less code.
Andrew
^ permalink raw reply [flat|nested] 37+ messages in thread
* Re: [PATCH net-next 09/12] gpio: tc956x: add TC956x/QPS615 support
2026-05-01 18:36 ` Andrew Lunn
@ 2026-05-03 1:45 ` Alex Elder
2026-05-03 2:48 ` Andrew Lunn
2026-05-03 3:05 ` Andrew Lunn
0 siblings, 2 replies; 37+ messages in thread
From: Alex Elder @ 2026-05-03 1:45 UTC (permalink / raw)
To: Andrew Lunn
Cc: andrew+netdev, davem, edumazet, kuba, pabeni, maxime.chevallier,
rmk+kernel, andersson, konradybcio, robh, krzk+dt, conor+dt,
linusw, brgl, arnd, gregkh, daniel, mohd.anwar, a0987203069,
alexandre.torgue, ast, boon.khai.ng, chenchuangyu, chenhuacai,
daniel, hawk, hkallweit1, inochiama, john.fastabend, julianbraha,
livelycarpet87, matthew.gerlach, mcoquelin.stm32, me,
prabhakar.mahadev-lad.rj, richardcochran, rohan.g.thomas, sdf,
siyanteng, weishangjuan, wens, netdev, bpf, linux-arm-msm,
devicetree, linux-gpio, linux-stm32, linux-arm-kernel,
linux-kernel
On 5/1/26 1:36 PM, Andrew Lunn wrote:
>> + * There is a TC956X PCI power controller driver that accesses the
>> + * direction and output value registers for GPIOs 2 and 3. These
>> + * GPIOs control the reset signal for the two downstream PCIe ports.
>> + * Their values will never change during operation of this driver, and
>> + * this driver reserves these two GPIOS.
>
> Why doesn't this power controller driver actually use this driver to
> control the GPIOs? Chicken/egg?
I am not the one with authority on this, but yes, that's my
understanding. *Something* about this chip requires that the
PCIe ports need to have some configuration done on them *before*
PCIe is powered up. So that driver uses the I2C interface to
apply these settings. Meanwhile this driver uses the PCIe-mapped
memory to manage the GPIO registers.
> Maybe add a comment why gpio-regmap.c cannot be used. You probably
> need to instantiate it twice, but i still think you will end up with
> less code.
It's possible gpio-regmap.c *could* be used. We started with
vendor code and this code got separated at some point along
the way. It was working, and I don't think I pursued other
options at that point. I'll look at this possibility before we
send out the next version.
What do you mean instantiate it twice?
-Alex
>
> Andrew
^ permalink raw reply [flat|nested] 37+ messages in thread
* Re: [PATCH net-next 09/12] gpio: tc956x: add TC956x/QPS615 support
2026-05-03 1:45 ` Alex Elder
@ 2026-05-03 2:48 ` Andrew Lunn
2026-05-03 3:05 ` Andrew Lunn
1 sibling, 0 replies; 37+ messages in thread
From: Andrew Lunn @ 2026-05-03 2:48 UTC (permalink / raw)
To: Alex Elder
Cc: andrew+netdev, davem, edumazet, kuba, pabeni, maxime.chevallier,
rmk+kernel, andersson, konradybcio, robh, krzk+dt, conor+dt,
linusw, brgl, arnd, gregkh, daniel, mohd.anwar, a0987203069,
alexandre.torgue, ast, boon.khai.ng, chenchuangyu, chenhuacai,
daniel, hawk, hkallweit1, inochiama, john.fastabend, julianbraha,
livelycarpet87, matthew.gerlach, mcoquelin.stm32, me,
prabhakar.mahadev-lad.rj, richardcochran, rohan.g.thomas, sdf,
siyanteng, weishangjuan, wens, netdev, bpf, linux-arm-msm,
devicetree, linux-gpio, linux-stm32, linux-arm-kernel,
linux-kernel
> It's possible gpio-regmap.c *could* be used. We started with
> vendor code and this code got separated at some point along
> the way. It was working, and I don't think I pursued other
> options at that point. I'll look at this possibility before we
> send out the next version.
The GPIO subsystem has made a big effort to provide generic code,
since GPIOs are pretty simple things with a lot in common. So if the
generic code works, or can be made to work with minor changes, you
should use it.
> What do you mean instantiate it twice?
I _think_ you need one instance for the first 32 GPIOs, and a second
one for the remaining GPIOs. But maybe config->reg_stride might allow
it to work with a single instance?
Andrew
^ permalink raw reply [flat|nested] 37+ messages in thread
* Re: [PATCH net-next 09/12] gpio: tc956x: add TC956x/QPS615 support
2026-05-03 1:45 ` Alex Elder
2026-05-03 2:48 ` Andrew Lunn
@ 2026-05-03 3:05 ` Andrew Lunn
1 sibling, 0 replies; 37+ messages in thread
From: Andrew Lunn @ 2026-05-03 3:05 UTC (permalink / raw)
To: Alex Elder
Cc: andrew+netdev, davem, edumazet, kuba, pabeni, maxime.chevallier,
rmk+kernel, andersson, konradybcio, robh, krzk+dt, conor+dt,
linusw, brgl, arnd, gregkh, daniel, mohd.anwar, a0987203069,
alexandre.torgue, ast, boon.khai.ng, chenchuangyu, chenhuacai,
daniel, hawk, hkallweit1, inochiama, john.fastabend, julianbraha,
livelycarpet87, matthew.gerlach, mcoquelin.stm32, me,
prabhakar.mahadev-lad.rj, richardcochran, rohan.g.thomas, sdf,
siyanteng, weishangjuan, wens, netdev, bpf, linux-arm-msm,
devicetree, linux-gpio, linux-stm32, linux-arm-kernel,
linux-kernel
On Sat, May 02, 2026 at 08:45:48PM -0500, Alex Elder wrote:
> On 5/1/26 1:36 PM, Andrew Lunn wrote:
> > > + * There is a TC956X PCI power controller driver that accesses the
> > > + * direction and output value registers for GPIOs 2 and 3. These
> > > + * GPIOs control the reset signal for the two downstream PCIe ports.
> > > + * Their values will never change during operation of this driver, and
> > > + * this driver reserves these two GPIOS.
> >
> > Why doesn't this power controller driver actually use this driver to
> > control the GPIOs? Chicken/egg?
>
> I am not the one with authority on this, but yes, that's my
> understanding. *Something* about this chip requires that the
> PCIe ports need to have some configuration done on them *before*
> PCIe is powered up. So that driver uses the I2C interface to
> apply these settings. Meanwhile this driver uses the PCIe-mapped
> memory to manage the GPIO registers.
The diagram you have is:
----------------------------------
| Host |
------+...+----------+........+---
|i2c| | PCIe |
----------------+...+----------+........+------
| TC956x |I2C| |upstream| |
| ----- --+--------+--- |
| ----- ------ ------- | PCIe switch | |
| |SPI| |GPIO| |reset| | | |
| ----- ------ |clock| | DS3 DS2 DS1 | |
| ------- ---++--++--++-- |
| ----- ------ downstream// \\ \\ | downstream
| |MCU| |SRAM| /==========/ \\ \===== PCIe port 1
| ----- ------ //PCIe port 3 \\ |
| || \======= downstream
| ----+-----------++-----------+---- | PCIe port 2
| | M | internal PCIe endpoint | M | |
| | S |------------------------| S | ------ |
| | I | PCIe | | PCIe | I | |UART| |
| | G |function 0| |function 1| G | ------ |
| | E |----++----| |----++----| E | |
| | N | eMAC 0 | | eMAC 1 | N | |
--------+.......+------+.....+-----------------
|USXGMII| |SGMII|
--+.......+-- --+.....+--
| ARQ113C | | QEP8121 |
| PHY | | PHY |
------------- -----------
The two Ethernet controllers are hanging off port 3 of the
switch. However, the GPIO block is just floating in space. What
address space is it in?
I'm wondering if the GPIO controller should be a device/driver of its
own? It probes first. The PCI power controller driver then probes, and
has phandles to the GPIO controller so it can activate ports 1 and
2. Parallel to that the Ethernet driver(s) can probe, also using
phandles to the GPIO they need.
Looking at this diagram, putting the GPIO controller within one of the
port 3 functions is wrong. But maybe the diagram is not accurate.
Andrew
^ permalink raw reply [flat|nested] 37+ messages in thread
* Re: [PATCH net-next 09/12] gpio: tc956x: add TC956x/QPS615 support
2026-05-01 15:54 ` [PATCH net-next 09/12] gpio: tc956x: add TC956x/QPS615 support Alex Elder
2026-05-01 18:36 ` Andrew Lunn
@ 2026-05-03 3:42 ` Julian Braha
1 sibling, 0 replies; 37+ messages in thread
From: Julian Braha @ 2026-05-03 3:42 UTC (permalink / raw)
To: Alex Elder, andrew+netdev, davem, edumazet, kuba, pabeni,
maxime.chevallier, rmk+kernel, andersson, konradybcio, robh,
krzk+dt, conor+dt, linusw, brgl, arnd, gregkh
Cc: daniel, mohd.anwar, a0987203069, alexandre.torgue, ast,
boon.khai.ng, chenchuangyu, chenhuacai, daniel, hawk, hkallweit1,
inochiama, john.fastabend, livelycarpet87, matthew.gerlach,
mcoquelin.stm32, me, prabhakar.mahadev-lad.rj, richardcochran,
rohan.g.thomas, sdf, siyanteng, weishangjuan, wens, netdev, bpf,
linux-arm-msm, devicetree, linux-gpio, linux-stm32,
linux-arm-kernel, linux-kernel
On 5/1/26 16:54, Alex Elder wrote:
> +config GPIO_TC956X
> + tristate "Toshiba TC956X GPIO support"
> + depends on TOSHIBA_TC956X_PCI
> + default m if TOSHIBA_TC956X_PCI
Hi Alex,
In your Kconfig changes, this condition 'if TOSHIBA_TC956X_PCI' is dead
code. Since you have the dependency on TOSHIBA_TC956X_PCI, you can just
make the 'default m' unconditional - assuming this is what you intended.
Perhaps you would prefer to use 'default TOSHIBA_TC956X_PCI', which
would have GPIO_TC956X default to 'm' or 'y' when TOSHIBA_TC956X_PCI is
'm' or 'y', respectively.
- Julian Braha
^ permalink raw reply [flat|nested] 37+ messages in thread
* [PATCH net-next 10/12] net: stmmac: tc956x: add TC956x/QPS615 support
2026-05-01 15:54 [PATCH net-next 00/12] net: enable TC956x support Alex Elder
` (8 preceding siblings ...)
2026-05-01 15:54 ` [PATCH net-next 09/12] gpio: tc956x: add TC956x/QPS615 support Alex Elder
@ 2026-05-01 15:54 ` Alex Elder
2026-05-01 19:04 ` Andrew Lunn
2026-05-01 15:54 ` [PATCH net-next 11/12] misc: tc956x_pci: " Alex Elder
` (2 subsequent siblings)
12 siblings, 1 reply; 37+ messages in thread
From: Alex Elder @ 2026-05-01 15:54 UTC (permalink / raw)
To: andrew+netdev, davem, edumazet, kuba, pabeni, maxime.chevallier,
rmk+kernel, andersson, konradybcio, robh, krzk+dt, conor+dt,
linusw, brgl, arnd, gregkh
Cc: Daniel Thompson, elder, mohd.anwar, a0987203069, alexandre.torgue,
ast, boon.khai.ng, chenchuangyu, chenhuacai, daniel, hawk,
hkallweit1, inochiama, john.fastabend, julianbraha,
livelycarpet87, matthew.gerlach, mcoquelin.stm32, me,
prabhakar.mahadev-lad.rj, richardcochran, rohan.g.thomas, sdf,
siyanteng, weishangjuan, wens, netdev, bpf, linux-arm-msm,
devicetree, linux-gpio, linux-stm32, linux-arm-kernel,
linux-kernel
From: Daniel Thompson <daniel@riscstar.com>
Toshiba TC956x is an Ethernet AVB/TSN bridge and is essentially a
small and highly-specialized SoC. TC956x includes an "eMAC" subsystem
that can be accessed, along with several other peripherals, via two
PCIe endpoint functions. There is a main driver for the endpoint that
decomposes things and creates auxiliary bus devices to model the SoC.
The eMAC consists of a Designware XGMAC, XPCS and PMA. Each eMAC is
supported by an MSIGEN that bridges TC956x level interrupts to PCIe
MSIs.
Add a driver for the eMAC/MSIGEN combination.
Co-developed-by: Alex Elder <elder@riscstar.com>
Signed-off-by: Alex Elder <elder@riscstar.com>
Signed-off-by: Daniel Thompson <daniel@riscstar.com>
---
drivers/net/ethernet/stmicro/stmmac/Kconfig | 13 +
drivers/net/ethernet/stmicro/stmmac/Makefile | 2 +
.../ethernet/stmicro/stmmac/dwmac-tc956x.c | 791 ++++++++++++++++++
include/soc/toshiba/tc956x-dwmac.h | 84 ++
4 files changed, 890 insertions(+)
create mode 100644 drivers/net/ethernet/stmicro/stmmac/dwmac-tc956x.c
create mode 100644 include/soc/toshiba/tc956x-dwmac.h
diff --git a/drivers/net/ethernet/stmicro/stmmac/Kconfig b/drivers/net/ethernet/stmicro/stmmac/Kconfig
index e3dd5adda5aca..66bcfaccbe21f 100644
--- a/drivers/net/ethernet/stmicro/stmmac/Kconfig
+++ b/drivers/net/ethernet/stmicro/stmmac/Kconfig
@@ -404,6 +404,19 @@ config DWMAC_MOTORCOMM
This enables glue driver for Motorcomm DWMAC-based PCI Ethernet
controllers. Currently only YT6801 is supported.
+config DWMAC_TC956X
+ tristate "Toshiba TC956X DWMAC support"
+ depends on PCI
+ depends on COMMON_CLK
+ depends on TOSHIBA_TC956X_PCI
+ default m if TOSHIBA_TC956X_PCI
+ help
+ This selects the Toshiba TC956X (and Qualcomm QPS615) support in the
+ stmmac driver.
+
+ This provides support for the ethernet controllers found on these
+ devices.
+
config STMMAC_PCI
tristate "STMMAC PCI bus support"
depends on PCI
diff --git a/drivers/net/ethernet/stmicro/stmmac/Makefile b/drivers/net/ethernet/stmicro/stmmac/Makefile
index a1cea2f57252e..e8e7f95dbe3e8 100644
--- a/drivers/net/ethernet/stmicro/stmmac/Makefile
+++ b/drivers/net/ethernet/stmicro/stmmac/Makefile
@@ -51,4 +51,6 @@ obj-$(CONFIG_STMMAC_PCI) += stmmac-pci.o
obj-$(CONFIG_DWMAC_INTEL) += dwmac-intel.o
obj-$(CONFIG_DWMAC_LOONGSON) += dwmac-loongson.o
obj-$(CONFIG_DWMAC_MOTORCOMM) += dwmac-motorcomm.o
+obj-$(CONFIG_TC956X_PCI) += tc956x-pci.o
+obj-$(CONFIG_DWMAC_TC956X) += dwmac-tc956x.o
stmmac-pci-objs:= stmmac_pci.o
diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac-tc956x.c b/drivers/net/ethernet/stmicro/stmmac/dwmac-tc956x.c
new file mode 100644
index 0000000000000..27ff83dd3bd9e
--- /dev/null
+++ b/drivers/net/ethernet/stmicro/stmmac/dwmac-tc956x.c
@@ -0,0 +1,791 @@
+// SPDX-License-Identifier: GPL-2.0
+
+/*
+ * Copyright (C) 2026 by RISCstar Solutions Corporation. All rights reserved.
+ *
+ * Derived from code having the following copyrights:
+ * Copyright (C) 2011-2012 Vayavya Labs Pvt Ltd
+ * Copyright (C) 2025 Toshiba Electronic Devices & Storage Corporation
+ */
+
+#include <linux/auxiliary_bus.h>
+#include <linux/bitops.h>
+#include <linux/iopoll.h>
+#include <linux/irqdomain.h>
+#include <linux/irqchip/chained_irq.h>
+#include <linux/pcs/pcs-xpcs-regmap.h>
+#include <linux/pcs/pcs-xpcs.h>
+#include <linux/phy.h>
+#include <linux/regmap.h>
+#include <linux/stmmac.h>
+#include <linux/types.h>
+#include <linux/units.h>
+
+#include <soc/toshiba/tc956x-dwmac.h>
+
+#include "common.h"
+#include "dwxgmac2.h"
+#include "stmmac.h"
+
+#define DRIVER_NAME "dwmac-tc956x"
+
+#define TC956X_PTP_CLOCK_RATE (250 * HZ_PER_MHZ)
+
+#define TC956X_RX_FIFO_KB 46 /* Shared by all RX queues */
+#define TC956X_TX_FIFO_KB 46 /* Shared by all TX queues */
+
+/* Fields and values for the EMACTL registers */
+#define EMAC_SP_SEL_MASK GENMASK(3, 0)
+#define SP_SEL_SGMII_2500M 4
+#define SP_SEL_SGMII_1000M 5
+#define SP_SEL_SGMII_100M 6
+#define SP_SEL_SGMII_10M 7
+#define EMAC_PHY_INF_SEL_MASK GENMASK(5, 4)
+#define PCS_CLK_PHY 1 /* Clock from PHY */
+#define EMAC_INV_SGM_SIG_DET BIT(6) /* 1 = polarity inverted */
+#define EMAC_LPIHWCLKEN BIT(8) /* 1 = low power mode */
+#define EMAC_INIT_DONE BIT(21)
+
+/* MSIGEN Registers */
+#define MSI_OUT_EN_OFFSET 0x0000
+#define MSI_MASK_CLR_OFFSET 0x000c
+#define MSI_MASK_VALUE BIT(0)
+#define MSI_INT_STS_OFFSET 0x0010
+
+enum msigen_hwirq {
+ HWIRQ_LPI = 0,
+ HWIRQ_PMT = 1,
+ HWIRQ_EVENT = 2,
+ HWIRQ_TX0 = 3,
+ HWIRQ_RX0 = 11,
+ HWIRQ_XPCS = 19,
+ HWIRQ_PHY = 20,
+ HWIRQ_PFMAILBOX = 21,
+ HWIRQ_MSIREQ_PLS = 24
+};
+
+#define HWIRQ_COUNT 25
+
+/* Offset to the XPCS memory block, relative to the EMAC address range */
+#define DWMAC_XPCS_OFFSET 0x3a00
+
+/* Offset to the PMATOP memory block, relative to the EMAC address range */
+#define DWMAC_PMATOP_OFFSET 0x4000
+
+#define PMA_CML_GL_PM_CFG0 0x01b8
+
+/*
+ * Five sets three registers must be configured for PMA. The HWT_REFCLK
+ * registers are each separated by 0x14 bytes. The Common0 configuration
+ * registers are separated by 0x8 bytes.
+ */
+#define PMA_REG_COUNT 5
+
+#define PMA_HWT_REFCK_R_EN 0x1080
+#define PMA_HWT_REFCK_TERM_EN 0x1090
+#define PMA_HWT_REFCK_STRIDE 0x0014
+
+#define PMA_COMM_CFG_0_1 0x1888
+#define PMA_COMM_CFG_0_1_STRIDE 0x0008
+
+/* PMA_COMM_CFG_0_1 fields (WRITE_MASK is a field name) */
+#define COMM_CFG_WRITE_MASK_MASK GENMASK(16, 9)
+#define WRITE_MASK_VALUE 0xf7 /* Power-on value */
+#define COMM_CFG_ENABLE BIT(8)
+#define COMM_CFG_WRITE_DATA_MASK GENMASK(7, 0)
+#define WRITE_DATA_VALUE 0x04 /* Power-on value */
+
+/**
+ * struct tc956x_data - Toshiba-specific platform data
+ * @dev: Device pointer
+ * @irq_domain: MSIGEN IRQ domain
+ * @auxbus_data: Pointer to data passed from the parent device
+ * @plat: Pointer to our stmmac platform data
+ * @dma_cfg: DMA config buffer used by plat_stmmacenet_data
+ * @mdio_bus_data: MDIO bus data used by plat_stmmacenet_data
+ * @axi: AXI data used by plat_stmmacenet_data
+ * @desc: DMA descriptor data used by mac_device_info
+ * @dma: DMA operations data used by mac_device_info
+ */
+struct tc956x_data {
+ struct device *dev;
+ struct irq_domain *irq_domain;
+ struct tc956x_dwmac_data *auxbus_data;
+ struct plat_stmmacenet_data *plat;
+
+ /* These three fields are used by the plat_stmmacenet_data structure */
+ struct stmmac_dma_cfg dma_cfg;
+ struct stmmac_mdio_bus_data mdio_bus_data;
+ struct stmmac_axi axi;
+
+ /* These two fields are used by the mac_device_info structure */
+ struct stmmac_desc_ops desc;
+ struct stmmac_dma_ops dma;
+};
+
+struct tc956x_mac_speed {
+ phy_interface_t phy_interface;
+ int speed;
+ u32 sp_sel;
+};
+
+static struct tc956x_mac_speed mac_speed[] = {
+ { PHY_INTERFACE_MODE_2500BASEX, SPEED_2500, SP_SEL_SGMII_2500M, },
+ { PHY_INTERFACE_MODE_SGMII, SPEED_2500, SP_SEL_SGMII_2500M, },
+ { PHY_INTERFACE_MODE_SGMII, SPEED_1000, SP_SEL_SGMII_1000M, },
+ { PHY_INTERFACE_MODE_SGMII, SPEED_100, SP_SEL_SGMII_100M, },
+ { PHY_INTERFACE_MODE_SGMII, SPEED_10, SP_SEL_SGMII_10M, },
+};
+
+/* TC956x uses indirect addressing so this need only describe a 1KiB range */
+static const struct regmap_config xpcs_regmap_config = {
+ .reg_bits = 32,
+ .val_bits = 32,
+ .reg_base = 0x00, /* Minimum XPCS reg offset */
+ .max_register = 0xff, /* Register DW_VR_CSR_VIEWPORT */
+ .reg_shift = REGMAP_UPSHIFT(2),
+};
+
+static void tc956x_msigen_irq_handler(struct irq_desc *desc)
+{
+ struct irq_domain *irq_domain = irq_desc_get_handler_data(desc);
+ struct irq_chip *chip = irq_desc_get_chip(desc);
+ struct irq_chip_generic *gc;
+ unsigned long status;
+ unsigned int hwirq;
+
+ gc = irq_get_domain_generic_chip(irq_domain, 0);
+
+ chained_irq_enter(chip, desc);
+
+ status = irq_reg_readl(gc, MSI_INT_STS_OFFSET);
+ for_each_set_bit(hwirq, &status, HWIRQ_COUNT)
+ generic_handle_domain_irq(irq_domain, hwirq);
+
+ /*
+ * Clear the MSI flag. Most interrupts within TC956X are level-high
+ * type. If any interrupts are still asserted then clearing this flag
+ * will cause the (edge-triggered) MSI to be regenerated.
+ */
+ irq_reg_writel(gc, MSI_MASK_VALUE, MSI_MASK_CLR_OFFSET);
+
+ chained_irq_exit(chip, desc);
+}
+
+static int tc956x_msigen_irq_chip_init(struct irq_chip_generic *gc)
+{
+ struct tc956x_data *td = gc->domain->host_data;
+
+ gc->reg_base = td->auxbus_data->msigen;
+ gc->chip_types[0].regs.mask = MSI_OUT_EN_OFFSET;
+ gc->chip_types[0].chip.irq_mask = irq_gc_mask_clr_bit;
+ gc->chip_types[0].chip.irq_unmask = irq_gc_mask_set_bit;
+
+ /* Disable all interrupts */
+ irq_reg_writel(gc, 0, MSI_OUT_EN_OFFSET);
+
+ return 0;
+}
+
+static void tc956x_msigen_irq_chip_exit(struct irq_chip_generic *gc)
+{
+ irq_reg_writel(gc, 0, MSI_OUT_EN_OFFSET);
+}
+
+static int tc956x_msigen_irq_domain_init(struct irq_domain *irq_domain)
+{
+ struct tc956x_data *td = irq_domain->host_data;
+
+ irq_set_chained_handler_and_data(td->auxbus_data->msigen_irq,
+ tc956x_msigen_irq_handler,
+ irq_domain);
+
+ return 0;
+}
+
+static void tc956x_msigen_irq_domain_exit(struct irq_domain *irq_domain)
+{
+ struct tc956x_data *td = irq_domain->host_data;
+
+ irq_set_chained_handler_and_data(td->auxbus_data->msigen_irq,
+ NULL, NULL);
+}
+
+/* We have one IRQ chip instance with 25 IRQs in its domain */
+static struct irq_domain *
+tc956x_msigen_irq_domain_instantiate(struct tc956x_data *td)
+{
+ struct irq_domain_chip_generic_info dgc_info;
+ struct irq_domain_info info;
+
+ dgc_info.name = "tc956x-msigen";
+ dgc_info.handler = handle_level_irq;
+ dgc_info.irqs_per_chip = HWIRQ_COUNT;
+ dgc_info.num_ct = 1;
+ dgc_info.init = tc956x_msigen_irq_chip_init;
+ dgc_info.exit = tc956x_msigen_irq_chip_exit;
+
+ info.domain_flags = IRQ_DOMAIN_FLAG_DESTROY_GC;
+ info.size = HWIRQ_COUNT;
+ info.hwirq_max = HWIRQ_COUNT;
+ info.ops = &irq_generic_chip_ops;
+ info.host_data = td;
+ info.dgc_info = &dgc_info;
+ info.init = tc956x_msigen_irq_domain_init;
+ info.exit = tc956x_msigen_irq_domain_exit;
+
+ return devm_irq_domain_instantiate(td->dev, &info);
+}
+
+/**
+ * tc956x_pma_init() - Initialize PMA
+ * @td: bsp_priv pointer
+ *
+ * Initialize (or re-initialize) the PMA, configure the clocks and wait for the
+ * eMAC to be ready.
+ */
+static void tc956x_pma_init(struct tc956x_data *td)
+{
+ const struct tc956x_chip *chip = td->auxbus_data->chip;
+ void __iomem *emac_ctl = td->auxbus_data->emac_ctl;
+ u32 id = td->auxbus_data->mac_id;
+ void __iomem *pmatop;
+ u32 val;
+ u32 i;
+
+ /*
+ * When we re-initialize the PMA then the reset will already have
+ * been deasserted. We must make sure the PMA reset is asserted before
+ * we change the clock settings.
+ */
+ tc956x_reset_assert(chip, id, MAC_RESET_PMA);
+
+ pmatop = td->auxbus_data->emac + DWMAC_PMATOP_OFFSET;
+
+ /* Power on CML buffer (0 = normal mode, 1 = power down) */
+ writel(0, pmatop + PMA_CML_GL_PM_CFG0);
+
+ /* This value switches clock from C0_REFCK to CLK_REF_I */
+ val = u32_encode_bits(WRITE_MASK_VALUE, COMM_CFG_WRITE_MASK_MASK);
+ val |= COMM_CFG_ENABLE;
+ val |= u32_encode_bits(WRITE_DATA_VALUE, COMM_CFG_WRITE_DATA_MASK);
+
+ for (i = 0; i < PMA_REG_COUNT; i++) {
+ u32 offset = i * PMA_HWT_REFCK_STRIDE;
+
+ /* Disable C0_REFCK and 100 ohm termination */
+ writel(0, pmatop + PMA_HWT_REFCK_R_EN + offset);
+ writel(0, pmatop + PMA_HWT_REFCK_TERM_EN + offset);
+
+ /* Switch clock from C0_REFCK to CLK_REF_I */
+ offset = i * PMA_COMM_CFG_0_1_STRIDE;
+ writel(val, pmatop + PMA_COMM_CFG_0_1 + offset);
+ }
+
+ tc956x_reset_deassert(chip, id, MAC_RESET_PMA);
+
+ WARN_ON(readl_poll_timeout(emac_ctl, val, val & EMAC_INIT_DONE, 50, 1000000));
+}
+
+static int tc956x_mac_speed_select(struct tc956x_data *td, int speed)
+{
+ phy_interface_t phy_interface = td->plat->phy_interface;
+ struct net_device *netdev;
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(mac_speed); i++) {
+ if (mac_speed[i].speed != speed)
+ continue;
+
+ if (mac_speed[i].phy_interface == phy_interface)
+ return mac_speed[i].sp_sel;
+ }
+ netdev = dev_get_drvdata(td->dev);
+ netdev_err(netdev, "%s/%d unsupported\n",
+ phy_modes(phy_interface), speed);
+
+ return -EOPNOTSUPP;
+}
+
+static int tc956x_mac_configure(struct tc956x_data *td, int speed)
+{
+ void __iomem *emac_ctl = td->auxbus_data->emac_ctl;
+ int sp_sel;
+ u32 val;
+
+ sp_sel = tc956x_mac_speed_select(td, speed);
+ if (sp_sel < 0)
+ return sp_sel;
+
+ val = readl(emac_ctl);
+ val |= EMAC_LPIHWCLKEN;
+ val &= ~EMAC_INV_SGM_SIG_DET;
+ val = u32_replace_bits(val, PCS_CLK_PHY, EMAC_PHY_INF_SEL_MASK);
+ val = u32_replace_bits(val, sp_sel, EMAC_SP_SEL_MASK);
+ writel(val, emac_ctl);
+
+ return 0;
+}
+
+static int tc956x_mac_enable(struct tc956x_data *td)
+{
+ const struct tc956x_chip *chip = td->auxbus_data->chip;
+ struct plat_stmmacenet_data *plat = td->plat;
+ u32 id = td->auxbus_data->mac_id;
+ int ret;
+
+ tc956x_clock_enable(chip, id, MAC_CLOCK_TX);
+ tc956x_clock_enable(chip, id, MAC_CLOCK_RX);
+ tc956x_clock_enable(chip, id, MAC_CLOCK_ALL);
+ if (id)
+ tc956x_clock_enable(chip, id, MAC_CLOCK_RMII);
+
+ /* Set the speed related registers */
+ ret = tc956x_mac_configure(td, plat->max_speed);
+ if (ret)
+ return ret;
+
+ tc956x_reset_deassert(chip, id, MAC_RESET_MAC);
+ tc956x_pma_init(td);
+ tc956x_reset_deassert(chip, id, MAC_RESET_XPCS);
+
+ return 0;
+}
+
+static void tc956x_mac_disable(struct tc956x_data *td)
+{
+ const struct tc956x_chip *chip = td->auxbus_data->chip;
+ u32 id = td->auxbus_data->mac_id;
+
+ tc956x_reset_assert(chip, id, MAC_RESET_MAC);
+ tc956x_reset_assert(chip, id, MAC_RESET_PMA);
+ tc956x_reset_assert(chip, id, MAC_RESET_XPCS);
+
+ tc956x_clock_disable(chip, id, MAC_CLOCK_ALL);
+ tc956x_clock_disable(chip, id, MAC_CLOCK_RX);
+ tc956x_clock_disable(chip, id, MAC_CLOCK_TX);
+ if (id)
+ tc956x_clock_disable(chip, id, MAC_CLOCK_RMII);
+}
+
+static void tc956x_mac_init_state(struct tc956x_data *td)
+{
+ const struct tc956x_chip *chip = td->auxbus_data->chip;
+ u32 id = td->auxbus_data->mac_id;
+
+ tc956x_clock_disable(chip, id, MAC_CLOCK_125M);
+ tc956x_clock_disable(chip, id, MAC_CLOCK_312_5M);
+
+ tc956x_mac_disable(td);
+}
+
+/*
+ * Override method for dwxgmac301_dma_ops->init_rx_chan
+ *
+ * This differs from the dwxgmac301_dma_ops->init_rx_chan by translating the DMA
+ * address for TC956x internal bus. The window that provides DMA access to PCI
+ * is linearly mapped at 0x10_0000_0000.
+ */
+static void tc956x_dma_init_rx_chan(struct stmmac_priv *priv,
+ void __iomem *ioaddr,
+ struct stmmac_dma_cfg *dma_cfg,
+ dma_addr_t phy, u32 chan)
+{
+ dma_addr_t translated = phy + TC956X_SLV00_SRC_ADDR;
+
+ dwxgmac2_dma_init_rx_chan(priv, ioaddr, dma_cfg, phy, chan);
+
+ writel(upper_32_bits(translated),
+ ioaddr + XGMAC_DMA_CH_RxDESC_HADDR(chan));
+ writel(lower_32_bits(translated),
+ ioaddr + XGMAC_DMA_CH_RxDESC_LADDR(chan));
+}
+
+/* Override method for dwxgmac301_dma_ops->init_tx_chan */
+static void tc956x_dma_init_tx_chan(struct stmmac_priv *priv,
+ void __iomem *ioaddr,
+ struct stmmac_dma_cfg *dma_cfg,
+ dma_addr_t phy, u32 chan)
+{
+ dma_addr_t translated = phy + TC956X_SLV00_SRC_ADDR;
+
+ dwxgmac2_dma_init_tx_chan(priv, ioaddr, dma_cfg, phy, chan);
+
+ writel(upper_32_bits(translated),
+ ioaddr + XGMAC_DMA_CH_TxDESC_HADDR(chan));
+ writel(lower_32_bits(translated),
+ ioaddr + XGMAC_DMA_CH_TxDESC_LADDR(chan));
+}
+
+/* Override method for dwxgmac210_desc_ops->set_addr */
+static void tc956x_desc_set_addr(struct dma_desc *p, dma_addr_t addr)
+{
+ dma_addr_t translated = addr + TC956X_SLV00_SRC_ADDR;
+
+ p->des0 = cpu_to_le32(lower_32_bits(translated));
+ p->des1 = cpu_to_le32(upper_32_bits(translated));
+}
+
+/* Override method for dwxgmac210_desc_ops->set_sec_addr */
+static void tc956x_desc_set_sec_addr(struct dma_desc *p, dma_addr_t addr,
+ bool is_valid)
+{
+ dma_addr_t translated = addr + TC956X_SLV00_SRC_ADDR;
+
+ p->des2 = cpu_to_le32(lower_32_bits(translated));
+ p->des3 = cpu_to_le32(upper_32_bits(translated));
+}
+
+/*
+ * Use mac_setup to apply the override methods above.
+ *
+ * The memory for the modified ops structures is pre-allocated as part of
+ * struct tc956x_data.
+ */
+static int tc956x_mac_setup(void *apriv, struct mac_device_info *mac)
+{
+ struct stmmac_priv *priv = apriv;
+ struct stmmac_desc_ops *desc;
+ struct stmmac_dma_ops *dma;
+ struct tc956x_data *td;
+
+ td = priv->plat->bsp_priv;
+
+ /* dwxgmac301_dma_ops needs extending to provide DMA address translation */
+ dma = &td->dma;
+ *dma = dwxgmac301_dma_ops;
+ dma->init_rx_chan = tc956x_dma_init_rx_chan;
+ dma->init_tx_chan = tc956x_dma_init_tx_chan;
+ mac->dma = dma;
+
+ /* dwxgmac210_desc_ops also needs extending for the same reason */
+ desc = &td->desc;
+ *desc = dwxgmac210_desc_ops;
+ desc->set_addr = tc956x_desc_set_addr;
+ desc->set_sec_addr = tc956x_desc_set_sec_addr;
+ mac->desc = desc;
+
+ priv->hw = mac;
+
+ return dwxgmac2_setup(priv);
+}
+
+static int tc956x_pcs_init(struct stmmac_priv *priv)
+{
+ struct xpcs_regmap_config xpcs_regmap_cfg;
+ void __iomem *emac = priv->ioaddr;
+ struct regmap *xpcs_regmap;
+ void __iomem *xpcs_addr;
+ struct dw_xpcs *xpcs;
+
+ xpcs_addr = emac + DWMAC_XPCS_OFFSET;
+ xpcs_regmap = devm_regmap_init_mmio(priv->device, xpcs_addr,
+ &xpcs_regmap_config);
+ if (IS_ERR(xpcs_regmap))
+ return PTR_ERR(xpcs_regmap);
+
+ xpcs_regmap_cfg.regmap = xpcs_regmap;
+ xpcs_regmap_cfg.reg_indir = true;
+
+ xpcs = devm_xpcs_regmap_register(priv->device, &xpcs_regmap_cfg);
+ if (IS_ERR(xpcs))
+ return PTR_ERR(xpcs);
+
+ xpcs_config_eee_mult_fact(xpcs, priv->plat->mult_fact_100ns);
+ priv->hw->phylink_pcs = xpcs_to_phylink_pcs(xpcs);
+
+ return 0;
+}
+
+static struct phylink_pcs *tc956x_select_pcs(struct stmmac_priv *priv,
+ phy_interface_t interface)
+{
+ return priv->hw->phylink_pcs;
+}
+
+static void tc956x_fix_mac_speed(void *bsp_priv, phy_interface_t interface,
+ int speed, unsigned int mode)
+{
+ struct tc956x_data *td = bsp_priv;
+
+ tc956x_mac_configure(td, speed);
+ tc956x_pma_init(td);
+}
+
+static int tc956x_dwmac_suspend(struct device *dev, void *bsp_priv)
+{
+ struct tc956x_data *td = bsp_priv;
+
+ tc956x_mac_disable(td);
+
+ return 0;
+}
+
+static int tc956x_dwmac_resume(struct device *dev, void *bsp_priv)
+{
+ struct tc956x_data *td = bsp_priv;
+
+ return tc956x_mac_enable(td);
+}
+
+/* Called by tc956x_dwmac_probe(); return errors with dev_err_probe() */
+static int tc956x_dwmac_parse_dt(struct tc956x_data *td)
+{
+ struct device_node *mdio_node;
+ struct device *dev = td->dev;
+ struct device_node *np;
+
+ np = dev_of_node(dev);
+ if (!np)
+ return dev_err_probe(dev, -EINVAL, "no devicetree node\n");
+
+ /* Find the MDIO bus */
+ for_each_child_of_node(np, mdio_node) {
+ if (of_device_is_compatible(mdio_node,
+ "snps,dwmac-mdio"))
+ break;
+ }
+
+ /* Pass the MDIO bus (if there is one) to the core driver */
+ if (mdio_node) {
+ td->plat->mdio_node = mdio_node;
+ td->plat->mdio_bus_data->needs_reset = true;
+ }
+
+ return 0;
+}
+
+static int tc956x_lookup_max_speed(phy_interface_t phy_interface)
+{
+ switch (phy_interface) {
+ case PHY_INTERFACE_MODE_SGMII:
+ case PHY_INTERFACE_MODE_2500BASEX:
+ return SPEED_2500;
+
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+/* Called by tc956x_dwmac_probe(); return errors with dev_err_probe() */
+static int tc956x_plat_dat_init(struct tc956x_data *td)
+{
+ struct plat_stmmacenet_data *plat;
+ phy_interface_t phy_interface;
+ struct device *dev = td->dev;
+ struct stmmac_axi *axi;
+ u32 speed;
+ int ret;
+ u32 i;
+
+ phy_interface = device_get_phy_mode(dev);
+ if (phy_interface < 0)
+ return -ENODEV;
+
+ /* The platform structure is allocated with devm_kzalloc() */
+ plat = stmmac_plat_dat_alloc(dev);
+ if (!plat)
+ return -ENOMEM;
+
+ ret = tc956x_lookup_max_speed(phy_interface);
+ if (ret < 0)
+ return dev_err_probe(dev, ret, "unsupported phy speed\n");
+ speed = ret;
+
+ plat->core_type = DWMAC_CORE_XGMAC;
+ plat->bus_id = td->auxbus_data->mac_id;
+ plat->phy_interface = phy_interface;
+ plat->mdio_bus_data = &td->mdio_bus_data;
+ /* Parent PCI device is used for DMA */
+ plat->dma_device = dev->parent;
+ plat->dma_cfg = &td->dma_cfg;
+ plat->dma_cfg->pbl = 32;
+ plat->dma_cfg->pblx8 = true;
+
+ /*
+ * Our MAC clock rate is fixed at 125 MHz. For XGMAC, clk_csr 0
+ * represents "divide by 62" and gets the best rate under 2.5 MHz.
+ */
+ plat->clk_csr = 0; /* MDC clock = clk_csr_i / 62 */
+ plat->force_sf_dma_mode = 1;
+ plat->max_speed = speed;
+ plat->unicast_filter_entries = 32;
+
+ /*
+ * TC956x has 8 RX queues but we observe significantly reduced RX
+ * bandwidth if we don't have at least 8k FIFO space per queue, so
+ * by default we avoid using all the queues.
+ */
+ plat->rx_queues_to_use = 4;
+
+ /*
+ * TX956x has 8 TX queues but only #0 to #3 work for general IP traffic.
+ * For now we will limit the driver to only these queues.
+ */
+ plat->tx_queues_to_use = 4;
+
+ /*
+ * Oversized FIFOs result in reduced performance in bandwidth tests.
+ * Limit them to 8KiB per queue, or the total available.
+ */
+ plat->tx_fifo_size =
+ min(TC956X_TX_FIFO_KB, 8 * plat->tx_queues_to_use) * SZ_1K;
+ plat->rx_fifo_size =
+ min(TC956X_RX_FIFO_KB, 8 * plat->rx_queues_to_use) * SZ_1K;
+ plat->host_dma_width = 36;
+
+ plat->rx_sched_algorithm = MTL_RX_ALGORITHM_SP;
+ plat->tx_sched_algorithm = MTL_TX_ALGORITHM_WRR;
+
+ /* Default RX chan is set to queue index (0..rx_queues_to_use-1) */
+ for (i = 0; i < plat->rx_queues_to_use; i++)
+ plat->rx_queues_cfg[i].mode_to_use = MTL_QUEUE_DCB;
+
+ for (i = 0; i < plat->tx_queues_to_use; i++) {
+ plat->tx_queues_cfg[i].weight = 12;
+ plat->tx_queues_cfg[i].mode_to_use = MTL_QUEUE_DCB;
+
+ /* Only queues 5-8 support time-based scheduling on TC956X */
+ if (i >= 5)
+ plat->tx_queues_cfg[i].tbs_en = 1;
+ }
+
+ plat->fix_mac_speed = tc956x_fix_mac_speed;
+ plat->suspend = tc956x_dwmac_suspend;
+ plat->resume = tc956x_dwmac_resume;
+ plat->mac_setup = tc956x_mac_setup;
+ plat->pcs_init = tc956x_pcs_init;
+ plat->select_pcs = tc956x_select_pcs;
+
+ plat->bsp_priv = td;
+ plat->clk_ptp_rate = TC956X_PTP_CLOCK_RATE;
+
+ /* AXI Configuration */
+ axi = &td->axi;
+ axi->axi_lpi_en = 1;
+ axi->axi_wr_osr_lmt = 31;
+ axi->axi_rd_osr_lmt = 31;
+ /* All sizes (2^2..2^8) are supported */
+ axi->axi_blen_regval = DMA_AXI_BLEN_MASK;
+ plat->axi = axi;
+
+ plat->mac_port_sel_speed = speed;
+ plat->flags = STMMAC_FLAG_MULTI_MSI_EN | STMMAC_FLAG_TSO_EN;
+
+ td->plat = plat;
+
+ return 0;
+}
+
+/*
+ * The domain was created with IRQ_DOMAIN_FLAG_DESTROY_GC, so any mapped IRQs
+ * will be disposed when the domain is removed (when the device is destroyed).
+ */
+static int tc956x_stmmac_resources_init(struct tc956x_data *td,
+ struct stmmac_resources *res)
+{
+ struct irq_domain *irq_domain = td->irq_domain;
+ u32 i;
+
+ res->irq = irq_create_mapping(irq_domain, HWIRQ_EVENT);
+ if (!res->irq)
+ return -EINVAL;
+
+ for (i = 0; i < td->plat->tx_queues_to_use; i++) {
+ res->tx_irq[i] = irq_create_mapping(irq_domain, HWIRQ_TX0 + i);
+ if (!res->tx_irq[i])
+ return -EINVAL;
+ }
+
+ for (i = 0; i < td->plat->rx_queues_to_use; i++) {
+ res->rx_irq[i] = irq_create_mapping(irq_domain, HWIRQ_RX0 + i);
+ if (!res->tx_irq[i])
+ return -EINVAL;
+ }
+
+ res->addr = td->auxbus_data->emac;
+
+ return 0;
+}
+
+static int tc956x_dwmac_probe(struct auxiliary_device *adev,
+ const struct auxiliary_device_id *id)
+{
+ struct stmmac_resources res = { };
+ struct device *dev = &adev->dev;
+ struct tc956x_data *td;
+ int ret;
+
+ td = devm_kzalloc(dev, sizeof(*td), GFP_KERNEL);
+ if (!td)
+ return -ENOMEM;
+
+ td->dev = dev;
+ td->auxbus_data = dev_get_platdata(dev);
+ if (!td->auxbus_data)
+ return dev_err_probe(dev, -EINVAL, "no platform data\n");
+
+ ret = tc956x_plat_dat_init(td);
+ if (ret)
+ return ret;
+
+ ret = tc956x_dwmac_parse_dt(td);
+ if (ret)
+ return ret;
+
+ td->irq_domain = tc956x_msigen_irq_domain_instantiate(td);
+ if (IS_ERR(td->irq_domain))
+ return dev_err_probe(dev, PTR_ERR(td->irq_domain),
+ "failed to instantiate IRQ domain\n");
+
+ /* Put the MAC in a known initial state */
+ tc956x_mac_init_state(td);
+
+ ret = tc956x_stmmac_resources_init(td, &res);
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "failed to initialize stmmac resources\n");
+
+ ret = tc956x_mac_enable(td);
+ if (ret)
+ return dev_err_probe(dev, ret, "failed to enable MAC\n");
+
+ ret = stmmac_dvr_probe(dev, td->plat, &res);
+ if (ret)
+ return dev_err_probe(dev, ret, "failed stmmac probe\n");
+
+ return 0;
+}
+
+static void tc956x_dwmac_remove(struct auxiliary_device *adev)
+{
+ struct device *dev = &adev->dev;
+ struct net_device *ndev = dev_get_drvdata(dev);
+ struct stmmac_priv *priv = netdev_priv(ndev);
+ struct tc956x_data *td = priv->plat->bsp_priv;
+
+ stmmac_dvr_remove(dev);
+ tc956x_mac_disable(td);
+}
+
+static const struct auxiliary_device_id tc956x_dwmac_ids[] = {
+ { .name = TC956X_PCIE_DRIVER_NAME "." TC956X_XGMAC_DEV_NAME, },
+ { },
+};
+MODULE_DEVICE_TABLE(auxiliary, tc956x_dwmac_ids);
+
+static struct auxiliary_driver tc956x_dwmac_driver = {
+ .name = DRIVER_NAME,
+ .probe = tc956x_dwmac_probe,
+ .remove = tc956x_dwmac_remove,
+ .id_table = tc956x_dwmac_ids,
+ .driver = {
+ .name = DRIVER_NAME,
+ .pm = &stmmac_simple_pm_ops,
+ .owner = THIS_MODULE,
+ },
+};
+module_auxiliary_driver(tc956x_dwmac_driver);
+
+MODULE_DESCRIPTION("Toshiba TC956x PCIe Ethernet Network Driver");
+MODULE_LICENSE("GPL");
diff --git a/include/soc/toshiba/tc956x-dwmac.h b/include/soc/toshiba/tc956x-dwmac.h
new file mode 100644
index 0000000000000..5ca39cf764be9
--- /dev/null
+++ b/include/soc/toshiba/tc956x-dwmac.h
@@ -0,0 +1,84 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+/*
+ * Copyright (C) 2026 by RISCstar Solutions Corporation. All rights reserved.
+ */
+
+#ifndef __TOSHIBA_TC956X_DWMAC_H__
+#define __TOSHIBA_TC956X_DWMAC_H__
+
+#include <linux/compiler_types.h>
+#include <linux/types.h>
+
+#define TC956X_PCIE_DRIVER_NAME "tc956x_pci"
+
+#define TC956X_XGMAC_DEV_NAME "dwmac-tc956x"
+
+/* Starting address of the space translated by the PCIe endpoint bridge */
+#define TC956X_SLV00_SRC_ADDR 0x0000001000000000ULL
+
+enum tc956x_reset_id {
+ MAC_RESET_MAC = 7,
+ MAC_RESET_PMA = 30,
+ MAC_RESET_XPCS = 31,
+};
+
+enum tc956x_clock_id {
+ MAC_CLOCK_TX = 7,
+ MAC_CLOCK_RX = 14,
+ MAC_CLOCK_ALL = 31,
+ MAC_CLOCK_125M = 29,
+ MAC_CLOCK_312_5M = 30,
+ MAC_CLOCK_RMII = 15, /* eMAC 1 only */
+};
+
+/**
+ * struct tc956x_dwmac_data - Structure passed to stmmac auxiliary devices.
+ * @chip: Context pointer needed for reset and clock operations
+ * @emac: I/O mapped address used by eMAC
+ * @emac_ctl: I/O mapped address used for eMAC control
+ * @msigen: I/O mapped address used by MSIGEN
+ * @msigen_irq: IRQ number used by MSIGEN
+ * @rev_id: Chip revision ID (for quirks)
+ * @mac_id: Unique device ID (0 or 1)
+ *
+ * This structure is passed via platform data to the stmmac auxiliary devices.
+ */
+struct tc956x_dwmac_data {
+ const struct tc956x_chip *chip;
+ void __iomem *emac;
+ void __iomem *emac_ctl;
+ void __iomem *msigen;
+ unsigned int msigen_irq;
+ u8 rev_id;
+ u8 mac_id;
+};
+
+extern void tc956x_reset_clock_set(const struct tc956x_chip *chip, bool reset,
+ bool reg0, bool set, u8 bit);
+
+static inline void tc956x_reset_assert(const struct tc956x_chip *chip,
+ u8 mac_id, enum tc956x_reset_id id)
+{
+ tc956x_reset_clock_set(chip, true, !mac_id, true, (u8)id);
+}
+
+static inline void tc956x_reset_deassert(const struct tc956x_chip *chip,
+ u8 mac_id, enum tc956x_reset_id id)
+{
+ tc956x_reset_clock_set(chip, true, !mac_id, false, (u8)id);
+}
+
+static inline void tc956x_clock_enable(const struct tc956x_chip *chip,
+ u8 mac_id, enum tc956x_clock_id id)
+{
+ tc956x_reset_clock_set(chip, false, !mac_id, true, (u8)id);
+}
+
+static inline void tc956x_clock_disable(const struct tc956x_chip *chip,
+ u8 mac_id, enum tc956x_clock_id id)
+{
+ tc956x_reset_clock_set(chip, false, !mac_id, false, (u8)id);
+}
+
+#endif /* __TOSHIBA_TC956X_DWMAC_H__*/
--
2.51.0
^ permalink raw reply related [flat|nested] 37+ messages in thread* Re: [PATCH net-next 10/12] net: stmmac: tc956x: add TC956x/QPS615 support
2026-05-01 15:54 ` [PATCH net-next 10/12] net: stmmac: " Alex Elder
@ 2026-05-01 19:04 ` Andrew Lunn
0 siblings, 0 replies; 37+ messages in thread
From: Andrew Lunn @ 2026-05-01 19:04 UTC (permalink / raw)
To: Alex Elder
Cc: andrew+netdev, davem, edumazet, kuba, pabeni, maxime.chevallier,
rmk+kernel, andersson, konradybcio, robh, krzk+dt, conor+dt,
linusw, brgl, arnd, gregkh, Daniel Thompson, mohd.anwar,
a0987203069, alexandre.torgue, ast, boon.khai.ng, chenchuangyu,
chenhuacai, daniel, hawk, hkallweit1, inochiama, john.fastabend,
julianbraha, livelycarpet87, matthew.gerlach, mcoquelin.stm32, me,
prabhakar.mahadev-lad.rj, richardcochran, rohan.g.thomas, sdf,
siyanteng, weishangjuan, wens, netdev, bpf, linux-arm-msm,
devicetree, linux-gpio, linux-stm32, linux-arm-kernel,
linux-kernel
> +static struct tc956x_mac_speed mac_speed[] = {
> + { PHY_INTERFACE_MODE_2500BASEX, SPEED_2500, SP_SEL_SGMII_2500M, },
> + { PHY_INTERFACE_MODE_SGMII, SPEED_2500, SP_SEL_SGMII_2500M, },
> + { PHY_INTERFACE_MODE_SGMII, SPEED_1000, SP_SEL_SGMII_1000M, },
That looks odd. Some vendors implemented 2500BaseX using SGMII
overclocked. But that is not strictly 2500BaseX. Having the 2500BASEX
entry suggests you have real 2500BASEX, so why have an SGMII entry
with SPEED_2500?
> +/* We have one IRQ chip instance with 25 IRQs in its domain */
One per MAC, or one overall?
> +static struct irq_domain *
> +tc956x_msigen_irq_domain_instantiate(struct tc956x_data *td)
> +{
> + struct irq_domain_chip_generic_info dgc_info;
> + struct irq_domain_info info;
> +
> + dgc_info.name = "tc956x-msigen";
If it is one per MAC, maybe this name should indicate which instance
of the MAC this is.
> +static int tc956x_mac_setup(void *apriv, struct mac_device_info *mac)
> +{
> + struct stmmac_priv *priv = apriv;
> + struct stmmac_desc_ops *desc;
> + struct stmmac_dma_ops *dma;
> + struct tc956x_data *td;
> +
> + td = priv->plat->bsp_priv;
> +
> + /* dwxgmac301_dma_ops needs extending to provide DMA address translation */
> + dma = &td->dma;
> + *dma = dwxgmac301_dma_ops;
> + dma->init_rx_chan = tc956x_dma_init_rx_chan;
> + dma->init_tx_chan = tc956x_dma_init_tx_chan;
> + mac->dma = dma;
I could be reading this wrong....
dma points to the global dwxgmac301_dma_ops, which you added a few
patches back.
You then modify it, changing two values in it.
Doesn't that break any other dwxgmac301 in the system? Shouldn't you
be making a copy of the global structure, and then making
modifications to your copy? mac->dma then points to your copy?
> + /* dwxgmac210_desc_ops also needs extending for the same reason */
> + desc = &td->desc;
> + *desc = dwxgmac210_desc_ops;
> + desc->set_addr = tc956x_desc_set_addr;
> + desc->set_sec_addr = tc956x_desc_set_sec_addr;
> + mac->desc = desc;
And the same problem here?
> +/* Called by tc956x_dwmac_probe(); return errors with dev_err_probe() */
> +static int tc956x_dwmac_parse_dt(struct tc956x_data *td)
> +{
> + struct device_node *mdio_node;
> + struct device *dev = td->dev;
> + struct device_node *np;
> +
> + np = dev_of_node(dev);
> + if (!np)
> + return dev_err_probe(dev, -EINVAL, "no devicetree node\n");
> +
> + /* Find the MDIO bus */
> + for_each_child_of_node(np, mdio_node) {
> + if (of_device_is_compatible(mdio_node,
> + "snps,dwmac-mdio"))
> + break;
> + }
It looks like if you put the ethernet properties into an ethernet node
in DT, this might go away? Or at least allow you to use
stmmac_of_get_mdio().
Andrew
^ permalink raw reply [flat|nested] 37+ messages in thread
* [PATCH net-next 11/12] misc: tc956x_pci: add TC956x/QPS615 support
2026-05-01 15:54 [PATCH net-next 00/12] net: enable TC956x support Alex Elder
` (9 preceding siblings ...)
2026-05-01 15:54 ` [PATCH net-next 10/12] net: stmmac: " Alex Elder
@ 2026-05-01 15:54 ` Alex Elder
2026-05-01 21:07 ` Andrew Lunn
2026-05-02 16:45 ` Jakub Kicinski
2026-05-01 15:54 ` [PATCH net-next 12/12] arm64: dts: qcom: qcs6490-rb3gen2: enable TC9564 with a single QCS8081 phy Alex Elder
2026-05-02 16:47 ` [PATCH net-next 00/12] net: enable TC956x support Jakub Kicinski
12 siblings, 2 replies; 37+ messages in thread
From: Alex Elder @ 2026-05-01 15:54 UTC (permalink / raw)
To: andrew+netdev, davem, edumazet, kuba, pabeni, maxime.chevallier,
rmk+kernel, andersson, konradybcio, robh, krzk+dt, conor+dt,
linusw, brgl, arnd, gregkh
Cc: daniel, elder, mohd.anwar, a0987203069, alexandre.torgue, ast,
boon.khai.ng, chenchuangyu, chenhuacai, daniel, hawk, hkallweit1,
inochiama, john.fastabend, julianbraha, livelycarpet87,
matthew.gerlach, mcoquelin.stm32, me, prabhakar.mahadev-lad.rj,
richardcochran, rohan.g.thomas, sdf, siyanteng, weishangjuan,
wens, netdev, bpf, linux-arm-msm, devicetree, linux-gpio,
linux-stm32, linux-arm-kernel, linux-kernel
The Toshiba TC956x is an Ethernet AVB/TSN bridge, and is
essentially a small and highly-specialized SoC. It implements
a number of internal functions, including a GPIO controller,
control registers managing internal reset and clock control
signals, a PCIe switch and internal endpoint, and mapping
hardware that translates between PCIe and internal addressing.
The internal PCIe endpoint implements two PCIe functions, each of
which has an attached eMAC. Each of these eMACs is represented as
two separate devices: a PCIe function; and the eMAC. The PCIe
function driver serves as the primary driver, creating other
associated devices using the auxiliary bus.
PCIe function 0 is responsible for managing common features on
the TC956x chip. It initializes a "chip" data structure that
keeps track of common information, and makes that available via
its platform_data pointer to its PCIe function 1 peer. It also
configures the address mapping hardware, and sets up an auxiliary
device for the GPIO controller.
As probing concludes, an auxiliary device is created to represent
the eMAC functionality attached to the PCIe function. A block
of information is set up to be shared with the auxiliary device.
It provides the IRQ to be used by the MAC device, as well as a
some memory-mapped I/O pointers and a few other bits of information
about the chip. This information is supplied via the auxiliary
device's platform_data pointer.
Co-developed-by: Daniel Thompson <daniel@riscstar.com>
Signed-off-by: Daniel Thompson <daniel@riscstar.com>
Signed-off-by: Alex Elder <elder@riscstar.com>
---
drivers/misc/Kconfig | 10 +
drivers/misc/Makefile | 1 +
drivers/misc/tc956x_pci.c | 667 ++++++++++++++++++++++++++++++++++++++
3 files changed, 678 insertions(+)
create mode 100644 drivers/misc/tc956x_pci.c
diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig
index 00683bf06258f..d021f37129e27 100644
--- a/drivers/misc/Kconfig
+++ b/drivers/misc/Kconfig
@@ -644,6 +644,16 @@ config MCHP_LAN966X_PCI
- lan966x-miim (MDIO_MSCC_MIIM)
- lan966x-switch (LAN966X_SWITCH)
+config TOSHIBA_TC956X_PCI
+ tristate "Toshiba TC956X PCI function support"
+ depends on PCI
+ select AUXILIARY_BUS
+ help
+ This enables support for the two PCI functions implemented by
+ the embedded PCIe endpoint in the Toshiba TC956X. This driver
+ creates auxiliary devices and requires drivers for these devices
+ to function.
+
source "drivers/misc/c2port/Kconfig"
source "drivers/misc/eeprom/Kconfig"
source "drivers/misc/cb710/Kconfig"
diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile
index b32a2597d2467..644d508338382 100644
--- a/drivers/misc/Makefile
+++ b/drivers/misc/Makefile
@@ -75,3 +75,4 @@ obj-$(CONFIG_MCHP_LAN966X_PCI) += lan966x-pci.o
obj-y += keba/
obj-y += amd-sbi/
obj-$(CONFIG_MISC_RP1) += rp1/
+obj-$(CONFIG_TOSHIBA_TC956X_PCI) += tc956x_pci.o
diff --git a/drivers/misc/tc956x_pci.c b/drivers/misc/tc956x_pci.c
new file mode 100644
index 0000000000000..741a0ae0d3afb
--- /dev/null
+++ b/drivers/misc/tc956x_pci.c
@@ -0,0 +1,667 @@
+// SPDX-License-Identifier: GPL-2.0
+
+/*
+ * Copyright (C) 2026 by RISCstar Solutions Corporation. All rights reserved.
+ */
+
+/*
+ * The Toshiba TC956X implements a PCIe Gen 3 switch that connects an
+ * upstream x4 port to three downstream PCIe ports--two external ones
+ * and an internal one which implements an internal PCIe endpoint. The
+ * endpoint implements two PCIe functions, each having a Synopsys XGMAC
+ * Ethernet interface.
+ *
+ * The TC956X implements other functionality, including an embedded
+ * MCU, a UART, a GPIO controller, internal resets and clocks, and
+ * interrupt handling. These features are separate from (and in some
+ * cases used by) both Ethernet XGMACs. Each Ethernet MAC must be
+ * attached to a working PHY for it to be functional, and for this
+ * reason either of them (or both!) might not be usable/used.
+ *
+ * To support the non-XGMAC functionality on the TC956X regardless of
+ * the presence of either Ethernet PHY, the Ethernet functions are
+ * treated as two parts: a PCIe function; and a Synopsys XGMAC component.
+ * The PCIe function has access to the BARs used by the XGMAC, and maps
+ * them for use. Each XGMAP is treated as an auxiliary sub-device of
+ * its (parent) PCIe function, and is probed and bound separate from it.
+ *
+ * This PCI driver binds to the Toshiba TC956X (physical) PCI function
+ * (VID 0x1179, DID 0x0220). There are two of these present on the
+ * TC956X SoC. This driver maps the PCI BARs and performs other initial
+ * setup, then creates auxiliary devices.
+ *
+ * Embedded PCI function 0 manages non-MAC functionality. This includes
+ * creating and registering the GPIO auxiliary device (if necessary), as
+ * well as asserting and deasserting internal reset signals and enabling
+ * and disabling internal clocks.
+ *
+ * Both PCI functions create auxiliary devices to implement an Ethernet
+ * XGMAC. A block of data (struct tc956x_dwmac_data) is shared using
+ * the auxiliary device's platform data with the stmmac driver that
+ * binds to the XGMAC auxiliary device. This includes a number of
+ * pointers to memory regions used by the stmmac driver.
+ */
+
+#include <linux/auxiliary_bus.h>
+#include <linux/compiler_types.h>
+#include <linux/device.h>
+#include <linux/dev_printk.h>
+#include <linux/init.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/pci.h>
+#include <linux/property.h>
+#include <linux/regmap.h>
+#include <linux/types.h>
+
+#include <soc/toshiba/tc956x-dwmac.h>
+
+#define DRIVER_NAME TC956X_PCIE_DRIVER_NAME
+
+#define GPIO_DEVICE_NAME "tc9564-gpio"
+
+#define PCI_DEVICE_ID_TOSHIBA_TC956X 0x0220
+
+/* PCI BAR assignments */
+#define PCI_BAR_BRIDGE_CONFIG 0 /* For TAMAP */
+#define PCI_BAR_SFR 4 /* For all other features */
+
+/* Chip and revision ID register */
+#define NCID_OFFSET 0x0000
+#define NCID_REV_ID_MASK GENMASK(7, 0)
+
+/* Reset and clock register offsets. MAC resets and clocks are controlled
+ * by bits in register 0 for MAC0, register 1 for MAC1. Other non-MAC
+ * resets and clocks (whose IDs are defined here) are controlled by bits
+ * in register 0.
+ *
+ * These are relative to the base of the clock/reset regmap.
+ */
+#define RSTCTRL0_OFFSET 0x0008
+#define RSTCTRL1_OFFSET 0x0010
+#define CLKCTRL0_OFFSET 0x0004
+#define CLKCTRL1_OFFSET 0x000c
+
+enum reset_id {
+ RESET_MCU = 0,
+ RESET_MCU1 = 1,
+ RESET_MSIGEN = 18,
+ RESET_INTC = 4,
+ RESET_UART0 = 16,
+};
+
+enum clock_id {
+ CLOCK_MCU = 0,
+ CLOCK_SRAM = 13,
+ CLOCK_MSIGEN = 18,
+ CLOCK_PLL = 24,
+ CLOCK_SGMII = 25,
+ CLOCK_REFCLK = 26,
+ CLOCK_INTC = 4,
+ CLOCK_UART0 = 16,
+};
+
+/*
+ * The TAMAP function has four AXI translation tables each with eight
+ * 4-byte registers. The Ethernet MAC accesses PCI resources through
+ * addressses based at TC956X_SLV00_SRC_ADDR, and the first translation
+ * table converts those to PCIe address space starting based at 0x0.
+ * We don't use the other three available TAMAC tables.
+ */
+#define ATR_AXI4_SLV0_OFFSET 0x0800
+#define AXI4_TABLE_ENTRY_COUNT 4
+#define AXI4_ENTRY_BASE(id) ((id) * AXI4_TABLE_STRIDE)
+#define AXI4_TABLE_STRIDE 0x20
+
+/* Address translation space parameters used for entry 0 */
+#define SLV00_ATR_SIZE 35 /* 2^36 (64 gigabytes) */
+/* TC956X_SLV00_SRC_ADDR is the source address, defined in the common header */
+#define SLV00_TRSL_ADDR 0x0000000000000000ULL
+
+/* Translation entry registers, fields, and values used */
+#define SRC_ADDR_LO_OFFSET 0x0000
+#define ATR_IMPL BIT(0) /* 1 = enabled */
+#define ATR_SIZE_MASK GENMASK(6, 1) /* size 2^(ATR + 1) */
+#define SRC_ADDR_HI_OFFSET 0x0004
+#define TRSL_ADDR_LO_OFFSET 0x0008
+#define TRSL_ADDR_HI_OFFSET 0x000c
+#define TRSL_PARAM_OFFSET 0x0010
+#define TRSL_ID_MASK GENMASK(3, 0)
+#define TRSL_ID_PCIE_TX_RX 0
+#define TRSL_PARAM_MASK GENMASK(27, 16)
+
+/*
+ * The TC956X implements an "SFR" address space, which provides access
+ * to *all* internal IP block registers, both MAC and non-MAC. This
+ * space is also accessible via an I2C interface used by the PCI pwrctl
+ * driver (in "pci-pwrctrl-tc9563.c"), though that driver accesses the
+ * range in a very limited way. For the MAC functions we divide up the
+ * range, providing specific addresses needed by the stmmac driver.
+ */
+#define EMAC_CTL_OFFSET(_mac_id) ((_mac_id) ? 0x1074 : 0x1070)
+#define MSIGEN_OFFSET(_mac_id) ((_mac_id) ? 0xf100 : 0xf000)
+#define DWMAC_OFFSET(_mac_id) ((_mac_id) ? 0x48000 : 0x40000)
+
+/*
+ * struct tc956x_chip - Common information related to the TC956X chip
+ * @dev: Device structure for function 0
+ * @sfr: Mapped SFR regions (BAR 4, one per PCI function)
+ * @bridge_config: Regmap used for bridge configuration (BAR 0)
+ * @reset_clock_regmap: Regmap used for resets and clocks
+ * @rev_id: Chip revision ID (for quirks)
+ */
+struct tc956x_chip {
+ struct device *dev;
+ void __iomem *sfr[2];
+ void __iomem *bridge_config;
+ struct regmap *reset_clock_regmap;
+ u8 rev_id;
+};
+
+static const struct regmap_config gpio_regmap_config = {
+ .name = "tc956x-gpio",
+ .reg_bits = 32,
+ .reg_stride = 4,
+ .reg_base = 0x1200, /* Register GPIOI0 */
+ .val_bits = 32,
+ .max_register = 0x1214, /* Register GPIOO1 */
+};
+
+static const struct regmap_config reset_clock_regmap_config = {
+ .name = "tc956x-clk-reset",
+ .reg_bits = 32,
+ .reg_stride = 4,
+ .reg_base = 0x1000, /* Register NCTLSTS */
+ .val_bits = 32,
+ .max_register = 0x1010, /* Register NRSTCTRL1 */
+};
+
+/* Common clock/reset register update function (also used for MACs) */
+void tc956x_reset_clock_set(const struct tc956x_chip *chip, bool reset,
+ bool reg0, bool set, u8 bit)
+{
+ u32 mask = BIT(bit);
+ u32 offset;
+
+ if (reset)
+ offset = reg0 ? RSTCTRL0_OFFSET : RSTCTRL1_OFFSET;
+ else
+ offset = reg0 ? CLKCTRL0_OFFSET : CLKCTRL1_OFFSET;
+
+ /* Note: no need to check for errors on read/write for MMIO regmap */
+ (void)regmap_update_bits(chip->reset_clock_regmap, offset, mask,
+ set ? mask : 0);
+}
+EXPORT_SYMBOL_GPL(tc956x_reset_clock_set);
+
+static inline void chip_reset_assert(const struct tc956x_chip *chip,
+ enum reset_id id)
+{
+ tc956x_reset_clock_set(chip, true, true, true, (u8)id);
+}
+
+static inline void chip_reset_deassert(const struct tc956x_chip *chip,
+ enum reset_id id)
+{
+ tc956x_reset_clock_set(chip, true, true, false, (u8)id);
+}
+
+static inline void chip_clock_enable(const struct tc956x_chip *chip,
+ enum clock_id id)
+{
+ tc956x_reset_clock_set(chip, false, true, true, (u8)id);
+}
+
+static inline void chip_clock_disable(const struct tc956x_chip *chip,
+ enum clock_id id)
+{
+ tc956x_reset_clock_set(chip, false, true, false, (u8)id);
+}
+
+static void adev_release(struct device *dev)
+{
+ struct auxiliary_device *adev = to_auxiliary_dev(dev);
+
+ of_node_put(adev->dev.of_node);
+ kfree(adev);
+}
+
+static void adev_remove(void *data)
+{
+ struct auxiliary_device *adev = data;
+
+ auxiliary_device_delete(adev);
+ auxiliary_device_uninit(adev);
+}
+
+static int adev_device_add(struct device *dev, const char *name, u32 id,
+ void *platform_data)
+{
+ struct auxiliary_device *adev;
+ int ret;
+
+ adev = kzalloc_obj(*adev);
+ if (!adev)
+ return -ENOMEM;
+
+ adev->id = id;
+ adev->name = name;
+ adev->dev.parent = dev;
+ adev->dev.platform_data = platform_data;
+ adev->dev.release = adev_release;
+ device_set_of_node_from_dev(&adev->dev, dev);
+
+ ret = auxiliary_device_init(adev);
+ if (ret) {
+ of_node_put(adev->dev.of_node);
+ kfree(adev);
+ return ret;
+ }
+
+ ret = auxiliary_device_add(adev);
+ if (ret) {
+ auxiliary_device_uninit(adev);
+ return ret;
+ }
+
+ return devm_add_action_or_reset(dev, adev_remove, adev);
+}
+
+/* The embedded GPIO controller has an auxiliary device driver */
+static int chip_gpio_adev_add(struct tc956x_chip *chip)
+{
+ struct device *dev = chip->dev;
+ struct regmap *regmap;
+
+ /* If needed, PCIe function 0 implements the GPIO controller. */
+ if (!device_property_present(dev, "gpio-controller"))
+ return 0;
+
+ regmap = devm_regmap_init_mmio(dev, chip->sfr[0], &gpio_regmap_config);
+ if (IS_ERR(regmap))
+ return PTR_ERR(regmap);
+
+ return adev_device_add(dev, GPIO_DEVICE_NAME, 0, regmap);
+}
+
+/* The two embedded XGMAC controllers have an auxiliary device driver */
+static int function_xgmac_adev_add(struct pci_dev *pdev,
+ struct tc956x_chip *chip,
+ unsigned int msigen_irq)
+{
+ u8 mac_id = PCI_FUNC(pdev->devfn);
+ struct device *dev = &pdev->dev;
+ struct tc956x_dwmac_data *data;
+ void __iomem *sfr;
+ int ret;
+
+ if (mac_id > 1)
+ return -EINVAL;
+ sfr = chip->sfr[mac_id];
+
+ data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ data->chip = chip;
+ data->msigen = sfr + MSIGEN_OFFSET(mac_id);
+ data->msigen_irq = msigen_irq;
+ data->emac = sfr + DWMAC_OFFSET(mac_id);
+ data->emac_ctl = sfr + EMAC_CTL_OFFSET(mac_id);
+ data->rev_id = chip->rev_id;
+ data->mac_id = mac_id;
+
+ ret = adev_device_add(dev, TC956X_XGMAC_DEV_NAME, mac_id, data);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static int chip_reset_clock_init(struct tc956x_chip *chip)
+{
+ void __iomem *base = chip->sfr[0];
+ struct device *dev = chip->dev;
+ struct regmap *regmap;
+
+ regmap = devm_regmap_init_mmio(dev, base, &reset_clock_regmap_config);
+ if (IS_ERR(regmap))
+ return PTR_ERR(regmap);
+ chip->reset_clock_regmap = regmap;
+
+ return 0;
+}
+
+static int chip_tamap_init(struct tc956x_chip *chip, struct pci_dev *pdev)
+{
+ void __iomem *base;
+
+ base = pcim_iomap_region(pdev, PCI_BAR_BRIDGE_CONFIG, DRIVER_NAME);
+ if (IS_ERR(base))
+ return PTR_ERR(base);
+
+ chip->bridge_config = base + ATR_AXI4_SLV0_OFFSET;
+
+ return 0;
+}
+
+/**
+ * chip_tamap_config() - Configure the table address map registers
+ * @chip: The TC956X chip pointer
+ *
+ * Populate the registers used to translate AXI bus accesses to PCI TLPs.
+ * TC956X_SLV00_SRC_ADDR defines the base address of the AXI address range.
+ * AXI addresses are translated to the PCIe address range, whose base address
+ * is defined by SLV00_TRSL_ADDR (which is 0x0).
+ */
+static void chip_tamap_config(struct tc956x_chip *chip)
+{
+ void __iomem *table_base = chip->bridge_config;
+ void __iomem *entry_base;
+ u32 trsl_param_val;
+ u32 atr_size_val;
+ u32 val;
+ u32 i;
+
+ /*
+ * The lower bits of the source address must be zero, because the
+ * SRC_ADDR_LO register encodes the address translation space size
+ * and "implmented" bit there. The size field defines the size of
+ * the translation space (2^(ATR_SIZE + 1)). The minimum size is
+ * 4096 bytes, so ATR_SIZE value must be 11 or more.
+ */
+ BUILD_BUG_ON(!!u32_get_bits(lower_32_bits(TC956X_SLV00_SRC_ADDR),
+ ATR_SIZE_MASK));
+ BUILD_BUG_ON(TC956X_SLV00_SRC_ADDR & ATR_IMPL);
+ BUILD_BUG_ON(SLV00_ATR_SIZE < 11);
+
+ /*
+ * We only use the first AXI4 slave TAMAC table:
+ * EDMA address region: 0x10 0000 0000 - 0x1f ffff ffff
+ * is translated to: 0x00 0000 0000 - 0x0f ffff ffff
+ */
+ entry_base = table_base + AXI4_ENTRY_BASE(0);
+
+ atr_size_val = u32_encode_bits(SLV00_ATR_SIZE, ATR_SIZE_MASK);
+ atr_size_val |= ATR_IMPL;
+ val = lower_32_bits(TC956X_SLV00_SRC_ADDR) | atr_size_val;
+ writel(val, entry_base + SRC_ADDR_LO_OFFSET);
+
+ val = upper_32_bits(TC956X_SLV00_SRC_ADDR);
+ writel(val, entry_base + SRC_ADDR_HI_OFFSET);
+
+ val = lower_32_bits(SLV00_TRSL_ADDR);
+ writel(val, entry_base + TRSL_ADDR_LO_OFFSET);
+
+ val = upper_32_bits(SLV00_TRSL_ADDR);
+ writel(val, entry_base + TRSL_ADDR_HI_OFFSET);
+
+ /* This TRSL_PARAM value is assigned for all four TAMAC tables */
+ trsl_param_val = u32_encode_bits(TRSL_ID_PCIE_TX_RX, TRSL_ID_MASK);
+
+ writel(trsl_param_val, entry_base + TRSL_PARAM_OFFSET);
+
+ /* Set all other unused entries to default values (no translation) */
+ for (i = 1; i < AXI4_TABLE_ENTRY_COUNT; i++) {
+ entry_base = table_base + AXI4_ENTRY_BASE(i);
+
+ writel(0x0, entry_base + SRC_ADDR_LO_OFFSET);
+ writel(0x0, entry_base + SRC_ADDR_HI_OFFSET);
+ writel(0x0, entry_base + TRSL_ADDR_LO_OFFSET);
+ writel(0x0, entry_base + TRSL_ADDR_HI_OFFSET);
+ writel(trsl_param_val, entry_base + TRSL_PARAM_OFFSET);
+ }
+}
+
+static void chip_msigen_enable(struct tc956x_chip *chip)
+{
+ chip_clock_enable(chip, CLOCK_MSIGEN);
+ chip_reset_deassert(chip, RESET_MSIGEN);
+}
+
+static void chip_msigen_disable(struct tc956x_chip *chip)
+{
+ chip_reset_assert(chip, RESET_MSIGEN);
+ chip_clock_disable(chip, CLOCK_MSIGEN);
+}
+
+static void chip_init_state(struct tc956x_chip *chip)
+{
+ /* The only IP block we currently use is MSIGEN */
+ chip_reset_assert(chip, RESET_MCU);
+ chip_reset_assert(chip, RESET_MCU1);
+ chip_reset_assert(chip, RESET_INTC);
+ chip_reset_assert(chip, RESET_UART0);
+ chip_clock_disable(chip, CLOCK_MCU);
+ chip_clock_disable(chip, CLOCK_SRAM);
+ chip_clock_disable(chip, CLOCK_PLL);
+ chip_clock_disable(chip, CLOCK_SGMII);
+ chip_clock_disable(chip, CLOCK_REFCLK);
+ chip_clock_disable(chip, CLOCK_INTC);
+ chip_clock_disable(chip, CLOCK_UART0);
+
+ /* Start with MSIGEN in reset with its clock disabled */
+ chip_msigen_disable(chip);
+}
+
+static void chip_link_del(void *data)
+{
+ struct device_link *link = data;
+
+ device_link_del(link);
+}
+
+/*
+ * Function 0 will allocate the chip structure that is shared by both
+ * functions. Once it has allocated the structure it assigns it as
+ * the PCI device platform data. Function 1 can access the shared
+ * chip structure by looking up the function 0 device to use its
+ * platform data..
+ *
+ * Returns a chip structure pointer, or a pointer-coded error.
+ */
+static struct tc956x_chip *chip_get(struct pci_dev *pdev)
+{
+ unsigned int devfn = pdev->devfn;
+ struct device *dev = &pdev->dev;
+ struct tc956x_chip *chip;
+ struct device_link *link;
+ struct pci_dev *peer;
+ int ret;
+
+ /* Function 0 just allocates the chip structure */
+ if (!PCI_FUNC(devfn)) {
+ chip = devm_kzalloc(dev, sizeof(*chip), GFP_KERNEL);
+ if (!chip)
+ return ERR_PTR(-ENOMEM);
+
+ /*
+ * The function whose device pointer matches the chip's
+ * device pointer manages common resources (like MSIGEN).
+ */
+ chip->dev = dev;
+
+ return chip;
+ }
+
+ /* Function 1 has to get the chip structure from function 0 */
+ peer = pci_get_slot(pdev->bus, PCI_DEVFN(PCI_SLOT(devfn), 0));
+ if (!peer)
+ return ERR_PTR(-ENXIO);
+
+ /* If function 0 hasn't set up the chip yet, try again later */
+ chip = dev_get_platdata(&peer->dev);
+ if (!chip)
+ return ERR_PTR(-EPROBE_DEFER);
+
+ /* Mark function 1's device as dependent on function 0 */
+ link = device_link_add(dev, &peer->dev, DL_FLAG_STATELESS);
+ if (!link)
+ return ERR_PTR(-ENODEV);
+
+ ret = devm_add_action_or_reset(&peer->dev, chip_link_del, link);
+ if (ret)
+ return ERR_PTR(ret);
+
+ return chip;
+}
+
+static int chip_init(struct tc956x_chip *chip, struct pci_dev *pdev)
+{
+ u32 id = PCI_FUNC(pdev->devfn) ? 1 : 0;
+ u32 val;
+ int ret;
+
+ /* Both chips need to map their SFR region */
+ chip->sfr[id] = pcim_iomap_region(pdev, PCI_BAR_SFR, DRIVER_NAME);
+ if (IS_ERR(chip->sfr[id]))
+ return PTR_ERR(chip->sfr[id]);
+
+ /* Function 0 handles common initialization */
+ if (id)
+ return 0;
+
+ ret = chip_tamap_init(chip, pdev);
+ if (ret)
+ return ret;
+
+ ret = chip_reset_clock_init(chip);
+ if (ret)
+ return ret;
+
+ chip_init_state(chip);
+
+ ret = chip_gpio_adev_add(chip);
+ if (ret)
+ return ret;
+
+ /* Get the revision ID */
+ val = readl(chip->sfr[0] + NCID_OFFSET);
+ chip->rev_id = u32_get_bits(val, NCID_REV_ID_MASK);
+
+ chip_tamap_config(chip);
+ chip_msigen_enable(chip);
+
+ return 0;
+}
+
+static int
+tc956x_function_probe(struct pci_dev *pdev, const struct pci_device_id *id)
+{
+ struct device *dev = &pdev->dev;
+ struct tc956x_chip *chip;
+ unsigned int msigen_irq;
+ int ret;
+
+ /* Despite being a PCI device, we require devicetree */
+ if (!dev->of_node)
+ return -EINVAL;
+
+ ret = pcim_enable_device(pdev);
+ if (ret)
+ return ret;
+
+ pci_set_master(pdev);
+
+ /* Function 1 gets -EPROBE_DEFER until function 0 sets platform data */
+ chip = chip_get(pdev);
+ if (IS_ERR(chip))
+ return dev_err_probe(dev, PTR_ERR(chip), "failed to get chip\n");
+
+ /* We called pcim_enable_device() so this will be freed automatically */
+ ret = pci_alloc_irq_vectors(pdev, 1, 1, PCI_IRQ_MSI);
+ if (ret < 1)
+ return dev_err_probe(dev, ret ? : -EIO,
+ "failed to allocate IRQ vectors\n");
+
+ ret = pci_irq_vector(pdev, 0);
+ if (ret < 1)
+ return dev_err_probe(dev, ret ? : -EIO, "failed to get IRQ\n");
+ msigen_irq = ret;
+
+ ret = chip_init(chip, pdev);
+ if (ret)
+ return dev_err_probe(dev, ret, "failed to initialize chip\n");
+
+ /* We're ready; the other function can now probe */
+ dev->platform_data = chip;
+
+ ret = function_xgmac_adev_add(pdev, chip, msigen_irq);
+ if (ret)
+ return dev_err_probe(dev, ret, "failed to add xgmap device\n");
+
+ return 0;
+}
+
+static void tc956x_function_remove(struct pci_dev *pdev)
+{
+ struct tc956x_chip *chip = dev_get_platdata(&pdev->dev);
+
+ pci_clear_master(pdev);
+
+ if (&pdev->dev == chip->dev)
+ chip_msigen_disable(chip);
+}
+
+static const struct pci_device_id tc956x_function_id_table[] = {
+ { PCI_DEVICE(PCI_VENDOR_ID_TOSHIBA, PCI_DEVICE_ID_TOSHIBA_TC956X), },
+ { },
+};
+MODULE_DEVICE_TABLE(pci, tc956x_function_id_table);
+
+static int tc956x_chip_suspend_noirq(struct device *dev)
+{
+ struct tc956x_chip *chip = dev_get_platdata(dev);
+ struct pci_dev *pdev = to_pci_dev(dev);
+
+ if (dev == chip->dev)
+ chip_msigen_disable(chip);
+
+ /* It seems most callers ignore the return value here */
+ pci_save_state(pdev);
+ pci_wake_from_d3(pdev, true);
+
+ return 0;
+}
+
+static int tc956x_chip_resume_noirq(struct device *dev)
+{
+ struct tc956x_chip *chip = dev_get_platdata(dev);
+ struct pci_dev *pdev = to_pci_dev(dev);
+
+ pci_wake_from_d3(pdev, false);
+ pci_set_power_state(pdev, PCI_D0);
+ pci_restore_state(pdev);
+
+ if (dev != chip->dev)
+ return 0;
+
+ /* Reconfigure tamap tables following suspend */
+ chip_tamap_config(chip);
+
+ chip_msigen_enable(chip);
+
+ return 0;
+}
+
+static DEFINE_NOIRQ_DEV_PM_OPS(tc956x_chip_pm_ops,
+ tc956x_chip_suspend_noirq,
+ tc956x_chip_resume_noirq);
+
+static struct pci_driver tc956x_function_driver = {
+ .name = DRIVER_NAME,
+ .id_table = tc956x_function_id_table,
+ .probe = tc956x_function_probe,
+ .remove = tc956x_function_remove,
+ .driver = {
+ .name = DRIVER_NAME,
+ .owner = THIS_MODULE,
+ .pm = pm_sleep_ptr(&tc956x_chip_pm_ops),
+ },
+};
+
+module_pci_driver(tc956x_function_driver);
+
+MODULE_DESCRIPTION("Toshiba TC956X PCIe Embedded Function Driver");
+MODULE_LICENSE("GPL");
--
2.51.0
^ permalink raw reply related [flat|nested] 37+ messages in thread* Re: [PATCH net-next 11/12] misc: tc956x_pci: add TC956x/QPS615 support
2026-05-01 15:54 ` [PATCH net-next 11/12] misc: tc956x_pci: " Alex Elder
@ 2026-05-01 21:07 ` Andrew Lunn
2026-05-03 2:06 ` Alex Elder
2026-05-02 16:45 ` Jakub Kicinski
1 sibling, 1 reply; 37+ messages in thread
From: Andrew Lunn @ 2026-05-01 21:07 UTC (permalink / raw)
To: Alex Elder
Cc: andrew+netdev, davem, edumazet, kuba, pabeni, maxime.chevallier,
rmk+kernel, andersson, konradybcio, robh, krzk+dt, conor+dt,
linusw, brgl, arnd, gregkh, daniel, mohd.anwar, a0987203069,
alexandre.torgue, ast, boon.khai.ng, chenchuangyu, chenhuacai,
daniel, hawk, hkallweit1, inochiama, john.fastabend, julianbraha,
livelycarpet87, matthew.gerlach, mcoquelin.stm32, me,
prabhakar.mahadev-lad.rj, richardcochran, rohan.g.thomas, sdf,
siyanteng, weishangjuan, wens, netdev, bpf, linux-arm-msm,
devicetree, linux-gpio, linux-stm32, linux-arm-kernel,
linux-kernel
> diff --git a/drivers/misc/tc956x_pci.c b/drivers/misc/tc956x_pci.c
> +static inline void chip_reset_assert(const struct tc956x_chip *chip,
> + enum reset_id id)
> +{
> + tc956x_reset_clock_set(chip, true, true, true, (u8)id);
> +}
This is in drivers/misc, where the rules might be different. But in
netdev, we don't like inline functions in .c files. It is better to
let the compiler decide.
> +static void chip_init_state(struct tc956x_chip *chip)
> +{
> + /* The only IP block we currently use is MSIGEN */
> + chip_reset_assert(chip, RESET_MCU);
> + chip_reset_assert(chip, RESET_MCU1);
> + chip_reset_assert(chip, RESET_INTC);
> + chip_reset_assert(chip, RESET_UART0);
> + chip_clock_disable(chip, CLOCK_MCU);
> + chip_clock_disable(chip, CLOCK_SRAM);
> + chip_clock_disable(chip, CLOCK_PLL);
> + chip_clock_disable(chip, CLOCK_SGMII);
With my networking hat on, this one standard out.
> + chip_clock_disable(chip, CLOCK_REFCLK);
The name REFCLK is sometimes used as for the clock signals for RGMII?
> +static int
> +tc956x_function_probe(struct pci_dev *pdev, const struct pci_device_id *id)
> +{
> + struct device *dev = &pdev->dev;
> + struct tc956x_chip *chip;
> + unsigned int msigen_irq;
> + int ret;
> +
> + /* Despite being a PCI device, we require devicetree */
> + if (!dev->of_node)
> + return -EINVAL;
Might be worth a dev_err(), since it is unusual.
Andrew
^ permalink raw reply [flat|nested] 37+ messages in thread* Re: [PATCH net-next 11/12] misc: tc956x_pci: add TC956x/QPS615 support
2026-05-01 21:07 ` Andrew Lunn
@ 2026-05-03 2:06 ` Alex Elder
0 siblings, 0 replies; 37+ messages in thread
From: Alex Elder @ 2026-05-03 2:06 UTC (permalink / raw)
To: Andrew Lunn
Cc: andrew+netdev, davem, edumazet, kuba, pabeni, maxime.chevallier,
rmk+kernel, andersson, konradybcio, robh, krzk+dt, conor+dt,
linusw, brgl, arnd, gregkh, daniel, mohd.anwar, a0987203069,
alexandre.torgue, ast, boon.khai.ng, chenchuangyu, chenhuacai,
daniel, hawk, hkallweit1, inochiama, john.fastabend, julianbraha,
livelycarpet87, matthew.gerlach, mcoquelin.stm32, me,
prabhakar.mahadev-lad.rj, richardcochran, rohan.g.thomas, sdf,
siyanteng, weishangjuan, wens, netdev, bpf, linux-arm-msm,
devicetree, linux-gpio, linux-stm32, linux-arm-kernel,
linux-kernel
On 5/1/26 4:07 PM, Andrew Lunn wrote:
>> diff --git a/drivers/misc/tc956x_pci.c b/drivers/misc/tc956x_pci.c
>
>> +static inline void chip_reset_assert(const struct tc956x_chip *chip,
>> + enum reset_id id)
>> +{
>> + tc956x_reset_clock_set(chip, true, true, true, (u8)id);
>> +}
>
> This is in drivers/misc, where the rules might be different. But in
> netdev, we don't like inline functions in .c files. It is better to
> let the compiler decide.
That was a mistake. I agree with that perspective. These functions
were moved out of the header file because they were only used here.
And in the process, I neglected to drop the inline. Will fix.
>> +static void chip_init_state(struct tc956x_chip *chip)
>> +{
>> + /* The only IP block we currently use is MSIGEN */
>> + chip_reset_assert(chip, RESET_MCU);
>> + chip_reset_assert(chip, RESET_MCU1);
>> + chip_reset_assert(chip, RESET_INTC);
>> + chip_reset_assert(chip, RESET_UART0);
>> + chip_clock_disable(chip, CLOCK_MCU);
>> + chip_clock_disable(chip, CLOCK_SRAM);
>> + chip_clock_disable(chip, CLOCK_PLL);
>> + chip_clock_disable(chip, CLOCK_SGMII);
>
> With my networking hat on, this one standard out.
>
>> + chip_clock_disable(chip, CLOCK_REFCLK);
>
> The name REFCLK is sometimes used as for the clock signals for RGMII?
You're saying that the REFCLK disable stood out, and you want to
understand what "REFCLK" actually represents?
I believe this is an *output* reference clock signal generated by the
TC9564. Looking at the schematic for the RB3gen2 it leads only to
a test point.
However I want to compare notes with Daniel on Monday about this.
Would it draw less attention if it were named "REFCLKO"?
In any case we can add some reassuring comments.
>
>> +static int
>> +tc956x_function_probe(struct pci_dev *pdev, const struct pci_device_id *id)
>> +{
>> + struct device *dev = &pdev->dev;
>> + struct tc956x_chip *chip;
>> + unsigned int msigen_irq;
>> + int ret;
>> +
>> + /* Despite being a PCI device, we require devicetree */
>> + if (!dev->of_node)
>> + return -EINVAL;
>
> Might be worth a dev_err(), since it is unusual.
Good suggestion. I'll add that.
Thanks a lot for your review.
-Alex
>
> Andrew
^ permalink raw reply [flat|nested] 37+ messages in thread
* Re: [PATCH net-next 11/12] misc: tc956x_pci: add TC956x/QPS615 support
2026-05-01 15:54 ` [PATCH net-next 11/12] misc: tc956x_pci: " Alex Elder
2026-05-01 21:07 ` Andrew Lunn
@ 2026-05-02 16:45 ` Jakub Kicinski
2026-05-03 2:06 ` Alex Elder
1 sibling, 1 reply; 37+ messages in thread
From: Jakub Kicinski @ 2026-05-02 16:45 UTC (permalink / raw)
To: Alex Elder
Cc: andrew+netdev, davem, edumazet, pabeni, maxime.chevallier,
rmk+kernel, andersson, konradybcio, robh, krzk+dt, conor+dt,
linusw, brgl, arnd, gregkh, daniel, mohd.anwar, a0987203069,
alexandre.torgue, ast, boon.khai.ng, chenchuangyu, chenhuacai,
daniel, hawk, hkallweit1, inochiama, john.fastabend, julianbraha,
livelycarpet87, matthew.gerlach, mcoquelin.stm32, me,
prabhakar.mahadev-lad.rj, richardcochran, rohan.g.thomas, sdf,
siyanteng, weishangjuan, wens, netdev, bpf, linux-arm-msm,
devicetree, linux-gpio, linux-stm32, linux-arm-kernel,
linux-kernel
On Fri, 1 May 2026 10:54:19 -0500 Alex Elder wrote:
> The Toshiba TC956x is an Ethernet AVB/TSN bridge, and is
> essentially a small and highly-specialized SoC. It implements
> a number of internal functions, including a GPIO controller,
> control registers managing internal reset and clock control
> signals, a PCIe switch and internal endpoint, and mapping
> hardware that translates between PCIe and internal addressing.
drivers/misc/tc956x_pci.c:541:17: error: call to undeclared function 'u32_get_bits'; ISO C99 and later do not support implicit function declarations [-Wimplicit-function-declaration]
541 | chip->rev_id = u32_get_bits(val, NCID_REV_ID_MASK);
| ^
--
pw-bot: cr
^ permalink raw reply [flat|nested] 37+ messages in thread* Re: [PATCH net-next 11/12] misc: tc956x_pci: add TC956x/QPS615 support
2026-05-02 16:45 ` Jakub Kicinski
@ 2026-05-03 2:06 ` Alex Elder
2026-05-03 2:14 ` Jakub Kicinski
0 siblings, 1 reply; 37+ messages in thread
From: Alex Elder @ 2026-05-03 2:06 UTC (permalink / raw)
To: Jakub Kicinski
Cc: andrew+netdev, davem, edumazet, pabeni, maxime.chevallier,
rmk+kernel, andersson, konradybcio, robh, krzk+dt, conor+dt,
linusw, brgl, arnd, gregkh, daniel, mohd.anwar, a0987203069,
alexandre.torgue, ast, boon.khai.ng, chenchuangyu, chenhuacai,
daniel, hawk, hkallweit1, inochiama, john.fastabend, julianbraha,
livelycarpet87, matthew.gerlach, mcoquelin.stm32, me,
prabhakar.mahadev-lad.rj, richardcochran, rohan.g.thomas, sdf,
siyanteng, weishangjuan, wens, netdev, bpf, linux-arm-msm,
devicetree, linux-gpio, linux-stm32, linux-arm-kernel,
linux-kernel
On 5/2/26 11:45 AM, Jakub Kicinski wrote:
> On Fri, 1 May 2026 10:54:19 -0500 Alex Elder wrote:
>> The Toshiba TC956x is an Ethernet AVB/TSN bridge, and is
>> essentially a small and highly-specialized SoC. It implements
>> a number of internal functions, including a GPIO controller,
>> control registers managing internal reset and clock control
>> signals, a PCIe switch and internal endpoint, and mapping
>> hardware that translates between PCIe and internal addressing.
>
> drivers/misc/tc956x_pci.c:541:17: error: call to undeclared function 'u32_get_bits'; ISO C99 and later do not support implicit function declarations [-Wimplicit-function-declaration]
> 541 | chip->rev_id = u32_get_bits(val, NCID_REV_ID_MASK);
> | ^
Yeah I think I noticed an error like that shows up with 32-bit builds?
In any case we didn't see it during development, and we'll make sure
<linux/bitfield.h> gets included.
Thanks.
-Alex
^ permalink raw reply [flat|nested] 37+ messages in thread
* Re: [PATCH net-next 11/12] misc: tc956x_pci: add TC956x/QPS615 support
2026-05-03 2:06 ` Alex Elder
@ 2026-05-03 2:14 ` Jakub Kicinski
2026-05-03 2:23 ` Alex Elder
0 siblings, 1 reply; 37+ messages in thread
From: Jakub Kicinski @ 2026-05-03 2:14 UTC (permalink / raw)
To: Alex Elder
Cc: andrew+netdev, davem, edumazet, pabeni, maxime.chevallier,
rmk+kernel, andersson, konradybcio, robh, krzk+dt, conor+dt,
linusw, brgl, arnd, gregkh, daniel, mohd.anwar, a0987203069,
alexandre.torgue, ast, boon.khai.ng, chenchuangyu, chenhuacai,
daniel, hawk, hkallweit1, inochiama, john.fastabend, julianbraha,
livelycarpet87, matthew.gerlach, mcoquelin.stm32, me,
prabhakar.mahadev-lad.rj, richardcochran, rohan.g.thomas, sdf,
siyanteng, weishangjuan, wens, netdev, bpf, linux-arm-msm,
devicetree, linux-gpio, linux-stm32, linux-arm-kernel,
linux-kernel
On Sat, 2 May 2026 21:06:33 -0500 Alex Elder wrote:
> > drivers/misc/tc956x_pci.c:541:17: error: call to undeclared function 'u32_get_bits'; ISO C99 and later do not support implicit function declarations [-Wimplicit-function-declaration]
> > 541 | chip->rev_id = u32_get_bits(val, NCID_REV_ID_MASK);
> > | ^
>
> Yeah I think I noticed an error like that shows up with 32-bit builds?
> In any case we didn't see it during development, and we'll make sure
> <linux/bitfield.h> gets included.
on x86 it hits on all configs, I suspected you're building for ARM?
^ permalink raw reply [flat|nested] 37+ messages in thread
* Re: [PATCH net-next 11/12] misc: tc956x_pci: add TC956x/QPS615 support
2026-05-03 2:14 ` Jakub Kicinski
@ 2026-05-03 2:23 ` Alex Elder
0 siblings, 0 replies; 37+ messages in thread
From: Alex Elder @ 2026-05-03 2:23 UTC (permalink / raw)
To: Jakub Kicinski
Cc: andrew+netdev, davem, edumazet, pabeni, maxime.chevallier,
rmk+kernel, andersson, konradybcio, robh, krzk+dt, conor+dt,
linusw, brgl, arnd, gregkh, daniel, mohd.anwar, a0987203069,
alexandre.torgue, ast, boon.khai.ng, chenchuangyu, chenhuacai,
daniel, hawk, hkallweit1, inochiama, john.fastabend, julianbraha,
livelycarpet87, matthew.gerlach, mcoquelin.stm32, me,
prabhakar.mahadev-lad.rj, richardcochran, rohan.g.thomas, sdf,
siyanteng, weishangjuan, wens, netdev, bpf, linux-arm-msm,
devicetree, linux-gpio, linux-stm32, linux-arm-kernel,
linux-kernel
On 5/2/26 9:14 PM, Jakub Kicinski wrote:
> On Sat, 2 May 2026 21:06:33 -0500 Alex Elder wrote:
>>> drivers/misc/tc956x_pci.c:541:17: error: call to undeclared function 'u32_get_bits'; ISO C99 and later do not support implicit function declarations [-Wimplicit-function-declaration]
>>> 541 | chip->rev_id = u32_get_bits(val, NCID_REV_ID_MASK);
>>> | ^
>>
>> Yeah I think I noticed an error like that shows up with 32-bit builds?
>> In any case we didn't see it during development, and we'll make sure
>> <linux/bitfield.h> gets included.
>
> on x86 it hits on all configs, I suspected you're building for ARM?
Yes. I'll spend a little more time trying to build on other
architectures. I normally like to add COMPILE_TEST.
-Alex
^ permalink raw reply [flat|nested] 37+ messages in thread
* [PATCH net-next 12/12] arm64: dts: qcom: qcs6490-rb3gen2: enable TC9564 with a single QCS8081 phy
2026-05-01 15:54 [PATCH net-next 00/12] net: enable TC956x support Alex Elder
` (10 preceding siblings ...)
2026-05-01 15:54 ` [PATCH net-next 11/12] misc: tc956x_pci: " Alex Elder
@ 2026-05-01 15:54 ` Alex Elder
2026-05-01 21:09 ` Andrew Lunn
2026-05-02 16:47 ` [PATCH net-next 00/12] net: enable TC956x support Jakub Kicinski
12 siblings, 1 reply; 37+ messages in thread
From: Alex Elder @ 2026-05-01 15:54 UTC (permalink / raw)
To: andrew+netdev, davem, edumazet, kuba, pabeni, maxime.chevallier,
rmk+kernel, andersson, konradybcio, robh, krzk+dt, conor+dt,
linusw, brgl, arnd, gregkh
Cc: Daniel Thompson, elder, mohd.anwar, a0987203069, alexandre.torgue,
ast, boon.khai.ng, chenchuangyu, chenhuacai, daniel, hawk,
hkallweit1, inochiama, john.fastabend, julianbraha,
livelycarpet87, matthew.gerlach, mcoquelin.stm32, me,
prabhakar.mahadev-lad.rj, richardcochran, rohan.g.thomas, sdf,
siyanteng, weishangjuan, wens, netdev, bpf, linux-arm-msm,
devicetree, linux-gpio, linux-stm32, linux-arm-kernel,
linux-kernel
From: Daniel Thompson <daniel@riscstar.com>
The QCS6490 RB3Gen2 includes a Toshiba TC9564 (a.k.a. Qualcomm QPS615).
TC9564 is an twin Ethernet-AVB/TSN bridge with an integrated PCIe switch.
There are multiple builds of RB3Gen2 with components included/excluded.
That means whether or not there is a phy attached to eMAC0 depends on
the exact board. However all versions include a TC9564 combined with a
single QCS8081 attached to eMAC1.
Add properties to the existing PCI nodes to describe how the TC9564 and
QCS8081 are connected to each other (and to the host SoC).
(Note: "pci1179,0220" is documented in the "net/toshiba,tc956x-dwmac.yaml"
binding, but checkpatch.pl doesn't recognize that.)
Co-developed-by: Alex Elder <elder@riscstar.com>
Signed-off-by: Alex Elder <elder@riscstar.com>
Signed-off-by: Daniel Thompson <daniel@riscstar.com>
---
arch/arm64/boot/dts/qcom/qcs6490-rb3gen2.dts | 45 +++++++++++++++++++-
1 file changed, 44 insertions(+), 1 deletion(-)
diff --git a/arch/arm64/boot/dts/qcom/qcs6490-rb3gen2.dts b/arch/arm64/boot/dts/qcom/qcs6490-rb3gen2.dts
index e393ccf1884af..72c89aa7a2e43 100644
--- a/arch/arm64/boot/dts/qcom/qcs6490-rb3gen2.dts
+++ b/arch/arm64/boot/dts/qcom/qcs6490-rb3gen2.dts
@@ -308,6 +308,16 @@ vdd_ntn_1p8: regulator-vdd-ntn-1p8 {
regulator-enable-ramp-delay = <10000>;
};
+ qep_1p8: regulator-qep-1p8 {
+ compatible = "regulator-fixed";
+ regulator-name = "qep_1p8";
+ gpio = <&pm7325_gpios 8 GPIO_ACTIVE_HIGH>;
+ regulator-min-microvolt = <1800000>;
+ regulator-max-microvolt = <1800000>;
+ enable-active-high;
+ regulator-always-on;
+ };
+
wcn6750-pmu {
compatible = "qcom,wcn6750-pmu";
pinctrl-0 = <&bt_en>;
@@ -937,20 +947,46 @@ pcie@3,0 {
ranges;
bus-range = <0x5 0xff>;
- pci@0,0 {
+ tc956x_emac0: pci@0,0 {
+ compatible = "pci1179,0220";
reg = <0x50000 0x0 0x0 0x0 0x0>;
#address-cells = <3>;
#size-cells = <2>;
device_type = "pci";
ranges;
+
+ gpio-controller;
+ #gpio-cells = <2>;
};
pci@0,1 {
+ compatible = "pci1179,0220";
reg = <0x50100 0x0 0x0 0x0 0x0>;
#address-cells = <3>;
#size-cells = <2>;
device_type = "pci";
ranges;
+
+ phy-mode = "sgmii";
+ phy-handle = <&tc956x_emac1_phy>;
+
+ mdio {
+ compatible = "snps,dwmac-mdio";
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ tc956x_emac1_phy: ethernet-phy@1c {
+ compatible = "ethernet-phy-id004d.d101";
+ reg = <0x1c>;
+ reset-gpios = <&tc956x_emac0 1 GPIO_ACTIVE_LOW>;
+ reset-assert-us = <20>;
+ reset-deassert-us = <20>;
+
+ pinctrl-names = "default";
+ pinctrl-0 = <&qep_irq_pin>;
+ interrupts-extended = <&tlmm 101 IRQ_TYPE_EDGE_FALLING>;
+ };
+ };
};
};
};
@@ -1524,6 +1560,13 @@ usb_hub_reset_state: usb-hub-reset-state {
drive-strength = <2>;
bias-disable;
};
+
+ qep_irq_pin: qep-irq-state {
+ pins = "gpio101";
+ function = "gpio";
+ drive-strength = <2>;
+ bias-disable;
+ };
};
&lpass_audiocc {
--
2.51.0
^ permalink raw reply related [flat|nested] 37+ messages in thread* Re: [PATCH net-next 12/12] arm64: dts: qcom: qcs6490-rb3gen2: enable TC9564 with a single QCS8081 phy
2026-05-01 15:54 ` [PATCH net-next 12/12] arm64: dts: qcom: qcs6490-rb3gen2: enable TC9564 with a single QCS8081 phy Alex Elder
@ 2026-05-01 21:09 ` Andrew Lunn
0 siblings, 0 replies; 37+ messages in thread
From: Andrew Lunn @ 2026-05-01 21:09 UTC (permalink / raw)
To: Alex Elder
Cc: andrew+netdev, davem, edumazet, kuba, pabeni, maxime.chevallier,
rmk+kernel, andersson, konradybcio, robh, krzk+dt, conor+dt,
linusw, brgl, arnd, gregkh, Daniel Thompson, mohd.anwar,
a0987203069, alexandre.torgue, ast, boon.khai.ng, chenchuangyu,
chenhuacai, daniel, hawk, hkallweit1, inochiama, john.fastabend,
julianbraha, livelycarpet87, matthew.gerlach, mcoquelin.stm32, me,
prabhakar.mahadev-lad.rj, richardcochran, rohan.g.thomas, sdf,
siyanteng, weishangjuan, wens, netdev, bpf, linux-arm-msm,
devicetree, linux-gpio, linux-stm32, linux-arm-kernel,
linux-kernel
> + tc956x_emac1_phy: ethernet-phy@1c {
> + compatible = "ethernet-phy-id004d.d101";
> + reg = <0x1c>;
> + reset-gpios = <&tc956x_emac0 1 GPIO_ACTIVE_LOW>;
> + reset-assert-us = <20>;
> + reset-deassert-us = <20>;
> +
> + pinctrl-names = "default";
> + pinctrl-0 = <&qep_irq_pin>;
> + interrupts-extended = <&tlmm 101 IRQ_TYPE_EDGE_FALLING>;
What is probably wrong. PHY interrupts are level, not edge.
Andrew
^ permalink raw reply [flat|nested] 37+ messages in thread
* Re: [PATCH net-next 00/12] net: enable TC956x support
2026-05-01 15:54 [PATCH net-next 00/12] net: enable TC956x support Alex Elder
` (11 preceding siblings ...)
2026-05-01 15:54 ` [PATCH net-next 12/12] arm64: dts: qcom: qcs6490-rb3gen2: enable TC9564 with a single QCS8081 phy Alex Elder
@ 2026-05-02 16:47 ` Jakub Kicinski
2026-05-03 2:07 ` Alex Elder
12 siblings, 1 reply; 37+ messages in thread
From: Jakub Kicinski @ 2026-05-02 16:47 UTC (permalink / raw)
To: Alex Elder
Cc: andrew+netdev, davem, edumazet, pabeni, maxime.chevallier,
rmk+kernel, andersson, konradybcio, robh, krzk+dt, conor+dt,
linusw, brgl, arnd, gregkh, daniel, mohd.anwar, a0987203069,
alexandre.torgue, ast, boon.khai.ng, chenchuangyu, chenhuacai,
daniel, hawk, hkallweit1, inochiama, john.fastabend, julianbraha,
livelycarpet87, matthew.gerlach, mcoquelin.stm32, me,
prabhakar.mahadev-lad.rj, richardcochran, rohan.g.thomas, sdf,
siyanteng, weishangjuan, wens, netdev, bpf, linux-arm-msm,
devicetree, linux-gpio, linux-stm32, linux-arm-kernel,
linux-kernel
On Fri, 1 May 2026 10:54:08 -0500 Alex Elder wrote:
> create mode 100644 Documentation/devicetree/bindings/net/toshiba,tc956x-dwmac.yaml
> create mode 100644 drivers/gpio/gpio-tc956x.c
> create mode 100644 drivers/misc/tc956x_pci.c
> create mode 100644 drivers/net/ethernet/stmicro/stmmac/dwmac-tc956x.c
> create mode 100644 drivers/net/pcs/pcs-xpcs-regmap.c
> create mode 100644 include/linux/pcs/pcs-xpcs-regmap.h
> create mode 100644 include/soc/toshiba/tc956x-dwmac.h
Please add an entry to MAITNAINERS for tx956x stuff?
^ permalink raw reply [flat|nested] 37+ messages in thread* Re: [PATCH net-next 00/12] net: enable TC956x support
2026-05-02 16:47 ` [PATCH net-next 00/12] net: enable TC956x support Jakub Kicinski
@ 2026-05-03 2:07 ` Alex Elder
0 siblings, 0 replies; 37+ messages in thread
From: Alex Elder @ 2026-05-03 2:07 UTC (permalink / raw)
To: Jakub Kicinski
Cc: andrew+netdev, davem, edumazet, pabeni, maxime.chevallier,
rmk+kernel, andersson, konradybcio, robh, krzk+dt, conor+dt,
linusw, brgl, arnd, gregkh, daniel, mohd.anwar, a0987203069,
alexandre.torgue, ast, boon.khai.ng, chenchuangyu, chenhuacai,
daniel, hawk, hkallweit1, inochiama, john.fastabend, julianbraha,
livelycarpet87, matthew.gerlach, mcoquelin.stm32, me,
prabhakar.mahadev-lad.rj, richardcochran, rohan.g.thomas, sdf,
siyanteng, weishangjuan, wens, netdev, bpf, linux-arm-msm,
devicetree, linux-gpio, linux-stm32, linux-arm-kernel,
linux-kernel
On 5/2/26 11:47 AM, Jakub Kicinski wrote:
> On Fri, 1 May 2026 10:54:08 -0500 Alex Elder wrote:
>> create mode 100644 Documentation/devicetree/bindings/net/toshiba,tc956x-dwmac.yaml
>> create mode 100644 drivers/gpio/gpio-tc956x.c
>> create mode 100644 drivers/misc/tc956x_pci.c
>> create mode 100644 drivers/net/ethernet/stmicro/stmmac/dwmac-tc956x.c
>> create mode 100644 drivers/net/pcs/pcs-xpcs-regmap.c
>> create mode 100644 include/linux/pcs/pcs-xpcs-regmap.h
>> create mode 100644 include/soc/toshiba/tc956x-dwmac.h
>
> Please add an entry to MAITNAINERS for tx956x stuff?
OK, I'll do that for the next version of the series. Thanks.
-Alex
^ permalink raw reply [flat|nested] 37+ messages in thread