Thread (30 messages) 30 messages, 6 authors, 2026-05-06

Re: [PATCH 5/5] PCI: rzg3s-host: Add support for RZ/V2H(P) SoC

From: "Lad, Prabhakar" <prabhakar.csengg@gmail.com>
Date: 2026-03-25 11:53:44
Also in: linux-pci, linux-renesas-soc, lkml

Hi Claudiu,

Thank you for the review.

On Wed, Mar 25, 2026 at 10:18 AM Claudiu Beznea
[off-list ref] wrote:
Hi, Prabhakar,

On 3/18/26 14:44, Prabhakar wrote:
quoted
From: Lad Prabhakar <prabhakar.mahadev-lad.rj@bp.renesas.com>

Add support for the RZ/V2H(P) SoC PCIe controller to the rzg3s-host
driver.

The RZ/V2H(P) SoC features two independent PCIe channels that share
physical lanes. The hardware supports two configuration modes: single
x4 mode where one controller uses all four lanes, or dual x2 mode
where both controllers use two lanes each.

Introduce configure_lanes() function pointer to configure the PCIe
lanes based on the number of channels enabled. Implement
rzv2h_pcie_configure_lanes() to detect the active PCIe channels at
boot time and program the lane mode via the system controller using
the new RZG3S_SYSC_FUNC_ID_LINK_MASTER function ID.

Signed-off-by: Lad Prabhakar <prabhakar.mahadev-lad.rj@bp.renesas.com>
---
  drivers/pci/controller/pcie-rzg3s-host.c | 142 +++++++++++++++++++++++
  1 file changed, 142 insertions(+)
diff --git a/drivers/pci/controller/pcie-rzg3s-host.c b/drivers/pci/controller/pcie-rzg3s-host.c
index a629e861bbd0..d1bf1e750d9b 100644
--- a/drivers/pci/controller/pcie-rzg3s-host.c
+++ b/drivers/pci/controller/pcie-rzg3s-host.c
@@ -179,6 +179,16 @@
  /* Timeouts experimentally determined */
  #define RZG3S_REQ_ISSUE_TIMEOUT_US          2500

+/**
+ * enum rzg3s_sysc_link_mode - PCIe link configuration modes
+ * @RZG3S_SYSC_LINK_MODE_SINGLE_X4: Single port with x4 lanes
+ * @RZG3S_SYSC_LINK_MODE_DUAL_X2: Dual ports with x2 lanes each
+ */
+enum rzg3s_sysc_link_mode {
+     RZG3S_SYSC_LINK_MODE_SINGLE_X4 = 1,
+     RZG3S_SYSC_LINK_MODE_DUAL_X2 = 3,
+};
+
  /**
   * struct rzg3s_sysc_function - System Controller function descriptor
   * @offset: Register offset from the System Controller base address
@@ -194,12 +204,14 @@ struct rzg3s_sysc_function {
   * @RZG3S_SYSC_FUNC_ID_RST_RSM_B: RST_RSM_B SYSC function ID
   * @RZG3S_SYSC_FUNC_ID_L1_ALLOW: L1 allow SYSC function ID
   * @RZG3S_SYSC_FUNC_ID_MODE: Mode SYSC function ID
+ * @RZG3S_SYSC_FUNC_ID_LINK_MASTER: Link master SYSC function ID
   * @RZG3S_SYSC_FUNC_ID_MAX: Max SYSC function ID
   */
  enum rzg3s_sysc_func_id {
      RZG3S_SYSC_FUNC_ID_RST_RSM_B,
      RZG3S_SYSC_FUNC_ID_L1_ALLOW,
      RZG3S_SYSC_FUNC_ID_MODE,
+     RZG3S_SYSC_FUNC_ID_LINK_MASTER,
      RZG3S_SYSC_FUNC_ID_MAX,
  };
@@ -261,6 +273,7 @@ struct rzg3s_pcie_host;
   * @config_pre_init: Optional callback for SoC-specific pre-configuration
   * @config_post_init: Callback for SoC-specific post-configuration
   * @config_deinit: Callback for SoC-specific de-initialization
+ * @setup_lanes: Callback for setting up the number of lanes
   * @power_resets: array with the resets that need to be de-asserted after
   *                power-on
   * @cfg_resets: array with the resets that need to be de-asserted after
@@ -268,17 +281,20 @@ struct rzg3s_pcie_host;
   * @sysc_info: System Controller info for each PCIe channel
   * @num_power_resets: number of power resets
   * @num_cfg_resets: number of configuration resets
+ * @num_channels: number of PCIe channels
   */
  struct rzg3s_pcie_soc_data {
      int (*init_phy)(struct rzg3s_pcie_host *host);
      void (*config_pre_init)(struct rzg3s_pcie_host *host);
      int (*config_post_init)(struct rzg3s_pcie_host *host);
      int (*config_deinit)(struct rzg3s_pcie_host *host);
+     int (*setup_lanes)(struct rzg3s_pcie_host *host);
      const char * const *power_resets;
      const char * const *cfg_resets;
      struct rzg3s_sysc_info sysc_info[RZG3S_PCIE_CHANNEL_ID_MAX];
      u8 num_power_resets;
      u8 num_cfg_resets;
+     u8 num_channels;
  };

  /**
@@ -309,6 +325,7 @@ struct rzg3s_pcie_port {
   * @intx_irqs: INTx interrupts
   * @max_link_speed: maximum supported link speed
   * @channel_id: PCIe channel identifier, used for System Controller access
+ * @num_lanes: The number of lanes
   */
  struct rzg3s_pcie_host {
      void __iomem *axi;
@@ -325,6 +342,7 @@ struct rzg3s_pcie_host {
      int intx_irqs[PCI_NUM_INTX];
      int max_link_speed;
      enum rzg3s_pcie_channel_id channel_id;
+     u8 num_lanes;
  };

  #define rzg3s_msi_to_host(_msi)     container_of(_msi, struct rzg3s_pcie_host, msi)
@@ -1155,6 +1173,13 @@ static int rzg3s_pcie_config_init(struct rzg3s_pcie_host *host)
      rzg3s_pcie_update_bits(host->pcie, PCI_CLASS_REVISION, mask,
                             field_prep(mask, PCI_CLASS_BRIDGE_PCI_NORMAL));

+     if (host->num_lanes) {
+             rzg3s_pcie_update_bits(host->pcie + RZG3S_PCI_CFG_PCIEC,
+                                    PCI_EXP_LNKCAP, PCI_EXP_LNKCAP_MLW,
+                                    FIELD_PREP(PCI_EXP_LNKCAP_MLW,
+                                               host->num_lanes));
+     }
+
      /* Disable access control to the CFGU */
      writel_relaxed(0, host->axi + RZG3S_PCI_PERM);
@@ -1687,6 +1712,63 @@ rzg3s_pcie_host_setup(struct rzg3s_pcie_host *host,
      return ret;
  }

+static int rzg3s_pcie_get_controller_id(struct rzg3s_pcie_host *host)
+{
+     struct device_node *np = host->dev->of_node;
+     u32 domain;
+     int ret;
+
+     if (host->data->num_channels == 1)
+             return 0;
+
+     ret = of_property_read_u32(np, "linux,pci-domain", &domain);
This introduces some limits in the systems with RZ/V2H(P) SoCs with regards to
the usage of linux,pci-domain. I would like the PCIe maintainers take on this.
+ DT maintainers too.
As this is necessary to index in the system controller driver specific data (as
there are different SYSC offsets for different PCIe controllers) I see the
following alternatives, if any:

1/ add a dedicated DT property for this, e.g. renesas,pcie-controller-id
2/ Add dedicated DT bindings for RZ/V2H(P) SoC that would be used to specify the
    system controller register offset and mask for different functionalities.

    E.g.:
    renesas,sysc-l1-allow = <&sysc 0x1020 0x1>;
    renesas,sysc-mode = <&sysc 0x1024 0x1>;
    renesas,sysc-link-master = <&sysc 0x1060 0x300>;

    And use them in each controller DT node. E.g.:

    pcie0: pcie@add1 {
        // ...

        renesas,sysc-l1-allow = <&sysc 0x1020 0x1>;
        renesas,sysc-mode = <&sysc 0x1024 0x1>;
        renesas,sysc-link-master = <&sysc 0x1060 0x300>;

        // ...
    };

    pcie0: pcie@add1 {
        // ...

        renesas,sysc-l1-allow = <&sysc 0x1050 0x1>;
        renesas,sysc-mode = <&sysc 0x1054 0x1>;
        renesas,sysc-link-master = <&sysc 0x1060 0x300>;

        // ...
    };
The current approach is being used to align with the existing driver
design and to reuse the already available DT property, rather than
introducing a new one.

Regarding option #2, I don’t see this as a scalable solution. For
every new register, we would need to introduce a separate DT property,
which would quickly become unwieldy and harder to maintain.

Im ok, with option #1 or any other suggestion based on feedback of
PCIe and DT maintainers.
3/ as sashiko.dev mentions [1], using aliases for the PCIe nodes should also be
    what you need here.

[1]
https://sashiko.dev/#/patchset/20260318124450.163471-1-prabhakar.mahadev-lad.rj%40bp.renesas.com
quoted
+     if (ret)
+             return ret;
+
+     if (domain >= host->data->num_channels)
+             return -EINVAL;
+
+     host->channel_id = domain;
+
+     return 0;
+}
+
+static int rzv2h_pcie_setup_lanes(struct rzg3s_pcie_host *host)
+{
+     struct device_node *np = host->dev->of_node;
+     static u8 rzv2h_num_total_lanes;
+     u32 num_lanes;
+     int ret;
+
+     ret = of_property_read_u32(np, "num-lanes", &num_lanes);
+     if (ret)
+             return ret;
+
+     /*
+      * RZ/V2H(P) supports up to 4 lanes, but only in single x4 mode.
+      * Dual x2 mode is only supported with 2 total lanes. Validate
+      * the configuration to avoid conflicts with other host, if any.
+      */
+     if (num_lanes != 4 && num_lanes != 2)
+             return -EINVAL;
+
+     if (rzv2h_num_total_lanes == 2 && num_lanes != 2)
+             return -EINVAL;
+
+     if (rzv2h_num_total_lanes == 4)
+             return -EINVAL;
+
+     rzv2h_num_total_lanes += num_lanes;
There is a a valid concern raised by sashiko.dev [1] with regards to
incrementing this if later the probe fails:

from [1]:
"For example, if rzg3s_pcie_resets_prepare_and_get() returns -EPROBE_DEFER,
the static variable is never decremented. On subsequent probe retries,
the variable will be artificially inflated, eventually causing the bounds
check to fail and returning a permanent -EINVAL. This would also prevent
driver unbind and rebind from working correctly."
The other alternative would be the below, where we wouldn't need to
use the num-lanes property but would need a comparison with the DT
compatible,

+       for_each_compatible_node(np, NULL, "renesas,r9a09g057-pcie") {
+               if (of_device_is_available(np))
+                       count++;
+       }
+       if (!count)
+               return 0;
+
+       /* If both PCIe channels are enabled configure the LINK_MASTER
in x2 lane mode.
+        * If only one channel is enabled check the port index and if
port1 is enabled
+        * configure the LINK_MASTER in x2 lane mode, otherwise keep
it in x4 lane mode.
+        */
+       if (count == RZV2H_MAX_PCIE_PORTS ||
+           (count == 1 && host->channel == 1))
+               host->link_mode = RZV2H_PCIE_MODE_DUAL_X2;
+       else
+               host->link_mode = RZV2H_PCIE_MODE_SINGLE_X4;
also:

"Additionally, since the driver sets .probe_type = PROBE_PREFER_ASYNCHRONOUS,
could multiple PCIe controllers probing concurrently cause a data race when
reading and modifying this static variable without locking?"
quoted
+
+     host->num_lanes = num_lanes;
+
+     return rzg3s_sysc_config_func(host->sysc,
+                                   RZG3S_SYSC_FUNC_ID_LINK_MASTER,
+                                   num_lanes == 2 ?
+                                   RZG3S_SYSC_LINK_MODE_DUAL_X2 :
+                                   RZG3S_SYSC_LINK_MODE_SINGLE_X4);
I think this one should also be configured on resume (to have the same
configuration sequence as in probe) even though RZ/V2H(P) don't currently
support s2ram. E.g. so something like:

if (host->num_lanes) {
        ret = rzg3s_sysc_config_func(host->sysc,
                                     RZG3S_SYSC_FUNC_ID_LINK_MASTER,
                                     host->num_lanes == 2  ?
                                     RZG3S_SYSC_LINK_MODE_DUAL_X2 :
                                     RZG3S_SYSC_LINK_MODE_SINGLE_X4);
        if (ret)
                goto assert_rst_rsm_b;
}

after ret = rzg3s_sysc_config_func(sysc, RZG3S_SYSC_FUNC_ID_RST_RSM_B, 1);
Ok.

Cheers,
Prabhakar
Keyboard shortcuts
hback out one level
jnext message in thread
kprevious message in thread
ldrill in
Escclose help / fold thread tree
?toggle this help