[PATCH v2 4/8] PCI: rzg3s-host: Add Initial PCIe Host Driver for Renesas RZ/G3S SoC

Manivannan Sadhasivam mani at kernel.org
Fri Jun 20 12:35:21 PDT 2025


On Wed, Jun 18, 2025 at 11:12:28PM +0530, Manivannan Sadhasivam wrote:
> On Fri, May 30, 2025 at 02:19:13PM +0300, Claudiu wrote:
> > From: Claudiu Beznea <claudiu.beznea.uj at bp.renesas.com>
> > 
> > The Renesas RZ/G3S features a PCIe IP that complies with the PCI Express
> > Base Specification 4.0 and supports speeds of up to 5 GT/s. It functions
> > only as a root complex, with a single-lane (x1) configuration. The
> > controller includes Type 1 configuration registers, as well as IP
> > specific registers (called AXI registers) required for various adjustments.
> > 
> > Signed-off-by: Claudiu Beznea <claudiu.beznea.uj at bp.renesas.com>
> > ---

[...]

> > +static int rzg3s_pcie_msi_setup(struct rzg3s_pcie_host *host)
> > +{
> > +	size_t size = RZG3S_PCI_MSI_INT_NR * sizeof(u32);
> > +	struct rzg3s_pcie_msi *msi = &host->msi;
> > +	struct device *dev = host->dev;
> > +	int id, ret;
> > +
> > +	msi->pages = __get_free_pages(GFP_KERNEL | GFP_DMA, 0);
> > +	if (!msi->pages)
> > +		return -ENOMEM;
> > +
> > +	msi->dma_addr = dma_map_single(dev, (void *)msi->pages, size * 2,
> > +				       DMA_BIDIRECTIONAL);
> > +	if (dma_mapping_error(dev, msi->dma_addr)) {
> > +		ret = -ENOMEM;
> > +		goto free_pages;
> > +	}

Why can't you use dma_alloc_coherent()?

> > +
> > +	/*
> > +	 * According to the RZ/G3S HW manual (Rev.1.10, section 34.4.5.2 Setting
> > +	 * the MSI Window) the MSI window need to be within any AXI window. Find
> > +	 * an AXI window to setup the MSI window.
> > +	 */
> > +	for (id = 0; id < RZG3S_MAX_WINDOWS; id++) {
> > +		u64 base, basel, baseu;
> > +		u64 mask, maskl, masku;
> > +
> > +		basel = readl(host->axi + RZG3S_PCI_AWBASEL(id));
> > +		/* Skip checking this AXI window if it's not enabled */
> > +		if (!(basel & RZG3S_PCI_AWBASEL_WIN_ENA))
> > +			continue;
> > +
> > +		baseu = readl(host->axi + RZG3S_PCI_AWBASEU(id));
> > +		base = baseu << 32 | basel;
> > +
> > +		maskl = readl(host->axi + RZG3S_PCI_AWMASKL(id));
> > +		masku = readl(host->axi + RZG3S_PCI_AWMASKU(id));
> > +		mask = masku << 32 | maskl;
> > +
> > +		if (msi->dma_addr < base || msi->dma_addr > base + mask)
> > +			continue;
> > +
> > +		break;
> > +	}
> > +
> > +	if (id == RZG3S_MAX_WINDOWS) {
> > +		ret = -EINVAL;
> > +		goto dma_unmap;
> > +	}
> > +
> > +	/* The MSI base address need to be aligned to the MSI size */
> > +	msi->window_base = ALIGN(msi->dma_addr, size);
> > +	if (msi->window_base < msi->dma_addr) {
> > +		ret = -EINVAL;
> > +		goto dma_unmap;
> > +	}
> > +
> > +	rzg3s_pcie_msi_hw_setup(host);
> > +
> > +	return 0;
> > +
> > +dma_unmap:
> > +	dma_unmap_single(dev, msi->dma_addr, size * 2, DMA_BIDIRECTIONAL);
> > +free_pages:
> > +	free_pages(msi->pages, 0);
> > +	return ret;
> > +}
> > +
> > +static int rzg3s_pcie_msi_enable(struct rzg3s_pcie_host *host)
> > +{
> > +	struct platform_device *pdev = to_platform_device(host->dev);
> > +	struct rzg3s_pcie_msi *msi = &host->msi;
> > +	struct device *dev = host->dev;
> > +	const char *devname;
> > +	int irq, ret;
> > +
> > +	mutex_init(&msi->map_lock);
> > +
> > +	irq = platform_get_irq_byname(pdev, "msi");
> > +	if (irq < 0)
> > +		return dev_err_probe(dev, irq ? irq : -EINVAL,
> > +				     "Failed to get MSI IRQ!\n");
> > +
> > +	devname = devm_kasprintf(dev, GFP_KERNEL, "%s-msi", dev_name(dev));
> > +	if (!devname)
> > +		return -ENOMEM;
> > +
> > +	ret = rzg3s_pcie_msi_allocate_domains(msi);
> > +	if (ret)
> > +		return ret;
> > +
> > +	ret = devm_request_irq(dev, irq, rzg3s_pcie_msi_irq, 0, devname, host);
> > +	if (ret) {
> > +		dev_err_probe(dev, ret, "Failed to request IRQ: %d\n", ret);
> > +		goto free_domains;
> > +	}
> > +

So you've modelled INTx as hierarchial IRQ domain, but not MSI. May I know why?
Both are chained to GIC, isn't it?

> > +	ret = rzg3s_pcie_msi_setup(host);
> > +	if (ret) {
> > +		dev_err_probe(dev, ret, "Failed to setup MSI!\n");
> > +		goto free_domains;
> > +	}
> > +
> > +	return 0;
> > +
> > +free_domains:
> > +	rzg3s_pcie_msi_free_domains(msi);
> > +	return ret;
> > +}
> > +

[...]

> > +static int rzg3s_soc_pcie_init_phy(struct rzg3s_pcie_host *host)
> > +{
> > +	static const u32 xcfgd_settings[RZG3S_PCI_PHY_XCFGD_NUM] = {
> > +		[8]  = 0xe0006801, 0x007f7e30, 0x183e0000, 0x978ff500,
> > +		       0xec000000, 0x009f1400, 0x0000d009,
> > +		[17] = 0x78000000,
> > +		[19] = 0x00880000, 0x000005c0, 0x07000000, 0x00780920,
> > +		       0xc9400ce2, 0x90000c0c, 0x000c1414, 0x00005034,
> > +		       0x00006000, 0x00000001,
> > +	};
> > +	static const u32 xcfga_cmn_settings[RZG3S_PCI_PHY_XCFGA_CMN_NUM] = {
> > +		0x00000d10, 0x08310100, 0x00c21404, 0x013c0010, 0x01874440,
> > +		0x1a216082, 0x00103440, 0x00000080, 0x00000010, 0x0c1000c1,
> > +		0x1000c100, 0x0222000c, 0x00640019, 0x00a00028, 0x01d11228,
> > +		0x0201001d,
> > +	};
> > +	static const u32 xcfga_rx_settings[RZG3S_PCI_PHY_XCFGA_RX_NUM] = {
> > +		0x07d55000, 0x030e3f00, 0x00000288, 0x102c5880, 0x0000000b,
> > +		0x04141441, 0x00641641, 0x00d63d63, 0x00641641, 0x01970377,
> > +		0x00190287, 0x00190028, 0x00000028,
> > +	};
> > +
> > +	writel(RZG3S_PCI_PERM_PIPE_PHY_REG_EN, host->axi + RZG3S_PCI_PERM);
> > +
> > +	for (u8 i = 0; i < RZG3S_PCI_PHY_XCFGD_NUM; i++)
> > +		writel(xcfgd_settings[i], host->axi + RZG3S_PCI_PHY_XCFGD(i));
> > +
> > +	for (u8 i = 0; i < RZG3S_PCI_PHY_XCFGA_CMN_NUM; i++) {
> > +		writel(xcfga_cmn_settings[i],
> > +		       host->axi + RZG3S_PCI_PHY_XCFGA_CMN(i));
> > +	}
> > +
> > +	for (u8 i = 0; i < RZG3S_PCI_PHY_XCFGA_RX_NUM; i++) {
> > +		writel(xcfga_rx_settings[i],
> > +		       host->axi + RZG3S_PCI_PHY_XCFGA_RX(i));
> > +	}
> > +
> > +	writel(0x107, host->axi + RZG3S_PCI_PHY_XCFGA_TX);
> > +
> > +	/* Select PHY settings values */
> > +	writel(RZG3S_PCI_PHY_XCFG_CTRL_PHYREG_SEL,
> > +	       host->axi + RZG3S_PCI_PHY_XCFG_CTRL);
> > +
> > +	writel(0, host->axi + RZG3S_PCI_PERM);
> > +
> > +	return 0;
> > +}

Why didn't these go into a PHY driver? Please provide justification.

> > +
> > +static void rzg3s_pcie_pm_runtime_put(void *data)
> > +{
> > +	pm_runtime_put_sync(data);
> > +}
> > +

[...]

> > +static int rzg3s_pcie_probe(struct platform_device *pdev)
> > +{
> > +	struct pci_host_bridge *bridge;
> > +	struct device *dev = &pdev->dev;
> > +	struct device_node *np = dev->of_node;
> > +	struct device_node *sysc_np __free(device_node) =
> > +		of_parse_phandle(np, "renesas,sysc", 0);
> > +	struct rzg3s_pcie_host *host;
> > +	int ret;
> > +
> > +	bridge = devm_pci_alloc_host_bridge(dev, sizeof(*host));
> > +	if (!bridge)
> > +		return -ENOMEM;
> > +
> > +	host = pci_host_bridge_priv(bridge);
> > +	host->dev = dev;
> > +	host->data = device_get_match_data(dev);
> > +	platform_set_drvdata(pdev, host);
> > +
> > +	host->axi = devm_platform_ioremap_resource(pdev, 0);
> > +	if (IS_ERR(host->axi))
> > +		return PTR_ERR(host->axi);
> > +	host->pcie = host->axi + RZG3S_PCI_CFG_BASE;
> > +
> > +	ret = of_property_read_u32(np, "vendor-id", &host->vendor_id);
> > +	if (ret)
> > +		return ret;
> > +
> > +	ret = of_property_read_u32(np, "device-id", &host->device_id);
> > +	if (ret)
> > +		return ret;
> > +
> > +	host->sysc = syscon_node_to_regmap(sysc_np);
> > +	if (IS_ERR(host->sysc))
> > +		return PTR_ERR(host->sysc);
> > +
> > +	ret = regmap_update_bits(host->sysc, RZG3S_SYS_PCIE_RST_RSM_B,
> > +				 RZG3S_SYS_PCIE_RST_RSM_B_MASK,
> > +				 FIELD_PREP(RZG3S_SYS_PCIE_RST_RSM_B_MASK, 1));
> > +	if (ret)
> > +		return ret;
> > +
> > +	ret = devm_add_action_or_reset(dev, rzg3s_pcie_sysc_signal_action,
> > +				       host->sysc);
> > +	if (ret)
> > +		return ret;
> > +
> > +	ret = rzg3s_pcie_resets_prepare(host);
> > +	if (ret)
> > +		return ret;
> > +
> > +	ret = devm_pm_runtime_enable(dev);
> > +	if (ret)
> > +		return ret;
> > +
> > +	ret = pm_runtime_resume_and_get(dev);
> > +	if (ret)
> > +		return ret;

Why do you need runtime PM? Do you need to enable any parent domain before
intializing the controller?

> > +
> > +	ret = devm_add_action_or_reset(dev, rzg3s_pcie_pm_runtime_put, dev);
> > +	if (ret)
> > +		return ret;
> > +

Why is this needed?

> > +	raw_spin_lock_init(&host->hw_lock);
> > +
> > +	ret = rzg3s_pcie_host_setup(host, rzg3s_pcie_intx_setup,
> > +				    rzg3s_pcie_msi_enable, true);
> > +	if (ret)
> > +		return ret;
> > +
> > +	bridge->sysdata = host;
> > +	bridge->ops = &rzg3s_pcie_root_ops;
> > +	bridge->child_ops = &rzg3s_pcie_child_ops;
> > +	ret = pci_host_probe(bridge);
> > +	if (ret)
> > +		return ret;
> > +
> > +	return devm_add_action_or_reset(dev, rzg3s_pcie_host_remove_action,
> > +					host);

Is this a workaround for not having a .remove() callback?

> > +}
> > +
> > +static int rzg3s_pcie_suspend_noirq(struct device *dev)
> > +{
> > +	struct rzg3s_pcie_host *host = dev_get_drvdata(dev);
> > +	const struct rzg3s_pcie_soc_data *data = host->data;
> > +	struct regmap *sysc = host->sysc;
> > +	int ret;
> > +
> > +	ret = pm_runtime_put_sync(dev);
> > +	if (ret)
> > +		return ret;
> > +
> > +	ret = reset_control_bulk_assert(data->num_power_resets,
> > +					host->power_resets);
> > +	if (ret)
> > +		goto rpm_restore;
> > +
> > +	ret = reset_control_bulk_assert(data->num_cfg_resets,
> > +					host->cfg_resets);
> > +	if (ret)
> > +		goto power_resets_restore;
> > +
> > +	ret = regmap_update_bits(sysc, RZG3S_SYS_PCIE_RST_RSM_B,
> > +				 RZG3S_SYS_PCIE_RST_RSM_B_MASK,
> > +				 FIELD_PREP(RZG3S_SYS_PCIE_RST_RSM_B_MASK, 0));
> > +	if (ret)
> > +		goto cfg_resets_restore;

Don't you need to control the endpoint state here? Like putting it into D3Cold,
toggling PERST#, enabling wakeup etc...?

> > +
> > +	return 0;
> > +
> > +	/* Restore the previous state if any error happens */
> > +cfg_resets_restore:
> > +	reset_control_bulk_deassert(data->num_cfg_resets,
> > +				    host->cfg_resets);
> > +power_resets_restore:
> > +	reset_control_bulk_deassert(data->num_power_resets,
> > +				    host->power_resets);
> > +rpm_restore:
> > +	pm_runtime_resume_and_get(dev);
> > +	return ret;
> > +}
> > +
> > +static int rzg3s_pcie_resume_noirq(struct device *dev)
> > +{
> > +	struct rzg3s_pcie_host *host = dev_get_drvdata(dev);
> > +	const struct rzg3s_pcie_soc_data *data = host->data;
> > +	struct regmap *sysc = host->sysc;
> > +	int ret;
> > +
> > +	ret = regmap_update_bits(sysc, RZG3S_SYS_PCIE_RST_RSM_B,
> > +				 RZG3S_SYS_PCIE_RST_RSM_B_MASK,
> > +				 FIELD_PREP(RZG3S_SYS_PCIE_RST_RSM_B_MASK, 1));
> > +	if (ret)
> > +		return ret;
> > +
> > +	/*
> > +	 * According to the RZ/G3S HW manual (Rev.1.10, section
> > +	 * 34.5.1.2 De-asserting the Reset) the PCIe IP needs to wait 5ms from
> > +	 * power on to the de-assertion of reset.
> > +	 */
> > +	usleep_range(5000, 5100);
> > +	ret = reset_control_bulk_deassert(data->num_power_resets,
> > +					  host->power_resets);
> > +	if (ret)
> > +		goto assert_rst_rsm_b;
> > +
> > +	ret = pm_runtime_resume_and_get(dev);
> > +	if (ret)
> > +		goto assert_power_resets;
> > +
> > +	ret = rzg3s_pcie_host_setup(host, NULL, rzg3s_pcie_msi_hw_setup, false);
> > +	if (ret)
> > +		goto rpm_put;
> > +
> > +	return 0;
> > +
> > +	/*
> > +	 * If any error happens there is no way to recover the IP. Put it in the
> > +	 * lowest possible power state.
> > +	 */
> > +rpm_put:
> > +	pm_runtime_put_sync(dev);
> > +assert_power_resets:
> > +	reset_control_bulk_assert(data->num_power_resets,
> > +				  host->power_resets);
> > +assert_rst_rsm_b:
> > +	regmap_update_bits(sysc, RZG3S_SYS_PCIE_RST_RSM_B,
> > +			   RZG3S_SYS_PCIE_RST_RSM_B_MASK,
> > +			   FIELD_PREP(RZG3S_SYS_PCIE_RST_RSM_B_MASK, 0));
> > +	return ret;
> > +}
> > +
> > +static const struct dev_pm_ops rzg3s_pcie_pm_ops = {
> > +	NOIRQ_SYSTEM_SLEEP_PM_OPS(rzg3s_pcie_suspend_noirq,
> > +				  rzg3s_pcie_resume_noirq)
> > +};
> > +
> > +const char * const rzg3s_soc_power_resets[] = {
> > +	"aresetn", "rst_cfg_b", "rst_load_b",
> > +};
> > +
> > +const char * const rzg3s_soc_cfg_resets[] = {
> > +	"rst_b", "rst_ps_b", "rst_gp_b", "rst_rsm_b",
> > +};
> > +
> > +static const struct rzg3s_pcie_soc_data rzg3s_soc_data = {
> > +	.power_resets = rzg3s_soc_power_resets,
> > +	.num_power_resets = ARRAY_SIZE(rzg3s_soc_power_resets),
> > +	.cfg_resets = rzg3s_soc_cfg_resets,
> > +	.num_cfg_resets = ARRAY_SIZE(rzg3s_soc_cfg_resets),
> > +	.init_phy = rzg3s_soc_pcie_init_phy,
> > +};

Are you expecting these callbacks to be different in next gen SoCs? I'd
recommend to get rid of callbacks until the support for newer SoCs get added.

> > +
> > +static const struct of_device_id rzg3s_pcie_of_match[] = {
> > +	{
> > +		.compatible = "renesas,r9a08g045s33-pcie",
> > +		.data = &rzg3s_soc_data,
> > +	},
> > +	{},
> > +};
> > +
> > +static struct platform_driver rzg3s_pcie_driver = {
> > +	.driver = {
> > +		.name = "rzg3s-pcie-host",
> > +		.of_match_table = rzg3s_pcie_of_match,
> > +		.pm = pm_ptr(&rzg3s_pcie_pm_ops),
> > +	},
> > +	.probe = rzg3s_pcie_probe,

You haven't implemented .remove(), but didn't set '.suppress_bind_attrs = true'.
PCI controller drivers acting as an IRQCHIP are not safe to be removed.

- Mani

-- 
மணிவண்ணன் சதாசிவம்



More information about the linux-arm-kernel mailing list