From 23a2ecf60eb60d3b110957ea1d50ab590b4fdf77 Mon Sep 17 00:00:00 2001 From: Sai Krishna Musham Date: Tue, 18 Aug 2026 21:07:54 +0530 Subject: [PATCH 001/139] dt-bindings: PCI: xilinx-versal-cpm: Add PERST# and reset support Add the optional 'resets' property to manage the PCIe IP reset, and the 'reset-gpios' property on the Root Port child node for GPIO-based control of the PCIe RP PERST# signal (assert/deassert). Both are optional to keep backward compatibility with existing device trees. Update the examples to show the reset controller and PERST# GPIO wiring. Signed-off-by: Sai Krishna Musham Signed-off-by: Manivannan Sadhasivam Reviewed-by: Rob Herring (Arm) Link: https://patch.msgid.link/20260818153755.3801111-2-sai.krishna.musham@amd.com --- .../bindings/pci/xilinx-versal-cpm.yaml | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/Documentation/devicetree/bindings/pci/xilinx-versal-cpm.yaml b/Documentation/devicetree/bindings/pci/xilinx-versal-cpm.yaml index 9823456addea..a28a88dc8e88 100644 --- a/Documentation/devicetree/bindings/pci/xilinx-versal-cpm.yaml +++ b/Documentation/devicetree/bindings/pci/xilinx-versal-cpm.yaml @@ -41,6 +41,10 @@ properties: description: Maps a Requester ID to an MSI controller and associated MSI sideband data. + resets: + description: Reset for the PCIe controller. + maxItems: 1 + ranges: maxItems: 2 @@ -61,6 +65,17 @@ properties: interrupt-controller: true +patternProperties: + '^pcie@[0-2],0$': + type: object + $ref: /schemas/pci/pci-pci-bridge.yaml# + + properties: + reg: + maxItems: 1 + + unevaluatedProperties: false + required: - reg - reg-names @@ -76,6 +91,9 @@ unevaluatedProperties: false examples: - | + #include + #include + versal { #address-cells = <2>; #size-cells = <2>; @@ -99,6 +117,16 @@ examples: reg = <0x0 0xfca10000 0x0 0x1000>, <0x6 0x00000000 0x0 0x10000000>; reg-names = "cpm_slcr", "cfg"; + resets = <&versal_reset VERSAL_RST_PCIE_CORE0>; + + pcie@0,0 { + device_type = "pci"; + reg = <0x0 0x0 0x0 0x0 0x0>; + reset-gpios = <&gpio1 38 GPIO_ACTIVE_LOW>; + #address-cells = <3>; + #size-cells = <2>; + ranges; + }; pcie_intc_0: interrupt-controller { #address-cells = <0>; #interrupt-cells = <1>; @@ -127,6 +155,16 @@ examples: <0x06 0x00000000 0x00 0x1000000>, <0x00 0xfce20000 0x00 0x1000000>; reg-names = "cpm_slcr", "cfg", "cpm_csr"; + resets = <&versal_reset VERSAL_RST_PCIE_CORE0>; + + pcie@0,0 { + device_type = "pci"; + reg = <0x0 0x0 0x0 0x0 0x0>; + reset-gpios = <&gpio1 38 GPIO_ACTIVE_LOW>; + #address-cells = <3>; + #size-cells = <2>; + ranges; + }; pcie_intc_1: interrupt-controller { #address-cells = <0>; From b428bc9a1532e6f2616242ce8425fa271331726b Mon Sep 17 00:00:00 2001 From: Sai Krishna Musham Date: Tue, 18 Aug 2026 21:07:55 +0530 Subject: [PATCH 002/139] PCI: xilinx-cpm: Add support for PCIe RP PERST# signal Add support for the PCIe Root Port PERST# signal using the GPIO framework, along with the PCIe IP reset. This reset is managed by the driver and occurs after the Initial Power Up sequence (PCIe CEM r6.0, 2.2.1) is handled in hardware before the driver's probe function is called. This is useful in warm reset scenarios where the power rails stay stable and only PERST# is toggled. Applying both the IP reset and PERST# resets the Root Port controller and Endpoint synchronously, improving reliability and avoiding lane errors. Both the reset GPIO and the reset controller are optional to keep backward compatibility with existing DTBs. Also add a PCIE_T_PERST_US (100 us) macro in pci.h for the PERST# active time (PCIe CEM r6.0, sec 2.11.2, "T_PERST"). As part of this, update the interrupt controller node parsing to use of_get_child_by_name() instead of of_get_next_child(), since the PCIe host bridge node now has multiple children. This ensures the correct node is selected during initialization. Signed-off-by: Sai Krishna Musham Signed-off-by: Manivannan Sadhasivam Link: https://patch.msgid.link/20260818153755.3801111-3-sai.krishna.musham@amd.com --- drivers/pci/controller/pcie-xilinx-cpm.c | 77 +++++++++++++++++++++++- drivers/pci/pci.h | 9 +++ 2 files changed, 85 insertions(+), 1 deletion(-) diff --git a/drivers/pci/controller/pcie-xilinx-cpm.c b/drivers/pci/controller/pcie-xilinx-cpm.c index d38f27e20761..1fdd7f920590 100644 --- a/drivers/pci/controller/pcie-xilinx-cpm.c +++ b/drivers/pci/controller/pcie-xilinx-cpm.c @@ -6,6 +6,8 @@ */ #include +#include +#include #include #include #include @@ -16,6 +18,7 @@ #include #include #include +#include #include "../pci.h" #include "pcie-xilinx-common.h" @@ -113,6 +116,8 @@ struct xilinx_cpm_variant { * @irq: Error interrupt number * @lock: lock protecting shared register access * @variant: CPM version check pointer + * @perst_gpio: GPIO descriptor for PERST# signal handling + * @rstc: Pointer to the PCIe controller reset */ struct xilinx_cpm_pcie { struct device *dev; @@ -125,6 +130,8 @@ struct xilinx_cpm_pcie { int irq; raw_spinlock_t lock; const struct xilinx_cpm_variant *variant; + struct gpio_desc *perst_gpio; + struct reset_control *rstc; }; static u32 pcie_read(struct xilinx_cpm_pcie *port, u32 reg) @@ -389,7 +396,7 @@ static int xilinx_cpm_pcie_init_irq_domain(struct xilinx_cpm_pcie *port) struct device_node *pcie_intc_node; /* Setup INTx */ - pcie_intc_node = of_get_next_child(node, NULL); + pcie_intc_node = of_get_child_by_name(node, "interrupt-controller"); if (!pcie_intc_node) { dev_err(dev, "No PCIe Intc node found\n"); return -EINVAL; @@ -469,6 +476,22 @@ static int xilinx_cpm_setup_irq(struct xilinx_cpm_pcie *port) return 0; } +/** + * xilinx_cpm_pcie_reset - Reset the PCIe controller and deassert PERST# + * @port: PCIe port information + * + * Reset the PCIe controller and then release the PERST# signal so that the + * link can train once the bridge is enabled. + */ +static void xilinx_cpm_pcie_reset(struct xilinx_cpm_pcie *port) +{ + reset_control_assert(port->rstc); + udelay(PCIE_T_PERST_US); + reset_control_deassert(port->rstc); + gpiod_set_value_cansleep(port->perst_gpio, 0); + msleep(PCIE_RESET_CONFIG_WAIT_MS); +} + /** * xilinx_cpm_pcie_init_port - Initialize hardware * @port: PCIe port information @@ -480,6 +503,9 @@ static void xilinx_cpm_pcie_init_port(struct xilinx_cpm_pcie *port) if (variant->version == CPM5NC_HOST) return; + if (port->perst_gpio || port->rstc) + xilinx_cpm_pcie_reset(port); + if (cpm_pcie_link_up(port)) dev_info(port->dev, "PCIe Link is UP\n"); else @@ -512,6 +538,42 @@ static void xilinx_cpm_pcie_init_port(struct xilinx_cpm_pcie *port) XILINX_CPM_PCIE_REG_RPSC); } +/** + * xilinx_cpm_pcie_parse_port - Parse the PCIe Root Port child node + * @port: PCIe port information + * + * Read the PERST# GPIO from the Root Port child node. + * + * Return: '0' on success and error value on failure + */ +static int xilinx_cpm_pcie_parse_port(struct xilinx_cpm_pcie *port) +{ + struct device *dev = port->dev; + + /* + * This platform currently supports only one Root Port, so the loop + * will execute only once. + * TODO: Enhance the driver to handle multiple Root Ports in the future. + */ + for_each_child_of_node_with_prefix(dev->of_node, pcie_port_node, "pcie") { + port->perst_gpio = devm_fwnode_gpiod_get(dev, + of_fwnode_handle(pcie_port_node), + "reset", GPIOD_OUT_HIGH, + NULL); + if (IS_ERR(port->perst_gpio)) { + if (PTR_ERR(port->perst_gpio) == -ENOENT) { + port->perst_gpio = NULL; + return 0; + } + return dev_err_probe(dev, PTR_ERR(port->perst_gpio), + "Failed to request reset GPIO\n"); + } + return 0; + } + + return 0; +} + /** * xilinx_cpm_pcie_parse_dt - Parse Device tree * @port: PCIe port information @@ -525,6 +587,19 @@ static int xilinx_cpm_pcie_parse_dt(struct xilinx_cpm_pcie *port, struct device *dev = port->dev; struct platform_device *pdev = to_platform_device(dev); struct resource *res; + int ret; + + /* CPM5NC does not support PERST# handling yet */ + if (port->variant->version != CPM5NC_HOST) { + port->rstc = devm_reset_control_get_optional_exclusive(dev, NULL); + if (IS_ERR(port->rstc)) + return dev_err_probe(dev, PTR_ERR(port->rstc), + "Failed to request reset\n"); + + ret = xilinx_cpm_pcie_parse_port(port); + if (ret) + return ret; + } port->cpm_base = devm_platform_ioremap_resource_byname(pdev, "cpm_slcr"); diff --git a/drivers/pci/pci.h b/drivers/pci/pci.h index ba3c3fddddc2..f5fdb5dffdb7 100644 --- a/drivers/pci/pci.h +++ b/drivers/pci/pci.h @@ -40,6 +40,15 @@ struct pcie_tlp_log; */ #define PCIE_T_PERST_CLK_US 100 +/* + * PERST# active time. + * + * See the "Power Sequencing and Reset Signal Timings" table of the PCI Express + * Card Electromechanical Specification, Revision 6.0, Section 2.11.2, Symbol + * "T_PERST". + */ +#define PCIE_T_PERST_US 100 + /* * PCIe r6.0, sec 5.3.3.2.1 * Recommends 1ms to 10ms timeout to check L2 ready. From ebaa06fc5bbeea062e6dc7b2f28f7b042f9cc2ca Mon Sep 17 00:00:00 2001 From: Lorenzo Bianconi Date: Wed, 29 Jul 2026 15:34:25 +0200 Subject: [PATCH 003/139] PCI/pwrctrl: tc9563: Rely on regmap APIs Replace the custom tc9563_pwrctrl_i2c_{read,write,bulk_write} functions with standard regmap APIs. The device uses 24-bit big-endian register addresses and 32-bit little-endian register values over I2C, which is directly supported by regmap. This removes the open-coded I2C transfer handling and lets the regmap core handle formatting, locking, and multi-register coalescing. Signed-off-by: Lorenzo Bianconi Signed-off-by: Bjorn Helgaas Reviewed-by: Manivannan Sadhasivam Reviewed-by: Bartosz Golaszewski Link: https://patch.msgid.link/20260729-pci-pwrctrl-tc956x-regmap-v1-1-20ce61a3ce12@oss.qualcomm.com --- drivers/pci/pwrctrl/Kconfig | 1 + drivers/pci/pwrctrl/pci-pwrctrl-tc9563.c | 153 +++++++---------------- 2 files changed, 44 insertions(+), 110 deletions(-) diff --git a/drivers/pci/pwrctrl/Kconfig b/drivers/pci/pwrctrl/Kconfig index 9eec767cda86..1952ab4f29b6 100644 --- a/drivers/pci/pwrctrl/Kconfig +++ b/drivers/pci/pwrctrl/Kconfig @@ -29,6 +29,7 @@ config PCI_PWRCTRL_TC9563 select PCI_PWRCTRL default m if ARCH_QCOM depends on I2C + select REGMAP_I2C help Say Y here to enable the PCI Power Control driver of TC9563 PCIe switch. diff --git a/drivers/pci/pwrctrl/pci-pwrctrl-tc9563.c b/drivers/pci/pwrctrl/pci-pwrctrl-tc9563.c index fd53a7db9875..d2a0c4f8c03e 100644 --- a/drivers/pci/pwrctrl/pci-pwrctrl-tc9563.c +++ b/drivers/pci/pwrctrl/pci-pwrctrl-tc9563.c @@ -16,10 +16,10 @@ #include #include #include +#include #include #include #include -#include #include "../pci.h" @@ -70,11 +70,6 @@ #define TC9563_L0S_L1_DELAY_UNIT_NS 256 /* Each unit represents 256 ns */ -struct tc9563_pwrctrl_reg_setting { - unsigned int offset; - unsigned int val; -}; - enum tc9563_pwrctrl_ports { TC9563_USP, TC9563_DSP1, @@ -111,13 +106,14 @@ struct tc9563_pwrctrl { struct gpio_desc *reset_gpio; struct i2c_adapter *adapter; struct i2c_client *client; + struct regmap *regmap; }; /* * downstream port power off sequence, hardcoding the address * as we don't know register names for these register offsets. */ -static const struct tc9563_pwrctrl_reg_setting common_pwroff_seq[] = { +static const struct reg_sequence common_pwroff_seq[] = { {0x82900c, 0x1}, {0x829010, 0x1}, {0x829018, 0x0}, @@ -140,7 +136,7 @@ static const struct tc9563_pwrctrl_reg_setting common_pwroff_seq[] = { {0x829114, 0x1}, }; -static const struct tc9563_pwrctrl_reg_setting dsp1_pwroff_seq[] = { +static const struct reg_sequence dsp1_pwroff_seq[] = { {TC9563_PORT_ACCESS_ENABLE, 0x2}, {TC9563_PORT_LANE_ACCESS_ENABLE, 0x3}, {TC9563_POWER_CONTROL, 0x014f4804}, @@ -148,7 +144,7 @@ static const struct tc9563_pwrctrl_reg_setting dsp1_pwroff_seq[] = { {TC9563_PORT_ACCESS_ENABLE, 0x4}, }; -static const struct tc9563_pwrctrl_reg_setting dsp2_pwroff_seq[] = { +static const struct reg_sequence dsp2_pwroff_seq[] = { {TC9563_PORT_ACCESS_ENABLE, 0x8}, {TC9563_PORT_LANE_ACCESS_ENABLE, 0x1}, {TC9563_POWER_CONTROL, 0x014f4804}, @@ -156,85 +152,11 @@ static const struct tc9563_pwrctrl_reg_setting dsp2_pwroff_seq[] = { {TC9563_PORT_ACCESS_ENABLE, 0x8}, }; -/* - * Since all transfers are initiated by the probe, no locks are necessary, - * as there are no concurrent calls. - */ -static int tc9563_pwrctrl_i2c_write(struct i2c_client *client, - u32 reg_addr, u32 reg_val) -{ - struct i2c_msg msg; - u8 msg_buf[7]; - int ret; - - msg.addr = client->addr; - msg.len = 7; - msg.flags = 0; - - /* Big Endian for reg addr */ - put_unaligned_be24(reg_addr, &msg_buf[0]); - - /* Little Endian for reg val */ - put_unaligned_le32(reg_val, &msg_buf[3]); - - msg.buf = msg_buf; - ret = i2c_transfer(client->adapter, &msg, 1); - return ret == 1 ? 0 : ret; -} - -static int tc9563_pwrctrl_i2c_read(struct i2c_client *client, - u32 reg_addr, u32 *reg_val) -{ - struct i2c_msg msg[2]; - u8 wr_data[3]; - u32 rd_data; - int ret; - - msg[0].addr = client->addr; - msg[0].len = 3; - msg[0].flags = 0; - - /* Big Endian for reg addr */ - put_unaligned_be24(reg_addr, &wr_data[0]); - - msg[0].buf = wr_data; - - msg[1].addr = client->addr; - msg[1].len = 4; - msg[1].flags = I2C_M_RD; - - msg[1].buf = (u8 *)&rd_data; - - ret = i2c_transfer(client->adapter, &msg[0], 2); - if (ret == 2) { - *reg_val = get_unaligned_le32(&rd_data); - return 0; - } - - /* If only one message successfully completed, return -EIO */ - return ret == 1 ? -EIO : ret; -} - -static int tc9563_pwrctrl_i2c_bulk_write(struct i2c_client *client, - const struct tc9563_pwrctrl_reg_setting *seq, - int len) -{ - int ret, i; - - for (i = 0; i < len; i++) { - ret = tc9563_pwrctrl_i2c_write(client, seq[i].offset, seq[i].val); - if (ret) - return ret; - } - - return 0; -} - static int tc9563_pwrctrl_disable_port(struct tc9563_pwrctrl *tc9563, enum tc9563_pwrctrl_ports port) { struct tc9563_pwrctrl_cfg *cfg = &tc9563->cfg[port]; - const struct tc9563_pwrctrl_reg_setting *seq; + const struct reg_sequence *seq; int ret, len; if (!cfg->disable_port) @@ -254,12 +176,12 @@ static int tc9563_pwrctrl_disable_port(struct tc9563_pwrctrl *tc9563, return 0; } - ret = tc9563_pwrctrl_i2c_bulk_write(tc9563->client, seq, len); + ret = regmap_multi_reg_write(tc9563->regmap, seq, len); if (ret) return ret; - return tc9563_pwrctrl_i2c_bulk_write(tc9563->client, common_pwroff_seq, - ARRAY_SIZE(common_pwroff_seq)); + return regmap_multi_reg_write(tc9563->regmap, common_pwroff_seq, + ARRAY_SIZE(common_pwroff_seq)); } static int tc9563_pwrctrl_set_port_l0s_l1_entry_delay(struct tc9563_pwrctrl *tc9563, @@ -275,14 +197,13 @@ static int tc9563_pwrctrl_set_port_l0s_l1_entry_delay(struct tc9563_pwrctrl *tc9 /* convert to units of 256ns */ units = ns / TC9563_L0S_L1_DELAY_UNIT_NS; - ret = tc9563_pwrctrl_i2c_write(tc9563->client, TC9563_PORT_SELECT, - BIT(port)); + ret = regmap_write(tc9563->regmap, TC9563_PORT_SELECT, BIT(port)); if (ret) return ret; - return tc9563_pwrctrl_i2c_write(tc9563->client, - is_l1 ? TC9563_PORT_L1_DELAY : TC9563_PORT_L0S_DELAY, - units); + return regmap_write(tc9563->regmap, + is_l1 ? TC9563_PORT_L1_DELAY : TC9563_PORT_L0S_DELAY, + units); } static int tc9563_pwrctrl_set_eth_l0s_l1_entry_delay(struct tc9563_pwrctrl *tc9563, @@ -297,8 +218,7 @@ static int tc9563_pwrctrl_set_eth_l0s_l1_entry_delay(struct tc9563_pwrctrl *tc95 /* convert to units of 256ns */ units = ns / TC9563_L0S_L1_DELAY_UNIT_NS; - ret = tc9563_pwrctrl_i2c_read(tc9563->client, TC9563_EMBEDDED_ETH_DELAY, - &rd_val); + ret = regmap_read(tc9563->regmap, TC9563_EMBEDDED_ETH_DELAY, &rd_val); if (ret) return ret; @@ -309,8 +229,7 @@ static int tc9563_pwrctrl_set_eth_l0s_l1_entry_delay(struct tc9563_pwrctrl *tc95 rd_val = u32_replace_bits(rd_val, units, TC9563_ETH_L0S_DELAY_MASK); - return tc9563_pwrctrl_i2c_write(tc9563->client, TC9563_EMBEDDED_ETH_DELAY, - rd_val); + return regmap_write(tc9563->regmap, TC9563_EMBEDDED_ETH_DELAY, rd_val); } static int tc9563_pwrctrl_set_tx_amplitude(struct tc9563_pwrctrl *tc9563, @@ -343,14 +262,14 @@ static int tc9563_pwrctrl_set_tx_amplitude(struct tc9563_pwrctrl *tc9563, return -EINVAL; } - struct tc9563_pwrctrl_reg_setting tx_amp_seq[] = { + struct reg_sequence tx_amp_seq[] = { {TC9563_PORT_ACCESS_ENABLE, port_access}, {TC9563_PORT_LANE_ACCESS_ENABLE, 0x3}, {TC9563_TX_MARGIN, amp}, }; - return tc9563_pwrctrl_i2c_bulk_write(tc9563->client, tx_amp_seq, - ARRAY_SIZE(tx_amp_seq)); + return regmap_multi_reg_write(tc9563->regmap, tx_amp_seq, + ARRAY_SIZE(tx_amp_seq)); } static int tc9563_pwrctrl_disable_dfe(struct tc9563_pwrctrl *tc9563, @@ -383,7 +302,7 @@ static int tc9563_pwrctrl_disable_dfe(struct tc9563_pwrctrl *tc9563, return -EINVAL; } - struct tc9563_pwrctrl_reg_setting disable_dfe_seq[] = { + struct reg_sequence disable_dfe_seq[] = { {TC9563_PORT_ACCESS_ENABLE, port_access}, {TC9563_PORT_LANE_ACCESS_ENABLE, lane_access}, {TC9563_DFE_ENABLE, 0x0}, @@ -397,15 +316,15 @@ static int tc9563_pwrctrl_disable_dfe(struct tc9563_pwrctrl *tc9563, {TC9563_PHY_RATE_CHANGE_OVERRIDE, 0x0}, }; - return tc9563_pwrctrl_i2c_bulk_write(tc9563->client, disable_dfe_seq, - ARRAY_SIZE(disable_dfe_seq)); + return regmap_multi_reg_write(tc9563->regmap, disable_dfe_seq, + ARRAY_SIZE(disable_dfe_seq)); } static int tc9563_pwrctrl_set_nfts(struct tc9563_pwrctrl *tc9563, enum tc9563_pwrctrl_ports port) { u8 *nfts = tc9563->cfg[port].nfts; - struct tc9563_pwrctrl_reg_setting nfts_seq[] = { + struct reg_sequence nfts_seq[] = { {TC9563_NFTS_2_5_GT, nfts[0]}, {TC9563_NFTS_5_GT, nfts[1]}, }; @@ -420,13 +339,12 @@ static int tc9563_pwrctrl_set_nfts(struct tc9563_pwrctrl *tc9563, return 0; } - ret = tc9563_pwrctrl_i2c_write(tc9563->client, TC9563_PORT_SELECT, - BIT(port)); + ret = regmap_write(tc9563->regmap, TC9563_PORT_SELECT, BIT(port)); if (ret) return ret; - return tc9563_pwrctrl_i2c_bulk_write(tc9563->client, nfts_seq, - ARRAY_SIZE(nfts_seq)); + return regmap_multi_reg_write(tc9563->regmap, nfts_seq, + ARRAY_SIZE(nfts_seq)); } static int tc9563_pwrctrl_assert_deassert_reset(struct tc9563_pwrctrl *tc9563, @@ -434,14 +352,14 @@ static int tc9563_pwrctrl_assert_deassert_reset(struct tc9563_pwrctrl *tc9563, { int ret, val; - ret = tc9563_pwrctrl_i2c_write(tc9563->client, TC9563_GPIO_CONFIG, - TC9563_GPIO_MASK); + ret = regmap_write(tc9563->regmap, TC9563_GPIO_CONFIG, + TC9563_GPIO_MASK); if (ret) return ret; val = deassert ? TC9563_GPIO_DEASSERT_BITS : 0; - return tc9563_pwrctrl_i2c_write(tc9563->client, TC9563_RESET_GPIO, val); + return regmap_write(tc9563->regmap, TC9563_RESET_GPIO, val); } static int tc9563_pwrctrl_parse_device_dt(struct device_node *node, @@ -572,6 +490,13 @@ static int tc9563_pwrctrl_power_on(struct pci_pwrctrl *pwrctrl) return ret; } +static const struct regmap_config tc9563_regmap_config = { + .reg_bits = 24, + .val_bits = 32, + .reg_format_endian = REGMAP_ENDIAN_BIG, + .val_format_endian = REGMAP_ENDIAN_LITTLE, +}; + static int tc9563_pwrctrl_probe(struct platform_device *pdev) { struct device_node *node = pdev->dev.of_node; @@ -602,6 +527,14 @@ static int tc9563_pwrctrl_probe(struct platform_device *pdev) return PTR_ERR(tc9563->client); } + tc9563->regmap = devm_regmap_init_i2c(tc9563->client, + &tc9563_regmap_config); + if (IS_ERR(tc9563->regmap)) { + ret = dev_err_probe(dev, PTR_ERR(tc9563->regmap), + "Failed to allocate register map\n"); + goto remove_i2c; + } + for (int i = 0; i < ARRAY_SIZE(tc9563_supply_names); i++) tc9563->supplies[i].supply = tc9563_supply_names[i]; From 854fc493df555479d73623e87ce2d8233c32b6bc Mon Sep 17 00:00:00 2001 From: Lorenzo Bianconi Date: Wed, 29 Jul 2026 15:34:26 +0200 Subject: [PATCH 004/139] PCI/pwrctrl: tc9563: Use devm-managed I2C dummy device allocation Switch from i2c_new_dummy_device() to its devm variant devm_i2c_new_dummy_device() so the I2C client is automatically unregistered when the driver is removed or on probe failure. This allows removing the manual i2c_unregister_device() calls from both the probe error path and the remove function, and lets the i2c_client pointer be a local variable in probe instead of storing it in the driver data structure. Signed-off-by: Lorenzo Bianconi Signed-off-by: Bjorn Helgaas Reviewed-by: Manivannan Sadhasivam Reviewed-by: Bartosz Golaszewski Link: https://patch.msgid.link/20260729-pci-pwrctrl-tc956x-regmap-v1-2-20ce61a3ce12@oss.qualcomm.com --- drivers/pci/pwrctrl/pci-pwrctrl-tc9563.c | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/drivers/pci/pwrctrl/pci-pwrctrl-tc9563.c b/drivers/pci/pwrctrl/pci-pwrctrl-tc9563.c index d2a0c4f8c03e..b0d8912690a2 100644 --- a/drivers/pci/pwrctrl/pci-pwrctrl-tc9563.c +++ b/drivers/pci/pwrctrl/pci-pwrctrl-tc9563.c @@ -105,7 +105,6 @@ struct tc9563_pwrctrl { struct tc9563_pwrctrl_cfg ep_cfg; struct gpio_desc *reset_gpio; struct i2c_adapter *adapter; - struct i2c_client *client; struct regmap *regmap; }; @@ -504,6 +503,7 @@ static int tc9563_pwrctrl_probe(struct platform_device *pdev) enum tc9563_pwrctrl_ports port; struct tc9563_pwrctrl *tc9563; struct device_node *i2c_node; + struct i2c_client *client; int ret, addr; tc9563 = devm_kzalloc(dev, sizeof(*tc9563), GFP_KERNEL); @@ -520,15 +520,14 @@ static int tc9563_pwrctrl_probe(struct platform_device *pdev) if (!tc9563->adapter) return dev_err_probe(dev, -EPROBE_DEFER, "Failed to find I2C adapter\n"); - tc9563->client = i2c_new_dummy_device(tc9563->adapter, addr); - if (IS_ERR(tc9563->client)) { + client = devm_i2c_new_dummy_device(dev, tc9563->adapter, addr); + if (IS_ERR(client)) { dev_err(dev, "Failed to create I2C client\n"); - i2c_put_adapter(tc9563->adapter); - return PTR_ERR(tc9563->client); + ret = PTR_ERR(client); + goto remove_i2c; } - tc9563->regmap = devm_regmap_init_i2c(tc9563->client, - &tc9563_regmap_config); + tc9563->regmap = devm_regmap_init_i2c(client, &tc9563_regmap_config); if (IS_ERR(tc9563->regmap)) { ret = dev_err_probe(dev, PTR_ERR(tc9563->regmap), "Failed to allocate register map\n"); @@ -606,7 +605,6 @@ static int tc9563_pwrctrl_probe(struct platform_device *pdev) power_off: tc9563_pwrctrl_power_off(&tc9563->pwrctrl); remove_i2c: - i2c_unregister_device(tc9563->client); i2c_put_adapter(tc9563->adapter); return ret; } @@ -618,7 +616,6 @@ static void tc9563_pwrctrl_remove(struct platform_device *pdev) struct tc9563_pwrctrl, pwrctrl); tc9563_pwrctrl_power_off(&tc9563->pwrctrl); - i2c_unregister_device(tc9563->client); i2c_put_adapter(tc9563->adapter); } From 1b3b0bc2b3672e928e06997444528f8797579eb2 Mon Sep 17 00:00:00 2001 From: Christopher Obbard Date: Sat, 29 Aug 2026 11:36:00 +0100 Subject: [PATCH 005/139] PCI: qcom: Only check bridge nodes for PERST# GPIOs qcom_pcie_parse_perst() walks the PCIe hierarchy described in the device tree below its Root Port and collects the PERST# GPIOs from each bridge node so all PERST# lines can be driven in unison during controller bring-up, reset and power sequencing. However, the recursive device tree walk currently visits every available child node without checking that it is a PCI bridge. This allows the walk to leave the PCI hierarchy and collect reset lines from children of PCI Endpoint nodes, where those reset lines belong to other drivers. This is reproducible on the Qualcomm RB3Gen2, where a Renesas uPD720201 USB host controller sits behind a PCIe switch downstream port. A Genesys GL3590 USB hub connected to that controller is described as a child of the PCI endpoint: pcie@2,0 { device_type = "pci"; usb-controller@0,0 { compatible = "pci1912,0014"; hub@1 { compatible = "usb5e3,610"; reset-gpios = <&tlmm 162 GPIO_ACTIVE_HIGH>; }; }; }; The USB controller is a PCI Endpoint, and not a bridge, but the PERST# walk descends through it and incorrectly claims the hub's reset GPIO: $ gpioinfo line 162: unnamed output consumer=PERST# As a result, the onboard-usb-dev driver cannot acquire its reset GPIO during probe: onboard-usb-dev 1c08000.pcie:...:usb-controller@0,0:hub@1: \ error -EBUSY: failed to get reset GPIO onboard-usb-dev 1c08000.pcie:...:usb-controller@0,0:hub@1: \ probe with driver onboard-usb-dev failed with error -16 The GPIO is also added to the root port's PERST# list and is asserted and deasserted as part of PCIe reset sequencing. On the RB3Gen2 this causes the USB hub to repeatedly re-enumerate: [ 116.479598] hub 2-1:1.0: USB hub found [ 116.488601] hub 2-1:1.0: USB hub found [ 116.496971] hub 2-1:1.0: USB hub found ... This disrupts the hub and devices connected to it. Restrict the recursive walk to children with device_type "pci", so PERST# GPIOs are collected only from PCI bridge nodes and the walk does not descend through endpoints. qocm_pcie_parse_ports() has made the same distinction since commit 45df22935bdc ("PCI: qcom: Restrict port parsing only to PCIe bridge child nodes"), but the PERST# walk added later did not carry over that check. This bug is distinct from commit 3edb3a038d42 ("PCI: qcom: Skip PERST# GPIOs provided by downstream PCIe devices"), which filters PERST# GPIOs based on their provider. Here the GPIO is provided by the SoC TLMM; the bug is that reset-gpios is consumed from a non-bridge node at all. Fixes: 2fd60a2edb83 ("PCI: qcom: Parse PERST# from all PCIe bridge nodes") Signed-off-by: Christopher Obbard Signed-off-by: Manivannan Sadhasivam Reviewed-by: Krishna Chaitanya Chundru Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20260829-wip-obbardc-drivers-pcie-qcom-rb3gen2-usb-fix-v1-1-337821131a99@oss.qualcomm.com --- drivers/pci/controller/dwc/pcie-qcom.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/pci/controller/dwc/pcie-qcom.c b/drivers/pci/controller/dwc/pcie-qcom.c index b58a607b713f..89a476c37098 100644 --- a/drivers/pci/controller/dwc/pcie-qcom.c +++ b/drivers/pci/controller/dwc/pcie-qcom.c @@ -1953,7 +1953,7 @@ static bool qcom_pcie_is_child_node(struct device *dev, return false; } -/* Parse PERST# from all nodes in depth first manner starting from @np */ +/* Collect PERST# GPIOs from PCI bridge nodes depth-first, starting at @np */ static int qcom_pcie_parse_perst(struct qcom_pcie *pcie, struct qcom_pcie_port *port, struct device_node *np) @@ -2019,6 +2019,9 @@ static int qcom_pcie_parse_perst(struct qcom_pcie *pcie, parse_child_node: for_each_available_child_of_node_scoped(np, child) { + if (!of_node_is_type(child, "pci")) + continue; + ret = qcom_pcie_parse_perst(pcie, port, child); if (ret) return ret; From ce8034bb9c5c50f9f5c305856aff5d1c3efe3a30 Mon Sep 17 00:00:00 2001 From: Hans Zhang <18255117159@163.com> Date: Fri, 21 Aug 2026 07:23:05 +0800 Subject: [PATCH 006/139] PCI: cadence: Fix enum type mismatch warning in LTSSM debugfs Fix Clang -Wenum-compare-conditional warning by using a single u32 variable to hold the LTSSM status value, replacing two separate enum variables of different types. Fixes: d70e964dc099 ("PCI: cadence: Add LGA IP debugfs for LTSSM status") Reported-by: kernel test robot Closes: https://lore.kernel.org/oe-kbuild-all/202608201933.w2579mwx-lkp@intel.com/ Signed-off-by: Hans Zhang <18255117159@163.com> Signed-off-by: Manivannan Sadhasivam Reviewed-by: Aksh Garg Link: https://patch.msgid.link/20260820232305.80596-1-18255117159@163.com --- .../pci/controller/cadence/pcie-cadence-debugfs.c | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/drivers/pci/controller/cadence/pcie-cadence-debugfs.c b/drivers/pci/controller/cadence/pcie-cadence-debugfs.c index 0a308f95e9f6..7b8354052aea 100644 --- a/drivers/pci/controller/cadence/pcie-cadence-debugfs.c +++ b/drivers/pci/controller/cadence/pcie-cadence-debugfs.c @@ -210,24 +210,21 @@ static const char *cdns_pcie_hpa_ltssm_status_string(enum cdns_pcie_hpa_ltssm lt static int ltssm_status_show(struct seq_file *s, void *v) { struct cdns_pcie *pci = s->private; - enum cdns_pcie_lga_ltssm lga_ltssm; - enum cdns_pcie_hpa_ltssm hpa_ltssm; const char *str_ltssm; - u32 val; + u32 val, ltssm_val; if (pci->is_hpa) { val = cdns_pcie_hpa_readl(pci, REG_BANK_IP_REG, CDNS_PCIE_HPA_PHY_DBG_STS_REG0); - hpa_ltssm = FIELD_GET(CDNS_PCIE_HPA_LTSSM_STATUS_MASK, val); - str_ltssm = cdns_pcie_hpa_ltssm_status_string(hpa_ltssm); + ltssm_val = FIELD_GET(CDNS_PCIE_HPA_LTSSM_STATUS_MASK, val); + str_ltssm = cdns_pcie_hpa_ltssm_status_string(ltssm_val); } else { val = cdns_pcie_readl(pci, CDNS_PCIE_LM_BASE); - lga_ltssm = FIELD_GET(CDNS_PCIE_LGA_LTSSM_STATUS_MASK, val); - str_ltssm = cdns_pcie_lga_ltssm_status_string(lga_ltssm); + ltssm_val = FIELD_GET(CDNS_PCIE_LGA_LTSSM_STATUS_MASK, val); + str_ltssm = cdns_pcie_lga_ltssm_status_string(ltssm_val); } - seq_printf(s, "%s (0x%02x)\n", str_ltssm, - pci->is_hpa ? hpa_ltssm : lga_ltssm); + seq_printf(s, "%s (0x%02x)\n", str_ltssm, ltssm_val); return 0; } From 0031f76f73b86f711d0e4e95a967aea1821fb867 Mon Sep 17 00:00:00 2001 From: hanzhijian Date: Sat, 22 Aug 2026 09:22:05 +0800 Subject: [PATCH 007/139] PCI: imx6: Fix pwrctrl device leak on PM runtime setup failure In imx_pcie_probe(), after pci_pwrctrl_create_devices() creates the pwrctrl devices, the error paths for imx_add_pcie_ep() and dw_pcie_host_init() both jump to err_pwrctrl_destroy to tear them down. However, the error path for devm_pm_runtime_set_active_enabled() returns directly, leaking the pwrctrl devices. Jump to err_pwrctrl_destroy instead so the pwrctrl devices are cleaned up on this error path too. Signed-off-by: hanzhijian Signed-off-by: Manivannan Sadhasivam Link: https://patch.msgid.link/20260822012205.2459806-1-hanzhijian1991@gmail.com --- drivers/pci/controller/dwc/pci-imx6.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/pci/controller/dwc/pci-imx6.c b/drivers/pci/controller/dwc/pci-imx6.c index 39790e66b98d..616c42932faa 100644 --- a/drivers/pci/controller/dwc/pci-imx6.c +++ b/drivers/pci/controller/dwc/pci-imx6.c @@ -1975,7 +1975,7 @@ static int imx_pcie_probe(struct platform_device *pdev) pm_runtime_no_callbacks(dev); ret = devm_pm_runtime_set_active_enabled(dev); if (ret < 0) - return ret; + goto err_pwrctrl_destroy; } if (imx_check_flag(imx_pcie, IMX_PCIE_FLAG_SKIP_L23_READY)) From a54ac2eb6eca8baa86b89494b8a0d9439a0f72ee Mon Sep 17 00:00:00 2001 From: Liu Zhenlong Date: Mon, 17 Aug 2026 01:16:33 +0800 Subject: [PATCH 008/139] PCI: aspeed: Fix clk and PHY leak in aspeed_pcie_port_init() error paths aspeed_pcie_port_init() calls clk_prepare_enable() to enable the port clock, but if phy_init() or phy_set_mode_ext() fails afterwards, the function returns without calling clk_disable_unprepare(), leaking the clock reference on every probe failure. Additionally, when phy_set_mode_ext() fails, phy_init() has already succeeded, so phy_exit() is also missing, leaking the PHY reference. Add the matching clk_disable_unprepare() to both error paths, and phy_exit() to the phy_set_mode_ext() failure path, mirroring the cleanup pattern in pci-aardvark. Compile-tested with gcc on arm64 defconfig using COMPILE_TEST. Fixes: 9aa0cb68fcc1 ("PCI: aspeed: Add ASPEED PCIe RC driver") Signed-off-by: Liu Zhenlong Signed-off-by: Manivannan Sadhasivam Signed-off-by: Bjorn Helgaas Assisted-by: LLM Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20260816171633.15025-1-dragonliu2018@gmail.com --- drivers/pci/controller/pcie-aspeed.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/drivers/pci/controller/pcie-aspeed.c b/drivers/pci/controller/pcie-aspeed.c index 74b03a55643f..ecb7562f84c0 100644 --- a/drivers/pci/controller/pcie-aspeed.c +++ b/drivers/pci/controller/pcie-aspeed.c @@ -761,16 +761,21 @@ static int aspeed_pcie_port_init(struct aspeed_pcie_port *port) port->slot); ret = phy_init(port->phy); - if (ret) + if (ret) { + clk_disable_unprepare(port->clk); return dev_err_probe(dev, ret, "failed to init phy pcie for slot (%d)\n", port->slot); + } ret = phy_set_mode_ext(port->phy, PHY_MODE_PCIE, PHY_MODE_PCIE_RC); - if (ret) + if (ret) { + phy_exit(port->phy); + clk_disable_unprepare(port->clk); return dev_err_probe(dev, ret, "failed to set phy mode for slot (%d)\n", port->slot); + } reset_control_deassert(port->perst); msleep(PCIE_RESET_CONFIG_WAIT_MS); From afe2aa9dc93d904730bab63f5ccb514a318e6464 Mon Sep 17 00:00:00 2001 From: Koichiro Den Date: Wed, 29 Jul 2026 02:23:04 +0900 Subject: [PATCH 009/139] PCI: endpoint: pci-epf-vntb: Pass PF/VF number when BAR programming vntb_epf_mw_set_trans() programs the memory-window BAR through pci_epc_set_bar() with hardcoded function numbers (0, 0), so the BAR lands on the wrong function whenever the vNTB EPF is bound to anything but PF0. The other BAR programming sites in the vNTB driver already pass the EPF's own numbers. Pass the EPF's own func_no/vfunc_no here as well. Fixes: e35f56bb0330 ("PCI: endpoint: Support NTB transfer between RC and EP") Signed-off-by: Koichiro Den Signed-off-by: Manivannan Sadhasivam Reviewed-by: Frank Li Link: https://patch.msgid.link/20260728172306.2751813-2-den@valinux.co.jp --- drivers/pci/endpoint/functions/pci-epf-vntb.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/pci/endpoint/functions/pci-epf-vntb.c b/drivers/pci/endpoint/functions/pci-epf-vntb.c index c3caec927d74..fba65abfb6b2 100644 --- a/drivers/pci/endpoint/functions/pci-epf-vntb.c +++ b/drivers/pci/endpoint/functions/pci-epf-vntb.c @@ -1427,7 +1427,8 @@ static int vntb_epf_mw_set_trans(struct ntb_dev *ndev, int pidx, int idx, epf_bar->barno = barno; epf_bar->size = size; - ret = pci_epc_set_bar(ntb->epf->epc, 0, 0, epf_bar); + ret = pci_epc_set_bar(ntb->epf->epc, ntb->epf->func_no, + ntb->epf->vfunc_no, epf_bar); if (ret) { dev_err(dev, "failure set mw trans\n"); return ret; From 52c73e3cecd0603346e176da1d04a0c4d3c98f1b Mon Sep 17 00:00:00 2001 From: Koichiro Den Date: Wed, 29 Jul 2026 02:23:05 +0900 Subject: [PATCH 010/139] PCI: endpoint: pci-ep-msi: Make embedded doorbell IRQ exclusive Embedded doorbells may use the same virq for several messages. This does not mean the IRQ itself should be shared. Consumers only need to request each distinct virq once, as vNTB and pci-epf-test already do. IRQF_SHARED would let different EPFs register handlers for the same IRQ. The embedded doorbell interface cannot tell which EPF a write targets, so every handler would handle it. Only the first EPF can allocate doorbells today, so this cannot happen yet. Drop IRQF_SHARED before extending the embedded backend to non-first EPFs. A second request for the same IRQ will then fail with -EBUSY instead of attaching another handler. Signed-off-by: Koichiro Den Signed-off-by: Manivannan Sadhasivam Reviewed-by: Frank Li Link: https://patch.msgid.link/20260728172306.2751813-3-den@valinux.co.jp --- drivers/pci/endpoint/pci-ep-msi.c | 9 +++++---- include/linux/pci-epf.h | 3 ++- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/drivers/pci/endpoint/pci-ep-msi.c b/drivers/pci/endpoint/pci-ep-msi.c index 0855c7930abb..a123a189b4e6 100644 --- a/drivers/pci/endpoint/pci-ep-msi.c +++ b/drivers/pci/endpoint/pci-ep-msi.c @@ -10,7 +10,6 @@ #include #include #include -#include #include #include #include @@ -112,8 +111,11 @@ static int pci_epf_alloc_doorbell_embedded(struct pci_epf *epf, u16 num_db) * * Still, pci_epf_alloc_doorbell() allows requesting multiple doorbells. * For such backends we replicate the same address/data for each entry - * and mark the IRQ as shared (IRQF_SHARED). Consumers must treat them - * as equivalent "kick" doorbells. + * as equivalent "kick" doorbells. Consumers must request each distinct + * IRQ only once. + * + * Keep the IRQ exclusive because the source cannot identify which EPF + * rang the doorbell. */ for (i = 0; i < num_db; i++) msg[i] = (struct pci_epf_doorbell_msg) { @@ -121,7 +123,6 @@ static int pci_epf_alloc_doorbell_embedded(struct pci_epf *epf, u16 num_db) .msg.address_hi = (u32)(addr >> 32), .msg.data = doorbell->u.db_mmio.data, .virq = doorbell->u.db_mmio.irq, - .irq_flags = IRQF_SHARED, .type = PCI_EPF_DOORBELL_EMBEDDED, .bar = doorbell->bar, .offset = (doorbell->bar == NO_BAR) ? 0 : diff --git a/include/linux/pci-epf.h b/include/linux/pci-epf.h index 704e1dc8b30a..226f3e59cf60 100644 --- a/include/linux/pci-epf.h +++ b/include/linux/pci-epf.h @@ -163,7 +163,8 @@ enum pci_epf_doorbell_type { * For MSI-backed doorbells this is the MSI message, while for * "embedded" doorbells this represents an MMIO write that asserts * an interrupt on the EP side. - * @virq: IRQ number of this doorbell message + * @virq: IRQ number of this doorbell message. Multiple messages may use the + * same IRQ; consumers must request each distinct IRQ only once. * @irq_flags: Required flags for request_irq()/request_threaded_irq(). * Callers may OR-in additional flags (e.g. IRQF_ONESHOT). * @type: Doorbell type. From 68fcdf30a1582574fe67e0b02488f8ba0d4d1242 Mon Sep 17 00:00:00 2001 From: Koichiro Den Date: Wed, 29 Jul 2026 02:23:06 +0900 Subject: [PATCH 011/139] PCI: endpoint: pci-ep-msi: Let non-first EPFs use embedded doorbells pci_epf_alloc_doorbell() rejects every EPF that is not the first one attached to the EPC because the MSI-backed path can populate doorbell messages only for that EPF. This also prevents non-first EPFs from using the embedded doorbell backend. Keep the MSI-backed path limited to the first attached EPF. Let non-first EPFs skip it and try the embedded doorbell directly. The embedded IRQ is exclusive, so a second embedded user fails to request the IRQ instead of receiving another EPF's notifications. Signed-off-by: Koichiro Den Signed-off-by: Manivannan Sadhasivam Reviewed-by: Frank Li Link: https://patch.msgid.link/20260728172306.2751813-4-den@valinux.co.jp --- drivers/pci/endpoint/pci-ep-msi.c | 31 +++++++++++++++++-------------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/drivers/pci/endpoint/pci-ep-msi.c b/drivers/pci/endpoint/pci-ep-msi.c index a123a189b4e6..5ea7eef9794f 100644 --- a/drivers/pci/endpoint/pci-ep-msi.c +++ b/drivers/pci/endpoint/pci-ep-msi.c @@ -199,28 +199,31 @@ static int pci_epf_alloc_doorbell_msi(struct pci_epf *epf, u16 num_db) int pci_epf_alloc_doorbell(struct pci_epf *epf, u16 num_db) { struct pci_epc *epc = epf->epc; + struct pci_epf *first_epf; struct device *dev = &epf->dev; int ret; - /* TODO: Multi-EPF support */ - if (list_first_entry_or_null(&epc->pci_epf, struct pci_epf, list) != epf) { - dev_err(dev, "Doorbell doesn't support multiple EPF\n"); - return -EINVAL; - } - if (epf->db_msg) return -EBUSY; - ret = pci_epf_alloc_doorbell_msi(epf, num_db); - if (!ret) - return 0; - /* - * Fall back to embedded doorbell only when platform MSI is unavailable - * for this EPC. + * The MSI-backed doorbell path currently targets the first EPF attached + * to the EPC. Let non-first EPFs try the embedded doorbell instead. */ - if (ret != -ENODEV) - return ret; + first_epf = list_first_entry_or_null(&epc->pci_epf, struct pci_epf, + list); + if (first_epf == epf) { + ret = pci_epf_alloc_doorbell_msi(epf, num_db); + if (!ret) + return 0; + + /* + * Fall back to embedded doorbell only when platform MSI is + * unavailable for this EPC. + */ + if (ret != -ENODEV) + return ret; + } ret = pci_epf_alloc_doorbell_embedded(epf, num_db); if (ret) { From f0a5c6bd797c2b9900034983a1118d27eb2f2f16 Mon Sep 17 00:00:00 2001 From: Marek Vasut Date: Fri, 21 Aug 2026 04:05:51 +0200 Subject: [PATCH 012/139] PCI: rcar-gen4: Limit Max_Read_Request_Size to 256 Bytes R-Car Gen4 PCIe controller has a hardware limitation of 256 Bytes Max_Payload_Size (MPS). PCIe specification indicates that the MPS must not exceed minimum MPS of any element along the packet path. The PCI core already enforces this, but add a check to warn if MPS is ever set larger than 256. R-Car Gen4 Reference Manual, chapter 104.4.8 Usage notes for MRRS (Max Read Request Size) states: Please set "Max Read Request Size" to 128 bytes or 256 bytes. If "Max Read Request Size" is set to anything other than the above, the transferred data will not match the expected value. This limitation also seems to apply to devices doing DMA reads, i.e., issuing MRd TLPs. This limitation can be triggered by using non-HMB NVMe SSD with Max_Read_Request_Size 512 Bytes, for example Crucial P5 Plus. Any write to the SSD (MRd TLP issued by the SSD controller) longer than 256 Bytes wraps around at the 256 Byte boundary, and the same data are written to the SSD starting at offset 0 and at 256 Bytes. Limit Max_Read_Request_Size to at most 256 Bytes for each device connected to this PCIe controller to avoid this behavior. A non-HMB (Host Memory Buffer) NVMe SSD can be identified using the following command. Affected SSD reports 'hmpre' field as 0: $ nvme id-ctrl /dev/nvme0 | grep hmpre hmpre : 0 The symptom is a read from the SSD which wraps around at 256 Byte boundary. The test for this symptom can be implemented by writing 512 Byte of random data into the SSD and reading the data back. If the read back data repeat after 256 Bytes, the device is affected. $ dd if=/dev/urandom of=/tmp/data.bin bs=256 count=2 ; \ dd if=/tmp/data.bin of=/dev/nvme0n1 bs=256 count=2 ; \ dd if=/dev/nvme0n1 bs=256 count=2 of=/tmp/readback.bin Expected data: $ hexdump -vC /tmp/data.bin 00000000 97 81 b7 3b 0e 38 2b 4d a7 d3 e0 47 ff c2 4b ca 00000010 c1 85 98 f0 4a ac 03 a0 3b ab f3 19 44 dd 06 8b ... 00000100 7a ce 3c b2 e1 d5 d9 11 88 63 10 59 76 3c dc 32 <-- random 00000110 72 32 2a 7d a3 e1 aa 13 7c da 58 a1 7b 21 11 50 <-- data Faulty readback, collected without this change in place: $ hexdump -vC /tmp/readback.bin 00000000 97 81 b7 3b 0e 38 2b 4d a7 d3 e0 47 ff c2 4b ca <---. 00000010 c1 85 98 f0 4a ac 03 a0 3b ab f3 19 44 dd 06 8b <-. | ... | | 00000100 97 81 b7 3b 0e 38 2b 4d a7 d3 e0 47 ff c2 4b ca <-:-+- repeated 00000110 c1 85 98 f0 4a ac 03 a0 3b ab f3 19 44 dd 06 8b <-+--- data ^^^ | '--- Repeat starts at offset 0x100 = 256 Bytes Fixes: 0d0c551011df ("PCI: rcar-gen4: Add R-Car Gen4 PCIe controller support for host mode") Signed-off-by: Marek Vasut Signed-off-by: Manivannan Sadhasivam Signed-off-by: Bjorn Helgaas Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20260821020636.111719-1-marek.vasut+renesas@mailbox.org --- drivers/pci/controller/dwc/pcie-rcar-gen4.c | 54 +++++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/drivers/pci/controller/dwc/pcie-rcar-gen4.c b/drivers/pci/controller/dwc/pcie-rcar-gen4.c index fbe465a29068..241adcd72789 100644 --- a/drivers/pci/controller/dwc/pcie-rcar-gen4.c +++ b/drivers/pci/controller/dwc/pcie-rcar-gen4.c @@ -411,6 +411,57 @@ static int rcar_gen4_pcie_host_msi_init(struct dw_pcie_rp *pp) return ret; } +static int rcar_gen4_pcie_enable_device(struct pci_host_bridge *bridge, + struct pci_dev *dev) +{ + /* + * R-Car Gen4 PCIe controller has a hardware limitation of 256 Bytes + * Max_Payload_Size (MPS). PCIe specification indicates that the MPS + * must not exceed minimum MPS of any element along the packet path. + * The controller reports Max_Payload_Size_Supported (MPSS) 256 Bytes + * for header type 0 and 128 Bytes for header type 1. The PCIe core + * will not allow MPS to be set higher than MPSS; warn here in case + * something went very wrong in the core. + * + * For details, refer to chapter "104.1.1 Features" in either of: + * R-Car S4 R19UH0161EJ0140 Rev.1.40 Jul. 31, 2026 or + * R-Car V4H R19UH0186EJ0140 Rev.1.40 Aug. 7, 2026 or + * R-Car V4M R19UH0217EJ0110 Rev.1.10 Jun. 30, 2026. + */ + WARN_ON(pcie_get_mps(dev) > 256); + + /* + * R-Car Gen4 Reference Manual, chapter 104.4.8 Usage notes for + * MRRS (Max Read Request Size) states: + * + * Please set "Max Read Request Size" to 128 bytes or 256 bytes. + * If "Max Read Request Size" is set to anything other than the + * above, the transferred data will not match the expected value. + * + * This limitation also seems to apply to devices issuing MRd TLPs. + * This limitation can be triggered by using non-HMB NVMe SSD with + * Max_Read_Request_Size 512 Bytes, for example Crucial P5 Plus. + * Any write into the SSD (MRd TLP issued by the SSD) longer than + * 256 Bytes wraps around at 256 Byte boundary, and the same data + * are written into the SSD starting at offset 0 and at 256 Bytes. + * + * Limit Max_Read_Request_Size to at most 256 Bytes for each + * device connected to this PCIe controller to avoid this behavior. + * + * For details, refer to aforementioned chapter in either of: + * R-Car S4 R19UH0161EJ0140 Rev.1.40 Jul. 31, 2026 or + * R-Car V4H R19UH0186EJ0140 Rev.1.40 Aug. 7, 2026 or + * R-Car V4M R19UH0217EJ0110 Rev.1.10 Jun. 30, 2026. + */ + bridge->no_inc_mrrs = 1; + if (pcie_get_readrq(dev) > 256) { + pci_info(dev, "Limiting MRRS to 256 bytes\n"); + pcie_set_readrq(dev, 256); + } + + return 0; +} + /* Host mode */ static int rcar_gen4_pcie_host_init(struct dw_pcie_rp *pp) { @@ -418,6 +469,9 @@ static int rcar_gen4_pcie_host_init(struct dw_pcie_rp *pp) struct rcar_gen4_pcie *rcar = to_rcar_gen4_pcie(dw); int ret; + if (pp->bridge) + pp->bridge->enable_device = rcar_gen4_pcie_enable_device; + gpiod_set_value_cansleep(dw->pe_rst, 1); ret = rcar_gen4_pcie_common_init(rcar); From 355039f010cee6d90bc76bc1bccf2afe12d8d461 Mon Sep 17 00:00:00 2001 From: Marek Vasut Date: Fri, 21 Aug 2026 14:27:49 +0200 Subject: [PATCH 013/139] PCI: dwc: Add dw_pcie_ep_ops->post_deinit() callback If .pre_init() succeeds but subsequent initialization fails, it is mandatory to correctly undo the hardware configuration which the .pre_init() callback did. Introduce a .post_deinit() callback to do exactly that, undo what the .pre_init() callback did. Usually, that means stop the clock, assert reset, and possibly program some registers to quiesce the hardware. Invoke the .post_deinit() callback in dw_pcie_ep_deinit() to retain functional symmetry. Signed-off-by: Marek Vasut Signed-off-by: Manivannan Sadhasivam Signed-off-by: Bjorn Helgaas Tested-by: Koichiro Den Reviewed-by: Koichiro Den Link: https://patch.msgid.link/20260821122808.238248-1-marek.vasut+renesas@mailbox.org --- drivers/pci/controller/dwc/pcie-designware-ep.c | 8 +++++++- drivers/pci/controller/dwc/pcie-designware.h | 1 + 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/drivers/pci/controller/dwc/pcie-designware-ep.c b/drivers/pci/controller/dwc/pcie-designware-ep.c index de8ee3db4360..1a3491b5003e 100644 --- a/drivers/pci/controller/dwc/pcie-designware-ep.c +++ b/drivers/pci/controller/dwc/pcie-designware-ep.c @@ -1194,6 +1194,9 @@ void dw_pcie_ep_deinit(struct dw_pcie_ep *ep) epc->mem->window.page_size); pci_epc_mem_exit(epc); + + if (ep->ops->post_deinit) + ep->ops->post_deinit(ep); } EXPORT_SYMBOL_GPL(dw_pcie_ep_deinit); @@ -1553,7 +1556,7 @@ int dw_pcie_ep_init(struct dw_pcie_ep *ep) ep->page_size); if (ret < 0) { dev_err(dev, "Failed to initialize address space\n"); - return ret; + goto err_deinit; } ep->msi_mem = pci_epc_mem_alloc_addr(epc, &ep->msi_mem_phys, @@ -1568,6 +1571,9 @@ int dw_pcie_ep_init(struct dw_pcie_ep *ep) err_exit_epc_mem: pci_epc_mem_exit(epc); +err_deinit: + if (ep->ops->post_deinit) + ep->ops->post_deinit(ep); return ret; } diff --git a/drivers/pci/controller/dwc/pcie-designware.h b/drivers/pci/controller/dwc/pcie-designware.h index 0735ae940924..a53ac27cd244 100644 --- a/drivers/pci/controller/dwc/pcie-designware.h +++ b/drivers/pci/controller/dwc/pcie-designware.h @@ -475,6 +475,7 @@ struct dw_pcie_rp { struct dw_pcie_ep_ops { int (*pre_init)(struct dw_pcie_ep *ep); + void (*post_deinit)(struct dw_pcie_ep *ep); int (*init)(struct dw_pcie_ep *ep); int (*raise_irq)(struct dw_pcie_ep *ep, u8 func_no, unsigned int type, u16 interrupt_num); From 0b4b2674688c6a719e93f0e52ddaf6432847bc1a Mon Sep 17 00:00:00 2001 From: Marek Vasut Date: Fri, 21 Aug 2026 14:27:50 +0200 Subject: [PATCH 014/139] PCI: rcar-gen4: Use .post_deinit() to handle dw_pcie_ep_init() failures Implement .post_deinit() callback in R-Car Gen4 'struct dw_pcie_ep_ops' which asserts reset and stops the clock. This undoes start of clock and deassert of reset performed in .pre_init(), in case dw_pcie_ep_init() fails after successful call of .pre_init() callback. The use of .post_deinit() callback correctly handles the clock and reset unwind, unlike the call of rcar_gen4_pcie_ep_deinit() in dw_pcie_ep_init() which could not discern at which point the dw_pcie_ep_init() failed and can attempt to stop clock and assert reset twice. Since dw_pcie_ep_deinit() also invokes the .post_deinit() callback, drop calls to rcar_gen4_pcie_ep_deinit() in both rcar_gen4_add_dw_pcie_ep() and dw_pcie_ep_init_registers() fail paths and rcar_gen4_remove_dw_pcie_ep() to avoid duplicate stop of clock and assert of reset, and drop no longer used rcar_gen4_pcie_ep_deinit() entirely. Initialize PCIEDMAINTSTSEN early in rcar_gen4_pcie_ep_pre_init() to 0, to make sure that edma_int bits will never be left set in case of failure of dw_pcie_ep_init(), and will only be set in case dw_pcie_ep_init() succeeds. Signed-off-by: Marek Vasut Signed-off-by: Manivannan Sadhasivam Signed-off-by: Bjorn Helgaas Tested-by: Koichiro Den Reviewed-by: Koichiro Den Link: https://patch.msgid.link/20260821122808.238248-2-marek.vasut+renesas@mailbox.org --- drivers/pci/controller/dwc/pcie-rcar-gen4.c | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/drivers/pci/controller/dwc/pcie-rcar-gen4.c b/drivers/pci/controller/dwc/pcie-rcar-gen4.c index 241adcd72789..5a076aa3f490 100644 --- a/drivers/pci/controller/dwc/pcie-rcar-gen4.c +++ b/drivers/pci/controller/dwc/pcie-rcar-gen4.c @@ -541,6 +541,8 @@ static int rcar_gen4_pcie_ep_pre_init(struct dw_pcie_ep *ep) struct rcar_gen4_pcie *rcar = to_rcar_gen4_pcie(dw); int ret; + writel(0, rcar->base + PCIEDMAINTSTSEN); + ret = rcar_gen4_pcie_common_init(rcar); if (ret) return ret; @@ -550,8 +552,11 @@ static int rcar_gen4_pcie_ep_pre_init(struct dw_pcie_ep *ep) return 0; } -static void rcar_gen4_pcie_ep_deinit(struct rcar_gen4_pcie *rcar) +static void rcar_gen4_pcie_ep_post_deinit(struct dw_pcie_ep *ep) { + struct dw_pcie *dw = to_dw_pcie_from_ep(ep); + struct rcar_gen4_pcie *rcar = to_rcar_gen4_pcie(dw); + writel(0, rcar->base + PCIEDMAINTSTSEN); rcar_gen4_pcie_common_deinit(rcar); } @@ -606,6 +611,7 @@ static unsigned int rcar_gen4_pcie_ep_get_dbi2_offset(struct dw_pcie_ep *ep, static const struct dw_pcie_ep_ops pcie_ep_ops = { .pre_init = rcar_gen4_pcie_ep_pre_init, + .post_deinit = rcar_gen4_pcie_ep_post_deinit, .raise_irq = rcar_gen4_pcie_ep_raise_irq, .get_features = rcar_gen4_pcie_ep_get_features, .get_dbi_offset = rcar_gen4_pcie_ep_get_dbi_offset, @@ -624,16 +630,13 @@ static int rcar_gen4_add_dw_pcie_ep(struct rcar_gen4_pcie *rcar) ep->ops = &pcie_ep_ops; ret = dw_pcie_ep_init(ep); - if (ret) { - rcar_gen4_pcie_ep_deinit(rcar); + if (ret) return ret; - } ret = dw_pcie_ep_init_registers(ep); if (ret) { dev_err(dev, "Failed to initialize DWC endpoint registers\n"); dw_pcie_ep_deinit(ep); - rcar_gen4_pcie_ep_deinit(rcar); } pci_epc_init_notify(ep->epc); @@ -644,7 +647,6 @@ static int rcar_gen4_add_dw_pcie_ep(struct rcar_gen4_pcie *rcar) static void rcar_gen4_remove_dw_pcie_ep(struct rcar_gen4_pcie *rcar) { dw_pcie_ep_deinit(&rcar->dw.ep); - rcar_gen4_pcie_ep_deinit(rcar); } /* Common */ From 028f86457e822ab9de5b0d6d23efddc1b8b54ae1 Mon Sep 17 00:00:00 2001 From: Abel Vesa Date: Tue, 25 Aug 2026 14:41:31 +0300 Subject: [PATCH 015/139] PCI/pwrctrl: tc9563: Allow RESX reset assertion to sleep The RESX GPIO may be provided by a sleeping GPIO controller, such as an I2C or SPI I/O expander. Calling gpiod_set_value() for such a GPIO triggers the following warning: WARNING: ... drivers/gpio/gpiolib.c:4030 at gpiod_set_value() The TC9563 power-on and power-off callbacks already sleep while enabling or disabling regulators and while waiting for oscillator stabilization. Use gpiod_set_value_cansleep() for RESX so it can be driven without triggering the warning. Fixes: 4c9c7be47310 ("PCI: pwrctrl: Add power control driver for TC9563") Signed-off-by: Abel Vesa Signed-off-by: Bjorn Helgaas Reviewed-by: Manivannan Sadhasivam Reviewed-by: Bartosz Golaszewski Link: https://patch.msgid.link/20260825-pci-pwrctl-tc9563-use-cansleep-for-resx-v1-1-0dbe7cfd8200@oss.qualcomm.com --- drivers/pci/pwrctrl/pci-pwrctrl-tc9563.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/pci/pwrctrl/pci-pwrctrl-tc9563.c b/drivers/pci/pwrctrl/pci-pwrctrl-tc9563.c index b0d8912690a2..59ad219c26c0 100644 --- a/drivers/pci/pwrctrl/pci-pwrctrl-tc9563.c +++ b/drivers/pci/pwrctrl/pci-pwrctrl-tc9563.c @@ -398,7 +398,7 @@ static int tc9563_pwrctrl_power_off(struct pci_pwrctrl *pwrctrl) struct tc9563_pwrctrl *tc9563 = container_of(pwrctrl, struct tc9563_pwrctrl, pwrctrl); - gpiod_set_value(tc9563->reset_gpio, 1); + gpiod_set_value_cansleep(tc9563->reset_gpio, 1); regulator_bulk_disable(ARRAY_SIZE(tc9563->supplies), tc9563->supplies); @@ -418,7 +418,7 @@ static int tc9563_pwrctrl_power_on(struct pci_pwrctrl *pwrctrl) if (ret < 0) return dev_err_probe(dev, ret, "cannot enable regulators\n"); - gpiod_set_value(tc9563->reset_gpio, 0); + gpiod_set_value_cansleep(tc9563->reset_gpio, 0); fsleep(TC9563_OSC_STAB_DELAY_US); From f7c2204047009dd488ef05288c8cc9a8d70c058a Mon Sep 17 00:00:00 2001 From: LeoLiu-oc Date: Tue, 1 Sep 2026 16:36:53 +0800 Subject: [PATCH 016/139] PCI/P2PDMA: Add Zhaoxin host bridges to allowlist Zhaoxin host bridges support peer-to-peer DMA. Add their device IDs (0x1003, 0x1005-0x1008) to the allowlist so p2pdma works on these platforms. The entries cover the KX-6000, KH-40000, KX-6000G, KX-7000 and KH-50000 families. Mark the KX-6000/KH-40000/KX-6000G/KX-7000 entries REQ_SAME_HOST_BRIDGE because P2P requests are only supported between devices below the same host bridge. KH-50000 doesn't have this restriction. Signed-off-by: LeoLiu-oc Signed-off-by: Bjorn Helgaas Link: https://patch.msgid.link/20260901083653.48890-1-leoliu-oc@zhaoxin.com --- drivers/pci/p2pdma.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/pci/p2pdma.c b/drivers/pci/p2pdma.c index 9334eb314663..9fbbf1692f0e 100644 --- a/drivers/pci/p2pdma.c +++ b/drivers/pci/p2pdma.c @@ -569,6 +569,12 @@ static const struct pci_p2pdma_whitelist_entry { {PCI_VENDOR_ID_NVIDIA, 0x2f96, 0}, {PCI_VENDOR_ID_NVIDIA, 0x2f97, 0}, {PCI_VENDOR_ID_NVIDIA, 0x2f98, 0}, + /* Zhaoxin KX-6000/KH-40000/KX-6000G/KX-7000/KH-50000 */ + {PCI_VENDOR_ID_ZHAOXIN, 0x1003, REQ_SAME_HOST_BRIDGE}, + {PCI_VENDOR_ID_ZHAOXIN, 0x1005, REQ_SAME_HOST_BRIDGE}, + {PCI_VENDOR_ID_ZHAOXIN, 0x1006, REQ_SAME_HOST_BRIDGE}, + {PCI_VENDOR_ID_ZHAOXIN, 0x1007, REQ_SAME_HOST_BRIDGE}, + {PCI_VENDOR_ID_ZHAOXIN, 0x1008, 0}, {} }; From 13335fca4c23b1a6736ec9c7efc3750154a2ca24 Mon Sep 17 00:00:00 2001 From: Sreeraj S Kurup Date: Tue, 28 Jul 2026 12:28:10 +0000 Subject: [PATCH 017/139] PCI: mediatek-gen3: Fix 64-bit type truncation in mtk_pcie_set_trans_table() The variables cpu_addr, pci_addr, and remaining in mtk_pcie_set_trans_table() are of type resource_size_t, which is 64-bit on 64-bit architectures. Using 32-bit helpers fls(), ffs(), and BIT() causes 64-bit integer truncation when computing window sizes and address alignments above 4GB. Fix this by switching to fls64(), __ffs64(), and BIT_ULL(). Note that __ffs64() uses 0-based indexing, so the '- 1' offset is removed. Additionally, return -ENOMEM when translation tables are exhausted to prevent silent failure. Signed-off-by: Sreeraj S Kurup Signed-off-by: Manivannan Sadhasivam Link: https://patch.msgid.link/20260728122810.3475-1-sreekuttan2156239@gmail.com --- drivers/pci/controller/pcie-mediatek-gen3.c | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/drivers/pci/controller/pcie-mediatek-gen3.c b/drivers/pci/controller/pcie-mediatek-gen3.c index bcbd08c82a5c..e2fb6e372ede 100644 --- a/drivers/pci/controller/pcie-mediatek-gen3.c +++ b/drivers/pci/controller/pcie-mediatek-gen3.c @@ -7,6 +7,8 @@ */ #include +#include +#include #include #include #include @@ -334,10 +336,10 @@ static int mtk_pcie_set_trans_table(struct mtk_gen3_pcie *pcie, while (remaining && (*num < PCIE_MAX_TRANS_TABLES)) { /* Table size needs to be a power of 2 */ - table_size = BIT(fls(remaining) - 1); + table_size = BIT_ULL(fls64(remaining) - 1); if (cpu_addr > 0) { - addr_align = BIT(ffs(cpu_addr) - 1); + addr_align = BIT_ULL(__ffs64(cpu_addr)); table_size = min(table_size, addr_align); } @@ -349,7 +351,8 @@ static int mtk_pcie_set_trans_table(struct mtk_gen3_pcie *pcie, } table = pcie->base + PCIE_TRANS_TABLE_BASE_REG + *num * PCIE_ATR_TLB_SET_OFFSET; - writel_relaxed(lower_32_bits(cpu_addr) | PCIE_ATR_SIZE(fls(table_size) - 1), table); + writel_relaxed(lower_32_bits(cpu_addr) | + PCIE_ATR_SIZE(fls64(table_size) - 1), table); writel_relaxed(upper_32_bits(cpu_addr), table + PCIE_ATR_SRC_ADDR_MSB_OFFSET); writel_relaxed(lower_32_bits(pci_addr), table + PCIE_ATR_TRSL_ADDR_LSB_OFFSET); writel_relaxed(upper_32_bits(pci_addr), table + PCIE_ATR_TRSL_ADDR_MSB_OFFSET); @@ -375,9 +378,11 @@ static int mtk_pcie_set_trans_table(struct mtk_gen3_pcie *pcie, (*num)++; } - if (remaining) - dev_warn(pcie->dev, "not enough translate table for addr: %#llx, limited to [%d]\n", + if (remaining) { + dev_err(pcie->dev, "not enough translate table for addr: %#llx, limited to [%d]\n", (unsigned long long)cpu_addr, PCIE_MAX_TRANS_TABLES); + return -ENOMEM; + } return 0; } From b9022584ebbae81f7bbe4a61bd1df0d0a95e1a99 Mon Sep 17 00:00:00 2001 From: Yuho Choi Date: Sun, 2 Aug 2026 18:11:46 -0400 Subject: [PATCH 018/139] PCI: xgene: Use managed clock for PCIe controller xgene_pcie_init_port() obtains and enables the controller clock manually. When xgene_pcie_setup() or pci_host_probe() fails, probe returns without disabling, unpreparing, or releasing the clock. A failed enable also leaves the clk_get() reference unreleased. Use devm_clk_get_enabled() so the clock is unwound automatically on probe failure and device removal. Fixes: 5f6b6ccdbe1c ("PCI: xgene: Add APM X-Gene PCIe driver") Signed-off-by: Yuho Choi Signed-off-by: Manivannan Sadhasivam Link: https://patch.msgid.link/20260802221146.573233-1-dbgh9129@gmail.com --- drivers/pci/controller/pci-xgene.c | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/drivers/pci/controller/pci-xgene.c b/drivers/pci/controller/pci-xgene.c index 83c9a2930eec..a5b8bbc04a22 100644 --- a/drivers/pci/controller/pci-xgene.c +++ b/drivers/pci/controller/pci-xgene.c @@ -320,18 +320,11 @@ static void xgene_pcie_linkup(struct xgene_pcie *port, static int xgene_pcie_init_port(struct xgene_pcie *port) { struct device *dev = port->dev; - int rc; - port->clk = clk_get(dev, NULL); + port->clk = devm_clk_get_enabled(dev, NULL); if (IS_ERR(port->clk)) { dev_err(dev, "clock not available\n"); - return -ENODEV; - } - - rc = clk_prepare_enable(port->clk); - if (rc) { - dev_err(dev, "clock enable failed\n"); - return rc; + return PTR_ERR(port->clk); } return 0; From 52cbf88730b743745404b3fa5a3fc8b480768276 Mon Sep 17 00:00:00 2001 From: Zhengping Zhang Date: Wed, 5 Aug 2026 14:05:07 +0800 Subject: [PATCH 019/139] PCI: mediatek: Find INTx controller by property All existing DTS users already name the INTx child node "interrupt-controller". Use of_get_child_by_name() to find it instead of assuming it is the first child node. Signed-off-by: Zhengping Zhang Signed-off-by: Manivannan Sadhasivam Link: https://patch.msgid.link/tencent_5E7D6E7FB25B4CD732F08547C11F5DA24808@qq.com --- drivers/pci/controller/pcie-mediatek.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/pci/controller/pcie-mediatek.c b/drivers/pci/controller/pcie-mediatek.c index a60d1ae076f8..3a3e91cd0c21 100644 --- a/drivers/pci/controller/pcie-mediatek.c +++ b/drivers/pci/controller/pcie-mediatek.c @@ -583,9 +583,9 @@ static int mtk_pcie_init_irq_domain(struct mtk_pcie_port *port, int ret; /* Setup INTx */ - pcie_intc_node = of_get_next_child(node, NULL); + pcie_intc_node = of_get_child_by_name(node, "interrupt-controller"); if (!pcie_intc_node) { - dev_err(dev, "no PCIe Intc node found\n"); + dev_err(dev, "no PCIe INTx interrupt-controller node found\n"); return -ENODEV; } From f03f56f21ce0770f38f3251924a0ca98699c38ed Mon Sep 17 00:00:00 2001 From: Lad Prabhakar Date: Tue, 11 Aug 2026 21:00:11 +0100 Subject: [PATCH 020/139] PCI: rzg3s: Disable refclk on probe failure rzg3s_pcie_host_init_port() enables the PCIe reference clock using clk_prepare_enable(), but if rzg3s_pcie_probe() subsequently fails, for example because pci_host_probe() returns an error, the probe error path only releases the clock handle with clk_put(). Balance clk_prepare_enable() by calling clk_disable_unprepare() in the probe cleanup path before releasing the clock, ensuring the reference clock is properly disabled on probe failure. Fixes: 7ef502fb35b28 ("PCI: Add Renesas RZ/G3S host controller driver") Signed-off-by: Lad Prabhakar Signed-off-by: Manivannan Sadhasivam Reviewed-by: Claudiu Beznea Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20260811200013.185956-2-prabhakar.mahadev-lad.rj@bp.renesas.com --- drivers/pci/controller/pcie-rzg3s-host.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/pci/controller/pcie-rzg3s-host.c b/drivers/pci/controller/pcie-rzg3s-host.c index a1a78bdb7af1..01488660ab08 100644 --- a/drivers/pci/controller/pcie-rzg3s-host.c +++ b/drivers/pci/controller/pcie-rzg3s-host.c @@ -1897,6 +1897,7 @@ static int rzg3s_pcie_probe(struct platform_device *pdev) return 0; host_probe_teardown: + clk_disable_unprepare(host->port.refclk); rzg3s_pcie_teardown_irqdomain(host); host->data->config_deinit(host); rpm_put: From 49daf16c881bc91a55355a3e7f03471c2b213255 Mon Sep 17 00:00:00 2001 From: Lad Prabhakar Date: Tue, 11 Aug 2026 21:00:12 +0100 Subject: [PATCH 021/139] PCI: rzg3s: Propagate platform_get_irq_byname() errors platform_get_irq_byname() may return specific error codes such as -EPROBE_DEFER, but rzg3s_pcie_init_irqdomain() replaces all failures with -EINVAL. This breaks probe deferral by preventing the driver core from retrying probe once the interrupt provider becomes available. Propagate the original error code instead. Fixes: 7ef502fb35b28 ("PCI: Add Renesas RZ/G3S host controller driver") Signed-off-by: Lad Prabhakar Signed-off-by: Manivannan Sadhasivam Reviewed-by: Claudiu Beznea Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20260811200013.185956-3-prabhakar.mahadev-lad.rj@bp.renesas.com --- drivers/pci/controller/pcie-rzg3s-host.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/pci/controller/pcie-rzg3s-host.c b/drivers/pci/controller/pcie-rzg3s-host.c index 01488660ab08..f51b5d7c1998 100644 --- a/drivers/pci/controller/pcie-rzg3s-host.c +++ b/drivers/pci/controller/pcie-rzg3s-host.c @@ -1019,7 +1019,7 @@ static int rzg3s_pcie_init_irqdomain(struct rzg3s_pcie_host *host) irq = platform_get_irq_byname(pdev, irq_name); if (irq < 0) - return dev_err_probe(dev, -EINVAL, + return dev_err_probe(dev, irq, "Failed to parse and map INT%c IRQ\n", 'A' + i); From 5d9e9b3a27994a2fadce283801b7104a68ea4c48 Mon Sep 17 00:00:00 2001 From: Lad Prabhakar Date: Tue, 11 Aug 2026 21:00:13 +0100 Subject: [PATCH 022/139] PCI: rzg3s: Fix IRQ domain initialization error handling rzg3s_pcie_init_irqdomain() installs chained handlers for the INTx parent interrupts before creating the INTx irqdomain and initializing MSI support. If any subsequent step fails, such as obtaining an INTx IRQ, creating the irqdomain, or initializing MSI, the error path returns without removing any chained handlers that were already installed. This leaves stale handler and data pointers attached to the parent interrupts. Add a helper to tear down the INTx IRQ setup, including removing the IRQ domain and clearing the chained handlers. Use it to unwind partially completed initialization and from the normal IRQ domain teardown path. Fixes: 7ef502fb35b28 ("PCI: Add Renesas RZ/G3S host controller driver") Signed-off-by: Lad Prabhakar Signed-off-by: Manivannan Sadhasivam Reviewed-by: Claudiu Beznea Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20260811200013.185956-4-prabhakar.mahadev-lad.rj@bp.renesas.com --- drivers/pci/controller/pcie-rzg3s-host.c | 50 +++++++++++++++++------- 1 file changed, 36 insertions(+), 14 deletions(-) diff --git a/drivers/pci/controller/pcie-rzg3s-host.c b/drivers/pci/controller/pcie-rzg3s-host.c index f51b5d7c1998..3f7393c49a08 100644 --- a/drivers/pci/controller/pcie-rzg3s-host.c +++ b/drivers/pci/controller/pcie-rzg3s-host.c @@ -1006,22 +1006,38 @@ static const struct irq_domain_ops rzg3s_pcie_intx_domain_ops = { .xlate = irq_domain_xlate_onetwocell, }; +static void rzg3s_pcie_teardown_intx(struct rzg3s_pcie_host *host, + int count) +{ + while (--count >= 0) { + irq_set_chained_handler_and_data(host->intx_irqs[count], NULL, + NULL); + } + + if (host->intx_domain) + irq_domain_remove(host->intx_domain); +} + static int rzg3s_pcie_init_irqdomain(struct rzg3s_pcie_host *host) { struct device *dev = host->dev; struct platform_device *pdev = to_platform_device(dev); + int i, ret; - for (int i = 0; i < PCI_NUM_INTX; i++) { + for (i = 0; i < PCI_NUM_INTX; i++) { char irq_name[5] = {0}; int irq; scnprintf(irq_name, ARRAY_SIZE(irq_name), "int%c", 'a' + i); irq = platform_get_irq_byname(pdev, irq_name); - if (irq < 0) - return dev_err_probe(dev, irq, - "Failed to parse and map INT%c IRQ\n", - 'A' + i); + if (irq < 0) { + ret = irq; + dev_err_probe(dev, ret, + "Failed to parse and map INT%c IRQ\n", + 'A' + i); + goto teardown_intx; + } host->intx_irqs[i] = irq; irq_set_chained_handler_and_data(irq, @@ -1033,21 +1049,27 @@ static int rzg3s_pcie_init_irqdomain(struct rzg3s_pcie_host *host) PCI_NUM_INTX, &rzg3s_pcie_intx_domain_ops, host); - if (!host->intx_domain) - return dev_err_probe(dev, -EINVAL, - "Failed to add irq domain for INTx IRQs\n"); + if (!host->intx_domain) { + ret = -EINVAL; + dev_err_probe(dev, ret, + "Failed to add irq domain for INTx IRQs\n"); + goto teardown_intx; + } irq_domain_update_bus_token(host->intx_domain, DOMAIN_BUS_WIRED); if (IS_ENABLED(CONFIG_PCI_MSI)) { - int ret = rzg3s_pcie_init_msi(host); + ret = rzg3s_pcie_init_msi(host); - if (ret) { - irq_domain_remove(host->intx_domain); - return ret; - } + if (ret) + goto teardown_intx; } return 0; + +teardown_intx: + rzg3s_pcie_teardown_intx(host, i); + + return ret; } static void rzg3s_pcie_teardown_irqdomain(struct rzg3s_pcie_host *host) @@ -1055,7 +1077,7 @@ static void rzg3s_pcie_teardown_irqdomain(struct rzg3s_pcie_host *host) if (IS_ENABLED(CONFIG_PCI_MSI)) rzg3s_pcie_teardown_msi(host); - irq_domain_remove(host->intx_domain); + rzg3s_pcie_teardown_intx(host, PCI_NUM_INTX); } static int rzg3s_pcie_set_max_link_speed(struct rzg3s_pcie_host *host) From 84c3520c9e8c14b09a5cf645cc4fa7c6d9d61127 Mon Sep 17 00:00:00 2001 From: Koichiro Den Date: Tue, 1 Sep 2026 15:32:37 +0900 Subject: [PATCH 023/139] PCI: endpoint: pci-epf-vntb: Serialize virtual PCI bus scan The virtual PCI bus is scanned without holding the PCI rescan/remove lock. This can race with other PCI topology changes. Hold the lock across the bus scan and device addition. Fixes: e35f56bb0330 ("PCI: endpoint: Support NTB transfer between RC and EP") Suggested-by: Manivannan Sadhasivam Signed-off-by: Koichiro Den Signed-off-by: Manivannan Sadhasivam Reviewed-by: Frank Li Link: https://lore.kernel.org/r/3tgrcel5fl55ulf3zjya4fkz4t2ms7iwwjif5rnzn2rafsydd6@i4e5etm7uqvz/ Link: https://patch.msgid.link/20260901063238.631166-2-den@valinux.co.jp --- drivers/pci/endpoint/functions/pci-epf-vntb.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/pci/endpoint/functions/pci-epf-vntb.c b/drivers/pci/endpoint/functions/pci-epf-vntb.c index fba65abfb6b2..544e00bdf50f 100644 --- a/drivers/pci/endpoint/functions/pci-epf-vntb.c +++ b/drivers/pci/endpoint/functions/pci-epf-vntb.c @@ -1332,14 +1332,19 @@ static int vpci_scan_bus(void *sysdata) struct pci_bus *vpci_bus; struct epf_ntb *ndev = sysdata; + pci_lock_rescan_remove(); + vpci_bus = pci_scan_bus(ndev->vbus_number, &vpci_ops, sysdata); if (!vpci_bus) { pr_err("create pci bus failed\n"); + pci_unlock_rescan_remove(); return -EINVAL; } pci_bus_add_devices(vpci_bus); + pci_unlock_rescan_remove(); + return 0; } From 55f914fdc951fe86a4845dedaf7ffe8e9ff66574 Mon Sep 17 00:00:00 2001 From: Biju Das Date: Thu, 3 Sep 2026 14:33:10 +0100 Subject: [PATCH 024/139] dt-bindings: PCI: renesas,r9a08g045s33-pcie: Document RZ/G3L SoC Extend the existing device tree bindings for Renesas RZ/G3S PCIe controller to include support for the RZ/G3L (renesas,r9a08g046-pcie) PCIe controller. The RZ/G3L PCIe controller is similar to RZ/G3S but has some key differences: - Has 3 clocks compared to 2 on other SoCs. - Has a different set of reset signals Add device tree bindings for renesas,r9a08g046-pcie compatible IPs. Signed-off-by: Biju Das Signed-off-by: Manivannan Sadhasivam Reviewed-by: Krzysztof Kozlowski Reviewed-by: Geert Uytterhoeven Link: https://patch.msgid.link/20260903133320.144245-2-biju.das.jz@bp.renesas.com --- .../bindings/pci/renesas,r9a08g045-pcie.yaml | 33 ++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/Documentation/devicetree/bindings/pci/renesas,r9a08g045-pcie.yaml b/Documentation/devicetree/bindings/pci/renesas,r9a08g045-pcie.yaml index 09d78bd1a577..7827d81680fe 100644 --- a/Documentation/devicetree/bindings/pci/renesas,r9a08g045-pcie.yaml +++ b/Documentation/devicetree/bindings/pci/renesas,r9a08g045-pcie.yaml @@ -13,7 +13,7 @@ description: | PCIe host controller found in Renesas RZ/G3S and similar SoCs complies with PCIe Base Specification 4.0 and supports different link speeds depending on the SoC variant: - - Gen2 (5 GT/s): RZ/G3S + - Gen2 (5 GT/s): RZ/G3{L,S} - Gen3 (8 GT/s): RZ/G3E, RZ/V2H(P), RZ/V2N properties: @@ -21,6 +21,7 @@ properties: oneOf: - enum: - renesas,r9a08g045-pcie # RZ/G3S + - renesas,r9a08g046-pcie # RZ/G3L - renesas,r9a09g047-pcie # RZ/G3E - renesas,r9a09g057-pcie # RZ/V2H(P) - items: @@ -87,14 +88,18 @@ properties: interrupt-controller: true clocks: + minItems: 2 items: - description: System clock - description: PM control clock or clock for L1 substate handling + - description: PM control clock clock-names: + minItems: 2 items: - const: aclk - enum: [pm, pmu] + - const: pmu resets: minItems: 1 @@ -215,6 +220,8 @@ allOf: maxItems: 16 interrupt-names: maxItems: 16 + clocks: + maxItems: 2 clock-names: items: - const: aclk @@ -223,6 +230,28 @@ allOf: minItems: 7 reset-names: minItems: 7 + - if: + properties: + compatible: + contains: + const: renesas,r9a08g046-pcie + then: + properties: + interrupts: + maxItems: 16 + interrupt-names: + maxItems: 16 + clocks: + minItems: 3 + clock-names: + items: + - const: aclk + - const: pm + - const: pmu + resets: + maxItems: 1 + reset-names: + maxItems: 1 - if: properties: compatible: @@ -236,6 +265,8 @@ allOf: minItems: 23 interrupt-names: minItems: 23 + clocks: + maxItems: 2 clock-names: items: - const: aclk From c56d39e0f68d49ee45e610714bcfc87e14208498 Mon Sep 17 00:00:00 2001 From: Biju Das Date: Thu, 3 Sep 2026 14:33:11 +0100 Subject: [PATCH 025/139] PCI: rzg3s-host: Add support for RZ/G3L SoC Add SoC-specific data for the RZ/G3L (R9A08G046) PCIe controller and register its "renesas,r9a08g046-pcie" compatible string. RZ/G3L reuses the RZ/G3E config init/deinit callbacks and power resets, with its own L1-allow syscon offset and mask. Also increase the timeout in rzg3s_pcie_set_max_link_speed() to fix the "Failed to set max link speed" message seen on RZ/G3L. Signed-off-by: Biju Das Signed-off-by: Manivannan Sadhasivam Reviewed-by: Geert Uytterhoeven Link: https://patch.msgid.link/20260903133320.144245-3-biju.das.jz@bp.renesas.com --- drivers/pci/controller/pcie-rzg3s-host.c | 25 +++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/drivers/pci/controller/pcie-rzg3s-host.c b/drivers/pci/controller/pcie-rzg3s-host.c index 3f7393c49a08..077cfb0834b3 100644 --- a/drivers/pci/controller/pcie-rzg3s-host.c +++ b/drivers/pci/controller/pcie-rzg3s-host.c @@ -1099,7 +1099,7 @@ static int rzg3s_pcie_set_max_link_speed(struct rzg3s_pcie_host *host) FIELD_GET(RZG3S_PCI_PCSTAT1_LTSSM_STATE, tmp) == ltssm_state_l0, PCIE_LINK_WAIT_SLEEP_MS * MILLI, PCIE_LINK_WAIT_SLEEP_MS * MILLI * - PCIE_LINK_WAIT_MAX_RETRIES); + PCIE_LINK_WAIT_MAX_RETRIES * 10); if (ret) return ret; @@ -2097,6 +2097,25 @@ static const struct rzg3s_pcie_soc_data rzg3e_soc_data = { }, }; +static const struct rzg3s_pcie_soc_data rzg3l_soc_data = { + .power_resets = rzg3e_soc_power_resets, + .num_power_resets = ARRAY_SIZE(rzg3e_soc_power_resets), + .num_pcie_controllers = 1, + .config_pre_init = rzg3e_pcie_config_pre_init, + .config_post_init = rzg3e_pcie_config_post_init, + .config_deinit = rzg3e_pcie_config_deinit, + .sysc_info = { + [RZG3S_PCIE_CONTROLLER_ID_0] = { + .functions = { + [RZG3S_SYSC_FUNC_ID_L1_ALLOW] = { + .offset = 0x3a0, + .mask = BIT(8), + }, + }, + }, + }, +}; + static const struct rzg3s_pcie_soc_data rzv2h_soc_data = { .power_resets = rzg3e_soc_power_resets, .num_power_resets = ARRAY_SIZE(rzg3e_soc_power_resets), @@ -2146,6 +2165,10 @@ static const struct of_device_id rzg3s_pcie_of_match[] = { .compatible = "renesas,r9a08g045-pcie", .data = &rzg3s_soc_data, }, + { + .compatible = "renesas,r9a08g046-pcie", + .data = &rzg3l_soc_data, + }, { .compatible = "renesas,r9a09g047-pcie", .data = &rzg3e_soc_data, From 5cdbd6d3a3a147fa9975e35f2602cbec9a72a932 Mon Sep 17 00:00:00 2001 From: Koichiro Den Date: Tue, 1 Sep 2026 15:32:38 +0900 Subject: [PATCH 026/139] PCI: endpoint: pci-epf-vntb: Manage virtual NTB and PCI bus lifetime The virtual PCI driver registers an ntb_dev but has no remove callback. Unbinding the endpoint function can therefore free BARs while the NTB device and its client still use them. It also leaves the virtual PCI devices and root bus allocated. Allocate an ntb_dev for each virtual PCI probe and unregister it from the matching remove callback. Start command processing only after registration. Publish the device for doorbell IRQs at the same point. During remove, stop the command work and drain IRQ handlers before unregistering the device. Retain the root bus returned by pci_scan_bus() so it can be removed on unbind. Unregister the virtual PCI driver before releasing endpoint resources. Stop and remove the root bus under the PCI rescan/remove lock, then release its host bridge. Fixes: e35f56bb0330 ("PCI: endpoint: Support NTB transfer between RC and EP") Signed-off-by: Koichiro Den Signed-off-by: Manivannan Sadhasivam Signed-off-by: Bjorn Helgaas Reviewed-by: Frank Li Cc: stable@vger.kernel.org # 6.0+ Link: https://patch.msgid.link/20260901063238.631166-3-den@valinux.co.jp --- drivers/pci/endpoint/functions/pci-epf-vntb.c | 85 +++++++++++++++---- 1 file changed, 70 insertions(+), 15 deletions(-) diff --git a/drivers/pci/endpoint/functions/pci-epf-vntb.c b/drivers/pci/endpoint/functions/pci-epf-vntb.c index 544e00bdf50f..07ba338732d4 100644 --- a/drivers/pci/endpoint/functions/pci-epf-vntb.c +++ b/drivers/pci/endpoint/functions/pci-epf-vntb.c @@ -132,7 +132,7 @@ struct epf_ntb_ctrl { } __packed; struct epf_ntb { - struct ntb_dev ntb; + struct ntb_dev *ntb; struct pci_epf *epf; struct config_group group; @@ -166,10 +166,15 @@ struct epf_ntb { void __iomem *vpci_mw_addr[MAX_MW]; struct delayed_work cmd_handler; + struct pci_bus *vpci_bus; }; #define to_epf_ntb(epf_group) container_of((epf_group), struct epf_ntb, group) -#define ntb_ndev(__ntb) container_of(__ntb, struct epf_ntb, ntb) + +static struct epf_ntb *ntb_ndev(struct ntb_dev *ntb) +{ + return ntb->pdev->sysdata; +} static struct pci_epf_header epf_ntb_header = { .vendorid = PCI_ANY_ID, @@ -195,7 +200,7 @@ static int epf_ntb_link_up(struct epf_ntb *ntb, bool link_up) else ntb->reg->link_status &= ~LINK_STATUS_UP; - ntb_link_event(&ntb->ntb); + ntb_link_event(ntb->ntb); return 0; } @@ -284,7 +289,7 @@ static void epf_ntb_cmd_handler(struct work_struct *work) i++) { if (ntb->epf_db[i]) { atomic64_or(1 << (i - EPF_IRQ_DB_START), &ntb->db); - ntb_db_event(&ntb->ntb, i - EPF_IRQ_DB_START); + ntb_db_event(ntb->ntb, i - EPF_IRQ_DB_START); ntb->epf_db[i] = 0; } } @@ -348,12 +353,18 @@ static void epf_ntb_cmd_handler(struct work_struct *work) static irqreturn_t epf_ntb_doorbell_handler(int irq, void *data) { struct epf_ntb *ntb = data; + struct ntb_dev *ndev; int i; + /* Pair with smp_store_release() in pci_vntb_probe() */ + ndev = smp_load_acquire(&ntb->ntb); + if (!ndev) + return IRQ_HANDLED; + for (i = EPF_IRQ_DB_START; i < ntb->db_count; i++) if (irq == ntb->epf->db_msg[i].virq) { atomic64_or(1 << (i - EPF_IRQ_DB_START), &ntb->db); - ntb_db_event(&ntb->ntb, i - EPF_IRQ_DB_START); + ntb_db_event(ndev, i - EPF_IRQ_DB_START); } return IRQ_HANDLED; @@ -985,7 +996,6 @@ static int epf_ntb_epc_init(struct epf_ntb *ntb) } INIT_DELAYED_WORK(&ntb->cmd_handler, epf_ntb_cmd_handler); - queue_work(kpcintb_workqueue, &ntb->cmd_handler.work); atomic64_set(&ntb->peer_db_pending, 0); enable_work(&ntb->peer_db_work); @@ -1340,6 +1350,7 @@ static int vpci_scan_bus(void *sysdata) pci_unlock_rescan_remove(); return -EINVAL; } + ndev->vpci_bus = vpci_bus; pci_bus_add_devices(vpci_bus); @@ -1425,7 +1436,7 @@ static int vntb_epf_mw_set_trans(struct ntb_dev *ndev, int pidx, int idx, int ret; struct device *dev; - dev = &ntb->ntb.dev; + dev = &ndev->dev; barno = ntb->epf_ntb_bar[BAR_MW1 + idx]; epf_bar = &ntb->epf->bar[barno]; epf_bar->phys_addr = addr; @@ -1564,7 +1575,7 @@ static void vntb_epf_peer_db_work(struct work_struct *work) ret = pci_epc_raise_irq(epf->epc, func_no, vfunc_no, PCI_IRQ_MSI, interrupt_num); if (ret) - dev_err(&ntb->ntb.dev, + dev_err(&epf->dev, "Failed to raise IRQ for interrupt_num %u: %d\n", interrupt_num, ret); } @@ -1682,13 +1693,18 @@ static const struct ntb_dev_ops vntb_epf_ops = { static int pci_vntb_probe(struct pci_dev *pdev, const struct pci_device_id *id) { - int ret; struct epf_ntb *ndev = (struct epf_ntb *)pdev->sysdata; struct device *dev = &pdev->dev; + struct ntb_dev *ntb; + int ret; - ndev->ntb.pdev = pdev; - ndev->ntb.topo = NTB_TOPO_NONE; - ndev->ntb.ops = &vntb_epf_ops; + ntb = devm_kzalloc(dev, sizeof(*ntb), GFP_KERNEL); + if (!ntb) + return -ENOMEM; + + ntb->pdev = pdev; + ntb->topo = NTB_TOPO_NONE; + ntb->ops = &vntb_epf_ops; ret = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(32)); if (ret) { @@ -1696,16 +1712,41 @@ static int pci_vntb_probe(struct pci_dev *pdev, const struct pci_device_id *id) return ret; } - ret = ntb_register_device(&ndev->ntb); + ret = ntb_register_device(ntb); if (ret) { dev_err(dev, "Failed to register NTB device\n"); return ret; } + /* Publish after ntb_register_device() succeeds */ + smp_store_release(&ndev->ntb, ntb); + queue_delayed_work(kpcintb_workqueue, &ndev->cmd_handler, 0); + dev_dbg(dev, "PCI Virtual NTB driver loaded\n"); return 0; } +static void pci_vntb_remove(struct pci_dev *pdev) +{ + struct epf_ntb *ndev = pdev->sysdata; + struct ntb_dev *ntb; + unsigned int i; + + /* Stop the work reader, then close and drain the IRQ gate */ + cancel_delayed_work_sync(&ndev->cmd_handler); + + ntb = xchg(&ndev->ntb, NULL); + + if (ndev->msi_doorbell) + for (i = 0; i < ndev->db_count; i++) { + if (epf_ntb_db_irq_is_duplicated(ndev->epf, i)) + continue; + synchronize_irq(ndev->epf->db_msg[i].virq); + } + + ntb_unregister_device(ntb); +} + static struct pci_device_id pci_vntb_table[] = { { PCI_DEVICE(0xffff, 0xffff), @@ -1717,6 +1758,7 @@ static struct pci_driver vntb_pci_driver = { .name = "pci-vntb", .id_table = pci_vntb_table, .probe = pci_vntb_probe, + .remove = pci_vntb_remove, }; /* ============ PCIe EPF Driver Bind ====================*/ @@ -1797,12 +1839,25 @@ static int epf_ntb_bind(struct pci_epf *epf) */ static void epf_ntb_unbind(struct pci_epf *epf) { + struct pci_host_bridge *bridge; struct epf_ntb *ntb = epf_get_drvdata(epf); + pci_unregister_driver(&vntb_pci_driver); + + if (ntb->vpci_bus) { + bridge = to_pci_host_bridge(ntb->vpci_bus->bridge); + + pci_lock_rescan_remove(); + pci_stop_root_bus(ntb->vpci_bus); + pci_remove_root_bus(ntb->vpci_bus); + ntb->vpci_bus = NULL; + pci_unlock_rescan_remove(); + + pci_free_host_bridge(bridge); + } + epf_ntb_epc_cleanup(ntb); epf_ntb_config_spad_bar_free(ntb); - - pci_unregister_driver(&vntb_pci_driver); } // EPF driver probe From 7bff67d3d686be285b31b8b3d2b3739a5d4402b4 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Thu, 20 Aug 2026 10:20:41 +0200 Subject: [PATCH 027/139] PCI: Add ACS quirk for Pericom PI7C9X2G304 switches [12d8:b304] According to the existing quirk the Pericom E5 errata for ACS P2P applies to PI7C9X2G404, PI7C9X2G304 and PI7C9X2G303 models. The Arduino Ventuno Q board has a PI7C9X2G304 switch (verified from chip markings) with a product-id of 0xb304. Add 12d8:b304 to the existing list of quirks for this erratum. Signed-off-by: Hans de Goede Signed-off-by: Bjorn Helgaas Reviewed-by: Loic Poulain Reviewed-by: Manivannan Sadhasivam Link: https://patch.msgid.link/20260820082041.13470-1-johannes.goede@oss.qualcomm.com --- drivers/pci/quirks.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/pci/quirks.c b/drivers/pci/quirks.c index de9bbccda21f..e22fd11497b8 100644 --- a/drivers/pci/quirks.c +++ b/drivers/pci/quirks.c @@ -6263,6 +6263,10 @@ DECLARE_PCI_FIXUP_ENABLE(PCI_VENDOR_ID_PERICOM, 0x2303, pci_fixup_pericom_acs_store_forward); DECLARE_PCI_FIXUP_RESUME(PCI_VENDOR_ID_PERICOM, 0x2303, pci_fixup_pericom_acs_store_forward); +DECLARE_PCI_FIXUP_ENABLE(PCI_VENDOR_ID_PERICOM, 0xb304, + pci_fixup_pericom_acs_store_forward); +DECLARE_PCI_FIXUP_RESUME(PCI_VENDOR_ID_PERICOM, 0xb304, + pci_fixup_pericom_acs_store_forward); DECLARE_PCI_FIXUP_ENABLE(PCI_VENDOR_ID_PERICOM, 0xb404, pci_fixup_pericom_acs_store_forward); DECLARE_PCI_FIXUP_RESUME(PCI_VENDOR_ID_PERICOM, 0xb404, From 0e279797d044f1afc962bff4a76a02711525c3bc Mon Sep 17 00:00:00 2001 From: "vladimir.oltean@nxp.com" Date: Sat, 12 Sep 2026 19:14:40 +0300 Subject: [PATCH 028/139] PCI: keystone: Remove device links to PHY This is a partial revert of the device link pieces from commit 49229238ab47 ("PCI: keystone: Cleanup PHY handling"). The trouble with this is that a PHY consumer driver dereferences fields from struct phy, which will become no longer possible. Since commit 987351e1ea77 ("phy: core: Add consumer device link support") from 2019, the PHY core also adds a device link to order PHY provider and consumer suspend/resume operations. The reverted functionality is from 2018 and is redundant with the PHY core now. Signed-off-by: Vladimir Oltean Signed-off-by: Bjorn Helgaas Reviewed-by: Aksh Garg Link: https://patch.msgid.link/20260912161440.1032188-4-vladimir.oltean@nxp.com --- drivers/pci/controller/dwc/pci-keystone.c | 31 +++-------------------- 1 file changed, 4 insertions(+), 27 deletions(-) diff --git a/drivers/pci/controller/dwc/pci-keystone.c b/drivers/pci/controller/dwc/pci-keystone.c index 602516239a57..bd736a1624bc 100644 --- a/drivers/pci/controller/dwc/pci-keystone.c +++ b/drivers/pci/controller/dwc/pci-keystone.c @@ -129,7 +129,6 @@ struct keystone_pcie { int num_lanes; u32 num_viewport; struct phy **phy; - struct device_link **link; struct device_node *msi_intc_np; struct irq_domain *intx_irq_domain; struct device_node *np; @@ -1131,7 +1130,6 @@ static int ks_pcie_probe(struct platform_device *pdev) enum dw_pcie_device_mode mode; struct dw_pcie *pci; struct keystone_pcie *ks_pcie; - struct device_link **link; struct gpio_desc *gpiod; struct resource *res; void __iomem *base; @@ -1202,31 +1200,17 @@ static int ks_pcie_probe(struct platform_device *pdev) if (!phy) return -ENOMEM; - link = devm_kcalloc(dev, num_lanes, sizeof(*link), GFP_KERNEL); - if (!link) - return -ENOMEM; - for (i = 0; i < num_lanes; i++) { snprintf(name, sizeof(name), "pcie-phy%d", i); phy[i] = devm_phy_optional_get(dev, name); if (IS_ERR(phy[i])) { ret = PTR_ERR(phy[i]); - goto err_link; - } - - if (!phy[i]) - continue; - - link[i] = device_link_add(dev, &phy[i]->dev, DL_FLAG_STATELESS); - if (!link[i]) { - ret = -EINVAL; - goto err_link; + goto err; } } ks_pcie->np = np; ks_pcie->pci = pci; - ks_pcie->link = link; ks_pcie->num_lanes = num_lanes; ks_pcie->phy = phy; @@ -1236,7 +1220,7 @@ static int ks_pcie_probe(struct platform_device *pdev) ret = PTR_ERR(gpiod); if (ret != -EPROBE_DEFER) dev_err(dev, "Failed to get reset GPIO\n"); - goto err_link; + goto err; } /* Obtain references to the PHYs */ @@ -1251,7 +1235,7 @@ static int ks_pcie_probe(struct platform_device *pdev) if (ret) { dev_err(dev, "failed to enable phy\n"); - goto err_link; + goto err; } platform_set_drvdata(pdev, ks_pcie); @@ -1338,25 +1322,18 @@ static int ks_pcie_probe(struct platform_device *pdev) pm_runtime_disable(dev); ks_pcie_disable_phy(ks_pcie); -err_link: - while (--i >= 0 && link[i]) - device_link_del(link[i]); - +err: return ret; } static void ks_pcie_remove(struct platform_device *pdev) { struct keystone_pcie *ks_pcie = platform_get_drvdata(pdev); - struct device_link **link = ks_pcie->link; - int num_lanes = ks_pcie->num_lanes; struct device *dev = &pdev->dev; pm_runtime_put(dev); pm_runtime_disable(dev); ks_pcie_disable_phy(ks_pcie); - while (num_lanes--) - device_link_del(link[num_lanes]); } static struct platform_driver ks_pcie_driver = { From b59e98ae3841f84d0fb21dbf80e69e6c241a29d9 Mon Sep 17 00:00:00 2001 From: "vladimir.oltean@nxp.com" Date: Sat, 12 Sep 2026 19:14:38 +0300 Subject: [PATCH 029/139] PCI: cadence: Remove device links to PHY This is a partial revert of the device link pieces from commit dfb80534692d ("PCI: cadence: Add generic PHY support to host and EP drivers"). The trouble with this is that a PHY consumer driver dereferences fields from struct phy, which will become no longer possible. Since commit 987351e1ea77 ("phy: core: Add consumer device link support") from 2019, the PHY core also adds a device link to order PHY provider and consumer suspend/resume operations. The reverted functionality is from 2018 and is redundant with the PHY core now. Signed-off-by: Vladimir Oltean Signed-off-by: Bjorn Helgaas Reviewed-by: Aksh Garg Link: https://patch.msgid.link/20260912161440.1032188-2-vladimir.oltean@nxp.com --- .../pci/controller/cadence/pcie-cadence-plat.c | 4 ---- drivers/pci/controller/cadence/pcie-cadence.c | 16 +--------------- drivers/pci/controller/cadence/pcie-cadence.h | 2 -- 3 files changed, 1 insertion(+), 21 deletions(-) diff --git a/drivers/pci/controller/cadence/pcie-cadence-plat.c b/drivers/pci/controller/cadence/pcie-cadence-plat.c index a1ea24fc3b63..5900d68c6e83 100644 --- a/drivers/pci/controller/cadence/pcie-cadence-plat.c +++ b/drivers/pci/controller/cadence/pcie-cadence-plat.c @@ -41,7 +41,6 @@ static int cdns_plat_pcie_probe(struct platform_device *pdev) struct pci_host_bridge *bridge; struct cdns_pcie_ep *ep; struct cdns_pcie_rc *rc; - int phy_count; bool is_rc; int ret; @@ -122,9 +121,6 @@ static int cdns_plat_pcie_probe(struct platform_device *pdev) pm_runtime_put_sync(dev); pm_runtime_disable(dev); cdns_pcie_disable_phy(cdns_plat_pcie->pcie); - phy_count = cdns_plat_pcie->pcie->phy_count; - while (phy_count--) - device_link_del(cdns_plat_pcie->pcie->link[phy_count]); return 0; } diff --git a/drivers/pci/controller/cadence/pcie-cadence.c b/drivers/pci/controller/cadence/pcie-cadence.c index a1eada56edba..0ac980249941 100644 --- a/drivers/pci/controller/cadence/pcie-cadence.c +++ b/drivers/pci/controller/cadence/pcie-cadence.c @@ -222,7 +222,6 @@ int cdns_pcie_init_phy(struct device *dev, struct cdns_pcie *pcie) struct device_node *np = dev->of_node; int phy_count; struct phy **phy; - struct device_link **link; int i; int ret; const char *name; @@ -238,10 +237,6 @@ int cdns_pcie_init_phy(struct device *dev, struct cdns_pcie *pcie) if (!phy) return -ENOMEM; - link = devm_kcalloc(dev, phy_count, sizeof(*link), GFP_KERNEL); - if (!link) - return -ENOMEM; - for (i = 0; i < phy_count; i++) { of_property_read_string_index(np, "phy-names", i, &name); phy[i] = devm_phy_get(dev, name); @@ -249,17 +244,10 @@ int cdns_pcie_init_phy(struct device *dev, struct cdns_pcie *pcie) ret = PTR_ERR(phy[i]); goto err_phy; } - link[i] = device_link_add(dev, &phy[i]->dev, DL_FLAG_STATELESS); - if (!link[i]) { - devm_phy_put(dev, phy[i]); - ret = -EINVAL; - goto err_phy; - } } pcie->phy_count = phy_count; pcie->phy = phy; - pcie->link = link; ret = cdns_pcie_enable_phy(pcie); if (ret) @@ -268,10 +256,8 @@ int cdns_pcie_init_phy(struct device *dev, struct cdns_pcie *pcie) return 0; err_phy: - while (--i >= 0) { - device_link_del(link[i]); + while (--i >= 0) devm_phy_put(dev, phy[i]); - } return ret; } diff --git a/drivers/pci/controller/cadence/pcie-cadence.h b/drivers/pci/controller/cadence/pcie-cadence.h index b4d51adefea5..4a96e1869e53 100644 --- a/drivers/pci/controller/cadence/pcie-cadence.h +++ b/drivers/pci/controller/cadence/pcie-cadence.h @@ -260,7 +260,6 @@ struct cdns_plat_pcie_of_data { * @is_hpa: indicates if the architecture is HPA * @phy_count: number of supported PHY devices * @phy: list of pointers to specific PHY control blocks - * @link: list of pointers to corresponding device link representations * @ops: Platform-specific ops to control various inputs from Cadence PCIe * wrapper * @cdns_pcie_reg_offsets: Register bank offsets for different SoC @@ -276,7 +275,6 @@ struct cdns_pcie { bool is_hpa; int phy_count; struct phy **phy; - struct device_link **link; const struct cdns_pcie_ops *ops; const struct cdns_plat_pcie_of_data *cdns_pcie_reg_offsets; int max_link_speed; From 52cd9bb3772e82d792cb0def3c2a5a9b6bd24ffb Mon Sep 17 00:00:00 2001 From: "vladimir.oltean@nxp.com" Date: Sat, 12 Sep 2026 19:14:39 +0300 Subject: [PATCH 030/139] PCI: dra7xx: Remove device links to PHY This is a full revert of commit 7a4db656a635 ("PCI: dra7xx: Create functional dependency between PCIe and PHY"). The trouble with this is that a PHY consumer driver dereferences fields from struct phy, which will become no longer possible. Since commit 987351e1ea77 ("phy: core: Add consumer device link support") from 2019, the PHY core also adds a device link to order PHY provider and consumer suspend/resume operations. The reverted functionality is from 2017 and is redundant with the PHY core now. Signed-off-by: Vladimir Oltean Signed-off-by: Bjorn Helgaas Reviewed-by: Aksh Garg Link: https://patch.msgid.link/20260912161440.1032188-3-vladimir.oltean@nxp.com --- drivers/pci/controller/dwc/pci-dra7xx.c | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/drivers/pci/controller/dwc/pci-dra7xx.c b/drivers/pci/controller/dwc/pci-dra7xx.c index 6ae5b27e27b3..6c9e88177600 100644 --- a/drivers/pci/controller/dwc/pci-dra7xx.c +++ b/drivers/pci/controller/dwc/pci-dra7xx.c @@ -9,7 +9,6 @@ #include #include -#include #include #include #include @@ -680,7 +679,6 @@ static int dra7xx_pcie_probe(struct platform_device *pdev) int i; int phy_count; struct phy **phy; - struct device_link **link; void __iomem *base; struct dw_pcie *pci; struct dra7xx_pcie *dra7xx; @@ -728,10 +726,6 @@ static int dra7xx_pcie_probe(struct platform_device *pdev) if (!phy) return -ENOMEM; - link = devm_kcalloc(dev, phy_count, sizeof(*link), GFP_KERNEL); - if (!link) - return -ENOMEM; - dra7xx->clk = devm_clk_get_optional(dev, NULL); if (IS_ERR(dra7xx->clk)) return dev_err_probe(dev, PTR_ERR(dra7xx->clk), @@ -746,12 +740,6 @@ static int dra7xx_pcie_probe(struct platform_device *pdev) phy[i] = devm_phy_get(dev, name); if (IS_ERR(phy[i])) return PTR_ERR(phy[i]); - - link[i] = device_link_add(dev, &phy[i]->dev, DL_FLAG_STATELESS); - if (!link[i]) { - ret = -EINVAL; - goto err_link; - } } dra7xx->base = base; @@ -853,10 +841,6 @@ static int dra7xx_pcie_probe(struct platform_device *pdev) pm_runtime_disable(dev); dra7xx_pcie_disable_phy(dra7xx); -err_link: - while (--i >= 0) - device_link_del(link[i]); - return ret; } From 768e5d0936d00d663ce2b8c192cc6ab0b61ad461 Mon Sep 17 00:00:00 2001 From: Felix Gu Date: Sat, 22 Aug 2026 23:07:47 +0800 Subject: [PATCH 031/139] PCI: dra7xx: Fix clock enable leak on probe failure dra7xx_pcie_probe() enables the optional clock but never disables it when probing fails afterwards. Switch to devm_clk_get_optional_enabled(), which disables the clock automatically on failure. Fixes: 5af9405397bf ("PCI: dra7xx: Get an optional clock") Signed-off-by: Felix Gu Signed-off-by: Manivannan Sadhasivam Signed-off-by: Bjorn Helgaas Reviewed-by: Luca Ceresoli Link: https://patch.msgid.link/20260822-dra7xx-v1-1-23e204d5548b@gmail.com --- drivers/pci/controller/dwc/pci-dra7xx.c | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/drivers/pci/controller/dwc/pci-dra7xx.c b/drivers/pci/controller/dwc/pci-dra7xx.c index 6c9e88177600..3460a83b4395 100644 --- a/drivers/pci/controller/dwc/pci-dra7xx.c +++ b/drivers/pci/controller/dwc/pci-dra7xx.c @@ -726,15 +726,11 @@ static int dra7xx_pcie_probe(struct platform_device *pdev) if (!phy) return -ENOMEM; - dra7xx->clk = devm_clk_get_optional(dev, NULL); + dra7xx->clk = devm_clk_get_optional_enabled(dev, NULL); if (IS_ERR(dra7xx->clk)) return dev_err_probe(dev, PTR_ERR(dra7xx->clk), "clock request failed"); - ret = clk_prepare_enable(dra7xx->clk); - if (ret) - return ret; - for (i = 0; i < phy_count; i++) { snprintf(name, sizeof(name), "pcie-phy%d", i); phy[i] = devm_phy_get(dev, name); From fe108800a865b268eec103e4940c248c85a86e9f Mon Sep 17 00:00:00 2001 From: Vladimir Oltean Date: Mon, 10 Aug 2026 15:43:13 +0300 Subject: [PATCH 032/139] PCI: cadence: Preserve all error codes in cdns_plat_pcie_probe() The blamed commit functionally changed the error path of cdns_pcie_host_probe(), now cdns_plat_pcie_probe(). When the old code path executed "goto err_get_sync", the PCIe controller probe function propagated the pm_runtime_get_sync() error code. The new code doesn't, and returns 0. Similarly for the "goto err_init" previously triggered by cdns_pcie_host_init() errors, and now triggered by cdns_pcie_host_setup() and cdns_pcie_ep_setup() errors. These are not propagated and will result in probing success, which is incorrect. Fixes: bd22885aa188 ("PCI: cadence: Refactor driver to use as a core library") Signed-off-by: Vladimir Oltean Signed-off-by: Manivannan Sadhasivam Signed-off-by: Bjorn Helgaas Link: https://patch.msgid.link/20260810124315.433448-2-vladimir.oltean@nxp.com --- drivers/pci/controller/cadence/pcie-cadence-plat.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/pci/controller/cadence/pcie-cadence-plat.c b/drivers/pci/controller/cadence/pcie-cadence-plat.c index 5900d68c6e83..13edc6be21f5 100644 --- a/drivers/pci/controller/cadence/pcie-cadence-plat.c +++ b/drivers/pci/controller/cadence/pcie-cadence-plat.c @@ -122,7 +122,7 @@ static int cdns_plat_pcie_probe(struct platform_device *pdev) pm_runtime_disable(dev); cdns_pcie_disable_phy(cdns_plat_pcie->pcie); - return 0; + return ret; } static void cdns_plat_pcie_shutdown(struct platform_device *pdev) From a685daa02d97e6efcfcf24766f0926b859252d62 Mon Sep 17 00:00:00 2001 From: Vladimir Oltean Date: Mon, 10 Aug 2026 15:43:14 +0300 Subject: [PATCH 033/139] PCI: Add missing headers transitively included by The tegra as well as a few dwc PCI controller drivers uses PM runtime operations without including the required header. Similarly, pcie-rockchip-host, pcie-starfive as well as a few dwc PCI controllers use the regulator consumer API without including . pcie-spacemit-k1.c uses of_get_next_available_child() and of_node_put() without including . It seems these function prototypes were indirectly provided by , mostly by mistake (none of the functions it exports need it). Before the PHY header can drop the unnecessary includes, make sure the PCI controller drivers include what they use. Signed-off-by: Vladimir Oltean Signed-off-by: Manivannan Sadhasivam Signed-off-by: Bjorn Helgaas Link: https://patch.msgid.link/20260810124315.433448-3-vladimir.oltean@nxp.com --- drivers/pci/controller/dwc/pci-keystone.c | 1 + drivers/pci/controller/dwc/pcie-dw-rockchip.c | 1 + drivers/pci/controller/dwc/pcie-histb.c | 1 + drivers/pci/controller/dwc/pcie-qcom-ep.c | 1 + drivers/pci/controller/dwc/pcie-spacemit-k1.c | 3 +++ drivers/pci/controller/dwc/pcie-tegra194.c | 1 + drivers/pci/controller/pci-tegra.c | 1 + drivers/pci/controller/pcie-rockchip-host.c | 1 + drivers/pci/controller/plda/pcie-starfive.c | 1 + 9 files changed, 11 insertions(+) diff --git a/drivers/pci/controller/dwc/pci-keystone.c b/drivers/pci/controller/dwc/pci-keystone.c index 602516239a57..2ea85470ab89 100644 --- a/drivers/pci/controller/dwc/pci-keystone.c +++ b/drivers/pci/controller/dwc/pci-keystone.c @@ -24,6 +24,7 @@ #include #include #include +#include #include #include #include diff --git a/drivers/pci/controller/dwc/pcie-dw-rockchip.c b/drivers/pci/controller/dwc/pcie-dw-rockchip.c index af26a07c0c9e..14976861f8ef 100644 --- a/drivers/pci/controller/dwc/pcie-dw-rockchip.c +++ b/drivers/pci/controller/dwc/pcie-dw-rockchip.c @@ -21,6 +21,7 @@ #include #include #include +#include #include #include #include diff --git a/drivers/pci/controller/dwc/pcie-histb.c b/drivers/pci/controller/dwc/pcie-histb.c index a52071589377..432a54c5bfce 100644 --- a/drivers/pci/controller/dwc/pcie-histb.c +++ b/drivers/pci/controller/dwc/pcie-histb.c @@ -18,6 +18,7 @@ #include #include #include +#include #include #include diff --git a/drivers/pci/controller/dwc/pcie-qcom-ep.c b/drivers/pci/controller/dwc/pcie-qcom-ep.c index 56184e6ca6e6..8364696a1b98 100644 --- a/drivers/pci/controller/dwc/pcie-qcom-ep.c +++ b/drivers/pci/controller/dwc/pcie-qcom-ep.c @@ -19,6 +19,7 @@ #include #include #include +#include #include #include #include diff --git a/drivers/pci/controller/dwc/pcie-spacemit-k1.c b/drivers/pci/controller/dwc/pcie-spacemit-k1.c index 0564c46e2f48..92a71ea90ac3 100644 --- a/drivers/pci/controller/dwc/pcie-spacemit-k1.c +++ b/drivers/pci/controller/dwc/pcie-spacemit-k1.c @@ -12,9 +12,12 @@ #include #include #include +#include #include #include +#include #include +#include #include #include diff --git a/drivers/pci/controller/dwc/pcie-tegra194.c b/drivers/pci/controller/dwc/pcie-tegra194.c index 63a0f56cad8e..8ab23d1d7bd6 100644 --- a/drivers/pci/controller/dwc/pcie-tegra194.c +++ b/drivers/pci/controller/dwc/pcie-tegra194.c @@ -27,6 +27,7 @@ #include #include #include +#include #include #include #include diff --git a/drivers/pci/controller/pci-tegra.c b/drivers/pci/controller/pci-tegra.c index 59b56446c507..96bd522fa67b 100644 --- a/drivers/pci/controller/pci-tegra.c +++ b/drivers/pci/controller/pci-tegra.c @@ -36,6 +36,7 @@ #include #include #include +#include #include #include #include diff --git a/drivers/pci/controller/pcie-rockchip-host.c b/drivers/pci/controller/pcie-rockchip-host.c index d203c4876d30..be8ff99b243f 100644 --- a/drivers/pci/controller/pcie-rockchip-host.c +++ b/drivers/pci/controller/pcie-rockchip-host.c @@ -24,6 +24,7 @@ #include #include #include +#include #include "../pci.h" #include "pcie-rockchip.h" diff --git a/drivers/pci/controller/plda/pcie-starfive.c b/drivers/pci/controller/plda/pcie-starfive.c index 02d5aa6dc14b..b693eabdf85b 100644 --- a/drivers/pci/controller/plda/pcie-starfive.c +++ b/drivers/pci/controller/plda/pcie-starfive.c @@ -21,6 +21,7 @@ #include #include #include +#include #include #include "../../pci.h" From ff8a969f7dc8e79ea6113abcfa983e070b2b585a Mon Sep 17 00:00:00 2001 From: "Maciej W. Rozycki" Date: Sat, 1 Aug 2026 22:11:48 +0200 Subject: [PATCH 034/139] PCI: Skip Target Speed quirk on clamped ports with no link Since commit 72780f796468 ("PCI: Always lift 2.5GT/s restriction in PCIe failed link retraining") the Target Speed quirk lifts a firmware-imposed 2.5GT/s restriction on any downstream port, without checking whether the link is up. Where nothing is plugged in, the retraining that follows can never complete, so each attempt costs PCIE_LINK_RETRAIN_TIMEOUT_MS. The quirk makes two of them -- the initial one and the restore on the error path -- adding a fixed 2 s to every boot. On an MSI PRO Z690-A WIFI DDR4 (Intel 600 Series PCH) with one empty x1 slot, running v7.2-rc5: 0.541 pci 0000:00:1c.0: removing 2.5GT/s downstream link speed restriction 1.541 pci 0000:00:1c.0: retraining failed 2.541 pci 0000:00:1c.2: [8086:7aba] type 01 class 0x060400 Where the Link Speed has already been clamped at 2.5GT/s and no link has been established there is nothing worth doing, which is what the kerneldoc for the quirk already describes: the restriction is to be lifted where firmware arranged it "and the port reports its link already being up". Bail out early in that case, before either the ASM2824 workaround or the removal of the restriction is considered. Ports whose link is up are unaffected, and so is the ASM2824 workaround, which is reached with the Target Link Speed not clamped. With this applied the quirk returns without touching the port: both messages are gone, enumeration proceeds from 0000:00:1c.0 to 0000:00:1c.2 in 1 ms rather than 2 s, and the systemd "kernel" boot phase goes from 3.011 s to 1.036 s. Fixes: 72780f796468 ("PCI: Always lift 2.5GT/s restriction in PCIe failed link retraining") Reported-by: Andreas Wild Closes: https://lore.kernel.org/lkml/20260801092152.5643-1-andiwild@gmail.com/ Closes: https://bugzilla.kernel.org/show_bug.cgi?id=221801 Signed-off-by: Maciej W. Rozycki Signed-off-by: Andreas Wild Signed-off-by: Bjorn Helgaas Tested-by: Andreas Wild Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20260801201244.4421-1-andiwild@gmail.com --- drivers/pci/quirks.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/drivers/pci/quirks.c b/drivers/pci/quirks.c index de9bbccda21f..7cbdbdd5ddc8 100644 --- a/drivers/pci/quirks.c +++ b/drivers/pci/quirks.c @@ -108,7 +108,11 @@ int pcie_failed_link_retrain(struct pci_dev *dev) pcie_capability_read_word(dev, PCI_EXP_LNKSTA, &lnksta); pcie_capability_read_word(dev, PCI_EXP_LNKCTL2, &oldlnkctl2); - if (!(lnksta & PCI_EXP_LNKSTA_DLLLA) && pcie_lbms_seen(dev, lnksta)) { + if (lnksta & PCI_EXP_LNKSTA_DLLLA) { + ; + } else if (PCIE_LNKCTL2_TLS2SPEED(oldlnkctl2) == PCIE_SPEED_2_5GT) { + return ret; + } else if (pcie_lbms_seen(dev, lnksta)) { pci_info(dev, "broken device, retraining non-functional downstream link at 2.5GT/s\n"); ret = pcie_set_target_speed(dev, PCIE_SPEED_2_5GT, false); if (ret) From 6a0b9a75b9863afd14fce33f650335f285fb90a4 Mon Sep 17 00:00:00 2001 From: Feng Tang Date: Fri, 18 Sep 2026 16:24:12 +0800 Subject: [PATCH 035/139] PCI/P2PDMA: Add Alibaba T-HEAD Yitian 710 to allowlist Alibaba T-HEAD Yitian 710 supports PCI peer-to-peer transactions. Add it to the allowlist. Two NVMe cards with CMB (Command Memory Buffer) have run p2pmem-test [1] fine, under same root complex and different root complexes. [1] https://github.com/sbates130272/p2pmem-test Signed-off-by: Feng Tang Signed-off-by: Bjorn Helgaas Acked-by: Logan Gunthorpe Link: https://patch.msgid.link/20260918082412.15967-1-feng.tang@linux.alibaba.com --- drivers/pci/p2pdma.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/pci/p2pdma.c b/drivers/pci/p2pdma.c index 9fbbf1692f0e..772640a08bf9 100644 --- a/drivers/pci/p2pdma.c +++ b/drivers/pci/p2pdma.c @@ -575,6 +575,8 @@ static const struct pci_p2pdma_whitelist_entry { {PCI_VENDOR_ID_ZHAOXIN, 0x1006, REQ_SAME_HOST_BRIDGE}, {PCI_VENDOR_ID_ZHAOXIN, 0x1007, REQ_SAME_HOST_BRIDGE}, {PCI_VENDOR_ID_ZHAOXIN, 0x1008, 0}, + /* Alibaba T-HEAD Yitian 710 CPU */ + {PCI_VENDOR_ID_ALIBABA, 0x8000, 0}, {} }; From 5ab17178fca6026d329cb6ced33c30f3b7d3e50a Mon Sep 17 00:00:00 2001 From: Runyu Xiao Date: Mon, 21 Sep 2026 22:26:08 +0800 Subject: [PATCH 036/139] PCI: pciehp: Make 'pciehp_poll_mode' parameter read-only pciehp_request_irq() and pciehp_free_irq() select different resources based on the global pciehp_poll_mode parameter. The parameter is currently writable through sysfs, so changing it while a controller is active can make teardown try to stop a nonexistent polling thread or call free_irq() for an IRQ that was not requested. Keep the parameter available for boot and module configuration, but prevent runtime changes to the registration mode. Reproducer: Boot an x86_64 guest in QEMU with a native PCIe hotplug root port and an e1000 device, using these device options: -device pcie-root-port,id=rp1,chassis=1,slot=1 -device e1000,bus=rp1 Pass "pcie_ports=native pciehp.pciehp_poll_mode=1" to the guest kernel. As root in the guest, replace the device name below with a bound pciehp service device and run: # dev=0000:00:1c.0:pcie004 # driver=/sys/bus/pci_express/drivers/pciehp # echo 0 > /sys/module/pciehp/parameters/poll_mode # echo "$dev" > "$driver/unbind" # dmesg | grep 'Trying to free already-free IRQ' The unfixed kernel accepts the runtime mode change and reports the warning when the service device is removed. The fixed kernel rejects the write and removes the device without the warning. The mode change is a deliberate privileged test action and is not expected during ordinary operation. Assisted-by: LLM Codex Signed-off-by: Runyu Xiao Signed-off-by: Bjorn Helgaas Reviewed-by: Lukas Wunner Link: https://patch.msgid.link/20260921142608.270312-1-runyu.xiao@seu.edu.cn Cc: stable@vger.kernel.org --- drivers/pci/hotplug/pciehp_core.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/pci/hotplug/pciehp_core.c b/drivers/pci/hotplug/pciehp_core.c index 2cafd3b26f34..5ce32dc7e178 100644 --- a/drivers/pci/hotplug/pciehp_core.c +++ b/drivers/pci/hotplug/pciehp_core.c @@ -38,7 +38,7 @@ int pciehp_poll_time; * not really modular, but the easiest way to keep compat with existing * bootargs behaviour is to continue using module_param here. */ -module_param(pciehp_poll_mode, bool, 0644); +module_param(pciehp_poll_mode, bool, 0444); module_param(pciehp_poll_time, int, 0644); MODULE_PARM_DESC(pciehp_poll_mode, "Using polling mechanism for hot-plug events or not"); MODULE_PARM_DESC(pciehp_poll_time, "Polling mechanism frequency, in seconds"); From 5b555a496160914394c330748fc95e46a1d71800 Mon Sep 17 00:00:00 2001 From: Marek Vasut Date: Mon, 21 Sep 2026 22:27:43 +0200 Subject: [PATCH 037/139] dt-bindings: PCI: rcar-gen4-pci-host: Add R-Car X5H PCIe4 compatible Document bindings for R-Car X5H (R8A78000) PCIe4 host module. The binding document is deliberately using "renesas,rcar-gen5-pcie4" DT compatible string to discern R-Car X5H PCIe4 controller supported by this binding, from R-Car X5H PCIe6 controller which will use a separate binding. The R-Car X5H PCIe4 controller does no longer include PHY register range, the PHY is now a separate IP and referenced via the 'phy' DT property. The 'reg' and 'reg-names' DT properties therefore differ between R-Car Gen4 PCIe and R-Car X5H PCIe4, and the difference is handled in the allOf section. Signed-off-by: Marek Vasut Signed-off-by: Manivannan Sadhasivam Reviewed-by: Krzysztof Kozlowski Link: https://patch.msgid.link/20260921202823.72620-2-marek.vasut+renesas@mailbox.org --- .../bindings/pci/rcar-gen4-pci-host.yaml | 72 +++++++++++++------ 1 file changed, 51 insertions(+), 21 deletions(-) diff --git a/Documentation/devicetree/bindings/pci/rcar-gen4-pci-host.yaml b/Documentation/devicetree/bindings/pci/rcar-gen4-pci-host.yaml index 64f456c6a75a..9767a52f3904 100644 --- a/Documentation/devicetree/bindings/pci/rcar-gen4-pci-host.yaml +++ b/Documentation/devicetree/bindings/pci/rcar-gen4-pci-host.yaml @@ -10,30 +10,20 @@ title: Renesas R-Car Gen4 PCIe Host maintainers: - Yoshihiro Shimoda -allOf: - - $ref: snps,dw-pcie.yaml# - properties: compatible: - items: - - enum: - - renesas,r8a779f0-pcie # R-Car S4-8 - - renesas,r8a779g0-pcie # R-Car V4H - - renesas,r8a779h0-pcie # R-Car V4M - - const: renesas,rcar-gen4-pcie # R-Car Gen4 + oneOf: + - items: + - enum: + - renesas,r8a779f0-pcie # R-Car S4-8 + - renesas,r8a779g0-pcie # R-Car V4H + - renesas,r8a779h0-pcie # R-Car V4M + - const: renesas,rcar-gen4-pcie # R-Car Gen4 - reg: - maxItems: 7 - - reg-names: - items: - - const: dbi - - const: dbi2 - - const: atu - - const: dma - - const: app - - const: phy - - const: config + - items: + - enum: + - renesas,r8a78000-pcie4 # R-Car X5H PCIe4 + - const: renesas,rcar-gen5-pcie4 # R-Car Gen5 PCIe4 interrupts: maxItems: 4 @@ -84,6 +74,46 @@ required: - resets - reset-names +allOf: + - $ref: snps,dw-pcie.yaml# + - if: + properties: + compatible: + contains: + const: renesas,rcar-gen4-pcie + then: + properties: + reg: + maxItems: 7 + + reg-names: + items: + - const: dbi + - const: dbi2 + - const: atu + - const: dma + - const: app + - const: phy + - const: config + - if: + properties: + compatible: + contains: + const: renesas,rcar-gen5-pcie4 + then: + properties: + reg: + maxItems: 6 + + reg-names: + items: + - const: dbi + - const: dbi2 + - const: atu + - const: dma + - const: app + - const: config + unevaluatedProperties: false examples: From 1e184f38851ff6fac0b72bfb7f51db47ed6f257f Mon Sep 17 00:00:00 2001 From: Zhijian Han Date: Fri, 4 Sep 2026 09:23:07 +0800 Subject: [PATCH 038/139] PCI: imx6: Fix resource leaks in probe error paths imx_pcie_probe() leaks both pwrctrl devices and power domains on failure: - imx_pcie_attach_pd() attaches the "pcie" and "pcie_phy" power domains and adds device links to them, but nothing detaches the domains or removes the links on probe failure or deferral, so they leak. - A failure of devm_pm_runtime_set_active_enabled() returns directly without destroying the pwrctrl devices. - A partial failure inside imx_pcie_attach_pd() leaks the power domains that were already attached. Add imx_pcie_detach_pd_action() to remove the device links and detach the power domains in reverse order of acquisition and register it with devm_add_action_or_reset() so they are released automatically on probe failure. Fixes: 2c5768344f88 ("PCI: imx6: Move pci_pwrctrl_create_devices() to imx_pcie_probe()") Reported-by: sashiko-bot@kernel.org Link: https://lore.kernel.org/all/20260822013640.182C01F000E9@smtp.kernel.org/ Signed-off-by: Zhijian Han [mani: removed the imx_pcie_detach_pd() helper] Signed-off-by: Manivannan Sadhasivam [bhelgaas: update commit log to match inlining of imx_pcie_detach_pd() into imx_pcie_detach_pd_action()] Signed-off-by: Bjorn Helgaas Link: https://patch.msgid.link/20260904012307.3434449-1-hanzhijian1991@gmail.com --- drivers/pci/controller/dwc/pci-imx6.c | 54 +++++++++++++++++++++------ 1 file changed, 43 insertions(+), 11 deletions(-) diff --git a/drivers/pci/controller/dwc/pci-imx6.c b/drivers/pci/controller/dwc/pci-imx6.c index 616c42932faa..6cf9106cce69 100644 --- a/drivers/pci/controller/dwc/pci-imx6.c +++ b/drivers/pci/controller/dwc/pci-imx6.c @@ -180,8 +180,12 @@ struct imx_pcie { struct imx_lut_data luts[IMX95_MAX_LUT]; /* power domain for pcie */ struct device *pd_pcie; + /* device link for pcie power domain */ + struct device_link *pd_link; /* power domain for pcie phy */ struct device *pd_pcie_phy; + /* device link for pcie phy power domain */ + struct device_link *pd_phy_link; struct phy *phy; const struct imx_pcie_drvdata *drvdata; @@ -639,10 +643,34 @@ static int imx6q_pcie_abort_handler(unsigned long addr, } #endif +static void imx_pcie_detach_pd_action(void *data) +{ + struct imx_pcie *imx_pcie = data; + + if (!IS_ERR_OR_NULL(imx_pcie->pd_phy_link)) { + device_link_del(imx_pcie->pd_phy_link); + imx_pcie->pd_phy_link = NULL; + } + + if (!IS_ERR_OR_NULL(imx_pcie->pd_link)) { + device_link_del(imx_pcie->pd_link); + imx_pcie->pd_link = NULL; + } + + if (!IS_ERR_OR_NULL(imx_pcie->pd_pcie_phy)) { + dev_pm_domain_detach(imx_pcie->pd_pcie_phy, true); + imx_pcie->pd_pcie_phy = NULL; + } + + if (!IS_ERR_OR_NULL(imx_pcie->pd_pcie)) { + dev_pm_domain_detach(imx_pcie->pd_pcie, true); + imx_pcie->pd_pcie = NULL; + } +} + static int imx_pcie_attach_pd(struct device *dev) { struct imx_pcie *imx_pcie = dev_get_drvdata(dev); - struct device_link *link; /* Do nothing when in a single power domain */ if (dev->pm_domain) @@ -654,11 +682,11 @@ static int imx_pcie_attach_pd(struct device *dev) /* Do nothing when power domain missing */ if (!imx_pcie->pd_pcie) return 0; - link = device_link_add(dev, imx_pcie->pd_pcie, - DL_FLAG_STATELESS | - DL_FLAG_PM_RUNTIME | - DL_FLAG_RPM_ACTIVE); - if (!link) { + imx_pcie->pd_link = device_link_add(dev, imx_pcie->pd_pcie, + DL_FLAG_STATELESS | + DL_FLAG_PM_RUNTIME | + DL_FLAG_RPM_ACTIVE); + if (!imx_pcie->pd_link) { dev_err(dev, "Failed to add device_link to pcie pd\n"); return -EINVAL; } @@ -667,11 +695,11 @@ static int imx_pcie_attach_pd(struct device *dev) if (IS_ERR(imx_pcie->pd_pcie_phy)) return PTR_ERR(imx_pcie->pd_pcie_phy); - link = device_link_add(dev, imx_pcie->pd_pcie_phy, - DL_FLAG_STATELESS | - DL_FLAG_PM_RUNTIME | - DL_FLAG_RPM_ACTIVE); - if (!link) { + imx_pcie->pd_phy_link = device_link_add(dev, imx_pcie->pd_pcie_phy, + DL_FLAG_STATELESS | + DL_FLAG_PM_RUNTIME | + DL_FLAG_RPM_ACTIVE); + if (!imx_pcie->pd_phy_link) { dev_err(dev, "Failed to add device_link to pcie_phy pd\n"); return -EINVAL; } @@ -1951,6 +1979,10 @@ static int imx_pcie_probe(struct platform_device *pdev) platform_set_drvdata(pdev, imx_pcie); + ret = devm_add_action_or_reset(dev, imx_pcie_detach_pd_action, imx_pcie); + if (ret) + return ret; + ret = imx_pcie_attach_pd(dev); if (ret) return ret; From 5a642c81b31bbd426edceb071ba50839c225220a Mon Sep 17 00:00:00 2001 From: Richard Zhu Date: Mon, 3 Aug 2026 11:43:19 +0800 Subject: [PATCH 039/139] PCI: imx6: Update MPLLB bandwidth to improve i.MX95 Gen3 PCIe stability Poor bandwidth margins are observed during i.MX95 Gen3 PCIe tests with the default MPLLB_BANDWIDTH value (120). The margins worsened across voltage and temperature (VT) variations and different test matrices, potentially causing link stability issues. Testing with MPLLB_BANDWIDTH value of 140 (0x8c) shows significant improvement in bandwidth margins across all VT conditions and test scenarios. Implement PHY register write helper function and configure: - MPLLB_BW_OVRD_IN = 140 (0x8c) for improved bandwidth margin - MPLLB_BW_OVRD_EN to enable the override This ensures robust PCIe Gen3 performance across all operating conditions. Signed-off-by: Richard Zhu [mani: added regmap_read() to flush IMX95_PCIE_PHY_CR_PARA_SEL update] Signed-off-by: Manivannan Sadhasivam Signed-off-by: Bjorn Helgaas Reviewed-by: Frank Li Link: https://patch.msgid.link/20260803034319.992323-1-hongxing.zhu@oss.nxp.com --- drivers/pci/controller/dwc/pci-imx6.c | 38 +++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/drivers/pci/controller/dwc/pci-imx6.c b/drivers/pci/controller/dwc/pci-imx6.c index 6cf9106cce69..3e146a8920b9 100644 --- a/drivers/pci/controller/dwc/pci-imx6.c +++ b/drivers/pci/controller/dwc/pci-imx6.c @@ -80,6 +80,18 @@ #define IMX95_SID_MASK GENMASK(5, 0) #define IMX95_MAX_LUT 32 +#define IMX95_PCIE_PHY_REG_ADDR 0x3008 +#define IMX95_PCIE_PHY_REG_EN BIT(31) +#define IMX95_PCIE_PHY_REG_ADDR_MASK GENMASK(15, 0) + +#define IMX95_PCIE_PHY_REG_DATA 0x300c + +#define IMX95_PCIE_PHY_MPLLB_OVRD_IN 0x2004 +#define IMX95_PCIE_PHY_MPLLB_OVRD_BW_EN BIT(10) + +#define IMX95_PCIE_PHY_MPLLB_BW_IN 0x2005 +#define IMX95_PCIE_PHY_MPLLB_BW_VAL 0x8c + #define IMX95_PCIE_RST_CTRL 0x3010 #define IMX95_PCIE_COLD_RST BIT(0) @@ -274,8 +286,19 @@ static int imx95_pcie_select_ref_clk_src(struct imx_pcie *imx_pcie) return 0; } +static void imx95_pcie_phy_write(struct imx_pcie *imx_pcie, int addr, u16 data) +{ + regmap_update_bits(imx_pcie->iomuxc_gpr, IMX95_PCIE_PHY_REG_ADDR, + IMX95_PCIE_PHY_REG_EN, IMX95_PCIE_PHY_REG_EN); + regmap_update_bits(imx_pcie->iomuxc_gpr, IMX95_PCIE_PHY_REG_ADDR, + IMX95_PCIE_PHY_REG_ADDR_MASK, addr); + regmap_write(imx_pcie->iomuxc_gpr, IMX95_PCIE_PHY_REG_DATA, data); +} + static int imx95_pcie_init_phy(struct imx_pcie *imx_pcie) { + u32 val; + /* * ERR051624: The Controller Without Vaux Cannot Exit L23 Ready * Through Beacon or PERST# De-assertion @@ -294,6 +317,21 @@ static int imx95_pcie_init_phy(struct imx_pcie *imx_pcie) IMX95_PCIE_PHY_CR_PARA_SEL, IMX95_PCIE_PHY_CR_PARA_SEL); + /* Flush the IMX95_PCIE_PHY_CR_PARA_SEL update */ + regmap_read(imx_pcie->iomuxc_gpr, IMX95_PCIE_SS_RW_REG_0, &val); + + /* + * A delay is required between the assertion of + * IMX95_PCIE_PHY_CR_PARA_SEL and subsequent PHY register write + * operation to ensure values are successfully written. + */ + udelay(200); + + imx95_pcie_phy_write(imx_pcie, IMX95_PCIE_PHY_MPLLB_BW_IN, + IMX95_PCIE_PHY_MPLLB_BW_VAL); + imx95_pcie_phy_write(imx_pcie, IMX95_PCIE_PHY_MPLLB_OVRD_IN, + IMX95_PCIE_PHY_MPLLB_OVRD_BW_EN); + return 0; } From eeeb72fab4a569c1cc547c9bf220c808dd37f83d Mon Sep 17 00:00:00 2001 From: Marek Vasut Date: Mon, 21 Sep 2026 22:27:44 +0200 Subject: [PATCH 040/139] PCI: rcar-gen4: Rework .additional_common_init() into .init() Rework the current complicated initialization callchain that invokes host_init->common_init->additional_common_init() for host and ep_init->common_init->additional_common_init() for endpoint into simpler host_init->init() and ep_init->init() . The init callback is now always implemented for all R-Car Gen4 SoCs; therefore there is no need to check whether the callback is not NULL. For R-Car S4, the .init() callback is rcar_gen4_pcie_common_init(), for R-Car V4H and V4M, the .init() callback is implemented using a new function rcar_gen4_v4h_v4m_pcie_init(), which calls the common rcar_gen4_pcie_common_init() followed by R-Car V4H- and V4M-specific hardware initialization. The hardware initialization sequence remains unchanged. Signed-off-by: Marek Vasut Signed-off-by: Manivannan Sadhasivam [bhelgaas: drop 'dwc' tags, commit log typo] Signed-off-by: Bjorn Helgaas Link: https://patch.msgid.link/20260921202823.72620-3-marek.vasut+renesas@mailbox.org --- drivers/pci/controller/dwc/pcie-rcar-gen4.c | 56 ++++++++++++--------- 1 file changed, 32 insertions(+), 24 deletions(-) diff --git a/drivers/pci/controller/dwc/pcie-rcar-gen4.c b/drivers/pci/controller/dwc/pcie-rcar-gen4.c index 5a076aa3f490..a19ebbab2f8c 100644 --- a/drivers/pci/controller/dwc/pcie-rcar-gen4.c +++ b/drivers/pci/controller/dwc/pcie-rcar-gen4.c @@ -87,7 +87,7 @@ MODULE_FIRMWARE(RCAR_GEN4_PCIE_FIRMWARE_NAME); struct rcar_gen4_pcie; struct rcar_gen4_pcie_drvdata { - void (*additional_common_init)(struct rcar_gen4_pcie *rcar); + int (*init)(struct rcar_gen4_pcie *rcar); int (*ltssm_control)(struct rcar_gen4_pcie *rcar, bool enable); enum dw_pcie_device_mode mode; }; @@ -241,9 +241,6 @@ static int rcar_gen4_pcie_common_init(struct rcar_gen4_pcie *rcar) reset_control_status(dw->core_rsts[DW_PCIE_PWR_RST].rstc); fsleep(1000); - if (rcar->drvdata->additional_common_init) - rcar->drvdata->additional_common_init(rcar); - return 0; err_unprepare: @@ -252,6 +249,31 @@ static int rcar_gen4_pcie_common_init(struct rcar_gen4_pcie *rcar) return ret; } +static int rcar_gen4_v4h_v4m_pcie_init(struct rcar_gen4_pcie *rcar) +{ + struct dw_pcie *dw = &rcar->dw; + u32 val; + int ret; + + /* R-Car Gen4 common initialization. */ + ret = rcar_gen4_pcie_common_init(rcar); + if (ret) + return ret; + + /* R-Car V4H and V4M specific additional initialization. */ + val = dw_pcie_readl_dbi(dw, PCIE_PORT_LANE_SKEW); + val &= ~PORT_LANE_SKEW_INSERT_MASK; + if (dw->num_lanes < 4) + val |= BIT(6); + dw_pcie_writel_dbi(dw, PCIE_PORT_LANE_SKEW, val); + + val = readl(rcar->base + PCIEPWRMNGCTRL); + val |= APP_CLK_REQ_N | APP_CLK_PM_EN; + writel(val, rcar->base + PCIEPWRMNGCTRL); + + return 0; +} + static void rcar_gen4_pcie_common_deinit(struct rcar_gen4_pcie *rcar) { struct dw_pcie *dw = &rcar->dw; @@ -474,7 +496,7 @@ static int rcar_gen4_pcie_host_init(struct dw_pcie_rp *pp) gpiod_set_value_cansleep(dw->pe_rst, 1); - ret = rcar_gen4_pcie_common_init(rcar); + ret = rcar->drvdata->init(rcar); if (ret) return ret; @@ -543,7 +565,7 @@ static int rcar_gen4_pcie_ep_pre_init(struct dw_pcie_ep *ep) writel(0, rcar->base + PCIEDMAINTSTSEN); - ret = rcar_gen4_pcie_common_init(rcar); + ret = rcar->drvdata->init(rcar); if (ret) return ret; @@ -739,22 +761,6 @@ static int r8a779f0_pcie_ltssm_control(struct rcar_gen4_pcie *rcar, bool enable) return 0; } -static void rcar_gen4_pcie_additional_common_init(struct rcar_gen4_pcie *rcar) -{ - struct dw_pcie *dw = &rcar->dw; - u32 val; - - val = dw_pcie_readl_dbi(dw, PCIE_PORT_LANE_SKEW); - val &= ~PORT_LANE_SKEW_INSERT_MASK; - if (dw->num_lanes < 4) - val |= BIT(6); - dw_pcie_writel_dbi(dw, PCIE_PORT_LANE_SKEW, val); - - val = readl(rcar->base + PCIEPWRMNGCTRL); - val |= APP_CLK_REQ_N | APP_CLK_PM_EN; - writel(val, rcar->base + PCIEPWRMNGCTRL); -} - static void rcar_gen4_pcie_phy_reg_update_bits(struct rcar_gen4_pcie *rcar, u32 offset, u32 mask, u32 val) { @@ -906,23 +912,25 @@ static int rcar_gen4_pcie_ltssm_control(struct rcar_gen4_pcie *rcar, bool enable } static struct rcar_gen4_pcie_drvdata drvdata_r8a779f0_pcie = { + .init = rcar_gen4_pcie_common_init, .ltssm_control = r8a779f0_pcie_ltssm_control, .mode = DW_PCIE_RC_TYPE, }; static struct rcar_gen4_pcie_drvdata drvdata_r8a779f0_pcie_ep = { + .init = rcar_gen4_pcie_common_init, .ltssm_control = r8a779f0_pcie_ltssm_control, .mode = DW_PCIE_EP_TYPE, }; static struct rcar_gen4_pcie_drvdata drvdata_rcar_gen4_pcie = { - .additional_common_init = rcar_gen4_pcie_additional_common_init, + .init = rcar_gen4_v4h_v4m_pcie_init, .ltssm_control = rcar_gen4_pcie_ltssm_control, .mode = DW_PCIE_RC_TYPE, }; static struct rcar_gen4_pcie_drvdata drvdata_rcar_gen4_pcie_ep = { - .additional_common_init = rcar_gen4_pcie_additional_common_init, + .init = rcar_gen4_v4h_v4m_pcie_init, .ltssm_control = rcar_gen4_pcie_ltssm_control, .mode = DW_PCIE_EP_TYPE, }; From a9b2dc6de8d0e0a040fefe04636c82287cdf7845 Mon Sep 17 00:00:00 2001 From: Marek Vasut Date: Mon, 21 Sep 2026 22:27:45 +0200 Subject: [PATCH 041/139] PCI: rcar-gen4: Add .deinit() callback Add rcar_gen4_pcie_drvdata->deinit() callback in preparation for R-Car Gen5 support. The .deinit() callback will be used to tear down the PHY on R-Car Gen5. For now, it does what rcar_gen4_pcie_common_deinit() used to do directly. Signed-off-by: Marek Vasut Signed-off-by: Manivannan Sadhasivam Signed-off-by: Bjorn Helgaas Link: https://patch.msgid.link/20260921202823.72620-4-marek.vasut+renesas@mailbox.org --- drivers/pci/controller/dwc/pcie-rcar-gen4.c | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/drivers/pci/controller/dwc/pcie-rcar-gen4.c b/drivers/pci/controller/dwc/pcie-rcar-gen4.c index a19ebbab2f8c..04871ac11c34 100644 --- a/drivers/pci/controller/dwc/pcie-rcar-gen4.c +++ b/drivers/pci/controller/dwc/pcie-rcar-gen4.c @@ -88,6 +88,7 @@ MODULE_FIRMWARE(RCAR_GEN4_PCIE_FIRMWARE_NAME); struct rcar_gen4_pcie; struct rcar_gen4_pcie_drvdata { int (*init)(struct rcar_gen4_pcie *rcar); + void (*deinit)(struct rcar_gen4_pcie *rcar); int (*ltssm_control)(struct rcar_gen4_pcie *rcar, bool enable); enum dw_pcie_device_mode mode; }; @@ -520,7 +521,7 @@ static int rcar_gen4_pcie_host_init(struct dw_pcie_rp *pp) return 0; err: - rcar_gen4_pcie_common_deinit(rcar); + rcar->drvdata->deinit(rcar); return ret; } @@ -530,7 +531,7 @@ static void rcar_gen4_pcie_host_deinit(struct dw_pcie_rp *pp) struct rcar_gen4_pcie *rcar = to_rcar_gen4_pcie(dw); gpiod_set_value_cansleep(dw->pe_rst, 1); - rcar_gen4_pcie_common_deinit(rcar); + rcar->drvdata->deinit(rcar); } static const struct dw_pcie_host_ops rcar_gen4_pcie_host_ops = { @@ -580,7 +581,7 @@ static void rcar_gen4_pcie_ep_post_deinit(struct dw_pcie_ep *ep) struct rcar_gen4_pcie *rcar = to_rcar_gen4_pcie(dw); writel(0, rcar->base + PCIEDMAINTSTSEN); - rcar_gen4_pcie_common_deinit(rcar); + rcar->drvdata->deinit(rcar); } static int rcar_gen4_pcie_ep_raise_irq(struct dw_pcie_ep *ep, u8 func_no, @@ -913,24 +914,28 @@ static int rcar_gen4_pcie_ltssm_control(struct rcar_gen4_pcie *rcar, bool enable static struct rcar_gen4_pcie_drvdata drvdata_r8a779f0_pcie = { .init = rcar_gen4_pcie_common_init, + .deinit = rcar_gen4_pcie_common_deinit, .ltssm_control = r8a779f0_pcie_ltssm_control, .mode = DW_PCIE_RC_TYPE, }; static struct rcar_gen4_pcie_drvdata drvdata_r8a779f0_pcie_ep = { .init = rcar_gen4_pcie_common_init, + .deinit = rcar_gen4_pcie_common_deinit, .ltssm_control = r8a779f0_pcie_ltssm_control, .mode = DW_PCIE_EP_TYPE, }; static struct rcar_gen4_pcie_drvdata drvdata_rcar_gen4_pcie = { .init = rcar_gen4_v4h_v4m_pcie_init, + .deinit = rcar_gen4_pcie_common_deinit, .ltssm_control = rcar_gen4_pcie_ltssm_control, .mode = DW_PCIE_RC_TYPE, }; static struct rcar_gen4_pcie_drvdata drvdata_rcar_gen4_pcie_ep = { .init = rcar_gen4_v4h_v4m_pcie_init, + .deinit = rcar_gen4_pcie_common_deinit, .ltssm_control = rcar_gen4_pcie_ltssm_control, .mode = DW_PCIE_EP_TYPE, }; From bb0cf158cfa61d4b5336bdc1bde1ac515ffe2871 Mon Sep 17 00:00:00 2001 From: Marek Vasut Date: Mon, 21 Sep 2026 22:27:46 +0200 Subject: [PATCH 042/139] PCI: rcar-gen4: Add .speed_control() callback Introduce new rcar_gen4_pcie_drvdata->speed_control() callback and implement it on R-Car Gen4. The callback implements the second half of what is currently present in rcar_gen4_pcie_start_link(), and rcar_gen4_pcie_start_link() does call the .speed_control() callback. This is a preparatory work for R-Car Gen5 support, where the .speed_control() implementation is different. Signed-off-by: Marek Vasut [mani: commit log] Signed-off-by: Manivannan Sadhasivam Signed-off-by: Bjorn Helgaas Link: https://patch.msgid.link/20260921202823.72620-5-marek.vasut+renesas@mailbox.org --- drivers/pci/controller/dwc/pcie-rcar-gen4.c | 39 ++++++++++++++------- 1 file changed, 26 insertions(+), 13 deletions(-) diff --git a/drivers/pci/controller/dwc/pcie-rcar-gen4.c b/drivers/pci/controller/dwc/pcie-rcar-gen4.c index 04871ac11c34..c768a9c7b3b7 100644 --- a/drivers/pci/controller/dwc/pcie-rcar-gen4.c +++ b/drivers/pci/controller/dwc/pcie-rcar-gen4.c @@ -90,6 +90,7 @@ struct rcar_gen4_pcie_drvdata { int (*init)(struct rcar_gen4_pcie *rcar); void (*deinit)(struct rcar_gen4_pcie *rcar); int (*ltssm_control)(struct rcar_gen4_pcie *rcar, bool enable); + int (*speed_control)(struct rcar_gen4_pcie *rcar); enum dw_pcie_device_mode mode; }; @@ -141,20 +142,10 @@ static int rcar_gen4_pcie_speed_change(struct dw_pcie *dw) return -ETIMEDOUT; } -/* - * Enable LTSSM of this controller and manually initiate the speed change. - * Always return 0. - */ -static int rcar_gen4_pcie_start_link(struct dw_pcie *dw) +static int rcar_gen4_pcie_speed_control(struct rcar_gen4_pcie *rcar) { - struct rcar_gen4_pcie *rcar = to_rcar_gen4_pcie(dw); - int i, changes, ret; - - if (rcar->drvdata->ltssm_control) { - ret = rcar->drvdata->ltssm_control(rcar, true); - if (ret) - return ret; - } + struct dw_pcie *dw = &rcar->dw; + int i, changes; /* * Require direct speed change with retrying here if the max_link_speed @@ -178,6 +169,24 @@ static int rcar_gen4_pcie_start_link(struct dw_pcie *dw) return 0; } +/* + * Enable LTSSM of this controller and manually initiate the speed change. + * Always return 0. + */ +static int rcar_gen4_pcie_start_link(struct dw_pcie *dw) +{ + struct rcar_gen4_pcie *rcar = to_rcar_gen4_pcie(dw); + int ret; + + if (rcar->drvdata->ltssm_control) { + ret = rcar->drvdata->ltssm_control(rcar, true); + if (ret) + return ret; + } + + return rcar->drvdata->speed_control(rcar); +} + static void rcar_gen4_pcie_stop_link(struct dw_pcie *dw) { struct rcar_gen4_pcie *rcar = to_rcar_gen4_pcie(dw); @@ -916,6 +925,7 @@ static struct rcar_gen4_pcie_drvdata drvdata_r8a779f0_pcie = { .init = rcar_gen4_pcie_common_init, .deinit = rcar_gen4_pcie_common_deinit, .ltssm_control = r8a779f0_pcie_ltssm_control, + .speed_control = rcar_gen4_pcie_speed_control, .mode = DW_PCIE_RC_TYPE, }; @@ -923,6 +933,7 @@ static struct rcar_gen4_pcie_drvdata drvdata_r8a779f0_pcie_ep = { .init = rcar_gen4_pcie_common_init, .deinit = rcar_gen4_pcie_common_deinit, .ltssm_control = r8a779f0_pcie_ltssm_control, + .speed_control = rcar_gen4_pcie_speed_control, .mode = DW_PCIE_EP_TYPE, }; @@ -930,6 +941,7 @@ static struct rcar_gen4_pcie_drvdata drvdata_rcar_gen4_pcie = { .init = rcar_gen4_v4h_v4m_pcie_init, .deinit = rcar_gen4_pcie_common_deinit, .ltssm_control = rcar_gen4_pcie_ltssm_control, + .speed_control = rcar_gen4_pcie_speed_control, .mode = DW_PCIE_RC_TYPE, }; @@ -937,6 +949,7 @@ static struct rcar_gen4_pcie_drvdata drvdata_rcar_gen4_pcie_ep = { .init = rcar_gen4_v4h_v4m_pcie_init, .deinit = rcar_gen4_pcie_common_deinit, .ltssm_control = rcar_gen4_pcie_ltssm_control, + .speed_control = rcar_gen4_pcie_speed_control, .mode = DW_PCIE_EP_TYPE, }; From 487a731517391bbc6e887ac242fc11d4f61088e8 Mon Sep 17 00:00:00 2001 From: Marek Vasut Date: Mon, 21 Sep 2026 22:27:47 +0200 Subject: [PATCH 043/139] PCI: rcar-gen4: Handle PERST# via reset subsystem Handle PERST# via both GPIO and reset subsystem. On R-Car Gen4, the PERST# signal is operated as a GPIO, on R-Car Gen5 it might only be accessible via SCMI reset via reset subsystem. Support both options. This is a preparatory work for R-Car Gen5 support. Signed-off-by: Marek Vasut Signed-off-by: Manivannan Sadhasivam Signed-off-by: Bjorn Helgaas Link: https://patch.msgid.link/20260921202823.72620-6-marek.vasut+renesas@mailbox.org --- drivers/pci/controller/dwc/pcie-rcar-gen4.c | 41 +++++++++++++++++++-- 1 file changed, 37 insertions(+), 4 deletions(-) diff --git a/drivers/pci/controller/dwc/pcie-rcar-gen4.c b/drivers/pci/controller/dwc/pcie-rcar-gen4.c index c768a9c7b3b7..cf1f5de45182 100644 --- a/drivers/pci/controller/dwc/pcie-rcar-gen4.c +++ b/drivers/pci/controller/dwc/pcie-rcar-gen4.c @@ -99,6 +99,7 @@ struct rcar_gen4_pcie { void __iomem *base; void __iomem *phy_base; struct platform_device *pdev; + struct reset_control *perst; const struct rcar_gen4_pcie_drvdata *drvdata; }; #define to_rcar_gen4_pcie(_dw) container_of(_dw, struct rcar_gen4_pcie, dw) @@ -317,12 +318,25 @@ static void rcar_gen4_pcie_unprepare(struct rcar_gen4_pcie *rcar) static int rcar_gen4_pcie_get_resources(struct rcar_gen4_pcie *rcar) { + struct device *dev = rcar->dw.dev; + struct device_node *root_port; + rcar->phy_base = devm_platform_ioremap_resource_byname(rcar->pdev, "phy"); if (IS_ERR(rcar->phy_base)) return PTR_ERR(rcar->phy_base); + root_port = of_get_next_available_child(dev->of_node, NULL); + if (root_port) { + rcar->perst = of_reset_control_get_optional_exclusive(root_port, "perst"); + of_node_put(root_port); + if (IS_ERR(rcar->perst)) + return dev_err_probe(dev, PTR_ERR(rcar->perst), "Failed to get PERST#\n"); + } + /* Renesas-specific registers */ rcar->base = devm_platform_ioremap_resource_byname(rcar->pdev, "app"); + if (IS_ERR(rcar->base)) + reset_control_put(rcar->perst); return PTR_ERR_OR_ZERO(rcar->base); } @@ -494,6 +508,21 @@ static int rcar_gen4_pcie_enable_device(struct pci_host_bridge *bridge, return 0; } +static void rcar_gen4_pcie_host_perst_assert(struct dw_pcie_rp *pp, bool assert) +{ + struct dw_pcie *dw = to_dw_pcie_from_pp(pp); + struct rcar_gen4_pcie *rcar = to_rcar_gen4_pcie(dw); + + if (dw->pe_rst) { + gpiod_set_value_cansleep(dw->pe_rst, assert); + } else { + if (assert) + reset_control_assert(rcar->perst); + else + reset_control_deassert(rcar->perst); + } +} + /* Host mode */ static int rcar_gen4_pcie_host_init(struct dw_pcie_rp *pp) { @@ -504,7 +533,7 @@ static int rcar_gen4_pcie_host_init(struct dw_pcie_rp *pp) if (pp->bridge) pp->bridge->enable_device = rcar_gen4_pcie_enable_device; - gpiod_set_value_cansleep(dw->pe_rst, 1); + rcar_gen4_pcie_host_perst_assert(pp, true); ret = rcar->drvdata->init(rcar); if (ret) @@ -525,7 +554,7 @@ static int rcar_gen4_pcie_host_init(struct dw_pcie_rp *pp) msleep(PCIE_T_PVPERL_MS); /* pe_rst requires 100msec delay */ - gpiod_set_value_cansleep(dw->pe_rst, 0); + rcar_gen4_pcie_host_perst_assert(pp, false); return 0; @@ -539,7 +568,7 @@ static void rcar_gen4_pcie_host_deinit(struct dw_pcie_rp *pp) struct dw_pcie *dw = to_dw_pcie_from_pp(pp); struct rcar_gen4_pcie *rcar = to_rcar_gen4_pcie(dw); - gpiod_set_value_cansleep(dw->pe_rst, 1); + rcar_gen4_pcie_host_perst_assert(pp, true); rcar->drvdata->deinit(rcar); } @@ -713,7 +742,7 @@ static int rcar_gen4_pcie_probe(struct platform_device *pdev) err = rcar_gen4_pcie_prepare(rcar); if (err) - return err; + goto err_prepare; err = rcar_gen4_add_dw_pcie(rcar); if (err) @@ -724,6 +753,9 @@ static int rcar_gen4_pcie_probe(struct platform_device *pdev) err_unprepare: rcar_gen4_pcie_unprepare(rcar); +err_prepare: + reset_control_put(rcar->perst); + return err; } @@ -747,6 +779,7 @@ static void rcar_gen4_pcie_remove(struct platform_device *pdev) rcar_gen4_remove_dw_pcie(rcar); rcar_gen4_pcie_unprepare(rcar); + reset_control_put(rcar->perst); } static int r8a779f0_pcie_ltssm_control(struct rcar_gen4_pcie *rcar, bool enable) From 3283c4db60093970509b4b11e0b8a18f342aafb5 Mon Sep 17 00:00:00 2001 From: Marek Vasut Date: Mon, 21 Sep 2026 22:27:48 +0200 Subject: [PATCH 044/139] PCI: rcar-gen4: Add support for R-Car X5H PCIe4 Add support for R8A78000 (R-Car X5H) PCIe4. This driver previously supported R-Car Gen4 S4/V4H/V4M. PCIe features of R-Car X5H PCIe4 are almost all the same. The controller initialization sequence is slightly different and is factored out into controller-specific callbacks, in a manner similar to previous R-Car Gen4 handling. The controller does have a PHY attached to it, but the PHY is operated by a separate PHY driver. The PHY driver instance binding is handled in rcar_gen4_pcie_get_resources() and controlled in the aforementioned controller-specific callbacks. The controller driver is deliberately using "renesas,rcar-gen5-pcie4" DT compatible string to discern R-Car X5H PCIe4 controller supported by this driver, from R-Car X5H PCIe6 controller which will most likely use a separate driver. The R-Car X5H PCIe4 controller embeds HDMA instead of EDMA embedded in the R-Car Gen4 PCIe controller, "dw-edma" driver supports both DMA variants. Endpoint mode is currently not implemented for R-Car Gen5 PCIe4. Signed-off-by: Marek Vasut Signed-off-by: Manivannan Sadhasivam Signed-off-by: Bjorn Helgaas Link: https://patch.msgid.link/20260921202823.72620-7-marek.vasut+renesas@mailbox.org --- drivers/pci/controller/dwc/pcie-rcar-gen4.c | 120 +++++++++++++++++++- 1 file changed, 118 insertions(+), 2 deletions(-) diff --git a/drivers/pci/controller/dwc/pcie-rcar-gen4.c b/drivers/pci/controller/dwc/pcie-rcar-gen4.c index cf1f5de45182..0b7ae4a69308 100644 --- a/drivers/pci/controller/dwc/pcie-rcar-gen4.c +++ b/drivers/pci/controller/dwc/pcie-rcar-gen4.c @@ -19,6 +19,7 @@ #include #include #include +#include #include #include #include @@ -36,6 +37,7 @@ /* MSI Capability */ #define MSICAP0 0x0050 +#define MSICAP0_MMESCAP_MASK GENMASK(19, 17) #define MSICAP0_MSIE BIT(16) /* PCIe Interrupt Status 0 */ @@ -74,6 +76,11 @@ #define PCIEPWRMNGCTRL 0x0070 #define APP_CLK_REQ_N BIT(11) #define APP_CLK_PM_EN BIT(10) +#define APP_READY_ENTR_L23 BIT(6) +#define APP_REQ_ENTR_L1 BIT(5) + +/* PCI Express capability */ +#define EXPCAP(x) (0x0070 + (x)) #define RCAR_NUM_SPEED_CHANGE_RETRIES 10 #define RCAR_MAX_LINK_SPEED 4 @@ -98,6 +105,7 @@ struct rcar_gen4_pcie { struct dw_pcie dw; void __iomem *base; void __iomem *phy_base; + struct phy *phy; struct platform_device *pdev; struct reset_control *perst; const struct rcar_gen4_pcie_drvdata *drvdata; @@ -170,6 +178,27 @@ static int rcar_gen4_pcie_speed_control(struct rcar_gen4_pcie *rcar) return 0; } +static int rcar_gen5_pcie_speed_control(struct rcar_gen4_pcie *rcar) +{ + struct dw_pcie *dw = &rcar->dw; + u32 lnkcap = dw_pcie_readl_dbi(dw, EXPCAP(PCI_EXP_LNKCAP)); + u32 lnksta = dw_pcie_readw_dbi(dw, EXPCAP(PCI_EXP_LNKSTA)); + u32 val; + + if ((lnksta & PCI_EXP_LNKSTA_CLS) == (lnkcap & PCI_EXP_LNKCAP_SLS)) + return 0; + + /* Retrain link */ + val = dw_pcie_readw_dbi(dw, EXPCAP(PCI_EXP_LNKCTL)); + val |= PCI_EXP_LNKCTL_RL; + dw_pcie_writew_dbi(dw, EXPCAP(PCI_EXP_LNKCTL), val); + + /* Wait for link retrain, 500ms must be enough for all link rates. */ + return read_poll_timeout(dw_pcie_readw_dbi, lnksta, !(lnksta & PCI_EXP_LNKSTA_LT), + 1000, 5 * PCIE_RESET_CONFIG_WAIT_MS * USEC_PER_MSEC, + false, dw, EXPCAP(PCI_EXP_LNKSTA)); +} + /* * Enable LTSSM of this controller and manually initiate the speed change. * Always return 0. @@ -285,6 +314,49 @@ static int rcar_gen4_v4h_v4m_pcie_init(struct rcar_gen4_pcie *rcar) return 0; } +static int rcar_gen5_pcie_init(struct rcar_gen4_pcie *rcar) +{ + struct dw_pcie *dw = &rcar->dw; + int ret; + u32 val; + + /* R-Car Gen4 and Gen5 common initialization. */ + ret = rcar_gen4_pcie_common_init(rcar); + if (ret) + return ret; + + /* R-Car Gen5 specific additional initialization. */ + ret = phy_init(rcar->phy); + if (ret) + goto err_unprepare; + + dw_pcie_dbi_ro_wr_en(dw); + + val = dw_pcie_readl_dbi(dw, PCIE_PORT_LANE_SKEW); + val &= ~PORT_LANE_SKEW_INSERT_MASK; + if (dw->num_lanes < 8) + val |= BIT(6); + dw_pcie_writel_dbi(dw, PCIE_PORT_LANE_SKEW, val); + + val = dw_pcie_readl_dbi(dw, MSICAP0); + FIELD_MODIFY(MSICAP0_MMESCAP_MASK, &val, 4); + dw_pcie_writel_dbi(dw, MSICAP0, val); + + dw_pcie_dbi_ro_wr_dis(dw); + + val = readl(rcar->base + PCIEPWRMNGCTRL); + val |= APP_CLK_REQ_N | APP_CLK_PM_EN | + APP_READY_ENTR_L23 | APP_REQ_ENTR_L1; + writel(val, rcar->base + PCIEPWRMNGCTRL); + + return 0; + +err_unprepare: + clk_bulk_disable_unprepare(DW_PCIE_NUM_CORE_CLKS, dw->core_clks); + + return ret; +} + static void rcar_gen4_pcie_common_deinit(struct rcar_gen4_pcie *rcar) { struct dw_pcie *dw = &rcar->dw; @@ -293,6 +365,12 @@ static void rcar_gen4_pcie_common_deinit(struct rcar_gen4_pcie *rcar) clk_bulk_disable_unprepare(DW_PCIE_NUM_CORE_CLKS, dw->core_clks); } +static void rcar_gen5_pcie_deinit(struct rcar_gen4_pcie *rcar) +{ + phy_exit(rcar->phy); + rcar_gen4_pcie_common_deinit(rcar); +} + static int rcar_gen4_pcie_prepare(struct rcar_gen4_pcie *rcar) { struct device *dev = rcar->dw.dev; @@ -322,8 +400,12 @@ static int rcar_gen4_pcie_get_resources(struct rcar_gen4_pcie *rcar) struct device_node *root_port; rcar->phy_base = devm_platform_ioremap_resource_byname(rcar->pdev, "phy"); - if (IS_ERR(rcar->phy_base)) - return PTR_ERR(rcar->phy_base); + if (IS_ERR(rcar->phy_base)) { + rcar->phy_base = NULL; + rcar->phy = devm_phy_get(dev, NULL); + if (IS_ERR(rcar->phy)) + return PTR_ERR(rcar->phy); + } root_port = of_get_next_available_child(dev->of_node, NULL); if (root_port) { @@ -804,6 +886,28 @@ static int r8a779f0_pcie_ltssm_control(struct rcar_gen4_pcie *rcar, bool enable) return 0; } +static int rcar_gen5_pcie_ltssm_control(struct rcar_gen4_pcie *rcar, bool enable) +{ + u32 val; + + val = readl(rcar->base + PCIERSTCTRL1); + if (enable) { + val |= APP_LTSSM_ENABLE; + val &= ~APP_HOLD_PHY_RST; + } else { + val &= ~APP_LTSSM_ENABLE; + val |= APP_HOLD_PHY_RST; + } + writel(val, rcar->base + PCIERSTCTRL1); + + if (enable) + phy_power_on(rcar->phy); + else + phy_power_off(rcar->phy); + + return 0; +} + static void rcar_gen4_pcie_phy_reg_update_bits(struct rcar_gen4_pcie *rcar, u32 offset, u32 mask, u32 val) { @@ -986,6 +1090,14 @@ static struct rcar_gen4_pcie_drvdata drvdata_rcar_gen4_pcie_ep = { .mode = DW_PCIE_EP_TYPE, }; +static struct rcar_gen4_pcie_drvdata drvdata_rcar_gen5_pcie = { + .init = rcar_gen5_pcie_init, + .deinit = rcar_gen5_pcie_deinit, + .ltssm_control = rcar_gen5_pcie_ltssm_control, + .speed_control = rcar_gen5_pcie_speed_control, + .mode = DW_PCIE_RC_TYPE, +}; + static const struct of_device_id rcar_gen4_pcie_of_match[] = { { .compatible = "renesas,r8a779f0-pcie", @@ -1003,6 +1115,10 @@ static const struct of_device_id rcar_gen4_pcie_of_match[] = { .compatible = "renesas,rcar-gen4-pcie-ep", .data = &drvdata_rcar_gen4_pcie_ep, }, + { + .compatible = "renesas,rcar-gen5-pcie4", + .data = &drvdata_rcar_gen5_pcie, + }, {}, }; MODULE_DEVICE_TABLE(of, rcar_gen4_pcie_of_match); From 056fd7eb017d73d9d175b733043edfbeb43569be Mon Sep 17 00:00:00 2001 From: Marek Vasut Date: Mon, 21 Sep 2026 22:36:46 +0200 Subject: [PATCH 045/139] PCI: rcar-gen4: Drop documentation about obtaining platform firmware Renesas R-Car V4H (r8a779g0) has a PCIe controller, and it requires specific firmware downloading. The firmware is now available via linux-firmware.git repository. So the existing documentation about obtaining the firmware becomes obsolete. Hence, drop it. Signed-off-by: Marek Vasut Signed-off-by: Manivannan Sadhasivam Signed-off-by: Bjorn Helgaas Link: https://patch.msgid.link/20260921203716.73663-1-marek.vasut+renesas@mailbox.org --- Documentation/PCI/controller/index.rst | 1 - .../PCI/controller/rcar-pcie-firmware.rst | 32 ------------------- MAINTAINERS | 1 - 3 files changed, 34 deletions(-) delete mode 100644 Documentation/PCI/controller/rcar-pcie-firmware.rst diff --git a/Documentation/PCI/controller/index.rst b/Documentation/PCI/controller/index.rst index c15e2c9ac855..096d1c3d9807 100644 --- a/Documentation/PCI/controller/index.rst +++ b/Documentation/PCI/controller/index.rst @@ -8,4 +8,3 @@ PCI Native Host Bridge and Endpoint Drivers :maxdepth: 2 pci-controller-drivers - rcar-pcie-firmware diff --git a/Documentation/PCI/controller/rcar-pcie-firmware.rst b/Documentation/PCI/controller/rcar-pcie-firmware.rst deleted file mode 100644 index 67d3bf66e315..000000000000 --- a/Documentation/PCI/controller/rcar-pcie-firmware.rst +++ /dev/null @@ -1,32 +0,0 @@ -.. SPDX-License-Identifier: GPL-2.0 - -================================================= -Firmware of PCIe controller for Renesas R-Car V4H -================================================= - -Renesas R-Car V4H (r8a779g0) has a PCIe controller, requiring a specific -firmware download during startup. - -However, Renesas currently cannot distribute the firmware free of charge. - -The firmware file "104_PCIe_fw_addr_data_ver1.05.txt" (note that the file name -might be different between different datasheet revisions) can be found in the -datasheet encoded as text, and as such, the file's content must be converted -back to binary form. This can be achieved using the following example script: - -.. code-block:: sh - - $ awk '/^\s*0x[0-9A-Fa-f]{4}\s+0x[0-9A-Fa-f]{4}/ { print substr($2,5,2) substr($2,3,2) }' \ - 104_PCIe_fw_addr_data_ver1.05.txt | \ - xxd -p -r > rcar_gen4_pcie.bin - -Once the text content has been converted into a binary firmware file, verify -its checksum as follows: - -.. code-block:: sh - - $ sha1sum rcar_gen4_pcie.bin - 1d0bd4b189b4eb009f5d564b1f93a79112994945 rcar_gen4_pcie.bin - -The resulting binary file called "rcar_gen4_pcie.bin" should be placed in the -"/lib/firmware" directory before the driver runs. diff --git a/MAINTAINERS b/MAINTAINERS index 3a19da74d00c..8df569bbf3a9 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -20954,7 +20954,6 @@ M: Yoshihiro Shimoda L: linux-pci@vger.kernel.org L: linux-renesas-soc@vger.kernel.org S: Maintained -F: Documentation/PCI/controller/rcar-pcie-firmware.rst F: Documentation/devicetree/bindings/pci/*rcar* F: drivers/pci/controller/*rcar* F: drivers/pci/controller/dwc/*rcar* From 99d1fdd09b22b43be504b309ca80137ed6b3448c Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Wed, 2 Sep 2026 16:24:49 +0200 Subject: [PATCH 046/139] dt-bindings: PCI: tegra: Convert to json-schema Convert the Tegra PCIe controller bindings from the free-form text format to json-schema. Signed-off-by: Thierry Reding Signed-off-by: Manivannan Sadhasivam Signed-off-by: Bjorn Helgaas Reviewed-by: Rob Herring (Arm) Link: https://patch.msgid.link/20260902142449.735168-1-thierry.reding@kernel.org --- .../bindings/pci/nvidia,tegra20-pcie.txt | 670 ------------------ .../bindings/pci/nvidia,tegra20-pcie.yaml | 532 ++++++++++++++ 2 files changed, 532 insertions(+), 670 deletions(-) delete mode 100644 Documentation/devicetree/bindings/pci/nvidia,tegra20-pcie.txt create mode 100644 Documentation/devicetree/bindings/pci/nvidia,tegra20-pcie.yaml diff --git a/Documentation/devicetree/bindings/pci/nvidia,tegra20-pcie.txt b/Documentation/devicetree/bindings/pci/nvidia,tegra20-pcie.txt deleted file mode 100644 index d099f3476ccc..000000000000 --- a/Documentation/devicetree/bindings/pci/nvidia,tegra20-pcie.txt +++ /dev/null @@ -1,670 +0,0 @@ -NVIDIA Tegra PCIe controller - -Required properties: -- compatible: Must be: - - "nvidia,tegra20-pcie": for Tegra20 - - "nvidia,tegra30-pcie": for Tegra30 - - "nvidia,tegra124-pcie": for Tegra124 and Tegra132 - - "nvidia,tegra210-pcie": for Tegra210 - - "nvidia,tegra186-pcie": for Tegra186 -- power-domains: To ungate power partition by BPMP powergate driver. Must - contain BPMP phandle and PCIe power partition ID. This is required only - for Tegra186. -- device_type: Must be "pci" -- reg: A list of physical base address and length for each set of controller - registers. Must contain an entry for each entry in the reg-names property. -- reg-names: Must include the following entries: - "pads": PADS registers - "afi": AFI registers - "cs": configuration space region -- interrupts: A list of interrupt outputs of the controller. Must contain an - entry for each entry in the interrupt-names property. -- interrupt-names: Must include the following entries: - "intr": The Tegra interrupt that is asserted for controller interrupts - "msi": The Tegra interrupt that is asserted when an MSI is received -- bus-range: Range of bus numbers associated with this controller -- #address-cells: Address representation for root ports (must be 3) - - cell 0 specifies the bus and device numbers of the root port: - [23:16]: bus number - [15:11]: device number - - cell 1 denotes the upper 32 address bits and should be 0 - - cell 2 contains the lower 32 address bits and is used to translate to the - CPU address space -- #size-cells: Size representation for root ports (must be 2) -- ranges: Describes the translation of addresses for root ports and standard - PCI regions. The entries must be 6 cells each, where the first three cells - correspond to the address as described for the #address-cells property - above, the fourth cell is the physical CPU address to translate to and the - fifth and six cells are as described for the #size-cells property above. - - The first two entries are expected to translate the addresses for the root - port registers, which are referenced by the assigned-addresses property of - the root port nodes (see below). - - The remaining entries setup the mapping for the standard I/O, memory and - prefetchable PCI regions. The first cell determines the type of region - that is setup: - - 0x81000000: I/O memory region - - 0x82000000: non-prefetchable memory region - - 0xc2000000: prefetchable memory region - Please refer to the standard PCI bus binding document for a more detailed - explanation. -- #interrupt-cells: Size representation for interrupts (must be 1) -- interrupt-map-mask and interrupt-map: Standard PCI IRQ mapping properties - Please refer to the standard PCI bus binding document for a more detailed - explanation. -- clocks: Must contain an entry for each entry in clock-names. - See ../clocks/clock-bindings.txt for details. -- clock-names: Must include the following entries: - - pex - - afi - - pll_e - - cml (not required for Tegra20) -- resets: Must contain an entry for each entry in reset-names. - See ../reset/reset.txt for details. -- reset-names: Must include the following entries: - - pex - - afi - - pcie_x - -Optional properties: -- pinctrl-names: A list of pinctrl state names. Must contain the following - entries: - - "default": active state, puts PCIe I/O out of deep power down state - - "idle": puts PCIe I/O into deep power down state -- pinctrl-0: phandle for the default/active state of pin configurations. -- pinctrl-1: phandle for the idle state of pin configurations. - -Required properties on Tegra124 and later (deprecated): -- phys: Must contain an entry for each entry in phy-names. -- phy-names: Must include the following entries: - - pcie - -These properties are deprecated in favour of per-lane PHYs define in each of -the root ports (see below). - -Power supplies for Tegra20: -- avdd-pex-supply: Power supply for analog PCIe logic. Must supply 1.05 V. -- vdd-pex-supply: Power supply for digital PCIe I/O. Must supply 1.05 V. -- avdd-pex-pll-supply: Power supply for dedicated (internal) PCIe PLL. Must - supply 1.05 V. -- avdd-plle-supply: Power supply for PLLE, which is shared with SATA. Must - supply 1.05 V. -- vddio-pex-clk-supply: Power supply for PCIe clock. Must supply 3.3 V. - -Power supplies for Tegra30: -- Required: - - avdd-pex-pll-supply: Power supply for dedicated (internal) PCIe PLL. Must - supply 1.05 V. - - avdd-plle-supply: Power supply for PLLE, which is shared with SATA. Must - supply 1.05 V. - - vddio-pex-ctl-supply: Power supply for PCIe control I/O partition. Must - supply 1.8 V. - - hvdd-pex-supply: High-voltage supply for PCIe I/O and PCIe output clocks. - Must supply 3.3 V. -- Optional: - - If lanes 0 to 3 are used: - - avdd-pexa-supply: Power supply for analog PCIe logic. Must supply 1.05 V. - - vdd-pexa-supply: Power supply for digital PCIe I/O. Must supply 1.05 V. - - If lanes 4 or 5 are used: - - avdd-pexb-supply: Power supply for analog PCIe logic. Must supply 1.05 V. - - vdd-pexb-supply: Power supply for digital PCIe I/O. Must supply 1.05 V. - -Power supplies for Tegra124: -- Required: - - avddio-pex-supply: Power supply for analog PCIe logic. Must supply 1.05 V. - - dvddio-pex-supply: Power supply for digital PCIe I/O. Must supply 1.05 V. - - hvdd-pex-supply: High-voltage supply for PCIe I/O and PCIe output clocks. - Must supply 3.3 V. - - vddio-pex-ctl-supply: Power supply for PCIe control I/O partition. Must - supply 2.8-3.3 V. - -Power supplies for Tegra210: -- Required: - - hvddio-pex-supply: High-voltage supply for PCIe I/O and PCIe output - clocks. Must supply 1.8 V. - - dvddio-pex-supply: Power supply for digital PCIe I/O. Must supply 1.05 V. - - vddio-pex-ctl-supply: Power supply for PCIe control I/O partition. Must - supply 1.8 V. - -Power supplies for Tegra186: -- Required: - - dvdd-pex-supply: Power supply for digital PCIe I/O. Must supply 1.05 V. - - hvdd-pex-pll-supply: High-voltage supply for PLLE (shared with USB3). Must - supply 1.8 V. - - hvdd-pex-supply: High-voltage supply for PCIe I/O and PCIe output clocks. - Must supply 1.8 V. - - vddio-pexctl-aud-supply: Power supply for PCIe side band signals. Must - supply 1.8 V. - -Root ports are defined as subnodes of the PCIe controller node. - -Required properties: -- device_type: Must be "pci" -- assigned-addresses: Address and size of the port configuration registers -- reg: PCI bus address of the root port -- #address-cells: Must be 3 -- #size-cells: Must be 2 -- ranges: Sub-ranges distributed from the PCIe controller node. An empty - property is sufficient. -- nvidia,num-lanes: Number of lanes to use for this port. Valid combinations - are: - - Root port 0 uses 4 lanes, root port 1 is unused. - - Both root ports use 2 lanes. - -Required properties for Tegra124 and later: -- phys: Must contain an phandle to a PHY for each entry in phy-names. -- phy-names: Must include an entry for each active lane. Note that the number - of entries does not have to (though usually will) be equal to the specified - number of lanes in the nvidia,num-lanes property. Entries are of the form - "pcie-N": where N ranges from 0 to the value specified in nvidia,num-lanes. - -Examples: -========= - -Tegra20: --------- - -SoC DTSI: - - pcie-controller@80003000 { - compatible = "nvidia,tegra20-pcie"; - device_type = "pci"; - reg = <0x80003000 0x00000800 /* PADS registers */ - 0x80003800 0x00000200 /* AFI registers */ - 0x90000000 0x10000000>; /* configuration space */ - reg-names = "pads", "afi", "cs"; - interrupts = <0 98 0x04 /* controller interrupt */ - 0 99 0x04>; /* MSI interrupt */ - interrupt-names = "intr", "msi"; - - #interrupt-cells = <1>; - interrupt-map-mask = <0 0 0 0>; - interrupt-map = <0 0 0 0 &intc GIC_SPI 98 IRQ_TYPE_LEVEL_HIGH>; - - bus-range = <0x00 0xff>; - #address-cells = <3>; - #size-cells = <2>; - - ranges = <0x82000000 0 0x80000000 0x80000000 0 0x00001000 /* port 0 registers */ - 0x82000000 0 0x80001000 0x80001000 0 0x00001000 /* port 1 registers */ - 0x81000000 0 0 0x82000000 0 0x00010000 /* downstream I/O */ - 0x82000000 0 0xa0000000 0xa0000000 0 0x10000000 /* non-prefetchable memory */ - 0xc2000000 0 0xb0000000 0xb0000000 0 0x10000000>; /* prefetchable memory */ - - clocks = <&tegra_car 70>, <&tegra_car 72>, <&tegra_car 118>; - clock-names = "pex", "afi", "pll_e"; - resets = <&tegra_car 70>, <&tegra_car 72>, <&tegra_car 74>; - reset-names = "pex", "afi", "pcie_x"; - status = "disabled"; - - pci@1,0 { - device_type = "pci"; - assigned-addresses = <0x82000800 0 0x80000000 0 0x1000>; - reg = <0x000800 0 0 0 0>; - status = "disabled"; - - #address-cells = <3>; - #size-cells = <2>; - - ranges; - - nvidia,num-lanes = <2>; - }; - - pci@2,0 { - device_type = "pci"; - assigned-addresses = <0x82001000 0 0x80001000 0 0x1000>; - reg = <0x001000 0 0 0 0>; - status = "disabled"; - - #address-cells = <3>; - #size-cells = <2>; - - ranges; - - nvidia,num-lanes = <2>; - }; - }; - -Board DTS: - - pcie-controller@80003000 { - status = "okay"; - - vdd-supply = <&pci_vdd_reg>; - pex-clk-supply = <&pci_clk_reg>; - - /* root port 00:01.0 */ - pci@1,0 { - status = "okay"; - - /* bridge 01:00.0 (optional) */ - pci@0,0 { - reg = <0x010000 0 0 0 0>; - - #address-cells = <3>; - #size-cells = <2>; - - device_type = "pci"; - - /* endpoint 02:00.0 */ - pci@0,0 { - reg = <0x020000 0 0 0 0>; - }; - }; - }; - }; - -Note that devices on the PCI bus are dynamically discovered using PCI's bus -enumeration and therefore don't need corresponding device nodes in DT. However -if a device on the PCI bus provides a non-probeable bus such as I2C or SPI, -device nodes need to be added in order to allow the bus' children to be -instantiated at the proper location in the operating system's device tree (as -illustrated by the optional nodes in the example above). - -Tegra30: --------- - -SoC DTSI: - - pcie-controller@3000 { - compatible = "nvidia,tegra30-pcie"; - device_type = "pci"; - reg = <0x00003000 0x00000800 /* PADS registers */ - 0x00003800 0x00000200 /* AFI registers */ - 0x10000000 0x10000000>; /* configuration space */ - reg-names = "pads", "afi", "cs"; - interrupts = ; /* MSI interrupt */ - interrupt-names = "intr", "msi"; - - #interrupt-cells = <1>; - interrupt-map-mask = <0 0 0 0>; - interrupt-map = <0 0 0 0 &intc GIC_SPI 98 IRQ_TYPE_LEVEL_HIGH>; - - bus-range = <0x00 0xff>; - #address-cells = <3>; - #size-cells = <2>; - - ranges = <0x82000000 0 0x00000000 0x00000000 0 0x00001000 /* port 0 configuration space */ - 0x82000000 0 0x00001000 0x00001000 0 0x00001000 /* port 1 configuration space */ - 0x82000000 0 0x00004000 0x00004000 0 0x00001000 /* port 2 configuration space */ - 0x81000000 0 0 0x02000000 0 0x00010000 /* downstream I/O */ - 0x82000000 0 0x20000000 0x20000000 0 0x08000000 /* non-prefetchable memory */ - 0xc2000000 0 0x28000000 0x28000000 0 0x18000000>; /* prefetchable memory */ - - clocks = <&tegra_car TEGRA30_CLK_PCIE>, - <&tegra_car TEGRA30_CLK_AFI>, - <&tegra_car TEGRA30_CLK_PLL_E>, - <&tegra_car TEGRA30_CLK_CML0>; - clock-names = "pex", "afi", "pll_e", "cml"; - resets = <&tegra_car 70>, - <&tegra_car 72>, - <&tegra_car 74>; - reset-names = "pex", "afi", "pcie_x"; - status = "disabled"; - - pci@1,0 { - device_type = "pci"; - assigned-addresses = <0x82000800 0 0x00000000 0 0x1000>; - reg = <0x000800 0 0 0 0>; - status = "disabled"; - - #address-cells = <3>; - #size-cells = <2>; - ranges; - - nvidia,num-lanes = <2>; - }; - - pci@2,0 { - device_type = "pci"; - assigned-addresses = <0x82001000 0 0x00001000 0 0x1000>; - reg = <0x001000 0 0 0 0>; - status = "disabled"; - - #address-cells = <3>; - #size-cells = <2>; - ranges; - - nvidia,num-lanes = <2>; - }; - - pci@3,0 { - device_type = "pci"; - assigned-addresses = <0x82001800 0 0x00004000 0 0x1000>; - reg = <0x001800 0 0 0 0>; - status = "disabled"; - - #address-cells = <3>; - #size-cells = <2>; - ranges; - - nvidia,num-lanes = <2>; - }; - }; - -Board DTS: - - pcie-controller@3000 { - status = "okay"; - - avdd-pexa-supply = <&ldo1_reg>; - vdd-pexa-supply = <&ldo1_reg>; - avdd-pexb-supply = <&ldo1_reg>; - vdd-pexb-supply = <&ldo1_reg>; - avdd-pex-pll-supply = <&ldo1_reg>; - avdd-plle-supply = <&ldo1_reg>; - vddio-pex-ctl-supply = <&sys_3v3_reg>; - hvdd-pex-supply = <&sys_3v3_pexs_reg>; - - pci@1,0 { - status = "okay"; - }; - - pci@3,0 { - status = "okay"; - }; - }; - -Tegra124: ---------- - -SoC DTSI: - - pcie-controller@1003000 { - compatible = "nvidia,tegra124-pcie"; - device_type = "pci"; - reg = <0x0 0x01003000 0x0 0x00000800 /* PADS registers */ - 0x0 0x01003800 0x0 0x00000800 /* AFI registers */ - 0x0 0x02000000 0x0 0x10000000>; /* configuration space */ - reg-names = "pads", "afi", "cs"; - interrupts = , /* controller interrupt */ - ; /* MSI interrupt */ - interrupt-names = "intr", "msi"; - - #interrupt-cells = <1>; - interrupt-map-mask = <0 0 0 0>; - interrupt-map = <0 0 0 0 &gic GIC_SPI 98 IRQ_TYPE_LEVEL_HIGH>; - - bus-range = <0x00 0xff>; - #address-cells = <3>; - #size-cells = <2>; - - ranges = <0x82000000 0 0x01000000 0x0 0x01000000 0 0x00001000 /* port 0 configuration space */ - 0x82000000 0 0x01001000 0x0 0x01001000 0 0x00001000 /* port 1 configuration space */ - 0x81000000 0 0x0 0x0 0x12000000 0 0x00010000 /* downstream I/O (64 KiB) */ - 0x82000000 0 0x13000000 0x0 0x13000000 0 0x0d000000 /* non-prefetchable memory (208 MiB) */ - 0xc2000000 0 0x20000000 0x0 0x20000000 0 0x20000000>; /* prefetchable memory (512 MiB) */ - - clocks = <&tegra_car TEGRA124_CLK_PCIE>, - <&tegra_car TEGRA124_CLK_AFI>, - <&tegra_car TEGRA124_CLK_PLL_E>, - <&tegra_car TEGRA124_CLK_CML0>; - clock-names = "pex", "afi", "pll_e", "cml"; - resets = <&tegra_car 70>, - <&tegra_car 72>, - <&tegra_car 74>; - reset-names = "pex", "afi", "pcie_x"; - status = "disabled"; - - pci@1,0 { - device_type = "pci"; - assigned-addresses = <0x82000800 0 0x01000000 0 0x1000>; - reg = <0x000800 0 0 0 0>; - status = "disabled"; - - #address-cells = <3>; - #size-cells = <2>; - ranges; - - nvidia,num-lanes = <2>; - }; - - pci@2,0 { - device_type = "pci"; - assigned-addresses = <0x82001000 0 0x01001000 0 0x1000>; - reg = <0x001000 0 0 0 0>; - status = "disabled"; - - #address-cells = <3>; - #size-cells = <2>; - ranges; - - nvidia,num-lanes = <1>; - }; - }; - -Board DTS: - - pcie-controller@1003000 { - status = "okay"; - - avddio-pex-supply = <&vdd_1v05_run>; - dvddio-pex-supply = <&vdd_1v05_run>; - avdd-pex-pll-supply = <&vdd_1v05_run>; - hvdd-pex-supply = <&vdd_3v3_lp0>; - hvdd-pex-pll-e-supply = <&vdd_3v3_lp0>; - vddio-pex-ctl-supply = <&vdd_3v3_lp0>; - avdd-pll-erefe-supply = <&avdd_1v05_run>; - - /* Mini PCIe */ - pci@1,0 { - phys = <&{/padctl@7009f000/pads/pcie/lanes/pcie-4}>; - phy-names = "pcie-0"; - status = "okay"; - }; - - /* Gigabit Ethernet */ - pci@2,0 { - phys = <&{/padctl@7009f000/pads/pcie/lanes/pcie-2}>; - phy-names = "pcie-0"; - status = "okay"; - }; - }; - -Tegra210: ---------- - -SoC DTSI: - - pcie-controller@1003000 { - compatible = "nvidia,tegra210-pcie"; - device_type = "pci"; - reg = <0x0 0x01003000 0x0 0x00000800 /* PADS registers */ - 0x0 0x01003800 0x0 0x00000800 /* AFI registers */ - 0x0 0x02000000 0x0 0x10000000>; /* configuration space */ - reg-names = "pads", "afi", "cs"; - interrupts = , /* controller interrupt */ - ; /* MSI interrupt */ - interrupt-names = "intr", "msi"; - - #interrupt-cells = <1>; - interrupt-map-mask = <0 0 0 0>; - interrupt-map = <0 0 0 0 &gic GIC_SPI 98 IRQ_TYPE_LEVEL_HIGH>; - - bus-range = <0x00 0xff>; - #address-cells = <3>; - #size-cells = <2>; - - ranges = <0x82000000 0 0x01000000 0x0 0x01000000 0 0x00001000 /* port 0 configuration space */ - 0x82000000 0 0x01001000 0x0 0x01001000 0 0x00001000 /* port 1 configuration space */ - 0x81000000 0 0x0 0x0 0x12000000 0 0x00010000 /* downstream I/O (64 KiB) */ - 0x82000000 0 0x13000000 0x0 0x13000000 0 0x0d000000 /* non-prefetchable memory (208 MiB) */ - 0xc2000000 0 0x20000000 0x0 0x20000000 0 0x20000000>; /* prefetchable memory (512 MiB) */ - - clocks = <&tegra_car TEGRA210_CLK_PCIE>, - <&tegra_car TEGRA210_CLK_AFI>, - <&tegra_car TEGRA210_CLK_PLL_E>, - <&tegra_car TEGRA210_CLK_CML0>; - clock-names = "pex", "afi", "pll_e", "cml"; - resets = <&tegra_car 70>, - <&tegra_car 72>, - <&tegra_car 74>; - reset-names = "pex", "afi", "pcie_x"; - status = "disabled"; - - pci@1,0 { - device_type = "pci"; - assigned-addresses = <0x82000800 0 0x01000000 0 0x1000>; - reg = <0x000800 0 0 0 0>; - status = "disabled"; - - #address-cells = <3>; - #size-cells = <2>; - ranges; - - nvidia,num-lanes = <4>; - }; - - pci@2,0 { - device_type = "pci"; - assigned-addresses = <0x82001000 0 0x01001000 0 0x1000>; - reg = <0x001000 0 0 0 0>; - status = "disabled"; - - #address-cells = <3>; - #size-cells = <2>; - ranges; - - nvidia,num-lanes = <1>; - }; - }; - -Board DTS: - - pcie-controller@1003000 { - status = "okay"; - - avdd-pll-uerefe-supply = <&avdd_1v05_pll>; - hvddio-pex-supply = <&vdd_1v8>; - dvddio-pex-supply = <&vdd_pex_1v05>; - dvdd-pex-pll-supply = <&vdd_pex_1v05>; - hvdd-pex-pll-e-supply = <&vdd_1v8>; - vddio-pex-ctl-supply = <&vdd_1v8>; - - pci@1,0 { - phys = <&{/padctl@7009f000/pads/pcie/lanes/pcie-0}>, - <&{/padctl@7009f000/pads/pcie/lanes/pcie-1}>, - <&{/padctl@7009f000/pads/pcie/lanes/pcie-2}>, - <&{/padctl@7009f000/pads/pcie/lanes/pcie-3}>; - phy-names = "pcie-0", "pcie-1", "pcie-2", "pcie-3"; - status = "okay"; - }; - - pci@2,0 { - phys = <&{/padctl@7009f000/pads/pcie/lanes/pcie-4}>; - phy-names = "pcie-0"; - status = "okay"; - }; - }; - -Tegra186: ---------- - -SoC DTSI: - - pcie@10003000 { - compatible = "nvidia,tegra186-pcie"; - power-domains = <&bpmp TEGRA186_POWER_DOMAIN_PCX>; - device_type = "pci"; - reg = <0x0 0x10003000 0x0 0x00000800 /* PADS registers */ - 0x0 0x10003800 0x0 0x00000800 /* AFI registers */ - 0x0 0x40000000 0x0 0x10000000>; /* configuration space */ - reg-names = "pads", "afi", "cs"; - - interrupts = , /* controller interrupt */ - ; /* MSI interrupt */ - interrupt-names = "intr", "msi"; - - #interrupt-cells = <1>; - interrupt-map-mask = <0 0 0 0>; - interrupt-map = <0 0 0 0 &gic GIC_SPI 72 IRQ_TYPE_LEVEL_HIGH>; - - bus-range = <0x00 0xff>; - #address-cells = <3>; - #size-cells = <2>; - - ranges = <0x82000000 0 0x10000000 0x0 0x10000000 0 0x00001000 /* port 0 configuration space */ - 0x82000000 0 0x10001000 0x0 0x10001000 0 0x00001000 /* port 1 configuration space */ - 0x82000000 0 0x10004000 0x0 0x10004000 0 0x00001000 /* port 2 configuration space */ - 0x81000000 0 0x0 0x0 0x50000000 0 0x00010000 /* downstream I/O (64 KiB) */ - 0x82000000 0 0x50100000 0x0 0x50100000 0 0x07F00000 /* non-prefetchable memory (127 MiB) */ - 0xc2000000 0 0x58000000 0x0 0x58000000 0 0x28000000>; /* prefetchable memory (640 MiB) */ - - clocks = <&bpmp TEGRA186_CLK_AFI>, - <&bpmp TEGRA186_CLK_PCIE>, - <&bpmp TEGRA186_CLK_PLLE>; - clock-names = "afi", "pex", "pll_e"; - - resets = <&bpmp TEGRA186_RESET_AFI>, - <&bpmp TEGRA186_RESET_PCIE>, - <&bpmp TEGRA186_RESET_PCIEXCLK>; - reset-names = "afi", "pex", "pcie_x"; - - status = "disabled"; - - pci@1,0 { - device_type = "pci"; - assigned-addresses = <0x82000800 0 0x10000000 0 0x1000>; - reg = <0x000800 0 0 0 0>; - status = "disabled"; - - #address-cells = <3>; - #size-cells = <2>; - ranges; - - nvidia,num-lanes = <2>; - }; - - pci@2,0 { - device_type = "pci"; - assigned-addresses = <0x82001000 0 0x10001000 0 0x1000>; - reg = <0x001000 0 0 0 0>; - status = "disabled"; - - #address-cells = <3>; - #size-cells = <2>; - ranges; - - nvidia,num-lanes = <1>; - }; - - pci@3,0 { - device_type = "pci"; - assigned-addresses = <0x82001800 0 0x10004000 0 0x1000>; - reg = <0x001800 0 0 0 0>; - status = "disabled"; - - #address-cells = <3>; - #size-cells = <2>; - ranges; - - nvidia,num-lanes = <1>; - }; - }; - -Board DTS: - - pcie@10003000 { - status = "okay"; - - dvdd-pex-supply = <&vdd_pex>; - hvdd-pex-pll-supply = <&vdd_1v8>; - hvdd-pex-supply = <&vdd_1v8>; - vddio-pexctl-aud-supply = <&vdd_1v8>; - - pci@1,0 { - nvidia,num-lanes = <4>; - status = "okay"; - }; - - pci@2,0 { - nvidia,num-lanes = <0>; - status = "disabled"; - }; - - pci@3,0 { - nvidia,num-lanes = <1>; - status = "disabled"; - }; - }; diff --git a/Documentation/devicetree/bindings/pci/nvidia,tegra20-pcie.yaml b/Documentation/devicetree/bindings/pci/nvidia,tegra20-pcie.yaml new file mode 100644 index 000000000000..fcc7875e6853 --- /dev/null +++ b/Documentation/devicetree/bindings/pci/nvidia,tegra20-pcie.yaml @@ -0,0 +1,532 @@ +# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/pci/nvidia,tegra20-pcie.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: NVIDIA Tegra PCIe controller + +maintainers: + - Thierry Reding + - Jon Hunter + +properties: + compatible: + oneOf: + - enum: + - nvidia,tegra20-pcie + - nvidia,tegra30-pcie + - nvidia,tegra124-pcie + - nvidia,tegra210-pcie + - nvidia,tegra186-pcie + + - items: + - const: nvidia,tegra132-pcie + - const: nvidia,tegra124-pcie + + reg: + items: + - description: PADS registers + - description: AFI registers + - description: configuration space region + + reg-names: + items: + - const: pads + - const: afi + - const: cs + + interrupts: + items: + - description: PCIe controller interrupt + - description: MSI controller interrupt + + interrupt-names: + items: + - const: intr + - const: msi + + clocks: + items: + - description: PCI controller clock + - description: AFI interface clock + - description: reference PLL clock + - description: CML clock + minItems: 3 + + clock-names: + items: + - const: pex + - const: afi + - const: pll_e + - const: cml + minItems: 3 + + resets: + items: + - description: AFI interface reset + - description: PCI controller reset + - description: PCI bus reset + + reset-names: + items: + - const: pex + - const: afi + - const: pcie_x + + interconnects: + items: + - description: AFI memory read client + - description: AFI memory write client + + interconnect-names: + items: + - const: dma-mem # read + - const: write + + iommus: + maxItems: 1 + + operating-points-v2: + $ref: /schemas/types.yaml#/definitions/phandle + + power-domains: + items: + - description: phandle to the core power domain + + pinctrl-names: + items: + # active state, puts PCIe I/O out of deep power down state + - const: default + # puts PCIe I/O into deep power down state + - const: idle + +patternProperties: + "^pci@1?[0-9a-f](,[0-7])?$": + description: Root ports are defined as subnodes of the PCIe controller + node. + + Note that devices on the PCI bus are dynamically discovered using PCI's + bus enumeration and therefore don't need corresponding device nodes in + DT. However if a device on the PCI bus provides a non-probeable bus such + as I2C or SPI, device nodes need to be added in order to allow the bus' + children to be instantiated at the proper location in the operating + system's device tree (as illustrated by the optional nodes in the + examples below). + + $ref: /schemas/pci/pci-pci-bridge.yaml# + unevaluatedProperties: false + + properties: + nvidia,num-lanes: + $ref: /schemas/types.yaml#/definitions/uint32 + description: | + Number of lanes to use for this port. Valid combinations + are: + + - Root port 0 uses 4 lanes, root port 1 is unused. + - Both root ports use 2 lanes. + enum: [ 0, 1, 2, 4 ] + + phys: + description: Must contain an phandle to a PHY for each entry in + phy-names. + minItems: 1 + maxItems: 4 + + phy-names: + description: Must include an entry for each active lane. Note that the + number of entries does not have to (though usually will) be equal to + the specified number of lanes in the nvidia,num-lanes property. + Entries are of the form "pcie-N", where N ranges from 0 to the value + specified in nvidia,num-lanes. + items: + enum: + - pcie-0 + - pcie-1 + - pcie-2 + - pcie-3 + minItems: 1 + maxItems: 4 + +required: + - reg + - reg-names + - interrupts + - interrupt-names + - clocks + - clock-names + - resets + - reset-names + +unevaluatedProperties: false + +allOf: + - $ref: /schemas/pinctrl/pinctrl-consumer.yaml# + - $ref: /schemas/pci/pci-host-bridge.yaml# + - $ref: pci-iommu.yaml# + - if: + properties: + compatible: + contains: + const: nvidia,tegra20-pcie + then: + properties: + clocks: + maxItems: 3 + + clock-names: + maxItems: 3 + + avdd-pex-supply: + description: Power supply for analog PCIe logic. Must supply 1.05 V. + + vdd-pex-supply: + description: Power supply for digital PCIe I/O. Must supply 1.05 V. + + avdd-pex-pll-supply: + description: Power supply for dedicated (internal) PCIe PLL. Must + supply 1.05 V. + + avdd-plle-supply: + description: Power supply for PLLE, which is shared with SATA. Must + supply 1.05 V. + + vddio-pex-clk-supply: + description: Power supply for PCIe clock. Must supply 3.3 V. + + required: + - avdd-pex-supply + - vdd-pex-supply + - avdd-pex-pll-supply + - avdd-plle-supply + - vddio-pex-clk-supply + + - if: + properties: + compatible: + contains: + const: nvidia,tegra30-pcie + then: + properties: + avdd-pex-pll-supply: + description: Power supply for dedicated (internal) PCIe PLL. Must + supply 1.05 V. + + avdd-plle-supply: + description: Power supply for PLLE, which is shared with SATA. Must + supply 1.05 V. + + vddio-pex-ctl-supply: + description: Power supply for PCIe control I/O partition. Must + supply 1.8 V. + + hvdd-pex-supply: + description: High-voltage supply for PCIe I/O and PCIe output + clocks. Must supply 3.3 V. + + avdd-pexa-supply: + description: Power supply for analog PCIe logic. Must supply 1.05 V. + Required if lanes 0 through 3 are used. + + vdd-pexa-supply: + description: Power supply for digital PCIe I/O. Must supply 1.05 V. + Required if lanes 0 through 3 are used. + + avdd-pexb-supply: + description: Power supply for analog PCIe logic. Must supply 1.05 V. + Required if lanes 4 and 5 are used. + + vdd-pexb-supply: + description: Power supply for digital PCIe I/O. Must supply 1.05 V. + Required if lanes 4 and 5 are used. + + required: + - avdd-pex-pll-supply + - avdd-plle-supply + - vddio-pex-ctl-supply + - hvdd-pex-supply + + - if: + properties: + compatible: + contains: + const: nvidia,tegra124-pcie + then: + properties: + phys: + minItems: 1 + maxItems: 1 + deprecated: true + + phy-names: + items: + - const: pcie + deprecated: true + + avddio-pex-supply: + description: Power supply for analog PCIe logic. Must supply 1.05 V. + + dvddio-pex-supply: + description: Power supply for digital PCIe I/O. Must supply 1.05 V. + + hvdd-pex-supply: + description: High-voltage supply for PCIe I/O and PCIe output + clocks. Must supply 3.3 V. + + vddio-pex-ctl-supply: + description: Power supply for PCIe control I/O partition. Must + supply 2.8-3.3 V. + + avdd-pex-pll-supply: + deprecated: true + + hvdd-pex-pll-e-supply: + deprecated: true + + avdd-pll-erefe-supply: + deprecated: true + + required: + - avddio-pex-supply + - dvddio-pex-supply + - hvdd-pex-supply + - vddio-pex-ctl-supply + + - if: + properties: + compatible: + contains: + const: nvidia,tegra210-pcie + then: + properties: + hvddio-pex-supply: + description: High-voltage supply for PCIe I/O and PCIe output + clocks. Must supply 1.8 V. + + dvddio-pex-supply: + description: Power supply for digital PCIe I/O. Must supply 1.05 V. + + vddio-pex-ctl-supply: + description: Power supply for PCIe control I/O partition. Must + supply 1.8 V. + + avdd-pll-uerefe-supply: + deprecated: true + + dvdd-pex-pll-supply: + deprecated: true + + hvdd-pex-pll-e-supply: + deprecated: true + + required: + - hvddio-pex-supply + - dvddio-pex-supply + - vddio-pex-ctl-supply + + - if: + properties: + compatible: + contains: + const: nvidia,tegra186-pcie + then: + properties: + clocks: + maxItems: 3 + + clock-names: + maxItems: 3 + + power-domains: + description: To ungate power partition by BPMP powergate driver. + Must contain BPMP phandle and PCIe power partition ID. + + dvdd-pex-supply: + description: Power supply for digital PCIe I/O. Must supply 1.05 V. + + hvdd-pex-pll-supply: + description: High-voltage supply for PLLE (shared with USB3). Must + supply 1.8 V. + + hvdd-pex-supply: + description: High-voltage supply for PCIe I/O and PCIe output + clocks. Must supply 1.8 V. + + vddio-pexctl-aud-supply: + description: Power supply for PCIe side band signals. Must supply + 1.8 V. + + required: + - dvdd-pex-supply + - hvdd-pex-pll-supply + - hvdd-pex-supply + - vddio-pexctl-aud-supply + +examples: + # Tegra20 + - | + #include + + pcie@80003000 { + compatible = "nvidia,tegra20-pcie"; + device_type = "pci"; + reg = <0x80003000 0x00000800>, /* PADS registers */ + <0x80003800 0x00000200>, /* AFI registers */ + <0x90000000 0x10000000>; /* configuration space */ + reg-names = "pads", "afi", "cs"; + interrupts = <0 98 0x04>, /* controller interrupt */ + <0 99 0x04>; /* MSI interrupt */ + interrupt-names = "intr", "msi"; + + #interrupt-cells = <1>; + interrupt-map-mask = <0 0 0 0>; + interrupt-map = <0 0 0 0 &intc GIC_SPI 98 IRQ_TYPE_LEVEL_HIGH>; + + bus-range = <0x00 0xff>; + #address-cells = <3>; + #size-cells = <2>; + + ranges = <0x02000000 0 0x80000000 0x80000000 0 0x00001000>, /* port 0 registers */ + <0x02000000 0 0x80001000 0x80001000 0 0x00001000>, /* port 1 registers */ + <0x01000000 0 0 0x82000000 0 0x00010000>, /* downstream I/O */ + <0x02000000 0 0xa0000000 0xa0000000 0 0x10000000>, /* non-prefetchable memory */ + <0x42000000 0 0xb0000000 0xb0000000 0 0x10000000>; /* prefetchable memory */ + + clocks = <&tegra_car 70>, <&tegra_car 72>, <&tegra_car 118>; + clock-names = "pex", "afi", "pll_e"; + resets = <&tegra_car 70>, <&tegra_car 72>, <&tegra_car 74>; + reset-names = "pex", "afi", "pcie_x"; + + avdd-pex-supply = <&pci_vdd_reg>; + vdd-pex-supply = <&pci_vdd_reg>; + avdd-pex-pll-supply = <&pci_vdd_reg>; + avdd-plle-supply = <&pci_vdd_reg>; + vddio-pex-clk-supply = <&pci_clk_reg>; + + /* root port 00:01.0 */ + pci@1,0 { + device_type = "pci"; + assigned-addresses = <0x82000800 0 0x80000000 0 0x1000>; + reg = <0x000800 0 0 0 0>; + bus-range = <0x00 0xff>; + + #address-cells = <3>; + #size-cells = <2>; + + ranges; + + nvidia,num-lanes = <2>; + + /* bridge 01:00.0 (optional) */ + pci@0,0 { + reg = <0x010000 0 0 0 0>; + bus-range = <0x00 0xff>; + + #address-cells = <3>; + #size-cells = <2>; + ranges; + + device_type = "pci"; + + /* endpoint 02:00.0 */ + ethernet@0,0 { + reg = <0x020000 0 0 0 0>; + }; + }; + }; + + pci@2,0 { + device_type = "pci"; + assigned-addresses = <0x82001000 0 0x80001000 0 0x1000>; + reg = <0x001000 0 0 0 0>; + bus-range = <0x00 0xff>; + + #address-cells = <3>; + #size-cells = <2>; + + ranges; + + nvidia,num-lanes = <2>; + }; + }; + + # Tegra210 + - | + #include + #include + + pcie@1003000 { + compatible = "nvidia,tegra210-pcie"; + device_type = "pci"; + reg = <0x01003000 0x00000800>, /* PADS registers */ + <0x01003800 0x00000800>, /* AFI registers */ + <0x02000000 0x10000000>; /* configuration space */ + reg-names = "pads", "afi", "cs"; + interrupts = , /* controller interrupt */ + ; /* MSI interrupt */ + interrupt-names = "intr", "msi"; + + #interrupt-cells = <1>; + interrupt-map-mask = <0 0 0 0>; + interrupt-map = <0 0 0 0 &gic GIC_SPI 98 IRQ_TYPE_LEVEL_HIGH>; + + bus-range = <0x00 0xff>; + #address-cells = <3>; + #size-cells = <2>; + + ranges = <0x02000000 0 0x01000000 0x01000000 0 0x00001000>, /* port 0 configuration space */ + <0x02000000 0 0x01001000 0x01001000 0 0x00001000>, /* port 1 configuration space */ + <0x01000000 0 0x0 0x12000000 0 0x00010000>, /* downstream I/O (64 KiB) */ + <0x02000000 0 0x13000000 0x13000000 0 0x0d000000>, /* non-prefetchable memory (208 MiB) */ + <0x42000000 0 0x20000000 0x20000000 0 0x20000000>; /* prefetchable memory (512 MiB) */ + + clocks = <&tegra_car TEGRA210_CLK_PCIE>, + <&tegra_car TEGRA210_CLK_AFI>, + <&tegra_car TEGRA210_CLK_PLL_E>, + <&tegra_car TEGRA210_CLK_CML0>; + clock-names = "pex", "afi", "pll_e", "cml"; + resets = <&tegra_car 70>, + <&tegra_car 72>, + <&tegra_car 74>; + reset-names = "pex", "afi", "pcie_x"; + + avdd-pll-uerefe-supply = <&avdd_1v05_pll>; + hvddio-pex-supply = <&vdd_1v8>; + dvddio-pex-supply = <&vdd_pex_1v05>; + dvdd-pex-pll-supply = <&vdd_pex_1v05>; + hvdd-pex-pll-e-supply = <&vdd_1v8>; + vddio-pex-ctl-supply = <&vdd_1v8>; + + pci@1,0 { + device_type = "pci"; + assigned-addresses = <0x82000800 0 0x01000000 0 0x1000>; + reg = <0x000800 0 0 0 0>; + bus-range = <0x00 0xff>; + + #address-cells = <3>; + #size-cells = <2>; + ranges; + + nvidia,num-lanes = <4>; + + phys = <&phy_pcie0>, <&phy_pcie1>, <&phy_pcie2>, <&phy_pcie3>; + phy-names = "pcie-0", "pcie-1", "pcie-2", "pcie-3"; + }; + + pci@2,0 { + device_type = "pci"; + assigned-addresses = <0x82001000 0 0x01001000 0 0x1000>; + reg = <0x001000 0 0 0 0>; + bus-range = <0x00 0xff>; + + #address-cells = <3>; + #size-cells = <2>; + ranges; + + nvidia,num-lanes = <1>; + + phys = <&phy_pcie4>; + phy-names = "pcie-0"; + }; + }; From 5c382ef425c875bf854d411fb0a45909878a233a Mon Sep 17 00:00:00 2001 From: Wei Huang Date: Thu, 24 Sep 2026 14:05:43 -0500 Subject: [PATCH 047/139] PCI/TPH: Validate the firmware Steering Tag response TPH _DSM returns an eight-byte Steering Tag information buffer. Checking only the ACPI object type allows a short firmware response to be read beyond the end of its buffer. For safety, require a non-NULL buffer containing the complete value before copying it. Use memcpy() so that reading the response does not depend on the alignment of the firmware buffer. Fixes: d2e8a34876ce ("PCI/TPH: Add Steering Tag support") Signed-off-by: Wei Huang Signed-off-by: Bjorn Helgaas Link: https://patch.msgid.link/20260924190543.3424792-1-wei.huang2@amd.com --- drivers/pci/tph.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/pci/tph.c b/drivers/pci/tph.c index 655ffd60e62f..4d064b86befc 100644 --- a/drivers/pci/tph.c +++ b/drivers/pci/tph.c @@ -119,12 +119,14 @@ static acpi_status tph_invoke_dsm(acpi_handle handle, u32 cpu_uid, if (!out_obj) return AE_ERROR; - if (out_obj->type != ACPI_TYPE_BUFFER) { + if (out_obj->type != ACPI_TYPE_BUFFER || + out_obj->buffer.length < sizeof(st_out->value) || + !out_obj->buffer.pointer) { ACPI_FREE(out_obj); return AE_ERROR; } - st_out->value = *((u64 *)(out_obj->buffer.pointer)); + memcpy(&st_out->value, out_obj->buffer.pointer, sizeof(st_out->value)); ACPI_FREE(out_obj); From c687d310b1cf294e9365151db07dfd49e75b40f9 Mon Sep 17 00:00:00 2001 From: Marek Vasut Date: Thu, 3 Sep 2026 22:50:52 +0200 Subject: [PATCH 048/139] dt-bindings: PCI: rcar-gen4-pci-host: Document Application/Local register reset The R-Car Gen4 PCIe controller does contain core reset, which puts the controller back into well-defined state, but it does also tear down the PCIe link. This is called Application/Local register reset in the documentation. Document this reset. Signed-off-by: Marek Vasut Signed-off-by: Manivannan Sadhasivam [bhelgaas: squash maxItems fix from https://patch.msgid.link/20260925203039.110653-1-marek.vasut+renesas@mailbox.org] Signed-off-by: Bjorn Helgaas Link: https://patch.msgid.link/20260903205153.283553-1-marek.vasut+renesas@mailbox.org --- .../devicetree/bindings/pci/rcar-gen4-pci-host.yaml | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/Documentation/devicetree/bindings/pci/rcar-gen4-pci-host.yaml b/Documentation/devicetree/bindings/pci/rcar-gen4-pci-host.yaml index 9767a52f3904..362c43a1a15d 100644 --- a/Documentation/devicetree/bindings/pci/rcar-gen4-pci-host.yaml +++ b/Documentation/devicetree/bindings/pci/rcar-gen4-pci-host.yaml @@ -47,11 +47,14 @@ properties: maxItems: 1 resets: - maxItems: 1 + minItems: 1 + maxItems: 2 reset-names: + minItems: 1 items: - const: pwr + - const: core max-link-speed: maximum: 4 @@ -141,8 +144,8 @@ examples: clocks = <&cpg CPG_MOD 624>, <&pcie0_clkref>; clock-names = "core", "ref"; power-domains = <&sysc R8A779F0_PD_ALWAYS_ON>; - resets = <&cpg 624>; - reset-names = "pwr"; + resets = <&cpg 624>,<&cpg 1121>; + reset-names = "pwr", "core"; max-link-speed = <4>; num-lanes = <2>; #address-cells = <3>; From 26a78bf6d4c62be68eed2db7d0d2431331be1bb4 Mon Sep 17 00:00:00 2001 From: Marek Vasut Date: Thu, 3 Sep 2026 22:50:53 +0200 Subject: [PATCH 049/139] dt-bindings: PCI: rcar-gen4-pci-ep: Document Application/Local register reset The R-Car Gen4 PCIe controller does contain core reset, which puts the controller back into well-defined state, but it does also tear down the PCIe link. This is called Application/Local register reset in the documentation. Document this reset. Signed-off-by: Marek Vasut Signed-off-by: Manivannan Sadhasivam [bhelgaas: squash maxItems fix from https://patch.msgid.link/20260925203039.110653-2-marek.vasut+renesas@mailbox.org] Signed-off-by: Bjorn Helgaas Link: https://patch.msgid.link/20260903205153.283553-2-marek.vasut+renesas@mailbox.org --- .../devicetree/bindings/pci/rcar-gen4-pci-ep.yaml | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/Documentation/devicetree/bindings/pci/rcar-gen4-pci-ep.yaml b/Documentation/devicetree/bindings/pci/rcar-gen4-pci-ep.yaml index b23293314a6d..ce177f0eca75 100644 --- a/Documentation/devicetree/bindings/pci/rcar-gen4-pci-ep.yaml +++ b/Documentation/devicetree/bindings/pci/rcar-gen4-pci-ep.yaml @@ -56,11 +56,14 @@ properties: maxItems: 1 resets: - maxItems: 1 + minItems: 1 + maxItems: 2 reset-names: + minItems: 1 items: - const: pwr + - const: core max-link-speed: maximum: 4 @@ -109,8 +112,8 @@ examples: clocks = <&cpg CPG_MOD 624>, <&pcie0_clkref>; clock-names = "core", "ref"; power-domains = <&sysc R8A779F0_PD_ALWAYS_ON>; - resets = <&cpg 624>; - reset-names = "pwr"; + resets = <&cpg 624>,<&cpg 1121>; + reset-names = "pwr", "core"; max-link-speed = <4>; num-lanes = <2>; max-functions = /bits/ 8 <2>; From 295657e392df0ccd19e95f3eb7db309ee2aad449 Mon Sep 17 00:00:00 2001 From: Marek Vasut Date: Thu, 3 Sep 2026 22:50:54 +0200 Subject: [PATCH 050/139] PCI: rcar-gen4: Add Application/Local register reset control The R-Car Gen4 PCIe controller does contain core reset, which puts the controller back into well-defined state, but it does also tear down the PCIe link. This is called Application/Local register reset in the documentation. Deassert the core reset after the core clock has been enabled, and assert the core reset in case of error or when shutting down the controller. This also fixes reinitialization failure of the controller, where if the controller driver is unbound and bound again via sysfs attributes, the controller driver will fail to probe the second time because it fails to access the controller DBI, which returns all zeroes unless the core reset is toggled. The following commands trigger this fault on R-Car V4H: $ echo e65d0000.pcie > /sys/bus/platform/drivers/pcie-rcar-gen4/unbind $ echo e65d0000.pcie > /sys/bus/platform/drivers/pcie-rcar-gen4/bind Fixes: faf5a975ee3b ("PCI: rcar-gen4: Add support for R-Car V4H") Signed-off-by: Marek Vasut [mani: added fixes tag and cced noautosel] Signed-off-by: Manivannan Sadhasivam Signed-off-by: Bjorn Helgaas Cc: # DTS dependency Link: https://patch.msgid.link/20260903205153.283553-3-marek.vasut+renesas@mailbox.org --- drivers/pci/controller/dwc/pcie-rcar-gen4.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/pci/controller/dwc/pcie-rcar-gen4.c b/drivers/pci/controller/dwc/pcie-rcar-gen4.c index 0b7ae4a69308..21c3dd9b8891 100644 --- a/drivers/pci/controller/dwc/pcie-rcar-gen4.c +++ b/drivers/pci/controller/dwc/pcie-rcar-gen4.c @@ -237,6 +237,8 @@ static int rcar_gen4_pcie_common_init(struct rcar_gen4_pcie *rcar) return ret; } + reset_control_deassert(dw->core_rsts[DW_PCIE_CORE_RST].rstc); + if (!reset_control_status(dw->core_rsts[DW_PCIE_PWR_RST].rstc)) { reset_control_assert(dw->core_rsts[DW_PCIE_PWR_RST].rstc); /* @@ -284,6 +286,7 @@ static int rcar_gen4_pcie_common_init(struct rcar_gen4_pcie *rcar) return 0; err_unprepare: + reset_control_assert(dw->core_rsts[DW_PCIE_CORE_RST].rstc); clk_bulk_disable_unprepare(DW_PCIE_NUM_CORE_CLKS, dw->core_clks); return ret; @@ -362,6 +365,7 @@ static void rcar_gen4_pcie_common_deinit(struct rcar_gen4_pcie *rcar) struct dw_pcie *dw = &rcar->dw; reset_control_assert(dw->core_rsts[DW_PCIE_PWR_RST].rstc); + reset_control_assert(dw->core_rsts[DW_PCIE_CORE_RST].rstc); clk_bulk_disable_unprepare(DW_PCIE_NUM_CORE_CLKS, dw->core_clks); } From 28cc5bc5a6c6cffbe7ba27c0acbfbe06747fa173 Mon Sep 17 00:00:00 2001 From: Myeonghun Pak Date: Thu, 10 Sep 2026 18:17:25 -0400 Subject: [PATCH 051/139] PCI: aardvark: Disable PHY on probe failures advk_pcie_setup_phy() initializes and powers on the PHY before the emulated bridge and IRQ domains are created. If any of those later initialization steps or pci_host_probe() fails, probe returns without powering off or exiting the PHY. Replace the direct returns with staged cleanup labels. Remove IRQ domains in reverse order, release the emulated bridge allocation only after successful initialization, and disable the PHY on every failure after advk_pcie_setup_phy() succeeds. A setup_phy() failure still returns directly, so its partial internal cleanup is not repeated. Fixes: 366697018c9a ("PCI: aardvark: Add PHY support") Co-developed-by: Ijae Kim Signed-off-by: Ijae Kim Signed-off-by: Myeonghun Pak Signed-off-by: Manivannan Sadhasivam Link: https://patch.msgid.link/20260910221725.91980-1-mhun512@gmail.com --- drivers/pci/controller/pci-aardvark.c | 31 ++++++++++++++++----------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/drivers/pci/controller/pci-aardvark.c b/drivers/pci/controller/pci-aardvark.c index ecb81ac73019..2bbdb73cfb45 100644 --- a/drivers/pci/controller/pci-aardvark.c +++ b/drivers/pci/controller/pci-aardvark.c @@ -1864,28 +1864,25 @@ static int advk_pcie_probe(struct platform_device *pdev) ret = advk_sw_pci_bridge_init(pcie); if (ret) { dev_err(dev, "Failed to register emulated root PCI bridge\n"); - return ret; + goto err_disable_phy; } ret = advk_pcie_init_irq_domain(pcie); if (ret) { dev_err(dev, "Failed to initialize irq\n"); - return ret; + goto err_cleanup_bridge; } ret = advk_pcie_init_msi_irq_domain(pcie); if (ret) { dev_err(dev, "Failed to initialize irq\n"); - advk_pcie_remove_irq_domain(pcie); - return ret; + goto err_remove_irq_domain; } ret = advk_pcie_init_rp_irq_domain(pcie); if (ret) { dev_err(dev, "Failed to initialize irq\n"); - advk_pcie_remove_msi_irq_domain(pcie); - advk_pcie_remove_irq_domain(pcie); - return ret; + goto err_remove_msi_irq_domain; } bridge->sysdata = pcie; @@ -1893,14 +1890,22 @@ static int advk_pcie_probe(struct platform_device *pdev) bridge->map_irq = advk_pcie_map_irq; ret = pci_host_probe(bridge); - if (ret < 0) { - advk_pcie_remove_rp_irq_domain(pcie); - advk_pcie_remove_msi_irq_domain(pcie); - advk_pcie_remove_irq_domain(pcie); - return ret; - } + if (ret < 0) + goto err_remove_rp_irq_domain; return 0; + +err_remove_rp_irq_domain: + advk_pcie_remove_rp_irq_domain(pcie); +err_remove_msi_irq_domain: + advk_pcie_remove_msi_irq_domain(pcie); +err_remove_irq_domain: + advk_pcie_remove_irq_domain(pcie); +err_cleanup_bridge: + pci_bridge_emul_cleanup(&pcie->bridge); +err_disable_phy: + advk_pcie_disable_phy(pcie); + return ret; } static void advk_pcie_remove(struct platform_device *pdev) From f29719b5064c07cec23c928055df302181374df5 Mon Sep 17 00:00:00 2001 From: Hans Zhang <18255117159@163.com> Date: Sat, 12 Sep 2026 00:25:36 +0800 Subject: [PATCH 052/139] PCI: dwc: Align register macros with Synopsys documentation The DWC PCIe register and field macros are inconsistently named and often do not match the Synopsys DesignWare PCIe Controller Reference Manual. This makes it harder to cross-reference the code with the documentation when debugging or maintaining the driver. As suggested during review, do not add an "_OFF" suffix to the Port Link Control register: the suffix is ambiguous (offset vs. off) and unnecessary. Use PORT_LINK_CTRL, and include the register name in its field macros, e.g. PORT_LINK_CTRL_DLE, PORT_LINK_CTRL_FLM, and PORT_LINK_CTRL_LC_*. Align other DWC PCIe controller-internal register macros in the same spirit by dropping the redundant PCIE_ prefix and using the documented base names, such as PORT_FORCE_LINK, PORT_AFAC, PORT_LANE_SKEW, TIMER_CTRL_MAX_FUNC_NUM, PORT_LINK_DEBUG0/1, LINK_WIDTH_SPEED_CTRL, GEN3_CTRL, GEN3_EQ_CTRL, MULTI_LANE_CTRL, MISC_CONTROL_1, MSIX_DOORBELL, and the ATU_* registers. Field macros are renamed consistently so their register association is clear. This improves code maintainability and eases future reference to the controller manual. No functional change intended. Co-developed-by: Feizhou Yu Signed-off-by: Feizhou Yu Signed-off-by: Hans Zhang <18255117159@163.com> [mani: fixed checkpatch warnings by moving comments] Signed-off-by: Manivannan Sadhasivam Link: https://patch.msgid.link/20260911162537.469018-2-18255117159@163.com --- drivers/pci/controller/dwc/pci-imx6.c | 26 +- drivers/pci/controller/dwc/pci-keystone.c | 4 +- .../pci/controller/dwc/pcie-designware-ep.c | 10 +- .../pci/controller/dwc/pcie-designware-host.c | 18 +- drivers/pci/controller/dwc/pcie-designware.c | 176 ++++++------- drivers/pci/controller/dwc/pcie-designware.h | 239 +++++++++--------- drivers/pci/controller/dwc/pcie-fu740.c | 6 +- drivers/pci/controller/dwc/pcie-nxp-s32g.c | 18 +- drivers/pci/controller/dwc/pcie-qcom-common.c | 28 +- drivers/pci/controller/dwc/pcie-qcom.c | 6 +- drivers/pci/controller/dwc/pcie-rcar-gen4.c | 26 +- .../pci/controller/dwc/pcie-tegra194-acpi.c | 22 +- drivers/pci/controller/dwc/pcie-tegra194.c | 86 +++---- drivers/pci/controller/dwc/pcie-ultrarisc.c | 10 +- 14 files changed, 344 insertions(+), 331 deletions(-) diff --git a/drivers/pci/controller/dwc/pci-imx6.c b/drivers/pci/controller/dwc/pci-imx6.c index 39790e66b98d..8383a71534c0 100644 --- a/drivers/pci/controller/dwc/pci-imx6.c +++ b/drivers/pci/controller/dwc/pci-imx6.c @@ -955,9 +955,9 @@ static int imx_pcie_wait_for_speed_change(struct imx_pcie *imx_pcie) unsigned int retries; for (retries = 0; retries < 200; retries++) { - tmp = dw_pcie_readl_dbi(pci, PCIE_LINK_WIDTH_SPEED_CONTROL); + tmp = dw_pcie_readl_dbi(pci, LINK_WIDTH_SPEED_CTRL); /* Test if the speed change finished. */ - if (!(tmp & PORT_LOGIC_SPEED_CHANGE)) + if (!(tmp & SPEED_CHANGE)) return 0; usleep_range(100, 1000); } @@ -1040,9 +1040,9 @@ static int imx_pcie_start_link(struct dw_pcie *pci) * Start Directed Speed Change so the best possible * speed both link partners support can be negotiated. */ - tmp = dw_pcie_readl_dbi(pci, PCIE_LINK_WIDTH_SPEED_CONTROL); - tmp |= PORT_LOGIC_SPEED_CHANGE; - dw_pcie_writel_dbi(pci, PCIE_LINK_WIDTH_SPEED_CONTROL, tmp); + tmp = dw_pcie_readl_dbi(pci, LINK_WIDTH_SPEED_CTRL); + tmp |= SPEED_CHANGE; + dw_pcie_writel_dbi(pci, LINK_WIDTH_SPEED_CTRL, tmp); dw_pcie_dbi_ro_wr_dis(pci); ret = imx_pcie_wait_for_speed_change(imx_pcie); @@ -1058,8 +1058,8 @@ static int imx_pcie_start_link(struct dw_pcie *pci) err_reset_phy: dev_dbg(dev, "PHY DEBUG_R0=0x%08x DEBUG_R1=0x%08x\n", - dw_pcie_readl_dbi(pci, PCIE_PORT_DEBUG0), - dw_pcie_readl_dbi(pci, PCIE_PORT_DEBUG1)); + dw_pcie_readl_dbi(pci, PORT_LINK_DEBUG0), + dw_pcie_readl_dbi(pci, PORT_LINK_DEBUG1)); imx_pcie_reset_phy(imx_pcie); return 0; } @@ -1498,18 +1498,18 @@ static void imx_pcie_host_post_init(struct dw_pcie_rp *pp) /* * ERR051586: Compliance with 8GT/s Receiver Impedance ECN * - * The default value of GEN3_RELATED_OFF[GEN3_ZRXDC_NONCOMPL] + * The default value of GEN3_CTRL[GEN3_ZRXDC_NONCOMPL] * is 1 which makes receiver non-compliant with the ZRX-DC * parameter for 2.5 GT/s when operating at 8 GT/s or higher. * It causes unnecessary timeout in L1. * - * Workaround: Program GEN3_RELATED_OFF[GEN3_ZRXDC_NONCOMPL] + * Workaround: Program GEN3_CTRL[GEN3_ZRXDC_NONCOMPL] * to 0. */ dw_pcie_dbi_ro_wr_en(pci); - val = dw_pcie_readl_dbi(pci, GEN3_RELATED_OFF); - val &= ~GEN3_RELATED_OFF_GEN3_ZRXDC_NONCOMPL; - dw_pcie_writel_dbi(pci, GEN3_RELATED_OFF, val); + val = dw_pcie_readl_dbi(pci, GEN3_CTRL); + val &= ~GEN3_CTRL_GEN3_ZRXDC_NONCOMPL; + dw_pcie_writel_dbi(pci, GEN3_CTRL, val); dw_pcie_dbi_ro_wr_dis(pci); } @@ -1521,7 +1521,7 @@ static void imx_pcie_host_post_init(struct dw_pcie_rp *pp) } /* - * In old DWC implementations, PCIE_ATU_INHIBIT_PAYLOAD in iATU Ctrl2 + * In old DWC implementations, ATU_INHIBIT_PAYLOAD in iATU Ctrl2 * register is reserved, so the generic DWC implementation of sending the * PME_Turn_Off message using a dummy MMIO write cannot be used. */ diff --git a/drivers/pci/controller/dwc/pci-keystone.c b/drivers/pci/controller/dwc/pci-keystone.c index 602516239a57..c0806744544e 100644 --- a/drivers/pci/controller/dwc/pci-keystone.c +++ b/drivers/pci/controller/dwc/pci-keystone.c @@ -497,8 +497,8 @@ static bool ks_pcie_link_up(struct dw_pcie *pci) { u32 val; - val = dw_pcie_readl_dbi(pci, PCIE_PORT_DEBUG0); - return (val & PORT_LOGIC_LTSSM_STATE_MASK) == PORT_LOGIC_LTSSM_STATE_L0; + val = dw_pcie_readl_dbi(pci, PORT_LINK_DEBUG0); + return (val & PORT_LINK_DEBUG0_LTSSM_MASK) == DW_PCIE_LTSSM_L0; } static void ks_pcie_stop_link(struct dw_pcie *pci) diff --git a/drivers/pci/controller/dwc/pcie-designware-ep.c b/drivers/pci/controller/dwc/pcie-designware-ep.c index de8ee3db4360..0262d85ed390 100644 --- a/drivers/pci/controller/dwc/pcie-designware-ep.c +++ b/drivers/pci/controller/dwc/pcie-designware-ep.c @@ -163,7 +163,7 @@ static void dw_pcie_ep_clear_ib_maps(struct dw_pcie_ep *ep, u8 func_no, enum pci /* Tear down the BAR Match Mode mapping, if any. */ if (ep_func->bar_to_atu[bar]) { atu_index = ep_func->bar_to_atu[bar] - 1; - dw_pcie_disable_atu(pci, PCIE_ATU_REGION_DIR_IB, atu_index); + dw_pcie_disable_atu(pci, ATU_REGION_DIR_IB, atu_index); clear_bit(atu_index, ep->ib_window_map); ep_func->bar_to_atu[bar] = 0; return; @@ -177,7 +177,7 @@ static void dw_pcie_ep_clear_ib_maps(struct dw_pcie_ep *ep, u8 func_no, enum pci if (!indexes) return; for (i = 0; i < num; i++) { - dw_pcie_disable_atu(pci, PCIE_ATU_REGION_DIR_IB, indexes[i]); + dw_pcie_disable_atu(pci, ATU_REGION_DIR_IB, indexes[i]); clear_bit(indexes[i], ep->ib_window_map); } devm_kfree(dev, indexes); @@ -647,7 +647,7 @@ static void dw_pcie_ep_unmap_addr(struct pci_epc *epc, u8 func_no, u8 vfunc_no, return; ep->outbound_addr[atu_index] = 0; - dw_pcie_disable_atu(pci, PCIE_ATU_REGION_DIR_OB, atu_index); + dw_pcie_disable_atu(pci, ATU_REGION_DIR_OB, atu_index); clear_bit(atu_index, ep->ob_window_map); } @@ -1077,10 +1077,10 @@ int dw_pcie_ep_raise_msix_irq_doorbell(struct dw_pcie_ep *ep, u8 func_no, if (!ep_func || !ep_func->msix_cap) return -EINVAL; - msg_data = (func_no << PCIE_MSIX_DOORBELL_PF_SHIFT) | + msg_data = (func_no << MSIX_DOORBELL_PF_SHIFT) | (interrupt_num - 1); - dw_pcie_writel_dbi(pci, PCIE_MSIX_DOORBELL, msg_data); + dw_pcie_writel_dbi(pci, MSIX_DOORBELL, msg_data); return 0; } diff --git a/drivers/pci/controller/dwc/pcie-designware-host.c b/drivers/pci/controller/dwc/pcie-designware-host.c index f5a38e6fd8d7..77aa79ef0ccf 100644 --- a/drivers/pci/controller/dwc/pcie-designware-host.c +++ b/drivers/pci/controller/dwc/pcie-designware-host.c @@ -440,7 +440,7 @@ static int dw_pcie_config_ecam_iatu(struct dw_pcie_rp *pp) atu.parent_bus_addr = pp->cfg0_base + SZ_1M; /* 1MiB is to cover 1 (bus) * 32 (devices) * 8 (functions) */ atu.size = SZ_1M; - atu.ctrl2 = PCIE_ATU_CFG_SHIFT_MODE_ENABLE; + atu.ctrl2 = ATU_CFG_SHIFT_MODE_ENABLE; ret = dw_pcie_prog_outbound_atu(pci, &atu); if (ret) return ret; @@ -455,7 +455,7 @@ static int dw_pcie_config_ecam_iatu(struct dw_pcie_rp *pp) atu.type = PCIE_TLP_TYPE_CFG1_RDWR; atu.parent_bus_addr = pp->cfg0_base + SZ_2M; atu.size = (SZ_1M * bus_range_max) - SZ_2M; - atu.ctrl2 = PCIE_ATU_CFG_SHIFT_MODE_ENABLE; + atu.ctrl2 = ATU_CFG_SHIFT_MODE_ENABLE; return dw_pcie_prog_outbound_atu(pci, &atu); } @@ -747,8 +747,8 @@ static void __iomem *dw_pcie_other_conf_map_bus(struct pci_bus *bus, if (!dw_pcie_link_up(pci)) return NULL; - busdev = PCIE_ATU_BUS(bus->number) | PCIE_ATU_DEV(PCI_SLOT(devfn)) | - PCIE_ATU_FUNC(PCI_FUNC(devfn)); + busdev = ATU_BUS(bus->number) | ATU_DEV(PCI_SLOT(devfn)) | + ATU_FUNC(PCI_FUNC(devfn)); if (pci_is_root_bus(bus->parent)) type = PCIE_TLP_TYPE_CFG0_RDWR; @@ -884,10 +884,10 @@ static int dw_pcie_iatu_setup(struct dw_pcie_rp *pp) * the MEM/IO (dma-)ranges setups. */ for (i = 0; i < pci->num_ob_windows; i++) - dw_pcie_disable_atu(pci, PCIE_ATU_REGION_DIR_OB, i); + dw_pcie_disable_atu(pci, ATU_REGION_DIR_OB, i); for (i = 0; i < pci->num_ib_windows; i++) - dw_pcie_disable_atu(pci, PCIE_ATU_REGION_DIR_IB, i); + dw_pcie_disable_atu(pci, ATU_REGION_DIR_IB, i); /* * NOTE: For outbound address translation, outbound iATU at index 0 is @@ -1165,9 +1165,9 @@ int dw_pcie_setup_rc(struct dw_pcie_rp *pp) /* Program correct class for RC */ dw_pcie_writew_dbi(pci, PCI_CLASS_DEVICE, PCI_CLASS_BRIDGE_PCI); - val = dw_pcie_readl_dbi(pci, PCIE_LINK_WIDTH_SPEED_CONTROL); - val |= PORT_LOGIC_SPEED_CHANGE; - dw_pcie_writel_dbi(pci, PCIE_LINK_WIDTH_SPEED_CONTROL, val); + val = dw_pcie_readl_dbi(pci, LINK_WIDTH_SPEED_CTRL); + val |= SPEED_CHANGE; + dw_pcie_writel_dbi(pci, LINK_WIDTH_SPEED_CTRL, val); dw_pcie_dbi_ro_wr_dis(pci); diff --git a/drivers/pci/controller/dwc/pcie-designware.c b/drivers/pci/controller/dwc/pcie-designware.c index 593388f29bdd..855d2a54b3c6 100644 --- a/drivers/pci/controller/dwc/pcie-designware.c +++ b/drivers/pci/controller/dwc/pcie-designware.c @@ -436,9 +436,9 @@ static inline void __iomem *dw_pcie_select_atu(struct dw_pcie *pci, u32 dir, u32 index) { if (dw_pcie_cap_is(pci, IATU_UNROLL)) - return pci->atu_base + PCIE_ATU_UNROLL_BASE(dir, index); + return pci->atu_base + ATU_UNROLL_BASE(dir, index); - dw_pcie_writel_dbi(pci, PCIE_ATU_VIEWPORT, dir | index); + dw_pcie_writel_dbi(pci, IATU_VIEWPORT, dir | index); return pci->atu_base; } @@ -480,13 +480,13 @@ static void dw_pcie_writel_atu(struct dw_pcie *pci, u32 dir, u32 index, static inline u32 dw_pcie_readl_atu_ob(struct dw_pcie *pci, u32 index, u32 reg) { - return dw_pcie_readl_atu(pci, PCIE_ATU_REGION_DIR_OB, index, reg); + return dw_pcie_readl_atu(pci, ATU_REGION_DIR_OB, index, reg); } static inline void dw_pcie_writel_atu_ob(struct dw_pcie *pci, u32 index, u32 reg, u32 val) { - dw_pcie_writel_atu(pci, PCIE_ATU_REGION_DIR_OB, index, reg, val); + dw_pcie_writel_atu(pci, ATU_REGION_DIR_OB, index, reg, val); } static inline u32 dw_pcie_enable_ecrc(u32 val) @@ -527,7 +527,7 @@ static inline u32 dw_pcie_enable_ecrc(u32 val) * handle the scenario is to program TD bit by default. */ - return val | PCIE_ATU_TD; + return val | ATU_TD; } int dw_pcie_prog_outbound_atu(struct dw_pcie *pci, @@ -548,44 +548,44 @@ int dw_pcie_prog_outbound_atu(struct dw_pcie *pci, return -EINVAL; } - dw_pcie_writel_atu_ob(pci, atu->index, PCIE_ATU_LOWER_BASE, + dw_pcie_writel_atu_ob(pci, atu->index, ATU_LOWER_BASE, lower_32_bits(parent_bus_addr)); - dw_pcie_writel_atu_ob(pci, atu->index, PCIE_ATU_UPPER_BASE, + dw_pcie_writel_atu_ob(pci, atu->index, ATU_UPPER_BASE, upper_32_bits(parent_bus_addr)); - dw_pcie_writel_atu_ob(pci, atu->index, PCIE_ATU_LIMIT, + dw_pcie_writel_atu_ob(pci, atu->index, ATU_LIMIT, lower_32_bits(limit_addr)); if (dw_pcie_ver_is_ge(pci, 460A)) - dw_pcie_writel_atu_ob(pci, atu->index, PCIE_ATU_UPPER_LIMIT, + dw_pcie_writel_atu_ob(pci, atu->index, ATU_UPPER_LIMIT, upper_32_bits(limit_addr)); - dw_pcie_writel_atu_ob(pci, atu->index, PCIE_ATU_LOWER_TARGET, + dw_pcie_writel_atu_ob(pci, atu->index, ATU_LOWER_TARGET, lower_32_bits(atu->pci_addr)); - dw_pcie_writel_atu_ob(pci, atu->index, PCIE_ATU_UPPER_TARGET, + dw_pcie_writel_atu_ob(pci, atu->index, ATU_UPPER_TARGET, upper_32_bits(atu->pci_addr)); - val = atu->type | atu->routing | PCIE_ATU_FUNC_NUM(atu->func_no); + val = atu->type | atu->routing | ATU_FUNC_NUM(atu->func_no); if (upper_32_bits(limit_addr) > upper_32_bits(parent_bus_addr) && dw_pcie_ver_is_ge(pci, 460A)) - val |= PCIE_ATU_INCREASE_REGION_SIZE; + val |= ATU_INCREASE_REGION_SIZE; if (!dw_pcie_ver_is_ge(pci, 510A)) val = dw_pcie_enable_ecrc(val); - dw_pcie_writel_atu_ob(pci, atu->index, PCIE_ATU_REGION_CTRL1, val); + dw_pcie_writel_atu_ob(pci, atu->index, ATU_REGION_CTRL1, val); - val = PCIE_ATU_ENABLE | atu->ctrl2; + val = ATU_ENABLE | atu->ctrl2; if (atu->type == PCIE_TLP_TYPE_MSG) { /* The data-less messages only for now */ - val |= PCIE_ATU_INHIBIT_PAYLOAD | atu->code; + val |= ATU_INHIBIT_PAYLOAD | atu->code; } - dw_pcie_writel_atu_ob(pci, atu->index, PCIE_ATU_REGION_CTRL2, val); + dw_pcie_writel_atu_ob(pci, atu->index, ATU_REGION_CTRL2, val); /* * Make sure ATU enable takes effect before any subsequent config * and I/O accesses. */ for (retries = 0; retries < LINK_WAIT_MAX_IATU_RETRIES; retries++) { - val = dw_pcie_readl_atu_ob(pci, atu->index, PCIE_ATU_REGION_CTRL2); - if (val & PCIE_ATU_ENABLE) + val = dw_pcie_readl_atu_ob(pci, atu->index, ATU_REGION_CTRL2); + if (val & ATU_ENABLE) return 0; mdelay(LINK_WAIT_IATU); @@ -598,13 +598,13 @@ int dw_pcie_prog_outbound_atu(struct dw_pcie *pci, static inline u32 dw_pcie_readl_atu_ib(struct dw_pcie *pci, u32 index, u32 reg) { - return dw_pcie_readl_atu(pci, PCIE_ATU_REGION_DIR_IB, index, reg); + return dw_pcie_readl_atu(pci, ATU_REGION_DIR_IB, index, reg); } static inline void dw_pcie_writel_atu_ib(struct dw_pcie *pci, u32 index, u32 reg, u32 val) { - dw_pcie_writel_atu(pci, PCIE_ATU_REGION_DIR_IB, index, reg, val); + dw_pcie_writel_atu(pci, ATU_REGION_DIR_IB, index, reg, val); } int dw_pcie_prog_inbound_atu(struct dw_pcie *pci, int index, int type, @@ -622,36 +622,36 @@ int dw_pcie_prog_inbound_atu(struct dw_pcie *pci, int index, int type, return -EINVAL; } - dw_pcie_writel_atu_ib(pci, index, PCIE_ATU_LOWER_BASE, + dw_pcie_writel_atu_ib(pci, index, ATU_LOWER_BASE, lower_32_bits(pci_addr)); - dw_pcie_writel_atu_ib(pci, index, PCIE_ATU_UPPER_BASE, + dw_pcie_writel_atu_ib(pci, index, ATU_UPPER_BASE, upper_32_bits(pci_addr)); - dw_pcie_writel_atu_ib(pci, index, PCIE_ATU_LIMIT, + dw_pcie_writel_atu_ib(pci, index, ATU_LIMIT, lower_32_bits(limit_addr)); if (dw_pcie_ver_is_ge(pci, 460A)) - dw_pcie_writel_atu_ib(pci, index, PCIE_ATU_UPPER_LIMIT, + dw_pcie_writel_atu_ib(pci, index, ATU_UPPER_LIMIT, upper_32_bits(limit_addr)); - dw_pcie_writel_atu_ib(pci, index, PCIE_ATU_LOWER_TARGET, + dw_pcie_writel_atu_ib(pci, index, ATU_LOWER_TARGET, lower_32_bits(parent_bus_addr)); - dw_pcie_writel_atu_ib(pci, index, PCIE_ATU_UPPER_TARGET, + dw_pcie_writel_atu_ib(pci, index, ATU_UPPER_TARGET, upper_32_bits(parent_bus_addr)); val = type; if (upper_32_bits(limit_addr) > upper_32_bits(pci_addr) && dw_pcie_ver_is_ge(pci, 460A)) - val |= PCIE_ATU_INCREASE_REGION_SIZE; - dw_pcie_writel_atu_ib(pci, index, PCIE_ATU_REGION_CTRL1, val); - dw_pcie_writel_atu_ib(pci, index, PCIE_ATU_REGION_CTRL2, PCIE_ATU_ENABLE); + val |= ATU_INCREASE_REGION_SIZE; + dw_pcie_writel_atu_ib(pci, index, ATU_REGION_CTRL1, val); + dw_pcie_writel_atu_ib(pci, index, ATU_REGION_CTRL2, ATU_ENABLE); /* * Make sure ATU enable takes effect before any subsequent config * and I/O accesses. */ for (retries = 0; retries < LINK_WAIT_MAX_IATU_RETRIES; retries++) { - val = dw_pcie_readl_atu_ib(pci, index, PCIE_ATU_REGION_CTRL2); - if (val & PCIE_ATU_ENABLE) + val = dw_pcie_readl_atu_ib(pci, index, ATU_REGION_CTRL2); + if (val & ATU_ENABLE) return 0; mdelay(LINK_WAIT_IATU); @@ -671,24 +671,24 @@ int dw_pcie_prog_ep_inbound_atu(struct dw_pcie *pci, u8 func_no, int index, !IS_ALIGNED(parent_bus_addr, size)) return -EINVAL; - dw_pcie_writel_atu_ib(pci, index, PCIE_ATU_LOWER_TARGET, + dw_pcie_writel_atu_ib(pci, index, ATU_LOWER_TARGET, lower_32_bits(parent_bus_addr)); - dw_pcie_writel_atu_ib(pci, index, PCIE_ATU_UPPER_TARGET, + dw_pcie_writel_atu_ib(pci, index, ATU_UPPER_TARGET, upper_32_bits(parent_bus_addr)); - dw_pcie_writel_atu_ib(pci, index, PCIE_ATU_REGION_CTRL1, type | - PCIE_ATU_FUNC_NUM(func_no)); - dw_pcie_writel_atu_ib(pci, index, PCIE_ATU_REGION_CTRL2, - PCIE_ATU_ENABLE | PCIE_ATU_FUNC_NUM_MATCH_EN | - PCIE_ATU_BAR_MODE_ENABLE | (bar << 8)); + dw_pcie_writel_atu_ib(pci, index, ATU_REGION_CTRL1, type | + ATU_FUNC_NUM(func_no)); + dw_pcie_writel_atu_ib(pci, index, ATU_REGION_CTRL2, + ATU_ENABLE | ATU_FUNC_NUM_MATCH_EN | + ATU_BAR_MODE_ENABLE | (bar << 8)); /* * Make sure ATU enable takes effect before any subsequent config * and I/O accesses. */ for (retries = 0; retries < LINK_WAIT_MAX_IATU_RETRIES; retries++) { - val = dw_pcie_readl_atu_ib(pci, index, PCIE_ATU_REGION_CTRL2); - if (val & PCIE_ATU_ENABLE) + val = dw_pcie_readl_atu_ib(pci, index, ATU_REGION_CTRL2); + if (val & ATU_ENABLE) return 0; mdelay(LINK_WAIT_IATU); @@ -701,7 +701,7 @@ int dw_pcie_prog_ep_inbound_atu(struct dw_pcie *pci, u8 func_no, int index, void dw_pcie_disable_atu(struct dw_pcie *pci, u32 dir, int index) { - dw_pcie_writel_atu(pci, dir, index, PCIE_ATU_REGION_CTRL2, 0); + dw_pcie_writel_atu(pci, dir, index, ATU_REGION_CTRL2, 0); } const char *dw_pcie_ltssm_status_string(enum dw_pcie_ltssm ltssm) @@ -824,9 +824,9 @@ bool dw_pcie_link_up(struct dw_pcie *pci) if (pci->ops && pci->ops->link_up) return pci->ops->link_up(pci); - val = dw_pcie_readl_dbi(pci, PCIE_PORT_DEBUG1); - return ((val & PCIE_PORT_DEBUG1_LINK_UP) && - (!(val & PCIE_PORT_DEBUG1_LINK_IN_TRAINING))); + val = dw_pcie_readl_dbi(pci, PORT_LINK_DEBUG1); + return ((val & PORT_LINK_DEBUG1_LINK_UP) && + (!(val & PORT_LINK_DEBUG1_LINK_IN_TRAINING))); } EXPORT_SYMBOL_GPL(dw_pcie_link_up); @@ -834,9 +834,9 @@ void dw_pcie_upconfig_setup(struct dw_pcie *pci) { u32 val; - val = dw_pcie_readl_dbi(pci, PCIE_PORT_MULTI_LANE_CTRL); - val |= PORT_MLTI_UPCFG_SUPPORT; - dw_pcie_writel_dbi(pci, PCIE_PORT_MULTI_LANE_CTRL, val); + val = dw_pcie_readl_dbi(pci, MULTI_LANE_CTRL); + val |= MULTI_LANE_CTRL_UPCFG_SUPPORT; + dw_pcie_writel_dbi(pci, MULTI_LANE_CTRL, val); } EXPORT_SYMBOL_GPL(dw_pcie_upconfig_setup); @@ -894,36 +894,36 @@ static void dw_pcie_link_set_max_link_width(struct dw_pcie *pci, u32 num_lanes) return; /* Set the number of lanes */ - plc = dw_pcie_readl_dbi(pci, PCIE_PORT_LINK_CONTROL); - plc &= ~PORT_LINK_FAST_LINK_MODE; - plc &= ~PORT_LINK_MODE_MASK; + plc = dw_pcie_readl_dbi(pci, PORT_LINK_CTRL); + plc &= ~PORT_LINK_CTRL_FLM; + plc &= ~PORT_LINK_CTRL_LC_MASK; /* Set link width speed control register */ - lwsc = dw_pcie_readl_dbi(pci, PCIE_LINK_WIDTH_SPEED_CONTROL); - lwsc &= ~PORT_LOGIC_LINK_WIDTH_MASK; - lwsc |= PORT_LOGIC_LINK_WIDTH_1_LANES; + lwsc = dw_pcie_readl_dbi(pci, LINK_WIDTH_SPEED_CTRL); + lwsc &= ~LINK_WIDTH_MASK; + lwsc |= LINK_WIDTH_1_LANES; switch (num_lanes) { case 1: - plc |= PORT_LINK_MODE_1_LANES; + plc |= PORT_LINK_CTRL_LC_1_LANES; break; case 2: - plc |= PORT_LINK_MODE_2_LANES; + plc |= PORT_LINK_CTRL_LC_2_LANES; break; case 4: - plc |= PORT_LINK_MODE_4_LANES; + plc |= PORT_LINK_CTRL_LC_4_LANES; break; case 8: - plc |= PORT_LINK_MODE_8_LANES; + plc |= PORT_LINK_CTRL_LC_8_LANES; break; case 16: - plc |= PORT_LINK_MODE_16_LANES; + plc |= PORT_LINK_CTRL_LC_16_LANES; break; default: dev_err(pci->dev, "num-lanes %u: invalid value\n", num_lanes); return; } - dw_pcie_writel_dbi(pci, PCIE_PORT_LINK_CONTROL, plc); - dw_pcie_writel_dbi(pci, PCIE_LINK_WIDTH_SPEED_CONTROL, lwsc); + dw_pcie_writel_dbi(pci, PORT_LINK_CTRL, plc); + dw_pcie_writel_dbi(pci, LINK_WIDTH_SPEED_CTRL, lwsc); cap = dw_pcie_find_capability(pci, PCI_CAP_ID_EXP); lnkcap = dw_pcie_readl_dbi(pci, cap + PCI_EXP_LNKCAP); @@ -937,48 +937,48 @@ void dw_pcie_iatu_detect(struct dw_pcie *pci) u32 val, min, dir; u64 max; - val = dw_pcie_readl_dbi(pci, PCIE_ATU_VIEWPORT); + val = dw_pcie_readl_dbi(pci, IATU_VIEWPORT); if (val == 0xFFFFFFFF) { dw_pcie_cap_set(pci, IATU_UNROLL); max_region = min((int)pci->atu_size / 512, 256); } else { - pci->atu_base = pci->dbi_base + PCIE_ATU_VIEWPORT_BASE; - pci->atu_size = PCIE_ATU_VIEWPORT_SIZE; + pci->atu_base = pci->dbi_base + ATU_VIEWPORT_BASE; + pci->atu_size = ATU_VIEWPORT_SIZE; - dw_pcie_writel_dbi(pci, PCIE_ATU_VIEWPORT, 0xFF); - max_region = dw_pcie_readl_dbi(pci, PCIE_ATU_VIEWPORT) + 1; + dw_pcie_writel_dbi(pci, IATU_VIEWPORT, 0xFF); + max_region = dw_pcie_readl_dbi(pci, IATU_VIEWPORT) + 1; } for (ob = 0; ob < max_region; ob++) { - dw_pcie_writel_atu_ob(pci, ob, PCIE_ATU_LOWER_TARGET, 0x11110000); - val = dw_pcie_readl_atu_ob(pci, ob, PCIE_ATU_LOWER_TARGET); + dw_pcie_writel_atu_ob(pci, ob, ATU_LOWER_TARGET, 0x11110000); + val = dw_pcie_readl_atu_ob(pci, ob, ATU_LOWER_TARGET); if (val != 0x11110000) break; } for (ib = 0; ib < max_region; ib++) { - dw_pcie_writel_atu_ib(pci, ib, PCIE_ATU_LOWER_TARGET, 0x11110000); - val = dw_pcie_readl_atu_ib(pci, ib, PCIE_ATU_LOWER_TARGET); + dw_pcie_writel_atu_ib(pci, ib, ATU_LOWER_TARGET, 0x11110000); + val = dw_pcie_readl_atu_ib(pci, ib, ATU_LOWER_TARGET); if (val != 0x11110000) break; } if (ob) { - dir = PCIE_ATU_REGION_DIR_OB; + dir = ATU_REGION_DIR_OB; } else if (ib) { - dir = PCIE_ATU_REGION_DIR_IB; + dir = ATU_REGION_DIR_IB; } else { dev_err(pci->dev, "No iATU regions found\n"); return; } - dw_pcie_writel_atu(pci, dir, 0, PCIE_ATU_LIMIT, 0x0); - min = dw_pcie_readl_atu(pci, dir, 0, PCIE_ATU_LIMIT); + dw_pcie_writel_atu(pci, dir, 0, ATU_LIMIT, 0x0); + min = dw_pcie_readl_atu(pci, dir, 0, ATU_LIMIT); if (dw_pcie_ver_is_ge(pci, 460A)) { - dw_pcie_writel_atu(pci, dir, 0, PCIE_ATU_UPPER_LIMIT, 0xFFFFFFFF); - max = dw_pcie_readl_atu(pci, dir, 0, PCIE_ATU_UPPER_LIMIT); + dw_pcie_writel_atu(pci, dir, 0, ATU_UPPER_LIMIT, 0xFFFFFFFF); + max = dw_pcie_readl_atu(pci, dir, 0, ATU_UPPER_LIMIT); } else { max = 0; } @@ -1273,19 +1273,19 @@ void dw_pcie_setup(struct dw_pcie *pci) /* Configure Gen1 N_FTS */ if (pci->n_fts[0]) { - val = dw_pcie_readl_dbi(pci, PCIE_PORT_AFR); - val &= ~(PORT_AFR_N_FTS_MASK | PORT_AFR_CC_N_FTS_MASK); - val |= PORT_AFR_N_FTS(pci->n_fts[0]); - val |= PORT_AFR_CC_N_FTS(pci->n_fts[0]); - dw_pcie_writel_dbi(pci, PCIE_PORT_AFR, val); + val = dw_pcie_readl_dbi(pci, PORT_AFAC); + val &= ~(PORT_AFAC_N_FTS_MASK | PORT_AFAC_CC_N_FTS_MASK); + val |= PORT_AFAC_N_FTS(pci->n_fts[0]); + val |= PORT_AFAC_CC_N_FTS(pci->n_fts[0]); + dw_pcie_writel_dbi(pci, PORT_AFAC, val); } /* Configure Gen2+ N_FTS */ if (pci->n_fts[1]) { - val = dw_pcie_readl_dbi(pci, PCIE_LINK_WIDTH_SPEED_CONTROL); - val &= ~PORT_LOGIC_N_FTS_MASK; + val = dw_pcie_readl_dbi(pci, LINK_WIDTH_SPEED_CTRL); + val &= ~N_FTS_MASK; val |= pci->n_fts[1]; - dw_pcie_writel_dbi(pci, PCIE_LINK_WIDTH_SPEED_CONTROL, val); + dw_pcie_writel_dbi(pci, LINK_WIDTH_SPEED_CTRL, val); } if (dw_pcie_cap_is(pci, CDM_CHECK)) { @@ -1295,10 +1295,10 @@ void dw_pcie_setup(struct dw_pcie *pci) dw_pcie_writel_dbi(pci, PCIE_PL_CHK_REG_CONTROL_STATUS, val); } - val = dw_pcie_readl_dbi(pci, PCIE_PORT_LINK_CONTROL); - val &= ~PORT_LINK_FAST_LINK_MODE; - val |= PORT_LINK_DLL_LINK_EN; - dw_pcie_writel_dbi(pci, PCIE_PORT_LINK_CONTROL, val); + val = dw_pcie_readl_dbi(pci, PORT_LINK_CTRL); + val &= ~PORT_LINK_CTRL_FLM; + val |= PORT_LINK_CTRL_DLE; + dw_pcie_writel_dbi(pci, PORT_LINK_CTRL, val); dw_pcie_link_set_max_link_width(pci, pci->num_lanes); } diff --git a/drivers/pci/controller/dwc/pcie-designware.h b/drivers/pci/controller/dwc/pcie-designware.h index 0735ae940924..aa79f000b160 100644 --- a/drivers/pci/controller/dwc/pcie-designware.h +++ b/drivers/pci/controller/dwc/pcie-designware.h @@ -71,38 +71,42 @@ #define LINK_WAIT_IATU 9 /* Synopsys-specific PCIe configuration registers */ -#define PCIE_PORT_FORCE 0x708 +#define PORT_FORCE_LINK 0x708 /* Port Force Link */ /* Bit[7:0] LINK_NUM: Link Number. Not used for endpoint */ -#define PORT_LINK_NUM_MASK GENMASK(7, 0) -#define PORT_FORCE_DO_DESKEW_FOR_SRIS BIT(23) +#define PORT_FORCE_LINK_NUM_MASK GENMASK(7, 0) /* Link Number. Not used for endpoint */ +#define PORT_FORCE_LINK_DDFS BIT(23) /* do deskew for sris */ -#define PCIE_PORT_AFR 0x70C -#define PORT_AFR_N_FTS_MASK GENMASK(15, 8) -#define PORT_AFR_N_FTS(n) FIELD_PREP(PORT_AFR_N_FTS_MASK, n) -#define PORT_AFR_CC_N_FTS_MASK GENMASK(23, 16) -#define PORT_AFR_CC_N_FTS(n) FIELD_PREP(PORT_AFR_CC_N_FTS_MASK, n) -#define PORT_AFR_ENTER_ASPM BIT(30) -#define PORT_AFR_L0S_ENTRANCE_LAT_SHIFT 24 -#define PORT_AFR_L0S_ENTRANCE_LAT_MASK GENMASK(26, 24) -#define PORT_AFR_L1_ENTRANCE_LAT_SHIFT 27 -#define PORT_AFR_L1_ENTRANCE_LAT_MASK GENMASK(29, 27) +#define PORT_AFAC 0x70C /* Ack Frequency and L0-L1 ASPM Control */ +/* Ack the number of Fast Training Sequence mask */ +#define PORT_AFAC_N_FTS_MASK GENMASK(15, 8) +/* Ack the number of Fast Training Sequence */ +#define PORT_AFAC_N_FTS(n) FIELD_PREP(PORT_AFAC_N_FTS_MASK, n) +#define PORT_AFAC_CC_N_FTS_MASK GENMASK(23, 16) /* Common Clock N_FTS mask */ +/* Common Clock N_FTS */ +#define PORT_AFAC_CC_N_FTS(n) FIELD_PREP(PORT_AFAC_CC_N_FTS_MASK, n) +#define PORT_AFAC_ENTER_ASPM BIT(30) /* ASPM L1 Entry Control */ +#define PORT_AFAC_L0S_ELS 24 /* L0s Entrance Latency shift */ +#define PORT_AFAC_L0S_ELM GENMASK(26, 24) /* L0s Entrance Latency mask */ +#define PORT_AFAC_L1_ELS 27 /* L1 Entrance Latency shift */ +#define PORT_AFAC_L1_ELM GENMASK(29, 27) /* L1 Entrance Latency mask */ -#define PCIE_PORT_LINK_CONTROL 0x710 -#define PORT_LINK_DLL_LINK_EN BIT(5) -#define PORT_LINK_FAST_LINK_MODE BIT(7) -#define PORT_LINK_MODE_MASK GENMASK(21, 16) -#define PORT_LINK_MODE(n) FIELD_PREP(PORT_LINK_MODE_MASK, n) -#define PORT_LINK_MODE_1_LANES PORT_LINK_MODE(0x1) -#define PORT_LINK_MODE_2_LANES PORT_LINK_MODE(0x3) -#define PORT_LINK_MODE_4_LANES PORT_LINK_MODE(0x7) -#define PORT_LINK_MODE_8_LANES PORT_LINK_MODE(0xf) -#define PORT_LINK_MODE_16_LANES PORT_LINK_MODE(0x1f) +#define PORT_LINK_CTRL 0x710 /* Port Link Control Register */ +#define PORT_LINK_CTRL_DLE BIT(5) /* DLL Link Enable */ +#define PORT_LINK_CTRL_FLM BIT(7) /* Fast Link Mode */ +#define PORT_LINK_CTRL_LC_MASK GENMASK(21, 16) /* Link Mode Enable (LINK_CAPABLE) mask */ +/* Link Mode Enable (LINK_CAPABLE) */ +#define PORT_LINK_CTRL_LC(n) FIELD_PREP(PORT_LINK_CTRL_LC_MASK, n) +#define PORT_LINK_CTRL_LC_1_LANES PORT_LINK_CTRL_LC(0x1) /* Link Mode 1 lane */ +#define PORT_LINK_CTRL_LC_2_LANES PORT_LINK_CTRL_LC(0x3) /* Link Mode 2 lane */ +#define PORT_LINK_CTRL_LC_4_LANES PORT_LINK_CTRL_LC(0x7) /* Link Mode 4 lane */ +#define PORT_LINK_CTRL_LC_8_LANES PORT_LINK_CTRL_LC(0xf) /* Link Mode 8 lane */ +#define PORT_LINK_CTRL_LC_16_LANES PORT_LINK_CTRL_LC(0x1f) /* Link Mode 16 lane */ -#define PCIE_PORT_LANE_SKEW 0x714 -#define PORT_LANE_SKEW_INSERT_MASK GENMASK(23, 0) +#define PORT_LANE_SKEW 0x714 /* Lane Skew */ +#define PORT_LANE_SKEW_INSERT_MASK GENMASK(23, 0) /* - * PCIE_TIMER_CTRL_MAX_FUNC_NUM: Timer Control and Max Function Number + * TIMER_CTRL_MAX_FUNC_NUM: Timer Control and Max Function Number * Register. * * This register holds the ack frequency, latency, replay, fast link @@ -116,68 +120,80 @@ * 0x2 (SF_64): Scaling Factor is 64 (1ms is 16us) * 0x3 (SF_16): Scaling Factor is 16 (1ms is 64us) */ -#define PCIE_TIMER_CTRL_MAX_FUNC_NUM 0x718 -#define PORT_FLT_SF_MASK GENMASK(30, 29) -#define PORT_FLT_SF_VAL_1024 0x0 -#define PORT_FLT_SF_VAL_256 0x1 -#define PORT_FLT_SF_VAL_64 0x2 -#define PORT_FLT_SF_VAL_16 0x3 +#define TIMER_CTRL_MAX_FUNC_NUM 0x718 +#define PORT_FLT_SF_MASK GENMASK(30, 29) +#define PORT_FLT_SF_VAL_1024 0x0 +#define PORT_FLT_SF_VAL_256 0x1 +#define PORT_FLT_SF_VAL_64 0x2 +#define PORT_FLT_SF_VAL_16 0x3 -#define PCIE_PORT_DEBUG0 0x728 -#define PORT_LOGIC_LTSSM_STATE_MASK 0x3f -#define PORT_LOGIC_LTSSM_STATE_L0 0x11 -#define PCIE_PORT_DEBUG1 0x72C -#define PCIE_PORT_DEBUG1_LINK_UP BIT(4) -#define PCIE_PORT_DEBUG1_LINK_IN_TRAINING BIT(29) +#define PORT_LINK_DEBUG0 0x728 /* Port Link Debug Register 0 */ +#define PORT_LINK_DEBUG0_LTSSM_MASK 0x3f /* LTSSM current state mask */ +#define PORT_LINK_DEBUG1 0x72C /* Port Link Debug Register 1 */ +/* LTSSM reports PHY link up or LTSSM is in Loopback */ +#define PORT_LINK_DEBUG1_LINK_UP BIT(4) +#define PORT_LINK_DEBUG1_LINK_IN_TRAINING BIT(29) /* LTSSM performing link training */ -#define PCIE_LINK_WIDTH_SPEED_CONTROL 0x80C -#define PORT_LOGIC_N_FTS_MASK GENMASK(7, 0) -#define PORT_LOGIC_SPEED_CHANGE BIT(17) -#define PORT_LOGIC_LINK_WIDTH_MASK GENMASK(12, 8) -#define PORT_LOGIC_LINK_WIDTH(n) FIELD_PREP(PORT_LOGIC_LINK_WIDTH_MASK, n) -#define PORT_LOGIC_LINK_WIDTH_1_LANES PORT_LOGIC_LINK_WIDTH(0x1) -#define PORT_LOGIC_LINK_WIDTH_2_LANES PORT_LOGIC_LINK_WIDTH(0x2) -#define PORT_LOGIC_LINK_WIDTH_4_LANES PORT_LOGIC_LINK_WIDTH(0x4) -#define PORT_LOGIC_LINK_WIDTH_8_LANES PORT_LOGIC_LINK_WIDTH(0x8) +#define LINK_WIDTH_SPEED_CTRL 0x80C /* Link Width and Speed Change Control */ +#define N_FTS_MASK GENMASK(7, 0) +#define SPEED_CHANGE BIT(17) +#define LINK_WIDTH_MASK GENMASK(12, 8) +#define LINK_WIDTH(n) FIELD_PREP(LINK_WIDTH_MASK, n) +#define LINK_WIDTH_1_LANES LINK_WIDTH(0x1) +#define LINK_WIDTH_2_LANES LINK_WIDTH(0x2) +#define LINK_WIDTH_4_LANES LINK_WIDTH(0x4) +#define LINK_WIDTH_8_LANES LINK_WIDTH(0x8) +/* Integrated MSI Reception Module Lower Address */ #define PCIE_MSI_ADDR_LO 0x820 +/* Integrated MSI Reception Module Upper Address */ #define PCIE_MSI_ADDR_HI 0x824 +/* Integrated MSI Reception Module Interrupt0 Enable */ #define PCIE_MSI_INTR0_ENABLE 0x828 +/* Integrated MSI Reception Module Interrupt0 Mask */ #define PCIE_MSI_INTR0_MASK 0x82C +/* Integrated MSI Reception Module Interrupt0 Status */ #define PCIE_MSI_INTR0_STATUS 0x830 -#define GEN3_RELATED_OFF 0x890 -#define GEN3_RELATED_OFF_GEN3_ZRXDC_NONCOMPL BIT(0) -#define GEN3_RELATED_OFF_EQ_PHASE_2_3 BIT(9) -#define GEN3_RELATED_OFF_RXEQ_RGRDLESS_RXTS BIT(13) -#define GEN3_RELATED_OFF_GEN3_EQ_DISABLE BIT(16) -#define GEN3_RELATED_OFF_RATE_SHADOW_SEL_SHIFT 24 -#define GEN3_RELATED_OFF_RATE_SHADOW_SEL_MASK GENMASK(25, 24) +#define GEN3_CTRL 0x890 /* Gen3 Control Register */ +/* Gen3 Receiver Impedance ZRX-DC Not Compliant */ +#define GEN3_CTRL_GEN3_ZRXDC_NONCOMPL BIT(0) +#define GEN3_CTRL_EQ_PHASE_2_3 BIT(9) /* Equalization Phase 2 and Phase 3 */ +#define GEN3_CTRL_RXEQ_RGRDLESS_RXTS BIT(13) +#define GEN3_CTRL_GEN3_EQ_DISABLE BIT(16) /* Equalization Disable */ +#define GEN3_CTRL_RATE_SHADOW_SEL_SHIFT 24 /* Rate Shadow Select shift */ +#define GEN3_CTRL_RATE_SHADOW_SEL_MASK GENMASK(25, 24) /* Rate Shadow Select mask */ -#define GEN3_EQ_CONTROL_OFF 0x8A8 -#define GEN3_EQ_CONTROL_OFF_FB_MODE GENMASK(3, 0) -#define GEN3_EQ_CONTROL_OFF_PHASE23_EXIT_MODE BIT(4) -#define GEN3_EQ_CONTROL_OFF_PSET_REQ_VEC GENMASK(23, 8) -#define GEN3_EQ_CONTROL_OFF_FOM_INC_INITIAL_EVAL BIT(24) +#define GEN3_EQ_CTRL 0x8A8 /* Gen3 EQ Control */ +#define GEN3_EQ_CTRL_FB_MODE GENMASK(3, 0) /* Feedback Mode */ +#define GEN3_EQ_CTRL_PHASE23_EXIT_MODE BIT(4) /* Behavior After 24 ms Timeout */ +#define GEN3_EQ_CTRL_PSET_REQ_VEC GENMASK(23, 8) /* Preset Request Vector */ +/* Include or not the FOM feedback from the initial preset */ +#define GEN3_EQ_CTRL_FOM_INC_INITIAL_EVAL BIT(24) -#define GEN3_EQ_FB_MODE_DIR_CHANGE_OFF 0x8AC -#define GEN3_EQ_FMDC_T_MIN_PHASE23 GENMASK(4, 0) -#define GEN3_EQ_FMDC_N_EVALS GENMASK(9, 5) -#define GEN3_EQ_FMDC_MAX_PRE_CURSOR_DELTA GENMASK(13, 10) -#define GEN3_EQ_FMDC_MAX_POST_CURSOR_DELTA GENMASK(17, 14) +/* Gen3 EQ Direction Change Feedback Mode Control */ +#define GEN3_EQ_FB_MODE_DIR_CHANGE 0x8AC +#define GEN3_EQ_FMDC_T_MIN_PHASE23 GENMASK(4, 0) +#define GEN3_EQ_FMDC_N_EVALS GENMASK(9, 5) +#define GEN3_EQ_FMDC_MAX_PRE_CURSOR_DELTA GENMASK(13, 10) +#define GEN3_EQ_FMDC_MAX_POST_CURSOR_DELTA GENMASK(17, 14) -#define COHERENCY_CONTROL_1_OFF 0x8E0 -#define CFG_MEMTYPE_BOUNDARY_LOW_ADDR_MASK GENMASK(31, 2) -#define CFG_MEMTYPE_VALUE BIT(0) +#define MISC_CONTROL_1 0x8BC /* DBI Read-Only Write Enable */ +#define DBI_RO_WR_EN BIT(0) /* Write to RO Registers Using DBI */ -#define COHERENCY_CONTROL_2_OFF 0x8E4 -#define COHERENCY_CONTROL_3_OFF 0x8E8 +#define COHERENCY_CTRL1 0x8E0 /* Cache Coherency Control Register 1 */ +/* Boundary Lower Address For Memory Type */ +#define CFG_MEMTYPE_BOUNDARY_LOW_ADDR_MASK GENMASK(31, 2) +#define CFG_MEMTYPE_VALUE BIT(0) /* Sets the memory type */ -#define PCIE_PORT_MULTI_LANE_CTRL 0x8C0 -#define PORT_MLTI_UPCFG_SUPPORT BIT(7) +#define COHERENCY_CTRL2 0x8E4 /* Cache Coherency Control Register 2 */ +#define COHERENCY_CTRL3 0x8E8 /* Cache Coherency Control Register 3 */ -#define PCIE_VERSION_NUMBER 0x8F8 -#define PCIE_VERSION_TYPE 0x8FC +#define MULTI_LANE_CTRL 0x8C0 /* UpConfigure Multi-lane Control */ +#define MULTI_LANE_CTRL_UPCFG_SUPPORT BIT(7) /* Upconfigure Support */ + +#define PCIE_VERSION_NUMBER 0x8F8 /* PCIe Controller IIP Release Version Number */ +#define PCIE_VERSION_TYPE 0x8FC /* PCIe Controller IIP Release Version Type */ /* * iATU inbound and outbound windows CSRs. Before the IP-core v4.80a each @@ -186,38 +202,35 @@ * v4.80a in a way so the viewport was unrolled into the directly accessible * iATU/eDMA CSRs space. */ -#define PCIE_ATU_VIEWPORT 0x900 -#define PCIE_ATU_REGION_DIR_IB BIT(31) -#define PCIE_ATU_REGION_DIR_OB 0 -#define PCIE_ATU_VIEWPORT_BASE 0x904 -#define PCIE_ATU_UNROLL_BASE(dir, index) \ - (((index) << 9) | ((dir == PCIE_ATU_REGION_DIR_IB) ? BIT(8) : 0)) -#define PCIE_ATU_VIEWPORT_SIZE 0x2C -#define PCIE_ATU_REGION_CTRL1 0x000 -#define PCIE_ATU_INCREASE_REGION_SIZE BIT(13) -#define PCIE_ATU_TD BIT(8) -#define PCIE_ATU_FUNC_NUM(pf) ((pf) << 20) -#define PCIE_ATU_REGION_CTRL2 0x004 -#define PCIE_ATU_ENABLE BIT(31) -#define PCIE_ATU_BAR_MODE_ENABLE BIT(30) -#define PCIE_ATU_CFG_SHIFT_MODE_ENABLE BIT(28) -#define PCIE_ATU_INHIBIT_PAYLOAD BIT(22) -#define PCIE_ATU_FUNC_NUM_MATCH_EN BIT(19) -#define PCIE_ATU_LOWER_BASE 0x008 -#define PCIE_ATU_UPPER_BASE 0x00C -#define PCIE_ATU_LIMIT 0x010 -#define PCIE_ATU_LOWER_TARGET 0x014 -#define PCIE_ATU_BUS(x) FIELD_PREP(GENMASK(31, 24), x) -#define PCIE_ATU_DEV(x) FIELD_PREP(GENMASK(23, 19), x) -#define PCIE_ATU_FUNC(x) FIELD_PREP(GENMASK(18, 16), x) -#define PCIE_ATU_UPPER_TARGET 0x018 -#define PCIE_ATU_UPPER_LIMIT 0x020 +#define IATU_VIEWPORT 0x900 /* iATU Index */ +#define ATU_REGION_DIR_IB BIT(31) +#define ATU_REGION_DIR_OB 0 +#define ATU_VIEWPORT_BASE 0x904 +#define ATU_UNROLL_BASE(dir, index) \ + (((index) << 9) | ((dir == ATU_REGION_DIR_IB) ? BIT(8) : 0)) +#define ATU_VIEWPORT_SIZE 0x2C +#define ATU_REGION_CTRL1 0x000 +#define ATU_INCREASE_REGION_SIZE BIT(13) +#define ATU_TD BIT(8) +#define ATU_FUNC_NUM(pf) ((pf) << 20) +#define ATU_REGION_CTRL2 0x004 +#define ATU_ENABLE BIT(31) +#define ATU_BAR_MODE_ENABLE BIT(30) +#define ATU_CFG_SHIFT_MODE_ENABLE BIT(28) +#define ATU_INHIBIT_PAYLOAD BIT(22) +#define ATU_FUNC_NUM_MATCH_EN BIT(19) +#define ATU_LOWER_BASE 0x008 +#define ATU_UPPER_BASE 0x00C +#define ATU_LIMIT 0x010 +#define ATU_LOWER_TARGET 0x014 +#define ATU_BUS(x) FIELD_PREP(GENMASK(31, 24), x) +#define ATU_DEV(x) FIELD_PREP(GENMASK(23, 19), x) +#define ATU_FUNC(x) FIELD_PREP(GENMASK(18, 16), x) +#define ATU_UPPER_TARGET 0x018 +#define ATU_UPPER_LIMIT 0x020 -#define PCIE_MISC_CONTROL_1_OFF 0x8BC -#define PCIE_DBI_RO_WR_EN BIT(0) - -#define PCIE_MSIX_DOORBELL 0x948 -#define PCIE_MSIX_DOORBELL_PF_SHIFT 24 +#define MSIX_DOORBELL 0x948 +#define MSIX_DOORBELL_PF_SHIFT 24 /* * eDMA CSRs. DW PCIe IP-core v4.70a and older had the eDMA registers accessible @@ -274,7 +287,7 @@ /* * RAS-DES register definitions */ -#define PCIE_RAS_DES_EVENT_COUNTER_CONTROL 0x8 +#define RAS_DES_EVENT_COUNTER_CONTROL 0x8 #define EVENT_COUNTER_ALL_CLEAR 0x3 #define EVENT_COUNTER_ENABLE_ALL 0x7 #define EVENT_COUNTER_ENABLE_SHIFT 2 @@ -288,7 +301,7 @@ #define EVENT_COUNTER_GROUP_SEL_SHIFT 24 #define EVENT_COUNTER_GROUP_5 0x5 -#define PCIE_RAS_DES_EVENT_COUNTER_DATA 0xc +#define RAS_DES_EVENT_COUNTER_DATA 0xc /* PTM register definitions */ #define PTM_RES_REQ_CTRL 0x8 @@ -376,7 +389,7 @@ enum dw_pcie_core_rst { }; enum dw_pcie_ltssm { - /* Need to align with PCIE_PORT_DEBUG0 bits 0:5 */ + /* Need to align with PORT_LINK_DEBUG0 bits 0:5 */ DW_PCIE_LTSSM_DETECT_QUIET = 0x0, DW_PCIE_LTSSM_DETECT_ACT = 0x1, DW_PCIE_LTSSM_POLL_ACTIVE = 0x2, @@ -813,9 +826,9 @@ static inline void dw_pcie_dbi_ro_wr_en(struct dw_pcie *pci) u32 reg; u32 val; - reg = PCIE_MISC_CONTROL_1_OFF; + reg = MISC_CONTROL_1; val = dw_pcie_readl_dbi(pci, reg); - val |= PCIE_DBI_RO_WR_EN; + val |= DBI_RO_WR_EN; dw_pcie_writel_dbi(pci, reg, val); } @@ -824,9 +837,9 @@ static inline void dw_pcie_dbi_ro_wr_dis(struct dw_pcie *pci) u32 reg; u32 val; - reg = PCIE_MISC_CONTROL_1_OFF; + reg = MISC_CONTROL_1; val = dw_pcie_readl_dbi(pci, reg); - val &= ~PCIE_DBI_RO_WR_EN; + val &= ~DBI_RO_WR_EN; dw_pcie_writel_dbi(pci, reg, val); } @@ -851,9 +864,9 @@ static inline enum dw_pcie_ltssm dw_pcie_get_ltssm(struct dw_pcie *pci) if (pci->ops && pci->ops->get_ltssm) return pci->ops->get_ltssm(pci); - val = dw_pcie_readl_dbi(pci, PCIE_PORT_DEBUG0); + val = dw_pcie_readl_dbi(pci, PORT_LINK_DEBUG0); - return (enum dw_pcie_ltssm)FIELD_GET(PORT_LOGIC_LTSSM_STATE_MASK, val); + return (enum dw_pcie_ltssm)FIELD_GET(PORT_LINK_DEBUG0_LTSSM_MASK, val); } const char *dw_pcie_ltssm_status_string(enum dw_pcie_ltssm ltssm); diff --git a/drivers/pci/controller/dwc/pcie-fu740.c b/drivers/pci/controller/dwc/pcie-fu740.c index d0a34f680397..6c7ce1603170 100644 --- a/drivers/pci/controller/dwc/pcie-fu740.c +++ b/drivers/pci/controller/dwc/pcie-fu740.c @@ -215,9 +215,9 @@ static int fu740_pcie_start_link(struct dw_pcie *pci) tmp |= orig; dw_pcie_writel_dbi(pci, cap_exp + PCI_EXP_LNKCAP, tmp); - tmp = dw_pcie_readl_dbi(pci, PCIE_LINK_WIDTH_SPEED_CONTROL); - tmp |= PORT_LOGIC_SPEED_CHANGE; - dw_pcie_writel_dbi(pci, PCIE_LINK_WIDTH_SPEED_CONTROL, tmp); + tmp = dw_pcie_readl_dbi(pci, LINK_WIDTH_SPEED_CTRL); + tmp |= SPEED_CHANGE; + dw_pcie_writel_dbi(pci, LINK_WIDTH_SPEED_CTRL, tmp); ret = dw_pcie_wait_for_link(pci); if (ret) { diff --git a/drivers/pci/controller/dwc/pcie-nxp-s32g.c b/drivers/pci/controller/dwc/pcie-nxp-s32g.c index 31e1169b8ab6..386e44dd0477 100644 --- a/drivers/pci/controller/dwc/pcie-nxp-s32g.c +++ b/drivers/pci/controller/dwc/pcie-nxp-s32g.c @@ -107,7 +107,7 @@ static void s32g_pcie_reset_mstr_ace(struct dw_pcie *pci) u32 ddr_base_high = upper_32_bits(S32G_MEMORY_BOUNDARY_ADDR); dw_pcie_dbi_ro_wr_en(pci); - dw_pcie_writel_dbi(pci, COHERENCY_CONTROL_3_OFF, 0x0); + dw_pcie_writel_dbi(pci, COHERENCY_CTRL3, 0x0); /* * Ncore is a cache-coherent interconnect module that enables the @@ -125,9 +125,9 @@ static void s32g_pcie_reset_mstr_ace(struct dw_pcie *pci) * Define the start of DDR as seen by Linux as this boundary between * "memory" and "peripherals", with peripherals being below. */ - dw_pcie_writel_dbi(pci, COHERENCY_CONTROL_1_OFF, + dw_pcie_writel_dbi(pci, COHERENCY_CTRL1, (ddr_base_low & CFG_MEMTYPE_BOUNDARY_LOW_ADDR_MASK)); - dw_pcie_writel_dbi(pci, COHERENCY_CONTROL_2_OFF, ddr_base_high); + dw_pcie_writel_dbi(pci, COHERENCY_CTRL2, ddr_base_high); dw_pcie_dbi_ro_wr_dis(pci); } @@ -154,13 +154,13 @@ static int s32g_init_pcie_controller(struct dw_pcie_rp *pp) dw_pcie_dbi_ro_wr_en(pci); - val = dw_pcie_readl_dbi(pci, PCIE_PORT_FORCE); - val |= PORT_FORCE_DO_DESKEW_FOR_SRIS; - dw_pcie_writel_dbi(pci, PCIE_PORT_FORCE, val); + val = dw_pcie_readl_dbi(pci, PORT_FORCE_LINK); + val |= PORT_FORCE_LINK_DDFS; + dw_pcie_writel_dbi(pci, PORT_FORCE_LINK, val); - val = dw_pcie_readl_dbi(pci, GEN3_RELATED_OFF); - val |= GEN3_RELATED_OFF_EQ_PHASE_2_3; - dw_pcie_writel_dbi(pci, GEN3_RELATED_OFF, val); + val = dw_pcie_readl_dbi(pci, GEN3_CTRL); + val |= GEN3_CTRL_EQ_PHASE_2_3; + dw_pcie_writel_dbi(pci, GEN3_CTRL, val); dw_pcie_dbi_ro_wr_dis(pci); diff --git a/drivers/pci/controller/dwc/pcie-qcom-common.c b/drivers/pci/controller/dwc/pcie-qcom-common.c index 0da73caf2011..0b39648d2214 100644 --- a/drivers/pci/controller/dwc/pcie-qcom-common.c +++ b/drivers/pci/controller/dwc/pcie-qcom-common.c @@ -15,9 +15,9 @@ void qcom_pcie_common_set_equalization(struct dw_pcie *pci) u16 speed; /* - * GEN3_RELATED_OFF register is repurposed to apply equalization + * GEN3_CTRL register is repurposed to apply equalization * settings at various data transmission rates through registers namely - * GEN3_EQ_*. The RATE_SHADOW_SEL bit field of GEN3_RELATED_OFF + * GEN3_EQ_*. The RATE_SHADOW_SEL bit field of GEN3_CTRL * determines the data rate for which these equalization settings are * applied. */ @@ -28,25 +28,25 @@ void qcom_pcie_common_set_equalization(struct dw_pcie *pci) break; } - reg = dw_pcie_readl_dbi(pci, GEN3_RELATED_OFF); - reg &= ~GEN3_RELATED_OFF_GEN3_ZRXDC_NONCOMPL; - FIELD_MODIFY(GEN3_RELATED_OFF_RATE_SHADOW_SEL_MASK, ®, + reg = dw_pcie_readl_dbi(pci, GEN3_CTRL); + reg &= ~GEN3_CTRL_GEN3_ZRXDC_NONCOMPL; + FIELD_MODIFY(GEN3_CTRL_RATE_SHADOW_SEL_MASK, ®, speed - PCIE_SPEED_8_0GT); - dw_pcie_writel_dbi(pci, GEN3_RELATED_OFF, reg); + dw_pcie_writel_dbi(pci, GEN3_CTRL, reg); - reg = dw_pcie_readl_dbi(pci, GEN3_EQ_FB_MODE_DIR_CHANGE_OFF); + reg = dw_pcie_readl_dbi(pci, GEN3_EQ_FB_MODE_DIR_CHANGE); FIELD_MODIFY(GEN3_EQ_FMDC_T_MIN_PHASE23, ®, 0x1); FIELD_MODIFY(GEN3_EQ_FMDC_N_EVALS, ®, 0xd); FIELD_MODIFY(GEN3_EQ_FMDC_MAX_PRE_CURSOR_DELTA, ®, 0x5); FIELD_MODIFY(GEN3_EQ_FMDC_MAX_POST_CURSOR_DELTA, ®, 0x5); - dw_pcie_writel_dbi(pci, GEN3_EQ_FB_MODE_DIR_CHANGE_OFF, reg); + dw_pcie_writel_dbi(pci, GEN3_EQ_FB_MODE_DIR_CHANGE, reg); - reg = dw_pcie_readl_dbi(pci, GEN3_EQ_CONTROL_OFF); - reg &= ~(GEN3_EQ_CONTROL_OFF_FB_MODE | - GEN3_EQ_CONTROL_OFF_PHASE23_EXIT_MODE | - GEN3_EQ_CONTROL_OFF_FOM_INC_INITIAL_EVAL | - GEN3_EQ_CONTROL_OFF_PSET_REQ_VEC); - dw_pcie_writel_dbi(pci, GEN3_EQ_CONTROL_OFF, reg); + reg = dw_pcie_readl_dbi(pci, GEN3_EQ_CTRL); + reg &= ~(GEN3_EQ_CTRL_FB_MODE | + GEN3_EQ_CTRL_PHASE23_EXIT_MODE | + GEN3_EQ_CTRL_FOM_INC_INITIAL_EVAL | + GEN3_EQ_CTRL_PSET_REQ_VEC); + dw_pcie_writel_dbi(pci, GEN3_EQ_CTRL, reg); } } EXPORT_SYMBOL_GPL(qcom_pcie_common_set_equalization); diff --git a/drivers/pci/controller/dwc/pcie-qcom.c b/drivers/pci/controller/dwc/pcie-qcom.c index b58a607b713f..5dd5e9cec4ad 100644 --- a/drivers/pci/controller/dwc/pcie-qcom.c +++ b/drivers/pci/controller/dwc/pcie-qcom.c @@ -1295,9 +1295,9 @@ static int qcom_pcie_post_init_2_9_0(struct qcom_pcie *pcie) writel(DEVICE_TYPE_RC, pcie->parf + PARF_DEVICE_TYPE); writel(BYPASS | MSTR_AXI_CLK_EN | AHB_CLK_EN, pcie->parf + PARF_MHI_CLOCK_RESET_CTRL); - writel(GEN3_RELATED_OFF_RXEQ_RGRDLESS_RXTS | - GEN3_RELATED_OFF_GEN3_ZRXDC_NONCOMPL, - pci->dbi_base + GEN3_RELATED_OFF); + writel(GEN3_CTRL_RXEQ_RGRDLESS_RXTS | + GEN3_CTRL_GEN3_ZRXDC_NONCOMPL, + pci->dbi_base + GEN3_CTRL); writel(MST_WAKEUP_EN | SLV_WAKEUP_EN | MSTR_ACLK_CGC_DIS | SLV_ACLK_CGC_DIS | CORE_CLK_CGC_DIS | diff --git a/drivers/pci/controller/dwc/pcie-rcar-gen4.c b/drivers/pci/controller/dwc/pcie-rcar-gen4.c index fbe465a29068..2751848e9603 100644 --- a/drivers/pci/controller/dwc/pcie-rcar-gen4.c +++ b/drivers/pci/controller/dwc/pcie-rcar-gen4.c @@ -122,17 +122,17 @@ static int rcar_gen4_pcie_speed_change(struct dw_pcie *dw) u32 val; int i; - val = dw_pcie_readl_dbi(dw, PCIE_LINK_WIDTH_SPEED_CONTROL); - val &= ~PORT_LOGIC_SPEED_CHANGE; - dw_pcie_writel_dbi(dw, PCIE_LINK_WIDTH_SPEED_CONTROL, val); + val = dw_pcie_readl_dbi(dw, LINK_WIDTH_SPEED_CTRL); + val &= ~SPEED_CHANGE; + dw_pcie_writel_dbi(dw, LINK_WIDTH_SPEED_CTRL, val); - val = dw_pcie_readl_dbi(dw, PCIE_LINK_WIDTH_SPEED_CONTROL); - val |= PORT_LOGIC_SPEED_CHANGE; - dw_pcie_writel_dbi(dw, PCIE_LINK_WIDTH_SPEED_CONTROL, val); + val = dw_pcie_readl_dbi(dw, LINK_WIDTH_SPEED_CTRL); + val |= SPEED_CHANGE; + dw_pcie_writel_dbi(dw, LINK_WIDTH_SPEED_CTRL, val); for (i = 0; i < RCAR_NUM_SPEED_CHANGE_RETRIES; i++) { - val = dw_pcie_readl_dbi(dw, PCIE_LINK_WIDTH_SPEED_CONTROL); - if (!(val & PORT_LOGIC_SPEED_CHANGE)) + val = dw_pcie_readl_dbi(dw, LINK_WIDTH_SPEED_CTRL); + if (!(val & SPEED_CHANGE)) return 0; usleep_range(10000, 11000); } @@ -688,11 +688,11 @@ static void rcar_gen4_pcie_additional_common_init(struct rcar_gen4_pcie *rcar) struct dw_pcie *dw = &rcar->dw; u32 val; - val = dw_pcie_readl_dbi(dw, PCIE_PORT_LANE_SKEW); + val = dw_pcie_readl_dbi(dw, PORT_LANE_SKEW); val &= ~PORT_LANE_SKEW_INSERT_MASK; if (dw->num_lanes < 4) val |= BIT(6); - dw_pcie_writel_dbi(dw, PCIE_PORT_LANE_SKEW, val); + dw_pcie_writel_dbi(dw, PORT_LANE_SKEW, val); val = readl(rcar->base + PCIEPWRMNGCTRL); val |= APP_CLK_REQ_N | APP_CLK_PM_EN; @@ -803,9 +803,9 @@ static int rcar_gen4_pcie_ltssm_control(struct rcar_gen4_pcie *rcar, bool enable return 0; } - val = dw_pcie_readl_dbi(dw, PCIE_PORT_FORCE); - val |= PORT_FORCE_DO_DESKEW_FOR_SRIS; - dw_pcie_writel_dbi(dw, PCIE_PORT_FORCE, val); + val = dw_pcie_readl_dbi(dw, PORT_FORCE_LINK); + val |= PORT_FORCE_LINK_DDFS; + dw_pcie_writel_dbi(dw, PORT_FORCE_LINK, val); val = readl(rcar->base + PCIEMSR0); val |= APP_SRIS_MODE; diff --git a/drivers/pci/controller/dwc/pcie-tegra194-acpi.c b/drivers/pci/controller/dwc/pcie-tegra194-acpi.c index 2d737b49ea8f..352591dcef83 100644 --- a/drivers/pci/controller/dwc/pcie-tegra194-acpi.c +++ b/drivers/pci/controller/dwc/pcie-tegra194-acpi.c @@ -39,8 +39,8 @@ static int tegra194_acpi_init(struct pci_config_window *cfg) static void atu_reg_write(struct tegra194_pcie_ecam *pcie_ecam, int index, u32 val, u32 reg) { - u32 offset = PCIE_ATU_UNROLL_BASE(PCIE_ATU_REGION_DIR_OB, index) + - PCIE_ATU_VIEWPORT_BASE; + u32 offset = ATU_UNROLL_BASE(ATU_REGION_DIR_OB, index) + + ATU_VIEWPORT_BASE; writel(val, pcie_ecam->iatu_base + offset + reg); } @@ -50,17 +50,17 @@ static void program_outbound_atu(struct tegra194_pcie_ecam *pcie_ecam, u64 pci_addr, u64 size) { atu_reg_write(pcie_ecam, index, lower_32_bits(cpu_addr), - PCIE_ATU_LOWER_BASE); + ATU_LOWER_BASE); atu_reg_write(pcie_ecam, index, upper_32_bits(cpu_addr), - PCIE_ATU_UPPER_BASE); + ATU_UPPER_BASE); atu_reg_write(pcie_ecam, index, lower_32_bits(pci_addr), - PCIE_ATU_LOWER_TARGET); + ATU_LOWER_TARGET); atu_reg_write(pcie_ecam, index, lower_32_bits(cpu_addr + size - 1), - PCIE_ATU_LIMIT); + ATU_LIMIT); atu_reg_write(pcie_ecam, index, upper_32_bits(pci_addr), - PCIE_ATU_UPPER_TARGET); - atu_reg_write(pcie_ecam, index, type, PCIE_ATU_REGION_CTRL1); - atu_reg_write(pcie_ecam, index, PCIE_ATU_ENABLE, PCIE_ATU_REGION_CTRL2); + ATU_UPPER_TARGET); + atu_reg_write(pcie_ecam, index, type, ATU_REGION_CTRL1); + atu_reg_write(pcie_ecam, index, ATU_ENABLE, ATU_REGION_CTRL2); } static void __iomem *tegra194_map_bus(struct pci_bus *bus, @@ -81,8 +81,8 @@ static void __iomem *tegra194_map_bus(struct pci_bus *bus, return NULL; } - busdev = PCIE_ATU_BUS(bus->number) | PCIE_ATU_DEV(PCI_SLOT(devfn)) | - PCIE_ATU_FUNC(PCI_FUNC(devfn)); + busdev = ATU_BUS(bus->number) | ATU_DEV(PCI_SLOT(devfn)) | + ATU_FUNC(PCI_FUNC(devfn)); if (bus->parent->number == cfg->busr.start) { if (PCI_SLOT(devfn) == 0) diff --git a/drivers/pci/controller/dwc/pcie-tegra194.c b/drivers/pci/controller/dwc/pcie-tegra194.c index 63a0f56cad8e..8c610212a180 100644 --- a/drivers/pci/controller/dwc/pcie-tegra194.c +++ b/drivers/pci/controller/dwc/pcie-tegra194.c @@ -385,9 +385,9 @@ static irqreturn_t tegra_pcie_rp_irq_handler(int irq, void *arg) val |= APPL_CAR_RESET_OVRD_CYA_OVERRIDE_CORE_RST_N; appl_writel(pcie, val, APPL_CAR_RESET_OVRD); - val = dw_pcie_readl_dbi(pci, PCIE_LINK_WIDTH_SPEED_CONTROL); - val |= PORT_LOGIC_SPEED_CHANGE; - dw_pcie_writel_dbi(pci, PCIE_LINK_WIDTH_SPEED_CONTROL, val); + val = dw_pcie_readl_dbi(pci, LINK_WIDTH_SPEED_CTRL); + val |= SPEED_CHANGE; + dw_pcie_writel_dbi(pci, LINK_WIDTH_SPEED_CTRL, val); } } @@ -621,15 +621,15 @@ static inline u32 event_counter_prog(struct tegra_pcie_dw *pcie, u32 event) u32 val; val = dw_pcie_readl_dbi(&pcie->pci, pcie->ras_des_cap + - PCIE_RAS_DES_EVENT_COUNTER_CONTROL); + RAS_DES_EVENT_COUNTER_CONTROL); val &= ~(EVENT_COUNTER_EVENT_SEL_MASK << EVENT_COUNTER_EVENT_SEL_SHIFT); val |= EVENT_COUNTER_GROUP_5 << EVENT_COUNTER_GROUP_SEL_SHIFT; val |= event << EVENT_COUNTER_EVENT_SEL_SHIFT; val |= EVENT_COUNTER_ENABLE_ALL << EVENT_COUNTER_ENABLE_SHIFT; dw_pcie_writel_dbi(&pcie->pci, pcie->ras_des_cap + - PCIE_RAS_DES_EVENT_COUNTER_CONTROL, val); + RAS_DES_EVENT_COUNTER_CONTROL, val); val = dw_pcie_readl_dbi(&pcie->pci, pcie->ras_des_cap + - PCIE_RAS_DES_EVENT_COUNTER_DATA); + RAS_DES_EVENT_COUNTER_DATA); return val; } @@ -657,14 +657,14 @@ static int aspm_state_cnt(struct seq_file *s, void *data) /* Clear all counters */ dw_pcie_writel_dbi(&pcie->pci, pcie->ras_des_cap + - PCIE_RAS_DES_EVENT_COUNTER_CONTROL, + RAS_DES_EVENT_COUNTER_CONTROL, EVENT_COUNTER_ALL_CLEAR); /* Re-enable counting */ val = EVENT_COUNTER_ENABLE_ALL << EVENT_COUNTER_ENABLE_SHIFT; val |= EVENT_COUNTER_GROUP_5 << EVENT_COUNTER_GROUP_SEL_SHIFT; dw_pcie_writel_dbi(&pcie->pci, pcie->ras_des_cap + - PCIE_RAS_DES_EVENT_COUNTER_CONTROL, val); + RAS_DES_EVENT_COUNTER_CONTROL, val); return 0; } @@ -683,7 +683,7 @@ static void init_host_aspm(struct tegra_pcie_dw *pcie) val = EVENT_COUNTER_ENABLE_ALL << EVENT_COUNTER_ENABLE_SHIFT; val |= EVENT_COUNTER_GROUP_5 << EVENT_COUNTER_GROUP_SEL_SHIFT; dw_pcie_writel_dbi(pci, pcie->ras_des_cap + - PCIE_RAS_DES_EVENT_COUNTER_CONTROL, val); + RAS_DES_EVENT_COUNTER_CONTROL, val); /* Program T_cmrt and T_pwr_on values */ val = dw_pcie_readl_dbi(pci, l1ss + PCI_L1SS_CAP); @@ -713,13 +713,13 @@ static void init_host_aspm(struct tegra_pcie_dw *pcie) } /* Program L0s and L1 entrance latencies */ - val = dw_pcie_readl_dbi(pci, PCIE_PORT_AFR); - val &= ~PORT_AFR_L0S_ENTRANCE_LAT_MASK; - val |= (pcie->aspm_l0s_enter_lat << PORT_AFR_L0S_ENTRANCE_LAT_SHIFT); - val &= ~PORT_AFR_L1_ENTRANCE_LAT_MASK; - val |= (pcie->aspm_l1_enter_lat << PORT_AFR_L1_ENTRANCE_LAT_SHIFT); - val |= PORT_AFR_ENTER_ASPM; - dw_pcie_writel_dbi(pci, PCIE_PORT_AFR, val); + val = dw_pcie_readl_dbi(pci, PORT_AFAC); + val &= ~PORT_AFAC_L0S_ELM; + val |= (pcie->aspm_l0s_enter_lat << PORT_AFAC_L0S_ELS); + val &= ~PORT_AFAC_L1_ELM; + val |= (pcie->aspm_l1_enter_lat << PORT_AFAC_L1_ELS); + val |= PORT_AFAC_ENTER_ASPM; + dw_pcie_writel_dbi(pci, PORT_AFAC, val); } static void init_debugfs(struct tegra_pcie_dw *pcie) @@ -870,29 +870,29 @@ static void config_gen3_gen4_eq_presets(struct tegra_pcie_dw *pcie) dw_pcie_writeb_dbi(pci, offset + i, val); } - val = dw_pcie_readl_dbi(pci, GEN3_RELATED_OFF); - val &= ~GEN3_RELATED_OFF_RATE_SHADOW_SEL_MASK; - dw_pcie_writel_dbi(pci, GEN3_RELATED_OFF, val); + val = dw_pcie_readl_dbi(pci, GEN3_CTRL); + val &= ~GEN3_CTRL_RATE_SHADOW_SEL_MASK; + dw_pcie_writel_dbi(pci, GEN3_CTRL, val); - val = dw_pcie_readl_dbi(pci, GEN3_EQ_CONTROL_OFF); - FIELD_MODIFY(GEN3_EQ_CONTROL_OFF_PSET_REQ_VEC, &val, 0x3ff); - val &= ~GEN3_EQ_CONTROL_OFF_FB_MODE; - dw_pcie_writel_dbi(pci, GEN3_EQ_CONTROL_OFF, val); + val = dw_pcie_readl_dbi(pci, GEN3_EQ_CTRL); + FIELD_MODIFY(GEN3_EQ_CTRL_PSET_REQ_VEC, &val, 0x3ff); + val &= ~GEN3_EQ_CTRL_FB_MODE; + dw_pcie_writel_dbi(pci, GEN3_EQ_CTRL, val); - val = dw_pcie_readl_dbi(pci, GEN3_RELATED_OFF); - val &= ~GEN3_RELATED_OFF_RATE_SHADOW_SEL_MASK; - val |= (0x1 << GEN3_RELATED_OFF_RATE_SHADOW_SEL_SHIFT); - dw_pcie_writel_dbi(pci, GEN3_RELATED_OFF, val); + val = dw_pcie_readl_dbi(pci, GEN3_CTRL); + val &= ~GEN3_CTRL_RATE_SHADOW_SEL_MASK; + val |= (0x1 << GEN3_CTRL_RATE_SHADOW_SEL_SHIFT); + dw_pcie_writel_dbi(pci, GEN3_CTRL, val); - val = dw_pcie_readl_dbi(pci, GEN3_EQ_CONTROL_OFF); - FIELD_MODIFY(GEN3_EQ_CONTROL_OFF_PSET_REQ_VEC, &val, + val = dw_pcie_readl_dbi(pci, GEN3_EQ_CTRL); + FIELD_MODIFY(GEN3_EQ_CTRL_PSET_REQ_VEC, &val, pcie->of_data->gen4_preset_vec); - val &= ~GEN3_EQ_CONTROL_OFF_FB_MODE; - dw_pcie_writel_dbi(pci, GEN3_EQ_CONTROL_OFF, val); + val &= ~GEN3_EQ_CTRL_FB_MODE; + dw_pcie_writel_dbi(pci, GEN3_EQ_CTRL, val); - val = dw_pcie_readl_dbi(pci, GEN3_RELATED_OFF); - val &= ~GEN3_RELATED_OFF_RATE_SHADOW_SEL_MASK; - dw_pcie_writel_dbi(pci, GEN3_RELATED_OFF, val); + val = dw_pcie_readl_dbi(pci, GEN3_CTRL); + val &= ~GEN3_CTRL_RATE_SHADOW_SEL_MASK; + dw_pcie_writel_dbi(pci, GEN3_CTRL, val); } static int tegra_pcie_dw_host_init(struct dw_pcie_rp *pp) @@ -940,9 +940,9 @@ static int tegra_pcie_dw_host_init(struct dw_pcie_rp *pp) init_host_aspm(pcie); if (!pcie->of_data->has_l1ss_exit_fix) { - val = dw_pcie_readl_dbi(pci, GEN3_RELATED_OFF); - val &= ~GEN3_RELATED_OFF_GEN3_ZRXDC_NONCOMPL; - dw_pcie_writel_dbi(pci, GEN3_RELATED_OFF, val); + val = dw_pcie_readl_dbi(pci, GEN3_CTRL); + val &= ~GEN3_CTRL_GEN3_ZRXDC_NONCOMPL; + dw_pcie_writel_dbi(pci, GEN3_CTRL, val); } if (pcie->update_fc_fixup) { @@ -1869,9 +1869,9 @@ static void pex_ep_event_pex_rst_deassert(struct tegra_pcie_dw *pcie) pci_epc_deinit_notify(pcie->pci.ep.epc); dw_pcie_ep_cleanup(&pcie->pci.ep); - val = dw_pcie_readl_dbi(pci, PCIE_LINK_WIDTH_SPEED_CONTROL); - val &= ~PORT_LOGIC_SPEED_CHANGE; - dw_pcie_writel_dbi(pci, PCIE_LINK_WIDTH_SPEED_CONTROL, val); + val = dw_pcie_readl_dbi(pci, LINK_WIDTH_SPEED_CTRL); + val &= ~SPEED_CHANGE; + dw_pcie_writel_dbi(pci, LINK_WIDTH_SPEED_CTRL, val); if (pcie->update_fc_fixup) { val = dw_pcie_readl_dbi(pci, CFG_TIMER_CTRL_MAX_FUNC_NUM_OFF); @@ -1884,9 +1884,9 @@ static void pex_ep_event_pex_rst_deassert(struct tegra_pcie_dw *pcie) init_host_aspm(pcie); if (!pcie->of_data->has_l1ss_exit_fix) { - val = dw_pcie_readl_dbi(pci, GEN3_RELATED_OFF); - val &= ~GEN3_RELATED_OFF_GEN3_ZRXDC_NONCOMPL; - dw_pcie_writel_dbi(pci, GEN3_RELATED_OFF, val); + val = dw_pcie_readl_dbi(pci, GEN3_CTRL); + val &= ~GEN3_CTRL_GEN3_ZRXDC_NONCOMPL; + dw_pcie_writel_dbi(pci, GEN3_CTRL, val); } pcie->pcie_cap_base = dw_pcie_find_capability(&pcie->pci, diff --git a/drivers/pci/controller/dwc/pcie-ultrarisc.c b/drivers/pci/controller/dwc/pcie-ultrarisc.c index 51e89180490d..a0d328c0c09b 100644 --- a/drivers/pci/controller/dwc/pcie-ultrarisc.c +++ b/drivers/pci/controller/dwc/pcie-ultrarisc.c @@ -52,18 +52,18 @@ static int ultrarisc_pcie_host_init(struct dw_pcie_rp *pp) val &= ~FAST_LINK_MODE; dw_pcie_writel_dbi(pci, PCIE_CUS_CORE, val); - val = dw_pcie_readl_dbi(pci, PCIE_TIMER_CTRL_MAX_FUNC_NUM); + val = dw_pcie_readl_dbi(pci, TIMER_CTRL_MAX_FUNC_NUM); FIELD_MODIFY(PORT_FLT_SF_MASK, &val, PORT_FLT_SF_VAL_64); - dw_pcie_writel_dbi(pci, PCIE_TIMER_CTRL_MAX_FUNC_NUM, val); + dw_pcie_writel_dbi(pci, TIMER_CTRL_MAX_FUNC_NUM, val); cap_exp = dw_pcie_find_capability(pci, PCI_CAP_ID_EXP); val = dw_pcie_readl_dbi(pci, cap_exp + PCI_EXP_LNKCTL2); FIELD_MODIFY(PCI_EXP_LNKCTL2_TLS, &val, PCI_EXP_LNKCTL2_TLS_16_0GT); dw_pcie_writel_dbi(pci, cap_exp + PCI_EXP_LNKCTL2, val); - val = dw_pcie_readl_dbi(pci, PCIE_PORT_FORCE); - FIELD_MODIFY(PORT_LINK_NUM_MASK, &val, 0); - dw_pcie_writel_dbi(pci, PCIE_PORT_FORCE, val); + val = dw_pcie_readl_dbi(pci, PORT_FORCE_LINK); + FIELD_MODIFY(PORT_FORCE_LINK_NUM_MASK, &val, 0); + dw_pcie_writel_dbi(pci, PORT_FORCE_LINK, val); val = dw_pcie_readl_dbi(pci, cap_exp + PCI_EXP_DEVCTL2); FIELD_MODIFY(PCI_EXP_DEVCTL2_COMP_TIMEOUT, &val, From 4996364a46e738cc455f5fa9b8b09f2f3348bcc9 Mon Sep 17 00:00:00 2001 From: Hans Zhang <18255117159@163.com> Date: Sat, 12 Sep 2026 00:25:37 +0800 Subject: [PATCH 053/139] PCI: dwc: Remove unused PCIE_ATU_UNR_* register definitions The PCIE_ATU_UNR_* macros are not used anywhere in the kernel. They were presumably intended for unrolled iATU register access, but the driver now uses the ATU_* macros and computes unrolled offsets via ATU_UNROLL_BASE(). Remove these unused definitions. No functional change intended. Signed-off-by: Hans Zhang <18255117159@163.com> Signed-off-by: Manivannan Sadhasivam Link: https://patch.msgid.link/20260911162537.469018-3-18255117159@163.com --- drivers/pci/controller/dwc/pcie-designware.h | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/drivers/pci/controller/dwc/pcie-designware.h b/drivers/pci/controller/dwc/pcie-designware.h index aa79f000b160..aea474ad803c 100644 --- a/drivers/pci/controller/dwc/pcie-designware.h +++ b/drivers/pci/controller/dwc/pcie-designware.h @@ -271,18 +271,6 @@ #define MARGINING_MAXLANES GENMASK(20, 16) #define MARGINING_SAMPLE_RATE_TIMING GENMASK(13, 8) #define MARGINING_SAMPLE_RATE_VOLTAGE GENMASK(5, 0) -/* - * iATU Unroll-specific register definitions - * From 4.80 core version the address translation will be made by unroll - */ -#define PCIE_ATU_UNR_REGION_CTRL1 0x00 -#define PCIE_ATU_UNR_REGION_CTRL2 0x04 -#define PCIE_ATU_UNR_LOWER_BASE 0x08 -#define PCIE_ATU_UNR_UPPER_BASE 0x0C -#define PCIE_ATU_UNR_LOWER_LIMIT 0x10 -#define PCIE_ATU_UNR_LOWER_TARGET 0x14 -#define PCIE_ATU_UNR_UPPER_TARGET 0x18 -#define PCIE_ATU_UNR_UPPER_LIMIT 0x20 /* * RAS-DES register definitions From ae43799529c480510dcc14bd0ed141a1d85b9259 Mon Sep 17 00:00:00 2001 From: Logan Odell Date: Thu, 10 Sep 2026 10:36:48 -0700 Subject: [PATCH 054/139] PCI/IOV: Read VF config values that need not match PF Per PCIe r7.0, sec 7.5.1.1.5 and 7.5.1.2.3, VF Revision IDs and Subsystem IDs may differ from the PF's. Read these values from VF config space instead of using cached values from VF 0. Per 7.5.1.1.6 and 7.5.1.2.3, VF Class Codes and Subsystem Vendor IDs must be the same as the PF's. Use the PF's value instead of reading from VF config space. Per 7.5.1.1.9, the Header Type for a VF must be 0, so hardcode that assignment. Fixes: cf0921bea66c ("PCI/IOV: Use VF0 cached config registers for other VFs") Signed-off-by: Logan Odell [bhelgaas: commit log, move "rev" decl inside block that uses it] Signed-off-by: Bjorn Helgaas Link: https://patch.msgid.link/20260910173648.1304517-1-loganodell@google.com --- drivers/pci/iov.c | 25 ------------------------- drivers/pci/pci.h | 4 ---- drivers/pci/probe.c | 15 ++++++++++----- 3 files changed, 10 insertions(+), 34 deletions(-) diff --git a/drivers/pci/iov.c b/drivers/pci/iov.c index 9d408fb8ac25..9185934a900f 100644 --- a/drivers/pci/iov.c +++ b/drivers/pci/iov.c @@ -179,29 +179,6 @@ bool pci_iov_is_memory_decoding_enabled(struct pci_dev *dev) return cmd & PCI_SRIOV_CTRL_MSE; } -static void pci_read_vf_config_common(struct pci_dev *virtfn) -{ - struct pci_dev *physfn = virtfn->physfn; - - /* - * Some config registers are the same across all associated VFs. - * Read them once from VF0 so we can skip reading them from the - * other VFs. - * - * PCIe r4.0, sec 9.3.4.1, technically doesn't require all VFs to - * have the same Revision ID and Subsystem ID, but we assume they - * do. - */ - pci_read_config_dword(virtfn, PCI_CLASS_REVISION, - &physfn->sriov->class); - pci_read_config_byte(virtfn, PCI_HEADER_TYPE, - &physfn->sriov->hdr_type); - pci_read_config_word(virtfn, PCI_SUBSYSTEM_VENDOR_ID, - &physfn->sriov->subsystem_vendor); - pci_read_config_word(virtfn, PCI_SUBSYSTEM_ID, - &physfn->sriov->subsystem_device); -} - int pci_iov_sysfs_link(struct pci_dev *dev, struct pci_dev *virtfn, int id) { @@ -329,8 +306,6 @@ static struct pci_dev *pci_iov_scan_device(struct pci_dev *dev, int id, virtfn->physfn = pci_dev_get(dev); virtfn->no_command_memory = 1; - if (id == 0) - pci_read_vf_config_common(virtfn); rc = pci_setup_device(virtfn); if (rc) { diff --git a/drivers/pci/pci.h b/drivers/pci/pci.h index ba3c3fddddc2..5b3dd1adbf35 100644 --- a/drivers/pci/pci.h +++ b/drivers/pci/pci.h @@ -711,10 +711,6 @@ struct pci_sriov { u16 driver_max_VFs; /* Max num VFs driver supports */ struct pci_dev *dev; /* Lowest numbered PF */ struct pci_dev *self; /* This PF */ - u32 class; /* VF device */ - u8 hdr_type; /* VF header type */ - u16 subsystem_vendor; /* VF subsystem vendor */ - u16 subsystem_device; /* VF subsystem device */ resource_size_t barsz[PCI_SRIOV_NUM_BARS]; /* VF BAR size */ u16 vf_rebar_cap; /* VF Resizable BAR capability offset */ bool drivers_autoprobe; /* Auto probing of VFs by driver */ diff --git a/drivers/pci/probe.c b/drivers/pci/probe.c index 27008e2ea5af..721daf5c5184 100644 --- a/drivers/pci/probe.c +++ b/drivers/pci/probe.c @@ -1890,8 +1890,13 @@ static u32 pci_class(struct pci_dev *dev) u32 class; #ifdef CONFIG_PCI_IOV - if (dev->is_virtfn) - return dev->physfn->sriov->class; + if (dev->is_virtfn) { + u8 rev; + + if (pci_read_config_byte(dev, PCI_REVISION_ID, &rev)) + rev = 0; + return (dev->physfn->class << 8) | rev; + } #endif pci_read_config_dword(dev, PCI_CLASS_REVISION, &class); return class; @@ -1901,8 +1906,8 @@ static void pci_subsystem_ids(struct pci_dev *dev, u16 *vendor, u16 *device) { #ifdef CONFIG_PCI_IOV if (dev->is_virtfn) { - *vendor = dev->physfn->sriov->subsystem_vendor; - *device = dev->physfn->sriov->subsystem_device; + *vendor = dev->physfn->subsystem_vendor; + pci_read_config_word(dev, PCI_SUBSYSTEM_ID, device); return; } #endif @@ -1916,7 +1921,7 @@ static u8 pci_hdr_type(struct pci_dev *dev) #ifdef CONFIG_PCI_IOV if (dev->is_virtfn) - return dev->physfn->sriov->hdr_type; + return 0; #endif pci_read_config_byte(dev, PCI_HEADER_TYPE, &hdr_type); return hdr_type; From c5e29930eef894647224c4f37ed04273a6876fbb Mon Sep 17 00:00:00 2001 From: "Mario Limonciello (AMD)" Date: Wed, 30 Sep 2026 16:39:22 -0500 Subject: [PATCH 055/139] PCI/PM: Split out code from pci_pm_suspend_noirq() into helper In order to unify suspend and hibernate code paths without code duplication the common code should be in common helpers. Move the common code from pci_pm_suspend_noirq() into a pci_pm_suspend_noirq_common() helper. No intended functional changes. Signed-off-by: Mario Limonciello (AMD) Signed-off-by: Bjorn Helgaas Tested-by: Eric Naim Reviewed-by: Rafael J. Wysocki (Intel) Link: https://patch.msgid.link/20260930213923.566846-2-superm1@kernel.org --- drivers/pci/pci-driver.c | 77 +++++++++++++++++++++++++--------------- 1 file changed, 49 insertions(+), 28 deletions(-) diff --git a/drivers/pci/pci-driver.c b/drivers/pci/pci-driver.c index e16aa59dd7ac..8334214f8c1e 100644 --- a/drivers/pci/pci-driver.c +++ b/drivers/pci/pci-driver.c @@ -818,6 +818,52 @@ static void pci_pm_complete(struct device *dev) #endif /* !CONFIG_PM_SLEEP */ +#if defined(CONFIG_SUSPEND) +/** + * pci_pm_suspend_noirq_common - prepare a device to enter a low-power state + * @pci_dev: pci device + * + * Save the device state and decide whether bus-level power management should + * skipped. Returns true if bus-level power management should be skipped, + * false otherwise. + */ +static bool pci_pm_suspend_noirq_common(struct pci_dev *pci_dev) +{ + if (!pci_dev->state_saved) { + pci_save_state(pci_dev); + + /* + * If the device is a bridge with a child in D0 below it, + * it needs to stay in D0, so check skip_bus_pm to avoid + * putting it into a low-power state in that case. + */ + if (!pci_dev->skip_bus_pm && pci_power_manageable(pci_dev)) + pci_prepare_to_sleep(pci_dev); + } + + pci_dbg(pci_dev, "PCI PM: Sleep power state: %s\n", + pci_power_name(pci_dev->current_state)); + + if (pci_dev->current_state == PCI_D0) { + pci_dev->skip_bus_pm = true; + /* + * Per PCI PM r1.2, table 6-1, a bridge must be in D0 if any + * downstream device is in D0, so avoid changing the power state + * of the parent bridge by setting the skip_bus_pm flag for it. + */ + if (pci_dev->bus->self) + pci_dev->bus->self->skip_bus_pm = true; + } + + if (pci_dev->skip_bus_pm && pm_suspend_no_platform()) { + pci_dbg(pci_dev, "PCI PM: Skipped\n"); + return true; + } + + return false; +} +#endif /* CONFIG_SUSPEND */ + #ifdef CONFIG_SUSPEND static void pcie_pme_root_status_cleanup(struct pci_dev *pci_dev) { @@ -907,6 +953,7 @@ static int pci_pm_suspend_noirq(struct device *dev) { struct pci_dev *pci_dev = to_pci_dev(dev); const struct dev_pm_ops *pm = dev->driver ? dev->driver->pm : NULL; + bool skip_bus_pm; if (dev_pm_skip_suspend(dev)) return 0; @@ -937,36 +984,10 @@ static int pci_pm_suspend_noirq(struct device *dev) } } - if (!pci_dev->state_saved) { - pci_save_state(pci_dev); + skip_bus_pm = pci_pm_suspend_noirq_common(pci_dev); - /* - * If the device is a bridge with a child in D0 below it, - * it needs to stay in D0, so check skip_bus_pm to avoid - * putting it into a low-power state in that case. - */ - if (!pci_dev->skip_bus_pm && pci_power_manageable(pci_dev)) - pci_prepare_to_sleep(pci_dev); - } - - pci_dbg(pci_dev, "PCI PM: Suspend power state: %s\n", - pci_power_name(pci_dev->current_state)); - - if (pci_dev->current_state == PCI_D0) { - pci_dev->skip_bus_pm = true; - /* - * Per PCI PM r1.2, table 6-1, a bridge must be in D0 if any - * downstream device is in D0, so avoid changing the power state - * of the parent bridge by setting the skip_bus_pm flag for it. - */ - if (pci_dev->bus->self) - pci_dev->bus->self->skip_bus_pm = true; - } - - if (pci_dev->skip_bus_pm && pm_suspend_no_platform()) { - pci_dbg(pci_dev, "PCI PM: Skipped\n"); + if (skip_bus_pm) goto Fixup; - } set_unknown: pci_pm_set_unknown_state(pci_dev); From 8f23d7dc6cd3bad251c46d406acfa4b7e9f52453 Mon Sep 17 00:00:00 2001 From: "Mario Limonciello (AMD)" Date: Wed, 30 Sep 2026 16:39:23 -0500 Subject: [PATCH 056/139] PCI: Align hibernate poweroff flow with suspend flow for bridges MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit During S3 suspend, pci_pm_suspend_noirq() puts PCIe bridges with downstream devices in a low-power state (D3hot or D3cold) when the platform allows it. The hibernate poweroff_noirq path never did this: it only called pci_prepare_to_sleep() for devices with no subordinate, so bridges with active children were left in D0. On many designs the platform firmware leaves bridges alone when the system enters S4. This prevents being able to meet various energy certification criteria for different parts of the world, particularly in designs with a dGPU. Align the hibernate flow with suspend by making pci_pm_poweroff_noirq() use pci_pm_suspend_noirq_common() instead of the open-coded pci_prepare_to_sleep() call. This reuses exactly the logic the S3 suspend path uses, including the skip_bus_pm handling that keeps a bridge in D0 when a downstream device must stay in D0 (e.g. a configured wakeup source) and the pm_suspend_no_platform() bus-PM skip. This alone is safe for the normal power-off-and-reboot path, since the hibernation image is snapshotted before poweroff_noirq runs and restoring it always goes through a full boot that re-enumerates and retrains the PCIe links. It isn't enough if hibernation_platform_enter() aborts after dpm_suspend_end(PMSG_HIBERNATE) but before power-off: that recovers via dpm_resume_start(PMSG_RESTORE) without rebooting, which calls pci_pm_restore_noirq() instead of pci_pm_resume_noirq(). Give pci_pm_restore_noirq() the same D3cold handling pci_pm_resume_noirq() already has, so a bridge coming out of D3cold gets pci_pm_bridge_power_up_actions() before its children's restore_noirq callbacks run against a possibly untrained link. Mirror the suspend_noirq guard for drivers as well: if a driver's poweroff_noirq callback already left the device in a low-power state without saving its configuration, skip pci_pm_suspend_noirq_common() and go straight to the fixups, exactly as pci_pm_suspend_noirq() does. This avoids having the core call pci_save_state() on a device the driver has already powered down, whose configuration space may no longer be readable, which could otherwise corrupt the saved state used on a poweroff abort. Because the poweroff_noirq path now mirrors the already-shipping suspend_noirq path and is guarded identically, bridges that must remain in D0 are unaffected; only bridges that S3 suspend would have powered down are now also powered down at hibernate. Signed-off-by: Mario Limonciello (AMD) Signed-off-by: Bjorn Helgaas Tested-by: Eric Naim Acked-by: Rafael J. Wysocki (Intel) Cc: AceLan Kao Cc: Kai-Heng Feng Cc: Mark Pearson Cc: Denis Benato Cc: Merthan KarakaÅŸ Link: https://patch.msgid.link/20260930213923.566846-3-superm1@kernel.org --- drivers/pci/pci-driver.c | 27 +++++++++++++++++++++++---- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/drivers/pci/pci-driver.c b/drivers/pci/pci-driver.c index 8334214f8c1e..7efe808cdf0c 100644 --- a/drivers/pci/pci-driver.c +++ b/drivers/pci/pci-driver.c @@ -818,7 +818,7 @@ static void pci_pm_complete(struct device *dev) #endif /* !CONFIG_PM_SLEEP */ -#if defined(CONFIG_SUSPEND) +#if defined(CONFIG_SUSPEND) || defined(CONFIG_HIBERNATE_CALLBACKS) /** * pci_pm_suspend_noirq_common - prepare a device to enter a low-power state * @pci_dev: pci device @@ -862,7 +862,7 @@ static bool pci_pm_suspend_noirq_common(struct pci_dev *pci_dev) return false; } -#endif /* CONFIG_SUSPEND */ +#endif /* CONFIG_SUSPEND || CONFIG_HIBERNATE_CALLBACKS */ #ifdef CONFIG_SUSPEND static void pcie_pme_root_status_cleanup(struct pci_dev *pci_dev) @@ -1217,6 +1217,8 @@ static int pci_pm_poweroff(struct device *dev) struct pci_dev *pci_dev = to_pci_dev(dev); const struct dev_pm_ops *pm = dev->driver ? dev->driver->pm : NULL; + pci_dev->skip_bus_pm = false; + if (pci_has_legacy_pm_support(pci_dev)) return pci_legacy_suspend(dev, PMSG_HIBERNATE); @@ -1259,6 +1261,7 @@ static int pci_pm_poweroff_noirq(struct device *dev) { struct pci_dev *pci_dev = to_pci_dev(dev); const struct dev_pm_ops *pm = dev->driver ? dev->driver->pm : NULL; + bool skip_bus_pm; if (dev_pm_skip_suspend(dev)) return 0; @@ -1272,16 +1275,26 @@ static int pci_pm_poweroff_noirq(struct device *dev) } if (pm->poweroff_noirq) { + pci_power_t prev = pci_dev->current_state; int error; error = pm->poweroff_noirq(dev); suspend_report_result(dev, pm->poweroff_noirq, error); if (error) return error; + + if (!pci_dev->state_saved && pci_dev->current_state != PCI_D0 + && pci_dev->current_state != PCI_UNKNOWN) { + pci_WARN_ONCE(pci_dev, pci_dev->current_state != prev, + "PCI PM: State of device not saved by %pS\n", + pm->poweroff_noirq); + goto Fixup; + } } - if (!pci_dev->state_saved && !pci_has_subordinate(pci_dev)) - pci_prepare_to_sleep(pci_dev); + skip_bus_pm = pci_pm_suspend_noirq_common(pci_dev); + if (skip_bus_pm) + goto Fixup; /* * The reason for doing this here is the same as for the analogous code @@ -1290,6 +1303,7 @@ static int pci_pm_poweroff_noirq(struct device *dev) if (pci_dev->class == PCI_CLASS_SERIAL_USB_EHCI) pci_write_config_word(pci_dev, PCI_COMMAND, 0); +Fixup: pci_fixup_device(pci_fixup_suspend_late, pci_dev); return 0; @@ -1299,10 +1313,15 @@ static int pci_pm_restore_noirq(struct device *dev) { struct pci_dev *pci_dev = to_pci_dev(dev); const struct dev_pm_ops *pm = dev->driver ? dev->driver->pm : NULL; + pci_power_t prev_state = pci_dev->current_state; + bool skip_bus_pm = pci_dev->skip_bus_pm; pci_pm_default_resume_early(pci_dev); pci_fixup_device(pci_fixup_resume_early, pci_dev); + if (!skip_bus_pm && prev_state == PCI_D3cold) + pci_pm_bridge_power_up_actions(pci_dev); + if (pci_has_legacy_pm_support(pci_dev)) return 0; From 42b5b17cf4bb6154028b31f12db94ecb9069fd74 Mon Sep 17 00:00:00 2001 From: Bjorn Helgaas Date: Wed, 23 Sep 2026 14:57:20 -0500 Subject: [PATCH 057/139] PCI: tegra264: Fix Link Capabilities register offset The PCI Express Capability begins at 0x48. Link Capabilities is a 32-bit register at offset 0xc, and Link Status is a 16-bit register at offset 0x12: Link Capabilities is at 0x48 + 0xc = 0x54 Link Status is at 0x48 + 0x12 = 0x5a Previously the driver read Link Capabilities with a 16-bit read from XTL_RC_PCIE_CFG_LINK_CAPS (0x56), which incorrectly read just the upper half of the register. When a hotplug-capable port has no link during probe, tegra264_pcie_icc_set() consequently derives the maximum speed and width from unrelated bits and requests the wrong interconnect bandwidth. Correct the Link Capabilities usage by adding a XTL_RC_PCIE_CAP definition for the base of the PCIe Capability, using the existing PCI_EXP_LNKCAP (0xc) and PCI_EXP_LNKSTA (0x12) offsets so they're easily searchable, and reading the entire 32 bits of Link Capabilities. Fixes: 01c3c27a0ef6 ("PCI: tegra264: Add Tegra264 support") Based-on-patch-by: Linmao Li Link: https://lore.kernel.org/20260827093919.2825467-1-lilinmao@kylinos.cn Signed-off-by: Bjorn Helgaas Tested-by: Thierry Reding Reviewed-by: Thierry Reding Link: https://patch.msgid.link/20260923195719.1933175-2-bhelgaas@google.com --- drivers/pci/controller/pcie-tegra264.c | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/drivers/pci/controller/pcie-tegra264.c b/drivers/pci/controller/pcie-tegra264.c index 653136db401e..c97576ab4a31 100644 --- a/drivers/pci/controller/pcie-tegra264.c +++ b/drivers/pci/controller/pcie-tegra264.c @@ -49,8 +49,7 @@ #define XAL_RC_BAR_CNTL_STANDARD_64B_BAR_EN BIT(2) /* XTL registers */ -#define XTL_RC_PCIE_CFG_LINK_CAPS 0x56 -#define XTL_RC_PCIE_CFG_LINK_STATUS 0x5a +#define XTL_RC_PCIE_CAP 0x48 /* PCIe Capability */ #define XTL_RC_MGMT_PERST_CONTROL 0x218 #define XTL_RC_MGMT_PERST_CONTROL_PERST_O_N BIT(0) @@ -118,11 +117,11 @@ static void tegra264_pcie_icc_set(struct tegra264_pcie *pcie) * possible, so this is as good as it gets for now. */ if (pcie->link_up) { - value = readw(pcie->ecam + XTL_RC_PCIE_CFG_LINK_STATUS); + value = readw(pcie->ecam + XTL_RC_PCIE_CAP + PCI_EXP_LNKSTA); speed = FIELD_GET(PCI_EXP_LNKSTA_CLS, value); width = FIELD_GET(PCI_EXP_LNKSTA_NLW, value); } else { - value = readw(pcie->ecam + XTL_RC_PCIE_CFG_LINK_CAPS); + value = readl(pcie->ecam + XTL_RC_PCIE_CAP + PCI_EXP_LNKCAP); speed = FIELD_GET(PCI_EXP_LNKCAP_SLS, value); width = FIELD_GET(PCI_EXP_LNKCAP_MLW, value); } @@ -263,7 +262,7 @@ static bool tegra264_pcie_supports_hotplug(struct tegra264_pcie *pcie) static bool tegra264_pcie_link_up(struct tegra264_pcie *pcie, enum pci_bus_speed *speed) { - u16 value = readw(pcie->ecam + XTL_RC_PCIE_CFG_LINK_STATUS); + u16 value = readw(pcie->ecam + XTL_RC_PCIE_CAP + PCI_EXP_LNKSTA); if (value & PCI_EXP_LNKSTA_DLLLA) { if (speed) From 5f30217ec296fb5742f6fd306586dbca5b81b4eb Mon Sep 17 00:00:00 2001 From: Imre Kaloz Date: Wed, 30 Sep 2026 21:10:58 +0200 Subject: [PATCH 058/139] PCI: Disable MSI for ULi M1575 EHCI controller With MSI, the ULi M1575 EHCI controller (10b9:5239) intermittently stops executing its asynchronous schedule. Control transfers time out ("device not accepting address", "device descriptor read/64"), the port is handed to the companion OHCI controller, and the device ends up at full speed. On a configured hub the stall shows up as hub descriptor and status read failures. Only re-initializing the controller recovers it. On a Sun Ultra 45, 65 of 800 controller initializations stalled with MSI and none of 200 with INTx. Each initialization is an unbind and bind of ehci-pci on the EHCI function, with MSI or INTx selected through its msi_bus attribute; with this quirk, 0 of 100 cycles of the same loop stalled. The stall reproduces on a kernel that already contains commit 0b60557230ad ("usb: ehci: Prevent missed ehci interrupts with edge-triggered MSI"). The OHCI functions (10b9:5237) keep MSI. They showed no stall, and the hub enumerated through them on MSI during the EHCI failures, so only the EHCI function is quirked. The illumos EHCI driver also disables MSI on this chip, in ehci_register_intrs_and_init_mutex() in usr/src/uts/common/io/usb/hcd/ehci/ehci_util.c. Until USB HCDs started trying MSI first in v5.9, the controller always used INTx. Disable MSI so it uses INTx again on this chipset. Fixes: 306c54d0edb6 ("usb: hcd: Try MSI interrupts on PCI devices") Signed-off-by: Imre Kaloz Signed-off-by: Bjorn Helgaas Reviewed-by: Andy Shevchenko Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20260930191100.6127-1-kaloz@kernel.org --- drivers/pci/quirks.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/pci/quirks.c b/drivers/pci/quirks.c index 7cbdbdd5ddc8..010822baee34 100644 --- a/drivers/pci/quirks.c +++ b/drivers/pci/quirks.c @@ -1945,6 +1945,8 @@ DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_ATI, 0x4388, quirk_no_msi); DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_ATI, 0x4389, quirk_no_msi); DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_ATI, 0x438a, quirk_no_msi); DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_ATI, 0x438b, quirk_no_msi); +/* ULi M1575 EHCI (10b9:5239): MSI stalls the async schedule */ +DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_AL, 0x5239, quirk_no_msi); static void quirk_pcie_mch(struct pci_dev *pdev) { From 0b9df403ddfa6949ecd05bb7b218a5e685a5d016 Mon Sep 17 00:00:00 2001 From: Scott Lee Date: Mon, 5 Oct 2026 21:26:51 +0800 Subject: [PATCH 059/139] PCI/P2PDMA: Add Intel Haswell client host bridge to allowlist P2P DMA between two devices that do not share an upstream bridge is only permitted when the traffic goes through a host bridge that is listed in pci_p2pdma_whitelist[] with REQ_SAME_HOST_BRIDGE. The Haswell client host bridge (8086:0c08, e.g. H97/H87/B85) is not in that list, and cpu_supports_p2pdma() returns false on Intel, so calc_map_type_and_dist() refuses P2P for any pair of devices behind different root ports of the same host bridge, even though that root complex handles the traffic correctly. Measured on an ASUS H97-PRO with a Xeon E3-1231 v3 (Haswell, 8086:0c08 at 00:00.0) and two GPUs on separate root ports of that host bridge (00:01.0 and 00:1c.4): - pci_p2pdma_distance() < 0 before the change; peer access granted after (amdgpu stops reporting "PCIe P2P access ... is not supported by the chipset" and hipDeviceCanAccessPeer() returns true in both directions) - 10 KB cross-device copy: 62-75 us vs 130-131 us host-staged (~2x) - 512 MB cross-device copy verified byte-wise against a known buffer: no corruption - tensor-parallel LLM inference (>25 GB model across both GPUs): +53% tokens/s REQ_SAME_HOST_BRIDGE is the correct flag here: the two devices share the upstream bridge (two root ports of one host bridge) rather than sitting behind a PCIe switch. This entry only permits P2P DMA to be used on this platform, it does not force it: the existing checks on ACS redirect, on the map type and on pci_p2pdma_distance() all still apply. Tested on one board only, so the usual caveat applies - broad testing on other Haswell boards would be needed before this can be considered generally safe. Signed-off-by: Scott Lee Signed-off-by: Bjorn Helgaas Link: https://patch.msgid.link/20261005132651.18368-1-dsix123@gmail.com --- drivers/pci/p2pdma.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/pci/p2pdma.c b/drivers/pci/p2pdma.c index 772640a08bf9..0b22ca48b0b4 100644 --- a/drivers/pci/p2pdma.c +++ b/drivers/pci/p2pdma.c @@ -545,6 +545,8 @@ static const struct pci_p2pdma_whitelist_entry { /* Intel Xeon E7 v3/Xeon E5 v3/Core i7 */ {PCI_VENDOR_ID_INTEL, 0x2f00, REQ_SAME_HOST_BRIDGE}, {PCI_VENDOR_ID_INTEL, 0x2f01, REQ_SAME_HOST_BRIDGE}, + /* Intel Haswell (client) */ + {PCI_VENDOR_ID_INTEL, 0x0c08, REQ_SAME_HOST_BRIDGE}, /* Intel Skylake-E */ {PCI_VENDOR_ID_INTEL, 0x2030, 0}, {PCI_VENDOR_ID_INTEL, 0x2031, 0}, From 39e0f013dbe2a3267fda037cd84f8ae344282803 Mon Sep 17 00:00:00 2001 From: Jose Ignacio Tornos Martinez Date: Mon, 5 Oct 2026 12:54:35 +0200 Subject: [PATCH 060/139] PCI: Add device-specific reset for Qualcomm SDX62/SDX65 modems Qualcomm SDX62/SDX65 5G modems (17cb:0308) lack working reset methods for VFIO passthrough scenarios. These devices have no FLR capability, advertise NoSoftRst+ (blocking PM reset), and have broken bus reset. Standard reset methods were already disabled for these devices by commit 6a4f64c3a3ad ("PCI: Avoid SBR for Qualcomm WCN6855/WCN7850 WiFi, SDX62/SDX65 modems") because they were not working (quirk_no_bus_reset). VFIO attempts to reset devices on every reassignment. Without a proper reset capability, these devices never successfully initialize even on first VM assignment. Add a device-specific reset method using BAR-space hardware reset registers that exist in these devices. SDX62/SDX65 modem devices use MHI SoC reset via BAR0 (sequence from MHI driver: mhi_soc_reset(), mhi_pci_reset_prepare()): - Write reset request to offset 0xb0 - Wait 2 seconds for reset completion These are true hardware reset mechanisms (not power management or firmware error recovery), providing proper device reset for VFIO scenarios, allowing to enforce isolation between successive passthrough users. Testing shows stable operation over 109 VM crash/reset cycles. Device- specific reset is position #1 in the reset hierarchy, so these Qualcomm devices will use hardware reset as their primary reset method. Signed-off-by: Jose Ignacio Tornos Martinez Signed-off-by: Bjorn Helgaas Reviewed-by: Manivannan Sadhasivam Acked-by: Alex Williamson Link: https://patch.msgid.link/20261005105436.83962-2-jtornosm@redhat.com --- drivers/pci/quirks.c | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/drivers/pci/quirks.c b/drivers/pci/quirks.c index de9bbccda21f..e72af7d2c775 100644 --- a/drivers/pci/quirks.c +++ b/drivers/pci/quirks.c @@ -4230,6 +4230,45 @@ static int reset_hinic_vf_dev(struct pci_dev *pdev, bool probe) return 0; } +#define MHI_SOC_RESET_REQ_OFFSET 0xb0 +#define MHI_SOC_RESET_REQ BIT(0) + +/* + * Qualcomm modem device-specific reset using MHI SoC reset via BAR0 + * register. + */ +static int reset_qualcomm_modem(struct pci_dev *pdev, bool probe) +{ + void __iomem *bar; + u16 cmd; + + if (probe) + return 0; + + if (pdev->current_state != PCI_D0) + return -EINVAL; + + pci_read_config_word(pdev, PCI_COMMAND, &cmd); + pci_write_config_word(pdev, PCI_COMMAND, cmd | PCI_COMMAND_MEMORY); + + bar = pci_iomap(pdev, 0, 0); + if (!bar) { + pci_write_config_word(pdev, PCI_COMMAND, cmd); + return -ENODEV; + } + + iowrite32(MHI_SOC_RESET_REQ, bar + MHI_SOC_RESET_REQ_OFFSET); + ioread32(bar + MHI_SOC_RESET_REQ_OFFSET); + + /* Be sure device reset has been executed */ + msleep(2000); + + pci_iounmap(pdev, bar); + pci_write_config_word(pdev, PCI_COMMAND, cmd); + + return 0; +} + static const struct pci_dev_reset_methods pci_dev_reset_methods[] = { { PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82599_SFP_VF, reset_intel_82599_sfp_virtfn }, @@ -4245,6 +4284,7 @@ static const struct pci_dev_reset_methods pci_dev_reset_methods[] = { reset_chelsio_generic_dev }, { PCI_VENDOR_ID_HUAWEI, PCI_DEVICE_ID_HINIC_VF, reset_hinic_vf_dev }, + { PCI_VENDOR_ID_QCOM, 0x0308, reset_qualcomm_modem }, /* SDX62/SDX65 modems */ { 0 } }; From 2973773a8c011d06addebfad5f18f1288fb0fb56 Mon Sep 17 00:00:00 2001 From: Jose Ignacio Tornos Martinez Date: Mon, 5 Oct 2026 12:54:36 +0200 Subject: [PATCH 061/139] PCI: Add device-specific reset for Qualcomm WCN6855/WCN7850 WLAN Qualcomm WCN6855 (17cb:1103) and WCN7850 (17cb:1107) WLAN devices lack working reset methods for VFIO passthrough scenarios. These devices have no FLR capability, advertise NoSoftRst+ (blocking PM reset), and have broken bus reset. Standard reset methods were already disabled for these devices by commit 6a4f64c3a3ad ("PCI: Avoid SBR for Qualcomm WCN6855/WCN7850 WiFi, SDX62/SDX65 modems") because they were not working (quirk_no_bus_reset). Resets of this device always failed prior to this commit, so VFIO on the host could not enforce isolation between successive passthrough users. If a guest driver deinitialized the device, it may have been functional if passed through to a subsequent guest, despite the lack of isolation. Otherwise the device may have been left in an undefined state (e.g., DMA and interrupts active) and unusable. Add a device-specific reset method using BAR-space hardware reset registers that exist in these devices. WCN6855/WCN7850 WLAN devices use SoC global reset via BAR0 (sequence from ath11k/ath12k driver: ath11k_pci_soc_global_reset(), ath11k_pci_sw_reset(), ath11k_mhi_set_mhictrl_reset()): - Write/clear reset bit at offset 0x3008 - Wait for PCIe link recovery (up to 5 seconds) - Clear MHI controller SYSERR status at offset 0x38 These are true hardware reset mechanisms (not power management or firmware error recovery), providing proper device reset for VFIO scenarios. Testing shows stable operation over 100+ VM crash/reset cycles, compared to previous approaches that failed after ~30 cycles. Device-specific reset is position #1 in the reset hierarchy, so these Qualcomm devices will use hardware reset as their primary reset method. Signed-off-by: Jose Ignacio Tornos Martinez Signed-off-by: Bjorn Helgaas Reviewed-by: Manivannan Sadhasivam Acked-by: Alex Williamson Link: https://patch.msgid.link/20261005105436.83962-3-jtornosm@redhat.com --- drivers/pci/quirks.c | 75 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) diff --git a/drivers/pci/quirks.c b/drivers/pci/quirks.c index e72af7d2c775..f90cf7793c0c 100644 --- a/drivers/pci/quirks.c +++ b/drivers/pci/quirks.c @@ -22,6 +22,7 @@ #include /* isa_dma_bridge_buggy */ #include #include +#include #include #include #include @@ -4230,6 +4231,78 @@ static int reset_hinic_vf_dev(struct pci_dev *pdev, bool probe) return 0; } +#define QUALCOMM_WLAN_PCIE_SOC_GLOBAL_RESET 0x3008 +#define QUALCOMM_WLAN_PCIE_SOC_GLOBAL_RESET_V BIT(0) +#define QUALCOMM_WLAN_MHICTRL 0x38 +#define QUALCOMM_WLAN_MHICTRL_RESET_MASK 0x2 + +/* + * Qualcomm WLAN device-specific reset using SoC global reset via BAR0 + * registers. + */ +static int reset_qualcomm_wlan(struct pci_dev *pdev, bool probe) +{ + void __iomem *bar; + u32 val; + u16 cmd; + int ret; + + if (probe) + return 0; + + if (pdev->current_state != PCI_D0) + return -EINVAL; + + pci_read_config_word(pdev, PCI_COMMAND, &cmd); + pci_write_config_word(pdev, PCI_COMMAND, cmd | PCI_COMMAND_MEMORY); + + bar = pci_iomap(pdev, 0, 0); + if (!bar) { + pci_write_config_word(pdev, PCI_COMMAND, cmd); + return -ENODEV; + } + + val = ioread32(bar + QUALCOMM_WLAN_PCIE_SOC_GLOBAL_RESET); + if (PCI_POSSIBLE_ERROR(val)) { + ret = -ENODEV; + goto out_restore; + } + val |= QUALCOMM_WLAN_PCIE_SOC_GLOBAL_RESET_V; + iowrite32(val, bar + QUALCOMM_WLAN_PCIE_SOC_GLOBAL_RESET); + ioread32(bar + QUALCOMM_WLAN_PCIE_SOC_GLOBAL_RESET); + + msleep(10); + + val &= ~QUALCOMM_WLAN_PCIE_SOC_GLOBAL_RESET_V; + iowrite32(val, bar + QUALCOMM_WLAN_PCIE_SOC_GLOBAL_RESET); + ioread32(bar + QUALCOMM_WLAN_PCIE_SOC_GLOBAL_RESET); + + msleep(10); + + ret = read_poll_timeout(ioread32, val, !PCI_POSSIBLE_ERROR(val), + 20 * USEC_PER_MSEC, 5 * USEC_PER_SEC, false, + bar + QUALCOMM_WLAN_PCIE_SOC_GLOBAL_RESET); + if (ret) { + pci_err(pdev, "PCIe link failed to recover after reset\n"); + goto out_restore; + } + + /* + * After SOC_GLOBAL_RESET, MHISTATUS may still have SYSERR bit set + * and thus need to set MHICTRL_RESET to clear SYSERR. + */ + iowrite32(QUALCOMM_WLAN_MHICTRL_RESET_MASK, bar + QUALCOMM_WLAN_MHICTRL); + ioread32(bar + QUALCOMM_WLAN_MHICTRL); + + msleep(10); + +out_restore: + pci_iounmap(pdev, bar); + pci_write_config_word(pdev, PCI_COMMAND, cmd); + + return ret; +} + #define MHI_SOC_RESET_REQ_OFFSET 0xb0 #define MHI_SOC_RESET_REQ BIT(0) @@ -4285,6 +4358,8 @@ static const struct pci_dev_reset_methods pci_dev_reset_methods[] = { { PCI_VENDOR_ID_HUAWEI, PCI_DEVICE_ID_HINIC_VF, reset_hinic_vf_dev }, { PCI_VENDOR_ID_QCOM, 0x0308, reset_qualcomm_modem }, /* SDX62/SDX65 modems */ + { PCI_VENDOR_ID_QCOM, 0x1103, reset_qualcomm_wlan }, /* WCN6855 WLAN */ + { PCI_VENDOR_ID_QCOM, 0x1107, reset_qualcomm_wlan }, /* WCN7850 WLAN */ { 0 } }; From 624ee129620a054466b6a29d98cd7a40ccc239cd Mon Sep 17 00:00:00 2001 From: Donggeun Yoo Date: Sat, 3 Oct 2026 19:22:21 +0900 Subject: [PATCH 062/139] PCI/DOE: Fix double free of a duplicate feature's sysfs name When a DOE feature is reported twice, sysfs_add_file_to_group() fails with -EEXIST for the second copy and pci_doe_sysfs_feature_populate() frees that attribute's name, but leaves the pointer in place. pci_doe_sysfs_feature_remove() frees every name again when the device is removed, or when a later feature fails to register, so the name is freed twice: BUG: KASAN: double-free in pci_doe_sysfs_feature_remove+0x117/0x1b0 Call Trace: kfree+0x11a/0x420 pci_doe_sysfs_feature_remove+0x117/0x1b0 pci_doe_sysfs_teardown+0x90/0xf0 pci_remove_bus_device+0x128/0x2e0 pci_stop_and_remove_bus_device_locked+0x1d/0x30 remove_store+0xd2/0xf0 Clear the pointer after freeing the name. Fixes: 2311ab1820fe ("PCI/DOE: Expose DOE features via sysfs") Suggested-by: Alistair Francis Assisted-by: LLM Signed-off-by: Donggeun Yoo Signed-off-by: Bjorn Helgaas Reviewed-by: Alistair Francis Reviewed-by: Jonathan Cameron Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20261003102221.59894-1-donggeunyoo.kernel@gmail.com --- drivers/pci/doe.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/drivers/pci/doe.c b/drivers/pci/doe.c index ac95b1d2d999..6b5d9ba8e969 100644 --- a/drivers/pci/doe.c +++ b/drivers/pci/doe.c @@ -208,8 +208,9 @@ static int pci_doe_sysfs_feature_populate(struct pci_dev *pdev, pci_warn(pdev, "Failed adding %s to sysfs group\n", attrs[i].attr.name); goto fail; - } else - kfree(attrs[i].attr.name); + } + kfree(attrs[i].attr.name); + attrs[i].attr.name = NULL; } } From 07938f8a3cbf3e9cc27553b8982e2d56c15b354a Mon Sep 17 00:00:00 2001 From: Priyank Rathod Date: Mon, 28 Sep 2026 17:40:12 +0000 Subject: [PATCH 063/139] PCI/AER: Fix memory leak in aer_recover_queue() on kfifo buffer overflow When ACPI APEI/GHES processes PCIe AER error records, it allocates memory for aer_capability_regs (aer_regs) from ghes_estatus_pool and passes it to aer_recover_queue() to be enqueued into aer_recover_ring. If kfifo_in_spinlocked() fails due to a buffer overflow, aer_recover_queue() logged an error message but returned without freeing aer_regs. Because the entry was rejected and never inserted into the queue, aer_recover_work_func() could never dequeue or free it, leaking the allocated ghes_estatus_pool memory. aer_recover_queue() returns void, so the caller ghes_handle_aer() cannot free the buffer itself: ownership is transferred to the AER code, which until now only released it on the success path. Free aer_regs via ghes_estatus_pool_region_free() when kfifo_in_spinlocked() fails on buffer overflow. Fixes: e2abc47a5a1a ("ACPI: APEI: Fix AER info corruption when error status data has multiple sections") Signed-off-by: Priyank Rathod Signed-off-by: Bjorn Helgaas Reviewed-by: Kuppuswamy Sathyanarayanan Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20260928-b4-fix-aer-memleaks-v5-1-ba6b94c9c9a6@google.com --- drivers/pci/pcie/aer.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/drivers/pci/pcie/aer.c b/drivers/pci/pcie/aer.c index d8dcd238fda1..b013b853b555 100644 --- a/drivers/pci/pcie/aer.c +++ b/drivers/pci/pcie/aer.c @@ -1415,11 +1415,14 @@ void aer_recover_queue(int domain, unsigned int bus, unsigned int devfn, }; if (kfifo_in_spinlocked(&aer_recover_ring, &entry, 1, - &aer_recover_ring_lock)) + &aer_recover_ring_lock)) { schedule_work(&aer_recover_work); - else + } else { pr_err("buffer overflow in recovery for %04x:%02x:%02x.%x\n", domain, bus, PCI_SLOT(devfn), PCI_FUNC(devfn)); + ghes_estatus_pool_region_free((unsigned long)aer_regs, + sizeof(struct aer_capability_regs)); + } } EXPORT_SYMBOL_GPL(aer_recover_queue); #endif From d4c842c3af5bc52b3ca0b8a7f116114e43b6ba44 Mon Sep 17 00:00:00 2001 From: Priyank Rathod Date: Mon, 28 Sep 2026 17:40:13 +0000 Subject: [PATCH 064/139] PCI/AER: Fix memory leak in aer_recover_work_func() when pci_dev is missing When ACPI APEI/GHES processes PCIe AER error records, it allocates memory for aer_capability_regs (entry.regs) from ghes_estatus_pool and queues the entry into aer_recover_ring. In aer_recover_work_func(), items are popped from aer_recover_ring via kfifo_get(). If pci_get_domain_bus_and_slot() fails to find a matching pci_dev, the code previously executed 'continue', bypassing the call to ghes_estatus_pool_region_free(). As a result, the memory allocated for entry.regs from ghes_estatus_pool was leaked. This is reachable whenever the device reported by firmware is not (or is no longer) present in the PCI device tree, e.g. after hot-removal or when firmware reports an error for a device the kernel never enumerated. Refactor aer_recover_work_func() to ensure ghes_estatus_pool_region_free() is called unconditionally for every dequeued entry, releasing the pool memory even when pci_dev is missing. Fixes: e2abc47a5a1a ("ACPI: APEI: Fix AER info corruption when error status data has multiple sections") Signed-off-by: Priyank Rathod Signed-off-by: Bjorn Helgaas Reviewed-by: Kuppuswamy Sathyanarayanan Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20260928-b4-fix-aer-memleaks-v5-2-ba6b94c9c9a6@google.com --- drivers/pci/pcie/aer.c | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/drivers/pci/pcie/aer.c b/drivers/pci/pcie/aer.c index b013b853b555..a6600801af6e 100644 --- a/drivers/pci/pcie/aer.c +++ b/drivers/pci/pcie/aer.c @@ -1366,14 +1366,13 @@ static void aer_recover_work_func(struct work_struct *work) while (kfifo_get(&aer_recover_ring, &entry)) { pdev = pci_get_domain_bus_and_slot(entry.domain, entry.bus, entry.devfn); - if (!pdev) { + if (!pdev) pr_err_ratelimited("%04x:%02x:%02x.%x: no pci_dev found\n", entry.domain, entry.bus, PCI_SLOT(entry.devfn), PCI_FUNC(entry.devfn)); - continue; - } - pci_print_aer(pdev, entry.severity, entry.regs); + else + pci_print_aer(pdev, entry.severity, entry.regs); /* * Memory for aer_capability_regs(entry.regs) is being @@ -1385,13 +1384,15 @@ static void aer_recover_work_func(struct work_struct *work) ghes_estatus_pool_region_free((unsigned long)entry.regs, sizeof(struct aer_capability_regs)); - if (entry.severity == AER_NONFATAL) - pcie_do_recovery(pdev, pci_channel_io_normal, - aer_root_reset); - else if (entry.severity == AER_FATAL) - pcie_do_recovery(pdev, pci_channel_io_frozen, - aer_root_reset); - pci_dev_put(pdev); + if (pdev) { + if (entry.severity == AER_NONFATAL) + pcie_do_recovery(pdev, pci_channel_io_normal, + aer_root_reset); + else if (entry.severity == AER_FATAL) + pcie_do_recovery(pdev, pci_channel_io_frozen, + aer_root_reset); + pci_dev_put(pdev); + } } } From 6347fd9aee19cff3d4a087aba36927997b733f36 Mon Sep 17 00:00:00 2001 From: Kuppuswamy Sathyanarayanan Date: Fri, 2 Oct 2026 14:18:25 -0700 Subject: [PATCH 065/139] PCI: Assume control of portdrv-related features only when portdrv enabled Native control of PME, AER, DPC, and PCIe hotplug depends on the portdrv, so default to native handling of them only when CONFIG_PCIEPORTBUS is enabled. Native control of LTR and SHPC hotplug does not depend on portdrv, so keep defaulting those to native regardless. Signed-off-by: Kuppuswamy Sathyanarayanan Signed-off-by: Bjorn Helgaas Link: https://patch.msgid.link/20261002211828.1293177-2-sathyanarayanan.kuppuswamy@linux.intel.com --- drivers/pci/probe.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/drivers/pci/probe.c b/drivers/pci/probe.c index 27008e2ea5af..1e487a213eb0 100644 --- a/drivers/pci/probe.c +++ b/drivers/pci/probe.c @@ -658,6 +658,8 @@ static const struct device_type pci_host_bridge_type = { static void pci_init_host_bridge(struct pci_host_bridge *bridge) { + bool port_services = IS_ENABLED(CONFIG_PCIEPORTBUS); + INIT_LIST_HEAD(&bridge->windows); INIT_LIST_HEAD(&bridge->dma_ranges); INIT_LIST_HEAD(&bridge->ports); @@ -668,12 +670,12 @@ static void pci_init_host_bridge(struct pci_host_bridge *bridge) * may implement its own AER handling and use _OSC to prevent the * OS from interfering. */ - bridge->native_aer = 1; - bridge->native_pcie_hotplug = 1; + bridge->native_aer = port_services; + bridge->native_pcie_hotplug = port_services; bridge->native_shpc_hotplug = 1; - bridge->native_pme = 1; + bridge->native_pme = port_services; bridge->native_ltr = 1; - bridge->native_dpc = 1; + bridge->native_dpc = port_services; bridge->domain_nr = PCI_DOMAIN_NR_NOT_SET; bridge->native_cxl_error = 1; bridge->dev.type = &pci_host_bridge_type; From 0a1fdf4546ccf2f02845135fa5b36ed90685b220 Mon Sep 17 00:00:00 2001 From: Kuppuswamy Sathyanarayanan Date: Fri, 2 Oct 2026 14:18:26 -0700 Subject: [PATCH 066/139] PCI/ACPI: Tidy _OSC control bit checking Add OSC_OWNER() helper to prettify checking the _OSC control bits to learn whether the platform has granted us control of PCI features. No functional change intended. Signed-off-by: Kuppuswamy Sathyanarayanan Signed-off-by: Bjorn Helgaas Acked-by: Rafael J. Wysocki (Intel) Link: https://patch.msgid.link/20261002211828.1293177-3-sathyanarayanan.kuppuswamy@linux.intel.com --- drivers/acpi/pci_root.c | 35 +++++++++++++++++++++-------------- 1 file changed, 21 insertions(+), 14 deletions(-) diff --git a/drivers/acpi/pci_root.c b/drivers/acpi/pci_root.c index 88c65f34e305..756dc2f055f5 100644 --- a/drivers/acpi/pci_root.c +++ b/drivers/acpi/pci_root.c @@ -993,6 +993,12 @@ static void acpi_pci_root_release_info(struct pci_host_bridge *bridge) __acpi_pci_root_release_info(bridge->release_data); } +#define OSC_OWNER(ctrl, bit, flag) \ + do { \ + if (!((ctrl) & (bit))) \ + flag = 0; \ + } while (0) + struct pci_bus *acpi_pci_root_create(struct acpi_pci_root *root, struct acpi_pci_root_ops *ops, struct acpi_pci_root_info *info, @@ -1003,6 +1009,7 @@ struct pci_bus *acpi_pci_root_create(struct acpi_pci_root *root, int node = acpi_get_node(device->handle); struct pci_bus *bus; struct pci_host_bridge *host_bridge; + u32 ctrl, ext_ctrl; info->root = root; info->bridge = device; @@ -1028,21 +1035,21 @@ struct pci_bus *acpi_pci_root_create(struct acpi_pci_root *root, goto out_release_info; host_bridge = to_pci_host_bridge(bus->bridge); - if (!(root->osc_control_set & OSC_PCI_EXPRESS_NATIVE_HP_CONTROL)) - host_bridge->native_pcie_hotplug = 0; - if (!(root->osc_control_set & OSC_PCI_SHPC_NATIVE_HP_CONTROL)) - host_bridge->native_shpc_hotplug = 0; - if (!(root->osc_control_set & OSC_PCI_EXPRESS_AER_CONTROL)) - host_bridge->native_aer = 0; - if (!(root->osc_control_set & OSC_PCI_EXPRESS_PME_CONTROL)) - host_bridge->native_pme = 0; - if (!(root->osc_control_set & OSC_PCI_EXPRESS_LTR_CONTROL)) - host_bridge->native_ltr = 0; - if (!(root->osc_control_set & OSC_PCI_EXPRESS_DPC_CONTROL)) - host_bridge->native_dpc = 0; - if (!(root->osc_ext_control_set & OSC_CXL_ERROR_REPORTING_CONTROL)) - host_bridge->native_cxl_error = 0; + ctrl = root->osc_control_set; + ext_ctrl = root->osc_ext_control_set; + + OSC_OWNER(ctrl, OSC_PCI_EXPRESS_NATIVE_HP_CONTROL, + host_bridge->native_pcie_hotplug); + OSC_OWNER(ctrl, OSC_PCI_SHPC_NATIVE_HP_CONTROL, + host_bridge->native_shpc_hotplug); + OSC_OWNER(ctrl, OSC_PCI_EXPRESS_AER_CONTROL, host_bridge->native_aer); + OSC_OWNER(ctrl, OSC_PCI_EXPRESS_PME_CONTROL, host_bridge->native_pme); + OSC_OWNER(ctrl, OSC_PCI_EXPRESS_LTR_CONTROL, host_bridge->native_ltr); + OSC_OWNER(ctrl, OSC_PCI_EXPRESS_DPC_CONTROL, host_bridge->native_dpc); + + OSC_OWNER(ext_ctrl, OSC_CXL_ERROR_REPORTING_CONTROL, + host_bridge->native_cxl_error); acpi_dev_power_up_children_with_adr(device); From 4fe37866b0d92cdc45b62954a76489a9d9f8bd73 Mon Sep 17 00:00:00 2001 From: Kuppuswamy Sathyanarayanan Date: Fri, 2 Oct 2026 14:18:27 -0700 Subject: [PATCH 067/139] PCI/ACPI: Centralize pcie_ports_native checking If the user booted with "pcie_ports=native", we take control of the PCIe port services unconditionally, regardless of what _OSC says. Centralize the testing of pcie_ports_native in acpi_pci_root_create(), where we interpret the _OSC results, so other places only have to check host_bridge->native_X and we don't have to sprinkle tests of pcie_ports_native everywhere. Rather than overriding the host_bridge->native_X flags after the fact, fold "pcie_ports=native" into the _OSC control mask we evaluate, i.e., proceed as though the platform had granted control of the port services. That way there is a single mechanism deciding each native_X flag, and we can report exactly which features we are overriding _OSC for instead of just noting that we are overriding something: acpi PNP0A08:00: _OSC: OS forcing control ("pcie_ports=native") of [PCIeHotplug PME AER DPC] Forcing host_bridge->native_aer also restores DPC service binding under "pcie_ports=native", which regressed in commit 97ca178c899d ("PCI/DPC: Allow DPC on all Downstream Ports when OS controls AER"). That commit replaced the AER service test in get_port_device_capability() with host->native_aer, so DPC stayed off when host_bridge->native_aer was not set. This also extends "pcie_ports=native" to host_bridge->native_dpc, which had no pcie_ports_native fallback before. That effect is narrow. native_dpc is only consumed by pci_dpc_recovered(), where with CONFIG_PCIE_EDR=n it previously gave up immediately and now lets pciehp wait for DPC recovery before treating a Link Down as a hotplug event. Fold "pcie_ports=dpc-native" into the control mask the same way, so host_bridge->native_dpc reflects it as well, with the same narrow effect on pci_dpc_recovered(). Move the pcie_ports_dpc_native declaration to so acpi_pci_root_create() can test it. host_bridge->native_ltr is deliberately not forced. "pcie_ports=" controls PCIe port services and LTR is not one. There is no PCIE_PORT_SERVICE_LTR, and native_ltr is only used by pci_configure_ltr() to enable ASPM L1.2, so forcing it would be an ASPM policy decision users did not ask for. SHPC hotplug is left alone for a simpler reason: SHPC is a conventional PCI feature rather than a PCIe one, so "pcie_ports=" has no bearing on it. Fixes: 97ca178c899d ("PCI/DPC: Allow DPC on all Downstream Ports when OS controls AER") Closes: https://lore.kernel.org/linux-pci/20260901064554.2178688-1-kanie@linux.alibaba.com/ Signed-off-by: Kuppuswamy Sathyanarayanan Signed-off-by: Bjorn Helgaas Reviewed-by: Guixin Liu Acked-by: Rafael J. Wysocki (Intel) # ACPI core Link: https://patch.msgid.link/20261002211828.1293177-4-sathyanarayanan.kuppuswamy@linux.intel.com --- drivers/acpi/pci_root.c | 43 ++++++++++++++++++++++++++++++- drivers/pci/hotplug/pciehp_core.c | 2 +- drivers/pci/pci-acpi.c | 3 --- drivers/pci/pcie/aer.c | 6 ++--- drivers/pci/pcie/aer_cxl_rch.c | 2 +- drivers/pci/pcie/err.c | 2 +- drivers/pci/pcie/portdrv.c | 6 ++--- drivers/pci/pcie/portdrv.h | 2 -- include/linux/pci.h | 2 ++ 9 files changed, 53 insertions(+), 15 deletions(-) diff --git a/drivers/acpi/pci_root.c b/drivers/acpi/pci_root.c index 756dc2f055f5..a1a9c06012f8 100644 --- a/drivers/acpi/pci_root.c +++ b/drivers/acpi/pci_root.c @@ -999,6 +999,19 @@ static void acpi_pci_root_release_info(struct pci_host_bridge *bridge) flag = 0; \ } while (0) +#define FLAG(x) ((x) ? '+' : '-') + +/* + * _OSC control bits for the features implemented by the PCIe port driver, + * i.e., the ones "pcie_ports=native" applies to. LTR and SHPC hotplug are + * negotiated via _OSC as well, but they are not portdrv services, so + * "pcie_ports=" has no bearing on them. + */ +#define OSC_PCIE_PORT_SERVICE_CONTROLS (OSC_PCI_EXPRESS_NATIVE_HP_CONTROL | \ + OSC_PCI_EXPRESS_PME_CONTROL | \ + OSC_PCI_EXPRESS_AER_CONTROL | \ + OSC_PCI_EXPRESS_DPC_CONTROL) + struct pci_bus *acpi_pci_root_create(struct acpi_pci_root *root, struct acpi_pci_root_ops *ops, struct acpi_pci_root_info *info, @@ -1009,7 +1022,7 @@ struct pci_bus *acpi_pci_root_create(struct acpi_pci_root *root, int node = acpi_get_node(device->handle); struct pci_bus *bus; struct pci_host_bridge *host_bridge; - u32 ctrl, ext_ctrl; + u32 ctrl, ext_ctrl, override = 0; info->root = root; info->bridge = device; @@ -1039,6 +1052,26 @@ struct pci_bus *acpi_pci_root_create(struct acpi_pci_root *root, ctrl = root->osc_control_set; ext_ctrl = root->osc_ext_control_set; + /* + * If the user specified "pcie_ports=native", use the PCIe port + * services regardless of what _OSC says, i.e., proceed as though the + * platform had granted us control of them. "pcie_ports=dpc-native" + * does the same for DPC alone. This may conflict with firmware that + * expects to own those features. + */ + if (pcie_ports_native) + override = OSC_PCIE_PORT_SERVICE_CONTROLS & ~ctrl; + else if (pcie_ports_dpc_native) + override = OSC_PCI_EXPRESS_DPC_CONTROL & ~ctrl; + + if (override) { + decode_osc_control(root, pcie_ports_native ? + "OS forcing control (\"pcie_ports=native\") of" : + "OS forcing control (\"pcie_ports=dpc-native\") of", + override); + ctrl |= override; + } + OSC_OWNER(ctrl, OSC_PCI_EXPRESS_NATIVE_HP_CONTROL, host_bridge->native_pcie_hotplug); OSC_OWNER(ctrl, OSC_PCI_SHPC_NATIVE_HP_CONTROL, @@ -1051,6 +1084,14 @@ struct pci_bus *acpi_pci_root_create(struct acpi_pci_root *root, OSC_OWNER(ext_ctrl, OSC_CXL_ERROR_REPORTING_CONTROL, host_bridge->native_cxl_error); + dev_info(&root->device->dev, "OS native features: SHPCHotplug%c PCIeHotplug%c PME%c AER%c DPC%c LTR%c\n", + FLAG(host_bridge->native_shpc_hotplug), + FLAG(host_bridge->native_pcie_hotplug), + FLAG(host_bridge->native_pme), + FLAG(host_bridge->native_aer), + FLAG(host_bridge->native_dpc), + FLAG(host_bridge->native_ltr)); + acpi_dev_power_up_children_with_adr(device); pci_scan_child_bus(bus); diff --git a/drivers/pci/hotplug/pciehp_core.c b/drivers/pci/hotplug/pciehp_core.c index 2cafd3b26f34..b42829cf1377 100644 --- a/drivers/pci/hotplug/pciehp_core.c +++ b/drivers/pci/hotplug/pciehp_core.c @@ -258,7 +258,7 @@ static bool pme_is_native(struct pcie_device *dev) const struct pci_host_bridge *host; host = pci_find_host_bridge(dev->port->bus); - return pcie_ports_native || host->native_pme; + return host->native_pme; } static void pciehp_disable_interrupt(struct pcie_device *dev) diff --git a/drivers/pci/pci-acpi.c b/drivers/pci/pci-acpi.c index 42d545edd7fa..1150f2fbabf4 100644 --- a/drivers/pci/pci-acpi.c +++ b/drivers/pci/pci-acpi.c @@ -812,9 +812,6 @@ bool pciehp_is_native(struct pci_dev *bridge) if (!IS_ENABLED(CONFIG_HOTPLUG_PCI_PCIE)) return false; - if (pcie_ports_native) - return true; - host = pci_find_host_bridge(bridge->bus); return host->native_pcie_hotplug; } diff --git a/drivers/pci/pcie/aer.c b/drivers/pci/pcie/aer.c index d8dcd238fda1..e84dd686582a 100644 --- a/drivers/pci/pcie/aer.c +++ b/drivers/pci/pcie/aer.c @@ -260,7 +260,7 @@ int pcie_aer_is_native(struct pci_dev *dev) if (!dev->aer_cap) return 0; - return pcie_ports_native || host->native_aer; + return host->native_aer; } EXPORT_SYMBOL_NS_GPL(pcie_aer_is_native, "CXL"); @@ -1847,7 +1847,7 @@ static pci_ers_result_t aer_root_reset(struct pci_dev *dev) */ aer = root ? root->aer_cap : 0; - if ((host->native_aer || pcie_ports_native) && aer) + if (host->native_aer && aer) aer_disable_irq(root); if (type == PCI_EXP_TYPE_RC_EC || type == PCI_EXP_TYPE_RC_END) { @@ -1862,7 +1862,7 @@ static pci_ers_result_t aer_root_reset(struct pci_dev *dev) pci_is_root_bus(dev->bus) ? "Root" : "Downstream", rc); } - if ((host->native_aer || pcie_ports_native) && aer) { + if (host->native_aer && aer) { /* Clear Root Error Status */ pci_read_config_dword(root, aer + PCI_ERR_ROOT_STATUS, ®32); pci_write_config_dword(root, aer + PCI_ERR_ROOT_STATUS, reg32); diff --git a/drivers/pci/pcie/aer_cxl_rch.c b/drivers/pci/pcie/aer_cxl_rch.c index e471eefec9c4..b480dad8bbf4 100644 --- a/drivers/pci/pcie/aer_cxl_rch.c +++ b/drivers/pci/pcie/aer_cxl_rch.c @@ -31,7 +31,7 @@ static bool cxl_error_is_native(struct pci_dev *dev) { struct pci_host_bridge *host = pci_find_host_bridge(dev->bus); - return (pcie_ports_native || host->native_aer); + return host->native_aer; } static int cxl_rch_handle_error_iter(struct pci_dev *dev, void *data) diff --git a/drivers/pci/pcie/err.c b/drivers/pci/pcie/err.c index d77403d8855b..1a7fc71c79d8 100644 --- a/drivers/pci/pcie/err.c +++ b/drivers/pci/pcie/err.c @@ -273,7 +273,7 @@ pci_ers_result_t pcie_do_recovery(struct pci_dev *dev, * it is responsible for clearing this status. In that case, the * signaling device may not even be visible to the OS. */ - if (host->native_aer || pcie_ports_native) { + if (host->native_aer) { pcie_clear_device_status(dev); pci_aer_clear_nonfatal_status(dev); } diff --git a/drivers/pci/pcie/portdrv.c b/drivers/pci/pcie/portdrv.c index a9cbfc1d2bc7..32fc623dd410 100644 --- a/drivers/pci/pcie/portdrv.c +++ b/drivers/pci/pcie/portdrv.c @@ -223,7 +223,7 @@ static int get_port_device_capability(struct pci_dev *dev) if (dev->is_pciehp && (pci_pcie_type(dev) == PCI_EXP_TYPE_ROOT_PORT || pci_pcie_type(dev) == PCI_EXP_TYPE_DOWNSTREAM) && - (pcie_ports_native || host->native_pcie_hotplug)) { + host->native_pcie_hotplug) { services |= PCIE_PORT_SERVICE_HP; /* @@ -240,14 +240,14 @@ static int get_port_device_capability(struct pci_dev *dev) if ((pci_pcie_type(dev) == PCI_EXP_TYPE_ROOT_PORT || pci_pcie_type(dev) == PCI_EXP_TYPE_RC_EC) && dev->aer_cap && pci_aer_available() && - (pcie_ports_native || host->native_aer)) + host->native_aer) services |= PCIE_PORT_SERVICE_AER; #endif /* Root Ports and Root Complex Event Collectors may generate PMEs */ if ((pci_pcie_type(dev) == PCI_EXP_TYPE_ROOT_PORT || pci_pcie_type(dev) == PCI_EXP_TYPE_RC_EC) && - (pcie_ports_native || host->native_pme)) { + host->native_pme) { services |= PCIE_PORT_SERVICE_PME; /* diff --git a/drivers/pci/pcie/portdrv.h b/drivers/pci/pcie/portdrv.h index cc58bf2f2c84..e1b6a8af6f71 100644 --- a/drivers/pci/pcie/portdrv.h +++ b/drivers/pci/pcie/portdrv.h @@ -25,8 +25,6 @@ #define PCIE_PORT_DEVICE_MAXSERVICES 5 -extern bool pcie_ports_dpc_native; - #ifdef CONFIG_PCIEAER int pcie_aer_init(void); #else diff --git a/include/linux/pci.h b/include/linux/pci.h index d31a8d107b1e..f3e9c6c9c5e0 100644 --- a/include/linux/pci.h +++ b/include/linux/pci.h @@ -1915,12 +1915,14 @@ static inline int pci_irqd_intx_xlate(struct irq_domain *d, #ifdef CONFIG_PCIEPORTBUS extern bool pcie_ports_disabled; extern bool pcie_ports_native; +extern bool pcie_ports_dpc_native; int pcie_set_target_speed(struct pci_dev *port, enum pci_bus_speed speed_req, bool use_lt); #else #define pcie_ports_disabled true #define pcie_ports_native false +#define pcie_ports_dpc_native false static inline int pcie_set_target_speed(struct pci_dev *port, enum pci_bus_speed speed_req, From 622a70a52523008c7d7d3e1fa68d3729f5a982f3 Mon Sep 17 00:00:00 2001 From: Priyank Rathod Date: Mon, 28 Sep 2026 17:40:14 +0000 Subject: [PATCH 068/139] PCI/AER: Document that aer_recover_queue() takes ownership of aer_regs ghes_handle_aer() allocates the AER register snapshot that it passes to aer_recover_queue() from ghes_estatus_pool. aer_recover_queue() returns void, so the caller cannot tell whether the record was queued, and the AER code owns the buffer from then on and must free it on every path. None of this is documented at the definition of this exported function. With GHES enabled, a new caller that passed a buffer from any other allocator would hit the BUG() in gen_pool_free_owner() when the AER code returns the buffer to ghes_estatus_pool, and a caller that freed the buffer itself would cause a double free. Add a kernel-doc comment that describes the parameters and states that aer_recover_queue() takes ownership of @aer_regs, which must have been allocated from ghes_estatus_pool. No functional change. Suggested-by: Kuppuswamy Sathyanarayanan Link: https://lore.kernel.org/r/4513e7d4-4e2f-42d8-8f0c-2f0e03815dee@linux.intel.com Signed-off-by: Priyank Rathod Signed-off-by: Bjorn Helgaas Reviewed-by: Kuppuswamy Sathyanarayanan Link: https://patch.msgid.link/20260928-b4-fix-aer-memleaks-v5-3-ba6b94c9c9a6@google.com --- drivers/pci/pcie/aer.c | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/drivers/pci/pcie/aer.c b/drivers/pci/pcie/aer.c index a6600801af6e..a58244e00bc4 100644 --- a/drivers/pci/pcie/aer.c +++ b/drivers/pci/pcie/aer.c @@ -1404,6 +1404,24 @@ static void aer_recover_work_func(struct work_struct *work) static DEFINE_SPINLOCK(aer_recover_ring_lock); static DECLARE_WORK(aer_recover_work, aer_recover_work_func); +/** + * aer_recover_queue - queue an AER error record reported by firmware + * @domain: PCI domain (segment) of the device that reported the error + * @bus: bus number of the device that reported the error + * @devfn: encoded device and function number, as returned by PCI_DEVFN() + * @severity: AER_CORRECTABLE, AER_NONFATAL or AER_FATAL + * @aer_regs: snapshot of the device's AER Capability registers + * + * Queue an error record received from firmware through APEI GHES. The + * record is processed later from a workqueue, which logs the error and, + * for uncorrectable errors, attempts recovery of the device. + * + * Takes ownership of @aer_regs, which must have been allocated from + * ghes_estatus_pool with a size of sizeof(struct aer_capability_regs). + * The buffer is freed with ghes_estatus_pool_region_free() by the work + * item that processes the record, or immediately if the queue is full. + * The caller must not access or free @aer_regs after this call. + */ void aer_recover_queue(int domain, unsigned int bus, unsigned int devfn, int severity, struct aer_capability_regs *aer_regs) { From e86ac10110d00e6c3801f38bde1863951315222b Mon Sep 17 00:00:00 2001 From: Priyank Rathod Date: Fri, 18 Sep 2026 17:24:48 +0000 Subject: [PATCH 069/139] PCI/AER: Fix struct pci_dev reference leak in aer_process_err_devices() When an AER error occurs, candidate error-source devices are added to e_info->dev[] by add_error_device(), which takes a reference with pci_dev_get(). A device can be recorded without any error status being present: the Requester ID fast path in is_error_source(), if (e_info->id == pci_dev_id(dev)) return true; matches purely on the ID reported by the Root Port and returns true without reading the device's AER status registers. In aer_process_err_devices(), handle_error_source() is called only if aer_get_device_error_info() returns non-zero, i.e. only if an unmasked error status bit is actually set. It returns 0 if the device has become inaccessible (status and mask both read as all ones, so status & ~mask == 0) or if no unmasked status bit is set. Because handle_error_source() was responsible for calling pci_dev_put(), skipping it permanently leaks the reference taken in add_error_device(). Decouple reference lifetime from error handling by moving pci_dev_put() out of handle_error_source() and into the aer_process_err_devices() loop, so every recorded device is put exactly once. handle_error_source() is static and aer_process_err_devices() is its only caller, so no other path is affected. Fixes: 60271ab044a5 ("PCI/AER: Take reference on error devices") Signed-off-by: Priyank Rathod Signed-off-by: Bjorn Helgaas Link: https://patch.msgid.link/20260918-fix-aer-refcount-leak-v2-1-bdd1ff1c35a9@google.com --- drivers/pci/pcie/aer.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/pci/pcie/aer.c b/drivers/pci/pcie/aer.c index a58244e00bc4..e13c103ffaf4 100644 --- a/drivers/pci/pcie/aer.c +++ b/drivers/pci/pcie/aer.c @@ -1340,7 +1340,6 @@ static void handle_error_source(struct pci_dev *dev, struct aer_err_info *info) { cxl_rch_handle_error(dev, info); pci_aer_handle_error(dev, info); - pci_dev_put(dev); } #ifdef CONFIG_ACPI_APEI_PCIEAER @@ -1540,6 +1539,7 @@ static inline void aer_process_err_devices(struct aer_err_info *e_info) for (i = 0; i < e_info->error_dev_num && e_info->dev[i]; i++) { if (aer_get_device_error_info(e_info, i)) handle_error_source(e_info->dev[i], e_info); + pci_dev_put(e_info->dev[i]); } } From ed3ccc190765f194254c47617a0d894307d39862 Mon Sep 17 00:00:00 2001 From: Janusz Krzysztofik Date: Mon, 7 Sep 2026 10:59:18 +0200 Subject: [PATCH 070/139] PCI: Avoid FLR and SBR on Intel DG1 0x4905 graphics adapters DRM CI consistently reports failing device reset attempts on Intel DG1 discrete graphics adapters. Manual reproduction attempts allowed to narrow down the scope of that problem to a specific revision of that hardware, SKU 8086:4905. It has been confirmed that both reset methods claimed by the device, i.e., FLR and Secondary Bus Reset, suffer from that issue. Since consecutively failing device resets may lead to system hangs, disable both useless reset methods. A respective CI test will then be skipped instead of aborting whole CI jobs. Suggested-by: Joonas Lahtinen Closes: https://gitlab.freedesktop.org/drm/i915/kernel/-/work_items/11814 Signed-off-by: Janusz Krzysztofik Signed-off-by: Bjorn Helgaas Reviewed-by: Andi Shyti Reviewed-by: Krzysztof Karas Link: https://patch.msgid.link/20260907090249.1348552-2-janusz.krzysztofik@linux.intel.com --- drivers/pci/quirks.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/pci/quirks.c b/drivers/pci/quirks.c index f90cf7793c0c..259eeead1605 100644 --- a/drivers/pci/quirks.c +++ b/drivers/pci/quirks.c @@ -3812,6 +3812,9 @@ DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_TI, 0xb005, quirk_no_bus_reset); */ DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_ASMEDIA, 0x1164, quirk_no_bus_reset); +/* Intel DG1 0x4905 may not recover from bus reset */ +DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x4905, quirk_no_bus_reset); + static void quirk_no_pm_reset(struct pci_dev *dev) { /* @@ -5712,6 +5715,7 @@ DECLARE_PCI_FIXUP_EARLY(PCI_VENDOR_ID_INTEL, 0x443, quirk_intel_qat_vf_cap); * AMD Neural Processing Unit 0x1502 0x17f0 * Intel 82579LM Gigabit Ethernet Controller 0x1502 * Intel 82579V Gigabit Ethernet Controller 0x1503 + * Intel DG1 Discrete Graphics Adapter 0x4905 * Mediatek MT7922 802.11ax PCI Express Wireless Network Adapter * Mediatek MT7925 802.11be PCI Express Wireless Network Adapter */ @@ -5727,6 +5731,7 @@ DECLARE_PCI_FIXUP_EARLY(PCI_VENDOR_ID_AMD, 0x1502, quirk_no_flr); DECLARE_PCI_FIXUP_EARLY(PCI_VENDOR_ID_AMD, 0x17f0, quirk_no_flr); DECLARE_PCI_FIXUP_EARLY(PCI_VENDOR_ID_INTEL, 0x1502, quirk_no_flr); DECLARE_PCI_FIXUP_EARLY(PCI_VENDOR_ID_INTEL, 0x1503, quirk_no_flr); +DECLARE_PCI_FIXUP_EARLY(PCI_VENDOR_ID_INTEL, 0x4905, quirk_no_flr); DECLARE_PCI_FIXUP_EARLY(PCI_VENDOR_ID_MEDIATEK, 0x0616, quirk_no_flr); DECLARE_PCI_FIXUP_EARLY(PCI_VENDOR_ID_MEDIATEK, 0x7925, quirk_no_flr); From 36f9bd09920ed71c1b79db397170412b2de22319 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Wilczy=C5=84ski?= Date: Fri, 14 Aug 2026 07:05:19 +0000 Subject: [PATCH 071/139] PCI/sysfs: Stop reporting _DSM failures as -EPERM MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently, dsm_get_label() returns the literal -1 on every failure path. The sysfs read path passes that value to userspace as -EPERM. Reading the "label" or "acpi_index" attribute on a platform where the Device Name _DSM returns a malformed result then fails with: $ cat /sys/bus/pci/devices/0000:00:08.3/label cat: /sys/bus/pci/devices/0000:00:08.3/label: Operation not permitted Nothing in that path performs a permission check, and the read fails the same way for a privileged reader. The error code points at a cause that does not exist, and tools such as lspci report it on every invocation. Return -ENODEV when the device has no ACPI companion, and -EIO when the _DSM evaluation fails or returns an object that cannot be parsed. Other _DSM users in the tree report such failures the same way. The set of reads that succeed, and the bytes they return, stay the same. Only the error code of reads that already fail differs. Fixes: 6058989bad05 ("PCI: Export ACPI _DSM provided firmware instance number and string name to sysfs") Link: https://github.com/pciutils/pciutils/issues/175 Signed-off-by: Krzysztof WilczyÅ„ski Signed-off-by: Bjorn Helgaas Link: https://patch.msgid.link/20260814070522.2327975-2-kwilczynski@kernel.org --- drivers/pci/pci-label.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/pci/pci-label.c b/drivers/pci/pci-label.c index 0c6446519640..255e0ecffb09 100644 --- a/drivers/pci/pci-label.c +++ b/drivers/pci/pci-label.c @@ -160,12 +160,12 @@ static int dsm_get_label(struct device *dev, char *buf, int len = 0; if (!handle) - return -1; + return -ENODEV; obj = acpi_evaluate_dsm(handle, &pci_acpi_dsm_guid, 0x2, DSM_PCI_DEVICE_NAME, NULL); if (!obj) - return -1; + return -EIO; tmp = obj->package.elements; if (obj->type == ACPI_TYPE_PACKAGE && obj->package.count == 2 && @@ -190,7 +190,7 @@ static int dsm_get_label(struct device *dev, char *buf, ACPI_FREE(obj); - return len > 0 ? len : -1; + return len > 0 ? len : -EIO; } static ssize_t label_show(struct device *dev, struct device_attribute *attr, From 337bdf853101a0ee66eadf668f0fef21f8276b52 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Wilczy=C5=84ski?= Date: Fri, 14 Aug 2026 07:05:20 +0000 Subject: [PATCH 072/139] PCI/sysfs: Decouple acpi_index from the optional device name element MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The "_DSM for Naming a PCI or PCI Express Device Under Operating Systems" (PCI Firmware r3.3, sec 4.6.7) returns a package of two elements: 1) Instance number (mandatory Integer) 2) String name (optional String, null string if not populated) These are exposed as sysfs 'acpi_index' and 'label'. Previously, if the name was malformed, reads of 'acpi_index' failed unnecessarily. Validate each element only for the attribute that exports it. So "acpi_index" now depends on the instance number alone, and "label" reads fail with -EIO when the name element is neither a string nor a buffer. The package elements pointer is read only after the object type has been checked. On platforms with a valid instance number and a malformed name element the "acpi_index" attribute starts returning data. Because udev derives the onboard interface name from "acpi_index", an interface on such a platform may be renamed once, on the first boot after this change. That is the attribute assuming the value the firmware always provided. Link: https://github.com/pciutils/pciutils/issues/175 Signed-off-by: Krzysztof WilczyÅ„ski [bhelgaas: commit log] Signed-off-by: Bjorn Helgaas Link: https://patch.msgid.link/20260814070522.2327975-3-kwilczynski@kernel.org --- drivers/pci/pci-label.c | 55 +++++++++++++++++++++++++---------------- 1 file changed, 34 insertions(+), 21 deletions(-) diff --git a/drivers/pci/pci-label.c b/drivers/pci/pci-label.c index 255e0ecffb09..5b08f50653a3 100644 --- a/drivers/pci/pci-label.c +++ b/drivers/pci/pci-label.c @@ -157,7 +157,7 @@ static int dsm_get_label(struct device *dev, char *buf, { acpi_handle handle = ACPI_HANDLE(dev); union acpi_object *obj, *tmp; - int len = 0; + int len; if (!handle) return -ENODEV; @@ -167,30 +167,43 @@ static int dsm_get_label(struct device *dev, char *buf, if (!obj) return -EIO; - tmp = obj->package.elements; - if (obj->type == ACPI_TYPE_PACKAGE && obj->package.count == 2 && - tmp[0].type == ACPI_TYPE_INTEGER && - (tmp[1].type == ACPI_TYPE_STRING || - tmp[1].type == ACPI_TYPE_BUFFER)) { - /* - * The second string element is optional even when - * this _DSM is implemented; when not implemented, - * this entry must return a null string. - */ - if (attr == ACPI_ATTR_INDEX_SHOW) { - len = sysfs_emit(buf, "%llu\n", tmp->integer.value); - } else if (attr == ACPI_ATTR_LABEL_SHOW) { - if (tmp[1].type == ACPI_TYPE_STRING) - len = sysfs_emit(buf, "%s\n", - tmp[1].string.pointer); - else if (tmp[1].type == ACPI_TYPE_BUFFER) - len = dsm_label_utf16s_to_utf8s(tmp + 1, buf); - } + if (obj->type != ACPI_TYPE_PACKAGE || obj->package.count != 2) { + len = -EIO; + goto out; } + tmp = obj->package.elements; + if (tmp[0].type != ACPI_TYPE_INTEGER) { + len = -EIO; + goto out; + } + + if (attr == ACPI_ATTR_INDEX_SHOW) { + len = sysfs_emit(buf, "%llu\n", tmp[0].integer.value); + goto out; + } + + /* + * Per PCI Firmware r3.3, sec 4.6.7, the device name is optional + * even when this _DSM is implemented. When not implemented, this + * entry must return a NULL string. + */ + switch (tmp[1].type) { + case ACPI_TYPE_STRING: + len = sysfs_emit(buf, "%s\n", tmp[1].string.pointer); + break; + case ACPI_TYPE_BUFFER: + len = dsm_label_utf16s_to_utf8s(&tmp[1], buf); + break; + default: + len = -EIO; + break; + } + +out: ACPI_FREE(obj); - return len > 0 ? len : -EIO; + return len; } static ssize_t label_show(struct device *dev, struct device_attribute *attr, From 1832dc1579aa7cc7cd41843770767e7a3ffa8364 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Wilczy=C5=84ski?= Date: Fri, 14 Aug 2026 07:05:21 +0000 Subject: [PATCH 073/139] PCI/sysfs: Handle a malformed _DSM result in acpi_attr_is_visible() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently, the "label" and "acpi_index" attributes are created whenever the _DSM function 0 bitmap advertises the Device Name function, without evaluating that function. The documented ABI states that each attribute is created only if the firmware has given a name or an instance number to the PCI device. The bitmap says nothing about the object the function returns. Firmware that advertises the function but returns an object that cannot be parsed therefore produces attributes that exist and fail every read, as in the report below. Evaluate the Device Name _DSM when deciding attribute visibility and create each attribute only when the element it exports has a type the read path accepts, mirroring the checks performed at read time. The SMBIOS attribute group already follows this pattern by performing the DMI lookup in its is_visible() callback. The read side checks remain in place since the two evaluations happen at different times. This restores the behaviour from before commit 2fc59fe2ecdc ("PCI / pci-label: treat PCI label with index 0 as valid label"), which switched visibility from evaluating the function to checking only the function 0 bitmap. The check is now made for each attribute, not once for both. Fixes: 2fc59fe2ecdc ("PCI / pci-label: treat PCI label with index 0 as valid label") Closes: https://github.com/pciutils/pciutils/issues/175 Signed-off-by: Krzysztof WilczyÅ„ski Signed-off-by: Bjorn Helgaas Link: https://patch.msgid.link/20260814070522.2327975-4-kwilczynski@kernel.org --- Documentation/ABI/testing/sysfs-bus-pci | 14 ++++++++--- drivers/pci/pci-label.c | 33 ++++++++++++++++++++++++- 2 files changed, 42 insertions(+), 5 deletions(-) diff --git a/Documentation/ABI/testing/sysfs-bus-pci b/Documentation/ABI/testing/sysfs-bus-pci index 55ea1db749a1..c4ff6b538233 100644 --- a/Documentation/ABI/testing/sysfs-bus-pci +++ b/Documentation/ABI/testing/sysfs-bus-pci @@ -244,10 +244,16 @@ Contact: Narendra K , linux-bugs@dell.com Description: Reading this attribute will provide the firmware given name (SMBIOS type 41 string or ACPI _DSM string) of - the PCI device. The attribute will be created only - if the firmware has given a name to the PCI device. - ACPI _DSM string name will be given priority if the - system firmware provides SMBIOS type 41 string also. + the PCI device. The attribute will be created only if the + firmware naming mechanism is implemented for the device: + a SMBIOS type 41 record with a non-empty reference + designation, or a Device Name _DSM that returns the name + as a string or a buffer. The value read is empty when the + firmware implements the _DSM but gives the device no name, + which per PCI Firmware r3.3, sec 4.6.7 the firmware reports + as a NULL string. ACPI _DSM string name will be given + priority if the system firmware provides SMBIOS type 41 + string also. Users: Userspace applications interested in knowing the firmware assigned name of the PCI device. diff --git a/drivers/pci/pci-label.c b/drivers/pci/pci-label.c index 5b08f50653a3..abb941530c56 100644 --- a/drivers/pci/pci-label.c +++ b/drivers/pci/pci-label.c @@ -230,11 +230,42 @@ static umode_t acpi_attr_is_visible(struct kobject *kobj, struct attribute *a, int n) { struct device *dev = kobj_to_dev(kobj); + union acpi_object *obj, *tmp; + umode_t mode = 0; if (!device_has_acpi_name(dev)) return 0; - return a->mode; + /* + * The bitmap from _DSM function 0 only advertises function 7, + * and whether the returned object can be parsed is a separate + * question. Evaluate it and expose each attribute only if the + * element it exports has one of the types the read path + * accepts, mirroring the checks in dsm_get_label(). + */ + obj = acpi_evaluate_dsm(ACPI_HANDLE(dev), &pci_acpi_dsm_guid, 0x2, + DSM_PCI_DEVICE_NAME, NULL); + if (!obj) + return 0; + + if (obj->type != ACPI_TYPE_PACKAGE || obj->package.count != 2) + goto out; + + tmp = obj->package.elements; + if (tmp[0].type != ACPI_TYPE_INTEGER) + goto out; + + if (a == &dev_attr_acpi_index.attr) + mode = a->mode; + else if (a == &dev_attr_label.attr && + (tmp[1].type == ACPI_TYPE_STRING || + tmp[1].type == ACPI_TYPE_BUFFER)) + mode = a->mode; + +out: + ACPI_FREE(obj); + + return mode; } const struct attribute_group pci_dev_acpi_attr_group = { From af5ca2d75147bf283239e8ef0fbf6e5e0488ca91 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Wilczy=C5=84ski?= Date: Fri, 14 Aug 2026 07:05:22 +0000 Subject: [PATCH 074/139] PCI/sysfs: Pass the device name length in UTF-16 code units MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently, dsm_label_utf16s_to_utf8s() passes the ACPI buffer length to utf16s_to_utf8s() unchanged. The Device Name _DSM may return the device name as a buffer holding a UTF-16 string, and the ACPI length counts bytes while the converter counts wchar_t elements. The converter therefore receives a count twice the number of code units the buffer holds, and that count is its only bound on the input. A NUL code unit ends the conversion early, so a name that carries one is converted correctly and the error stays hidden. ACPICA zeroes the entire result allocation, and the padding placed after the buffer supplies that NUL for most lengths. Lengths that leave no padding, or a single byte of it, do not, and the conversion then runs up to its own length in bytes past the end of the allocation. The bytes beyond the allocation are decoded into the "label" attribute, which is world readable. Divide the buffer length by the size of wchar_t so that the converter receives a count of code units. The division truncates, so the converter is never told to read more bytes than the buffer holds, including for an odd length. Reaching the out of bounds read needs firmware that returns the name as a buffer and omits the terminator. Fixes: 6058989bad05 ("PCI: Export ACPI _DSM provided firmware instance number and string name to sysfs") Signed-off-by: Krzysztof WilczyÅ„ski Signed-off-by: Bjorn Helgaas Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20260814070522.2327975-5-kwilczynski@kernel.org --- drivers/pci/pci-label.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/pci/pci-label.c b/drivers/pci/pci-label.c index abb941530c56..08c461f1142d 100644 --- a/drivers/pci/pci-label.c +++ b/drivers/pci/pci-label.c @@ -144,7 +144,7 @@ static int dsm_label_utf16s_to_utf8s(union acpi_object *obj, char *buf) int len; len = utf16s_to_utf8s((const wchar_t *)obj->buffer.pointer, - obj->buffer.length, + obj->buffer.length / sizeof(wchar_t), UTF16_LITTLE_ENDIAN, buf, PAGE_SIZE - 1); buf[len++] = '\n'; From e7b44579a6b4495c03720da49f2eeaf13a94ba69 Mon Sep 17 00:00:00 2001 From: kai zhang Date: Wed, 7 Oct 2026 22:15:58 +0800 Subject: [PATCH 075/139] PCI/P2PDMA: Add Intel Xeon E3 v4/E5 v4/E7 v4 host bridge to allowlist For P2P paths routed through a host bridge, the P2PDMA core consults an allowlist of known-good hardware. Add Intel 8086:6f00 with REQ_SAME_HOST_BRIDGE so the entry permits peers only below the same host bridge. The bridge was tested on a single-socket Intel Xeon E5-2697A v4 system with two PCIe GPUs below different Root Ports of the same host bridge. Without the allowlist entry, peer access was rejected before a transfer was issued. With the entry, 64 MiB peer transfers completed in both directions with data integrity verified while the IOMMU used translated mode. Assisted-by: LLM Signed-off-by: kai zhang Signed-off-by: Bjorn Helgaas Link: https://patch.msgid.link/20261007141558.59852-1-zhangkai.cool@gmail.com --- drivers/pci/p2pdma.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/pci/p2pdma.c b/drivers/pci/p2pdma.c index 0b22ca48b0b4..5520d79da749 100644 --- a/drivers/pci/p2pdma.c +++ b/drivers/pci/p2pdma.c @@ -542,6 +542,8 @@ static const struct pci_p2pdma_whitelist_entry { /* Intel Xeon E5/Core i7 */ {PCI_VENDOR_ID_INTEL, 0x3c00, REQ_SAME_HOST_BRIDGE}, {PCI_VENDOR_ID_INTEL, 0x3c01, REQ_SAME_HOST_BRIDGE}, + /* Intel Xeon E7 v4/Xeon E5 v4/Xeon E3 v4/Xeon D */ + {PCI_VENDOR_ID_INTEL, 0x6f00, REQ_SAME_HOST_BRIDGE}, /* Intel Xeon E7 v3/Xeon E5 v3/Core i7 */ {PCI_VENDOR_ID_INTEL, 0x2f00, REQ_SAME_HOST_BRIDGE}, {PCI_VENDOR_ID_INTEL, 0x2f01, REQ_SAME_HOST_BRIDGE}, From 1319b085869ee2c49093d611f11923e820af9709 Mon Sep 17 00:00:00 2001 From: Leon Romanovsky Date: Sun, 30 Aug 2026 15:31:49 +0300 Subject: [PATCH 076/139] PCI/P2PDMA: Update DMABUF lifecycle docs after move_notify() rename MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Commit 95308225e5ba ("dma-buf: Rename dma_buf_move_notify() to dma_buf_invalidate_mappings()") left the DMABUF section of the P2PDMA documentation pointing at move_notify(), a symbol that no longer exists. Readers grepping for it find nothing, and this is the only place in Documentation/ describing the revocation requirement. Name the current function and record that importers which cannot unmap within bounded time have to be rejected at attach time, which is what makes the synchronous unmap on remove() achievable. Fixes: 95308225e5ba ("dma-buf: Rename dma_buf_move_notify() to dma_buf_invalidate_mappings()") Signed-off-by: Leon Romanovsky Signed-off-by: Bjorn Helgaas Reviewed-by: Christian König Reviewed-by: Logan Gunthorpe Link: https://patch.msgid.link/20260830-doc-p2p-move-v1-1-61a388620588@nvidia.com --- Documentation/driver-api/pci/p2pdma.rst | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Documentation/driver-api/pci/p2pdma.rst b/Documentation/driver-api/pci/p2pdma.rst index d3f406cca694..63cff9e4d2c9 100644 --- a/Documentation/driver-api/pci/p2pdma.rst +++ b/Documentation/driver-api/pci/p2pdma.rst @@ -167,9 +167,11 @@ In this case the initiator and target pci_devices are known and the P2P subsyste is used to determine the mapping type. The phys_addr_t-based DMA API is used to establish the dma_addr_t. -Lifecycle is controlled by DMABUF move_notify(). When the exporting driver wants +Lifecycle is controlled by DMABUF revocation. When the exporting driver wants to remove() it must deliver an invalidation shutdown to all DMABUF importing -drivers through move_notify() and synchronously DMA unmap all the MMIO. +drivers through dma_buf_invalidate_mappings() and synchronously DMA unmap all +the MMIO. Importers unable to complete that unmap within bounded time have to +be rejected when they attach, which dma_buf_attach_revocable() checks for. No importing driver can continue to have a DMA map to the MMIO after the exporting driver has destroyed its p2p_provider. From 7c4d6505a1f8a1ebe8019b7f988ed494b696653b Mon Sep 17 00:00:00 2001 From: Leon Romanovsky Date: Sun, 30 Aug 2026 14:16:19 +0300 Subject: [PATCH 077/139] PCI/P2PDMA: Do not tear down the allocate attribute on registration failure pci_p2pdma_add_resource() installs pci_p2pdma_unmap_mappings() as a devres action with the devres allocated p2p_pgmap as its data, and only then adds the range to the pool: error = devm_add_action_or_reset(&pdev->dev, pci_p2pdma_unmap_mappings, p2p_pgmap); if (error) goto pages_free; p2pdma = rcu_dereference_protected(pdev->p2pdma, 1); error = gen_pool_add_owner(p2pdma->pool, ...); if (error) goto pages_free; The action removes the allocate attribute for the whole device, which tears down existing userspace mappings of every BAR already registered on it. Both failures here get that wrong, in opposite ways. devm_add_action_or_reset() runs the action when it cannot allocate its devres node, so an -ENOMEM while registering a second BAR unmaps the first one. Use devm_add_action() and let the error path unwind only what this call created. gen_pool_add_owner() allocates a chunk and can also fail with -ENOMEM. There the action is registered, and the error path frees p2p_pgmap with devm_kfree() while leaving the action pointing at it. On unbind devres runs the action and pci_p2pdma_unmap_mappings() dereferences p2p_pgmap->mem->owner->kobj, which is freed memory. Give that failure its own label and drop the action with devm_remove_action(), which removes it without running it. Fixes: 7e9c7ef83d78 ("PCI/P2PDMA: Allow userspace VMA allocations through sysfs") Fixes: f58ef9d1d135 ("PCI/P2PDMA: Separate the mmap() support from the core logic") Signed-off-by: Leon Romanovsky Signed-off-by: Bjorn Helgaas Tested-by: Tushar Dave Reviewed-by: Logan Gunthorpe Reviewed-by: Jason Gunthorpe Link: https://patch.msgid.link/20260830-batch-p2p-fixes-v1-1-5044e8dfbe2e@nvidia.com --- drivers/pci/p2pdma.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/drivers/pci/p2pdma.c b/drivers/pci/p2pdma.c index 5520d79da749..4a4857f0ed91 100644 --- a/drivers/pci/p2pdma.c +++ b/drivers/pci/p2pdma.c @@ -440,8 +440,8 @@ int pci_p2pdma_add_resource(struct pci_dev *pdev, int bar, size_t size, goto pgmap_free; } - error = devm_add_action_or_reset(&pdev->dev, pci_p2pdma_unmap_mappings, - p2p_pgmap); + error = devm_add_action(&pdev->dev, pci_p2pdma_unmap_mappings, + p2p_pgmap); if (error) goto pages_free; @@ -451,13 +451,15 @@ int pci_p2pdma_add_resource(struct pci_dev *pdev, int bar, size_t size, range_len(&pgmap->range), dev_to_node(&pdev->dev), &pgmap->ref); if (error) - goto pages_free; + goto mappings_remove; pci_info(pdev, "added peer-to-peer DMA memory %#llx-%#llx\n", pgmap->range.start, pgmap->range.end); return 0; +mappings_remove: + devm_remove_action(&pdev->dev, pci_p2pdma_unmap_mappings, p2p_pgmap); pages_free: devm_memunmap_pages(&pdev->dev, pgmap); pgmap_free: From 1b82c6657e957f598b6e19e045e0bbd5cb370083 Mon Sep 17 00:00:00 2001 From: Leon Romanovsky Date: Sun, 30 Aug 2026 14:16:20 +0300 Subject: [PATCH 078/139] PCI/P2PDMA: Wait for RCU readers before freeing state pci_p2pmem_find_many() scans all PCI devices without locking or protection against driver unbind, including devices with poolless P2PDMA state. pci_has_p2pmem() may observe pdev->p2pdma just before driver unbind clears it, while pci_p2pdma_release() skips the grace period when no pool is present. This allows devres to free the object while it is still in use. Clear the pointer with RCU_INIT_POINTER() and always wait for pre-existing RCU readers before returning. The same grace period continues to protect gen_pool users for pool-backed providers. Fixes: 372d6d1b8ae3 ("PCI/P2PDMA: Refactor to separate core P2P functionality from memory allocation") Signed-off-by: Leon Romanovsky Signed-off-by: Bjorn Helgaas Tested-by: Tushar Dave Reviewed-by: Logan Gunthorpe Reviewed-by: Jason Gunthorpe Cc: Alex Williamson Cc: Matt Evans Link: https://patch.msgid.link/20260830-batch-p2p-fixes-v1-2-5044e8dfbe2e@nvidia.com --- drivers/pci/p2pdma.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/drivers/pci/p2pdma.c b/drivers/pci/p2pdma.c index 4a4857f0ed91..3216023ce92f 100644 --- a/drivers/pci/p2pdma.c +++ b/drivers/pci/p2pdma.c @@ -236,9 +236,8 @@ static void pci_p2pdma_release(void *data) return; /* Flush and disable pci_alloc_p2p_mem() */ - pdev->p2pdma = NULL; - if (p2pdma->pool) - synchronize_rcu(); + RCU_INIT_POINTER(pdev->p2pdma, NULL); + synchronize_rcu(); xa_destroy(&p2pdma->map_types); if (!p2pdma->pool) From ae57c51f0f4af3fff22171b01a92261f708eea87 Mon Sep 17 00:00:00 2001 From: Leon Romanovsky Date: Sun, 30 Aug 2026 14:16:21 +0300 Subject: [PATCH 079/139] PCI/P2PDMA: Restrict the p2pmem search to pool-backed providers pci_p2pmem_find_many() exists to pick a provider that the caller will then allocate from with pci_alloc_p2pmem(), which goes straight to the gen_pool: ret = (void *)gen_pool_alloc_owner(p2pdma->pool, size, (void **) &ref); pci_has_p2pmem() does not ask for that pool, only for the published flag. The two used to be equivalent, because a provider could only exist by way of pci_p2pdma_add_resource(), which always creates the pool. pcim_p2pdma_init() broke that. It registers a provider for the DMABUF path and never creates a pool, so pdev->p2pdma is set while p2pdma->pool stays NULL. Nothing publishes such a provider today, so the search cannot return one yet, but the flag alone no longer says what the caller needs. Ask for the pool as well, so the search covers the providers its result is used for. A later patch documents the pdev->p2pdma lifetime and RCU rules. Signed-off-by: Leon Romanovsky Signed-off-by: Bjorn Helgaas Tested-by: Tushar Dave Reviewed-by: Logan Gunthorpe Reviewed-by: Jason Gunthorpe Link: https://patch.msgid.link/20260830-batch-p2p-fixes-v1-3-5044e8dfbe2e@nvidia.com --- drivers/pci/p2pdma.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/drivers/pci/p2pdma.c b/drivers/pci/p2pdma.c index 3216023ce92f..feba32f43472 100644 --- a/drivers/pci/p2pdma.c +++ b/drivers/pci/p2pdma.c @@ -880,7 +880,13 @@ static bool pci_has_p2pmem(struct pci_dev *pdev) rcu_read_lock(); p2pdma = rcu_dereference(pdev->p2pdma); - res = p2pdma && p2pdma->p2pmem_published; + + /* + * The callers hand the result to pci_alloc_p2pmem(), so only a + * provider backed by a pool is of any use here. pcim_p2pdma_init() + * creates providers without one. + */ + res = p2pdma && p2pdma->pool && p2pdma->p2pmem_published; rcu_read_unlock(); return res; From fa291d6429ebc1efb7fab011f643b842ae5f23f1 Mon Sep 17 00:00:00 2001 From: Leon Romanovsky Date: Sun, 30 Aug 2026 14:16:22 +0300 Subject: [PATCH 080/139] PCI/P2PDMA: Safely terminate ACS redirect lists seq_buf marks an overflow by setting len to size + 1. The ACS diagnostic path unconditionally writes a terminator to buffer[len - 1], so a path with enough ACS ports to fill the 128-byte buffer writes one byte beyond the buffer when verbose diagnostics are requested. Use seq_buf_str() to terminate truncated output safely and remove the final semicolon only when the buffer did not overflow. Fixes: 52916982af48 ("PCI/P2PDMA: Support peer-to-peer memory") Signed-off-by: Leon Romanovsky Signed-off-by: Bjorn Helgaas Tested-by: Tushar Dave Reviewed-by: Logan Gunthorpe Reviewed-by: Jason Gunthorpe Link: https://patch.msgid.link/20260830-batch-p2p-fixes-v1-4-5044e8dfbe2e@nvidia.com --- drivers/pci/p2pdma.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/pci/p2pdma.c b/drivers/pci/p2pdma.c index feba32f43472..362ef7810fdb 100644 --- a/drivers/pci/p2pdma.c +++ b/drivers/pci/p2pdma.c @@ -784,11 +784,13 @@ calc_map_type_and_dist(struct pci_dev *provider, struct pci_dev *client, } if (verbose) { - acs_list.buffer[acs_list.len-1] = 0; /* drop final semicolon */ + /* Drop the final semicolon; the list is not empty here */ + if (!seq_buf_has_overflowed(&acs_list)) + acs_list.buffer[acs_list.len - 1] = '\0'; pci_warn(client, "ACS redirect is set between the client and provider (%s)\n", pci_name(provider)); pci_warn(client, "to disable ACS redirect for this path, add the kernel parameter: pci=disable_acs_redir=%s\n", - acs_list.buffer); + seq_buf_str(&acs_list)); } acs_redirects = true; From 36038f0e8377ef316b723d0d85b1510bc64069ab Mon Sep 17 00:00:00 2001 From: Leon Romanovsky Date: Sun, 30 Aug 2026 14:16:23 +0300 Subject: [PATCH 081/139] PCI/P2PDMA: Gate the host bridge whitelist warning on verbose calc_map_type_and_dist() prints every other diagnostic under its verbose argument, but reaches the "Host bridge not in P2PDMA whitelist" warning through host_bridge_whitelist(), which it hands acs_redirects instead. A caller that asked for a silent answer still gets the warning whenever any port on the path has an ACS redirect bit set, the CPU is not whitelisted by cpu_supports_p2pdma(), and the host bridge is not in pci_p2pdma_whitelist[]. pci_p2pmem_find_many() is such a caller. It sweeps every device with published p2pmem and asks for the distance to each client with verbose=false, and pci_p2pdma_distance_many() recomputes rather than consulting the map_types cache, so the warning repeats on every sweep. The argument was never meant to say "ACS redirects were found". When commit cf201bfe8cdc ("PCI/P2PDMA: Warn if host bridge not in whitelist") added it, acs_redirects was a bool pointer that the quiet entry point passed as NULL: if (verbose) map = calc_map_type_and_dist_warn(provider, pci_client, &distance); else map = calc_map_type_and_dist(provider, pci_client, &distance, NULL, NULL); so the argument was true on exactly the path that commit describes. Folding the two entry points into one verbose flag turned the pointer into a value and left the call site alone, silently narrowing the warning to paths that carry an ACS redirect. Pass verbose. This also restores the warning for a verbose caller that takes the host bridge route with no ACS redirect on the path, which until now was told it could not use peer-to-peer DMA without being told which vendor and device would have to be added to the whitelist. Fixes: d1b8dc09dd71 ("PCI/P2PDMA: Simplify distance calculation") Signed-off-by: Leon Romanovsky Signed-off-by: Bjorn Helgaas Tested-by: Tushar Dave Reviewed-by: Jason Gunthorpe Reviewed-by: Logan Gunthorpe Link: https://patch.msgid.link/20260830-batch-p2p-fixes-v1-5-5044e8dfbe2e@nvidia.com --- drivers/pci/p2pdma.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/drivers/pci/p2pdma.c b/drivers/pci/p2pdma.c index 362ef7810fdb..45260423dfb0 100644 --- a/drivers/pci/p2pdma.c +++ b/drivers/pci/p2pdma.c @@ -721,7 +721,6 @@ calc_map_type_and_dist(struct pci_dev *provider, struct pci_dev *client, { enum pci_p2pdma_map_type map_type = PCI_P2PDMA_MAP_THRU_HOST_BRIDGE; struct pci_dev *a = provider, *b = client, *bb; - bool acs_redirects = false; struct pci_p2pdma *p2pdma; struct seq_buf acs_list; int acs_cnt = 0; @@ -792,11 +791,10 @@ calc_map_type_and_dist(struct pci_dev *provider, struct pci_dev *client, pci_warn(client, "to disable ACS redirect for this path, add the kernel parameter: pci=disable_acs_redir=%s\n", seq_buf_str(&acs_list)); } - acs_redirects = true; map_through_host_bridge: if (!cpu_supports_p2pdma() && - !host_bridge_whitelist(provider, client, acs_redirects)) { + !host_bridge_whitelist(provider, client, verbose)) { if (verbose) pci_warn(client, "cannot be used for peer-to-peer DMA as the client and provider (%s) do not share an upstream bridge or whitelisted host bridge\n", pci_name(provider)); From 8e923e51892c7da91687dd5b8081ddb1f0fb395a Mon Sep 17 00:00:00 2001 From: Imre Kaloz Date: Sun, 27 Sep 2026 12:56:09 +0200 Subject: [PATCH 082/139] PCI: Avoid D3 on PLX PEX 8532/8533/8548 downstream ports Runtime PM suspends an idle downstream port of a PLX/Broadcom PEX 8532/8533/8548 switch to D3hot, but that takes the whole switch down with it: any device behind another port of the same switch then faults with a fatal bus error on its next access, e.g. mptsas probe on a Sun Ultra 45. This surfaced on sparc64 once commit a5fb3ff63287 ("PCI: Allow PCI bridges to go to D3Hot on all non-x86") let bridges enter D3hot. Keep these downstream ports out of D3 by setting PCI_DEV_FLAGS_NO_D3. Fixes: a5fb3ff63287 ("PCI: Allow PCI bridges to go to D3Hot on all non-x86") Signed-off-by: Imre Kaloz Signed-off-by: Bjorn Helgaas Tested-by: Magnus Lindholm Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20260927105609.8813-1-kaloz@kernel.org --- drivers/pci/quirks.c | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/drivers/pci/quirks.c b/drivers/pci/quirks.c index de9bbccda21f..a3c9250182a6 100644 --- a/drivers/pci/quirks.c +++ b/drivers/pci/quirks.c @@ -2381,6 +2381,24 @@ DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_PLX, PCI_DEVICE_ID_PLX_9050, DECLARE_PCI_FIXUP_HEADER(0x1402, 0x2000, quirk_plx_pci9050); DECLARE_PCI_FIXUP_HEADER(0x1402, 0x2600, quirk_plx_pci9050); +/* + * An idle downstream port of a PLX/Broadcom PEX 8532/8533/8548 switch + * autosuspends into D3hot, and that takes the whole switch down with + * it -- any device behind another port of the same switch then sees a + * fatal, non-recoverable bus fault on its next access. Keep these + * ports out of D3 entirely. + */ +static void quirk_plx_pex_no_d3(struct pci_dev *dev) +{ + if (pci_pcie_type(dev) == PCI_EXP_TYPE_DOWNSTREAM) { + dev->dev_flags |= PCI_DEV_FLAGS_NO_D3; + pci_info(dev, "avoiding D3 on this downstream port\n"); + } +} +DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_PLX, 0x8532, quirk_plx_pex_no_d3); +DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_PLX, 0x8533, quirk_plx_pex_no_d3); +DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_PLX, 0x8548, quirk_plx_pex_no_d3); + static void quirk_netmos(struct pci_dev *dev) { unsigned int num_parallel = (dev->subsystem_device & 0xf0) >> 4; From 7307899e381cb0c16245793a4adde7491d73a2b3 Mon Sep 17 00:00:00 2001 From: Rickey Bartlett Date: Mon, 24 Aug 2026 21:32:20 -0700 Subject: [PATCH 083/139] PCI: vmd: Flush DMA writes before handling MSI on Meteor Lake MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Per the Intel Core Ultra Processor Specification Update (ID 792254 of 9/1/2026), the Meteor Lake VMD (8086:7d0b) is affected by an MSI erratum: MTL016: MSI from VMD-Owned Device May Pass Memory Write Problem When the storage subsystem is configured to operate in RAID 0 or 1 mode, a Message Signaled Interrupt (MSI) from an Intel® Volume Management Device (Intel® VMD) owned device may interrupt a core before a previous write from the device is completed. Workaround None identified. The VMD MSI interrupt-handler should initially perform a dummy register read to the MSI initiator device prior to any writes to ensure proper PCIe ordering. The VMD can signal its MSI before the posted writes carrying a child device's DMA data have landed in memory. vmd_irq() then demuxes to the child handler while the child's completion queue is not yet coherent, so the handler observes no completion and returns without consuming it. The I/O is only recovered when the block layer timeout expires and polls the queue: nvme nvme0: I/O tag 253 (50fd) QID 1 timeout, completion polled The practical effect is therefore not a lost I/O but a 30 second stall of the entire storage stack, repeated under any sustained read load. This was originally diagnosed and fixed by Kai-Heng Feng in September 2024 [1]. That patch used udelay(4); Keith Busch objected that the delay is merely a side effect of the read, and that flushing the pending device-to-host writes is what the erratum actually requires. Kai-Heng agreed to respin with a dummy register read. The thread then stalled on an open question from Manivannan Sadhasivam [2]: whether the read must target the child device (the "MSI initiator" named by the erratum) rather than the VMD, and whether the workaround belongs in the NVMe driver instead. No revision followed, and the erratum has remained unmitigated in mainline since. This implements the flush read Keith asked for, and answers the open question empirically: the read MUST complete at the initiating child device. A read of the VMD's own config BAR was tried first and does not help - it terminates at the VMD and never traverses the downstream link, so it does not order against the child's posted writes (measured: timeout rate unchanged). Reading the initiator's config space does order correctly: per PCIe ordering rules the read completion cannot pass the device's earlier posted writes, so returning from the read guarantees the completion queue entry is visible. The initiator's config address is captured per-IRQ at MSI allocation time from the requesting device, so the hot path adds one config read only on affected parts, and only for vectors owned by a child device. The read is serialized with cfg_lock like all other VMD config access. No NVMe driver change is needed. Measured on a Dell Pro Max 14 MC14250 (Meteor Lake, VMD 8086:7d0b, KIOXIA BG6 512GB, 7.0.0-30-generic). The drive is healthy (46C, 0 media errors, 2% used) and ASPM is disabled on the link with all L1 substates off, so neither ASPM nor APST is involved. Dropping caches and reading 3000 shared libraries, measuring /proc/pressure/io "full" (every task on the system blocked on I/O), with nvme_core.io_timeout=5: unpatched: round 1 wall 37.4s full I/O stall 32.6s timeouts 0 round 2 wall 30.6s full I/O stall 28.7s timeouts 1 round 3 wall 30.3s full I/O stall 27.4s timeouts 1 VMD-BAR read: round 1 wall 44.8s full I/O stall 38.6s timeouts 8 (insufficient) round 2 wall 234.8s full I/O stall 207.9s timeouts 37 round 3 wall 3.6s full I/O stall 0.8s timeouts 0 this patch: round 1 wall 3.6s full I/O stall 1.3s timeouts 0 round 2 wall 3.6s full I/O stall 1.3s timeouts 0 round 3 wall 3.6s full I/O stall 1.4s timeouts 0 Before any workaround, 208 seconds of total-system I/O stall accumulated in the first 13 minutes of uptime, roughly 27% of wall clock. Userspace experiences this as GUI applications taking 30-60+ seconds to start while throughput between stalls looks entirely normal (1.9 GB/s QD1) - which is what makes the fault easy to misattribute to ASPM or to the drive. [1] https://lore.kernel.org/20240903025544.286223-1-kai.heng.feng@canonical.com/ [2] https://lore.kernel.org/20240913111142.4cgrmirofhhgrbqm@thinkpad/ Reported-by: Kai-Heng Feng Suggested-by: Keith Busch Assisted-by: LLM Signed-off-by: Rickey Bartlett Signed-off-by: Manivannan Sadhasivam [bhelgaas: include erratum source and text, update URLs, cc: stable] Signed-off-by: Bjorn Helgaas Tested-by: Varsh Satti Acked-by: Keith Busch Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20260825043220.9047-1-subtexel@gmail.com --- drivers/pci/controller/vmd.c | 52 ++++++++++++++++++++++++++++++++++-- 1 file changed, 50 insertions(+), 2 deletions(-) diff --git a/drivers/pci/controller/vmd.c b/drivers/pci/controller/vmd.c index 241023ecf677..e45ef8cb16e4 100644 --- a/drivers/pci/controller/vmd.c +++ b/drivers/pci/controller/vmd.c @@ -92,6 +92,22 @@ enum vmd_features { * referred to as MEMBAR2 or MSI-X BAR. */ VMD_FEAT_USE_BIOS_INFO = (1 << 6), + + /* + * Meteor Lake VMD (device ID 0x7d0b) is affected by erratum MTL016: + * the VMD may signal its MSI before the posted writes that carry the + * child device's DMA data have landed in memory. The demuxed handler + * then runs against a not-yet-coherent completion queue and misses the + * completion entirely, so the I/O is only recovered when the block + * layer timeout fires and polls the queue ("timeout, completion + * polled"). Intel's documented workaround is to issue a dummy read + * to the MSI initiator (the child device) before handling the + * interrupt: the read completion cannot pass the device's earlier + * posted writes, so it pulls them into memory per PCIe ordering + * rules. A read that terminates at the VMD itself does not order + * against the child's writes and is not sufficient (measured). + */ + VMD_FEAT_INTERRUPT_QUIRK = (1 << 7), }; #define VMD_BIOS_PM_QUIRK_LTR 0x1003 /* 3145728 ns */ @@ -114,6 +130,9 @@ static DEFINE_RAW_SPINLOCK(list_lock); * @irq: back pointer to parent. * @enabled: true if driver enabled IRQ * @virq: the virtual IRQ value provided to the requesting driver. + * @flush_addr: config space address of the initiating device, read before + * demuxing to flush its posted writes (MTL016); NULL if the + * VMD is not affected. * * Every MSI/MSI-X IRQ requested for a device in a VMD domain will be mapped to * a VMD IRQ using this structure. @@ -123,8 +142,14 @@ struct vmd_irq { struct vmd_irq_list *irq; bool enabled; unsigned int virq; + void __iomem *flush_addr; }; +struct vmd_dev; + +static void __iomem *vmd_cfg_addr(struct vmd_dev *vmd, struct pci_bus *bus, + unsigned int devfn, int reg, int len); + /** * struct vmd_irq_list - list of driver requested IRQs mapping to a VMD vector * @irq_list: the list of irq's the VMD one demuxes to. @@ -132,12 +157,15 @@ struct vmd_irq { * @count: number of child IRQs assigned to this vector; used to track * sharing. * @virq: The underlying VMD Linux interrupt number + * @vmd: back pointer to the owning VMD device; serializes the MTL016 + * flush read against other config space access. */ struct vmd_irq_list { struct list_head irq_list; struct srcu_struct srcu; unsigned int count; unsigned int virq; + struct vmd_dev *vmd; }; struct vmd_dev { @@ -295,6 +323,13 @@ static int vmd_msi_alloc(struct irq_domain *domain, unsigned int virq, INIT_LIST_HEAD(&vmdirq->node); vmdirq->irq = vmd_next_irq(vmd, desc); vmdirq->virq = virq + i; + if (vmd->features & VMD_FEAT_INTERRUPT_QUIRK) { + struct pci_dev *pdev = msi_desc_to_pci_dev(desc); + + vmdirq->flush_addr = vmd_cfg_addr(vmd, pdev->bus, + pdev->devfn, + PCI_VENDOR_ID, 2); + } irq_domain_set_info(domain, virq + i, vmdirq->irq->virq, &vmd_msi_controller, vmdirq, @@ -756,8 +791,20 @@ static irqreturn_t vmd_irq(int irq, void *data) int idx; idx = srcu_read_lock(&irqs->srcu); - list_for_each_entry_rcu(vmdirq, &irqs->irq_list, node) + list_for_each_entry_rcu(vmdirq, &irqs->irq_list, node) { + /* + * MTL016: the MSI may have outrun the initiating device's + * posted writes (e.g. its NVMe completion entry). A read + * that completes at the initiator flushes them, so the + * demuxed handler observes a coherent completion queue. + * The value is discarded; only the ordering matters. + */ + if (vmdirq->flush_addr) { + guard(raw_spinlock)(&irqs->vmd->cfg_lock); + readw(vmdirq->flush_addr); + } generic_handle_irq(vmdirq->virq); + } srcu_read_unlock(&irqs->srcu, idx); return IRQ_HANDLED; @@ -788,6 +835,7 @@ static int vmd_alloc_irqs(struct vmd_dev *vmd) return err; INIT_LIST_HEAD(&vmd->irqs[i].irq_list); + vmd->irqs[i].vmd = vmd; vmd->irqs[i].virq = pci_irq_vector(dev, i); err = devm_request_irq(&dev->dev, vmd->irqs[i].virq, vmd_irq, IRQF_NO_THREAD, @@ -1250,7 +1298,7 @@ static const struct pci_device_id vmd_ids[] = { {PCI_VDEVICE(INTEL, 0xa77f), .driver_data = VMD_FEATS_CLIENT,}, {PCI_VDEVICE(INTEL, 0x7d0b), - .driver_data = VMD_FEATS_CLIENT,}, + .driver_data = VMD_FEATS_CLIENT | VMD_FEAT_INTERRUPT_QUIRK,}, {PCI_VDEVICE(INTEL, 0xad0b), .driver_data = VMD_FEATS_CLIENT,}, {PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_VMD_9A0B), From 2c88c2dc18528489b403fe95741f5731e292f7ba Mon Sep 17 00:00:00 2001 From: Vladimir Nedoshivin Date: Fri, 11 Sep 2026 00:05:39 +0400 Subject: [PATCH 084/139] PCI: vmd: Flush DMA writes before handling MSI on Arrow Lake MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Per the Intel Core Ultra Processors (Series 2) Specification Update (ID 834774 of 9/1/2026), the Arrow Lake VMD device 0xad0b is affected by an MSI erratum: ARL004: MSI from VMD-Owned Device May Pass Memory Write Problem When the storage subsystem is configured to operate in RAID 0 or 1 mode, a Message Signaled Interrupt (MSI) from an Intel® Volume Management Device (Intel® VMD) owned device may interrupt a core before a previous write from the device is completed. Workaround None identified. The VMD MSI interrupt-handler should initially perform a dummy register read to the MSI initiator device prior to any writes to ensure proper PCIe ordering. The existing VMD_FEAT_INTERRUPT_QUIRK implements Intel's documented workaround by issuing a configuration-space read to the MSI initiator before dispatching the interrupt. Enable the existing quirk for 0xad0b as well as 0x7d0b and make the comments describe both MTL016 and ARL004. Tested on an MSI Vector 17 HX AI with Intel VMD 8086:ad0b and an NVMe device behind VMD. With the quirk enabled, the NVMe completion stalls observed without the workaround are no longer reproduced. Link: https://bugs.launchpad.net/bugs/2166325 Signed-off-by: Vladimir Nedoshivin [bhelgaas: include erratum source and text, cc: stable] Signed-off-by: Bjorn Helgaas Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20260910200539.10862-1-gvozd188@mail.ru --- drivers/pci/controller/vmd.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/drivers/pci/controller/vmd.c b/drivers/pci/controller/vmd.c index e45ef8cb16e4..9d97a832785e 100644 --- a/drivers/pci/controller/vmd.c +++ b/drivers/pci/controller/vmd.c @@ -94,7 +94,8 @@ enum vmd_features { VMD_FEAT_USE_BIOS_INFO = (1 << 6), /* - * Meteor Lake VMD (device ID 0x7d0b) is affected by erratum MTL016: + * Some Intel VMD devices are affected by interrupt ordering errata, + * including MTL016 (0x7d0b) and ARL004 (0xad0b): * the VMD may signal its MSI before the posted writes that carry the * child device's DMA data have landed in memory. The demuxed handler * then runs against a not-yet-coherent completion queue and misses the @@ -131,7 +132,7 @@ static DEFINE_RAW_SPINLOCK(list_lock); * @enabled: true if driver enabled IRQ * @virq: the virtual IRQ value provided to the requesting driver. * @flush_addr: config space address of the initiating device, read before - * demuxing to flush its posted writes (MTL016); NULL if the + * demuxing to flush its posted writes; NULL if the * VMD is not affected. * * Every MSI/MSI-X IRQ requested for a device in a VMD domain will be mapped to @@ -1300,7 +1301,7 @@ static const struct pci_device_id vmd_ids[] = { {PCI_VDEVICE(INTEL, 0x7d0b), .driver_data = VMD_FEATS_CLIENT | VMD_FEAT_INTERRUPT_QUIRK,}, {PCI_VDEVICE(INTEL, 0xad0b), - .driver_data = VMD_FEATS_CLIENT,}, + .driver_data = VMD_FEATS_CLIENT | VMD_FEAT_INTERRUPT_QUIRK,}, {PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_VMD_9A0B), .driver_data = VMD_FEATS_CLIENT,}, {PCI_VDEVICE(INTEL, 0xb60b), From 581cf67cb6f66eabc6bcd7f61dd64b2559270507 Mon Sep 17 00:00:00 2001 From: Niklas Cassel Date: Mon, 5 Oct 2026 14:41:55 +0200 Subject: [PATCH 085/139] PCI: Update saved Max Payload Size in pcie_set_mps() pcie_set_mps() changes the Max Payload Size (MPS) in the Device Control register but leaves the copy saved by pci_save_state() alone, so the next pci_restore_state() puts back the old value. The PCI core does change the MPS of devices whose state has already been saved: pci_configure_mps() reduces a Root Port's MPS when a device with a smaller MPS Supported is hot-added directly below it (commit 9f0e89359775 ("PCI: Match Root Port's MPS to endpoint's MPSS as necessary")), and with "pci=pcie_bus_safe", pcie_bus_configure_settings() can do the same when pciehp calls it after a hot-add. The Root Port's state was saved when it was added and again when portdrv probed it. Since commit 3fc686d550f6 ("PCI/ERR: Add support for resetting the Root Ports in a platform-specific way"), pcibios_reset_secondary_bus() restores that saved state, without saving it first, after host->reset_root_port() has reset the Root Port, e.g. during AER recovery or on Link Down with the qcom and Rockchip DWC drivers. The Root Port then goes back to the old, larger MPS while the device below it is restored to the smaller one, so the Root Port may send TLPs that the device treats as Malformed. Update the saved copy of Device Control when pcie_set_mps() changes the MPS, like commit 909f7bf9b080 ("PCI: Update saved_config_space upon resource assignment") does for BARs. Fixes: 3fc686d550f6 ("PCI/ERR: Add support for resetting the Root Ports in a platform-specific way") Assisted-by: LLM Signed-off-by: Niklas Cassel Signed-off-by: Bjorn Helgaas Reviewed-by: Shawn Lin Reviewed-by: Keith Busch Link: https://patch.msgid.link/20261005124154.2190628-2-cassel@kernel.org --- drivers/pci/pci.c | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c index b2879a6be5f8..5b126cd342ca 100644 --- a/drivers/pci/pci.c +++ b/drivers/pci/pci.c @@ -1736,6 +1736,24 @@ static void pci_restore_pcie_state(struct pci_dev *dev) pcie_capability_write_word(dev, PCI_EXP_SLTCTL2, cap[i++]); } +/* + * Update the saved copy of the Device Control register after changing it, so + * that pci_restore_state() doesn't put back a stale value. Device Control is + * the first register saved by pci_save_pcie_state(). + */ +static void pcie_update_saved_devctl(struct pci_dev *dev, u16 clear, u16 set) +{ + struct pci_cap_saved_state *save_state; + u16 *devctl; + + save_state = pci_find_saved_cap(dev, PCI_CAP_ID_EXP); + if (!save_state) + return; + + devctl = (u16 *)&save_state->cap.data[0]; + *devctl = (*devctl & ~clear) | set; +} + static int pci_save_pcix_state(struct pci_dev *dev) { int pos; @@ -5970,8 +5988,12 @@ int pcie_set_mps(struct pci_dev *dev, int mps) ret = pcie_capability_clear_and_set_word(dev, PCI_EXP_DEVCTL, PCI_EXP_DEVCTL_PAYLOAD, v); + if (ret) + return pcibios_err_to_errno(ret); - return pcibios_err_to_errno(ret); + pcie_update_saved_devctl(dev, PCI_EXP_DEVCTL_PAYLOAD, v); + + return 0; } EXPORT_SYMBOL(pcie_set_mps); From 967a73293ae5f57af68e815942078e110c1f6d08 Mon Sep 17 00:00:00 2001 From: Qiang Yu Date: Wed, 19 Aug 2026 23:36:54 -0700 Subject: [PATCH 086/139] PCI: qcom: Block accesses to downstream devices on link down After a PCIe link goes down, software may still access the BAR (MMIO) space or configuration space of devices behind that link before recovery has run. As the link is down, these accesses never complete, resulting in a storm of Completion Timeout AERs. Use the controller's Outbound blocker to drop these accesses to the PCIe address space as soon as the link-down interrupt fires, so that Completion Timeout AERs are reduced. The blocked range covers the entire address space (base 0x0, all-ones limit), since the Root Port's own DBI/iATU register space remains accessible regardless. The range is programmed once in the host init path, since the range registers are wiped by BCR reset. This leaves only the OUTBOUND_BLOCKER_EN bit to be flipped from the link-down IRQ handler, so the blocker can be armed with a single fast register write for immediate effect. The subsequent Root Port reset re-initialises the controller, which clears the enable bit and re-programs the range for the fresh link. Some IP revisions implement the Outbound blocker registers but do not wire up the "global" interrupt used to deliver the link-down event that arms the blocker, so there is no way to enable the blocker on those platforms. Restrict blocker initialization to the post_init hooks of the IP revisions that do have a global IRQ wired up (qcom_pcie_post_init_2_3_3(), qcom_pcie_post_init_2_7_0() and qcom_pcie_post_init_2_9_0()), instead of unconditionally programming it from the common host_init path. The link-down IRQ thread's blocker-enable write to PARF_SYS_CTRL can race with a Root Port reset triggered independently through AER, which reprograms PARF_SYS_CTRL as part of reinitializing the controller. Serialize these with a per-controller mutex. Signed-off-by: Qiang Yu [mani: renamed hw_lock to reset_lock, ECAM blocker to Outboound blocker, used guard(mutex)] Signed-off-by: Manivannan Sadhasivam Tested-by: Konrad Dybcio # X1E80100 CRD + ASUS PA27AC Link: https://patch.msgid.link/20260819-ecam_blocker-v2-1-e7a8fdc1c5cb@oss.qualcomm.com --- drivers/pci/controller/dwc/pcie-qcom.c | 68 ++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) diff --git a/drivers/pci/controller/dwc/pcie-qcom.c b/drivers/pci/controller/dwc/pcie-qcom.c index 89a476c37098..547c2058140f 100644 --- a/drivers/pci/controller/dwc/pcie-qcom.c +++ b/drivers/pci/controller/dwc/pcie-qcom.c @@ -20,6 +20,7 @@ #include #include #include +#include #include #include #include @@ -73,6 +74,23 @@ #define PARF_BDF_TO_SID_TABLE_N 0x2000 #define PARF_BDF_TO_SID_CFG 0x2c00 +/* + * Outbound blocker range registers. The blocked range has a write pair + * (WR_BASE/WR_LIMIT) and a read pair (RD_BASE/RD_LIMIT); each address is + * split into a low (32-bit) and a HI (upper 32-bit) register. + */ +#define PARF_BLOCK_SLV_AXI_WR_BASE 0x360 +#define PARF_BLOCK_SLV_AXI_WR_BASE_HI 0x364 +#define PARF_BLOCK_SLV_AXI_WR_LIMIT 0x368 +#define PARF_BLOCK_SLV_AXI_WR_LIMIT_HI 0x36c +#define PARF_BLOCK_SLV_AXI_RD_BASE 0x370 +#define PARF_BLOCK_SLV_AXI_RD_BASE_HI 0x374 +#define PARF_BLOCK_SLV_AXI_RD_LIMIT 0x378 +#define PARF_BLOCK_SLV_AXI_RD_LIMIT_HI 0x37c + +#define PARF_ECAM_BASE 0x380 +#define PARF_ECAM_BASE_HI 0x384 + /* ELBI registers */ #define ELBI_SYS_CTRL 0x04 #define ELBI_SYS_STTS 0x08 @@ -90,6 +108,7 @@ /* PARF_SYS_CTRL register fields */ #define MAC_PHY_POWERDOWN_IN_P2_D_MUX_EN BIT(29) +#define OUTBOUND_BLOCKER_EN BIT(26) #define MST_WAKEUP_EN BIT(13) #define SLV_WAKEUP_EN BIT(12) #define MSTR_ACLK_CGC_DIS BIT(10) @@ -308,6 +327,7 @@ struct qcom_pcie { struct gpio_desc *reset; int global_irq; bool use_pm_opp; + struct mutex reset_lock; }; #define to_qcom_pcie(x) dev_get_drvdata((x)->dev) @@ -445,6 +465,25 @@ static void qcom_pcie_configure_dbi_atu_base(struct qcom_pcie *pcie) } } +static void qcom_pcie_init_outbound_blocker(struct qcom_pcie *pcie) +{ + struct dw_pcie *pci = pcie->pci; + + /* ECAM base must match the DBI base address */ + writel(lower_32_bits(pci->dbi_phys_addr), pcie->parf + PARF_ECAM_BASE); + writel(upper_32_bits(pci->dbi_phys_addr), pcie->parf + PARF_ECAM_BASE_HI); + + writel(0, pcie->parf + PARF_BLOCK_SLV_AXI_WR_BASE); + writel(0, pcie->parf + PARF_BLOCK_SLV_AXI_WR_BASE_HI); + writel(U32_MAX, pcie->parf + PARF_BLOCK_SLV_AXI_WR_LIMIT); + writel(U32_MAX, pcie->parf + PARF_BLOCK_SLV_AXI_WR_LIMIT_HI); + + writel(0, pcie->parf + PARF_BLOCK_SLV_AXI_RD_BASE); + writel(0, pcie->parf + PARF_BLOCK_SLV_AXI_RD_BASE_HI); + writel(U32_MAX, pcie->parf + PARF_BLOCK_SLV_AXI_RD_LIMIT); + writel(U32_MAX, pcie->parf + PARF_BLOCK_SLV_AXI_RD_LIMIT_HI); +} + static void qcom_pcie_2_1_0_ltssm_enable(struct qcom_pcie *pcie) { struct dw_pcie *pci = pcie->pci; @@ -990,6 +1029,8 @@ static int qcom_pcie_post_init_2_3_3(struct qcom_pcie *pcie) dw_pcie_dbi_ro_wr_dis(pci); + qcom_pcie_init_outbound_blocker(pcie); + return 0; } @@ -1104,6 +1145,8 @@ static int qcom_pcie_post_init_2_7_0(struct qcom_pcie *pcie) qcom_pcie_set_slot_cap(pcie->pci); + qcom_pcie_init_outbound_blocker(pcie); + return 0; } @@ -1322,6 +1365,8 @@ static int qcom_pcie_post_init_2_9_0(struct qcom_pcie *pcie) for (i = 0; i < 256; i++) writel(0, pcie->parf + PARF_BDF_TO_SID_TABLE_N + (4 * i)); + qcom_pcie_init_outbound_blocker(pcie); + return 0; } @@ -1382,6 +1427,18 @@ static void qcom_pcie_configure_ports(struct qcom_pcie *pcie) dw_pcie_program_t_power_on(pcie->pci, port->l1ss_t_power_on); } +static void qcom_pcie_enable_outbound_blocker(struct qcom_pcie *pcie) +{ + u32 sys_ctrl; + + sys_ctrl = readl(pcie->parf + PARF_SYS_CTRL); + sys_ctrl |= OUTBOUND_BLOCKER_EN; + writel(sys_ctrl, pcie->parf + PARF_SYS_CTRL); + + /* Flush the write so the blocker is enabled before this function returns */ + readl(pcie->parf + PARF_SYS_CTRL); +} + static int qcom_pcie_host_init(struct dw_pcie_rp *pp) { struct dw_pcie *pci = to_dw_pcie_from_pp(pp); @@ -1775,6 +1832,8 @@ static int qcom_pcie_reset_root_port(struct pci_host_bridge *bridge, u32 val; int ret; + guard(mutex)(&pcie->reset_lock); + /* Wait for the pending transactions to be completed */ ret = readl_relaxed_poll_timeout(pcie->parf + PARF_STATUS, val, val & FLUSH_COMPLETED, 10, @@ -1878,6 +1937,11 @@ static irqreturn_t qcom_pcie_global_irq_thread(int irq, void *data) if (test_and_clear_bit(INT_ALL_LINK_DOWN, &status)) { dev_dbg(dev, "Received Link down event\n"); + + mutex_lock(&pcie->reset_lock); + qcom_pcie_enable_outbound_blocker(pcie); + mutex_unlock(&pcie->reset_lock); + for_each_pci_bridge(port, pp->bridge->bus) { if (pci_pcie_type(port) == PCI_EXP_TYPE_ROOT_PORT) pci_host_handle_link_down(port); @@ -2207,6 +2271,10 @@ static int qcom_pcie_probe(struct platform_device *pdev) INIT_LIST_HEAD(&pcie->ports); + ret = devm_mutex_init(dev, &pcie->reset_lock); + if (ret) + goto err_pm_runtime_put; + pci->dev = dev; pci->ops = &dw_pcie_ops; pp = &pci->pp; From 5af84e8928cb2ccc9696d90a0096a43c5bab5913 Mon Sep 17 00:00:00 2001 From: Mrinmay Sarkar Date: Mon, 31 Aug 2026 19:07:02 +0530 Subject: [PATCH 087/139] PCI: qcom: Skip system suspend/resume for firmware-managed PCIe For firmware-managed PCIe controllers, all resources, including clocks, regulators, resets, PHYs and link state, are owned and managed by firmware. The host driver must not attempt to manage these resources during system suspend or resume. Firmware will properly transition the devices/controller into relevant low power modes. Skip the suspend and resume operations for firmware-managed controllers. The associated power domain transitions are handled independently by the genpd framework through its own system suspend/resume callbacks, so no explicit action is required by the PCIe controller driver. Signed-off-by: Mrinmay Sarkar Signed-off-by: Manivannan Sadhasivam Link: https://patch.msgid.link/20260831-skip_suspend-v1-1-ba2d877e22b1@oss.qualcomm.com --- drivers/pci/controller/dwc/pcie-qcom.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/drivers/pci/controller/dwc/pcie-qcom.c b/drivers/pci/controller/dwc/pcie-qcom.c index 547c2058140f..7e38c41a33ce 100644 --- a/drivers/pci/controller/dwc/pcie-qcom.c +++ b/drivers/pci/controller/dwc/pcie-qcom.c @@ -2412,6 +2412,10 @@ static int qcom_pcie_suspend_noirq(struct device *dev) { struct qcom_pcie *pcie; int ret = 0; + const struct qcom_pcie_cfg *pcie_cfg = of_device_get_match_data(dev); + + if (pcie_cfg && pcie_cfg->firmware_managed) + return 0; pcie = dev_get_drvdata(dev); if (!pcie) @@ -2470,6 +2474,10 @@ static int qcom_pcie_resume_noirq(struct device *dev) { struct qcom_pcie *pcie; int ret; + const struct qcom_pcie_cfg *pcie_cfg = of_device_get_match_data(dev); + + if (pcie_cfg && pcie_cfg->firmware_managed) + return 0; pcie = dev_get_drvdata(dev); if (!pcie) From 5121d76cb9dde74662962cd5678a64abeaf3b3e4 Mon Sep 17 00:00:00 2001 From: Manivannan Sadhasivam Date: Mon, 7 Sep 2026 16:33:49 +0200 Subject: [PATCH 088/139] PCI: qcom: Honor IOMMU provider's #iommu-cells in qcom_pcie_config_sid_1_9_0() qcom_pcie_config_sid_1_9_0() reads the "iommu-map" property as an array of fixed four-word {rid-base, phandle, sid, rid-len} entries to program the BDF to SID translation table. But that layout only holds for an IOMMU with '#iommu-cells = <1>'. The PCIe SMMUs on these SoCs use '#iommu-cells = <2>' (SID and mask), so per the pci-iommu binding each entry is really five cells long. This used to work only because the DTs were themselves broken. They described iommu-map with four-cell entries that omitted the SID mask, which of_map_id() tolerated via its of_check_bad_map() fallback, and the four-word parsing coincidentally matched that malformed shape. Since commit ccb2fd725d41 ("of: Respect #{iommu,msi}-cells in maps") the OF core parses such maps correctly, so the device trees were converted to proper five-cell entries, e.g. commit c41749e9554d ("arm64: dts: qcom: sm8250: Fix the PCIe iommu-map entries"). With five-cell entries the fixed four-word stride slips by one cell for each entry after the first, so qcom_pcie_config_sid_1_9_0() reads the endpoint mapping's RID from the preceding entry's length cell and its SID from the phandle cell. As the RID is the hash key, the endpoint's real RID is never programmed into the BDF to SID table. Its DMA then hashes to an unprogrammed slot, gets tagged with SID 0 and the SMMU faults like below on QCS8300: arm-smmu 15200000.iommu: Unhandled context fault: fsr=0x402, iova=0xffa00000, cbfrsynra=0x0, cb=1 To fix this, walk the map with a stride of 3 + '#iommu-cells' of the referenced IOMMU and take the SID from the first specifier cell, which is all the BDF to SID table needs. Validate the layout instead of trusting the array size. Also, preserve the legacy behavior of the old DTs by detecting the same pattern that of_check_bad_map() recognizes and falling back to a stride of four. Fixes: 4c9398822106 ("PCI: qcom: Add support for configuring BDF to SID mapping for SM8250") Reported-by: Loic Poulain Signed-off-by: Manivannan Sadhasivam Tested-by: Loic Poulain Tested-by: Neil Armstrong # on SM8650-HDK Link: https://patch.msgid.link/20260907143349.317495-1-mani@kernel.org --- drivers/pci/controller/dwc/pcie-qcom.c | 90 ++++++++++++++++++-------- 1 file changed, 64 insertions(+), 26 deletions(-) diff --git a/drivers/pci/controller/dwc/pcie-qcom.c b/drivers/pci/controller/dwc/pcie-qcom.c index 7e38c41a33ce..686789ec7ddc 100644 --- a/drivers/pci/controller/dwc/pcie-qcom.c +++ b/drivers/pci/controller/dwc/pcie-qcom.c @@ -1186,50 +1186,90 @@ static void qcom_pcie_deinit_2_7_0(struct qcom_pcie *pcie) static int qcom_pcie_config_sid_1_9_0(struct qcom_pcie *pcie) { - /* iommu map structure */ - struct { - u32 bdf; - u32 phandle; - u32 smmu_sid; - u32 smmu_sid_len; - } *map; void __iomem *bdf_to_sid_base = pcie->parf + PARF_BDF_TO_SID_TABLE_N; struct device *dev = pcie->pci->dev; + struct device_node *iommu_np; u8 qcom_pcie_crc8_table[CRC8_TABLE_SIZE]; - int i, nr_map, size = 0; - u32 smmu_sid_base; + const __be32 *map; + u32 iommu_cells, entry_cells, phandle, smmu_sid_base; + int i, nr_cells, nr_map, size = 0; u32 val; - of_get_property(dev->of_node, "iommu-map", &size); - if (!size) + map = of_get_property(dev->of_node, "iommu-map", &size); + if (!map || !size) return 0; + if (size % sizeof(*map)) { + dev_err(dev, "Malformed iommu-map property\n"); + return -EINVAL; + } + nr_cells = size / sizeof(*map); + + /* + * Each iommu-map entry is: rid-base (1 cell), phandle (1 cell), + * IOMMU specifier (#iommu-cells cells), length (1 cell). Read + * #iommu-cells from the IOMMU provider referenced by the first + * entry to compute the per-entry stride. + */ + phandle = be32_to_cpu(map[1]); + iommu_np = of_find_node_by_phandle(phandle); + if (!iommu_np) { + dev_err(dev, "Failed to find IOMMU node in iommu-map\n"); + return -ENODEV; + } + + if (of_property_read_u32(iommu_np, "#iommu-cells", &iommu_cells)) + iommu_cells = 1; + of_node_put(iommu_np); + + entry_cells = 3 + iommu_cells; + + /* + * Retain backward compatibility with DTs that describe iommu-map + * with 4-cell entries against an IOMMU declaring #iommu-cells = 2, + * matching the fallback in drivers/of/base.c::of_check_bad_map(). + */ + if (iommu_cells == 2 && !(nr_cells % 4)) { + bool legacy = true; + + for (i = 0; i < nr_cells; i += 4) { + if (be32_to_cpu(map[i + 1]) != phandle || + be32_to_cpu(map[i + 3]) != 1) { + legacy = false; + break; + } + } + + if (legacy) { + dev_warn_once(dev, "iommu-map has 1-cell entries with #iommu-cells=2, using 1-cell\n"); + entry_cells = 4; + } + } + + if (nr_cells % entry_cells) { + dev_err(dev, "Malformed iommu-map property\n"); + return -EINVAL; + } + nr_map = nr_cells / entry_cells; + /* Enable BDF to SID translation by disabling bypass mode (default) */ val = readl(pcie->parf + PARF_BDF_TO_SID_CFG); val &= ~BDF_TO_SID_BYPASS; writel(val, pcie->parf + PARF_BDF_TO_SID_CFG); - map = kzalloc(size, GFP_KERNEL); - if (!map) - return -ENOMEM; - - of_property_read_u32_array(dev->of_node, "iommu-map", (u32 *)map, - size / sizeof(u32)); - - nr_map = size / (sizeof(*map)); - crc8_populate_msb(qcom_pcie_crc8_table, QCOM_PCIE_CRC8_POLYNOMIAL); /* Registers need to be zero out first */ memset_io(bdf_to_sid_base, 0, CRC8_TABLE_SIZE * sizeof(u32)); /* Extract the SMMU SID base from the first entry of iommu-map */ - smmu_sid_base = map[0].smmu_sid; + smmu_sid_base = be32_to_cpu(map[2]); /* Look for an available entry to hold the mapping */ for (i = 0; i < nr_map; i++) { - __be16 bdf_be = cpu_to_be16(map[i].bdf); - u32 val; + u32 bdf = be32_to_cpu(map[i * entry_cells]); + u32 sid = be32_to_cpu(map[i * entry_cells + 2]); + __be16 bdf_be = cpu_to_be16(bdf); u8 hash; hash = crc8(qcom_pcie_crc8_table, (u8 *)&bdf_be, sizeof(bdf_be), 0); @@ -1251,12 +1291,10 @@ static int qcom_pcie_config_sid_1_9_0(struct qcom_pcie *pcie) } /* BDF [31:16] | SID [15:8] | NEXT [7:0] */ - val = map[i].bdf << 16 | (map[i].smmu_sid - smmu_sid_base) << 8 | 0; + val = bdf << 16 | (sid - smmu_sid_base) << 8 | 0; writel(val, bdf_to_sid_base + hash * sizeof(u32)); } - kfree(map); - return 0; } From e473e430ec31e2605227eb5e30be3712d6338da1 Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Tue, 28 Apr 2026 15:27:09 +0200 Subject: [PATCH 089/139] PCI: mvebu: Simplify with scoped for each OF child loop Use scoped for-each loop when iterating over device nodes to make code a bit simpler. Signed-off-by: Krzysztof Kozlowski Signed-off-by: Manivannan Sadhasivam Link: https://patch.msgid.link/20260428132708.95998-3-krzysztof.kozlowski@oss.qualcomm.com --- drivers/pci/controller/pci-mvebu.c | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/drivers/pci/controller/pci-mvebu.c b/drivers/pci/controller/pci-mvebu.c index 69a9d77039e8..c41043bf80ba 100644 --- a/drivers/pci/controller/pci-mvebu.c +++ b/drivers/pci/controller/pci-mvebu.c @@ -1451,7 +1451,6 @@ static int mvebu_pcie_probe(struct platform_device *pdev) struct mvebu_pcie *pcie; struct pci_host_bridge *bridge; struct device_node *np = dev->of_node; - struct device_node *child; int num, i, ret; bridge = devm_pci_alloc_host_bridge(dev, sizeof(struct mvebu_pcie)); @@ -1473,16 +1472,14 @@ static int mvebu_pcie_probe(struct platform_device *pdev) return -ENOMEM; i = 0; - for_each_available_child_of_node(np, child) { + for_each_available_child_of_node_scoped(np, child) { struct mvebu_pcie_port *port = &pcie->ports[i]; ret = mvebu_pcie_parse_port(pcie, port, child); - if (ret < 0) { - of_node_put(child); + if (ret < 0) return ret; - } else if (ret == 0) { + else if (ret == 0) continue; - } port->dn = child; i++; @@ -1492,6 +1489,7 @@ static int mvebu_pcie_probe(struct platform_device *pdev) for (i = 0; i < pcie->nports; i++) { struct mvebu_pcie_port *port = &pcie->ports[i]; int irq = port->intx_irq; + struct device_node *child; child = port->dn; if (!child) From 7f173bfa6d42742de4ab647e001232c1165ab28e Mon Sep 17 00:00:00 2001 From: Geraldo Nascimento Date: Tue, 16 Jun 2026 12:25:28 -0300 Subject: [PATCH 090/139] PCI: rockchip-ep: Do not attempt 5.0 GT/s retraining Drop the 5.0 GT/s Link Speed retraining code block from Rockchip PCIe EP driver. The reason is that Shawn Lin from Rockchip has reiterated that there may be danger of "catastrophic failure" in using their PCIe with 5.0 GT/s speeds. While Rockchip has done so informally without issuing a proper errata, and the particulars are thus unknown, this may cause data loss or worse. This change is corroborated by RK3399 official datasheet [1], which states maximum link speed for this platform is 2.5 GT/s. [1] https://opensource.rock-chips.com/images/d/d7/Rockchip_RK3399_Datasheet_V2.1-20200323.pdf Fixes: bd6e61df4b2e ("PCI: rockchip-ep: Improve link training") Reported-by: Dragan Simic Reported-by: Shawn Lin Link: https://lore.kernel.org/all/ffd05070-9879-4468-94e3-b88968b4c21b@rock-chips.com/ Signed-off-by: Geraldo Nascimento Signed-off-by: Manivannan Sadhasivam Signed-off-by: Bjorn Helgaas Reviewed-by: Dragan Simic Reviewed-by: Shawn Lin Cc: stable@vger.kernel.org Link: https://patch.msgid.link/d994f8fb3481bbb8f3972f117334a6e8aea383fb.1781622998.git.geraldogabriel@gmail.com --- drivers/pci/controller/pcie-rockchip-ep.c | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/drivers/pci/controller/pcie-rockchip-ep.c b/drivers/pci/controller/pcie-rockchip-ep.c index 799461335762..9ebc227a1ef8 100644 --- a/drivers/pci/controller/pcie-rockchip-ep.c +++ b/drivers/pci/controller/pcie-rockchip-ep.c @@ -553,19 +553,6 @@ static void rockchip_pcie_ep_link_training(struct work_struct *work) if (ret) goto again; - /* - * Check the current speed: if gen2 speed was requested and we are not - * at gen2 speed yet, retrain again for gen2. - */ - val = rockchip_pcie_read(rockchip, PCIE_CORE_CTRL); - if (!PCIE_LINK_IS_GEN2(val) && rockchip->link_gen == 2) { - /* Enable retrain for gen2 */ - rockchip_pcie_ep_retrain_link(rockchip); - readl_poll_timeout(rockchip->apb_base + PCIE_CORE_CTRL, - val, PCIE_LINK_IS_GEN2(val), 50, - LINK_TRAIN_TIMEOUT); - } - /* Check again that the link is up */ if (!rockchip_pcie_ep_link_up(rockchip)) goto again; From 2a1e6c382fb7567679c07bd6a7060fefc7a5edc9 Mon Sep 17 00:00:00 2001 From: Geraldo Nascimento Date: Tue, 16 Jun 2026 12:25:45 -0300 Subject: [PATCH 091/139] PCI: rockchip-host: Do not attempt 5.0 GT/s retraining Drop the 5.0 GT/s Link Speed retraining from Rockchip PCIe Root Complex Mode Operation, so-called host driver. The reason is that Shawn Lin from Rockchip has reiterated that there may be danger of "catastrophic failure" in using their PCIe with 5.0GT/s speeds. While Rockchip has done so informally without issuing a proper errata, and the particulars are thus unknown, this may cause data loss or worse. This change is corroborated by RK3399 official datasheet [1], which states maximum link speed for this platform is 2.5 GT/s. [1] https://opensource.rock-chips.com/images/d/d7/Rockchip_RK3399_Datasheet_V2.1-20200323.pdf Fixes: e77f847df54c ("PCI: rockchip: Add Rockchip PCIe controller support") Reported-by: Dragan Simic Reported-by: Shawn Lin Link: https://lore.kernel.org/all/ffd05070-9879-4468-94e3-b88968b4c21b@rock-chips.com/ Signed-off-by: Geraldo Nascimento Signed-off-by: Manivannan Sadhasivam Signed-off-by: Bjorn Helgaas Reviewed-by: Dragan Simic Reviewed-by: Shawn Lin Cc: stable@vger.kernel.org Link: https://patch.msgid.link/68f6353e3a2ea4914de36c42b6906e41282adad3.1781622998.git.geraldogabriel@gmail.com --- drivers/pci/controller/pcie-rockchip-host.c | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/drivers/pci/controller/pcie-rockchip-host.c b/drivers/pci/controller/pcie-rockchip-host.c index d203c4876d30..0b52f2e370f4 100644 --- a/drivers/pci/controller/pcie-rockchip-host.c +++ b/drivers/pci/controller/pcie-rockchip-host.c @@ -328,26 +328,6 @@ static int rockchip_pcie_host_init_port(struct rockchip_pcie *rockchip) goto err_power_off_phy; } - if (rockchip->link_gen == 2) { - /* - * Enable retrain for gen2. This should be configured only after - * gen1 finished. - */ - status = rockchip_pcie_read(rockchip, PCIE_RC_CONFIG_CR + PCI_EXP_LNKCTL2); - status &= ~PCI_EXP_LNKCTL2_TLS; - status |= PCI_EXP_LNKCTL2_TLS_5_0GT; - rockchip_pcie_write(rockchip, status, PCIE_RC_CONFIG_CR + PCI_EXP_LNKCTL2); - status = rockchip_pcie_read(rockchip, PCIE_RC_CONFIG_CR + PCI_EXP_LNKCTL); - status |= PCI_EXP_LNKCTL_RL; - rockchip_pcie_write(rockchip, status, PCIE_RC_CONFIG_CR + PCI_EXP_LNKCTL); - - err = readl_poll_timeout(rockchip->apb_base + PCIE_CORE_CTRL, - status, PCIE_LINK_IS_GEN2(status), 20, - 500 * USEC_PER_MSEC); - if (err) - dev_dbg(dev, "PCIe link training gen2 timeout, fall back to gen1!\n"); - } - /* Check the final link width from negotiated lane counter from MGMT */ status = rockchip_pcie_read(rockchip, PCIE_CORE_CTRL); status = 0x1 << ((status & PCIE_CORE_PL_CONF_LANE_MASK) >> From 84d08ff902f1ec01177fef1a21a38934927f5aee Mon Sep 17 00:00:00 2001 From: Geraldo Nascimento Date: Tue, 16 Jun 2026 12:26:10 -0300 Subject: [PATCH 092/139] PCI: rockchip: Drive at 2.5 GT/s, error other speeds Configure the core to be driven at 2.5 GT/s Link Speed and ignore any other speed with a warning. Also drop the 5.0 GT/s Link Speed defines from Rockchip PCIe header. The reason is that Shawn Lin from Rockchip has reiterated that there may be danger of "catastrophic failure" in using their PCIe with 5.0 GT/s speeds. While Rockchip has done so informally without issuing a proper errata, and the particulars are thus unknown, this may cause data loss or worse. This change is corroborated by RK3399 official datasheet [1], which states maximum link speed for this platform is 2.5 GT/s. [1] https://opensource.rock-chips.com/images/d/d7/Rockchip_RK3399_Datasheet_V2.1-20200323.pdf Fixes: 956cd99b35a8 ("PCI: rockchip: Separate common code from RC driver") Reported-by: Dragan Simic Reported-by: Shawn Lin Link: https://lore.kernel.org/all/ffd05070-9879-4468-94e3-b88968b4c21b@rock-chips.com/ Signed-off-by: Geraldo Nascimento Signed-off-by: Manivannan Sadhasivam Signed-off-by: Bjorn Helgaas Reviewed-by: Dragan Simic Reviewed-by: Shawn Lin Cc: stable@vger.kernel.org Link: https://patch.msgid.link/7421df7a7b7778ee99363cccfdfabbfa8aa6ab5e.1781622998.git.geraldogabriel@gmail.com --- drivers/pci/controller/pcie-rockchip.c | 14 ++++++-------- drivers/pci/controller/pcie-rockchip.h | 3 --- 2 files changed, 6 insertions(+), 11 deletions(-) diff --git a/drivers/pci/controller/pcie-rockchip.c b/drivers/pci/controller/pcie-rockchip.c index 8d78ab1b31f0..45cc888ac553 100644 --- a/drivers/pci/controller/pcie-rockchip.c +++ b/drivers/pci/controller/pcie-rockchip.c @@ -66,8 +66,10 @@ int rockchip_pcie_parse_dt(struct rockchip_pcie *rockchip) } rockchip->link_gen = of_pci_get_max_link_speed(node); - if (rockchip->link_gen < 0 || rockchip->link_gen > 2) - rockchip->link_gen = 2; + if (rockchip->link_gen < 0 || rockchip->link_gen >= 2) { + rockchip->link_gen = 1; + dev_warn(dev, "invalid max-link-speed, limited to 2.5 GT/s\n"); + } for (i = 0; i < ROCKCHIP_NUM_PM_RSTS; i++) rockchip->pm_rsts[i].id = rockchip_pci_pm_rsts[i]; @@ -147,12 +149,8 @@ int rockchip_pcie_init_port(struct rockchip_pcie *rockchip) goto err_exit_phy; } - if (rockchip->link_gen == 2) - rockchip_pcie_write(rockchip, PCIE_CLIENT_GEN_SEL_2, - PCIE_CLIENT_CONFIG); - else - rockchip_pcie_write(rockchip, PCIE_CLIENT_GEN_SEL_1, - PCIE_CLIENT_CONFIG); + rockchip_pcie_write(rockchip, PCIE_CLIENT_GEN_SEL_1, + PCIE_CLIENT_CONFIG); regs = PCIE_CLIENT_ARI_ENABLE | PCIE_CLIENT_CONF_LANE_NUM(rockchip->lanes); diff --git a/drivers/pci/controller/pcie-rockchip.h b/drivers/pci/controller/pcie-rockchip.h index 3e82a69b9c00..b5da15601b58 100644 --- a/drivers/pci/controller/pcie-rockchip.h +++ b/drivers/pci/controller/pcie-rockchip.h @@ -42,7 +42,6 @@ #define PCIE_CLIENT_MODE_RC HWORD_SET_BIT(0x0040) #define PCIE_CLIENT_MODE_EP HWORD_CLR_BIT(0x0040) #define PCIE_CLIENT_GEN_SEL_1 HWORD_CLR_BIT(0x0080) -#define PCIE_CLIENT_GEN_SEL_2 HWORD_SET_BIT(0x0080) #define PCIE_CLIENT_LEGACY_INT_CTRL (PCIE_CLIENT_BASE + 0x0c) #define PCIE_CLIENT_INT_IN_ASSERT HWORD_SET_BIT(0x0002) #define PCIE_CLIENT_INT_IN_DEASSERT HWORD_CLR_BIT(0x0002) @@ -197,8 +196,6 @@ (((x) & PCIE_CORE_PL_CONF_LS_MASK) == PCIE_CORE_PL_CONF_LS_READY) #define PCIE_LINK_UP(x) \ (((x) & PCIE_CLIENT_LINK_STATUS_MASK) == PCIE_CLIENT_LINK_STATUS_UP) -#define PCIE_LINK_IS_GEN2(x) \ - (((x) & PCIE_CORE_PL_CONF_SPEED_MASK) == PCIE_CORE_PL_CONF_SPEED_5G) #define RC_REGION_0_ADDR_TRANS_H 0x00000000 #define RC_REGION_0_ADDR_TRANS_L 0x00000000 From 09425f5919473bb20bad8cbc9908e34c79c8b642 Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Tue, 28 Apr 2026 15:27:10 +0200 Subject: [PATCH 093/139] PCI: mvebu: Keep child node refcnt for probe duration Driver in probe() first iterates over child device nodes, storing the OF node pointer but does not take the reference to it for the remaining part of the probe where it is used. The pointer thus could become invalid if children OF nodes are being removed from the Devicetree, e.g. by removing OF overlay, while the probe is being executed. That's a highly theoretical issue, thus it does not look like bug with any effect. Nevertheless, make the code logically correct, so acquire the OF node refcnt for entire lifetime of the device, since the pointer is stored in each port state container structure. Signed-off-by: Krzysztof Kozlowski Signed-off-by: Manivannan Sadhasivam Signed-off-by: Bjorn Helgaas Link: https://patch.msgid.link/20260428132708.95998-4-krzysztof.kozlowski@oss.qualcomm.com --- drivers/pci/controller/pci-mvebu.c | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/drivers/pci/controller/pci-mvebu.c b/drivers/pci/controller/pci-mvebu.c index c41043bf80ba..cce1c1c0da58 100644 --- a/drivers/pci/controller/pci-mvebu.c +++ b/drivers/pci/controller/pci-mvebu.c @@ -1235,6 +1235,11 @@ static void mvebu_pcie_port_clk_put(void *data) clk_put(port->clk); } +static void mvebu_pcie_port_of_node_put(void *data) +{ + of_node_put(data); +} + static int mvebu_pcie_parse_port(struct mvebu_pcie *pcie, struct mvebu_pcie_port *port, struct device_node *child) { @@ -1481,7 +1486,11 @@ static int mvebu_pcie_probe(struct platform_device *pdev) else if (ret == 0) continue; - port->dn = child; + port->dn = of_node_get(child); + ret = devm_add_action_or_reset(dev, mvebu_pcie_port_of_node_put, + child); + if (ret) + return ret; i++; } pcie->nports = i; From 2aed243fe21ec77c5682aa8ee6a5912d94b3a4ff Mon Sep 17 00:00:00 2001 From: Alexander Stein Date: Fri, 19 Jun 2026 13:25:33 +0200 Subject: [PATCH 094/139] PCI: imx6: Use dev_err_probe() in error path of pci_pwrctrl_power_on_devices() pci_pwrctrl_power_on_devices() might return -EPROBE_DEFER resulting in an error message. Use dev_err_probe() to silence this non-error message. Signed-off-by: Alexander Stein Signed-off-by: Manivannan Sadhasivam Reviewed-by: Frank Li Link: https://patch.msgid.link/20260619112535.1127850-1-alexander.stein@ew.tq-group.com --- drivers/pci/controller/dwc/pci-imx6.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/pci/controller/dwc/pci-imx6.c b/drivers/pci/controller/dwc/pci-imx6.c index 3e146a8920b9..54e104679da0 100644 --- a/drivers/pci/controller/dwc/pci-imx6.c +++ b/drivers/pci/controller/dwc/pci-imx6.c @@ -1455,7 +1455,7 @@ static int imx_pcie_host_init(struct dw_pcie_rp *pp) if (!pp->skip_pwrctrl_off) { ret = pci_pwrctrl_power_on_devices(dev); if (ret) { - dev_err(dev, "failed to power on pwrctrl devices\n"); + dev_err_probe(dev, ret, "failed to power on pwrctrl devices\n"); goto err_reg_disable; } } From 17b478b29cf15f3a96d441d5de773bfd9859b4c2 Mon Sep 17 00:00:00 2001 From: Xincheng Zhang Date: Sat, 5 Sep 2026 10:56:34 +0800 Subject: [PATCH 095/139] PCI: dwc: Allow drivers to skip MEM outbound iATU setup Some DWC-based controllers do not require outbound iATU windows for MEM transactions. For those platforms, programming outbound iATU windows for host bridge MEM ranges is unnecessary and may constrain the available MEM aperture. The DWC PCIe RP Controller Databook v6.00a, section 3.12.2, describes the default outbound no-match behavior as pass-through. Section 3.12.5.6, "No Address Match Result", says the address is not translated and programmable TLP header fields come from the application transmit interface. This no-match path cannot translate between different parent-bus and PCI addresses, so require skipped MEM ranges to be identity-mapped. Add dw_pcie_rp->bypass_ob_mem_iatu so drivers can skip generic MEM iATU setup while using the common DWC host init path. Existing drivers keep the current behavior because the flag defaults to false. The I/O iATU setup does not use the MEM resource iterator, and the iterator may be uninitialized when MEM setup is skipped. Avoid using it in the I/O iATU error path. Signed-off-by: Xincheng Zhang Signed-off-by: Manivannan Sadhasivam Link: https://patch.msgid.link/20260905025635.211390-2-zhangxincheng@ultrarisc.com --- .../pci/controller/dwc/pcie-designware-host.c | 17 +++++++++++++++-- drivers/pci/controller/dwc/pcie-designware.h | 1 + 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/drivers/pci/controller/dwc/pcie-designware-host.c b/drivers/pci/controller/dwc/pcie-designware-host.c index f5a38e6fd8d7..b06797b4e31d 100644 --- a/drivers/pci/controller/dwc/pcie-designware-host.c +++ b/drivers/pci/controller/dwc/pcie-designware-host.c @@ -918,6 +918,20 @@ static int dw_pcie_iatu_setup(struct dw_pcie_rp *pp) atu.parent_bus_addr = entry->res->start - pci->parent_bus_offset; atu.pci_addr = entry->res->start - entry->offset; + if (pp->bypass_ob_mem_iatu) { + /* + * The no-match pass-through path preserves the correct + * PCI address only for identity-mapped MEM ranges. + */ + if (atu.parent_bus_addr != atu.pci_addr) { + dev_err(pci->dev, + "Cannot bypass outbound iATU for non-identity MEM range %pr\n", + entry->res); + return -EINVAL; + } + continue; + } + /* Adjust iATU size if MSG TLP region was allocated before */ if (pp->msg_res && pp->msg_res->parent == entry->res) res_size = resource_size(entry->res) - @@ -964,8 +978,7 @@ static int dw_pcie_iatu_setup(struct dw_pcie_rp *pp) ret = dw_pcie_prog_outbound_atu(pci, &atu); if (ret) { - dev_err(pci->dev, "Failed to set IO range %pr\n", - entry->res); + dev_err(pci->dev, "Failed to set IO range\n"); return ret; } ob_iatu_index++; diff --git a/drivers/pci/controller/dwc/pcie-designware.h b/drivers/pci/controller/dwc/pcie-designware.h index 0735ae940924..d89badcca268 100644 --- a/drivers/pci/controller/dwc/pcie-designware.h +++ b/drivers/pci/controller/dwc/pcie-designware.h @@ -469,6 +469,7 @@ struct dw_pcie_rp { struct pci_config_window *cfg; bool ecam_enabled; bool native_ecam; + bool bypass_ob_mem_iatu; bool skip_l23_ready; bool skip_pwrctrl_off; }; From 8da2b970ed4c8188ef464cee060707f2370e015c Mon Sep 17 00:00:00 2001 From: Xincheng Zhang Date: Sat, 5 Sep 2026 10:56:35 +0800 Subject: [PATCH 096/139] PCI: ultrarisc: Skip MEM outbound iATU setup on DP1000 DP1000 has 16 outbound iATU windows and each window can map up to 4GB. One window is used for CFG accesses, one for I/O space and one for the 32-bit MEM aperture, leaving only 13 windows for the 64-bit MEM aperture. This limits the 64-bit MEM aperture that can be described with outbound iATU windows. DP1000 routes MEM transactions without using outbound iATU windows, so skip generic DWC MEM outbound iATU setup to allow a larger 64-bit MEM aperture. DP1000 does not use ECAM for this controller. Config space accesses keep using the normal DWC config access path and CFG outbound iATU programming. I/O space is also unchanged and still uses an outbound iATU window. The new flag only skips generic MEM-window programming. The DP1000 integration has SoC wrapper address decode for the PCIe MEM apertures described by the "ranges" property. CPU physical accesses to those MEM apertures are routed directly as PCIe MEM transactions with the corresponding PCIe bus address, without consuming outbound iATU windows. Set 'dw_pcie_rp->bypass_ob_mem_iatu' for this controller. Signed-off-by: Xincheng Zhang Signed-off-by: Manivannan Sadhasivam Link: https://patch.msgid.link/20260905025635.211390-3-zhangxincheng@ultrarisc.com --- drivers/pci/controller/dwc/pcie-ultrarisc.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/pci/controller/dwc/pcie-ultrarisc.c b/drivers/pci/controller/dwc/pcie-ultrarisc.c index 51e89180490d..0d4989a0f3af 100644 --- a/drivers/pci/controller/dwc/pcie-ultrarisc.c +++ b/drivers/pci/controller/dwc/pcie-ultrarisc.c @@ -154,6 +154,12 @@ static int ultrarisc_pcie_probe(struct platform_device *pdev) pp = &pci->pp; + /* + * DP1000 routes MEM transactions without outbound iATU windows, which + * avoids the 16-window/4GB-per-window aperture limit. + */ + pp->bypass_ob_mem_iatu = true; + platform_set_drvdata(pdev, ultra); ret = ultrarisc_pcie_init_clks(ultra); From 46edf058e1531ceac70c59b0e69c3ff35b22e3f1 Mon Sep 17 00:00:00 2001 From: Jim Quinlan Date: Fri, 11 Sep 2026 19:35:26 -0400 Subject: [PATCH 097/139] PCI: brcmstb: Remove redundant const specifier for struct fields All of the variables using this particular structure are declared as const. There is no reason to declare its individual fields as being const. Signed-off-by: Jim Quinlan Signed-off-by: Manivannan Sadhasivam Reviewed-by: Florian Fainelli Link: https://patch.msgid.link/20260911233541.1650895-2-james.quinlan@broadcom.com --- drivers/pci/controller/pcie-brcmstb.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/pci/controller/pcie-brcmstb.c b/drivers/pci/controller/pcie-brcmstb.c index 8a0c353d2abf..89aaa19d508a 100644 --- a/drivers/pci/controller/pcie-brcmstb.c +++ b/drivers/pci/controller/pcie-brcmstb.c @@ -288,9 +288,9 @@ struct inbound_win { struct pcie_cfg_data { const int *offsets; - const enum pcie_soc_base soc_base; - const bool has_phy; - const u32 quirks; + enum pcie_soc_base soc_base; + bool has_phy; + u32 quirks; u8 num_inbound_wins; int (*perst_set)(struct brcm_pcie *pcie, u32 val); int (*bridge_sw_init_set)(struct brcm_pcie *pcie, u32 val); From e60d0d4dfddfac3058753a1c2255251401c92dad Mon Sep 17 00:00:00 2001 From: Rudi Heitbaum Date: Mon, 14 Sep 2026 12:35:31 +0000 Subject: [PATCH 098/139] PCI: imx6: Avoid dereferencing a NULL clock name of_clk_bulk_get() leaves 'clk_bulk_data->id' as NULL for every clock that has no matching entry in "clock-names", which is legal: a node may list more "clocks" phandles than it names. Both scans of the bulk array dereference that id unconditionally, so such a node oopses during probe. The loop in imx_pcie_probe() has been wrong since it was written. The loop in imx_setup_phy_mpll() was not: it read a fixed clks[] whose ids came from the driver's own clk_names[] and were never NULL, and only became wrong when the driver moved to the bulk array. Check 'clk_bulk_data->id' before dereferencing it in both. Fixes: f6a1fdfc78e2 ("PCI: imx6: Use devm_clk_bulk_get_all() to fetch clocks") Fixes: d8574ce57d76 ("PCI: imx6: Add external reference clock input mode support") Signed-off-by: Rudi Heitbaum Signed-off-by: Manivannan Sadhasivam Acked-by: Richard Zhu Cc: stable@vger.kernel.org Link: https://lore.kernel.org/all/am8iBwJSEhYhWTqk@6cfee64030a6/ Link: https://patch.msgid.link/aqfqE4OLhnN4a1lW@b65e9c1eb12f --- drivers/pci/controller/dwc/pci-imx6.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/drivers/pci/controller/dwc/pci-imx6.c b/drivers/pci/controller/dwc/pci-imx6.c index 54e104679da0..b7390da1ae35 100644 --- a/drivers/pci/controller/dwc/pci-imx6.c +++ b/drivers/pci/controller/dwc/pci-imx6.c @@ -584,7 +584,7 @@ static int imx_setup_phy_mpll(struct imx_pcie *imx_pcie) return 0; for (i = 0; i < imx_pcie->num_clks; i++) - if (strncmp(clks[i].id, "pcie_phy", 8) == 0) + if (clks[i].id && strncmp(clks[i].id, "pcie_phy", 8) == 0) phy_rate = clk_get_rate(clks[i].clk); switch (phy_rate) { @@ -1902,7 +1902,8 @@ static int imx_pcie_probe(struct platform_device *pdev) return dev_err_probe(dev, imx_pcie->num_clks, "failed to get clocks\n"); for (i = 0; i < imx_pcie->num_clks; i++) - if (strncmp(imx_pcie->clks[i].id, "extref", 6) == 0) + if (imx_pcie->clks[i].id && + strncmp(imx_pcie->clks[i].id, "extref", 6) == 0) imx_pcie->enable_ext_refclk = true; if (imx_check_flag(imx_pcie, IMX_PCIE_FLAG_HAS_PHYDRV)) { From e564a064a2d61641d6c5068d41e677e6180aa5b8 Mon Sep 17 00:00:00 2001 From: Pengpeng Hou Date: Sun, 20 Sep 2026 11:37:37 +0800 Subject: [PATCH 099/139] PCI: eswin: Publish the platform driver OF alias PCIE_ESWIN is tristate and eswin_pcie_driver uses an OF match table, but the table is not exported. The resulting module lacks the OF alias needed for automatic loading on an ESWIN PCIe host controller. Export the OF table. Keep builtin_platform_driver() and the intentional absence of an unregister path unchanged; module alias publication does not require making this interrupt-controller driver unloadable. The issue was found by our static-analysis tool. Assisted-by: LLM Fixes: b593c26d081a ("PCI: eswin: Add ESWIN PCIe Root Complex driver") Signed-off-by: Pengpeng Hou Signed-off-by: Manivannan Sadhasivam Link: https://patch.msgid.link/20260920033737.13957-1-hppiscas@163.com --- drivers/pci/controller/dwc/pcie-eswin.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/pci/controller/dwc/pcie-eswin.c b/drivers/pci/controller/dwc/pcie-eswin.c index ce8d64f8a395..1bb44696057e 100644 --- a/drivers/pci/controller/dwc/pcie-eswin.c +++ b/drivers/pci/controller/dwc/pcie-eswin.c @@ -388,6 +388,7 @@ static const struct of_device_id eswin_pcie_of_match[] = { { .compatible = "eswin,eic7700-pcie", .data = &eswin_eic7700_data }, {} }; +MODULE_DEVICE_TABLE(of, eswin_pcie_of_match); static struct platform_driver eswin_pcie_driver = { .probe = eswin_pcie_probe, From 6aaa3f42e3410ea8e26c857477260159f724cb1d Mon Sep 17 00:00:00 2001 From: Mohamad Raizudeen Date: Thu, 3 Sep 2026 22:16:15 +0530 Subject: [PATCH 100/139] PCI: Fix out-of-bounds MMIO read in nvme_disable_and_flr() In nvme_disable_and_flr(), the PCI bar is mapped using NVME_REG_CC + sizeof(cfg) which is (0x14 + 4 = 0x18 bytes) However, the function later reads the controller status from NVME_REG_CSTS - offset 0x1C, which is outside the mapped 0x18 byte boundary. This violates the API contract. Fix this by increasing the mapping size to include NVME_REG_CSTS. Also change sizeof(cfg) to sizeof(u32) to make the intent clearer. Fixes: ffb0863426eb9 ("PCI: Disable Samsung SM961/PM961 NVMe before FLR") Signed-off-by: Mohamad Raizudeen Signed-off-by: Bjorn Helgaas Reviewed-by: Alex Williamson Link: https://patch.msgid.link/20260903164615.5744-1-raizudeen.kerneldev@gmail.com --- drivers/pci/quirks.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/pci/quirks.c b/drivers/pci/quirks.c index 010822baee34..0256bc15d0d1 100644 --- a/drivers/pci/quirks.c +++ b/drivers/pci/quirks.c @@ -4099,7 +4099,7 @@ static int nvme_disable_and_flr(struct pci_dev *dev, bool probe) if (probe) return 0; - bar = pci_iomap(dev, 0, NVME_REG_CC + sizeof(cfg)); + bar = pci_iomap(dev, 0, NVME_REG_CSTS + sizeof(u32)); if (!bar) return -ENOTTY; From 926767a9be8866d26cc27ca46c46c03d921e2193 Mon Sep 17 00:00:00 2001 From: Stian Halseth Date: Wed, 7 Oct 2026 15:13:04 +0200 Subject: [PATCH 101/139] PCI: Set supported_speeds in set_pcie_port_type() sparc64 and powerpc create PCI devices from the device tree in of_create_pci_dev(), which calls set_pcie_port_type() but not pci_setup_device(). Since d2bd39c0456b, supported_speeds is only set in pci_setup_device(), so it stays 0 for these devices. pcie_get_speed_cap() then returns PCI_SPEED_UNKNOWN, the sysfs 'max_link_speed' attribute reads "Unknown", and the failed link retraining quirk passes PCI_SPEED_UNKNOWN to pcie_set_target_speed(), which warns on a SPARC T7-1: WARNING: drivers/pci/pcie/bwctrl.c:125 at pcie_set_target_speed+0x284/0x2e0 Set supported_speeds in set_pcie_port_type(), which all three paths call. Fixes: d2bd39c0456b ("PCI: Store all PCIe Supported Link Speeds") Closes: https://github.com/sparclinux/issues/issues/109 Signed-off-by: Stian Halseth Signed-off-by: Bjorn Helgaas Tested-by: Tony Rodriguez Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20261007131304.309511-1-stian@itx.no --- drivers/pci/probe.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/drivers/pci/probe.c b/drivers/pci/probe.c index 27008e2ea5af..52a79b033b06 100644 --- a/drivers/pci/probe.c +++ b/drivers/pci/probe.c @@ -1651,6 +1651,8 @@ void set_pcie_port_type(struct pci_dev *pdev) pdev->aspm_l1_support = 1; #endif + pdev->supported_speeds = pcie_get_supported_speeds(pdev); + parent = pci_upstream_bridge(pdev); if (!parent) return; @@ -2074,9 +2076,6 @@ int pci_setup_device(struct pci_dev *dev) set_pcie_untrusted(dev); - if (pci_is_pcie(dev)) - dev->supported_speeds = pcie_get_supported_speeds(dev); - /* "Unknown power state" */ dev->current_state = PCI_UNKNOWN; From f0486008f1cfaef895b4f52bb506a065b5441644 Mon Sep 17 00:00:00 2001 From: Jim Quinlan Date: Fri, 11 Sep 2026 19:35:27 -0400 Subject: [PATCH 102/139] PCI: brcmstb: Use flags u32 instead of bools Instead of having separate 'bool' fields for each SoC configuration, use a small bitvector (u32). Doing this uses less memory and provides a uniform method to assign flags for a specific SoC. The expectation is that more flags will be added as we replace conditions like "if (chipA || chipB)" with "if (behavior_X)". A helper macro "BFLAG()" is used to shorten the determination of the flag value. Signed-off-by: Jim Quinlan Signed-off-by: Manivannan Sadhasivam Signed-off-by: Bjorn Helgaas Reviewed-by: Florian Fainelli Link: https://patch.msgid.link/20260911233541.1650895-3-james.quinlan@broadcom.com --- drivers/pci/controller/pcie-brcmstb.c | 27 +++++++++++++++++---------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/drivers/pci/controller/pcie-brcmstb.c b/drivers/pci/controller/pcie-brcmstb.c index 89aaa19d508a..b531f769c7b3 100644 --- a/drivers/pci/controller/pcie-brcmstb.c +++ b/drivers/pci/controller/pcie-brcmstb.c @@ -286,16 +286,24 @@ struct inbound_win { */ #define CFG_QUIRK_AVOID_BRIDGE_SHUTDOWN BIT(0) +/* FLAGS */ +#define BFLAG(pcie, flag) ((pcie)->cfg->flags & CFG_FLG_ ## flag) + +/* The PCIe HW registers have phy control of a "RESCAL" reset block */ +#define CFG_FLG_HAS_PHY BIT(0) +/* The SoC PCIe HW can dump to the console PCIe error info */ +#define CFG_FLG_HAS_ERR_REPORT BIT(1) + + struct pcie_cfg_data { const int *offsets; enum pcie_soc_base soc_base; - bool has_phy; + u32 flags; u32 quirks; u8 num_inbound_wins; int (*perst_set)(struct brcm_pcie *pcie, u32 val); int (*bridge_sw_init_set)(struct brcm_pcie *pcie, u32 val); int (*post_setup)(struct brcm_pcie *pcie); - bool has_err_report; }; struct subdev_regulators { @@ -356,14 +364,14 @@ static int brcm_pcie_bridge_sw_init_set(struct brcm_pcie *pcie, u32 val) unsigned long flags; int ret; - if (pcie->cfg->has_err_report) + if (BFLAG(pcie, HAS_ERR_REPORT)) spin_lock_irqsave(&pcie->bridge_lock, flags); ret = pcie->cfg->bridge_sw_init_set(pcie, val); /* If we fail, assume the bridge is in reset (off) */ pcie->bridge_in_reset = ret ? true : val; - if (pcie->cfg->has_err_report) + if (BFLAG(pcie, HAS_ERR_REPORT)) spin_unlock_irqrestore(&pcie->bridge_lock, flags); return ret; @@ -1589,12 +1597,12 @@ static int brcm_phy_cntl(struct brcm_pcie *pcie, const int start) static inline int brcm_phy_start(struct brcm_pcie *pcie) { - return pcie->cfg->has_phy ? brcm_phy_cntl(pcie, 1) : 0; + return BFLAG(pcie, HAS_PHY) ? brcm_phy_cntl(pcie, 1) : 0; } static inline int brcm_phy_stop(struct brcm_pcie *pcie) { - return pcie->cfg->has_phy ? brcm_phy_cntl(pcie, 0) : 0; + return BFLAG(pcie, HAS_PHY) ? brcm_phy_cntl(pcie, 0) : 0; } static int brcm_pcie_turn_off(struct brcm_pcie *pcie) @@ -1898,7 +1906,7 @@ static void brcm_pcie_remove(struct platform_device *pdev) pci_stop_root_bus(bridge->bus); pci_remove_root_bus(bridge->bus); pci_unlock_rescan_remove(); - if (pcie->cfg->has_err_report) + if (BFLAG(pcie, HAS_ERR_REPORT)) brcm_unregister_die_notifiers(pcie); __brcm_pcie_remove(pcie); @@ -1999,9 +2007,8 @@ static const struct pcie_cfg_data bcm7216_cfg = { .soc_base = BCM7278, .perst_set = brcm_pcie_perst_set_7278, .bridge_sw_init_set = brcm_pcie_bridge_sw_init_set_7278, - .has_phy = true, + .flags = CFG_FLG_HAS_PHY | CFG_FLG_HAS_ERR_REPORT, .num_inbound_wins = 3, - .has_err_report = true, }; static const struct pcie_cfg_data bcm7712_cfg = { @@ -2179,7 +2186,7 @@ static int brcm_pcie_probe(struct platform_device *pdev) return ret; } - if (pcie->cfg->has_err_report) { + if (BFLAG(pcie, HAS_ERR_REPORT)) { spin_lock_init(&pcie->bridge_lock); brcm_register_die_notifiers(pcie); } From c6faa405df5596104fb5274b21f69e7af304ae02 Mon Sep 17 00:00:00 2001 From: Jim Quinlan Date: Fri, 11 Sep 2026 19:35:28 -0400 Subject: [PATCH 103/139] PCI: brcmstb: Add Broadcom quirks macro Introduce a 'BQUIRK()' macro to shorten the calculation of a specific quirk's value. Signed-off-by: Jim Quinlan Signed-off-by: Manivannan Sadhasivam Signed-off-by: Bjorn Helgaas Reviewed-by: Florian Fainelli Link: https://patch.msgid.link/20260911233541.1650895-4-james.quinlan@broadcom.com --- drivers/pci/controller/pcie-brcmstb.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/pci/controller/pcie-brcmstb.c b/drivers/pci/controller/pcie-brcmstb.c index b531f769c7b3..eac77828ab9f 100644 --- a/drivers/pci/controller/pcie-brcmstb.c +++ b/drivers/pci/controller/pcie-brcmstb.c @@ -277,6 +277,9 @@ struct inbound_win { u64 cpu_addr; }; +/* QUIRKS */ +#define BQUIRK(pcie, quirk) ((pcie)->cfg->quirks & CFG_QUIRK_ ## quirk) + /* * The RESCAL block is tied to PCIe controller #1, regardless of the number of * controllers, and turning off PCIe controller #1 prevents access to the RESCAL @@ -1627,7 +1630,7 @@ static int brcm_pcie_turn_off(struct brcm_pcie *pcie) u32p_replace_bits(&tmp, 1, PCIE_MISC_HARD_PCIE_HARD_DEBUG_SERDES_IDDQ_MASK); writel(tmp, base + HARD_DEBUG(pcie)); - if (!(pcie->cfg->quirks & CFG_QUIRK_AVOID_BRIDGE_SHUTDOWN)) + if (!(BQUIRK(pcie, AVOID_BRIDGE_SHUTDOWN))) /* Shutdown PCIe bridge */ ret = brcm_pcie_bridge_sw_init_set(pcie, 1); From 86ddadf13a31cccc6b2ac3f6dfad423ea8998039 Mon Sep 17 00:00:00 2001 From: Jim Quinlan Date: Fri, 11 Sep 2026 19:35:29 -0400 Subject: [PATCH 104/139] PCI: brcmstb: Declare and assign quirk OB_WIN_32BIT_ADDR Even though only BMIPs chips have this quirk, it is more uniform and readable to define this specific quirk in which the outbound region must reside in the lower 4GB region. Signed-off-by: Jim Quinlan Signed-off-by: Manivannan Sadhasivam Signed-off-by: Bjorn Helgaas Reviewed-by: Florian Fainelli Link: https://patch.msgid.link/20260911233541.1650895-5-james.quinlan@broadcom.com --- drivers/pci/controller/pcie-brcmstb.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/drivers/pci/controller/pcie-brcmstb.c b/drivers/pci/controller/pcie-brcmstb.c index eac77828ab9f..7061647af645 100644 --- a/drivers/pci/controller/pcie-brcmstb.c +++ b/drivers/pci/controller/pcie-brcmstb.c @@ -288,6 +288,8 @@ struct inbound_win { * or a hang (AXI). */ #define CFG_QUIRK_AVOID_BRIDGE_SHUTDOWN BIT(0) +/* The outbound windows must be within the 0-4GB region */ +#define CFG_QUIRK_OB_WIN_32BIT_ADDR BIT(1) /* FLAGS */ #define BFLAG(pcie, flag) ((pcie)->cfg->flags & CFG_FLG_ ## flag) @@ -519,7 +521,7 @@ static void brcm_pcie_set_outbound_win(struct brcm_pcie *pcie, PCIE_MISC_CPU_2_PCIE_MEM_WIN0_BASE_LIMIT_LIMIT_MASK); writel(tmp, pcie->base + PCIE_MEM_WIN0_BASE_LIMIT(win)); - if (is_bmips(pcie)) + if (BQUIRK(pcie, OB_WIN_32BIT_ADDR)) return; /* Write the cpu & limit addr upper bits */ @@ -1995,6 +1997,7 @@ static const struct pcie_cfg_data bcm7425_cfg = { .perst_set = brcm_pcie_perst_set_generic, .bridge_sw_init_set = brcm_pcie_bridge_sw_init_set_generic, .num_inbound_wins = 3, + .quirks = CFG_QUIRK_OB_WIN_32BIT_ADDR, }; static const struct pcie_cfg_data bcm7435_cfg = { @@ -2003,6 +2006,7 @@ static const struct pcie_cfg_data bcm7435_cfg = { .perst_set = brcm_pcie_perst_set_generic, .bridge_sw_init_set = brcm_pcie_bridge_sw_init_set_generic, .num_inbound_wins = 3, + .quirks = CFG_QUIRK_OB_WIN_32BIT_ADDR, }; static const struct pcie_cfg_data bcm7216_cfg = { From 2ed8567ad8cb675b11a6cdd5e5d54fb4efe32a7b Mon Sep 17 00:00:00 2001 From: Jim Quinlan Date: Fri, 11 Sep 2026 19:35:30 -0400 Subject: [PATCH 105/139] PCI: brcmstb: Declare and assign quirk OB_WIN_MAXSZ_128MB Even though only BMIPs chips have this quirk, it is more uniform and readable to define this specific quirk where each outbound window is must be <= SZ_128M. Signed-off-by: Jim Quinlan Signed-off-by: Manivannan Sadhasivam Signed-off-by: Bjorn Helgaas Reviewed-by: Florian Fainelli Link: https://patch.msgid.link/20260911233541.1650895-6-james.quinlan@broadcom.com --- drivers/pci/controller/pcie-brcmstb.c | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/drivers/pci/controller/pcie-brcmstb.c b/drivers/pci/controller/pcie-brcmstb.c index 7061647af645..8a2dab31650a 100644 --- a/drivers/pci/controller/pcie-brcmstb.c +++ b/drivers/pci/controller/pcie-brcmstb.c @@ -290,6 +290,8 @@ struct inbound_win { #define CFG_QUIRK_AVOID_BRIDGE_SHUTDOWN BIT(0) /* The outbound windows must be within the 0-4GB region */ #define CFG_QUIRK_OB_WIN_32BIT_ADDR BIT(1) +/* Each of the outbound windows size must be <= SZ_128M */ +#define CFG_QUIRK_OB_WIN_MAXSZ_128MB BIT(2) /* FLAGS */ #define BFLAG(pcie, flag) ((pcie)->cfg->flags & CFG_FLG_ ## flag) @@ -1294,11 +1296,11 @@ static int brcm_pcie_setup(struct brcm_pcie *pcie) return -EINVAL; } - if (is_bmips(pcie)) { + if (BQUIRK(pcie, OB_WIN_MAXSZ_128MB)) { u64 start = res->start; unsigned int j, nwins = resource_size(res) / SZ_128M; - /* bmips PCIe outbound windows have a 128MB max size */ + /* PCIe outbound windows have a 128MB max size */ if (nwins > BRCM_NUM_PCIE_OUT_WINS) nwins = BRCM_NUM_PCIE_OUT_WINS; for (j = 0; j < nwins; j++, start += SZ_128M) @@ -1997,7 +1999,8 @@ static const struct pcie_cfg_data bcm7425_cfg = { .perst_set = brcm_pcie_perst_set_generic, .bridge_sw_init_set = brcm_pcie_bridge_sw_init_set_generic, .num_inbound_wins = 3, - .quirks = CFG_QUIRK_OB_WIN_32BIT_ADDR, + .quirks = CFG_QUIRK_OB_WIN_32BIT_ADDR + | CFG_QUIRK_OB_WIN_MAXSZ_128MB, }; static const struct pcie_cfg_data bcm7435_cfg = { @@ -2006,7 +2009,8 @@ static const struct pcie_cfg_data bcm7435_cfg = { .perst_set = brcm_pcie_perst_set_generic, .bridge_sw_init_set = brcm_pcie_bridge_sw_init_set_generic, .num_inbound_wins = 3, - .quirks = CFG_QUIRK_OB_WIN_32BIT_ADDR, + .quirks = CFG_QUIRK_OB_WIN_32BIT_ADDR + | CFG_QUIRK_OB_WIN_MAXSZ_128MB, }; static const struct pcie_cfg_data bcm7216_cfg = { From 2a3c8e29ee4edd3bc3005cfdd0bf3449336241fd Mon Sep 17 00:00:00 2001 From: Jim Quinlan Date: Fri, 11 Sep 2026 19:35:31 -0400 Subject: [PATCH 106/139] PCI: brcmstb: Declare and assign flag IS_BMIPS Eliminate is_bmips(), which evaluates SoC chip IDs, and instead declare and use CFG_FLG_IS_BMIPS, a flag that indicates the SoC employs a CPU with a variant of the MIPS-1 ISA. Signed-off-by: Jim Quinlan Signed-off-by: Manivannan Sadhasivam Signed-off-by: Bjorn Helgaas Reviewed-by: Florian Fainelli Link: https://patch.msgid.link/20260911233541.1650895-7-james.quinlan@broadcom.com --- drivers/pci/controller/pcie-brcmstb.c | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/drivers/pci/controller/pcie-brcmstb.c b/drivers/pci/controller/pcie-brcmstb.c index 8a2dab31650a..9a0882cb0ca0 100644 --- a/drivers/pci/controller/pcie-brcmstb.c +++ b/drivers/pci/controller/pcie-brcmstb.c @@ -300,7 +300,8 @@ struct inbound_win { #define CFG_FLG_HAS_PHY BIT(0) /* The SoC PCIe HW can dump to the console PCIe error info */ #define CFG_FLG_HAS_ERR_REPORT BIT(1) - +/* SoC uses a Broadcom variant of the MIPS-1 ISA */ +#define CFG_FLG_IS_BMIPS BIT(2) struct pcie_cfg_data { const int *offsets; @@ -361,11 +362,6 @@ struct brcm_pcie { spinlock_t bridge_lock; }; -static inline bool is_bmips(const struct brcm_pcie *pcie) -{ - return pcie->cfg->soc_base == BCM7435 || pcie->cfg->soc_base == BCM7425; -} - static int brcm_pcie_bridge_sw_init_set(struct brcm_pcie *pcie, u32 val) { unsigned long flags; @@ -1176,7 +1172,7 @@ static int brcm_pcie_setup(struct brcm_pcie *pcie) return ret; tmp = readl(base + HARD_DEBUG(pcie)); - if (is_bmips(pcie)) + if (BFLAG(pcie, IS_BMIPS)) tmp &= ~PCIE_BMIPS_MISC_HARD_PCIE_HARD_DEBUG_SERDES_IDDQ_MASK; else tmp &= ~PCIE_MISC_HARD_PCIE_HARD_DEBUG_SERDES_IDDQ_MASK; @@ -1189,7 +1185,7 @@ static int brcm_pcie_setup(struct brcm_pcie *pcie) * is encoded as 0=128, 1=256, 2=512, 3=Rsvd, for BCM7278 it * is encoded as 0=Rsvd, 1=128, 2=256, 3=512. */ - if (is_bmips(pcie)) + if (BFLAG(pcie, IS_BMIPS)) burst = 0x1; /* 256 bytes */ else if (pcie->cfg->soc_base == BCM2711) burst = 0x0; /* 128 bytes */ @@ -2001,6 +1997,7 @@ static const struct pcie_cfg_data bcm7425_cfg = { .num_inbound_wins = 3, .quirks = CFG_QUIRK_OB_WIN_32BIT_ADDR | CFG_QUIRK_OB_WIN_MAXSZ_128MB, + .flags = CFG_FLG_IS_BMIPS, }; static const struct pcie_cfg_data bcm7435_cfg = { @@ -2011,6 +2008,7 @@ static const struct pcie_cfg_data bcm7435_cfg = { .num_inbound_wins = 3, .quirks = CFG_QUIRK_OB_WIN_32BIT_ADDR | CFG_QUIRK_OB_WIN_MAXSZ_128MB, + .flags = CFG_FLG_IS_BMIPS, }; static const struct pcie_cfg_data bcm7216_cfg = { From 45245b4e84aca7ee4ef7654b63d7a6c0e24cad6b Mon Sep 17 00:00:00 2001 From: Jim Quinlan Date: Fri, 11 Sep 2026 19:35:32 -0400 Subject: [PATCH 107/139] PCI: brcmstb: Declare and assign quirk 32BIT_PCI_OPS Declare a new quirk that indicates SoCs where the config-space accesses are restricted to 32bit R/W. Signed-off-by: Jim Quinlan Signed-off-by: Manivannan Sadhasivam Signed-off-by: Bjorn Helgaas Reviewed-by: Florian Fainelli Link: https://patch.msgid.link/20260911233541.1650895-8-james.quinlan@broadcom.com --- drivers/pci/controller/pcie-brcmstb.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/drivers/pci/controller/pcie-brcmstb.c b/drivers/pci/controller/pcie-brcmstb.c index 9a0882cb0ca0..beb6559a0d7b 100644 --- a/drivers/pci/controller/pcie-brcmstb.c +++ b/drivers/pci/controller/pcie-brcmstb.c @@ -292,6 +292,8 @@ struct inbound_win { #define CFG_QUIRK_OB_WIN_32BIT_ADDR BIT(1) /* Each of the outbound windows size must be <= SZ_128M */ #define CFG_QUIRK_OB_WIN_MAXSZ_128MB BIT(2) +/* PCIe HW can only access config space with 32bit R/W */ +#define CFG_QUIRK_32BIT_PCI_OPS BIT(3) /* FLAGS */ #define BFLAG(pcie, flag) ((pcie)->cfg->flags & CFG_FLG_ ## flag) @@ -1996,7 +1998,7 @@ static const struct pcie_cfg_data bcm7425_cfg = { .bridge_sw_init_set = brcm_pcie_bridge_sw_init_set_generic, .num_inbound_wins = 3, .quirks = CFG_QUIRK_OB_WIN_32BIT_ADDR - | CFG_QUIRK_OB_WIN_MAXSZ_128MB, + | CFG_QUIRK_OB_WIN_MAXSZ_128MB | CFG_QUIRK_32BIT_PCI_OPS, .flags = CFG_FLG_IS_BMIPS, }; @@ -2050,7 +2052,7 @@ static struct pci_ops brcm_pcie_ops = { .remove_bus = brcm_pcie_remove_bus, }; -static struct pci_ops brcm7425_pcie_ops = { +static struct pci_ops brcm_pcie_ops32 = { .map_bus = brcm7425_pcie_map_bus, .read = pci_generic_config_read32, .write = pci_generic_config_write32, @@ -2180,8 +2182,8 @@ static int brcm_pcie_probe(struct platform_device *pdev) } } - bridge->ops = pcie->cfg->soc_base == BCM7425 ? - &brcm7425_pcie_ops : &brcm_pcie_ops; + bridge->ops = BQUIRK(pcie, 32BIT_PCI_OPS) + ? &brcm_pcie_ops32 : &brcm_pcie_ops; bridge->sysdata = pcie; platform_set_drvdata(pdev, pcie); From 4e49ef31f10b696483271194014b67e2768ecd49 Mon Sep 17 00:00:00 2001 From: Jim Quinlan Date: Fri, 11 Sep 2026 19:35:33 -0400 Subject: [PATCH 108/139] PCI: brcmstb: Declare and assign quirk NO_RGR1_TIMER Very few SoCs do not have an internal bus timer whose timeout value may be adusted for L1SS operation. Declare this as a quirk and assign it accordingly. Signed-off-by: Jim Quinlan Signed-off-by: Manivannan Sadhasivam Signed-off-by: Bjorn Helgaas Reviewed-by: Florian Fainelli Link: https://patch.msgid.link/20260911233541.1650895-9-james.quinlan@broadcom.com --- drivers/pci/controller/pcie-brcmstb.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/drivers/pci/controller/pcie-brcmstb.c b/drivers/pci/controller/pcie-brcmstb.c index beb6559a0d7b..9c5383886d03 100644 --- a/drivers/pci/controller/pcie-brcmstb.c +++ b/drivers/pci/controller/pcie-brcmstb.c @@ -294,6 +294,8 @@ struct inbound_win { #define CFG_QUIRK_OB_WIN_MAXSZ_128MB BIT(2) /* PCIe HW can only access config space with 32bit R/W */ #define CFG_QUIRK_32BIT_PCI_OPS BIT(3) +/* PCIe HW does not have an internal bus timer */ +#define CFG_QUIRK_NO_RGR1_TIMER BIT(4) /* FLAGS */ #define BFLAG(pcie, flag) ((pcie)->cfg->flags & CFG_FLG_ ## flag) @@ -1339,8 +1341,8 @@ static void brcm_extend_rbus_timeout(struct brcm_pcie *pcie) const unsigned int REG_OFFSET = PCIE_RGR1_SW_INIT_1(pcie) - 8; u32 timeout_us = 4000000; /* 4 seconds, our setting for L1SS */ - /* 7712 does not have this (RGR1) timer */ - if (pcie->cfg->soc_base == BCM7712) + /* Don't do access if there is no timer */ + if (BQUIRK(pcie, NO_RGR1_TIMER)) return; /* Each unit in timeout register is 1/216,000,000 seconds */ @@ -1971,7 +1973,8 @@ static const struct pcie_cfg_data bcm2712_cfg = { .perst_set = brcm_pcie_perst_set_7278, .bridge_sw_init_set = brcm_pcie_bridge_sw_init_set_generic, .post_setup = brcm_pcie_post_setup_bcm2712, - .quirks = CFG_QUIRK_AVOID_BRIDGE_SHUTDOWN, + .quirks = CFG_QUIRK_AVOID_BRIDGE_SHUTDOWN | + CFG_QUIRK_NO_RGR1_TIMER, .num_inbound_wins = 10, }; @@ -2028,6 +2031,7 @@ static const struct pcie_cfg_data bcm7712_cfg = { .bridge_sw_init_set = brcm_pcie_bridge_sw_init_set_generic, .soc_base = BCM7712, .num_inbound_wins = 10, + .quirks = CFG_QUIRK_NO_RGR1_TIMER, }; static const struct of_device_id brcm_pcie_match[] = { From 94ac85ea1e88402ed5c2e27e33b2dea621ac2078 Mon Sep 17 00:00:00 2001 From: Jim Quinlan Date: Fri, 11 Sep 2026 19:35:34 -0400 Subject: [PATCH 109/139] PCI: brcmstb: Declare and assign quirk EARLY_PERST_ASSERT Declare and assign a quirk where PERST# must be asserted prior to an internal bus being turned on. Signed-off-by: Jim Quinlan Signed-off-by: Manivannan Sadhasivam Signed-off-by: Bjorn Helgaas Reviewed-by: Florian Fainelli Link: https://patch.msgid.link/20260911233541.1650895-10-james.quinlan@broadcom.com --- drivers/pci/controller/pcie-brcmstb.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/drivers/pci/controller/pcie-brcmstb.c b/drivers/pci/controller/pcie-brcmstb.c index 9c5383886d03..34ad975c3732 100644 --- a/drivers/pci/controller/pcie-brcmstb.c +++ b/drivers/pci/controller/pcie-brcmstb.c @@ -296,6 +296,8 @@ struct inbound_win { #define CFG_QUIRK_32BIT_PCI_OPS BIT(3) /* PCIe HW does not have an internal bus timer */ #define CFG_QUIRK_NO_RGR1_TIMER BIT(4) +/* PCIe PERST# must be asserted before internal bridge turned on */ +#define CFG_QUIRK_EARLY_PERST_ASSERT BIT(5) /* FLAGS */ #define BFLAG(pcie, flag) ((pcie)->cfg->flags & CFG_FLG_ ## flag) @@ -1159,8 +1161,8 @@ static int brcm_pcie_setup(struct brcm_pcie *pcie) if (ret) return ret; - /* Ensure that PERST# is asserted; some bootloaders may deassert it. */ - if (pcie->cfg->soc_base == BCM2711) { + if (BQUIRK(pcie, EARLY_PERST_ASSERT)) { + /* Ensure that PERST# is asserted; some bootloaders may deassert it. */ ret = pcie->cfg->perst_set(pcie, 1); if (ret) { pcie->cfg->bridge_sw_init_set(pcie, 0); @@ -1965,6 +1967,7 @@ static const struct pcie_cfg_data bcm2711_cfg = { .perst_set = brcm_pcie_perst_set_generic, .bridge_sw_init_set = brcm_pcie_bridge_sw_init_set_generic, .num_inbound_wins = 3, + .quirks = CFG_QUIRK_EARLY_PERST_ASSERT, }; static const struct pcie_cfg_data bcm2712_cfg = { From 8a1c4ba99946d5e0e07c357792deea666df23f5f Mon Sep 17 00:00:00 2001 From: Jim Quinlan Date: Fri, 11 Sep 2026 19:35:35 -0400 Subject: [PATCH 110/139] PCI: brcmstb: Declare and assign quirk PERST_PCIE_REV_CUTOFF The SW behavior for asserting PERST# for some SoCs only works up to a specific PCIe HW version. Declare this as a quirk and assign it accordingly. Signed-off-by: Jim Quinlan Signed-off-by: Manivannan Sadhasivam Signed-off-by: Bjorn Helgaas Reviewed-by: Florian Fainelli Link: https://patch.msgid.link/20260911233541.1650895-11-james.quinlan@broadcom.com --- drivers/pci/controller/pcie-brcmstb.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/pci/controller/pcie-brcmstb.c b/drivers/pci/controller/pcie-brcmstb.c index 34ad975c3732..26d1305f5e70 100644 --- a/drivers/pci/controller/pcie-brcmstb.c +++ b/drivers/pci/controller/pcie-brcmstb.c @@ -298,6 +298,8 @@ struct inbound_win { #define CFG_QUIRK_NO_RGR1_TIMER BIT(4) /* PCIe PERST# must be asserted before internal bridge turned on */ #define CFG_QUIRK_EARLY_PERST_ASSERT BIT(5) +/* PCIe SW PERST behavior only works up to a specific HW version */ +#define CFG_QUIRK_PERST_PCIE_REV_CUTOFF BIT(6) /* FLAGS */ #define BFLAG(pcie, flag) ((pcie)->cfg->flags & CFG_FLG_ ## flag) @@ -1987,6 +1989,7 @@ static const struct pcie_cfg_data bcm4908_cfg = { .perst_set = brcm_pcie_perst_set_4908, .bridge_sw_init_set = brcm_pcie_bridge_sw_init_set_generic, .num_inbound_wins = 3, + .quirks = CFG_QUIRK_PERST_PCIE_REV_CUTOFF, }; static const struct pcie_cfg_data bcm7278_cfg = { @@ -2168,7 +2171,7 @@ static int brcm_pcie_probe(struct platform_device *pdev) goto fail; pcie->hw_rev = readl(pcie->base + PCIE_MISC_REVISION); - if (pcie->cfg->soc_base == BCM4908 && + if (BQUIRK(pcie, PERST_PCIE_REV_CUTOFF) && pcie->hw_rev >= BRCM_PCIE_HW_REV_3_20) { dev_err(pcie->dev, "hardware revision with unsupported PERST# setup\n"); ret = -ENODEV; From cfe96ce583c51bf6e034d4038c652a1e8227867c Mon Sep 17 00:00:00 2001 From: Linmao Li Date: Tue, 15 Sep 2026 16:28:26 +0800 Subject: [PATCH 111/139] PCI: cadence: Store the core controller in platform drvdata cdns_plat_pcie_probe() stores a struct cdns_plat_pcie pointer with platform_set_drvdata(). cdns_plat_pcie_shutdown() and the callbacks in cdns_pcie_pm_ops retrieve the same pointer with dev_get_drvdata() but use it as a struct cdns_pcie pointer. This makes them read phy_count and phy beyond the wrapper. struct cdns_plat_pcie only contains a pointer to struct cdns_pcie. Drop the redundant wrapper and store the struct cdns_pcie pointer directly. Fixes: bd22885aa188 ("PCI: cadence: Refactor driver to use as a core library") Signed-off-by: Linmao Li Signed-off-by: Manivannan Sadhasivam Signed-off-by: Bjorn Helgaas Reviewed-by: Aksh Garg Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20260915082826.4135665-1-lilinmao@kylinos.cn --- .../controller/cadence/pcie-cadence-plat.c | 27 ++++++------------- 1 file changed, 8 insertions(+), 19 deletions(-) diff --git a/drivers/pci/controller/cadence/pcie-cadence-plat.c b/drivers/pci/controller/cadence/pcie-cadence-plat.c index 13edc6be21f5..4a24302cc5b2 100644 --- a/drivers/pci/controller/cadence/pcie-cadence-plat.c +++ b/drivers/pci/controller/cadence/pcie-cadence-plat.c @@ -14,14 +14,6 @@ #define CDNS_PLAT_CPU_TO_BUS_ADDR 0x0FFFFFFF -/** - * struct cdns_plat_pcie - private data for this PCIe platform driver - * @pcie: Cadence PCIe controller - */ -struct cdns_plat_pcie { - struct cdns_pcie *pcie; -}; - static const struct of_device_id cdns_plat_pcie_of_match[]; static u64 cdns_plat_cpu_addr_fixup(struct cdns_pcie *pcie, u64 cpu_addr) @@ -36,9 +28,9 @@ static const struct cdns_pcie_ops cdns_plat_ops = { static int cdns_plat_pcie_probe(struct platform_device *pdev) { const struct cdns_plat_pcie_of_data *data; - struct cdns_plat_pcie *cdns_plat_pcie; struct device *dev = &pdev->dev; struct pci_host_bridge *bridge; + struct cdns_pcie *pcie; struct cdns_pcie_ep *ep; struct cdns_pcie_rc *rc; bool is_rc; @@ -51,11 +43,6 @@ static int cdns_plat_pcie_probe(struct platform_device *pdev) is_rc = data->is_rc; pr_debug(" Started %s with is_rc: %d\n", __func__, is_rc); - cdns_plat_pcie = devm_kzalloc(dev, sizeof(*cdns_plat_pcie), GFP_KERNEL); - if (!cdns_plat_pcie) - return -ENOMEM; - - platform_set_drvdata(pdev, cdns_plat_pcie); if (is_rc) { if (!IS_ENABLED(CONFIG_PCIE_CADENCE_PLAT_HOST)) return -ENODEV; @@ -67,9 +54,9 @@ static int cdns_plat_pcie_probe(struct platform_device *pdev) rc = pci_host_bridge_priv(bridge); rc->pcie.dev = dev; rc->pcie.ops = &cdns_plat_ops; - cdns_plat_pcie->pcie = &rc->pcie; + pcie = &rc->pcie; - ret = cdns_pcie_init_phy(dev, cdns_plat_pcie->pcie); + ret = cdns_pcie_init_phy(dev, pcie); if (ret) { dev_err(dev, "failed to init phy\n"); return ret; @@ -94,9 +81,9 @@ static int cdns_plat_pcie_probe(struct platform_device *pdev) ep->pcie.dev = dev; ep->pcie.ops = &cdns_plat_ops; - cdns_plat_pcie->pcie = &ep->pcie; + pcie = &ep->pcie; - ret = cdns_pcie_init_phy(dev, cdns_plat_pcie->pcie); + ret = cdns_pcie_init_phy(dev, pcie); if (ret) { dev_err(dev, "failed to init phy\n"); return ret; @@ -114,13 +101,15 @@ static int cdns_plat_pcie_probe(struct platform_device *pdev) goto err_init; } + platform_set_drvdata(pdev, pcie); + return 0; err_init: err_get_sync: pm_runtime_put_sync(dev); pm_runtime_disable(dev); - cdns_pcie_disable_phy(cdns_plat_pcie->pcie); + cdns_pcie_disable_phy(pcie); return ret; } From efadda401c5c8e2390ba66508eba154dbd0db5a9 Mon Sep 17 00:00:00 2001 From: Sumit Kumar Date: Tue, 8 Sep 2026 11:48:22 +0530 Subject: [PATCH 112/139] PCI: dwc: Set DMA_BYPASS bit in outbound IATU CTRL2 register An outbound iATU region matches any local (AXI/application-side) address that falls within its Base/Limit window and rewrites it to the PCIe-side address programmed in that region's Target Address Registers before the transaction goes out on the link. This matching does not distinguish CPU-initiated transactions from ones issued by the internal DMA engine (eDMA/HDMA). Example: outbound region 0 is programmed with Base/Limit covering local addresses 0x8000_0000-0x8000_0FFF and Target 0x1_0000_0000, for CPU-initiated accesses. The DMA engine is later given a descriptor whose destination is the host address 0x8000_0080, which happens to fall inside region 0's Base/Limit range. The iATU matches the DMA transaction against region 0 too and rewrites it to 0x1_0000_0080 instead of passing 0x8000_0080 through unchanged. The transaction then lands on the wrong host address, causing data corruption or an IOMMU fault when the rewritten address has no valid mapping. Set the DMA_BYPASS bit (bit 27) in PCIE_ATU_REGION_CTRL2 when programming outbound iATU regions so only CPU/AXI-initiated transactions are matched against the region; DMA engine transactions bypass the translation and go out on the PCIe link with their original address. Signed-off-by: Sumit Kumar Signed-off-by: Manivannan Sadhasivam Signed-off-by: Bjorn Helgaas Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20260908-dma_bypass-v2-1-c3c81d408c0c@oss.qualcomm.com --- drivers/pci/controller/dwc/pcie-designware.c | 2 ++ drivers/pci/controller/dwc/pcie-designware.h | 1 + 2 files changed, 3 insertions(+) diff --git a/drivers/pci/controller/dwc/pcie-designware.c b/drivers/pci/controller/dwc/pcie-designware.c index 855d2a54b3c6..074b007dc296 100644 --- a/drivers/pci/controller/dwc/pcie-designware.c +++ b/drivers/pci/controller/dwc/pcie-designware.c @@ -577,6 +577,8 @@ int dw_pcie_prog_outbound_atu(struct dw_pcie *pci, /* The data-less messages only for now */ val |= ATU_INHIBIT_PAYLOAD | atu->code; } + if (dw_pcie_ver_is_ge(pci, 460A)) + val |= ATU_DMA_BYPASS; dw_pcie_writel_atu_ob(pci, atu->index, ATU_REGION_CTRL2, val); /* diff --git a/drivers/pci/controller/dwc/pcie-designware.h b/drivers/pci/controller/dwc/pcie-designware.h index aea474ad803c..61e9c4e979af 100644 --- a/drivers/pci/controller/dwc/pcie-designware.h +++ b/drivers/pci/controller/dwc/pcie-designware.h @@ -217,6 +217,7 @@ #define ATU_ENABLE BIT(31) #define ATU_BAR_MODE_ENABLE BIT(30) #define ATU_CFG_SHIFT_MODE_ENABLE BIT(28) +#define ATU_DMA_BYPASS BIT(27) #define ATU_INHIBIT_PAYLOAD BIT(22) #define ATU_FUNC_NUM_MATCH_EN BIT(19) #define ATU_LOWER_BASE 0x008 From f845c1e2d2d4506802244ea2a378ae7b04e90bfe Mon Sep 17 00:00:00 2001 From: Richard Zhu Date: Thu, 17 Sep 2026 14:33:22 +0800 Subject: [PATCH 113/139] PCI: dwc: Add suspend_poweroff flag for platforms with RC power loss Some platforms like i.MX power off their PCIe RC controllers during system suspend, requiring full re-initialization on resume. These platforms need to enter L2 state to properly notify endpoints before power loss. According to PCIe r7.0, sec 5.2, system software should transition the device into D3hot before broadcasting the PME_Turn_Off message to initiate L2 entry. In practice, endpoint drivers properly put their devices into D3hot during the suspend flow. However, some wakeup-capable endpoint devices that don't support PME from D3cold fail the D3cold capability check in pci_host_common_d3cold_possible(), which currently prevents L2 entry even though the devices are already in the required D3hot state. For platforms where the RC loses power during suspend, L2 entry is essential regardless of D3cold support, as the link will be lost anyway when the RC powers off. Without L2 entry, endpoints are not properly notified before power loss, which can cause issues during resume and reinitialization. Add a suspend_poweroff flag to allow L2 entry for devices that are in D3hot but fail the D3cold capability check, and enable it for i.MX PCIe controllers that lose power during suspend. Signed-off-by: Richard Zhu [mani: removed redundant comment] Signed-off-by: Manivannan Sadhasivam Signed-off-by: Bjorn Helgaas Link: https://patch.msgid.link/20260917063322.4102035-1-hongxing.zhu@oss.nxp.com --- drivers/pci/controller/dwc/pci-imx6.c | 2 ++ drivers/pci/controller/dwc/pcie-designware-host.c | 11 +++++++++-- drivers/pci/controller/dwc/pcie-designware.h | 2 ++ 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/drivers/pci/controller/dwc/pci-imx6.c b/drivers/pci/controller/dwc/pci-imx6.c index b7390da1ae35..add370ca944d 100644 --- a/drivers/pci/controller/dwc/pci-imx6.c +++ b/drivers/pci/controller/dwc/pci-imx6.c @@ -2049,6 +2049,8 @@ static int imx_pcie_probe(struct platform_device *pdev) goto err_pwrctrl_destroy; } + pci->pp.suspend_poweroff = true; + if (imx_check_flag(imx_pcie, IMX_PCIE_FLAG_SKIP_L23_READY)) pci->pp.skip_l23_ready = true; if (imx_check_flag(imx_pcie, IMX_PCIE_FLAG_KEEP_MSI_CAP)) diff --git a/drivers/pci/controller/dwc/pcie-designware-host.c b/drivers/pci/controller/dwc/pcie-designware-host.c index f5a38e6fd8d7..b28e0965e522 100644 --- a/drivers/pci/controller/dwc/pcie-designware-host.c +++ b/drivers/pci/controller/dwc/pcie-designware-host.c @@ -1224,14 +1224,21 @@ static int dw_pcie_pme_turn_off(struct dw_pcie *pci) int dw_pcie_suspend_noirq(struct dw_pcie *pci) { - bool pme_capable = false; + bool d3cold, pme_capable = false; int ret = 0; u32 val; if (!dw_pcie_link_up(pci)) goto stop_link; - if (!pci_host_common_d3cold_possible(pci->pp.bridge, &pme_capable)) + d3cold = pci_host_common_d3cold_possible(pci->pp.bridge, &pme_capable); + + /* + * Enter L2 if D3cold is possible, or if the platform loses RC power + * during suspend (in which case L2 entry is required to notify + * endpoints before power loss, regardless of D3cold support). + */ + if (!d3cold && !pci->pp.suspend_poweroff) return 0; if (pci->pp.ops->pme_turn_off) { diff --git a/drivers/pci/controller/dwc/pcie-designware.h b/drivers/pci/controller/dwc/pcie-designware.h index 0735ae940924..aff1accffb9b 100644 --- a/drivers/pci/controller/dwc/pcie-designware.h +++ b/drivers/pci/controller/dwc/pcie-designware.h @@ -471,6 +471,8 @@ struct dw_pcie_rp { bool native_ecam; bool skip_l23_ready; bool skip_pwrctrl_off; + /* Set if the RC loses power during suspend */ + bool suspend_poweroff; }; struct dw_pcie_ep_ops { From 1b09807c4addfd488ae4d0083d3b406ecd2e72f6 Mon Sep 17 00:00:00 2001 From: Vincent Jardin Date: Mon, 28 Sep 2026 15:48:09 +0200 Subject: [PATCH 114/139] PCI: layerscape: Enable async probe by default dw_pcie_wait_for_link() polls for up to PCIE_LINK_WAIT_MAX_RETRIES * PCIE_LINK_WAIT_SLEEP_MS (10 * 90 ms) per controller. The controllers are probed one after the other, so, it leads to slow boot time. On an LX2160A board with 4 enabled controllers and no link on any of them, dw_pcie_host_init() took almost 4s. Probing the 4 controllers in parallel allows to have it down to 1s. So enable async probe by default. Signed-off-by: Vincent Jardin [mani: commit log] Signed-off-by: Manivannan Sadhasivam Signed-off-by: Bjorn Helgaas Reviewed-by: Frank Li Link: https://patch.msgid.link/20260928-for-upstream-pci-layerscape-async-probe-v1-1-76412d887baa@free.fr --- drivers/pci/controller/dwc/pci-layerscape.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/pci/controller/dwc/pci-layerscape.c b/drivers/pci/controller/dwc/pci-layerscape.c index 14d6ac4fc53f..7f37a0537159 100644 --- a/drivers/pci/controller/dwc/pci-layerscape.c +++ b/drivers/pci/controller/dwc/pci-layerscape.c @@ -419,6 +419,7 @@ static struct platform_driver ls_pcie_driver = { .of_match_table = ls_pcie_of_match, .suppress_bind_attrs = true, .pm = &ls_pcie_pm_ops, + .probe_type = PROBE_PREFER_ASYNCHRONOUS, }, }; module_platform_driver(ls_pcie_driver); From 39d1c645bb99d8bd7e6f5b0183b192f30c88d917 Mon Sep 17 00:00:00 2001 From: Bjorn Helgaas Date: Tue, 13 Oct 2026 17:04:20 -0500 Subject: [PATCH 115/139] PCI: Expose devm_pci_alloc_host_bridge_release() devm_pci_alloc_host_bridge() allocates a struct pci_host_bridge and also parses device tree resources and claims them. There are drivers, e.g., pcie-iproc-bcma.c, that need to allocate a pci_host_bridge but do not need device tree resources. Expose devm_pci_alloc_host_bridge_release() so they can avoid the device tree parts by calling pci_alloc_host_bridge() directly and adding the devm release function manually. [bhelgaas: add EXPORT_SYMBOL] Signed-off-by: Bjorn Helgaas Signed-off-by: Manivannan Sadhasivam Reviewed-by: Manivannan Sadhasivam Link: https://patch.msgid.link/20261013220421.1588051-2-bhelgaas@google.com --- drivers/pci/pci.h | 1 + drivers/pci/probe.c | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/pci/pci.h b/drivers/pci/pci.h index ba3c3fddddc2..5e21cd29cc7c 100644 --- a/drivers/pci/pci.h +++ b/drivers/pci/pci.h @@ -502,6 +502,7 @@ enum pci_bar_type { pci_bar_mem64, /* A 64-bit memory BAR */ }; +void devm_pci_alloc_host_bridge_release(void *data); struct device *pci_get_host_bridge_device(struct pci_dev *dev); void pci_put_host_bridge_device(struct device *dev); diff --git a/drivers/pci/probe.c b/drivers/pci/probe.c index 27008e2ea5af..2fb9341090d6 100644 --- a/drivers/pci/probe.c +++ b/drivers/pci/probe.c @@ -696,10 +696,11 @@ struct pci_host_bridge *pci_alloc_host_bridge(size_t priv) } EXPORT_SYMBOL(pci_alloc_host_bridge); -static void devm_pci_alloc_host_bridge_release(void *data) +void devm_pci_alloc_host_bridge_release(void *data) { pci_free_host_bridge(data); } +EXPORT_SYMBOL(devm_pci_alloc_host_bridge_release); struct pci_host_bridge *devm_pci_alloc_host_bridge(struct device *dev, size_t priv) From 9850907ee1dd4376c09c4129e0adbe3b39d191b7 Mon Sep 17 00:00:00 2001 From: Semih Baskan Date: Tue, 13 Oct 2026 17:04:21 -0500 Subject: [PATCH 116/139] PCI: iproc: Use the EROM outbound window on BCMA The PCIe outbound window base on Northstar depends on the PCIe Gen2 core revision. Revision 0x01 uses 0x08000000, 0x40000000 and 0x48000000 for controllers 0 to 2, while revision 0x07 (NS-B0) uses 0x08000000, 0x20000000 and 0x28000000. Broadcom's own driver branches on the core revision for exactly this reason. bcm-ns.dtsi is shared by every Northstar SoC, so it cannot carry a value that is correct on both. Commit 767012397976 ("ARM: dts: BCM5301X: Describe PCIe controllers fully") gave the controllers a ranges property. The commit shipped in v7.1. With that property present, two things go wrong with this driver: 1) devm_pci_alloc_host_bridge() parses those ranges and requests them, then this driver adds its own window and requests the whole list a second time, so every controller fails to probe with -EBUSY. 2) The DT window itself is only correct on core revision 0x07. On revision 0x01 it points at an address the hardware does not decode, and the first MMIO access to a BAR takes an imprecise external abort. The enumeration ROM reports the correct base for the revision actually present, and bcma already provides it as addr_s[0]. To solve 1), use pci_alloc_host_bridge() directly to avoid the request of all the DT resources done in pci_parse_request_of_pci_ranges(). Instead, request the memory window from the EROM. To solve 2), parse the memory resources from DT and emit a warning if it describes a window that doesn't match the window from the EROM. The mismatch means the devicetree describes a window the hardware does not decode, and that should be fixed in the dts rather than ignored silently. The driver is correct whether or not the DT describes a window. The same commit also added compatible = "brcm,iproc-pcie", so these nodes now match pcie-iproc-platform. With CONFIG_PCIE_IPROC_PLATFORM enabled, which is the default on ARCH_BCM_IPROC, that driver binds them first and this driver's probe fails inside devm_pci_alloc_host_bridge(). This commit fixes the configurations where the BCMA driver is the one in use; OpenWrt builds that way, with PCIE_IPROC_PLATFORM disabled. The platform path takes the DT window as-is and has the same wrong address on core revision 0x01, so that side needs a devicetree fix either way. Based on: https://patch.msgid.link/20260807160526.379-1-strst.gs@gmail.com https://patch.msgid.link/20260912043626.437-1-strst.gs@gmail.com Fixes: 767012397976 ("ARM: dts: BCM5301X: Describe PCIe controllers fully") Signed-off-by: Semih Baskan Signed-off-by: Manivannan Sadhasivam Signed-off-by: Bjorn Helgaas Tested-by: Semih Baskan Link: https://patch.msgid.link/20261013220421.1588051-3-bhelgaas@google.com --- drivers/pci/controller/pcie-iproc-bcma.c | 40 ++++++++++++++++++++++-- 1 file changed, 37 insertions(+), 3 deletions(-) diff --git a/drivers/pci/controller/pcie-iproc-bcma.c b/drivers/pci/controller/pcie-iproc-bcma.c index 593418c2bc3a..1e258cd8fed4 100644 --- a/drivers/pci/controller/pcie-iproc-bcma.c +++ b/drivers/pci/controller/pcie-iproc-bcma.c @@ -11,7 +11,9 @@ #include #include #include +#include +#include "../pci.h" #include "pcie-iproc.h" @@ -31,6 +33,29 @@ static int iproc_bcma_pcie_map_irq(const struct pci_dev *dev, u8 slot, u8 pin) return bcma_core_irq(bdev, 5); } +static void iproc_bcma_pcie_check_dt_window(struct iproc_pcie *pcie) +{ + struct device_node *np = pcie->dev->of_node; + struct of_pci_range_parser parser; + struct of_pci_range range; + struct resource res; + + if (!np || of_pci_range_parser_init(&parser, np)) + return; + + for_each_of_pci_range(&parser, &range) { + if ((range.flags & IORESOURCE_TYPE_BITS) != IORESOURCE_MEM) + continue; + + if (of_pci_range_to_resource(&range, np, &res)) + continue; + + if (res.start != pcie->mem.start || res.end != pcie->mem.end) + dev_warn(pcie->dev, "DT window %pR does not match EROM window %pR, using EROM\n", + &res, &pcie->mem); + } +} + static int iproc_bcma_pcie_probe(struct bcma_device *bdev) { struct device *dev = &bdev->dev; @@ -38,10 +63,20 @@ static int iproc_bcma_pcie_probe(struct bcma_device *bdev) struct pci_host_bridge *bridge; int ret; - bridge = devm_pci_alloc_host_bridge(dev, sizeof(*pcie)); + bridge = pci_alloc_host_bridge(sizeof(*pcie)); if (!bridge) return -ENOMEM; + bridge->dev.parent = dev; + + ret = devm_add_action_or_reset(dev, devm_pci_alloc_host_bridge_release, + bridge); + if (ret) + return -ENOMEM; + + bridge->swizzle_irq = pci_common_swizzle; + bridge->map_irq = iproc_bcma_pcie_map_irq; + pcie = pci_host_bridge_priv(bridge); pcie->dev = dev; @@ -59,13 +94,12 @@ static int iproc_bcma_pcie_probe(struct bcma_device *bdev) pcie->mem.end = bdev->addr_s[0] + SZ_128M - 1; pcie->mem.name = "PCIe MEM space"; pcie->mem.flags = IORESOURCE_MEM; + iproc_bcma_pcie_check_dt_window(pcie); pci_add_resource(&bridge->windows, &pcie->mem); ret = devm_request_pci_bus_resources(dev, &bridge->windows); if (ret) return ret; - bridge->map_irq = iproc_bcma_pcie_map_irq; - bcma_set_drvdata(bdev, pcie); return iproc_pcie_setup(pcie, &bridge->windows); From 73d3d81832708137ba6332fc011663feaf97deee Mon Sep 17 00:00:00 2001 From: Wentao Liang Date: Thu, 17 Sep 2026 13:24:59 +0000 Subject: [PATCH 117/139] PCI: xilinx-xdma: Fix device node reference leak in xilinx_pl_dma_pcie_init_irq_domain() xilinx_pl_dma_pcie_init_irq_domain() takes a reference to the PCIe interrupt-controller child node with of_get_child_by_name(), but the error paths after the two irq_domain_create_linear() calls and after xilinx_pl_dma_pcie_init_msi_irq_domain() return directly without dropping it. irq_domain_create_linear() gets its own reference on the fwnode, so the domains do not consume the caller's one. Add an err_put_node label that drops the reference before returning. Fixes: 8d786149d78c ("PCI: xilinx-xdma: Add Xilinx XDMA Root Port driver") Signed-off-by: Wentao Liang Signed-off-by: Manivannan Sadhasivam Signed-off-by: Bjorn Helgaas Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20260917132459.2153240-1-vulab@iscas.ac.cn --- drivers/pci/controller/pcie-xilinx-dma-pl.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/drivers/pci/controller/pcie-xilinx-dma-pl.c b/drivers/pci/controller/pcie-xilinx-dma-pl.c index b037c8f315e4..e18eeecdc8a9 100644 --- a/drivers/pci/controller/pcie-xilinx-dma-pl.c +++ b/drivers/pci/controller/pcie-xilinx-dma-pl.c @@ -581,7 +581,7 @@ static int xilinx_pl_dma_pcie_init_irq_domain(struct pl_dma_pcie *port) port->pldma_domain = irq_domain_create_linear(of_fwnode_handle(pcie_intc_node), 32, &event_domain_ops, port); if (!port->pldma_domain) - return -ENOMEM; + goto err_put_node; irq_domain_update_bus_token(port->pldma_domain, DOMAIN_BUS_NEXUS); @@ -589,7 +589,7 @@ static int xilinx_pl_dma_pcie_init_irq_domain(struct pl_dma_pcie *port) &intx_domain_ops, port); if (!port->intx_domain) { dev_err(dev, "Failed to get a INTx IRQ domain\n"); - return -ENOMEM; + goto err_put_node; } irq_domain_update_bus_token(port->intx_domain, DOMAIN_BUS_WIRED); @@ -597,13 +597,17 @@ static int xilinx_pl_dma_pcie_init_irq_domain(struct pl_dma_pcie *port) ret = xilinx_pl_dma_pcie_init_msi_irq_domain(port); if (ret != 0) { irq_domain_remove(port->intx_domain); - return -ENOMEM; + goto err_put_node; } of_node_put(pcie_intc_node); raw_spin_lock_init(&port->lock); return 0; + +err_put_node: + of_node_put(pcie_intc_node); + return -ENOMEM; } static int xilinx_pl_dma_pcie_setup_irq(struct pl_dma_pcie *port) From f9eabc48327fae33cf0a02a8f312c24505dc413b Mon Sep 17 00:00:00 2001 From: Koichiro Den Date: Thu, 15 Oct 2026 23:27:16 +0900 Subject: [PATCH 118/139] PCI: endpoint: pci-epf-vntb: Track link state from both sides The control-region link status is currently updated only by COMMAND_LINK_UP and COMMAND_LINK_DOWN from the HOST. The virtual NTB link callbacks are empty. Consequently, the HOST can see the link as up before the VHOST has enabled it, and ntb_link_disable() on the VHOST leaves LINK_STATUS_UP set without notifying either NTB client. ntb_netdev can hide this device-level state bug because ntb_transport uses a separate per-QP LINK_DOWN_FLAG message. Use ntb_tool to observe the device link state directly: After bringing both sides up, start one waiter on each side: HOST# echo N > /sys/kernel/debug/ntb_tool//peer0/link_event VHOST# echo N > /sys/kernel/debug/ntb_tool//peer0/link_event While both waiters are blocked, disable the link on the VHOST: VHOST# echo N > /sys/kernel/debug/ntb_tool//link Without this patch, the link-disable command succeeds, but peer0/link remains Y on both sides and neither waiter returns. Track HOST and VHOST enablement separately and report the effective link as up only when both sides have enabled it, as pci-epf-ntb does for its two physical hosts. Both NTB clients observe this effective state, so notify both sides only when it changes, including the side whose request completes the transition. Serialize the two paths because HOST commands run from delayed work while the VHOST callbacks may run concurrently. With this patch, peer0/link changes to N on both sides and both waiters return. Fixes: e35f56bb0330 ("PCI: endpoint: Support NTB transfer between RC and EP") Signed-off-by: Koichiro Den Signed-off-by: Manivannan Sadhasivam Reviewed-by: Frank Li Link: https://lore.kernel.org/r/20261014071448.2473208-1-den@valinux.co.jp/ Link: https://patch.msgid.link/20261015142716.3452900-1-den@valinux.co.jp --- drivers/pci/endpoint/functions/pci-epf-vntb.c | 98 +++++++++++++++---- 1 file changed, 78 insertions(+), 20 deletions(-) diff --git a/drivers/pci/endpoint/functions/pci-epf-vntb.c b/drivers/pci/endpoint/functions/pci-epf-vntb.c index 07ba338732d4..bebdd93a067c 100644 --- a/drivers/pci/endpoint/functions/pci-epf-vntb.c +++ b/drivers/pci/endpoint/functions/pci-epf-vntb.c @@ -41,6 +41,7 @@ #include #include #include +#include #include #include @@ -147,7 +148,10 @@ struct epf_ntb { u16 vntb_pid; u16 vntb_vid; - bool linkup; + /* Serialize HOST and VHOST link state changes. */ + struct mutex link_lock; + bool host_link_enabled; + bool vhost_link_enabled; /* * True when doorbells are interrupt-driven (MSI or embedded), false @@ -183,24 +187,55 @@ static struct pci_epf_header epf_ntb_header = { .interrupt_pin = PCI_INTERRUPT_INTA, }; +static void epf_ntb_update_link(struct epf_ntb *ntb) +{ + u16 link_status = READ_ONCE(ntb->reg->link_status); + + /* The link is usable only after both sides have enabled it. */ + if (ntb->host_link_enabled && ntb->vhost_link_enabled) + link_status |= LINK_STATUS_UP; + else + link_status &= ~LINK_STATUS_UP; + + WRITE_ONCE(ntb->reg->link_status, link_status); +} + +static int epf_ntb_notify_link(struct ntb_dev *ndev) +{ + struct pci_epf *epf = ntb_ndev(ndev)->epf; + int ret; + + ntb_link_event(ndev); + + ret = pci_epc_raise_irq(epf->epc, epf->func_no, epf->vfunc_no, + PCI_IRQ_MSI, EPF_IRQ_LINK + 1); + if (ret) + dev_err(&epf->dev, "Failed to raise link event IRQ: %d\n", ret); + + return ret; +} + /** - * epf_ntb_link_up() - Raise link_up interrupt to Virtual Host (VHOST) + * epf_ntb_set_link_enabled() - Set HOST link enablement * @ntb: NTB device that facilitates communication between HOST and VHOST - * @link_up: true or false indicating Link is UP or Down - * - * Once NTB function in HOST invoke ntb_link_enable(), - * this NTB function driver will trigger a link event to VHOST. + * @enabled: true when the HOST has enabled the link * * Returns: Zero for success, or an error code in case of failure */ -static int epf_ntb_link_up(struct epf_ntb *ntb, bool link_up) +static int epf_ntb_set_link_enabled(struct epf_ntb *ntb, bool enabled) { - if (link_up) - ntb->reg->link_status |= LINK_STATUS_UP; - else - ntb->reg->link_status &= ~LINK_STATUS_UP; + bool notify; + + scoped_guard(mutex, &ntb->link_lock) { + notify = ntb->host_link_enabled != enabled && + ntb->vhost_link_enabled; + ntb->host_link_enabled = enabled; + epf_ntb_update_link(ntb); + } + + if (notify) + return epf_ntb_notify_link(ntb->ntb); - ntb_link_event(ntb->ntb); return 0; } @@ -325,16 +360,14 @@ static void epf_ntb_cmd_handler(struct work_struct *work) ctrl->command_status = COMMAND_STATUS_OK; break; case COMMAND_LINK_UP: - ntb->linkup = true; - ret = epf_ntb_link_up(ntb, true); + ret = epf_ntb_set_link_enabled(ntb, true); if (ret < 0) ctrl->command_status = COMMAND_STATUS_ERROR; else ctrl->command_status = COMMAND_STATUS_OK; goto reset_handler; case COMMAND_LINK_DOWN: - ntb->linkup = false; - ret = epf_ntb_link_up(ntb, false); + ret = epf_ntb_set_link_enabled(ntb, false); if (ret < 0) ctrl->command_status = COMMAND_STATUS_ERROR; else @@ -1472,11 +1505,29 @@ static int vntb_epf_peer_mw_get_addr(struct ntb_dev *ndev, int idx, return 0; } +static int vntb_epf_set_link_enabled(struct ntb_dev *ndev, bool enabled) +{ + struct epf_ntb *ntb = ntb_ndev(ndev); + bool notify; + + scoped_guard(mutex, &ntb->link_lock) { + notify = ntb->vhost_link_enabled != enabled && + ntb->host_link_enabled; + ntb->vhost_link_enabled = enabled; + epf_ntb_update_link(ntb); + } + + if (notify) + return epf_ntb_notify_link(ndev); + + return 0; +} + static int vntb_epf_link_enable(struct ntb_dev *ntb, enum ntb_speed max_speed, enum ntb_width max_width) { - return 0; + return vntb_epf_set_link_enabled(ntb, true); } static u32 vntb_epf_spad_read(struct ntb_dev *ndev, int idx) @@ -1636,7 +1687,7 @@ static u64 vntb_epf_link_is_up(struct ntb_dev *ndev, { struct epf_ntb *ntb = ntb_ndev(ndev); - return ntb->reg->link_status; + return READ_ONCE(ntb->reg->link_status); } static int vntb_epf_db_clear_mask(struct ntb_dev *ndev, u64 db_bits) @@ -1654,7 +1705,7 @@ static int vntb_epf_db_clear(struct ntb_dev *ndev, u64 db_bits) static int vntb_epf_link_disable(struct ntb_dev *ntb) { - return 0; + return vntb_epf_set_link_enabled(ntb, false); } static struct device *vntb_epf_get_dma_dev(struct ntb_dev *ndev) @@ -1797,6 +1848,10 @@ static int epf_ntb_bind(struct pci_epf *epf) goto err_bar_alloc; } + ntb->host_link_enabled = false; + ntb->vhost_link_enabled = false; + ntb->reg->link_status = 0; + ret = epf_ntb_epc_init(ntb); if (ret) { dev_err(dev, "Failed to initialize EPC\n"); @@ -1882,7 +1937,7 @@ static int epf_ntb_probe(struct pci_epf *epf, { struct epf_ntb *ntb; struct device *dev; - int i; + int ret, i; dev = &epf->dev; @@ -1893,6 +1948,9 @@ static int epf_ntb_probe(struct pci_epf *epf, epf->header = &epf_ntb_header; ntb->epf = epf; ntb->vbus_number = 0xff; + ret = devm_mutex_init(dev, &ntb->link_lock); + if (ret) + return ret; INIT_WORK(&ntb->peer_db_work, vntb_epf_peer_db_work); disable_work(&ntb->peer_db_work); From f043083843cac1105206437d8c58e725b6412fad Mon Sep 17 00:00:00 2001 From: Keng-Yu Lin Date: Thu, 15 Oct 2026 10:08:11 +0800 Subject: [PATCH 119/139] PCI/PM: Restore BARs after powering up during enumeration Commit 4d4c10f763d7 ("PCI: Explicitly put devices into D0 when initializing") added pci_pm_power_up_and_verify_state() to pci_pm_init(). The helper ignores pci_power_up()'s return value indicating that BARs need restoring. Its resume callers restore saved configuration afterwards; pci_pm_init() does not. D3hot-to-D0 may reset a device and lose its BARs (PCI PM 1.2, sec 5.4.1). After enumeration powers it up, pci_enable_device() reads D0 and skips the full power-up path. Previously, a device still in D3hot at enable time took that path, including BAR restoration. On AMD Venice SP7, two AMD xHCI controllers [1022:17d2] are in runtime D3hot with No_Soft_Reset clear before kdump. Diagnostic reads on v7.3-rc6-37 show the following for 0000:01:00.4 in the crash kernel: PMCSR BAR0 before the platform power call 0003 f4100004 after the platform power call 0003 f4100004 after native D0 power-up 0000 00000004 The upper BAR DWORD stays zero. pci_power_up() returns 1, but the BAR address remains zero while dev->resource[0] still contains 0xf4100000. Without BAR restoration, the crash kernel hangs during boot. The last console output includes: pci 0000:01:00.4: enabling device (0000 -> 0002) Restoring both controllers' BARs gives matching readbacks and allows kdump to save a vmcore and reboot. An AMD Turin xHCI [1022:1557] shows the same BAR loss; restoring its BAR also allows kdump to complete. Read the initial power state first. Otherwise, pci_power_up() may request a BAR restore based on a cached PCI_UNKNOWN state, even when the device is already in D0. For devices already in D0, only synchronize the platform state so that ACPI calls such as _REG still run. For other states, use pci_set_full_power_state() to power up the device and restore BARs when needed. Fixes: 4d4c10f763d7 ("PCI: Explicitly put devices into D0 when initializing") Suggested-by: Mario Limonciello Suggested-by: Rafael J. Wysocki Link: https://lore.kernel.org/r/CAJZ5v0ie2AvCbdv8-zkpYzHLcTwUPUj+=WWOQjDAPmSepSAQPg@mail.gmail.com/ Signed-off-by: Keng-Yu Lin [bhelgaas: shorten comment] Signed-off-by: Bjorn Helgaas Reviewed-by: Mario Limonciello (AMD) > --- Reviewed-by: Desnes Nunes Reviewed-by: Rafael J. Wysocki (Intel) Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20261015020811.1132314-1-keng-yu.lin@hpe.com --- drivers/pci/pci.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c index b2879a6be5f8..6154a7185c53 100644 --- a/drivers/pci/pci.c +++ b/drivers/pci/pci.c @@ -3255,7 +3255,15 @@ void pci_pm_init(struct pci_dev *dev) } poweron: - pci_pm_power_up_and_verify_state(dev); + pci_update_current_state(dev, PCI_UNKNOWN); + if (dev->current_state == PCI_D0) { + /* No need for full power-up, just sync-up platform state */ + platform_pci_set_power_state(dev, PCI_D0); + } else { + /* Power up the device and restore BARs if necessary */ + pci_set_full_power_state(dev, false); + } + pci_update_current_state(dev, PCI_D0); pm_runtime_forbid(&dev->dev); /* From 2cbfd6c8dcfc9c5539d0ffca22b5609593f93c03 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ilpo=20J=C3=A4rvinen?= Date: Mon, 18 Sep 2023 16:10:53 +0300 Subject: [PATCH 120/139] PCI/ASPM: Disable ASPM when driver requests it MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PCI core/ASPM service driver allows controlling ASPM state through pci_disable_link_state() and pci_enable_link_state() API. It was decided earlier (see the Link below), to not allow ASPM changes when OS does not have control over it but only log a warning about the problem (commit 2add0ec14c25 ("PCI/ASPM: Warn when driver asks to disable ASPM, but we can't do it")). Similarly, if ASPM is not enabled through config, ASPM cannot be disabled. A number of drivers have added workarounds to force ASPM off with own writes into the Link Control Register (some even with comments explaining why PCI core does not disable it under some circumstances). According to the comments, some drivers require ASPM to be off for reliable operation. Having custom ASPM handling in drivers is problematic because the state kept in the ASPM service driver is not updated by the changes made outside the link state management API. As the first step to address this issue, make pci_disable_link_state() unconditionally disable ASPM so the motivation for drivers to come up with custom ASPM handling code is eliminated. Make pci_disable_link_state() function comment to comply kerneldoc formatting while changing the description. [bhelgaas: To take advantage of the PCI core ASPM handling, drivers that need to quirk ASPM should depend on CONFIG_PCIEASPM and have their custom ASPM code removed.] Link: https://lore.kernel.org/all/CANUX_P3F5YhbZX3WGU-j1AGpbXb_T9Bis2ErhvKkFMtDvzatVQ@mail.gmail.com/ Link: https://lore.kernel.org/all/20230511131441.45704-1-ilpo.jarvinen@linux.intel.com/ Signed-off-by: Ilpo Järvinen [bhelgaas: extract just the aspm.c change and update commit log] Signed-off-by: Bjorn Helgaas Link: https://patch.msgid.link/20230918131103.24119-4-ilpo.jarvinen@linux.intel.com --- drivers/pci/pcie/aspm.c | 33 ++++++++++++++++++++------------- 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/drivers/pci/pcie/aspm.c b/drivers/pci/pcie/aspm.c index 95ac34a34bd5..b039b57f8466 100644 --- a/drivers/pci/pcie/aspm.c +++ b/drivers/pci/pcie/aspm.c @@ -1504,16 +1504,23 @@ static int __pci_disable_link_state(struct pci_dev *pdev, int state, bool locked return -EINVAL; /* * A driver requested that ASPM be disabled on this device, but - * if we don't have permission to manage ASPM (e.g., on ACPI + * if we might not have permission to manage ASPM (e.g., on ACPI * systems we have to observe the FADT ACPI_FADT_NO_ASPM bit and - * the _OSC method), we can't honor that request. Windows has - * a similar mechanism using "PciASPMOptOut", which is also - * ignored in this situation. + * the _OSC method), previously we chose to not honor disable + * request in that case. Windows has a similar mechanism using + * "PciASPMOptOut", which is also ignored in this situation. + * + * Not honoring the requests to disable ASPM, however, led to + * drivers forcing ASPM off on their own. As such changes of ASPM + * state are not tracked by this service driver, the state kept here + * became out of sync. + * + * Therefore, honor ASPM disable requests even when OS does not have + * ASPM control. Plain disable for ASPM is assumed to be slightly + * safer than fully managing it. */ - if (aspm_disabled) { - pci_warn(pdev, "can't disable ASPM; OS doesn't have ASPM control\n"); - return -EPERM; - } + if (aspm_disabled) + pci_warn(pdev, "OS doesn't have ASPM control, disabling ASPM anyway\n"); if (!locked) down_read(&pci_bus_sem); @@ -1540,13 +1547,13 @@ int pci_disable_link_state_locked(struct pci_dev *pdev, int state) EXPORT_SYMBOL(pci_disable_link_state_locked); /** - * pci_disable_link_state - Disable device's link state, so the link will - * never enter specific states. Note that if the BIOS didn't grant ASPM - * control to the OS, this does nothing because we can't touch the LNKCTL - * register. Returns 0 or a negative errno. - * + * pci_disable_link_state - Disable device's link state * @pdev: PCI device * @state: ASPM link state to disable + * + * Disable device's link state so the link will never enter specific states. + * + * Return: 0 or a negative errno */ int pci_disable_link_state(struct pci_dev *pdev, int state) { From 440b83f5b733ac4e6f6e3f0706bd718879859d9d Mon Sep 17 00:00:00 2001 From: LECOINTRE Philippe Date: Fri, 2 Oct 2026 08:07:07 +0000 Subject: [PATCH 121/139] PCI: dwc: Correct PCIE_DW_PLAT_EP reference in Kconfig help The Kconfig help text for PCIE_DW_PLAT_HOST and PCIE_DW_PLAT_EP incorrectly refers to PCI_DW_PLAT_EP. Replace the typo with the correct symbol name, PCIE_DW_PLAT_EP, so the help text matches the actual Kconfig option. Signed-off-by: Philippe Lecointre Signed-off-by: Manivannan Sadhasivam Link: https://patch.msgid.link/305ef70ecadb4db9aa42c66607240aba@thalesgroup.com --- drivers/pci/controller/dwc/Kconfig | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/pci/controller/dwc/Kconfig b/drivers/pci/controller/dwc/Kconfig index dcfbe7e229fd..0a0d56fcf0ac 100644 --- a/drivers/pci/controller/dwc/Kconfig +++ b/drivers/pci/controller/dwc/Kconfig @@ -295,7 +295,7 @@ config PCIE_DW_PLAT_HOST Designware IP. This controller can work either as EP or RC. In order to enable host-specific features PCIE_DW_PLAT_HOST must be selected and in - order to enable device-specific features PCI_DW_PLAT_EP must be + order to enable device-specific features PCIE_DW_PLAT_EP must be selected. config PCIE_DW_PLAT_EP @@ -310,7 +310,7 @@ config PCIE_DW_PLAT_EP in Designware IP. This controller can work either as EP or RC. In order to enable host-specific features PCIE_DW_PLAT_HOST must be selected and in - order to enable device-specific features PCI_DW_PLAT_EP must be + order to enable device-specific features PCIE_DW_PLAT_EP must be selected. config PCIE_QCOM_COMMON From 37042d66d842ecc68c6cb26c8fa6e92e91e04a50 Mon Sep 17 00:00:00 2001 From: Shawn Lin Date: Wed, 30 Sep 2026 10:22:09 +0800 Subject: [PATCH 122/139] PCI: dwc: Rename dw_pcie_allocate_domains() to dw_pcie_allocate_msi_domains() Rename dw_pcie_allocate_domains() to dw_pcie_allocate_msi_domains() so that the name says what it allocates and cannot be mistaken for a generic domain allocator. Signed-off-by: Shawn Lin Signed-off-by: Manivannan Sadhasivam Link: https://patch.msgid.link/1790734930-217000-2-git-send-email-shawn.lin@rock-chips.com --- drivers/pci/controller/dwc/pci-keystone.c | 2 +- drivers/pci/controller/dwc/pcie-designware-host.c | 6 +++--- drivers/pci/controller/dwc/pcie-designware.h | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/drivers/pci/controller/dwc/pci-keystone.c b/drivers/pci/controller/dwc/pci-keystone.c index c0806744544e..a08e396de06f 100644 --- a/drivers/pci/controller/dwc/pci-keystone.c +++ b/drivers/pci/controller/dwc/pci-keystone.c @@ -309,7 +309,7 @@ static int ks_pcie_msi_host_init(struct dw_pcie_rp *pp) dw_pcie_writel_dbi(pci, PCI_BASE_ADDRESS_0, ks_pcie->app.start); pp->msi_irq_chip = &ks_pcie_msi_irq_chip; - return dw_pcie_allocate_domains(pp); + return dw_pcie_allocate_msi_domains(pp); } static void ks_pcie_handle_intx_irq(struct keystone_pcie *ks_pcie, diff --git a/drivers/pci/controller/dwc/pcie-designware-host.c b/drivers/pci/controller/dwc/pcie-designware-host.c index 77aa79ef0ccf..5665fbc61809 100644 --- a/drivers/pci/controller/dwc/pcie-designware-host.c +++ b/drivers/pci/controller/dwc/pcie-designware-host.c @@ -207,7 +207,7 @@ static const struct irq_domain_ops dw_pcie_msi_domain_ops = { .free = dw_pcie_irq_domain_free, }; -int dw_pcie_allocate_domains(struct dw_pcie_rp *pp) +int dw_pcie_allocate_msi_domains(struct dw_pcie_rp *pp) { struct dw_pcie *pci = to_dw_pcie_from_pp(pp); struct irq_domain_info info = { @@ -225,7 +225,7 @@ int dw_pcie_allocate_domains(struct dw_pcie_rp *pp) return 0; } -EXPORT_SYMBOL_GPL(dw_pcie_allocate_domains); +EXPORT_SYMBOL_GPL(dw_pcie_allocate_msi_domains); void dw_pcie_free_msi(struct dw_pcie_rp *pp) { @@ -342,7 +342,7 @@ int dw_pcie_msi_host_init(struct dw_pcie_rp *pp) pp->msi_irq_chip = &dw_pci_msi_bottom_irq_chip; - ret = dw_pcie_allocate_domains(pp); + ret = dw_pcie_allocate_msi_domains(pp); if (ret) return ret; diff --git a/drivers/pci/controller/dwc/pcie-designware.h b/drivers/pci/controller/dwc/pcie-designware.h index 61e9c4e979af..374cd53d97f2 100644 --- a/drivers/pci/controller/dwc/pcie-designware.h +++ b/drivers/pci/controller/dwc/pcie-designware.h @@ -870,7 +870,7 @@ void dw_pcie_free_msi(struct dw_pcie_rp *pp); int dw_pcie_setup_rc(struct dw_pcie_rp *pp); int dw_pcie_host_init(struct dw_pcie_rp *pp); void dw_pcie_host_deinit(struct dw_pcie_rp *pp); -int dw_pcie_allocate_domains(struct dw_pcie_rp *pp); +int dw_pcie_allocate_msi_domains(struct dw_pcie_rp *pp); void __iomem *dw_pcie_own_conf_map_bus(struct pci_bus *bus, unsigned int devfn, int where); #else @@ -911,7 +911,7 @@ static inline void dw_pcie_host_deinit(struct dw_pcie_rp *pp) { } -static inline int dw_pcie_allocate_domains(struct dw_pcie_rp *pp) +static inline int dw_pcie_allocate_msi_domains(struct dw_pcie_rp *pp) { return 0; } From 8f0b8cc3a655f56b71e6246d9ddcba71b8b3eefb Mon Sep 17 00:00:00 2001 From: Shawn Lin Date: Wed, 30 Sep 2026 10:22:10 +0800 Subject: [PATCH 123/139] PCI: dwc: Rename pp->irq_domain to pp->msi_irq_domain pp->irq_domain is exclusively the MSI parent irq domain: it is created by dw_pcie_allocate_msi_domains() with msi_create_parent_irq_domain() and is only used to resolve the MSI child virqs in dw_handle_msi_irq() and in the keystone and dra7xx MSI handlers. The name gives no hint about that, in the same way dw_pcie_allocate_domains() did not say that it was only about MSI. Rename it to pp->msi_irq_domain, matching the previous rename. Signed-off-by: Shawn Lin Signed-off-by: Manivannan Sadhasivam Reviewed-by: Vignesh Raghavendra Cc: Vignesh Raghavendra Link: https://patch.msgid.link/1790734930-217000-3-git-send-email-shawn.lin@rock-chips.com --- drivers/pci/controller/dwc/pci-dra7xx.c | 2 +- drivers/pci/controller/dwc/pci-keystone.c | 2 +- drivers/pci/controller/dwc/pcie-designware-host.c | 8 ++++---- drivers/pci/controller/dwc/pcie-designware.h | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/drivers/pci/controller/dwc/pci-dra7xx.c b/drivers/pci/controller/dwc/pci-dra7xx.c index 6ae5b27e27b3..ba1bcfe65172 100644 --- a/drivers/pci/controller/dwc/pci-dra7xx.c +++ b/drivers/pci/controller/dwc/pci-dra7xx.c @@ -214,7 +214,7 @@ static int dra7xx_pcie_handle_msi(struct dw_pcie_rp *pp, int index) pos = find_first_bit(&val, MAX_MSI_IRQS_PER_CTRL); while (pos != MAX_MSI_IRQS_PER_CTRL) { - generic_handle_domain_irq(pp->irq_domain, + generic_handle_domain_irq(pp->msi_irq_domain, (index * MAX_MSI_IRQS_PER_CTRL) + pos); pos++; pos = find_next_bit(&val, MAX_MSI_IRQS_PER_CTRL, pos); diff --git a/drivers/pci/controller/dwc/pci-keystone.c b/drivers/pci/controller/dwc/pci-keystone.c index a08e396de06f..d6fa7141e0a0 100644 --- a/drivers/pci/controller/dwc/pci-keystone.c +++ b/drivers/pci/controller/dwc/pci-keystone.c @@ -633,7 +633,7 @@ static void ks_pcie_msi_irq_handler(struct irq_desc *desc) vector = offset + (pos << 3); dev_dbg(dev, "irq: bit %d, vector %d\n", pos, vector); - generic_handle_domain_irq(pp->irq_domain, vector); + generic_handle_domain_irq(pp->msi_irq_domain, vector); } chained_irq_exit(chip, desc); diff --git a/drivers/pci/controller/dwc/pcie-designware-host.c b/drivers/pci/controller/dwc/pcie-designware-host.c index 5665fbc61809..e387d4aac038 100644 --- a/drivers/pci/controller/dwc/pcie-designware-host.c +++ b/drivers/pci/controller/dwc/pcie-designware-host.c @@ -82,7 +82,7 @@ void dw_handle_msi_irq(struct dw_pcie_rp *pp) continue; for_each_set_bit(pos, &status, MAX_MSI_IRQS_PER_CTRL) - generic_handle_demux_domain_irq(pp->irq_domain, irq_off + pos); + generic_handle_demux_domain_irq(pp->msi_irq_domain, irq_off + pos); } } @@ -217,8 +217,8 @@ int dw_pcie_allocate_msi_domains(struct dw_pcie_rp *pp) .host_data = pp, }; - pp->irq_domain = msi_create_parent_irq_domain(&info, &dw_pcie_msi_parent_ops); - if (!pp->irq_domain) { + pp->msi_irq_domain = msi_create_parent_irq_domain(&info, &dw_pcie_msi_parent_ops); + if (!pp->msi_irq_domain) { dev_err(pci->dev, "Failed to create IRQ domain\n"); return -ENOMEM; } @@ -236,7 +236,7 @@ void dw_pcie_free_msi(struct dw_pcie_rp *pp) irq_set_chained_handler_and_data(pp->msi_irq[ctrl], NULL, NULL); } - irq_domain_remove(pp->irq_domain); + irq_domain_remove(pp->msi_irq_domain); } EXPORT_SYMBOL_GPL(dw_pcie_free_msi); diff --git a/drivers/pci/controller/dwc/pcie-designware.h b/drivers/pci/controller/dwc/pcie-designware.h index 374cd53d97f2..4ebdaa6bbb6d 100644 --- a/drivers/pci/controller/dwc/pcie-designware.h +++ b/drivers/pci/controller/dwc/pcie-designware.h @@ -456,7 +456,7 @@ struct dw_pcie_rp { int irq; const struct dw_pcie_host_ops *ops; int msi_irq[MAX_MSI_CTRLS]; - struct irq_domain *irq_domain; + struct irq_domain *msi_irq_domain; dma_addr_t msi_data; struct irq_chip *msi_irq_chip; u32 num_vectors; From fd3186c0bbb157375532695ab365afe4a2e6fa24 Mon Sep 17 00:00:00 2001 From: Hans Zhang <18255117159@163.com> Date: Wed, 30 Sep 2026 09:38:42 +0800 Subject: [PATCH 124/139] PCI: dwc: Describe DWC PCIe fields with full masks and FIELD_PREP() Several DWC PCIe register fields are described with a shift macro next to their mask, or with only a shift and the field width implied by the value written. Document the field position and width in a single full mask and use FIELD_PREP() to encode it. No functional change intended. Signed-off-by: Hans Zhang <18255117159@163.com> Signed-off-by: Manivannan Sadhasivam Link: https://patch.msgid.link/20260930013842.82476-1-18255117159@163.com --- .../pci/controller/dwc/pcie-designware-ep.c | 2 +- drivers/pci/controller/dwc/pcie-designware.h | 12 ++++------ drivers/pci/controller/dwc/pcie-tegra194.c | 22 +++++++++---------- 3 files changed, 16 insertions(+), 20 deletions(-) diff --git a/drivers/pci/controller/dwc/pcie-designware-ep.c b/drivers/pci/controller/dwc/pcie-designware-ep.c index 0262d85ed390..198823f105c5 100644 --- a/drivers/pci/controller/dwc/pcie-designware-ep.c +++ b/drivers/pci/controller/dwc/pcie-designware-ep.c @@ -1077,7 +1077,7 @@ int dw_pcie_ep_raise_msix_irq_doorbell(struct dw_pcie_ep *ep, u8 func_no, if (!ep_func || !ep_func->msix_cap) return -EINVAL; - msg_data = (func_no << MSIX_DOORBELL_PF_SHIFT) | + msg_data = FIELD_PREP(MSIX_DOORBELL_PF_MASK, func_no) | (interrupt_num - 1); dw_pcie_writel_dbi(pci, MSIX_DOORBELL, msg_data); diff --git a/drivers/pci/controller/dwc/pcie-designware.h b/drivers/pci/controller/dwc/pcie-designware.h index 4ebdaa6bbb6d..9c427f1546c0 100644 --- a/drivers/pci/controller/dwc/pcie-designware.h +++ b/drivers/pci/controller/dwc/pcie-designware.h @@ -85,9 +85,7 @@ /* Common Clock N_FTS */ #define PORT_AFAC_CC_N_FTS(n) FIELD_PREP(PORT_AFAC_CC_N_FTS_MASK, n) #define PORT_AFAC_ENTER_ASPM BIT(30) /* ASPM L1 Entry Control */ -#define PORT_AFAC_L0S_ELS 24 /* L0s Entrance Latency shift */ #define PORT_AFAC_L0S_ELM GENMASK(26, 24) /* L0s Entrance Latency mask */ -#define PORT_AFAC_L1_ELS 27 /* L1 Entrance Latency shift */ #define PORT_AFAC_L1_ELM GENMASK(29, 27) /* L1 Entrance Latency mask */ #define PORT_LINK_CTRL 0x710 /* Port Link Control Register */ @@ -161,7 +159,6 @@ #define GEN3_CTRL_EQ_PHASE_2_3 BIT(9) /* Equalization Phase 2 and Phase 3 */ #define GEN3_CTRL_RXEQ_RGRDLESS_RXTS BIT(13) #define GEN3_CTRL_GEN3_EQ_DISABLE BIT(16) /* Equalization Disable */ -#define GEN3_CTRL_RATE_SHADOW_SEL_SHIFT 24 /* Rate Shadow Select shift */ #define GEN3_CTRL_RATE_SHADOW_SEL_MASK GENMASK(25, 24) /* Rate Shadow Select mask */ #define GEN3_EQ_CTRL 0x8A8 /* Gen3 EQ Control */ @@ -231,7 +228,7 @@ #define ATU_UPPER_LIMIT 0x020 #define MSIX_DOORBELL 0x948 -#define MSIX_DOORBELL_PF_SHIFT 24 +#define MSIX_DOORBELL_PF_MASK GENMASK(28, 24) /* * eDMA CSRs. DW PCIe IP-core v4.70a and older had the eDMA registers accessible @@ -279,15 +276,14 @@ #define RAS_DES_EVENT_COUNTER_CONTROL 0x8 #define EVENT_COUNTER_ALL_CLEAR 0x3 #define EVENT_COUNTER_ENABLE_ALL 0x7 -#define EVENT_COUNTER_ENABLE_SHIFT 2 -#define EVENT_COUNTER_EVENT_SEL_MASK GENMASK(7, 0) -#define EVENT_COUNTER_EVENT_SEL_SHIFT 16 +#define EVENT_COUNTER_ENABLE_MASK GENMASK(4, 2) +#define EVENT_COUNTER_EVENT_SEL_MASK GENMASK(23, 16) +#define EVENT_COUNTER_GROUP_SEL_MASK GENMASK(27, 24) #define EVENT_COUNTER_EVENT_Tx_L0S 0x2 #define EVENT_COUNTER_EVENT_Rx_L0S 0x3 #define EVENT_COUNTER_EVENT_L1 0x5 #define EVENT_COUNTER_EVENT_L1_1 0x7 #define EVENT_COUNTER_EVENT_L1_2 0x8 -#define EVENT_COUNTER_GROUP_SEL_SHIFT 24 #define EVENT_COUNTER_GROUP_5 0x5 #define RAS_DES_EVENT_COUNTER_DATA 0xc diff --git a/drivers/pci/controller/dwc/pcie-tegra194.c b/drivers/pci/controller/dwc/pcie-tegra194.c index 8c610212a180..285a38178da8 100644 --- a/drivers/pci/controller/dwc/pcie-tegra194.c +++ b/drivers/pci/controller/dwc/pcie-tegra194.c @@ -622,10 +622,10 @@ static inline u32 event_counter_prog(struct tegra_pcie_dw *pcie, u32 event) val = dw_pcie_readl_dbi(&pcie->pci, pcie->ras_des_cap + RAS_DES_EVENT_COUNTER_CONTROL); - val &= ~(EVENT_COUNTER_EVENT_SEL_MASK << EVENT_COUNTER_EVENT_SEL_SHIFT); - val |= EVENT_COUNTER_GROUP_5 << EVENT_COUNTER_GROUP_SEL_SHIFT; - val |= event << EVENT_COUNTER_EVENT_SEL_SHIFT; - val |= EVENT_COUNTER_ENABLE_ALL << EVENT_COUNTER_ENABLE_SHIFT; + val &= ~EVENT_COUNTER_EVENT_SEL_MASK; + val |= FIELD_PREP(EVENT_COUNTER_GROUP_SEL_MASK, EVENT_COUNTER_GROUP_5); + val |= FIELD_PREP(EVENT_COUNTER_EVENT_SEL_MASK, event); + val |= FIELD_PREP(EVENT_COUNTER_ENABLE_MASK, EVENT_COUNTER_ENABLE_ALL); dw_pcie_writel_dbi(&pcie->pci, pcie->ras_des_cap + RAS_DES_EVENT_COUNTER_CONTROL, val); val = dw_pcie_readl_dbi(&pcie->pci, pcie->ras_des_cap + @@ -661,8 +661,8 @@ static int aspm_state_cnt(struct seq_file *s, void *data) EVENT_COUNTER_ALL_CLEAR); /* Re-enable counting */ - val = EVENT_COUNTER_ENABLE_ALL << EVENT_COUNTER_ENABLE_SHIFT; - val |= EVENT_COUNTER_GROUP_5 << EVENT_COUNTER_GROUP_SEL_SHIFT; + val = FIELD_PREP(EVENT_COUNTER_ENABLE_MASK, EVENT_COUNTER_ENABLE_ALL); + val |= FIELD_PREP(EVENT_COUNTER_GROUP_SEL_MASK, EVENT_COUNTER_GROUP_5); dw_pcie_writel_dbi(&pcie->pci, pcie->ras_des_cap + RAS_DES_EVENT_COUNTER_CONTROL, val); @@ -680,8 +680,8 @@ static void init_host_aspm(struct tegra_pcie_dw *pcie) PCI_EXT_CAP_ID_VNDR); /* Enable ASPM counters */ - val = EVENT_COUNTER_ENABLE_ALL << EVENT_COUNTER_ENABLE_SHIFT; - val |= EVENT_COUNTER_GROUP_5 << EVENT_COUNTER_GROUP_SEL_SHIFT; + val = FIELD_PREP(EVENT_COUNTER_ENABLE_MASK, EVENT_COUNTER_ENABLE_ALL); + val |= FIELD_PREP(EVENT_COUNTER_GROUP_SEL_MASK, EVENT_COUNTER_GROUP_5); dw_pcie_writel_dbi(pci, pcie->ras_des_cap + RAS_DES_EVENT_COUNTER_CONTROL, val); @@ -715,9 +715,9 @@ static void init_host_aspm(struct tegra_pcie_dw *pcie) /* Program L0s and L1 entrance latencies */ val = dw_pcie_readl_dbi(pci, PORT_AFAC); val &= ~PORT_AFAC_L0S_ELM; - val |= (pcie->aspm_l0s_enter_lat << PORT_AFAC_L0S_ELS); + val |= FIELD_PREP(PORT_AFAC_L0S_ELM, pcie->aspm_l0s_enter_lat); val &= ~PORT_AFAC_L1_ELM; - val |= (pcie->aspm_l1_enter_lat << PORT_AFAC_L1_ELS); + val |= FIELD_PREP(PORT_AFAC_L1_ELM, pcie->aspm_l1_enter_lat); val |= PORT_AFAC_ENTER_ASPM; dw_pcie_writel_dbi(pci, PORT_AFAC, val); } @@ -881,7 +881,7 @@ static void config_gen3_gen4_eq_presets(struct tegra_pcie_dw *pcie) val = dw_pcie_readl_dbi(pci, GEN3_CTRL); val &= ~GEN3_CTRL_RATE_SHADOW_SEL_MASK; - val |= (0x1 << GEN3_CTRL_RATE_SHADOW_SEL_SHIFT); + val |= FIELD_PREP(GEN3_CTRL_RATE_SHADOW_SEL_MASK, 0x1); dw_pcie_writel_dbi(pci, GEN3_CTRL, val); val = dw_pcie_readl_dbi(pci, GEN3_EQ_CTRL); From 4cd6501c50955f5f1c32daec1289ca5138e58ae5 Mon Sep 17 00:00:00 2001 From: Peng Fan Date: Thu, 24 Sep 2026 16:34:28 +0800 Subject: [PATCH 125/139] PCI: dwc: imx6: Use regmap_assign_bits() for conditional set/clear Replace multiple if/else blocks using regmap_set_bits()/regmap_clear_bits() with the simpler regmap_assign_bits() calls. Signed-off-by: Peng Fan Signed-off-by: Manivannan Sadhasivam Reviewed-by: Frank Li Acked-by: Richard Zhu Link: https://patch.msgid.link/20260924083429.461746-1-peng.fan@oss.nxp.com --- drivers/pci/controller/dwc/pci-imx6.c | 32 +++++++-------------------- 1 file changed, 8 insertions(+), 24 deletions(-) diff --git a/drivers/pci/controller/dwc/pci-imx6.c b/drivers/pci/controller/dwc/pci-imx6.c index add370ca944d..e04325d5575c 100644 --- a/drivers/pci/controller/dwc/pci-imx6.c +++ b/drivers/pci/controller/dwc/pci-imx6.c @@ -747,12 +747,8 @@ static int imx_pcie_attach_pd(struct device *dev) static int imx6q_pcie_enable_ref_clk(struct imx_pcie *imx_pcie, bool enable) { - if (enable) - regmap_set_bits(imx_pcie->iomuxc_gpr, IOMUXC_GPR1, - IMX6Q_GPR1_PCIE_REF_CLK_EN); - else - regmap_clear_bits(imx_pcie->iomuxc_gpr, IOMUXC_GPR1, - IMX6Q_GPR1_PCIE_REF_CLK_EN); + regmap_assign_bits(imx_pcie->iomuxc_gpr, IOMUXC_GPR1, + IMX6Q_GPR1_PCIE_REF_CLK_EN, enable); return 0; } @@ -868,12 +864,8 @@ static void imx_pcie_clk_disable(struct imx_pcie *imx_pcie) static int imx6sx_pcie_core_reset(struct imx_pcie *imx_pcie, bool assert) { - if (assert) - regmap_set_bits(imx_pcie->iomuxc_gpr, IOMUXC_GPR12, - IMX6SX_GPR12_PCIE_TEST_POWERDOWN); - else - regmap_clear_bits(imx_pcie->iomuxc_gpr, IOMUXC_GPR12, - IMX6SX_GPR12_PCIE_TEST_POWERDOWN); + regmap_assign_bits(imx_pcie->iomuxc_gpr, IOMUXC_GPR12, + IMX6SX_GPR12_PCIE_TEST_POWERDOWN, assert); /* Force PCIe PHY reset */ regmap_update_bits(imx_pcie->iomuxc_gpr, IOMUXC_GPR5, IMX6SX_GPR5_PCIE_BTNRST_RESET, @@ -883,12 +875,8 @@ static int imx6sx_pcie_core_reset(struct imx_pcie *imx_pcie, bool assert) static int imx6qp_pcie_core_reset(struct imx_pcie *imx_pcie, bool assert) { - if (assert) - regmap_set_bits(imx_pcie->iomuxc_gpr, IOMUXC_GPR1, - IMX6Q_GPR1_PCIE_TEST_PD); - else - regmap_clear_bits(imx_pcie->iomuxc_gpr, IOMUXC_GPR1, - IMX6Q_GPR1_PCIE_TEST_PD); + regmap_assign_bits(imx_pcie->iomuxc_gpr, IOMUXC_GPR1, + IMX6Q_GPR1_PCIE_TEST_PD, assert); regmap_update_bits(imx_pcie->iomuxc_gpr, IOMUXC_GPR1, IMX6Q_GPR1_PCIE_SW_RST, assert ? IMX6Q_GPR1_PCIE_SW_RST : 0); @@ -901,12 +889,8 @@ static int imx6qp_pcie_core_reset(struct imx_pcie *imx_pcie, bool assert) static int imx6q_pcie_core_reset(struct imx_pcie *imx_pcie, bool assert) { - if (assert) - regmap_set_bits(imx_pcie->iomuxc_gpr, IOMUXC_GPR1, - IMX6Q_GPR1_PCIE_TEST_PD); - else - regmap_clear_bits(imx_pcie->iomuxc_gpr, IOMUXC_GPR1, - IMX6Q_GPR1_PCIE_TEST_PD); + regmap_assign_bits(imx_pcie->iomuxc_gpr, IOMUXC_GPR1, + IMX6Q_GPR1_PCIE_TEST_PD, assert); if (!assert) usleep_range(200, 500); From 17c4115cbe99da4d147aee778c308da1d4bb000d Mon Sep 17 00:00:00 2001 From: Koichiro Den Date: Thu, 15 Oct 2026 10:44:01 +0900 Subject: [PATCH 126/139] PCI: endpoint: pci-epf-vntb: Use a local epf pointer in interrupt setup Use a local epf pointer in epf_ntb_configure_interrupt() to shorten the repeated ntb->epf accesses. No functional changes. Signed-off-by: Koichiro Den Signed-off-by: Manivannan Sadhasivam Reviewed-by: Frank Li Link: https://patch.msgid.link/20261015014402.2976188-2-den@valinux.co.jp --- drivers/pci/endpoint/functions/pci-epf-vntb.c | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/drivers/pci/endpoint/functions/pci-epf-vntb.c b/drivers/pci/endpoint/functions/pci-epf-vntb.c index bebdd93a067c..61858e373943 100644 --- a/drivers/pci/endpoint/functions/pci-epf-vntb.c +++ b/drivers/pci/endpoint/functions/pci-epf-vntb.c @@ -546,12 +546,14 @@ static int epf_ntb_config_spad_bar_alloc(struct epf_ntb *ntb) static int epf_ntb_configure_interrupt(struct epf_ntb *ntb) { const struct pci_epc_features *epc_features; + struct pci_epf *epf = ntb->epf; struct device *dev; int ret; - dev = &ntb->epf->dev; + dev = &epf->dev; - epc_features = pci_epc_get_features(ntb->epf->epc, ntb->epf->func_no, ntb->epf->vfunc_no); + epc_features = pci_epc_get_features(epf->epc, epf->func_no, + epf->vfunc_no); if (!(epc_features->msix_capable || epc_features->msi_capable)) { dev_err(dev, "MSI or MSI-X is required for doorbell\n"); @@ -565,10 +567,7 @@ static int epf_ntb_configure_interrupt(struct epf_ntb *ntb) } if (epc_features->msi_capable) { - ret = pci_epc_set_msi(ntb->epf->epc, - ntb->epf->func_no, - ntb->epf->vfunc_no, - 16); + ret = pci_epc_set_msi(epf->epc, epf->func_no, epf->vfunc_no, 16); if (ret) { dev_err(dev, "MSI configuration failed\n"); return ret; From a5401ce3ab2356b7861f53d09205008b7a49d57e Mon Sep 17 00:00:00 2001 From: Koichiro Den Date: Thu, 15 Oct 2026 10:44:02 +0900 Subject: [PATCH 127/139] PCI: endpoint: pci-epf-vntb: Honor MSI-X selection ntb_hw_epf tries MSI-X first and falls back to MSI. It reports the result in COMMAND_CONFIGURE_DOORBELL. pci-epf-vntb ignores MSIX_ENABLE, configures only MSI, and always raises peer interrupts with PCI_IRQ_MSI. When MSI-X is selected, the host does not program MSI, so raising it can issue a write to an invalid address. This was observed as IOMMU faults on the RC, with no doorbell interrupts delivered. Configure MSI-X when supported and use the selected type for peer doorbells and link events. Allocate the MSI-X Table and PBA in the config BAR and configure db_count entries. Use this EPF-owned layout even if the EPC has fixed, hardware-owned Table/PBA regions, as pci-epf-test and nvmet-pci-epf already do. Fixes: e35f56bb0330 ("PCI: endpoint: Support NTB transfer between RC and EP") Signed-off-by: Koichiro Den Signed-off-by: Manivannan Sadhasivam Reviewed-by: Frank Li Link: https://patch.msgid.link/20261015014402.2976188-3-den@valinux.co.jp --- drivers/pci/endpoint/functions/pci-epf-vntb.c | 47 ++++++++++++++++--- 1 file changed, 41 insertions(+), 6 deletions(-) diff --git a/drivers/pci/endpoint/functions/pci-epf-vntb.c b/drivers/pci/endpoint/functions/pci-epf-vntb.c index 61858e373943..f77e28f2fe96 100644 --- a/drivers/pci/endpoint/functions/pci-epf-vntb.c +++ b/drivers/pci/endpoint/functions/pci-epf-vntb.c @@ -152,6 +152,7 @@ struct epf_ntb { struct mutex link_lock; bool host_link_enabled; bool vhost_link_enabled; + bool peer_msix; /* * True when doorbells are interrupt-driven (MSI or embedded), false @@ -159,6 +160,7 @@ struct epf_ntb { */ bool msi_doorbell; u32 spad_size; + u32 msix_table_offset; enum pci_barno epf_ntb_bar[VNTB_BAR_NUM]; @@ -202,13 +204,16 @@ static void epf_ntb_update_link(struct epf_ntb *ntb) static int epf_ntb_notify_link(struct ntb_dev *ndev) { - struct pci_epf *epf = ntb_ndev(ndev)->epf; + struct epf_ntb *ntb = ntb_ndev(ndev); + struct pci_epf *epf = ntb->epf; + unsigned int irq_type; int ret; ntb_link_event(ndev); + irq_type = ntb->peer_msix ? PCI_IRQ_MSIX : PCI_IRQ_MSI; ret = pci_epc_raise_irq(epf->epc, epf->func_no, epf->vfunc_no, - PCI_IRQ_MSI, EPF_IRQ_LINK + 1); + irq_type, EPF_IRQ_LINK + 1); if (ret) dev_err(&epf->dev, "Failed to raise link event IRQ: %d\n", ret); @@ -311,6 +316,7 @@ static void epf_ntb_teardown_mw(struct epf_ntb *ntb, u32 mw) */ static void epf_ntb_cmd_handler(struct work_struct *work) { + const struct pci_epc_features *epc_features; struct epf_ntb_ctrl *ctrl; u32 command, argument; struct epf_ntb *ntb; @@ -343,6 +349,15 @@ static void epf_ntb_cmd_handler(struct work_struct *work) switch (command) { case COMMAND_CONFIGURE_DOORBELL: + epc_features = pci_epc_get_features(ntb->epf->epc, + ntb->epf->func_no, + ntb->epf->vfunc_no); + if (((argument & MSIX_ENABLE) && !epc_features->msix_capable) || + (!(argument & MSIX_ENABLE) && !epc_features->msi_capable)) { + ctrl->command_status = COMMAND_STATUS_ERROR; + break; + } + ntb->peer_msix = argument & MSIX_ENABLE; ctrl->command_status = COMMAND_STATUS_OK; break; case COMMAND_TEARDOWN_DOORBELL: @@ -484,8 +499,9 @@ static void epf_ntb_config_spad_bar_free(struct epf_ntb *ntb) * @ntb: NTB device that facilitates communication between HOST and VHOST * * Allocate the Local Memory mentioned in the above diagram. The size of - * CONFIG REGION is sizeof(struct epf_ntb_ctrl) and size of SCRATCHPAD REGION - * is obtained from "spad-count" configfs entry. + * CONFIG REGION is sizeof(struct epf_ntb_ctrl), plus space for the MSI-X + * Table and PBA if supported. The size of SCRATCHPAD REGION is obtained + * from "spad-count" configfs entry. * * Returns: Zero for success, or an error code in case of failure */ @@ -506,6 +522,12 @@ static int epf_ntb_config_spad_bar_alloc(struct epf_ntb *ntb) spad_count = ntb->spad_count; ctrl_size = ALIGN(sizeof(struct epf_ntb_ctrl), sizeof(u32)); + if (epc_features->msix_capable) { + ntb->msix_table_offset = ALIGN(ctrl_size, 8); + ctrl_size = ntb->msix_table_offset + + ntb->db_count * PCI_MSIX_ENTRY_SIZE + + BITS_TO_U64(ntb->db_count) * sizeof(u64); + } spad_size = 2 * spad_count * sizeof(u32); base = pci_epf_alloc_space(epf, ctrl_size + spad_size, @@ -574,6 +596,17 @@ static int epf_ntb_configure_interrupt(struct epf_ntb *ntb) } } + if (epc_features->msix_capable) { + ret = pci_epc_set_msix(epf->epc, epf->func_no, epf->vfunc_no, + ntb->db_count, + ntb->epf_ntb_bar[BAR_CONFIG], + ntb->msix_table_offset); + if (ret) { + dev_err(dev, "MSI-X configuration failed\n"); + return ret; + } + } + return 0; } @@ -1579,6 +1612,7 @@ static void vntb_epf_peer_db_work(struct work_struct *work) struct epf_ntb *ntb = container_of(work, struct epf_ntb, peer_db_work); struct pci_epf *epf = ntb->epf; unsigned int budget = VNTB_PEER_DB_WORK_BUDGET; + unsigned int irq_type; u8 func_no, vfunc_no; unsigned int db_bit; u32 interrupt_num; @@ -1590,6 +1624,7 @@ static void vntb_epf_peer_db_work(struct work_struct *work) func_no = epf->func_no; vfunc_no = epf->vfunc_no; + irq_type = ntb->peer_msix ? PCI_IRQ_MSIX : PCI_IRQ_MSI; /* * Drain doorbells from peer_db_pending in snapshots (atomic64_xchg()). @@ -1603,7 +1638,7 @@ static void vntb_epf_peer_db_work(struct work_struct *work) while (db_bits) { /* - * pci_epc_raise_irq() for MSI expects a 1-based + * pci_epc_raise_irq() for MSI/MSI-X expects a 1-based * interrupt number. The first usable doorbell starts * at EPF_IRQ_DB_START in the legacy slot layout. * @@ -1623,7 +1658,7 @@ static void vntb_epf_peer_db_work(struct work_struct *work) db_bits &= ~BIT_ULL(db_bit); ret = pci_epc_raise_irq(epf->epc, func_no, vfunc_no, - PCI_IRQ_MSI, interrupt_num); + irq_type, interrupt_num); if (ret) dev_err(&epf->dev, "Failed to raise IRQ for interrupt_num %u: %d\n", From 6b8c12d354dfa9c80d82eea8c0c45d331a28be11 Mon Sep 17 00:00:00 2001 From: Marek Vasut Date: Sun, 4 Oct 2026 03:18:13 +0200 Subject: [PATCH 128/139] PCI: rcar-gen4: Add missing PM ops The R-Car Gen4 PCIe controller is part of a power domain. On R-Car S4 and V4H, this is an always-on power domain which is not shut down in suspend. On R-Car V4M, this is a dedicated A2PCIPHY power domain, which is shut down during suspend, and the controller loses state, which prevents the PCIe from working after resume. Fix this by adding generic suspend/resume noirq ops for the controller, which tear the link down on suspend, and restart it on resume. Use the same PM ops on all of R-Car Gen4 to gracefully suspend and resume the PCIe link on V4H and S4 too. Test case which demonstrates the problem on R-Car V4M: $ hexdump -vC /sys/bus/pci/devices/0000:00:00.0/config > /tmp/pre $ echo s2idle > /sys/power/mem_sleep $ echo platform > /sys/power/pm_test $ echo mem > /sys/power/state $ hexdump -vC /sys/bus/pci/devices/0000:00:00.0/config > /tmp/post $ diff -Naru /tmp/pre /tmp/post ... -00000000 12 19 32 00 07 05 10 00 00 00 04 06 00 00 01 00 +00000000 12 19 32 00 07 05 10 00 00 00 00 ff 00 00 80 00 ^^^^^ Class 0604->00ff Fixes: 0270dbe4f891 ("arm64: dts: renesas: r8a779h0: Add PCIe Host and Endpoint nodes") Reported-by: Geert Uytterhoeven Signed-off-by: Marek Vasut Signed-off-by: Manivannan Sadhasivam Signed-off-by: Bjorn Helgaas Tested-by: Geert Uytterhoeven Tested-by: Koichiro Den Reviewed-by: Geert Uytterhoeven Link: https://patch.msgid.link/20261004011851.866833-1-marek.vasut+renesas@mailbox.org --- drivers/pci/controller/dwc/pcie-rcar-gen4.c | 27 +++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/drivers/pci/controller/dwc/pcie-rcar-gen4.c b/drivers/pci/controller/dwc/pcie-rcar-gen4.c index 21c3dd9b8891..b8237962de18 100644 --- a/drivers/pci/controller/dwc/pcie-rcar-gen4.c +++ b/drivers/pci/controller/dwc/pcie-rcar-gen4.c @@ -671,6 +671,8 @@ static int rcar_gen4_add_dw_pcie_rp(struct rcar_gen4_pcie *rcar) return -ENODEV; pp->num_vectors = MAX_MSI_IRQS; + /* Reserve an iATU window for the generic PME_Turn_Off implementation. */ + pp->use_atu_msg = true; pp->ops = &rcar_gen4_pcie_host_ops; return dw_pcie_host_init(pp); @@ -1062,6 +1064,26 @@ static int rcar_gen4_pcie_ltssm_control(struct rcar_gen4_pcie *rcar, bool enable return 0; } +static int rcar_gen4_pcie_suspend_noirq(struct device *dev) +{ + struct rcar_gen4_pcie *rcar = dev_get_drvdata(dev); + + if (rcar->drvdata->mode != DW_PCIE_RC_TYPE) + return 0; + + return dw_pcie_suspend_noirq(&rcar->dw); +} + +static int rcar_gen4_pcie_resume_noirq(struct device *dev) +{ + struct rcar_gen4_pcie *rcar = dev_get_drvdata(dev); + + if (rcar->drvdata->mode != DW_PCIE_RC_TYPE) + return 0; + + return dw_pcie_resume_noirq(&rcar->dw); +} + static struct rcar_gen4_pcie_drvdata drvdata_r8a779f0_pcie = { .init = rcar_gen4_pcie_common_init, .deinit = rcar_gen4_pcie_common_deinit, @@ -1127,10 +1149,15 @@ static const struct of_device_id rcar_gen4_pcie_of_match[] = { }; MODULE_DEVICE_TABLE(of, rcar_gen4_pcie_of_match); +static DEFINE_NOIRQ_DEV_PM_OPS(rcar_gen4_pcie_pm_ops, + rcar_gen4_pcie_suspend_noirq, + rcar_gen4_pcie_resume_noirq); + static struct platform_driver rcar_gen4_pcie_driver = { .driver = { .name = "pcie-rcar-gen4", .of_match_table = rcar_gen4_pcie_of_match, + .pm = pm_sleep_ptr(&rcar_gen4_pcie_pm_ops), .probe_type = PROBE_PREFER_ASYNCHRONOUS, }, .probe = rcar_gen4_pcie_probe, From c6479aac4f877d2c64b0eb3553ddd529423a9074 Mon Sep 17 00:00:00 2001 From: Deepanshu Kartikey Date: Sun, 9 Aug 2026 10:29:46 +0530 Subject: [PATCH 129/139] PCI/sysfs: Reject unaligned resource I/O port accesses pci_resource_io() validates that off+count stays within the BAR's range but never checks that the resulting port is aligned to the access size. A pwrite64()/pread64() on a resourceN file with an odd offset and count=2 or count=4 reaches outw()/outl() with a misaligned address. On arm64 this becomes a store/load to a Device-memory mapping (PCI_IOBASE + port), which architecturally requires natural alignment, causing an alignment fault and kernel oops. Reject misaligned accesses before they reach the low-level accessor. Reported-by: syzbot+7134530b25073b4ef373@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=7134530b25073b4ef373 Signed-off-by: Deepanshu Kartikey Signed-off-by: Bjorn Helgaas Tested-by: Rihyeon Kim Acked-by: Manivannan Sadhasivam Link: https://patch.msgid.link/20260809045946.15638-1-kartikey406@gmail.com --- drivers/pci/pci-sysfs.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/pci/pci-sysfs.c b/drivers/pci/pci-sysfs.c index 1f21856aac8a..6669e9ad5bd3 100644 --- a/drivers/pci/pci-sysfs.c +++ b/drivers/pci/pci-sysfs.c @@ -1212,6 +1212,9 @@ static ssize_t pci_resource_io(struct file *filp, struct kobject *kobj, if (port + count - 1 > pci_resource_end(pdev, bar)) return -EINVAL; + if (!IS_ALIGNED(port, count)) + return -EINVAL; + switch (count) { case 1: if (write) From ed1d71e41c7b2e755e0ef8e9af9cab9b6b185c4b Mon Sep 17 00:00:00 2001 From: Guangshuo Li Date: Wed, 16 Sep 2026 17:01:26 +0800 Subject: [PATCH 130/139] PCI: qcom-ep: Deinitialize endpoint on remove qcom_pcie_ep_probe() initializes the DesignWare PCIe endpoint with dw_pcie_ep_init(). The probe failure paths call dw_pcie_ep_deinit() to release the corresponding endpoint resources, but the remove path does not perform the matching teardown after a successful probe. As a result, resources initialized by the DesignWare endpoint core can remain allocated when the endpoint controller is removed. Notify endpoint functions about EPC deinitialization and call dw_pcie_ep_deinit() before disabling the controller resources. Keep the existing early return when the link is already disabled since Qualcomm endpoint controllers depend on the host refclk for hardware cleanup. This issue was found by manual code inspection. Fixes: f55fee56a631 ("PCI: qcom-ep: Add Qualcomm PCIe Endpoint controller driver") Signed-off-by: Guangshuo Li Signed-off-by: Manivannan Sadhasivam Signed-off-by: Bjorn Helgaas Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20260916090126.2963064-1-lgs201920130244@gmail.com --- drivers/pci/controller/dwc/pcie-qcom-ep.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/pci/controller/dwc/pcie-qcom-ep.c b/drivers/pci/controller/dwc/pcie-qcom-ep.c index 56184e6ca6e6..28949386b0e8 100644 --- a/drivers/pci/controller/dwc/pcie-qcom-ep.c +++ b/drivers/pci/controller/dwc/pcie-qcom-ep.c @@ -944,6 +944,8 @@ static void qcom_pcie_ep_remove(struct platform_device *pdev) if (pcie_ep->link_status == QCOM_PCIE_EP_LINK_DISABLED) return; + pci_epc_deinit_notify(pcie_ep->pci.ep.epc); + dw_pcie_ep_deinit(&pcie_ep->pci.ep); qcom_pcie_disable_resources(pcie_ep); } From bea2ff3b9de1e32716bc517082b7e3c7330e2151 Mon Sep 17 00:00:00 2001 From: Konrad Dybcio Date: Wed, 16 Sep 2026 14:37:49 +0200 Subject: [PATCH 131/139] PCI: qcom: Ignore -ENODEV after Root Port reset Root Port recovery may race with a device hot-unplug. In that case, dw_pcie_wait_for_link() returns -ENODEV because no device is detected after the controller has been successfully reset. This is a valid outcome and should not cause Root Port recovery to fail. Ignore -ENODEV while continuing to propagate other link-training errors. Fixes: 4d88cb82a6d9 ("PCI: qcom: Implement .reset_root_port() and use for link down") Signed-off-by: Konrad Dybcio Signed-off-by: Manivannan Sadhasivam Signed-off-by: Bjorn Helgaas Reviewed-by: Krishna Chaitanya Chundru Link: https://patch.msgid.link/20260916-topic-pcie_portrec_enodev-v1-1-5dc7bde39552@oss.qualcomm.com Assisted-by: LLM --- drivers/pci/controller/dwc/pcie-qcom.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/pci/controller/dwc/pcie-qcom.c b/drivers/pci/controller/dwc/pcie-qcom.c index 686789ec7ddc..18c96ed96f56 100644 --- a/drivers/pci/controller/dwc/pcie-qcom.c +++ b/drivers/pci/controller/dwc/pcie-qcom.c @@ -1918,6 +1918,9 @@ static int qcom_pcie_reset_root_port(struct pci_host_bridge *bridge, qcom_pcie_start_link(pci); ret = dw_pcie_wait_for_link(pci); + /* This linkdown might have been a hot-unplug */ + if (ret == -ENODEV) + ret = 0; if (ret) return ret; From a67716078b5a172e8c9f43bcc66cec8bafc40637 Mon Sep 17 00:00:00 2001 From: Ayiluri Naga Rashmi Date: Thu, 17 Sep 2026 16:43:28 +0530 Subject: [PATCH 132/139] PCI: qcom: Skip DWC MSI host initialization when DT 'msi-map' is present qcom_pcie_ecam_host_init() unconditionally initializes the DesignWare internal MSI controller and sets use_imsi_rx, irrespective of whether an external MSI controller is described for the host bridge. When an 'msi-map' DT property is present, MSI interrupts are handled by the mapped external MSI controller. Initializing the internal iMSI-RX domain in this case is unnecessary. Set use_imsi_rx based on the presence of 'msi-map' in DT and initialize the DesignWare MSI host only when iMSI-RX is required. Signed-off-by: Ayiluri Naga Rashmi Signed-off-by: Manivannan Sadhasivam Signed-off-by: Bjorn Helgaas Link: https://patch.msgid.link/20260917-skipmsi_forlpi-v1-1-14a4932a4311@oss.qualcomm.com --- drivers/pci/controller/dwc/pcie-qcom.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/drivers/pci/controller/dwc/pcie-qcom.c b/drivers/pci/controller/dwc/pcie-qcom.c index 18c96ed96f56..5cc0925b948d 100644 --- a/drivers/pci/controller/dwc/pcie-qcom.c +++ b/drivers/pci/controller/dwc/pcie-qcom.c @@ -2022,11 +2022,13 @@ static int qcom_pcie_ecam_host_init(struct pci_config_window *cfg) */ raw_spin_lock_init(&pp->lock); - ret = dw_pcie_msi_host_init(pp); - if (ret) - return ret; + pp->use_imsi_rx = !pci_host_of_has_msi_map(dev); + if (pp->use_imsi_rx) { + ret = dw_pcie_msi_host_init(pp); + if (ret) + return ret; + } - pp->use_imsi_rx = true; dw_pcie_msi_init(pp); return devm_add_action_or_reset(dev, qcom_pci_free_msi, pp); From 03ba5646f0f4c26998eb0bbfc9324e003afffb3e Mon Sep 17 00:00:00 2001 From: Alex Elder Date: Wed, 9 Sep 2026 16:55:54 +0200 Subject: [PATCH 133/139] gpio: tc9563: Add support for the embedded GPIO controller Add a driver for the GPIO controller embedded in the Toshiba TC9563 PCIe switch (and the Qualcomm QPS615). The device implements 37 GPIOs using two register banks: three registers control the first 32 GPIOs and three more control GPIOs 32-36. GPIOs 20 and 21 are reserved while GPIOs 22-24, 27-28, 31, and 34 are input-only. Register the driver as an auxiliary device driver. The TC9563 power controller creates the auxiliary device and provides a regmap that gives access to the GPIO registers, so use the gpio-regmap helpers to implement the GPIO chip. Signed-off-by: Alex Elder Co-developed-by: Daniel Thompson Signed-off-by: Daniel Thompson Co-developed-by: Lorenzo Bianconi Signed-off-by: Lorenzo Bianconi [bhelgaas: commit log per https://lore.kernel.org/arQ2mDjqHv4gmoOK@lore-desk] [bhelgaas: squash Geert Uytterhoeven Kconfig fix: https://lore.kernel.org/all/45484c37512a3102495d8b75b6e9f72a141ba42e.1792574560.git.geert+renesas@glider.be, Reviewed-by: Bartosz Golaszewski Acked-by: Lorenzo Bianconi ] Signed-off-by: Bjorn Helgaas Reviewed-by: Linus Walleij Reviewed-by: Manivannan Sadhasivam Acked-by: Bartosz Golaszewski Link: https://patch.msgid.link/20260909-pci-tc9563-aux-v5-2-c9b33f56c8d3@oss.qualcomm.com --- drivers/gpio/Kconfig | 12 ++++ drivers/gpio/Makefile | 1 + drivers/gpio/gpio-tc9563.c | 99 +++++++++++++++++++++++++++++++++ include/linux/soc/qcom/tc9563.h | 16 ++++++ 4 files changed, 128 insertions(+) create mode 100644 drivers/gpio/gpio-tc9563.c create mode 100644 include/linux/soc/qcom/tc9563.h diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig index a48586bb8edb..d429c730161f 100644 --- a/drivers/gpio/Kconfig +++ b/drivers/gpio/Kconfig @@ -1830,6 +1830,18 @@ config GPIO_LTC4283 endmenu +config GPIO_TC9563 + tristate "Toshiba TC9563 GPIO support" + depends on PCI_PWRCTRL_TC9563 || COMPILE_TEST + default m if ARCH_QCOM + select AUXILIARY_BUS + select GPIO_REGMAP + help + This enables support for the GPIO controller embedded in the Toshiba + TC9563 (and Qualcomm QPS615). This device connects to the host + via PCIe port, which is the upstream port on an internal PCIe + switch. + menu "PCI GPIO expanders" depends on PCI diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile index dc9e6d643b5b..792faa2668c9 100644 --- a/drivers/gpio/Makefile +++ b/drivers/gpio/Makefile @@ -182,6 +182,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_TC9563) += gpio-tc9563.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-tc9563.c b/drivers/gpio/gpio-tc9563.c new file mode 100644 index 000000000000..68c20c8cba79 --- /dev/null +++ b/drivers/gpio/gpio-tc9563.c @@ -0,0 +1,99 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2026 by RISCstar Solutions Corporation. All rights reserved. + */ + +/* + * The Toshiba TC9563 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. + * + * 37 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". + * + */ + +#include +#include +#include +#include +#include +#include + +/* + * There are two sets of registers, each representing (up to) 32 GPIOs with a + * stride of 4 bytes (IN1 is 4 bytes past IN0, EN1 is 4 bytes past EN0, etc.). + */ +#define TC9563_GPIO_COUNT 37 +#define TC9563_GPIO_PER_REG 32 +#define TC9563_GPIO_REG_STRIDE 4 + +static int tc9563_gpio_init_valid_mask(struct gpio_chip *gc, + unsigned long *valid_mask, + unsigned int ngpios) +{ + /* GPIOs 20 and 21 are reserved */ + bitmap_fill(valid_mask, ngpios); + bitmap_clear(valid_mask, 20, 2); + + return 0; +} + +static int tc9563_gpio_probe(struct auxiliary_device *adev, + const struct auxiliary_device_id *id) +{ + struct gpio_regmap_config config = { + .parent = &adev->dev, + .ngpio = TC9563_GPIO_COUNT, + .reg_stride = TC9563_GPIO_REG_STRIDE, + .ngpio_per_reg = TC9563_GPIO_PER_REG, + .reg_dat_base = GPIO_REGMAP_ADDR(TC9563_GPIO_IN0_OFFSET), + .reg_set_base = GPIO_REGMAP_ADDR(TC9563_GPIO_OUT0_OFFSET), + .reg_dir_in_base = GPIO_REGMAP_ADDR(TC9563_GPIO_EN0_OFFSET), + .init_valid_mask = tc9563_gpio_init_valid_mask, + }; + DECLARE_BITMAP(fixed_dir_mask, TC9563_GPIO_COUNT); + DECLARE_BITMAP(fixed_dir_out, TC9563_GPIO_COUNT); + + config.regmap = dev_get_platdata(&adev->dev); + if (!config.regmap) + return -EINVAL; + + /* + * Only some of our GPIOs are fixed direction: + * 22, 23, 24, 27, 28, 31, and 34 are input-only. + */ + bitmap_zero(fixed_dir_mask, TC9563_GPIO_COUNT); + bitmap_set(fixed_dir_mask, 22, 3); + bitmap_set(fixed_dir_mask, 27, 2); + set_bit(31, fixed_dir_mask); + set_bit(34, fixed_dir_mask); + config.fixed_direction_mask = fixed_dir_mask; + + bitmap_zero(fixed_dir_out, TC9563_GPIO_COUNT); + config.fixed_direction_output = fixed_dir_out; + + return PTR_ERR_OR_ZERO(devm_gpio_regmap_register(&adev->dev, &config)); +}; + +static const struct auxiliary_device_id tc9563_gpio_ids[] = { + { "pci_pwrctrl_tc9563." TC9563_GPIO_DEV_NAME }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(auxiliary, tc9563_gpio_ids); + +static struct auxiliary_driver tc9563_gpio_driver = { + .name = TC9563_GPIO_DEV_NAME, + .probe = tc9563_gpio_probe, + .id_table = tc9563_gpio_ids, +}; +module_auxiliary_driver(tc9563_gpio_driver); + +MODULE_AUTHOR("Alex Elder "); +MODULE_AUTHOR("Daniel Thompson "); +MODULE_AUTHOR("Lorenzo Bianconi "); +MODULE_DESCRIPTION("Toshiba TC9563 GPIO Driver"); +MODULE_LICENSE("GPL"); diff --git a/include/linux/soc/qcom/tc9563.h b/include/linux/soc/qcom/tc9563.h new file mode 100644 index 000000000000..0dfd25747b9a --- /dev/null +++ b/include/linux/soc/qcom/tc9563.h @@ -0,0 +1,16 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2026 Qualcomm Innovation Center, Inc. All rights reserved. + * Author: Lorenzo Bianconi + */ + +#ifndef __QCOM_TC9563_H +#define __QCOM_TC9563_H + +#define TC9563_GPIO_DEV_NAME "tc9563-gpio" + +#define TC9563_GPIO_IN0_OFFSET 0x801200 +#define TC9563_GPIO_EN0_OFFSET 0x801208 +#define TC9563_GPIO_OUT0_OFFSET 0x801210 + +#endif /* __QCOM_TC9563_H */ From 32184f8b8dc14100bd605e157ab70a5a278d7c1e Mon Sep 17 00:00:00 2001 From: Lorenzo Bianconi Date: Wed, 9 Sep 2026 16:55:53 +0200 Subject: [PATCH 134/139] dt-bindings: PCI: toshiba,tc9563: Document embedded GPIO controller The TC9563 PCIe switch embeds a GPIO controller providing 37 GPIO lines, accessed through the switch's I2C interface. The controller is integrated in the switch rather than being a separate device, so expose it by adding the gpio-controller and #gpio-cells properties to the switch node itself, and extend the example to show the downstream ports' reset-gpios being driven from those lines. Signed-off-by: Lorenzo Bianconi Signed-off-by: Bjorn Helgaas Reviewed-by: Krzysztof Kozlowski Link: https://patch.msgid.link/20260909-pci-tc9563-aux-v5-1-c9b33f56c8d3@oss.qualcomm.com --- .../devicetree/bindings/pci/toshiba,tc9563.yaml | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/Documentation/devicetree/bindings/pci/toshiba,tc9563.yaml b/Documentation/devicetree/bindings/pci/toshiba,tc9563.yaml index f084830c6d0c..c4ebb99f50c3 100644 --- a/Documentation/devicetree/bindings/pci/toshiba,tc9563.yaml +++ b/Documentation/devicetree/bindings/pci/toshiba,tc9563.yaml @@ -31,6 +31,11 @@ properties: description: GPIO controlling the RESX# pin. + gpio-controller: true + + '#gpio-cells': + const: 2 + vdd18-supply: true vdd09-supply: true @@ -128,7 +133,7 @@ examples: ranges; bus-range = <0x01 0xff>; - pcie@0,0 { + tc9563: pcie@0,0 { compatible = "pci1179,0623"; reg = <0x10000 0x0 0x0 0x0 0x0>; @@ -149,6 +154,9 @@ examples: resx-gpios = <&gpio 1 GPIO_ACTIVE_LOW>; + gpio-controller; + #gpio-cells = <2>; + pcie@1,0 { compatible = "pciclass,0604"; reg = <0x20800 0x0 0x0 0x0 0x0>; @@ -158,6 +166,8 @@ examples: ranges; bus-range = <0x03 0xff>; + reset-gpios = <&tc9563 2 GPIO_ACTIVE_LOW>; + toshiba,no-dfe-support; }; @@ -170,6 +180,8 @@ examples: ranges; bus-range = <0x04 0xff>; + reset-gpios = <&tc9563 3 GPIO_ACTIVE_LOW>; + toshiba,tx-amplitude-microvolt = <10>; }; From cf3c9cc3c80da7679d52fadcdcd9bafe5929b117 Mon Sep 17 00:00:00 2001 From: Lorenzo Bianconi Date: Wed, 9 Sep 2026 16:55:55 +0200 Subject: [PATCH 135/139] PCI/pwrctrl: tc9563: Add GPIO auxiliary device support The TC9563 embeds a GPIO controller used for per-port reset signals. Create an auxiliary device for it so the gpio-tc9563 driver can register the GPIO chip and enable DT-based GPIO lookups. Pass the tc9563 regmap to the auxiliary device as its platform data. The pwrctrl driver does not wait for the GPIO chip to be probed. The per-port reset GPIO lookup, returning -EPROBE_DEFER until the chip is registered, is added in the next patch. Signed-off-by: Lorenzo Bianconi [bhelgaas: squash Geert Uytterhoeven Kconfig fix: https://lore.kernel.org/all/45484c37512a3102495d8b75b6e9f72a141ba42e.1792574560.git.geert+renesas@glider.be, Reviewed-by: Bartosz Golaszewski Acked-by: Lorenzo Bianconi ] Signed-off-by: Bjorn Helgaas Reviewed-by: Bartosz Golaszewski Reviewed-by: Manivannan Sadhasivam Reviewed-by: Alex Elder Link: https://patch.msgid.link/20260909-pci-tc9563-aux-v5-3-c9b33f56c8d3@oss.qualcomm.com --- drivers/pci/pwrctrl/Kconfig | 1 + drivers/pci/pwrctrl/pci-pwrctrl-tc9563.c | 80 ++++++++++++++++++++++++ 2 files changed, 81 insertions(+) diff --git a/drivers/pci/pwrctrl/Kconfig b/drivers/pci/pwrctrl/Kconfig index 1952ab4f29b6..a2fc2a359702 100644 --- a/drivers/pci/pwrctrl/Kconfig +++ b/drivers/pci/pwrctrl/Kconfig @@ -29,6 +29,7 @@ config PCI_PWRCTRL_TC9563 select PCI_PWRCTRL default m if ARCH_QCOM depends on I2C + select AUXILIARY_BUS select REGMAP_I2C help Say Y here to enable the PCI Power Control driver of TC9563 PCIe diff --git a/drivers/pci/pwrctrl/pci-pwrctrl-tc9563.c b/drivers/pci/pwrctrl/pci-pwrctrl-tc9563.c index 59ad219c26c0..3fb862105fa5 100644 --- a/drivers/pci/pwrctrl/pci-pwrctrl-tc9563.c +++ b/drivers/pci/pwrctrl/pci-pwrctrl-tc9563.c @@ -4,12 +4,14 @@ */ #include +#include #include #include #include #include #include #include +#include #include #include #include @@ -18,6 +20,7 @@ #include #include #include +#include #include #include @@ -151,6 +154,8 @@ static const struct reg_sequence dsp2_pwroff_seq[] = { {TC9563_PORT_ACCESS_ENABLE, 0x8}, }; +static DEFINE_IDA(tc9563_pwrctrl_ida); + static int tc9563_pwrctrl_disable_port(struct tc9563_pwrctrl *tc9563, enum tc9563_pwrctrl_ports port) { @@ -393,6 +398,77 @@ static int tc9563_pwrctrl_parse_device_dt(struct device_node *node, return 0; } +static void tc9563_pwrctrl_adev_release(struct device *dev) +{ + struct auxiliary_device *adev = to_auxiliary_dev(dev); + + ida_free(&tc9563_pwrctrl_ida, adev->id); + of_node_put(adev->dev.of_node); + kfree(adev); +} + +static void tc9563_pwrctrl_adev_remove(void *data) +{ + struct auxiliary_device *adev = data; + + auxiliary_device_delete(adev); + auxiliary_device_uninit(adev); +} + +static int tc9563_pwrctrl_adev_add(struct device *dev, const char *name, + struct device_node *of_node, + void *priv_data) +{ + struct auxiliary_device *adev; + int id, ret; + + adev = kzalloc_obj(*adev); + if (!adev) + return -ENOMEM; + + id = ida_alloc(&tc9563_pwrctrl_ida, GFP_KERNEL); + if (id < 0) { + kfree(adev); + return id; + } + + adev->id = id; + adev->name = name; + adev->dev.parent = dev; + adev->dev.platform_data = priv_data; + adev->dev.release = tc9563_pwrctrl_adev_release; + adev->dev.of_node = of_node_get(of_node); + dev_set_of_node_reused(&adev->dev); + + ret = auxiliary_device_init(adev); + if (ret) { + ida_free(&tc9563_pwrctrl_ida, id); + 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, tc9563_pwrctrl_adev_remove, adev); +} + +static int tc9563_pwrctrl_add_gpio_adev(struct tc9563_pwrctrl *tc9563) +{ + struct device *dev = tc9563->pwrctrl.dev; + + if (!of_property_read_bool(dev->of_node, "gpio-controller") || + !of_property_present(dev->of_node, "#gpio-cells")) + return 0; + + return tc9563_pwrctrl_adev_add(dev, TC9563_GPIO_DEV_NAME, dev->of_node, + tc9563->regmap); +} + static int tc9563_pwrctrl_power_off(struct pci_pwrctrl *pwrctrl) { struct tc9563_pwrctrl *tc9563 = container_of(pwrctrl, @@ -596,6 +672,10 @@ static int tc9563_pwrctrl_probe(struct platform_device *pdev) tc9563->pwrctrl.power_on = tc9563_pwrctrl_power_on; tc9563->pwrctrl.power_off = tc9563_pwrctrl_power_off; + ret = tc9563_pwrctrl_add_gpio_adev(tc9563); + if (ret) + goto remove_i2c; + ret = devm_pci_pwrctrl_device_set_ready(dev, &tc9563->pwrctrl); if (ret) goto power_off; From 1dc62737b849676eb3af8b2cffa93a3778fa02d0 Mon Sep 17 00:00:00 2001 From: Lorenzo Bianconi Date: Wed, 9 Sep 2026 16:55:56 +0200 Subject: [PATCH 136/139] PCI/pwrctrl: tc9563: Switch per-port reset to GPIO descriptor API Remove the local TC9563_GPIO_MASK and TC9563_GPIO_DEASSERT_BITS definitions, which are no longer used after switching to the GPIO descriptor API. Move TC9563_GPIO_CONFIG and TC9563_RESET_GPIO definitions in tc9563.h header file. Replace the direct regmap-based per-port reset logic in assert_deassert_reset() with gpiod_direction_output() calls, falling back to the legacy regmap approach only when no reset-gpios DT property is present for a given port. Add the reset GPIO pointer to struct tc9563_pwrctrl_cfg and introduce tc9563_pwrctrl_parse_reset_line() to look up reset-gpios from each PCI downstream port child node. The lookup is done lazily at the beginning of power_on(), returning -EPROBE_DEFER until the GPIO chip is registered. Signed-off-by: Lorenzo Bianconi Signed-off-by: Bjorn Helgaas Reviewed-by: Bartosz Golaszewski Reviewed-by: Manivannan Sadhasivam Reviewed-by: Alex Elder Link: https://patch.msgid.link/20260909-pci-tc9563-aux-v5-4-c9b33f56c8d3@oss.qualcomm.com --- drivers/pci/pwrctrl/pci-pwrctrl-tc9563.c | 81 ++++++++++++++++++++---- include/linux/soc/qcom/tc9563.h | 3 + 2 files changed, 71 insertions(+), 13 deletions(-) diff --git a/drivers/pci/pwrctrl/pci-pwrctrl-tc9563.c b/drivers/pci/pwrctrl/pci-pwrctrl-tc9563.c index 3fb862105fa5..59bc0d77d3c4 100644 --- a/drivers/pci/pwrctrl/pci-pwrctrl-tc9563.c +++ b/drivers/pci/pwrctrl/pci-pwrctrl-tc9563.c @@ -26,9 +26,6 @@ #include "../pci.h" -#define TC9563_GPIO_CONFIG 0x801208 -#define TC9563_RESET_GPIO 0x801210 - #define TC9563_PORT_L0S_DELAY 0x82496c #define TC9563_PORT_L1_DELAY 0x824970 @@ -60,9 +57,6 @@ #define TC9563_POWER_CONTROL 0x82b09c #define TC9563_POWER_CONTROL_OVREN 0x82b2c8 -#define TC9563_GPIO_MASK 0xfffffff3 -#define TC9563_GPIO_DEASSERT_BITS 0xc /* Clear to deassert GPIO */ - #define TC9563_TX_MARGIN_MIN_UA 400000 /* @@ -88,6 +82,7 @@ struct tc9563_pwrctrl_cfg { u8 nfts[2]; /* GEN1 & GEN2 */ bool disable_dfe; bool disable_port; + struct gpio_desc *reset; }; #define TC9563_PWRCTL_MAX_SUPPLY 6 @@ -354,16 +349,40 @@ static int tc9563_pwrctrl_set_nfts(struct tc9563_pwrctrl *tc9563, static int tc9563_pwrctrl_assert_deassert_reset(struct tc9563_pwrctrl *tc9563, bool deassert) { - int ret, val; + int i; - ret = regmap_write(tc9563->regmap, TC9563_GPIO_CONFIG, - TC9563_GPIO_MASK); - if (ret) - return ret; + for (i = 0; i < ARRAY_SIZE(tc9563->cfg); i++) { + int err; - val = deassert ? TC9563_GPIO_DEASSERT_BITS : 0; + if (tc9563->cfg[i].reset) { + err = gpiod_direction_output(tc9563->cfg[i].reset, + !deassert); + if (err) + return err; + } else { + /* Fallback: legacy DTS without reset-gpios */ + switch (i) { + case TC9563_DSP1: + case TC9563_DSP2: + err = regmap_clear_bits(tc9563->regmap, + TC9563_GPIO_CONFIG, + BIT(i + 1)); + if (err) + return err; - return regmap_write(tc9563->regmap, TC9563_RESET_GPIO, val); + err = regmap_assign_bits(tc9563->regmap, + TC9563_RESET_GPIO, + BIT(i + 1), deassert); + if (err) + return err; + break; + default: + break; + } + } + } + + return 0; } static int tc9563_pwrctrl_parse_device_dt(struct device_node *node, @@ -398,6 +417,38 @@ static int tc9563_pwrctrl_parse_device_dt(struct device_node *node, return 0; } +static int tc9563_pwrctrl_parse_reset_line(struct tc9563_pwrctrl *tc9563) +{ + enum tc9563_pwrctrl_ports port = TC9563_USP; + struct device *dev = tc9563->pwrctrl.dev; + struct device_node *node = dev->of_node; + + for_each_child_of_node_scoped(node, child) { + struct tc9563_pwrctrl_cfg *cfg; + + if (++port >= TC9563_MAX) + break; + + cfg = &tc9563->cfg[port]; + if (cfg->reset) /* Already discovered */ + continue; + + cfg->reset = devm_fwnode_gpiod_get(dev, of_fwnode_handle(child), + "reset", GPIOD_ASIS, + NULL); + if (IS_ERR(cfg->reset)) { + int err = PTR_ERR(cfg->reset); + + cfg->reset = NULL; + if (err != -ENOENT) + return dev_err_probe(dev, err, + "failed to get reset\n"); + } + } + + return 0; +} + static void tc9563_pwrctrl_adev_release(struct device *dev) { struct auxiliary_device *adev = to_auxiliary_dev(dev); @@ -489,6 +540,10 @@ static int tc9563_pwrctrl_power_on(struct pci_pwrctrl *pwrctrl) struct tc9563_pwrctrl_cfg *cfg; int ret, i; + ret = tc9563_pwrctrl_parse_reset_line(tc9563); + if (ret) + return ret; + ret = regulator_bulk_enable(ARRAY_SIZE(tc9563->supplies), tc9563->supplies); if (ret < 0) diff --git a/include/linux/soc/qcom/tc9563.h b/include/linux/soc/qcom/tc9563.h index 0dfd25747b9a..086f37a40d80 100644 --- a/include/linux/soc/qcom/tc9563.h +++ b/include/linux/soc/qcom/tc9563.h @@ -13,4 +13,7 @@ #define TC9563_GPIO_EN0_OFFSET 0x801208 #define TC9563_GPIO_OUT0_OFFSET 0x801210 +#define TC9563_GPIO_CONFIG TC9563_GPIO_EN0_OFFSET +#define TC9563_RESET_GPIO TC9563_GPIO_OUT0_OFFSET + #endif /* __QCOM_TC9563_H */ From ee450e3a2d334c7e6383d8a55ad2d53b3822bacc Mon Sep 17 00:00:00 2001 From: Alexey Charkov Date: Wed, 30 Sep 2026 20:07:45 +0400 Subject: [PATCH 137/139] PCI/pwrctrl: generic: Fix leaking array from of_regulator_bulk_get_all() of_regulator_bulk_get_all() allocates the consumers array and returns it to the caller, who is expected to free it at an appropriate time. PCI pwrctrl never did, leading to a memory leak. Add an explicit kfree() for the consumers array. Fixes: 75996c92f4de ("PCI/pwrctrl: Add pwrctrl driver for PCI slots") Signed-off-by: Alexey Charkov Signed-off-by: Bjorn Helgaas Reviewed-by: Bartosz Golaszewski Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20260930-pci-pwrctrl-kfree-v1-1-f09bbffd8da3@flipper.net --- drivers/pci/pwrctrl/generic.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/pci/pwrctrl/generic.c b/drivers/pci/pwrctrl/generic.c index d56171e0ee24..4b6cbbb52248 100644 --- a/drivers/pci/pwrctrl/generic.c +++ b/drivers/pci/pwrctrl/generic.c @@ -63,6 +63,7 @@ static void devm_slot_pwrctrl_release(void *data) struct slot_pwrctrl *slot = data; regulator_bulk_free(slot->num_supplies, slot->supplies); + kfree(slot->supplies); } static int slot_pwrctrl_probe(struct platform_device *pdev) From 7bf2ebcb7ca1e8814d3d0e35214a8e4b2193a619 Mon Sep 17 00:00:00 2001 From: Shawn Lin Date: Thu, 8 Oct 2026 16:05:00 +0800 Subject: [PATCH 138/139] PCI/pwrctrl: Only destroy devices created by pwrctrl core pci_pwrctrl_create_device() only creates platform devices for OF nodes that pci_pwrctrl_is_required() accepts, but pci_pwrctrl_destroy_device() unregisters the platform device of every available child node without that check. Nodes under a PCI host controller that are not pwrctrl devices may have been populated by another mechanism. Destroying them here would tear down devices owned by other code. Mirror the check done at creation time so that only devices created by the pwrctrl core are removed. Fixes: 4c4132489201 ("PCI/pwrctrl: Add APIs to create, destroy pwrctrl devices") Signed-off-by: Shawn Lin Signed-off-by: Bjorn Helgaas Reviewed-by: Bartosz Golaszewski Cc: stable@vger.kernel.org Link: https://patch.msgid.link/1791446700-100166-1-git-send-email-shawn.lin@rock-chips.com --- drivers/pci/pwrctrl/core.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/pci/pwrctrl/core.c b/drivers/pci/pwrctrl/core.c index 10d817aefb09..c14df117ece3 100644 --- a/drivers/pci/pwrctrl/core.c +++ b/drivers/pci/pwrctrl/core.c @@ -388,6 +388,9 @@ static void pci_pwrctrl_destroy_device(struct device_node *np) for_each_available_child_of_node_scoped(np, child) pci_pwrctrl_destroy_device(child); + if (!pci_pwrctrl_is_required(np)) + return; + pdev = of_find_device_by_node(np); if (!pdev) return; From b6087ff2b7a4c9f98c78f56f0f357557fc008768 Mon Sep 17 00:00:00 2001 From: Lorenzo Bianconi Date: Wed, 9 Sep 2026 17:28:29 +0200 Subject: [PATCH 139/139] PCI/pwrctrl: Power off child devices on power-on failure pci_pwrctrl_power_on_device() recursively powers on the child nodes of a device before powering on the device itself. If powering on a child fails (e.g. returning -EPROBE_DEFER), the function returned immediately, leaving the sibling children that were already powered on in the same loop iteration enabled. Since probe deferral is a standard boot path, this leads to unbounded regulator and clock enable increments across retries. The error path in pci_pwrctrl_power_on_devices() only powers off the top-level children processed before the failing one and explicitly stops at it, so it can never clean up the siblings leaked deeper in the failed subtree. Power off the sibling children that were successfully powered on before a child fails, and power off all children if the device's own power_on() fails after its children were already enabled. Switch to a manual child iterator so the failing node can be tracked and its reference released in the error path. Fixes: b35cf3b6aa1e ("PCI/pwrctrl: Add APIs to power on/off pwrctrl devices") Signed-off-by: Lorenzo Bianconi Signed-off-by: Bjorn Helgaas Reviewed-by: Bartosz Golaszewski Reviewed-by: Manivannan Sadhasivam Link: https://patch.msgid.link/20260909-pwrtctl-power-on-rallback-v1-1-412ef996ba27@oss.qualcomm.com --- drivers/pci/pwrctrl/core.c | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/drivers/pci/pwrctrl/core.c b/drivers/pci/pwrctrl/core.c index c14df117ece3..0e2387cf4657 100644 --- a/drivers/pci/pwrctrl/core.c +++ b/drivers/pci/pwrctrl/core.c @@ -252,14 +252,16 @@ static int __pci_pwrctrl_power_on_device(struct device *dev) static int pci_pwrctrl_power_on_device(struct device_node *np) { struct platform_device *pdev; + struct device_node *child; int ret = 0; - for_each_available_child_of_node_scoped(np, child) { + for_each_available_child_of_node(np, child) { ret = pci_pwrctrl_power_on_device(child); if (ret) - return ret; + goto err_power_off; } + child = NULL; if (!pci_pwrctrl_is_required(np)) return 0; @@ -279,6 +281,19 @@ static int pci_pwrctrl_power_on_device(struct device_node *np) platform_device_put(pdev); + if (ret) + goto err_power_off; + + return 0; + +err_power_off: + for_each_available_child_of_node_scoped(np, tmp) { + if (tmp == child) + break; + pci_pwrctrl_power_off_device(tmp); + } + of_node_put(child); + return ret; }