[PATCH v2 7/7] ufs: Add UFS driver for Renesas R-Car X5H

Neil Armstrong neil.armstrong at linaro.org
Tue Oct 28 16:33:26 CET 2025


On 10/28/25 15:22, Marek Vasut wrote:
> From: Tuyen Dang <tuyen.dang.xa at renesas.com>
> 
> Add UFS driver for UFS controller present on Renesas R-Car X5H R8A78000.
> The controller uses different initialization code compared to previous
> generation UFS controller present in Renesas R-Car S4 R8A779F0, and the
> majority of the driver is the initialization, hence a new driver.
> 
> Signed-off-by: Yoshihiro Shimoda <yoshihiro.shimoda.uh at renesas.com>
> Signed-off-by: Tuyen Dang <tuyen.dang.xa at renesas.com>
> Signed-off-by: Marek Vasut <marek.vasut+renesas at mailbox.org>
> [Marek: Clean driver up, add SCMI reset handling, use read_poll_timeout(),
>          pass error values out of ufs_renesas_pre_init(), change the
> 	compatible string to "renesas,r8a78000-ufs" to align with
> 	previous generation "renesas,r8a779f0-ufs"]
> ---
> Cc: Bhupesh Sharma <bhupesh.linux at gmail.com>
> Cc: Casey Connolly <casey.connolly at linaro.org>
> Cc: Michal Simek <michal.simek at amd.com>
> Cc: Neha Malcom Francis <n-francis at ti.com>
> Cc: Neil Armstrong <neil.armstrong at linaro.org>
> Cc: Nobuhiro Iwamatsu <iwamatsu at nigauri.org>
> Cc: Sumit Garg <sumit.garg at kernel.org>
> Cc: Tom Rini <trini at konsulko.com>
> Cc: Tuyen Dang <tuyen.dang.xa at renesas.com>
> Cc: Venkatesh Yadav Abbarapu <venkatesh.abbarapu at amd.com>
> Cc: Yoshihiro Shimoda <yoshihiro.shimoda.uh at renesas.com>
> Cc: u-boot-qcom at groups.io
> Cc: u-boot at lists.denx.de
> ---
> V2: No change
> ---
>   drivers/ufs/Kconfig                 |   9 +
>   drivers/ufs/Makefile                |   1 +
>   drivers/ufs/ufs-renesas-rcar-gen5.c | 268 ++++++++++++++++++++++++++++
>   3 files changed, 278 insertions(+)
>   create mode 100644 drivers/ufs/ufs-renesas-rcar-gen5.c
> 
> diff --git a/drivers/ufs/Kconfig b/drivers/ufs/Kconfig
> index 4c6b08ad31c..363f53a6792 100644
> --- a/drivers/ufs/Kconfig
> +++ b/drivers/ufs/Kconfig
> @@ -56,4 +56,13 @@ config UFS_RENESAS
>   	  platform driver. UFS host on Renesas needs some vendor
>   	  specific configuration before accessing the hardware.
>   
> +config UFS_RENESAS_GEN5
> +	bool "Renesas R-Car X5H UFS Controller support"
> +	depends on UFS
> +	select BOUNCE_BUFFER
> +	help
> +	  This selects the Renesas X5H specific additions to UFSHCD
> +	  platform driver. UFS host on Renesas needs some vendor
> +	  specific configuration before accessing the hardware.
> +
>   endmenu
> diff --git a/drivers/ufs/Makefile b/drivers/ufs/Makefile
> index 17ca89e886f..07c94f03429 100644
> --- a/drivers/ufs/Makefile
> +++ b/drivers/ufs/Makefile
> @@ -10,3 +10,4 @@ obj-$(CONFIG_TI_J721E_UFS) += ti-j721e-ufs.o
>   obj-$(CONFIG_UFS_AMD_VERSAL2) += ufs-amd-versal2.o ufshcd-dwc.o
>   obj-$(CONFIG_UFS_PCI) += ufs-pci.o
>   obj-$(CONFIG_UFS_RENESAS) += ufs-renesas.o
> +obj-$(CONFIG_UFS_RENESAS_GEN5) += ufs-renesas-rcar-gen5.o
> diff --git a/drivers/ufs/ufs-renesas-rcar-gen5.c b/drivers/ufs/ufs-renesas-rcar-gen5.c
> new file mode 100644
> index 00000000000..3c66022579f
> --- /dev/null
> +++ b/drivers/ufs/ufs-renesas-rcar-gen5.c
> @@ -0,0 +1,268 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * Renesas UFS host controller driver
> + *
> + * Copyright (C) 2025 Renesas Electronics Corporation
> + */
> +
> +#include <clk.h>
> +#include <dm.h>
> +#include <ufs.h>
> +#include <asm/io.h>
> +#include <dm/device_compat.h>
> +#include <linux/iopoll.h>
> +#include <reset.h>
> +
> +#include "ufs.h"
> +
> +#define UFS_RENESAS_TIMEOUT_US		100000
> +
> +struct ufs_renesas_priv {
> +	struct clk_bulk		clks;
> +	struct reset_ctl_bulk	resets;
> +	fdt_addr_t		phy_base;
> +	/* The hardware needs initialization once */
> +	bool			initialized;
> +};
> +
> +static void ufs_dme_command(struct ufs_hba *hba, u32 cmd,
> +			    u32 arg1, u32 arg2, u32 arg3)
> +{
> +	ufshcd_writel(hba, arg1, REG_UIC_COMMAND_ARG_1);
> +	ufshcd_writel(hba, arg2, REG_UIC_COMMAND_ARG_2);
> +	ufshcd_writel(hba, arg3, REG_UIC_COMMAND_ARG_3);
> +	ufshcd_writel(hba, cmd, REG_UIC_COMMAND);
> +}
> +
> +static int ufs_renesas_pre_init(struct ufs_hba *hba)
> +{
> +	struct ufs_renesas_priv *priv = dev_get_priv(hba->dev);
> +	u32 val32;
> +	u16 val16;
> +	int ret;
> +
> +	writew(0x0001, priv->phy_base + 0x20000);
> +	writew(0x005c, priv->phy_base + 0x20212);
> +	writew(0x005c, priv->phy_base + 0x20214);
> +	writew(0x005c, priv->phy_base + 0x20216);
> +	writew(0x005c, priv->phy_base + 0x20218);
> +	writew(0x036a, priv->phy_base + 0x201d0);
> +	writew(0x0102, priv->phy_base + 0x201d2);
> +	writew(0x001f, priv->phy_base + 0x20082);
> +	writew(0x000b, priv->phy_base + 0x20084);
> +	writew(0x0126, priv->phy_base + 0x201d2);
> +	writew(0x01dc, priv->phy_base + 0x20214);
> +	writew(0x01dc, priv->phy_base + 0x20218);
> +	writew(0x0000, priv->phy_base + 0x201cc);
> +	writew(0x0200, priv->phy_base + 0x201ce);
> +	writew(0x0000, priv->phy_base + 0x20212);
> +	writew(0x0000, priv->phy_base + 0x20216);
> +
> +	ret = readw_poll_timeout(priv->phy_base + 0x201ec, val16,
> +				 !(val16 & BIT(12)), UFS_RENESAS_TIMEOUT_US);
> +	if (ret)
> +		return ret;
> +
> +	ret = readw_poll_timeout(priv->phy_base + 0x201e4, val16,
> +				 !(val16 & BIT(12)), UFS_RENESAS_TIMEOUT_US);
> +	if (ret)
> +		return ret;
> +
> +	ret = readw_poll_timeout(priv->phy_base + 0x201f0, val16,
> +				 !(val16 & BIT(12)), UFS_RENESAS_TIMEOUT_US);
> +	if (ret)
> +		return ret;
> +
> +	writew(0x0000, priv->phy_base + 0x20000);
> +
> +	ufshcd_writel(hba, BIT(0), REG_CONTROLLER_ENABLE);
> +
> +	ret = read_poll_timeout(ufshcd_readl, val32, (val32 & BIT(0)),
> +				1, UFS_RENESAS_TIMEOUT_US,
> +				hba, REG_CONTROLLER_ENABLE);
> +	if (ret)
> +		return ret;
> +
> +	ret = read_poll_timeout(ufshcd_readl, val32, (val32 & BIT(3)),
> +				1, UFS_RENESAS_TIMEOUT_US,
> +				hba, REG_CONTROLLER_STATUS);
> +	if (ret)
> +		return ret;
> +
> +	/* Skip IE because we cannot handle interrupts here */
> +	ufs_dme_command(hba, 0x00000002, 0x81010000, 0x00000000, 0x00000005);
> +	ufs_dme_command(hba, 0x00000002, 0x81150000, 0x00000000, 0x00000001);
> +	ufs_dme_command(hba, 0x00000002, 0x81180000, 0x00000000, 0x00000001);
> +	ufs_dme_command(hba, 0x00000002, 0x80090000, 0x00000000, 0x00000000);
> +	ufs_dme_command(hba, 0x00000002, 0x800a0000, 0x00000000, 0x000000c8);
> +	ufs_dme_command(hba, 0x00000002, 0x80090001, 0x00000000, 0x00000000);
> +	ufs_dme_command(hba, 0x00000002, 0x800a0001, 0x00000000, 0x000000c8);
> +	ufs_dme_command(hba, 0x00000002, 0x800a0004, 0x00000000, 0x00000000);
> +	ufs_dme_command(hba, 0x00000002, 0x800b0004, 0x00000000, 0x00000064);
> +	ufs_dme_command(hba, 0x00000002, 0x800a0005, 0x00000000, 0x00000000);
> +	ufs_dme_command(hba, 0x00000002, 0x800b0005, 0x00000000, 0x00000064);
> +	ufs_dme_command(hba, 0x00000002, 0xd0850000, 0x00000000, 0x00000001);
> +
> +	writew(0x0001, priv->phy_base + 0x20000);
> +
> +	clrbits_le16(priv->phy_base + 0x20022, BIT(0));
> +
> +	ret = readw_poll_timeout(priv->phy_base + (0x00198 << 1), val16,
> +				 (val16 & BIT(0)), UFS_RENESAS_TIMEOUT_US);
> +	if (ret)
> +		return ret;
> +
> +	writew(0x0368, priv->phy_base + 0x201d0);
> +
> +	ret = readw_poll_timeout(priv->phy_base + 0x201e4, val16,
> +				 !(val16 & BIT(11)), UFS_RENESAS_TIMEOUT_US);
> +	if (ret)
> +		return ret;
> +
> +	ret = readw_poll_timeout(priv->phy_base + 0x201e8, val16,
> +				 !(val16 & BIT(11)), UFS_RENESAS_TIMEOUT_US);
> +	if (ret)
> +		return ret;
> +
> +	ret = readw_poll_timeout(priv->phy_base + 0x201ec, val16,
> +				 !(val16 & BIT(11)), UFS_RENESAS_TIMEOUT_US);
> +	if (ret)
> +		return ret;
> +
> +	ret = readw_poll_timeout(priv->phy_base + 0x201f0, val16,
> +				 !(val16 & BIT(11)), UFS_RENESAS_TIMEOUT_US);
> +	if (ret)
> +		return ret;
> +
> +	priv->initialized = true;
> +
> +	return 0;
> +}
> +
> +static int ufs_renesas_init(struct ufs_hba *hba)
> +{
> +	hba->quirks |= UFSHCD_QUIRK_BROKEN_64BIT_ADDRESS | UFSHCD_QUIRK_HIBERN_FASTAUTO |
> +		       UFSHCD_QUIRK_BROKEN_LCC;
> +
> +	return 0;
> +}
> +
> +static int ufs_renesas_hce_enable_notify(struct ufs_hba *hba,
> +					 enum ufs_notify_change_status status)
> +{
> +	struct ufs_renesas_priv *priv = dev_get_priv(hba->dev);
> +	int ret;
> +
> +	if (priv->initialized)
> +		return 0;
> +
> +	if (status == PRE_CHANGE) {
> +		ret = ufs_renesas_pre_init(hba);
> +		if (ret)
> +			return ret;
> +	}
> +
> +	priv->initialized = true;
> +
> +	return 0;
> +}
> +
> +static int ufs_renesas_link_startup_notify(struct ufs_hba *hba,
> +					   enum ufs_notify_change_status status)
> +{
> +	if (status == PRE_CHANGE)
> +		return ufshcd_dme_set(hba, UIC_ARG_MIB(PA_LOCAL_TX_LCC_ENABLE), 0);
> +
> +	return 0;
> +}
> +
> +static int ufs_renesas_get_max_pwr_mode(struct ufs_hba *hba,
> +					struct ufs_pwr_mode_info *max_pwr_info)
> +{
> +	max_pwr_info->info.gear_rx = UFS_HS_G5;
> +	max_pwr_info->info.gear_tx = UFS_HS_G5;
> +	max_pwr_info->info.pwr_tx = FASTAUTO_MODE;
> +	max_pwr_info->info.pwr_rx = FASTAUTO_MODE;
> +	max_pwr_info->info.hs_rate = PA_HS_MODE_A;
> +
> +	max_pwr_info->info.lane_rx = 1;
> +	max_pwr_info->info.lane_tx = 1;
> +
> +	dev_info(hba->dev, "Max HS Gear: %d\n", max_pwr_info->info.gear_rx);
> +
> +	return 0;
> +}
> +
> +static struct ufs_hba_ops ufs_renesas_vops = {
> +	.init			= ufs_renesas_init,
> +	.hce_enable_notify	= ufs_renesas_hce_enable_notify,
> +	.link_startup_notify	= ufs_renesas_link_startup_notify,
> +	.get_max_pwr_mode	= ufs_renesas_get_max_pwr_mode,
> +};
> +
> +static int ufs_renesas_pltfm_probe(struct udevice *dev)
> +{
> +	struct ufs_renesas_priv *priv = dev_get_priv(dev);
> +	int ret;
> +
> +	priv->phy_base = dev_read_addr_name(dev, "phy");
> +	if (priv->phy_base == FDT_ADDR_T_NONE)
> +		return -EINVAL;
> +
> +	ret = reset_get_bulk(dev, &priv->resets);
> +	if (ret < 0)
> +		return ret;
> +
> +	ret = clk_get_bulk(dev, &priv->clks);
> +	if (ret < 0)
> +		goto err_clk_get;
> +
> +	ret = clk_enable_bulk(&priv->clks);
> +	if (ret)
> +		goto err_clk_enable;
> +
> +	reset_assert_bulk(&priv->resets);
> +	reset_deassert_bulk(&priv->resets);
> +
> +	ret = ufshcd_probe(dev, &ufs_renesas_vops);
> +	if (ret) {
> +		dev_err(dev, "ufshcd_probe() failed %d\n", ret);
> +		goto err_ufshcd_probe;
> +	}
> +
> +	return 0;
> +
> +err_ufshcd_probe:
> +	reset_assert_bulk(&priv->resets);
> +	clk_disable_bulk(&priv->clks);
> +err_clk_enable:
> +	clk_release_bulk(&priv->clks);
> +err_clk_get:
> +	reset_release_bulk(&priv->resets);
> +	return ret;
> +}
> +
> +static int ufs_renesas_pltfm_remove(struct udevice *dev)
> +{
> +	struct ufs_renesas_priv *priv = dev_get_priv(dev);
> +
> +	reset_release_bulk(&priv->resets);
> +	clk_disable_bulk(&priv->clks);
> +	clk_release_bulk(&priv->clks);
> +
> +	return 0;
> +}
> +
> +static const struct udevice_id ufs_renesas_pltfm_ids[] = {
> +	{ .compatible = "renesas,r8a78000-ufs" },
> +	{ /* sentinel */ }
> +};
> +
> +U_BOOT_DRIVER(ufs_renesas) = {
> +	.name		= "ufs-renesas-gen5",
> +	.id		= UCLASS_UFS,
> +	.of_match	= ufs_renesas_pltfm_ids,
> +	.probe		= ufs_renesas_pltfm_probe,
> +	.remove		= ufs_renesas_pltfm_remove,
> +	.priv_auto	= sizeof(struct ufs_renesas_priv),
> +};

Shame we dont't have a single register define... but it seems accepted
with the older driver and from Linux... so LGTM

Reviewed-by: Neil Armstrong <neil.armstrong at linaro.org>


More information about the U-Boot mailing list