* [PATCH 2/3] scsi: ufs: spacemit: k3: Add UFS Host Controller driver
2026-07-02 2:31 [PATCH 0/3] Add UFS Host driver support for SpacemiT K3 SoC Yixun Lan
2026-07-02 2:31 ` [PATCH 1/3] scsi: ufs: spacemit: dt-bindings: Add UFS controller for " Yixun Lan
@ 2026-07-02 2:31 ` Yixun Lan
2026-07-02 2:46 ` sashiko-bot
` (2 more replies)
2026-07-02 2:31 ` [PATCH 3/3] riscv: dts: spacemit: k3: Add UFS support Yixun Lan
2 siblings, 3 replies; 10+ messages in thread
From: Yixun Lan @ 2026-07-02 2:31 UTC (permalink / raw)
To: Alim Akhtar, Avri Altman, Bart Van Assche, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, James E.J. Bottomley,
Martin K. Petersen, Philipp Zabel, Paul Walmsley, Palmer Dabbelt,
Albert Ou, Alexandre Ghiti
Cc: linux-scsi, devicetree, linux-riscv, spacemit, linux-kernel,
Yixun Lan
SpacemiT K3 SoC consist of UFS (Universal Flash Storage) Host Controller
which has features compatible with JEDEC UFS 2.2, MIPI UniPro v1.61 and
M-PHY v3.0 standard.
Signed-off-by: Yixun Lan <dlan@kernel.org>
---
drivers/ufs/host/Kconfig | 12 +
drivers/ufs/host/Makefile | 1 +
drivers/ufs/host/ufs-spacemit.c | 931 ++++++++++++++++++++++++++++++++++++++++
drivers/ufs/host/ufs-spacemit.h | 90 ++++
4 files changed, 1034 insertions(+)
diff --git a/drivers/ufs/host/Kconfig b/drivers/ufs/host/Kconfig
index ff170c0b6da0..4f53b7f7688f 100644
--- a/drivers/ufs/host/Kconfig
+++ b/drivers/ufs/host/Kconfig
@@ -132,6 +132,18 @@ config SCSI_UFS_VARIABLE_SG_ENTRY_SIZE
bool
default y if SCSI_UFS_EXYNOS && SCSI_UFS_CRYPTO
+config SCSI_UFS_SPACEMIT
+ tristate "SpacemiT UFS controller driver"
+ depends on SCSI_UFSHCD_PLATFORM && ARCH_SPACEMIT
+ help
+ This selects the SpacemiT specific additions to UFSHCD platform driver.
+ UFS host on SpacemiT 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 SpacemiT chipset.
+ If unsure, say N.
+
config SCSI_UFS_SPRD
tristate "Unisoc specific hooks to UFS controller platform driver"
depends on SCSI_UFSHCD_PLATFORM && (ARCH_SPRD || COMPILE_TEST)
diff --git a/drivers/ufs/host/Makefile b/drivers/ufs/host/Makefile
index 65d8bb23ab7b..151bf43dfba7 100644
--- a/drivers/ufs/host/Makefile
+++ b/drivers/ufs/host/Makefile
@@ -11,6 +11,7 @@ obj-$(CONFIG_SCSI_UFS_HISI) += ufs-hisi.o
obj-$(CONFIG_SCSI_UFS_MEDIATEK) += ufs-mediatek.o
obj-$(CONFIG_SCSI_UFS_RENESAS) += ufs-renesas.o
obj-$(CONFIG_SCSI_UFS_ROCKCHIP) += ufs-rockchip.o
+obj-$(CONFIG_SCSI_UFS_SPACEMIT) += ufs-spacemit.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
diff --git a/drivers/ufs/host/ufs-spacemit.c b/drivers/ufs/host/ufs-spacemit.c
new file mode 100644
index 000000000000..be475f5566a8
--- /dev/null
+++ b/drivers/ufs/host/ufs-spacemit.c
@@ -0,0 +1,931 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2026 SpacemiT (Hangzhou) Technology Co. Ltd
+ */
+
+#include <linux/clk.h>
+#include <linux/clk-provider.h>
+#include <linux/delay.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <scsi/scsi_eh.h>
+#include <scsi/scsi_device.h>
+
+#include <ufs/ufshcd.h>
+#include <ufs/ufshci.h>
+#include <ufs/ufs_quirks.h>
+#include <ufs/unipro.h>
+
+#include "ufshcd-pltfrm.h"
+#include "ufs-spacemit.h"
+
+/* PA Layer Gettable and settable M-PHY Specific Attributes */
+#define PA_TXHSG1SYNCLENGTH 0x1552
+#define PA_TXHSG1PREPARELENGTH 0x1553
+#define PA_TXHSG2SYNCLENGTH 0x1554
+#define PA_TXHSG2PREPARELENGTH 0x1555
+#define PA_TXHSG3SYNCLENGTH 0x1556
+#define PA_TXHSG3PREPARELENGTH 0x1557
+#define PA_TXMK2EXTENSION 0x155A
+#define PA_PEERSCRAMBLING 0x155B
+#define PA_TXSKIP 0x155C
+#define PA_TXSKIPPERIOD 0x155D
+#define PA_PEER_TX_LCC_ENABLE 0x155F
+
+#define PA_SCRAMBLING 0x1585
+#define PA_MK2EXTENSIONGUARDBAND 0x15AB
+
+/*special TX/RX Configuration Attributes*/
+#define RX_LS_PRE_LEN_CAP 0x008D
+#define RX_LANE_HB8_BKDOOR_ATTR 0x00F4
+#define RX_PWRM_CLOSURE_LEN_CAP 0x008E
+#define RX_MIN_STALL_CAP 0x0088
+#define RX_LANE_SOF_BKDOOR_ATT 0x00F2
+#define RX_GARBAGE_COUNT_OFFSET 0x00F2
+
+/*special analog reg*/
+#define ANA_EQ_CTRL_REG_ATTR 0x00CD
+#define ANA_HSGEAR_CTRL_ATTR 0x00C1
+
+/*
+ * Keep UFS ACLK at a lower parent rate (409.6MHz) for stable init.
+ * This mirrors the "ufs-low-aclk-freq" change from the other environment.
+ */
+#define UFS_ACLK_LOW_FREQ_HZ 409600000UL
+
+/* PHY register magic values */
+#define MPHY_PU_ALL 0x87f
+#define MPHY_PU_WITH_HB8_RESET 0xb7f
+#define MPHY_DEVICE_RESET_DEASSERT 0x101
+#define MPHY_DEVICE_RESET_ASSERT 0x001
+#define MPHY_PLL_LOCK_BIT BIT(31)
+#define MPHY_PLL_LOCK_TIMEOUT_US 10000
+
+/* FSM states */
+#define FSM_STATE_HIBERN8 0x1
+#define FSM_STATE_ACTIVE 0x3
+#define FSM_STATE_LS_BURST 0x5
+
+struct ufs_reg_snapshot {
+ u32 reg_utrlba;
+ u32 reg_utrlbau;
+ u32 reg_utrmlba;
+ u32 reg_utrmlbau;
+
+ u32 reg_sys1clk;
+ u32 reg_tx_symbol_clk;
+ u32 reg_retry_timer;
+ u32 reg_pa_link;
+ u32 reg_cfg1;
+};
+
+#define VENDOR_DUMP_BUF_SIZE 2048
+#define VENDOR_MAX_OFFSET 0xE0
+
+static void ufs_spacemit_dump_host_regs(struct ufs_hba *hba)
+{
+ int i, offset;
+ size_t len = 0;
+ u8 *buf;
+ u32 val;
+ u32 host_reg[] = {
+ (UFS_PHY_MNG_BASE + UFS_MPHY_RST_CTRL),
+ (UFS_PHY_MNG_BASE + UFS_MPHY_PU_CTRL),
+ (UFS_PHY_MNG_BASE + UFS_DEVICE_IO_CTRL),
+ 0xFFF,
+ };
+
+ buf = kzalloc(VENDOR_DUMP_BUF_SIZE, GFP_ATOMIC);
+ if (!buf)
+ return;
+
+ len += scnprintf(buf + len, VENDOR_DUMP_BUF_SIZE - len, "vendor specific registers:");
+
+ for (offset = 0xC0, i = 0; offset < VENDOR_MAX_OFFSET; offset += 4, i++) {
+ val = ufshcd_readl(hba, offset);
+ if (i % 4 == 0)
+ len += scnprintf(buf + len, VENDOR_DUMP_BUF_SIZE - len, "\n");
+ len += scnprintf(buf + len, VENDOR_DUMP_BUF_SIZE - len, " 0x%03x: 0x%08x ",
+ offset, val);
+ }
+
+ len += scnprintf(buf + len, VENDOR_DUMP_BUF_SIZE - len, "\n mphy and atop registers:");
+
+ for (i = 0; host_reg[i] != 0xFFF; i++) {
+ val = ufshcd_readl(hba, host_reg[i]);
+ if (i % 4 == 0)
+ len += scnprintf(buf + len, VENDOR_DUMP_BUF_SIZE - len, "\n");
+ len += scnprintf(buf + len, VENDOR_DUMP_BUF_SIZE - len, " 0x%03x: 0x%08x ",
+ host_reg[i], val);
+ }
+ len += scnprintf(buf + len, VENDOR_DUMP_BUF_SIZE - len, "\n");
+
+ dev_warn(hba->dev, "%s", buf);
+
+ kfree(buf);
+}
+
+/* M-PHY FSM states */
+#define MPHY_RX_FSM_STATE 0xC1
+#define MPHY_TX_FSM_STATE 0x41
+
+static bool is_fsm_state_valid(u32 state)
+{
+ return (state == FSM_STATE_ACTIVE || state == FSM_STATE_LS_BURST);
+}
+
+static int ufs_spacemit_check_hibern8(struct ufs_hba *hba)
+{
+ u32 tx_fsm_val_0 = 0;
+ u32 tx_fsm_val_1 = 0;
+ int retries = DIV_ROUND_UP(HBRN8_POLL_TOUT_MS * 1000, 100);
+ int err = 0;
+
+ do {
+ err = ufshcd_dme_get(hba,
+ UIC_ARG_MIB_SEL(MPHY_TX_FSM_STATE,
+ UIC_ARG_MPHY_TX_GEN_SEL_INDEX(0)),
+ &tx_fsm_val_0);
+ err |= ufshcd_dme_get(hba,
+ UIC_ARG_MIB_SEL(MPHY_TX_FSM_STATE,
+ UIC_ARG_MPHY_TX_GEN_SEL_INDEX(1)),
+ &tx_fsm_val_1);
+ if (err || (tx_fsm_val_0 == TX_FSM_HIBERN8 &&
+ tx_fsm_val_1 == TX_FSM_HIBERN8))
+ break;
+
+ usleep_range(100, 200);
+ } while (--retries > 0);
+
+ if (!err && retries <= 0) {
+ err = ufshcd_dme_get(hba,
+ UIC_ARG_MIB_SEL(MPHY_TX_FSM_STATE,
+ UIC_ARG_MPHY_TX_GEN_SEL_INDEX(0)),
+ &tx_fsm_val_0);
+ err |= ufshcd_dme_get(hba,
+ UIC_ARG_MIB_SEL(MPHY_TX_FSM_STATE,
+ UIC_ARG_MPHY_TX_GEN_SEL_INDEX(1)),
+ &tx_fsm_val_1);
+ }
+
+ if (err) {
+ dev_err(hba->dev, "%s: unable to get TX_FSM_STATE, err %d\n",
+ __func__, err);
+ } else if (tx_fsm_val_0 != TX_FSM_HIBERN8 ||
+ tx_fsm_val_1 != TX_FSM_HIBERN8) {
+ err = -ETIMEDOUT;
+ dev_err(hba->dev,
+ "%s: invalid TX_FSM_STATE, lane0 = %u, lane1 = %u\n",
+ __func__, tx_fsm_val_0, tx_fsm_val_1);
+ }
+
+ return err;
+}
+
+static void ufs_spacemit_dump_fsm_state(struct ufs_hba *hba)
+{
+ u32 tx0_fsm_val, tx1_fsm_val, rx0_fsm_val, rx1_fsm_val;
+ int err;
+
+ err = ufshcd_dme_get(hba,
+ UIC_ARG_MIB_SEL(MPHY_TX_FSM_STATE, UIC_ARG_MPHY_TX_GEN_SEL_INDEX(0)),
+ &tx0_fsm_val);
+ if (err)
+ return;
+
+ usleep_range(100, 200);
+
+ err = ufshcd_dme_get(hba,
+ UIC_ARG_MIB_SEL(MPHY_TX_FSM_STATE, UIC_ARG_MPHY_TX_GEN_SEL_INDEX(1)),
+ &tx1_fsm_val);
+ if (err)
+ return;
+
+ usleep_range(100, 200);
+
+ err = ufshcd_dme_get(hba,
+ UIC_ARG_MIB_SEL(MPHY_RX_FSM_STATE, UIC_ARG_MPHY_RX_GEN_SEL_INDEX(0)),
+ &rx0_fsm_val);
+ if (err)
+ return;
+
+ usleep_range(100, 200);
+
+ err = ufshcd_dme_get(hba,
+ UIC_ARG_MIB_SEL(MPHY_RX_FSM_STATE, UIC_ARG_MPHY_RX_GEN_SEL_INDEX(1)),
+ &rx1_fsm_val);
+ if (err)
+ return;
+
+ usleep_range(100, 200);
+
+ if (!is_fsm_state_valid(tx0_fsm_val) || !is_fsm_state_valid(tx1_fsm_val) ||
+ !is_fsm_state_valid(rx0_fsm_val) || !is_fsm_state_valid(rx1_fsm_val)) {
+ dev_warn(hba->dev, "FSM state invalid - TX:[0x%x, 0x%x], RX:[0x%x, 0x%x]\n",
+ tx0_fsm_val, tx1_fsm_val, rx0_fsm_val, rx1_fsm_val);
+ }
+}
+
+static int ufs_spacemit_get_connected_tx_lanes(struct ufs_hba *hba, u32 *tx_lanes)
+{
+ int err;
+
+ err = ufshcd_dme_get(hba, UIC_ARG_MIB(PA_CONNECTEDTXDATALANES), tx_lanes);
+ if (err)
+ dev_err(hba->dev, "%s: couldn't read PA_CONNECTEDTXDATALANES %d\n", __func__, err);
+
+ return err;
+}
+
+static u32 ufs_spacemit_get_sys1clk_1us(struct ufs_hba *hba)
+{
+ struct ufs_clk_info *clki, *ufs_aclk = NULL;
+ struct list_head *head = &hba->clk_list_head;
+ unsigned long rate_hz = 0;
+
+ if (!list_empty(head)) {
+ list_for_each_entry(clki, head, list) {
+ if (clki->name && !strcmp(clki->name, "aclk") && clki->clk) {
+ ufs_aclk = clki;
+ break;
+ }
+ }
+ }
+
+ if (ufs_aclk && ufs_aclk->clk)
+ rate_hz = clk_get_rate(ufs_aclk->clk);
+
+ if (!rate_hz)
+ return 0;
+
+ return DIV_ROUND_CLOSEST(rate_hz, 1000000);
+}
+
+static int ufs_spacemit_wait_mphy_pll_lock(struct ufs_hba *hba)
+{
+ int timeout = MPHY_PLL_LOCK_TIMEOUT_US;
+ u32 val;
+
+ while (timeout-- > 0) {
+ val = ufshcd_readl(hba, UFS_PHY_MNG_BASE + UFS_MPHY_PU_CTRL);
+ if (val & MPHY_PLL_LOCK_BIT)
+ return 0;
+
+ udelay(1);
+ }
+
+ dev_err(hba->dev, "M-PHY PLL lock timeout\n");
+ return -ETIMEDOUT;
+}
+
+/**
+ * ufs_spacemit_mphy_init
+ * @hba: host controller instance
+ */
+static int ufs_spacemit_mphy_init(struct ufs_hba *hba)
+{
+ int ret;
+
+ /* reset all mphy logical */
+ ufshcd_writel(hba, 0x003, UFS_PHY_MNG_BASE + 0x0);
+ mdelay(1);
+
+ /* power up all */
+ ufshcd_writel(hba, MPHY_PU_ALL, UFS_PHY_MNG_BASE + 0x4);
+ mdelay(1);
+
+ /* asserted ana_rx_hb8_reset */
+ ufshcd_writel(hba, 0xb7f, UFS_PHY_MNG_BASE + 0x4);
+ mdelay(1);
+
+ /* deasserted ana_rx_hb8_reset */
+ ufshcd_writel(hba, MPHY_PU_ALL, UFS_PHY_MNG_BASE + 0x4);
+ mdelay(1);
+
+ /* deasserted ufs device reset & refer clk output enable */
+ ufshcd_writel(hba, 0x101, UFS_PHY_MNG_BASE + 0xC);
+ mdelay(1);
+
+ ret = ufs_spacemit_wait_mphy_pll_lock(hba);
+ if (ret < 0)
+ return ret;
+
+ dev_dbg(hba->dev, "M-PHY PLL locked successfully\n");
+
+ ufshcd_writel(hba, 0x1, UFS_PHY_MNG_BASE + 0x08);
+ udelay(5);
+
+ ufshcd_writel(hba, 0x40, UFS_ATOP_BASE + (0xC2 << 2));
+ udelay(5);
+
+ ufshcd_writel(hba, 0x0, UFS_PHY_MNG_BASE + 0x08);
+
+ mdelay(2);
+
+ dev_dbg(hba->dev, "M-PHY init completed\n");
+
+ return 0;
+}
+
+static int ufs_spacemit_uniprov1p6_init(struct ufs_hba *hba)
+{
+ /* PA_TXHSG1SYNCLENGTH */
+ ufshcd_dme_set(hba, UIC_ARG_MIB(0x1552), 0x4f);
+
+ /* PA_TXHSG1PREPARELENGTH */
+ ufshcd_dme_set(hba, UIC_ARG_MIB(0x1553), 0xf);
+
+ /* PA_TXHSG2SYNCLENGTH */
+ ufshcd_dme_set(hba, UIC_ARG_MIB(0x1554), 0x4f);
+
+ /* PA_TXHSG2PREPARELENGTH */
+ ufshcd_dme_set(hba, UIC_ARG_MIB(0x1555), 0xf);
+
+ /* PA_TXHSG3SYNCLENGTH */
+ ufshcd_dme_set(hba, UIC_ARG_MIB(0x1556), 0x4f);
+
+ /* PA_TXHSG3PREPARELENGTH */
+ ufshcd_dme_set(hba, UIC_ARG_MIB(0x1557), 0xf);
+
+ /* PA_TXMK2EXTENSION */
+ ufshcd_dme_set(hba, UIC_ARG_MIB(0x155A), 0x0);
+
+ /* PA_PEERSCRAMBLING */
+ ufshcd_dme_set(hba, UIC_ARG_MIB(0x155B), 0x1);
+
+ /* PA_TXSKIP */
+ ufshcd_dme_set(hba, UIC_ARG_MIB(0x155C), 0x1);
+
+ /* PA_TXSKIPPERIOD */
+ ufshcd_dme_set(hba, UIC_ARG_MIB(0x155D), 250);
+
+ /* PA_LOCAL_TX_LCC_ENABLE */
+ ufshcd_dme_set(hba, UIC_ARG_MIB(0x155E), 0x0);
+
+ /* PA_PEER_TX_LCC_ENABLE */
+ ufshcd_dme_set(hba, UIC_ARG_MIB(0x155F), 0x0);
+
+ /* PA_SCRAMBLING */
+ ufshcd_dme_set(hba, UIC_ARG_MIB(0x1585), 0x1);
+
+ /* PA_GRANULARITY */
+ ufshcd_dme_set(hba, UIC_ARG_MIB(0x15AA), 0x1);
+
+ /* PA_MK2EXTENSIONGUARDBAND */
+ ufshcd_dme_set(hba, UIC_ARG_MIB(0x15AB), 0x0);
+
+ /* PA_STALLNOCONFIGTIME */
+ ufshcd_dme_set(hba, UIC_ARG_MIB(0x15A3), 15);
+
+ /* PA_TACTIVATE */
+ ufshcd_dme_set(hba, UIC_ARG_MIB(0x15A8), 0x64);
+
+ /* PA_TXTRAILINGCLOCKS */
+ ufshcd_dme_set(hba, UIC_ARG_MIB(0x1564), 0x64);
+
+ /* RX_LS_PREPARELEN_TIME RX0 */
+ ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(0x008D, 4), 0x0B);
+
+ /* RX_LS_PREPARELEN_TIME RX1 */
+ ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(0x008D, 5), 0X0B);
+
+ /* RX_HIBERNATE_BKEN RX0 */
+ ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(0x00F4, UIC_ARG_MPHY_RX_GEN_SEL_INDEX(0)), 0x9F);
+
+ /* RX_HIBERNATE_BKEN RX1 */
+ ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(0x00F4, UIC_ARG_MPHY_RX_GEN_SEL_INDEX(1)), 0x9F);
+
+ /* PWM_BURST_closure_length */
+ ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(0x008E, UIC_ARG_MPHY_RX_GEN_SEL_INDEX(0)), 15);
+ ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(0x008E, UIC_ARG_MPHY_RX_GEN_SEL_INDEX(1)), 15);
+
+ /* min_stall_not_config_time*/
+ ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(0x0088, UIC_ARG_MPHY_RX_GEN_SEL_INDEX(0)), 0xFF);
+ ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(0x0088, UIC_ARG_MPHY_RX_GEN_SEL_INDEX(1)), 0xFF);
+
+ /* TX HB8_TIME CAP */
+ ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(0x000F, UIC_ARG_MPHY_TX_GEN_SEL_INDEX(0)), 0x64);
+ ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(0x000F, UIC_ARG_MPHY_TX_GEN_SEL_INDEX(1)), 0x64);
+
+ /*RX HB8_TIME CAP*/
+ ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(0x0092, UIC_ARG_MPHY_RX_GEN_SEL_INDEX(0)), 0x64);
+ ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(0x0092, UIC_ARG_MPHY_RX_GEN_SEL_INDEX(1)), 0x64);
+
+ /*TX EQ 3DB*/
+ ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(0x00CD, UIC_ARG_MPHY_TX_GEN_SEL_INDEX(0)), 0x5);
+
+ /*RX garbage cnt = 32 SI*/
+ ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(0x00F2, UIC_ARG_MPHY_RX_GEN_SEL_INDEX(0)), 0x9F);
+ ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(0x00F2, UIC_ARG_MPHY_RX_GEN_SEL_INDEX(1)), 0x9F);
+
+ dev_info(hba->dev, "UniPro v1.6 init completed\n");
+
+ return 0;
+}
+
+static void ufs_spacemit_set_dev_cap(struct ufs_host_params *host_params, u32 pwr_hs)
+{
+ if (!host_params)
+ return;
+
+ memset(host_params, 0, sizeof(struct ufs_host_params));
+ host_params->tx_lanes = UFS_SPACEMIT_K3_LIMIT_NUM_LANES_TX;
+ host_params->rx_lanes = UFS_SPACEMIT_K3_LIMIT_NUM_LANES_RX;
+ host_params->hs_rx_gear = UFS_SPACEMIT_K3_LIMIT_HSGEAR_RX;
+ host_params->hs_tx_gear = UFS_SPACEMIT_K3_LIMIT_HSGEAR_TX;
+ host_params->pwm_rx_gear = UFS_SPACEMIT_K3_LIMIT_PWMGEAR_RX;
+ host_params->pwm_tx_gear = UFS_SPACEMIT_K3_LIMIT_PWMGEAR_TX;
+ host_params->rx_pwr_pwm = UFS_SPACEMIT_K3_LIMIT_RX_PWR_PWM;
+ host_params->tx_pwr_pwm = UFS_SPACEMIT_K3_LIMIT_TX_PWR_PWM;
+ host_params->rx_pwr_hs = pwr_hs;
+ host_params->tx_pwr_hs = pwr_hs;
+ host_params->hs_rate = UFS_SPACEMIT_K3_LIMIT_HS_RATE;
+ host_params->desired_working_mode = UFS_HS_MODE;
+}
+
+static int ufs_spacemit_link_startup_pre_change(struct ufs_hba *hba)
+{
+ u32 value, sys1clk_1us;
+
+ ufs_spacemit_mphy_init(hba);
+
+ ufs_spacemit_uniprov1p6_init(hba);
+
+ /* config sysclk and tx symbol clk before link startup */
+ value = UFS_MAX_LINKSTARTUP_TIMER;
+
+ /* clear bit0~bit3, select b0 design */
+ value &= ~0xf;
+
+ ufshcd_writel(hba, value, UFS_PA_LINK_STARTUP_TIMER);
+
+ sys1clk_1us = ufs_spacemit_get_sys1clk_1us(hba);
+ if (!sys1clk_1us)
+ sys1clk_1us = DIV_ROUND_CLOSEST(UFS_ACLK_LOW_FREQ_HZ, 1000000);
+ ufshcd_writel(hba, sys1clk_1us, UFS_SYS1CLK_1US);
+ ufshcd_writel(hba, UFS_TX_SYMBO_CLK, UFS_TX_SYMBOL_CLK_NS_US);
+
+ dev_dbg(hba->dev, "REG_UFS_SYS1CLK_1US: 0x%x\n", ufshcd_readl(hba, UFS_SYS1CLK_1US));
+ dev_dbg(hba->dev, "REG_UFS_TX_SYMBOL_CLK_NS_US: 0x%x\n",
+ ufshcd_readl(hba, UFS_TX_SYMBOL_CLK_NS_US));
+
+ return 0;
+}
+
+static int ufs_spacemit_link_startup_post_change(struct ufs_hba *hba)
+{
+ u32 tx_lanes;
+
+ /* add 0xe8 make UFS2.1 run GEAR3 + 2Lane@409M */
+ mdelay(5);
+ ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(0xe8, UIC_ARG_MPHY_TX_GEN_SEL_INDEX(0)), 0x97);
+ mdelay(1);
+ ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(0xe8, UIC_ARG_MPHY_TX_GEN_SEL_INDEX(0)), 0xd7);
+ mdelay(1);
+ ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(0xe8, UIC_ARG_MPHY_TX_GEN_SEL_INDEX(0)), 0x17);
+
+ ufshcd_dme_set(hba, UIC_ARG_MIB(DL_AFC0REQTIMEOUTVAL), UFS_DL_AFC0REQTIMEOUTVAL_MAX);
+
+ return ufs_spacemit_get_connected_tx_lanes(hba, &tx_lanes);
+}
+
+static int ufs_spacemit_link_startup_notify(struct ufs_hba *hba,
+ enum ufs_notify_change_status status)
+{
+ switch (status) {
+ case PRE_CHANGE:
+ ufs_spacemit_link_startup_pre_change(hba);
+ break;
+ case POST_CHANGE:
+ ufs_spacemit_link_startup_post_change(hba);
+ break;
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+static int ufs_spacemit_negotiate_pwr_mode(struct ufs_hba *hba,
+ const struct ufs_pa_layer_attr *dev_max_params,
+ struct ufs_pa_layer_attr *dev_req_params)
+{
+ struct ufs_host_params host_params;
+ int ret;
+
+ ufs_spacemit_set_dev_cap(&host_params, FAST_MODE);
+ ret = ufshcd_negotiate_pwr_params(&host_params, dev_max_params,
+ dev_req_params);
+ if (ret) {
+ dev_err(hba->dev, "Failed to negotiate power params: %d\n", ret);
+ return ret;
+ }
+
+ dev_dbg(hba->dev,
+ "Power mode config - gear_rx:%d, gear_tx:%d, lane_rx:%d, lane_tx:%d, pwr_rx:%d, pwr_tx:%d, hs_rate:%d\n",
+ dev_req_params->gear_rx, dev_req_params->gear_tx, dev_req_params->lane_rx,
+ dev_req_params->lane_tx, dev_req_params->pwr_rx, dev_req_params->pwr_tx,
+ dev_req_params->hs_rate);
+
+ return ret;
+}
+
+static int ufs_spacemit_pwr_change_notify(struct ufs_hba *hba,
+ enum ufs_notify_change_status status,
+ struct ufs_pa_layer_attr *dev_req_params)
+{
+ struct ufs_spacemit_host *host = ufshcd_get_variant(hba);
+ int ret = 0;
+
+ if (!dev_req_params) {
+ dev_err(hba->dev, "Invalid Parameters\n");
+ return -EINVAL;
+ }
+
+ switch (status) {
+ case PRE_CHANGE:
+ break;
+ case POST_CHANGE:
+ /* cache the power mode parameters to use internally */
+ memcpy(&host->dev_req_params, dev_req_params, sizeof(*dev_req_params));
+ ret = ufs_spacemit_wait_mphy_pll_lock(hba);
+ if (ret < 0)
+ return ret;
+
+ dev_dbg(hba->dev, "M-PHY PLL locked after power mode change\n");
+ /*set ANA_HSGEAR_CTRL_ATTR back to default value*/
+ ufshcd_dme_set(hba, UIC_ARG_MIB(ANA_HSGEAR_CTRL_ATTR), 0x00);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return ret;
+}
+
+static int ufs_spacemit_quirk_host_pa_saveconfigtime(struct ufs_hba *hba)
+{
+ int err;
+ u32 pa_vs_config_reg1;
+
+ err = ufshcd_dme_get(hba, UIC_ARG_MIB(UFS_PA_VS_CONFIG_REG1), &pa_vs_config_reg1);
+ if (err)
+ return err;
+
+ /* Allow extension of MSB bits of PA_SaveConfigTime attribute */
+ return ufshcd_dme_set(hba, UIC_ARG_MIB(UFS_PA_VS_CONFIG_REG1),
+ (pa_vs_config_reg1 | (1 << 12)));
+}
+
+static int ufs_spacemit_apply_dev_quirks(struct ufs_hba *hba)
+{
+ if (hba->dev_quirks & UFS_DEVICE_QUIRK_HOST_PA_SAVECONFIGTIME)
+ ufs_spacemit_quirk_host_pa_saveconfigtime(hba);
+
+ if (hba->dev_info.wmanufacturerid == UFS_VENDOR_WDC)
+ hba->dev_quirks |= UFS_DEVICE_QUIRK_HOST_PA_TACTIVATE;
+
+ /*LCC_DISABLE*/
+ mdelay(50);
+ ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(TX_LCC_ENABLE, UIC_ARG_MPHY_TX_GEN_SEL_INDEX(0)), 0);
+ mdelay(1);
+ ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(TX_LCC_ENABLE, UIC_ARG_MPHY_TX_GEN_SEL_INDEX(1)), 0);
+
+ /*TX_Min_ActivateTime*/
+ mdelay(1);
+ ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(TX_MIN_ACTIVATETIME, UIC_ARG_MPHY_TX_GEN_SEL_INDEX(0)),
+ 0x0);
+ mdelay(1);
+ ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(TX_MIN_ACTIVATETIME, UIC_ARG_MPHY_TX_GEN_SEL_INDEX(1)),
+ 0x0);
+ mdelay(10);
+
+ /*use backdoor reg to pre-set TX RATE/GEAR to let PLL lock before set_power_mode switch*/
+ ufshcd_dme_set(hba, UIC_ARG_MIB(ANA_HSGEAR_CTRL_ATTR), 0x25);
+ mdelay(10);
+
+ return ufs_spacemit_wait_mphy_pll_lock(hba);
+}
+
+/**
+ * ufs_spacemit_advertise_quirks - advertise the known Spacemit UFS controller quirks
+ * @hba: host controller instance
+ *
+ * Spacemit UFS host controller might have some non standard behaviours (quirks)
+ * than what is specified by UFSHCI specification. Advertise all such
+ * quirks to standard UFS host controller driver so standard takes them into
+ * account.
+ */
+static void ufs_spacemit_advertise_quirks(struct ufs_hba *hba)
+{
+ hba->quirks |= UFSHCD_QUIRK_BROKEN_AUTO_HIBERN8;
+}
+
+/**
+ * ufs_spacemit_fsm_dump_work - Deferred work to dump FSM state
+ * @work: work structure
+ *
+ * This function is called from a workqueue context (not interrupt context),
+ * allowing safe execution of blocking operations like ufshcd_dme_get().
+ */
+static void ufs_spacemit_fsm_dump_work(struct work_struct *work)
+{
+ struct ufs_spacemit_host *host = container_of(work, struct ufs_spacemit_host,
+ fsm_dump_work);
+ struct ufs_hba *hba = host->hba;
+
+ /* Safe to call blocking functions in workqueue context */
+ if (ufshcd_is_link_active(hba))
+ ufs_spacemit_dump_fsm_state(hba);
+}
+
+/**
+ * ufs_spacemit_init - init phy and prepare clk
+ * @hba: host controller instance
+ */
+static int ufs_spacemit_init(struct ufs_hba *hba)
+{
+ int err = 0;
+ struct device *dev = hba->dev;
+ struct ufs_spacemit_host *host;
+
+ host = devm_kzalloc(dev, sizeof(*host), GFP_KERNEL);
+ if (!host)
+ return -ENOMEM;
+
+ host->rst = devm_reset_control_get_optional_exclusive_deasserted(dev, NULL);
+ if (IS_ERR(host->rst))
+ return dev_err_probe(dev, PTR_ERR(host->rst),
+ "Failed to get reset control\n");
+
+ host->hba = hba;
+ ufshcd_set_variant(hba, host);
+ ufs_spacemit_advertise_quirks(hba);
+
+ /* Initialize workqueue for deferred FSM state dump */
+ INIT_WORK(&host->fsm_dump_work, ufs_spacemit_fsm_dump_work);
+
+ err = ufshcd_vops_phy_initialization(host->hba);
+ return err;
+}
+
+/**
+ * ufs_spacemit_device_reset - Toggle device reset line
+ * @hba: per-adapter instance
+ *
+ * Toggles the reset line to reset the attached UFS device.
+ *
+ * Returns: 0 on success
+ */
+static int ufs_spacemit_device_reset(struct ufs_hba *hba)
+{
+ /* Stop device ref_clk & asserted ufs device reset */
+ ufshcd_writel(hba, 0x000, UFS_PHY_MNG_BASE + UFS_DEVICE_IO_CTRL);
+ mdelay(1);
+
+ /* Enable device ref_clk & de-asserted ufs device reset */
+ ufshcd_writel(hba, MPHY_DEVICE_RESET_DEASSERT, UFS_PHY_MNG_BASE + UFS_DEVICE_IO_CTRL);
+ mdelay(1);
+
+ return 0;
+}
+
+/**
+ * ufs_spacemit_event_notify - Handle UFS error events
+ * @hba: host controller instance
+ * @evt: event type
+ * @data: event-specific data
+ *
+ * Handles error events from UFS core, dumps registers immediately
+ * and schedules FSM state dump for later execution in workqueue context.
+ */
+static void ufs_spacemit_event_notify(struct ufs_hba *hba, enum ufs_event_type evt, void *data)
+{
+ struct ufs_spacemit_host *host = ufshcd_get_variant(hba);
+ bool dump_regs = false;
+
+ switch (evt) {
+ case UFS_EVT_PA_ERR:
+ if (data)
+ dev_warn(hba->dev, "PA error event, INT errors:0x%x, PA_ERR_CODE:0x%x\n",
+ hba->errors, *(u32 *)data);
+
+ dump_regs = true;
+ break;
+
+ case UFS_EVT_DL_ERR:
+ if (data)
+ dev_warn(hba->dev, "DL error event, INT errors:0x%x, DL_ERR:0x%x\n",
+ hba->errors, *(u32 *)data);
+
+ dump_regs = true;
+ break;
+
+ case UFS_EVT_ABORT:
+ dev_warn(hba->dev, "Abort event, INT errors:0x%x\n", hba->errors);
+ break;
+
+ default:
+ break;
+ }
+
+ /* Dump registers if error occurred (safe in interrupt context) */
+ if (hba->errors || dump_regs)
+ ufs_spacemit_dump_host_regs(hba);
+
+ /* Schedule FSM state dump in workqueue context (not in interrupt context) */
+ if (ufshcd_is_link_active(hba) && host)
+ queue_work(system_wq, &host->fsm_dump_work);
+}
+
+/**
+ * ufs_spacemit_hibern8_notify - Handle hibernate enter/exit
+ * @hba: host controller instance
+ * @cmd: UIC command (HIBER_ENTER or HIBER_EXIT)
+ * @status: notification status
+ *
+ * Manages M-PHY power state during hibernate transitions.
+ */
+static void ufs_spacemit_hibern8_notify(struct ufs_hba *hba, enum uic_cmd_dme cmd,
+ enum ufs_notify_change_status status)
+{
+ int ret;
+
+ dev_dbg(hba->dev, "Hibern8 notify: cmd=%d, status=%d\n", cmd, status);
+ if (status == PRE_CHANGE) {
+ if (cmd == UIC_CMD_DME_HIBER_EXIT) {
+ mdelay(1);
+
+ /* Enable reference clock */
+ ufshcd_writel(hba, MPHY_DEVICE_RESET_DEASSERT,
+ UFS_PHY_MNG_BASE + UFS_DEVICE_IO_CTRL);
+ mdelay(1);
+
+ /* Power up all */
+ ufshcd_writel(hba, MPHY_PU_ALL, UFS_PHY_MNG_BASE + UFS_MPHY_PU_CTRL);
+ mdelay(1);
+
+ /* Assert ana_rx_hb8_reset */
+ ufshcd_writel(hba, MPHY_PU_WITH_HB8_RESET,
+ UFS_PHY_MNG_BASE + UFS_MPHY_PU_CTRL);
+ mdelay(1);
+
+ /* Deassert ana_rx_hb8_reset */
+ ufshcd_writel(hba, MPHY_PU_ALL, UFS_PHY_MNG_BASE + UFS_MPHY_PU_CTRL);
+
+ ret = ufs_spacemit_wait_mphy_pll_lock(hba);
+ if (ret < 0)
+ return;
+
+ mdelay(1);
+ ufshcd_dme_set(hba, UIC_ARG_MIB(0xdd), 0x57);
+ mdelay(1);
+ ufshcd_dme_set(hba, UIC_ARG_MIB(0xe8), 0x57);
+ }
+ }
+
+ if (status == PRE_CHANGE) {
+ if (cmd == UIC_CMD_DME_HIBER_ENTER) {
+ ufshcd_dme_set(hba,
+ UIC_ARG_MIB_SEL(0xf1, UIC_ARG_MPHY_RX_GEN_SEL_INDEX(0)),
+ 0x84);
+ ufshcd_dme_set(hba,
+ UIC_ARG_MIB_SEL(0xf1, UIC_ARG_MPHY_RX_GEN_SEL_INDEX(1)),
+ 0x84);
+ ufshcd_dme_set(hba,
+ UIC_ARG_MIB_SEL(0xf1, UIC_ARG_MPHY_RX_GEN_SEL_INDEX(0)),
+ 0x85);
+ ufshcd_dme_set(hba,
+ UIC_ARG_MIB_SEL(0xf1, UIC_ARG_MPHY_RX_GEN_SEL_INDEX(1)),
+ 0x85);
+ }
+ }
+
+ if (status == POST_CHANGE) {
+ if (cmd == UIC_CMD_DME_HIBER_ENTER) {
+ ufs_spacemit_check_hibern8(hba);
+ ufshcd_dme_set(hba,
+ UIC_ARG_MIB_SEL(0xf1, UIC_ARG_MPHY_RX_GEN_SEL_INDEX(0)),
+ 0x84);
+ ufshcd_dme_set(hba,
+ UIC_ARG_MIB_SEL(0xf1, UIC_ARG_MPHY_RX_GEN_SEL_INDEX(1)),
+ 0x84);
+ ufshcd_dme_set(hba,
+ UIC_ARG_MIB_SEL(0xf1, UIC_ARG_MPHY_RX_GEN_SEL_INDEX(0)),
+ 0x80);
+ ufshcd_dme_set(hba,
+ UIC_ARG_MIB_SEL(0xf1, UIC_ARG_MPHY_RX_GEN_SEL_INDEX(1)),
+ 0x80);
+
+ mdelay(1);
+ ufshcd_dme_set(hba, UIC_ARG_MIB(0xdd), 0x57);
+ mdelay(1);
+ ufshcd_dme_set(hba, UIC_ARG_MIB(0xdd), 0xd7);
+ mdelay(1);
+ ufshcd_dme_set(hba, UIC_ARG_MIB(0xe8), 0x57);
+ mdelay(1);
+ ufshcd_dme_set(hba, UIC_ARG_MIB(0xe8), 0xd7);
+ mdelay(1);
+
+ /* Power down M-PHY */
+ ufshcd_writel(hba, 0x0, UFS_PHY_MNG_BASE + UFS_MPHY_PU_CTRL);
+ mdelay(1);
+
+ /* Keep reference clock enabled, assert device reset */
+ ufshcd_writel(hba, MPHY_DEVICE_RESET_ASSERT,
+ UFS_PHY_MNG_BASE + UFS_DEVICE_IO_CTRL);
+ }
+ }
+}
+
+/**
+ * ufs_spacemit_hce_enable_notify - Configure HCE enable sequence
+ * @hba: host controller instance
+ * @status: notification status (PRE_CHANGE or POST_CHANGE)
+ *
+ * Configures host controller enable with proper sequencing.
+ * Handles crypto enable if supported.
+ *
+ * Returns: 0 on success
+ */
+static int ufs_spacemit_hce_enable_notify(struct ufs_hba *hba,
+ enum ufs_notify_change_status status)
+{
+ struct ufs_spacemit_host *host = ufshcd_get_variant(hba);
+ u32 enable_val, val;
+
+ if (status == PRE_CHANGE) {
+ enable_val = CONTROLLER_ENABLE;
+
+ if (hba->caps & UFSHCD_CAP_CRYPTO)
+ enable_val = CRYPTO_GENERAL_ENABLE | CONTROLLER_ENABLE;
+
+ if (!host->first_hce_done) {
+ host->first_hce_done = true;
+ dev_dbg(hba->dev, "First HCE enable\n");
+ } else {
+ val = ufshcd_readl(hba, REG_CONTROLLER_ENABLE);
+ if (val == enable_val) {
+ ufshcd_writel(hba, enable_val & (1 << CONTROLLER_ENABLE),
+ REG_CONTROLLER_ENABLE);
+
+ while (ufshcd_readl(hba, REG_CONTROLLER_ENABLE) ==
+ (enable_val & (1 << CONTROLLER_ENABLE)))
+ ;
+ }
+ }
+ }
+
+ return 0;
+}
+
+/**
+ * struct ufs_hba_spacemit_vops - UFS Spacemit specific variant operations
+ *
+ * The variant operations configure the necessary controller and PHY
+ * handshake during initialization.
+ */
+static const struct ufs_hba_variant_ops ufs_hba_spacemit_vops = {
+ .name = "ufshcd-spacemit",
+ .init = ufs_spacemit_init,
+ .link_startup_notify = ufs_spacemit_link_startup_notify,
+ .negotiate_pwr_mode = ufs_spacemit_negotiate_pwr_mode,
+ .pwr_change_notify = ufs_spacemit_pwr_change_notify,
+ .device_reset = ufs_spacemit_device_reset,
+ .event_notify = ufs_spacemit_event_notify,
+ .apply_dev_quirks = ufs_spacemit_apply_dev_quirks,
+ .hibern8_notify = ufs_spacemit_hibern8_notify,
+ .hce_enable_notify = ufs_spacemit_hce_enable_notify,
+};
+
+static const struct of_device_id ufs_spacemit_of_match[] = {
+ { .compatible = "spacemit,k3-ufshc" },
+ { /* sentinel */ },
+};
+MODULE_DEVICE_TABLE(of, ufs_spacemit_of_match);
+
+static int ufs_spacemit_probe(struct platform_device *pdev)
+{
+ return ufshcd_pltfrm_init(pdev, &ufs_hba_spacemit_vops);
+}
+
+static void ufs_spacemit_remove(struct platform_device *pdev)
+{
+ ufshcd_pltfrm_remove(pdev);
+}
+
+static struct platform_driver ufs_spacemit_pltform = {
+ .probe = ufs_spacemit_probe,
+ .remove = ufs_spacemit_remove,
+ .driver = {
+ .name = "ufshcd-spacemit",
+ .of_match_table = of_match_ptr(ufs_spacemit_of_match),
+ },
+};
+module_platform_driver(ufs_spacemit_pltform);
+
+MODULE_DESCRIPTION("SpacemiT UFS Host Driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/ufs/host/ufs-spacemit.h b/drivers/ufs/host/ufs-spacemit.h
new file mode 100644
index 000000000000..6ae3c263a360
--- /dev/null
+++ b/drivers/ufs/host/ufs-spacemit.h
@@ -0,0 +1,90 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * SpacemiT UFS Host Controller driver
+ *
+ * Copyright (c) 2026 SpacemiT (Hangzhou) Technology Co. Ltd
+ */
+
+#ifndef _UFS_SPACEMIT_H_
+#define _UFS_SPACEMIT_H_
+
+#include <linux/reset-controller.h>
+#include <linux/reset.h>
+#include <linux/regulator/consumer.h>
+#include <linux/pm_qos.h>
+#include <linux/workqueue.h>
+
+/* Spacemit K3 UFS host controller vendor specific registers */
+#define UFS_SYS1CLK_1US 0xC0
+#define UFS_TX_SYMBOL_CLK_NS_US 0xC4
+#define UFS_LOCAL_PORT_ID_REG 0xC8
+#define UFS_PA_ERR_CODE 0xCC
+#define UFS_RETRY_TIMER_REG 0xD0
+#define UFS_PA_LINK_STARTUP_TIMER 0xD8
+#define UFS_CFG1 0xDC
+
+/* MPHY control registers */
+#define UFS_PHY_MNG_BASE 0x1B00
+#define UFS_MPHY_RST_CTRL 0x0
+#define UFS_MPHY_PU_CTRL 0x4
+#define UFS_MPHY_BKDR_CTRL 0x8
+#define UFS_DEVICE_IO_CTRL 0xC
+
+/* ATOP base*/
+#define UFS_ATOP_BASE 0x1C00
+
+#define UFS_SYSCLK 499
+#define UFS_TX_SYMBO_CLK 0x800
+#define UFS_MAX_LINKSTARTUP_TIMER 0xFFFFFFFF
+#define UFS_DL_AFC0REQTIMEOUTVAL_MAX 0xFFFF
+
+#define MPHY_TX_FSM_STATE 0x41
+#define TX_FSM_HIBERN8 0x1
+#define HBRN8_POLL_TOUT_MS 100
+#define DEFAULT_CLK_RATE_HZ 1000000
+#define BUS_VECTOR_NAME_LEN 32
+
+#define UFS_HW_VER_MAJOR_SHFT 28
+#define UFS_HW_VER_MAJOR_MASK (0x000F << UFS_HW_VER_MAJOR_SHFT)
+#define UFS_HW_VER_MINOR_SHFT 16
+#define UFS_HW_VER_MINOR_MASK (0x0FFF << UFS_HW_VER_MINOR_SHFT)
+#define UFS_HW_VER_STEP_SHFT 0
+#define UFS_HW_VER_STEP_MASK (0xFFFF << UFS_HW_VER_STEP_SHFT)
+
+#define UFS_SPACEMIT_K3_LIMIT_NUM_LANES_RX 2
+#define UFS_SPACEMIT_K3_LIMIT_NUM_LANES_TX 2
+#define UFS_SPACEMIT_K3_LIMIT_HSGEAR_RX UFS_HS_G3
+#define UFS_SPACEMIT_K3_LIMIT_HSGEAR_TX UFS_HS_G3
+#define UFS_SPACEMIT_K3_LIMIT_PWMGEAR_RX UFS_PWM_G4
+#define UFS_SPACEMIT_K3_LIMIT_PWMGEAR_TX UFS_PWM_G4
+#define UFS_SPACEMIT_K3_LIMIT_RX_PWR_PWM SLOW_MODE
+#define UFS_SPACEMIT_K3_LIMIT_TX_PWR_PWM SLOW_MODE
+#define UFS_SPACEMIT_K3_LIMIT_RX_PWR_HS FAST_MODE
+#define UFS_SPACEMIT_K3_LIMIT_TX_PWR_HS FAST_MODE
+#define UFS_SPACEMIT_K3_LIMIT_HS_RATE PA_HS_MODE_B
+#define UFS_SPACEMIT_K3_LIMIT_DESIRED_MODE 2
+
+#define UFS_PA_VS_CONFIG_REG1 0x9000
+#define UFS_DME_VS_CORE_CLK_CTRL 0xD002
+
+#define UFS_HCLKDIV_REG 0xFC
+
+struct ufs_spacemit_host {
+ struct ufs_hba *hba;
+ struct ufs_pa_layer_attr dev_req_params;
+ struct reset_control *rst;
+ u32 caps;
+ u32 lpm_qos;
+ u32 unipro_ver;
+ u32 remote_unipro_ver;
+ u8 prev_request_crypto;
+
+ bool is_lane_clks_on;
+ bool first_init_done;
+ bool first_hce_done;
+
+ /* Workqueue for deferred FSM state dump */
+ struct work_struct fsm_dump_work;
+};
+
+#endif /* _UFS_SPACEMIT_H_ */
--
2.54.0
^ permalink raw reply related [flat|nested] 10+ messages in thread