From: Alex Elder <elder@riscstar.com>
To: andrew+netdev@lunn.ch, davem@davemloft.net, edumazet@google.com,
kuba@kernel.org, pabeni@redhat.com,
maxime.chevallier@bootlin.com, rmk+kernel@armlinux.org.uk,
andersson@kernel.org, konradybcio@kernel.org, robh@kernel.org,
krzk+dt@kernel.org, conor+dt@kernel.org, linusw@kernel.org,
brgl@kernel.org, arnd@arndb.de, gregkh@linuxfoundation.org
Cc: daniel@riscstar.com, elder@riscstar.com,
mohd.anwar@oss.qualcomm.com, a0987203069@gmail.com,
alexandre.torgue@foss.st.com, ast@kernel.org,
boon.khai.ng@altera.com, chenchuangyu@xiaomi.com,
chenhuacai@kernel.org, daniel@iogearbox.net, hawk@kernel.org,
hkallweit1@gmail.com, inochiama@gmail.com,
john.fastabend@gmail.com, julianbraha@gmail.com,
livelycarpet87@gmail.com, matthew.gerlach@altera.com,
mcoquelin.stm32@gmail.com, me@ziyao.cc,
prabhakar.mahadev-lad.rj@bp.renesas.com,
richardcochran@gmail.com, rohan.g.thomas@altera.com,
sdf@fomichev.me, siyanteng@cqsoftware.com.cn,
weishangjuan@eswincomputing.com, wens@kernel.org,
netdev@vger.kernel.org, bpf@vger.kernel.org,
linux-arm-msm@vger.kernel.org, devicetree@vger.kernel.org,
linux-gpio@vger.kernel.org,
linux-stm32@st-md-mailman.stormreply.com,
linux-arm-kernel@lists.infradead.org,
linux-kernel@vger.kernel.org
Subject: [PATCH net-next 11/12] misc: tc956x_pci: add TC956x/QPS615 support
Date: Fri, 1 May 2026 10:54:19 -0500 [thread overview]
Message-ID: <20260501155421.3329862-12-elder@riscstar.com> (raw)
In-Reply-To: <20260501155421.3329862-1-elder@riscstar.com>
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
next prev parent reply other threads:[~2026-05-01 15:55 UTC|newest]
Thread overview: 27+ messages / expand[flat|nested] mbox.gz Atom feed top
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 16:50 ` Andrew Lunn
2026-05-01 18:07 ` Alex Elder
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
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
2026-05-01 20:55 ` Andrew Lunn
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
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 ` [PATCH net-next 07/12] net: stmmac: dwxgmac2: export symbols for XGMAC 3.01a DMA Alex Elder
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-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-01 15:54 ` [PATCH net-next 10/12] net: stmmac: " Alex Elder
2026-05-01 19:04 ` Andrew Lunn
2026-05-01 15:54 ` Alex Elder [this message]
2026-05-01 21:07 ` [PATCH net-next 11/12] misc: tc956x_pci: " 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-01 21:09 ` Andrew Lunn
2026-05-02 16:47 ` [PATCH net-next 00/12] net: enable TC956x support Jakub Kicinski
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260501155421.3329862-12-elder@riscstar.com \
--to=elder@riscstar.com \
--cc=a0987203069@gmail.com \
--cc=alexandre.torgue@foss.st.com \
--cc=andersson@kernel.org \
--cc=andrew+netdev@lunn.ch \
--cc=arnd@arndb.de \
--cc=ast@kernel.org \
--cc=boon.khai.ng@altera.com \
--cc=bpf@vger.kernel.org \
--cc=brgl@kernel.org \
--cc=chenchuangyu@xiaomi.com \
--cc=chenhuacai@kernel.org \
--cc=conor+dt@kernel.org \
--cc=daniel@iogearbox.net \
--cc=daniel@riscstar.com \
--cc=davem@davemloft.net \
--cc=devicetree@vger.kernel.org \
--cc=edumazet@google.com \
--cc=gregkh@linuxfoundation.org \
--cc=hawk@kernel.org \
--cc=hkallweit1@gmail.com \
--cc=inochiama@gmail.com \
--cc=john.fastabend@gmail.com \
--cc=julianbraha@gmail.com \
--cc=konradybcio@kernel.org \
--cc=krzk+dt@kernel.org \
--cc=kuba@kernel.org \
--cc=linusw@kernel.org \
--cc=linux-arm-kernel@lists.infradead.org \
--cc=linux-arm-msm@vger.kernel.org \
--cc=linux-gpio@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-stm32@st-md-mailman.stormreply.com \
--cc=livelycarpet87@gmail.com \
--cc=matthew.gerlach@altera.com \
--cc=maxime.chevallier@bootlin.com \
--cc=mcoquelin.stm32@gmail.com \
--cc=me@ziyao.cc \
--cc=mohd.anwar@oss.qualcomm.com \
--cc=netdev@vger.kernel.org \
--cc=pabeni@redhat.com \
--cc=prabhakar.mahadev-lad.rj@bp.renesas.com \
--cc=richardcochran@gmail.com \
--cc=rmk+kernel@armlinux.org.uk \
--cc=robh@kernel.org \
--cc=rohan.g.thomas@altera.com \
--cc=sdf@fomichev.me \
--cc=siyanteng@cqsoftware.com.cn \
--cc=weishangjuan@eswincomputing.com \
--cc=wens@kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox