[PATCH v1 3/3] scsi: ufs: starfive: Add UFS support for StarFive JHB100 SoC

From: Minda Chen

Date: Tue Apr 21 2026 - 05:17:13 EST


Add support for the UFS host controller on JHB100 SoC, built on
the Synopsys DWC UFS controller and using UFSHCD platform driver.
This controller requires specific configurations like
M-PHY/RMMI/UniPro

Signed-off-by: Minda Chen <minda.chen@xxxxxxxxxxxxxxxx>
---
MAINTAINERS | 1 +
drivers/ufs/host/Kconfig | 13 ++
drivers/ufs/host/Makefile | 1 +
drivers/ufs/host/ufs-starfive.c | 279 ++++++++++++++++++++++++++++++++
drivers/ufs/host/ufshcd-dwc.h | 17 ++
5 files changed, 311 insertions(+)
create mode 100644 drivers/ufs/host/ufs-starfive.c

diff --git a/MAINTAINERS b/MAINTAINERS
index 3792c51da63c..658f65c78482 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -27194,6 +27194,7 @@ UNIVERSAL FLASH STORAGE HOST CONTROLLER DRIVER STARFIVE
M: Minda Chen <minda.cheb@xxxxxxxxxxxxxxxx>
S: Maintained
F: Documentation/devicetree/bindings/ufs/starfive,ufs.yaml
+F: drivers/ufs/host/ufs-starfive.c

UNIWILL LAPTOP DRIVER
M: Armin Wolf <W_Armin@xxxxxx>
diff --git a/drivers/ufs/host/Kconfig b/drivers/ufs/host/Kconfig
index 964ae70e7390..b742f7a2b0b6 100644
--- a/drivers/ufs/host/Kconfig
+++ b/drivers/ufs/host/Kconfig
@@ -168,3 +168,16 @@ config SCSI_UFS_AMD_VERSAL2

Select this if you have UFS controller on AMD Versal Gen 2 SoC.
If unsure, say N.
+
+config SCSI_UFS_STARFIVE
+ tristate "Starfive UFS controller platform driver"
+ depends on OF && SCSI_UFSHCD_PLATFORM
+ depends on ARCH_STARFIVE || COMPILE_TEST
+ help
+ This selects the StarFive specific additions to UFSHCD platform driver.
+ UFS host on StarFive needs some vendor specific configuration before
+ accessing the hardware which includes PHY configuration and vendor
+ specific registers.
+
+ Select this if you have UFS controller on StarFive chipset.
+ If unsure, say N.
diff --git a/drivers/ufs/host/Makefile b/drivers/ufs/host/Makefile
index 65d8bb23ab7b..adfee2ae3b48 100644
--- a/drivers/ufs/host/Makefile
+++ b/drivers/ufs/host/Makefile
@@ -14,3 +14,4 @@ obj-$(CONFIG_SCSI_UFS_ROCKCHIP) += ufs-rockchip.o
obj-$(CONFIG_SCSI_UFS_SPRD) += ufs-sprd.o
obj-$(CONFIG_SCSI_UFS_TI_J721E) += ti-j721e-ufs.o
obj-$(CONFIG_SCSI_UFS_AMD_VERSAL2) += ufs-amd-versal2.o ufshcd-dwc.o
+obj-$(CONFIG_SCSI_UFS_STARFIVE) += ufs-starfive.o ufshcd-dwc.o
diff --git a/drivers/ufs/host/ufs-starfive.c b/drivers/ufs/host/ufs-starfive.c
new file mode 100644
index 000000000000..cdd5f9264cdb
--- /dev/null
+++ b/drivers/ufs/host/ufs-starfive.c
@@ -0,0 +1,279 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Starfive UFS host platform driver
+ *
+ * Copyright (C) 2026 Starfive, Inc.
+ *
+ * Authors: Minda Chen <minda.chen@xxxxxxxxxxxxxxxx>
+ */
+
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/kernel.h>
+#include <linux/mfd/syscon.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/reset.h>
+#include <ufs/unipro.h>
+
+#include "ufshcd-pltfrm.h"
+#include "ufshcd-dwc.h"
+#include "ufshci-dwc.h"
+
+struct ufs_starfive_host {
+ struct ufs_hba *hba;
+ struct regmap *syscon;
+ struct reset_control *core_reset;
+ struct reset_control *phy_reset;
+ struct clk *ufs_clk;
+};
+
+#define SRAM_STATUS 0x38
+#define SRAM_EXT_LD_DONE BIT(1)
+#define SRAM_INIT_DONE BIT(2)
+#define UFS_REFCLK 0x3c
+#define REFCLK_OEN BIT(8)
+#define RESET_I BIT(9)
+#define RESET_OEN BIT(10)
+
+#define MPHY_POLL_INTERVAL_US 100
+#define MPHY_POLL_TIMEOUT_US 10000
+
+static int ufs_starfive_phy_config(struct ufs_hba *hba, struct ufs_starfive_host *host)
+{
+ static struct ufs_dwc_phy_pair_data phy_data[] = {
+ { MPLL_SKIPCAL_COARSE_TUNE, 0},
+ { RX_AFE_ATT_IDAC(0), 0x8a},
+ { RX_AFE_ATT_IDAC(1), 0xc2},
+ { RX_AFE_CTLE_IDAC(0), 0x8e},
+ { RX_AFE_CTLE_IDAC(1), 0x8b},
+ { FAST_FLAGS(0), 0x0004 },
+ { FAST_FLAGS(1), 0x0004 },
+ { RX_ADAPT_DFE(0), 0xa00},
+ { RX_ADAPT_DFE(1), 0xa00},
+ };
+ struct ufs_dwc_phy_pair_data *data;
+ int ret, i;
+
+ for (i = 0; i < ARRAY_SIZE(phy_data); i++) {
+ data = &phy_data[i];
+ ret = ufs_dwc_phy_reg_write(hba, data->addr, data->value);
+ if (ret)
+ return ret;
+ }
+
+ ret = ufshcd_dme_set(hba, UIC_ARG_MIB(VS_MPHYDISABLE), 0);
+ if (ret)
+ return ret;
+
+ ret = ufshcd_dme_set(hba, UIC_ARG_MIB(VS_MPHYCFGUPDT), 1);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static int ufs_starfive_phy_init(struct ufs_hba *hba)
+{
+ struct ufs_starfive_host *host = ufshcd_get_variant(hba);
+ static struct ufshcd_dme_attr_val rmmi_config[] = {
+ { UIC_ARG_MIB(CBRATESEL), 0x1,
+ DME_LOCAL },
+ { UIC_ARG_MIB(CBREFCLKCTRL2), CBREFREFCLK_GATE_OVR_EN,
+ DME_LOCAL },
+ { UIC_ARG_MIB_SEL(RXSQCONTROL, SELIND_LN0_RX), 0x01,
+ DME_LOCAL },
+ { UIC_ARG_MIB_SEL(RXRHOLDCTRLOPT, SELIND_LN0_RX), 0x02,
+ DME_LOCAL },
+ { UIC_ARG_MIB_SEL(RXSQCONTROL, SELIND_LN1_RX), 0x01,
+ DME_LOCAL },
+ { UIC_ARG_MIB_SEL(RXRHOLDCTRLOPT, SELIND_LN1_RX), 0x02,
+ DME_LOCAL },
+ { UIC_ARG_MIB(EXT_COARSE_TUNE_RATEA), 0x25,
+ DME_LOCAL },
+ { UIC_ARG_MIB(EXT_COARSE_TUNE_RATEB), 0x51,
+ DME_LOCAL },
+ { UIC_ARG_MIB(CBCRCTRL), 0x01, DME_LOCAL },
+ { UIC_ARG_MIB(VS_MPHYCFGUPDT), 0x1,
+ DME_LOCAL },
+ };
+ int ret, val;
+
+ ret = ufshcd_dwc_dme_set_attrs(hba, rmmi_config,
+ ARRAY_SIZE(rmmi_config));
+ if (ret) {
+ dev_err(hba->dev, "set rmmi config failed\n");
+ return ret;
+ }
+
+ ret = reset_control_deassert(host->phy_reset);
+ if (ret) {
+ dev_err(hba->dev, "Failed to reset phy\n");
+ return ret;
+ }
+
+ ret = regmap_read_poll_timeout(host->syscon,
+ SRAM_STATUS, val,
+ (val & SRAM_INIT_DONE),
+ MPHY_POLL_INTERVAL_US,
+ MPHY_POLL_TIMEOUT_US);
+ if (ret) {
+ dev_err(hba->dev, "wait sram init done timeout\n");
+ return ret;
+ }
+
+ regmap_update_bits(host->syscon, SRAM_STATUS,
+ SRAM_EXT_LD_DONE, SRAM_EXT_LD_DONE);
+
+ ret = ufs_starfive_phy_config(hba, host);
+ if (ret) {
+ dev_err(hba->dev, "configure phy failed\n");
+ return ret;
+ }
+
+ return 0;
+}
+
+static int ufs_starfive_init(struct ufs_hba *hba)
+{
+ struct ufs_starfive_host *host;
+ struct device *dev = hba->dev;
+ struct platform_device *pdev;
+ int ret;
+
+ pdev = container_of(dev, struct platform_device, dev);
+ host = devm_kzalloc(dev, sizeof(*host), GFP_KERNEL);
+ if (!host)
+ return dev_err_probe(dev, -ENOMEM,
+ "no memory for starfive ufs host\n");
+
+ host->syscon = syscon_regmap_lookup_by_phandle(dev->of_node,
+ "starfive,syscon");
+
+ if (IS_ERR(host->syscon))
+ return dev_err_probe(dev, PTR_ERR(host->syscon), "getting the regmap failed\n");
+
+ host->core_reset = devm_reset_control_get_exclusive(hba->dev, "main");
+ if (IS_ERR(host->core_reset))
+ return dev_err_probe(dev, PTR_ERR(host->core_reset),
+ "Failed to get core clock resets");
+
+ host->phy_reset = devm_reset_control_get_exclusive(hba->dev, "phy");
+ if (IS_ERR(host->phy_reset))
+ return dev_err_probe(dev, PTR_ERR(host->phy_reset),
+ "Failed to get phy clk reset\n");
+
+ host->ufs_clk = devm_clk_get_enabled(&pdev->dev, "ufs");
+ if (IS_ERR(host->ufs_clk))
+ return dev_err_probe(dev, PTR_ERR(host->ufs_clk),
+ "Failed to get ufs clock\n");
+
+ regmap_update_bits(host->syscon, UFS_REFCLK,
+ REFCLK_OEN | RESET_OEN, 0);
+ usleep_range(2, 3);
+ regmap_update_bits(host->syscon, UFS_REFCLK, RESET_I, RESET_I);
+
+ ret = reset_control_deassert(host->core_reset);
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "Failed to reset core clock");
+
+ host->hba = hba;
+ ufshcd_set_variant(hba, host);
+ hba->caps |= UFSHCD_CAP_WB_EN;
+
+ return 0;
+}
+
+static int ufs_starfive_link_startup_notify(struct ufs_hba *hba,
+ enum ufs_notify_change_status status)
+{
+ int ret;
+
+ if (status == PRE_CHANGE) {
+ ret = ufshcd_vops_phy_initialization(hba);
+ if (ret) {
+ dev_err(hba->dev, "Phy setup failed (%d)\n", ret);
+ return ret;
+ }
+ } else { /* POST_CHANGE */
+ return ufshcd_dwc_link_startup_notify(hba, status);
+ }
+
+ return 0;
+}
+
+static int ufs_starfive_hce_enable_notify(struct ufs_hba *hba,
+ enum ufs_notify_change_status status)
+{
+ u32 val;
+
+ if (status != POST_CHANGE)
+ return 0;
+
+ /* Disable Gating clock. Auto hibernation quirk */
+ val = ufshcd_readl(hba, REG_BUSTHRTL);
+ val &= ~(LP_AH8_POWER_GATING_EN
+ | LP_POWER_GATING_EN
+ | CLK_GATING_EN);
+ ufshcd_writel(hba, val, REG_BUSTHRTL);
+
+ return 0;
+}
+
+static struct ufs_hba_variant_ops ufs_hba_vops = {
+ .name = "ufs_starfive_platform",
+ .init = ufs_starfive_init,
+ .link_startup_notify = ufs_starfive_link_startup_notify,
+ .phy_initialization = ufs_starfive_phy_init,
+ .hce_enable_notify = ufs_starfive_hce_enable_notify,
+};
+
+static int ufs_starfive_probe(struct platform_device *pdev)
+{
+ int err;
+
+ /* Perform generic probe */
+ err = ufshcd_pltfrm_init(pdev, &ufs_hba_vops);
+ if (err)
+ dev_err(&pdev->dev, "ufshcd_pltfrm_init() failed %d\n", err);
+
+ return err;
+}
+
+static void ufs_starfive_remove(struct platform_device *pdev)
+{
+ struct ufs_hba *hba = platform_get_drvdata(pdev);
+
+ pm_runtime_get_sync(&(pdev)->dev);
+ ufshcd_remove(hba);
+}
+
+static const struct dev_pm_ops ufs_starfive_pm_ops = {
+ SET_SYSTEM_SLEEP_PM_OPS(ufshcd_system_suspend, ufshcd_system_resume)
+ SET_RUNTIME_PM_OPS(ufshcd_runtime_suspend, ufshcd_runtime_resume, NULL)
+};
+
+static const struct of_device_id ufs_starfive_pltfm_match[] = {
+ { .compatible = "starfive,jhb100-ufs", },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, ufs_starfive_pltfm_match);
+
+static struct platform_driver ufs_starfive_driver = {
+ .probe = ufs_starfive_probe,
+ .remove = ufs_starfive_remove,
+ .driver = {
+ .name = "ufs-starfive",
+ .pm = &ufs_starfive_pm_ops,
+ .of_match_table = of_match_ptr(ufs_starfive_pltfm_match),
+ },
+};
+
+module_platform_driver(ufs_starfive_driver);
+
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:ufs-starfive");
+MODULE_DESCRIPTION("Starfive UFS host platform glue driver");
diff --git a/drivers/ufs/host/ufshcd-dwc.h b/drivers/ufs/host/ufshcd-dwc.h
index 8091f186a9b3..ab8728f92b22 100644
--- a/drivers/ufs/host/ufshcd-dwc.h
+++ b/drivers/ufs/host/ufshcd-dwc.h
@@ -12,7 +12,15 @@

#include <ufs/ufshcd.h>

+/* ufshcd vendor specific register */
+#define REG_BUSTHRTL 0xc0
+#define LP_AH8_POWER_GATING_EN BIT(17)
+#define LP_POWER_GATING_EN BIT(16)
+#define CLK_GATING_EN BIT(12)
+
/* RMMI Attributes */
+#define RXSQCONTROL 0x8009
+#define RXRHOLDCTRLOPT 0x8013
#define CBREFCLKCTRL2 0x8132
#define CBCRCTRL 0x811F
#define CBC10DIRECTCONF2 0x810E
@@ -24,6 +32,8 @@
#define CBCREGRDLSB 0x811A
#define CBCREGRDMSB 0x811B
#define CBCREGRDWRSEL 0x811C
+#define EXT_COARSE_TUNE_RATEA 0x814D
+#define EXT_COARSE_TUNE_RATEB 0x814E

#define CBREFREFCLK_GATE_OVR_EN BIT(7)

@@ -32,9 +42,11 @@
#define MRX_FSM_STATE 0xC1

/* M-PHY registers */
+#define MPLL_SKIPCAL_COARSE_TUNE 0x28
#define RX_OVRD_IN_1(n) (0x3006 + ((n) * 0x100))
#define RX_PCS_OUT(n) (0x300F + ((n) * 0x100))
#define FAST_FLAGS(n) (0x401C + ((n) * 0x100))
+#define RX_ADAPT_DFE(n) (0x401E + ((n) * 0x100))
#define RX_AFE_ATT_IDAC(n) (0x4000 + ((n) * 0x100))
#define RX_AFE_CTLE_IDAC(n) (0x4001 + ((n) * 0x100))
#define FW_CALIB_CCFG(n) (0x404D + ((n) * 0x100))
@@ -64,6 +76,11 @@ struct ufshcd_dme_attr_val {
u8 peer;
};

+struct ufs_dwc_phy_pair_data {
+ u32 addr;
+ u32 value;
+};
+
int ufshcd_dwc_link_startup_notify(struct ufs_hba *hba,
enum ufs_notify_change_status status);
int ufshcd_dwc_dme_set_attrs(struct ufs_hba *hba,
--
2.17.1