From 3351fe7ecc1ac717c23e9ad2c16e1fe4a7b84fd2 Mon Sep 17 00:00:00 2001 From: Tuyen Dang Date: Tue, 28 Oct 2025 15:22:27 +0100 Subject: [PATCH] ufs: Add UFS driver for Renesas R-Car X5H 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. [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"] Signed-off-by: Yoshihiro Shimoda Signed-off-by: Tuyen Dang Signed-off-by: Marek Vasut Reviewed-by: Neil Armstrong Link: https://patch.msgid.link/20251028142335.18125-7-marek.vasut+renesas@mailbox.org Signed-off-by: Neil Armstrong --- 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 3f8d8b50a32..68693b7d226 100644 --- a/drivers/ufs/Kconfig +++ b/drivers/ufs/Kconfig @@ -79,4 +79,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 4915b12e623..15329b9795a 100644 --- a/drivers/ufs/Makefile +++ b/drivers/ufs/Makefile @@ -11,4 +11,5 @@ 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 obj-$(CONFIG_ROCKCHIP_UFS) += ufs-rockchip.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 +#include +#include +#include +#include +#include +#include + +#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), +}; -- 2.47.3