All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH v2 0/3] Add UFS Host driver support for SpacemiT K3 SoC
@ 2026-07-25 10:37 ` Yixun Lan
  0 siblings, 0 replies; 13+ messages in thread
From: Yixun Lan @ 2026-07-25 10:37 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: Anirudh Srinivasan, Aurelien Jarno, Jennifer Berringer,
	linux-scsi, devicetree, linux-riscv, spacemit, linux-kernel,
	Yixun Lan

This series try to add UFS support for SpacemiT K3 SoC, the controller
components consists of System Bus Interface Unit, UFS Host Controller
Interface, UFS Transport Protocol Layer, UFS Host Registers, Device
Management Entity (DME), Transport Layer, Network Layer, Data Link
Layer, PHY Adapter Layer, and M-PHY Interface. A more detail functional
block diagram can be found in SpacemiT website, chapter 9.7.3 [1]

Please note, in order to test this driver, the UFS clock driver[2] here
should be applied first as a prerequisite patch.

One known issue is that the device will occasionally raise BKOPS interrupt
when doing some high load test, log from dmesg shows

[  806.710763] ufshcd-spacemit c0e00000.ufshc: ufshcd_bkops_exception_event_handler: device raised urgent BKOPS exception for bkops status 1

Link: https://spacemit.com/community/document/info?nodepath=hardware/key_stone/k3/k3_docs/k3_usermanual/09_memory_storage.md&lang=en [1]
Link: https://lore.kernel.org/all/20260630-06-clk-ufs-support-v1-0-cf7521d1d0fe@kernel.org/ [2]
Signed-off-by: Yixun Lan <dlan@kernel.org>
---
Changes in v2:
- fix missing interrupts property
- move ufs node to k3-com260.dtsi
- convert freq-table-hz to operating-points-v2
- rework reset_control, fix header and drop global variable
- drop dump_host_regs() debug interface
- converge and refactor dme_set() operations
- massive reduce of busy delay() loops
- drop vendor specific hce_enable_notify(), fallback to default
- drop unused member from struct ufs_spacemit_host
- add COMPILE_TEST support to Kconfig
- comment style fix
- Link to v1: https://patch.msgid.link/20260702-08-k3-ufs-support-v1-0-1a64a3ab128f@kernel.org

---
Yixun Lan (3):
      scsi: ufs: spacemit: dt-bindings: Add UFS controller for K3 SoC
      scsi: ufs: spacemit: k3: Add UFS Host Controller driver
      riscv: dts: spacemit: k3: Add UFS support

 .../devicetree/bindings/ufs/spacemit,k3-ufshc.yaml |  56 ++
 arch/riscv/boot/dts/spacemit/k3-com260.dtsi        |   4 +
 arch/riscv/boot/dts/spacemit/k3-pico-itx.dts       |   4 +
 arch/riscv/boot/dts/spacemit/k3.dtsi               |  27 +
 drivers/ufs/host/Kconfig                           |  12 +
 drivers/ufs/host/Makefile                          |   1 +
 drivers/ufs/host/ufs-spacemit.c                    | 686 +++++++++++++++++++++
 drivers/ufs/host/ufs-spacemit.h                    |  79 +++
 8 files changed, 869 insertions(+)
---
base-commit: dc59e4fea9d83f03bad6bddf3fa2e52491777482
change-id: 20260605-08-k3-ufs-support-c8b308e415e2

Best regards,
--  
Yixun Lan <dlan@kernel.org>


^ permalink raw reply	[flat|nested] 13+ messages in thread

* [PATCH v2 0/3] Add UFS Host driver support for SpacemiT K3 SoC
@ 2026-07-25 10:37 ` Yixun Lan
  0 siblings, 0 replies; 13+ messages in thread
From: Yixun Lan @ 2026-07-25 10:37 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: Anirudh Srinivasan, Aurelien Jarno, Jennifer Berringer,
	linux-scsi, devicetree, linux-riscv, spacemit, linux-kernel,
	Yixun Lan

This series try to add UFS support for SpacemiT K3 SoC, the controller
components consists of System Bus Interface Unit, UFS Host Controller
Interface, UFS Transport Protocol Layer, UFS Host Registers, Device
Management Entity (DME), Transport Layer, Network Layer, Data Link
Layer, PHY Adapter Layer, and M-PHY Interface. A more detail functional
block diagram can be found in SpacemiT website, chapter 9.7.3 [1]

Please note, in order to test this driver, the UFS clock driver[2] here
should be applied first as a prerequisite patch.

One known issue is that the device will occasionally raise BKOPS interrupt
when doing some high load test, log from dmesg shows

[  806.710763] ufshcd-spacemit c0e00000.ufshc: ufshcd_bkops_exception_event_handler: device raised urgent BKOPS exception for bkops status 1

Link: https://spacemit.com/community/document/info?nodepath=hardware/key_stone/k3/k3_docs/k3_usermanual/09_memory_storage.md&lang=en [1]
Link: https://lore.kernel.org/all/20260630-06-clk-ufs-support-v1-0-cf7521d1d0fe@kernel.org/ [2]
Signed-off-by: Yixun Lan <dlan@kernel.org>
---
Changes in v2:
- fix missing interrupts property
- move ufs node to k3-com260.dtsi
- convert freq-table-hz to operating-points-v2
- rework reset_control, fix header and drop global variable
- drop dump_host_regs() debug interface
- converge and refactor dme_set() operations
- massive reduce of busy delay() loops
- drop vendor specific hce_enable_notify(), fallback to default
- drop unused member from struct ufs_spacemit_host
- add COMPILE_TEST support to Kconfig
- comment style fix
- Link to v1: https://patch.msgid.link/20260702-08-k3-ufs-support-v1-0-1a64a3ab128f@kernel.org

---
Yixun Lan (3):
      scsi: ufs: spacemit: dt-bindings: Add UFS controller for K3 SoC
      scsi: ufs: spacemit: k3: Add UFS Host Controller driver
      riscv: dts: spacemit: k3: Add UFS support

 .../devicetree/bindings/ufs/spacemit,k3-ufshc.yaml |  56 ++
 arch/riscv/boot/dts/spacemit/k3-com260.dtsi        |   4 +
 arch/riscv/boot/dts/spacemit/k3-pico-itx.dts       |   4 +
 arch/riscv/boot/dts/spacemit/k3.dtsi               |  27 +
 drivers/ufs/host/Kconfig                           |  12 +
 drivers/ufs/host/Makefile                          |   1 +
 drivers/ufs/host/ufs-spacemit.c                    | 686 +++++++++++++++++++++
 drivers/ufs/host/ufs-spacemit.h                    |  79 +++
 8 files changed, 869 insertions(+)
---
base-commit: dc59e4fea9d83f03bad6bddf3fa2e52491777482
change-id: 20260605-08-k3-ufs-support-c8b308e415e2

Best regards,
--  
Yixun Lan <dlan@kernel.org>


_______________________________________________
linux-riscv mailing list
linux-riscv@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-riscv

^ permalink raw reply	[flat|nested] 13+ messages in thread

* [PATCH v2 1/3] scsi: ufs: spacemit: dt-bindings: Add UFS controller for K3 SoC
  2026-07-25 10:37 ` Yixun Lan
@ 2026-07-25 10:37   ` Yixun Lan
  -1 siblings, 0 replies; 13+ messages in thread
From: Yixun Lan @ 2026-07-25 10:37 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: Anirudh Srinivasan, Aurelien Jarno, Jennifer Berringer,
	linux-scsi, devicetree, linux-riscv, spacemit, linux-kernel,
	Yixun Lan

Document the compatible for UFS (Universal Flash Storage) Host Controller
which found in SpacemiT K3 SoC chip. Its features are listed below:

- Compliant with MIPI UniPro v1.61 specification.
- Compliant with M-PHY v3.0 specification.
- Compliant with UFS HCI v2.1 specification.
- Supports up to 2 Tx and 2 Rx lanes, up to HS-GEAR3 5.8 Gbps per lane.
- Supports standard low-power hibernate to reduce power consumption.

Signed-off-by: Yixun Lan <dlan@kernel.org>
---
 .../devicetree/bindings/ufs/spacemit,k3-ufshc.yaml | 56 ++++++++++++++++++++++
 1 file changed, 56 insertions(+)

diff --git a/Documentation/devicetree/bindings/ufs/spacemit,k3-ufshc.yaml b/Documentation/devicetree/bindings/ufs/spacemit,k3-ufshc.yaml
new file mode 100644
index 000000000000..45cd7837c3f7
--- /dev/null
+++ b/Documentation/devicetree/bindings/ufs/spacemit,k3-ufshc.yaml
@@ -0,0 +1,56 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/ufs/spacemit,k3-ufshc.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: SpacemiT K3 SoC UFS Host Controller
+
+maintainers:
+  - Yixun Lan <dlan@kernel.org>
+
+properties:
+  compatible:
+    const: spacemit,k3-ufshc
+
+  reg:
+    maxItems: 1
+
+  clocks:
+    maxItems: 2
+
+  clock-names:
+    items:
+      - const: aclk
+      - const: ref_clk
+
+  resets:
+    maxItems: 1
+
+required:
+  - reg
+  - clocks
+  - clock-names
+  - resets
+
+allOf:
+  - $ref: ufs-common.yaml
+
+unevaluatedProperties: false
+
+examples:
+  - |
+    #include <dt-bindings/clock/spacemit,k3-clocks.h>
+    #include <dt-bindings/reset/spacemit,k3-resets.h>
+    #include <dt-bindings/interrupt-controller/irq.h>
+    ufs@c0e00000 {
+        compatible = "spacemit,k3-ufshc";
+        reg = <0xc0e00000 0x40000>;
+        clocks = <&syscon_apmu CLK_APMU_UFS_ACLK>,
+        <&syscon_apmu CLK_APMU_UFS_REFCLK>;
+        clock-names = "aclk", "ref_clk";
+        resets = <&syscon_apmu RESET_APMU_UFS_ACLK>;
+        interrupts = <135 IRQ_TYPE_LEVEL_HIGH>;
+        freq-table-hz = <491520000 491520000 19200000 19200000>;
+        lanes-per-direction = <2>;
+    };

-- 
2.54.0


^ permalink raw reply related	[flat|nested] 13+ messages in thread

* [PATCH v2 1/3] scsi: ufs: spacemit: dt-bindings: Add UFS controller for K3 SoC
@ 2026-07-25 10:37   ` Yixun Lan
  0 siblings, 0 replies; 13+ messages in thread
From: Yixun Lan @ 2026-07-25 10:37 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: Anirudh Srinivasan, Aurelien Jarno, Jennifer Berringer,
	linux-scsi, devicetree, linux-riscv, spacemit, linux-kernel,
	Yixun Lan

Document the compatible for UFS (Universal Flash Storage) Host Controller
which found in SpacemiT K3 SoC chip. Its features are listed below:

- Compliant with MIPI UniPro v1.61 specification.
- Compliant with M-PHY v3.0 specification.
- Compliant with UFS HCI v2.1 specification.
- Supports up to 2 Tx and 2 Rx lanes, up to HS-GEAR3 5.8 Gbps per lane.
- Supports standard low-power hibernate to reduce power consumption.

Signed-off-by: Yixun Lan <dlan@kernel.org>
---
 .../devicetree/bindings/ufs/spacemit,k3-ufshc.yaml | 56 ++++++++++++++++++++++
 1 file changed, 56 insertions(+)

diff --git a/Documentation/devicetree/bindings/ufs/spacemit,k3-ufshc.yaml b/Documentation/devicetree/bindings/ufs/spacemit,k3-ufshc.yaml
new file mode 100644
index 000000000000..45cd7837c3f7
--- /dev/null
+++ b/Documentation/devicetree/bindings/ufs/spacemit,k3-ufshc.yaml
@@ -0,0 +1,56 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/ufs/spacemit,k3-ufshc.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: SpacemiT K3 SoC UFS Host Controller
+
+maintainers:
+  - Yixun Lan <dlan@kernel.org>
+
+properties:
+  compatible:
+    const: spacemit,k3-ufshc
+
+  reg:
+    maxItems: 1
+
+  clocks:
+    maxItems: 2
+
+  clock-names:
+    items:
+      - const: aclk
+      - const: ref_clk
+
+  resets:
+    maxItems: 1
+
+required:
+  - reg
+  - clocks
+  - clock-names
+  - resets
+
+allOf:
+  - $ref: ufs-common.yaml
+
+unevaluatedProperties: false
+
+examples:
+  - |
+    #include <dt-bindings/clock/spacemit,k3-clocks.h>
+    #include <dt-bindings/reset/spacemit,k3-resets.h>
+    #include <dt-bindings/interrupt-controller/irq.h>
+    ufs@c0e00000 {
+        compatible = "spacemit,k3-ufshc";
+        reg = <0xc0e00000 0x40000>;
+        clocks = <&syscon_apmu CLK_APMU_UFS_ACLK>,
+        <&syscon_apmu CLK_APMU_UFS_REFCLK>;
+        clock-names = "aclk", "ref_clk";
+        resets = <&syscon_apmu RESET_APMU_UFS_ACLK>;
+        interrupts = <135 IRQ_TYPE_LEVEL_HIGH>;
+        freq-table-hz = <491520000 491520000 19200000 19200000>;
+        lanes-per-direction = <2>;
+    };

-- 
2.54.0


_______________________________________________
linux-riscv mailing list
linux-riscv@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-riscv

^ permalink raw reply related	[flat|nested] 13+ messages in thread

* [PATCH v2 2/3] scsi: ufs: spacemit: k3: Add UFS Host Controller driver
  2026-07-25 10:37 ` Yixun Lan
@ 2026-07-25 10:37   ` Yixun Lan
  -1 siblings, 0 replies; 13+ messages in thread
From: Yixun Lan @ 2026-07-25 10:37 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: Anirudh Srinivasan, Aurelien Jarno, Jennifer Berringer,
	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 | 686 ++++++++++++++++++++++++++++++++++++++++
 drivers/ufs/host/ufs-spacemit.h |  79 +++++
 4 files changed, 778 insertions(+)

diff --git a/drivers/ufs/host/Kconfig b/drivers/ufs/host/Kconfig
index ff170c0b6da0..6146872fcecc 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 || COMPILE_TEST)
+	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..30e5092071af
--- /dev/null
+++ b/drivers/ufs/host/ufs-spacemit.c
@@ -0,0 +1,686 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2026 SpacemiT (Hangzhou) Technology Co. Ltd
+ */
+
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/io.h>
+#include <linux/iopoll.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/reset.h>
+#include <linux/slab.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 register */
+#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
+
+#define VENDOR_DUMP_BUF_SIZE		2048
+#define VENDOR_MAX_OFFSET		0xE0
+
+/* M-PHY FSM states */
+#define MPHY_RX_FSM_STATE	0xC1
+#define MPHY_TX_FSM_STATE	0x41
+
+static int ufs_spacemit_dme_set(struct ufs_hba *hba,
+				const struct ufshcd_dme_attr_val *v, int n)
+{
+	int ret = 0;
+	int attr_node = 0;
+
+	for (attr_node = 0; attr_node < n; attr_node++) {
+		ret = ufshcd_dme_set(hba, v[attr_node].attr_sel,
+				     v[attr_node].mib_val);
+
+		if (ret)
+			return ret;
+	}
+
+	return 0;
+}
+
+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 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)
+{
+	u32 val;
+	int err;
+
+	err = read_poll_timeout(ufshcd_readl, val, val & MPHY_PLL_LOCK_BIT,
+				10, MPHY_PLL_LOCK_TIMEOUT_US, false, hba,
+				UFS_PHY_MNG_BASE + UFS_MPHY_PU_CTRL);
+
+	if (err)
+		dev_err(hba->dev, "M-PHY PLL lock timeout\n");
+
+	return err;
+}
+
+/**
+ * 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);
+
+	/* power up all */
+	ufshcd_writel(hba, MPHY_PU_ALL, UFS_PHY_MNG_BASE + 0x4);
+
+	/* asserted ana_rx_hb8_reset */
+	ufshcd_writel(hba, 0xb7f, UFS_PHY_MNG_BASE + 0x4);
+	fsleep(500);
+
+	/* deasserted ana_rx_hb8_reset */
+	ufshcd_writel(hba, MPHY_PU_ALL, UFS_PHY_MNG_BASE + 0x4);
+
+	/* deasserted ufs device reset & refer clk output enable */
+	ufshcd_writel(hba, 0x101, UFS_PHY_MNG_BASE + 0xC);
+	fsleep(1000);
+
+	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);
+
+	ufshcd_writel(hba, 0x40, UFS_ATOP_BASE + (0xC2 << 2));
+
+	ufshcd_writel(hba, 0x0, UFS_PHY_MNG_BASE + 0x08);
+
+	fsleep(2000);
+
+	dev_dbg(hba->dev, "M-PHY init completed\n");
+
+	return 0;
+}
+
+static int ufs_spacemit_uniprov1p6_init(struct ufs_hba *hba)
+{
+	static const struct ufshcd_dme_attr_val setup_attrs[] = {
+		{ UIC_ARG_MIB(0x1552), 0x4f }, /* PA_TXHSG1SYNCLENGTH */
+		{ UIC_ARG_MIB(0x1553), 0xf }, /* PA_TXHSG1PREPARELENGTH */
+		{ UIC_ARG_MIB(0x1554), 0x4f }, /* PA_TXHSG2SYNCLENGTH */
+		{ UIC_ARG_MIB(0x1555), 0xf }, /* PA_TXHSG2PREPARELENGTH */
+		{ UIC_ARG_MIB(0x1556), 0x4f }, /* PA_TXHSG3SYNCLENGTH */
+		{ UIC_ARG_MIB(0x1557), 0xf }, /* PA_TXHSG3PREPARELENGTH */
+		{ UIC_ARG_MIB(0x155A), 0x0 }, /* PA_TXMK2EXTENSION */
+		{ UIC_ARG_MIB(0x155B), 0x1}, /* PA_PEERSCRAMBLING */
+		{ UIC_ARG_MIB(0x155C), 0x1 }, /* PA_TXSKIP */
+		{ UIC_ARG_MIB(0x155D), 250 }, /* PA_TXSKIPPERIOD */
+		{ UIC_ARG_MIB(0x155E), 0x0 }, /* PA_LOCAL_TX_LCC_ENABLE */
+		{ UIC_ARG_MIB(0x155F), 0x0 }, /* PA_PEER_TX_LCC_ENABLE */
+		{ UIC_ARG_MIB(0x1585), 0x1 }, /* PA_SCRAMBLING */
+		{ UIC_ARG_MIB(0x15AA), 0x1 }, /* PA_GRANULARITY */
+		{ UIC_ARG_MIB(0x15AB), 0x0 }, /* PA_MK2EXTENSIONGUARDBAND */
+		{ UIC_ARG_MIB(0x15A3), 15 }, /* PA_STALLNOCONFIGTIME */
+		{ UIC_ARG_MIB(0x15A8), 0x64 }, /* PA_TACTIVATE */
+		{ UIC_ARG_MIB(0x1564), 0x64 },	/* PA_TXTRAILINGCLOCKS */
+		{ UIC_ARG_MIB_SEL(0x008D, 4), 0x0B }, /* RX_LS_PREPARELEN_TIME RX0 */
+		{ UIC_ARG_MIB_SEL(0x008D, 5), 0x0B }, /* RX_LS_PREPARELEN_TIME RX1 */
+
+		/* RX_HIBERNATE_BKEN RX0 */
+		{ UIC_ARG_MIB_SEL(0x00F4, UIC_ARG_MPHY_RX_GEN_SEL_INDEX(0)), 0x9F },
+		/* RX_HIBERNATE_BKEN RX1 */
+		{ UIC_ARG_MIB_SEL(0x00F4, UIC_ARG_MPHY_RX_GEN_SEL_INDEX(1)), 0x9F },
+		/* PWM_BURST_closure_length */
+		{ UIC_ARG_MIB_SEL(0x008E, UIC_ARG_MPHY_RX_GEN_SEL_INDEX(0)),   15 },
+		{ UIC_ARG_MIB_SEL(0x008E, UIC_ARG_MPHY_RX_GEN_SEL_INDEX(1)),   15 },
+		/* min_stall_not_config_time */
+		{ UIC_ARG_MIB_SEL(0x0088, UIC_ARG_MPHY_RX_GEN_SEL_INDEX(0)), 0xFF },
+		{ UIC_ARG_MIB_SEL(0x0088, UIC_ARG_MPHY_RX_GEN_SEL_INDEX(1)), 0xFF },
+		/* TX HB8_TIME CAP */
+		{ UIC_ARG_MIB_SEL(0x000F, UIC_ARG_MPHY_TX_GEN_SEL_INDEX(0)), 0x64 },
+		{ UIC_ARG_MIB_SEL(0x000F, UIC_ARG_MPHY_TX_GEN_SEL_INDEX(1)), 0x64 },
+		/* RX HB8_TIME CAP */
+		{ UIC_ARG_MIB_SEL(0x0092, UIC_ARG_MPHY_RX_GEN_SEL_INDEX(0)), 0x64 },
+		{ UIC_ARG_MIB_SEL(0x0092, UIC_ARG_MPHY_RX_GEN_SEL_INDEX(1)), 0x64 },
+		/* TX EQ 3DB */
+		{ UIC_ARG_MIB_SEL(0x00CD, UIC_ARG_MPHY_TX_GEN_SEL_INDEX(0)),  0x5 },
+		/* RX garbage cnt = 32 SI */
+		{ UIC_ARG_MIB_SEL(0x00F2, UIC_ARG_MPHY_RX_GEN_SEL_INDEX(0)), 0x9F },
+		{ UIC_ARG_MIB_SEL(0x00F2, UIC_ARG_MPHY_RX_GEN_SEL_INDEX(1)), 0x9F },
+	};
+
+	return ufs_spacemit_dme_set(hba, setup_attrs, ARRAY_SIZE(setup_attrs));
+}
+
+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)
+{
+	/* Add 0xe8 make UFS2.1 run GEAR3 + 2Lane@409M */
+	static const struct ufshcd_dme_attr_val setup_attrs[] = {
+		{ UIC_ARG_MIB_SEL(0xe8, UIC_ARG_MPHY_TX_GEN_SEL_INDEX(0)), 0x97 },
+		{ UIC_ARG_MIB_SEL(0xe8, UIC_ARG_MPHY_TX_GEN_SEL_INDEX(0)), 0xd7 },
+		{ UIC_ARG_MIB_SEL(0xe8, UIC_ARG_MPHY_TX_GEN_SEL_INDEX(0)), 0x17 },
+		{ UIC_ARG_MIB(DL_AFC0REQTIMEOUTVAL), UFS_DL_AFC0REQTIMEOUTVAL_MAX },
+	};
+	u32 tx_lanes;
+
+	ufs_spacemit_dme_set(hba, setup_attrs, ARRAY_SIZE(setup_attrs));
+
+	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)
+{
+	static const struct ufshcd_dme_attr_val setup_attrs[] = {
+		/* LCC_DISABLE */
+		{ UIC_ARG_MIB_SEL(TX_LCC_ENABLE, UIC_ARG_MPHY_TX_GEN_SEL_INDEX(0)), 0 },
+		{ UIC_ARG_MIB_SEL(TX_LCC_ENABLE, UIC_ARG_MPHY_TX_GEN_SEL_INDEX(1)), 0 },
+		/* TX_Min_ActivateTime */
+		{ UIC_ARG_MIB_SEL(TX_MIN_ACTIVATETIME, UIC_ARG_MPHY_TX_GEN_SEL_INDEX(0)), 0x0 },
+		{ UIC_ARG_MIB_SEL(TX_MIN_ACTIVATETIME, UIC_ARG_MPHY_TX_GEN_SEL_INDEX(1)), 0x0 },
+		{ UIC_ARG_MIB(ANA_HSGEAR_CTRL_ATTR), 0x25 },
+	};
+
+	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;
+
+	ufs_spacemit_dme_set(hba, setup_attrs, ARRAY_SIZE(setup_attrs));
+
+	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_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;
+	struct reset_control *rst;
+
+	host = devm_kzalloc(dev, sizeof(*host), GFP_KERNEL);
+	if (!host)
+		return -ENOMEM;
+
+	rst = devm_reset_control_get_exclusive_deasserted(dev, NULL);
+	if (IS_ERR(rst))
+		return dev_err_probe(dev, PTR_ERR(rst), "Failed to get reset control\n");
+
+	host->hba = hba;
+	ufshcd_set_variant(hba, host);
+	ufs_spacemit_advertise_quirks(hba);
+
+	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);
+	usleep_range(10, 15);
+
+	/* Enable device ref_clk & de-asserted ufs device reset */
+	ufshcd_writel(hba, MPHY_DEVICE_RESET_DEASSERT, UFS_PHY_MNG_BASE + UFS_DEVICE_IO_CTRL);
+	usleep_range(10, 15);
+
+	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)
+{
+	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);
+
+		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);
+
+		break;
+	case UFS_EVT_ABORT:
+		dev_warn(hba->dev, "Abort event, INT errors:0x%x\n", hba->errors);
+		break;
+	default:
+		break;
+	}
+}
+
+static void ufs_spacemit_pre_hibern8(struct ufs_hba *hba, enum uic_cmd_dme cmd)
+{
+	static const struct ufshcd_dme_attr_val setup_attrs[] = {
+		{ UIC_ARG_MIB_SEL(0xf1, UIC_ARG_MPHY_RX_GEN_SEL_INDEX(0)), 0x84 },
+		{ UIC_ARG_MIB_SEL(0xf1, UIC_ARG_MPHY_RX_GEN_SEL_INDEX(1)), 0x84 },
+		{ UIC_ARG_MIB_SEL(0xf1, UIC_ARG_MPHY_RX_GEN_SEL_INDEX(0)), 0x85 },
+		{ UIC_ARG_MIB_SEL(0xf1, UIC_ARG_MPHY_RX_GEN_SEL_INDEX(1)), 0x85 },
+	};
+	int ret;
+
+	if (cmd == UIC_CMD_DME_HIBER_EXIT) {
+		/* Enable reference clock */
+		ufshcd_writel(hba, MPHY_DEVICE_RESET_DEASSERT,
+			      UFS_PHY_MNG_BASE + UFS_DEVICE_IO_CTRL);
+
+		/* Power up all */
+		ufshcd_writel(hba, MPHY_PU_ALL, UFS_PHY_MNG_BASE + UFS_MPHY_PU_CTRL);
+
+		/* Assert ana_rx_hb8_reset */
+		ufshcd_writel(hba, MPHY_PU_WITH_HB8_RESET,
+			      UFS_PHY_MNG_BASE + UFS_MPHY_PU_CTRL);
+		fsleep(500);
+
+		/* 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;
+
+		ufshcd_dme_set(hba, UIC_ARG_MIB(0xdd), 0x57);
+		ufshcd_dme_set(hba, UIC_ARG_MIB(0xe8), 0x57);
+	}
+
+	if (cmd == UIC_CMD_DME_HIBER_ENTER)
+		ufs_spacemit_dme_set(hba, setup_attrs, ARRAY_SIZE(setup_attrs));
+}
+
+static void ufs_spacemit_post_hibern8(struct ufs_hba *hba, enum uic_cmd_dme cmd)
+{
+	static const struct ufshcd_dme_attr_val setup_attrs[] = {
+		{ UIC_ARG_MIB_SEL(0xf1, UIC_ARG_MPHY_RX_GEN_SEL_INDEX(0)), 0x84 },
+		{ UIC_ARG_MIB_SEL(0xf1, UIC_ARG_MPHY_RX_GEN_SEL_INDEX(1)), 0x84 },
+		{ UIC_ARG_MIB_SEL(0xf1, UIC_ARG_MPHY_RX_GEN_SEL_INDEX(0)), 0x80 },
+		{ UIC_ARG_MIB_SEL(0xf1, UIC_ARG_MPHY_RX_GEN_SEL_INDEX(1)), 0x80 },
+		{ UIC_ARG_MIB(0xdd), 0x57 },
+		{ UIC_ARG_MIB(0xdd), 0xd7 },
+		{ UIC_ARG_MIB(0xe8), 0x57 },
+		{ UIC_ARG_MIB(0xe8), 0xd7 },
+	};
+
+	if (cmd == UIC_CMD_DME_HIBER_ENTER) {
+		ufs_spacemit_check_hibern8(hba);
+
+		ufs_spacemit_dme_set(hba, setup_attrs, ARRAY_SIZE(setup_attrs));
+
+		/* Power down M-PHY */
+		ufshcd_writel(hba, 0x0, UFS_PHY_MNG_BASE + UFS_MPHY_PU_CTRL);
+
+		/* Keep reference clock enabled, assert device reset */
+		ufshcd_writel(hba, MPHY_DEVICE_RESET_ASSERT,
+			      UFS_PHY_MNG_BASE + UFS_DEVICE_IO_CTRL);
+	}
+}
+
+/**
+ * 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)
+{
+	if (status == PRE_CHANGE)
+		ufs_spacemit_pre_hibern8(hba, cmd);
+
+	if (status == POST_CHANGE)
+		ufs_spacemit_post_hibern8(hba, cmd);
+}
+
+/**
+ * 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,
+};
+
+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..5e459f6ba243
--- /dev/null
+++ b/drivers/ufs/host/ufs-spacemit.h
@@ -0,0 +1,79 @@
+/* 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/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 ufshcd_dme_attr_val {
+	u32 attr_sel;
+	u32 mib_val;
+};
+#endif /* _UFS_SPACEMIT_H_ */

-- 
2.54.0


^ permalink raw reply related	[flat|nested] 13+ messages in thread

* [PATCH v2 2/3] scsi: ufs: spacemit: k3: Add UFS Host Controller driver
@ 2026-07-25 10:37   ` Yixun Lan
  0 siblings, 0 replies; 13+ messages in thread
From: Yixun Lan @ 2026-07-25 10:37 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: Anirudh Srinivasan, Aurelien Jarno, Jennifer Berringer,
	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 | 686 ++++++++++++++++++++++++++++++++++++++++
 drivers/ufs/host/ufs-spacemit.h |  79 +++++
 4 files changed, 778 insertions(+)

diff --git a/drivers/ufs/host/Kconfig b/drivers/ufs/host/Kconfig
index ff170c0b6da0..6146872fcecc 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 || COMPILE_TEST)
+	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..30e5092071af
--- /dev/null
+++ b/drivers/ufs/host/ufs-spacemit.c
@@ -0,0 +1,686 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2026 SpacemiT (Hangzhou) Technology Co. Ltd
+ */
+
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/io.h>
+#include <linux/iopoll.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/reset.h>
+#include <linux/slab.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 register */
+#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
+
+#define VENDOR_DUMP_BUF_SIZE		2048
+#define VENDOR_MAX_OFFSET		0xE0
+
+/* M-PHY FSM states */
+#define MPHY_RX_FSM_STATE	0xC1
+#define MPHY_TX_FSM_STATE	0x41
+
+static int ufs_spacemit_dme_set(struct ufs_hba *hba,
+				const struct ufshcd_dme_attr_val *v, int n)
+{
+	int ret = 0;
+	int attr_node = 0;
+
+	for (attr_node = 0; attr_node < n; attr_node++) {
+		ret = ufshcd_dme_set(hba, v[attr_node].attr_sel,
+				     v[attr_node].mib_val);
+
+		if (ret)
+			return ret;
+	}
+
+	return 0;
+}
+
+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 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)
+{
+	u32 val;
+	int err;
+
+	err = read_poll_timeout(ufshcd_readl, val, val & MPHY_PLL_LOCK_BIT,
+				10, MPHY_PLL_LOCK_TIMEOUT_US, false, hba,
+				UFS_PHY_MNG_BASE + UFS_MPHY_PU_CTRL);
+
+	if (err)
+		dev_err(hba->dev, "M-PHY PLL lock timeout\n");
+
+	return err;
+}
+
+/**
+ * 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);
+
+	/* power up all */
+	ufshcd_writel(hba, MPHY_PU_ALL, UFS_PHY_MNG_BASE + 0x4);
+
+	/* asserted ana_rx_hb8_reset */
+	ufshcd_writel(hba, 0xb7f, UFS_PHY_MNG_BASE + 0x4);
+	fsleep(500);
+
+	/* deasserted ana_rx_hb8_reset */
+	ufshcd_writel(hba, MPHY_PU_ALL, UFS_PHY_MNG_BASE + 0x4);
+
+	/* deasserted ufs device reset & refer clk output enable */
+	ufshcd_writel(hba, 0x101, UFS_PHY_MNG_BASE + 0xC);
+	fsleep(1000);
+
+	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);
+
+	ufshcd_writel(hba, 0x40, UFS_ATOP_BASE + (0xC2 << 2));
+
+	ufshcd_writel(hba, 0x0, UFS_PHY_MNG_BASE + 0x08);
+
+	fsleep(2000);
+
+	dev_dbg(hba->dev, "M-PHY init completed\n");
+
+	return 0;
+}
+
+static int ufs_spacemit_uniprov1p6_init(struct ufs_hba *hba)
+{
+	static const struct ufshcd_dme_attr_val setup_attrs[] = {
+		{ UIC_ARG_MIB(0x1552), 0x4f }, /* PA_TXHSG1SYNCLENGTH */
+		{ UIC_ARG_MIB(0x1553), 0xf }, /* PA_TXHSG1PREPARELENGTH */
+		{ UIC_ARG_MIB(0x1554), 0x4f }, /* PA_TXHSG2SYNCLENGTH */
+		{ UIC_ARG_MIB(0x1555), 0xf }, /* PA_TXHSG2PREPARELENGTH */
+		{ UIC_ARG_MIB(0x1556), 0x4f }, /* PA_TXHSG3SYNCLENGTH */
+		{ UIC_ARG_MIB(0x1557), 0xf }, /* PA_TXHSG3PREPARELENGTH */
+		{ UIC_ARG_MIB(0x155A), 0x0 }, /* PA_TXMK2EXTENSION */
+		{ UIC_ARG_MIB(0x155B), 0x1}, /* PA_PEERSCRAMBLING */
+		{ UIC_ARG_MIB(0x155C), 0x1 }, /* PA_TXSKIP */
+		{ UIC_ARG_MIB(0x155D), 250 }, /* PA_TXSKIPPERIOD */
+		{ UIC_ARG_MIB(0x155E), 0x0 }, /* PA_LOCAL_TX_LCC_ENABLE */
+		{ UIC_ARG_MIB(0x155F), 0x0 }, /* PA_PEER_TX_LCC_ENABLE */
+		{ UIC_ARG_MIB(0x1585), 0x1 }, /* PA_SCRAMBLING */
+		{ UIC_ARG_MIB(0x15AA), 0x1 }, /* PA_GRANULARITY */
+		{ UIC_ARG_MIB(0x15AB), 0x0 }, /* PA_MK2EXTENSIONGUARDBAND */
+		{ UIC_ARG_MIB(0x15A3), 15 }, /* PA_STALLNOCONFIGTIME */
+		{ UIC_ARG_MIB(0x15A8), 0x64 }, /* PA_TACTIVATE */
+		{ UIC_ARG_MIB(0x1564), 0x64 },	/* PA_TXTRAILINGCLOCKS */
+		{ UIC_ARG_MIB_SEL(0x008D, 4), 0x0B }, /* RX_LS_PREPARELEN_TIME RX0 */
+		{ UIC_ARG_MIB_SEL(0x008D, 5), 0x0B }, /* RX_LS_PREPARELEN_TIME RX1 */
+
+		/* RX_HIBERNATE_BKEN RX0 */
+		{ UIC_ARG_MIB_SEL(0x00F4, UIC_ARG_MPHY_RX_GEN_SEL_INDEX(0)), 0x9F },
+		/* RX_HIBERNATE_BKEN RX1 */
+		{ UIC_ARG_MIB_SEL(0x00F4, UIC_ARG_MPHY_RX_GEN_SEL_INDEX(1)), 0x9F },
+		/* PWM_BURST_closure_length */
+		{ UIC_ARG_MIB_SEL(0x008E, UIC_ARG_MPHY_RX_GEN_SEL_INDEX(0)),   15 },
+		{ UIC_ARG_MIB_SEL(0x008E, UIC_ARG_MPHY_RX_GEN_SEL_INDEX(1)),   15 },
+		/* min_stall_not_config_time */
+		{ UIC_ARG_MIB_SEL(0x0088, UIC_ARG_MPHY_RX_GEN_SEL_INDEX(0)), 0xFF },
+		{ UIC_ARG_MIB_SEL(0x0088, UIC_ARG_MPHY_RX_GEN_SEL_INDEX(1)), 0xFF },
+		/* TX HB8_TIME CAP */
+		{ UIC_ARG_MIB_SEL(0x000F, UIC_ARG_MPHY_TX_GEN_SEL_INDEX(0)), 0x64 },
+		{ UIC_ARG_MIB_SEL(0x000F, UIC_ARG_MPHY_TX_GEN_SEL_INDEX(1)), 0x64 },
+		/* RX HB8_TIME CAP */
+		{ UIC_ARG_MIB_SEL(0x0092, UIC_ARG_MPHY_RX_GEN_SEL_INDEX(0)), 0x64 },
+		{ UIC_ARG_MIB_SEL(0x0092, UIC_ARG_MPHY_RX_GEN_SEL_INDEX(1)), 0x64 },
+		/* TX EQ 3DB */
+		{ UIC_ARG_MIB_SEL(0x00CD, UIC_ARG_MPHY_TX_GEN_SEL_INDEX(0)),  0x5 },
+		/* RX garbage cnt = 32 SI */
+		{ UIC_ARG_MIB_SEL(0x00F2, UIC_ARG_MPHY_RX_GEN_SEL_INDEX(0)), 0x9F },
+		{ UIC_ARG_MIB_SEL(0x00F2, UIC_ARG_MPHY_RX_GEN_SEL_INDEX(1)), 0x9F },
+	};
+
+	return ufs_spacemit_dme_set(hba, setup_attrs, ARRAY_SIZE(setup_attrs));
+}
+
+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)
+{
+	/* Add 0xe8 make UFS2.1 run GEAR3 + 2Lane@409M */
+	static const struct ufshcd_dme_attr_val setup_attrs[] = {
+		{ UIC_ARG_MIB_SEL(0xe8, UIC_ARG_MPHY_TX_GEN_SEL_INDEX(0)), 0x97 },
+		{ UIC_ARG_MIB_SEL(0xe8, UIC_ARG_MPHY_TX_GEN_SEL_INDEX(0)), 0xd7 },
+		{ UIC_ARG_MIB_SEL(0xe8, UIC_ARG_MPHY_TX_GEN_SEL_INDEX(0)), 0x17 },
+		{ UIC_ARG_MIB(DL_AFC0REQTIMEOUTVAL), UFS_DL_AFC0REQTIMEOUTVAL_MAX },
+	};
+	u32 tx_lanes;
+
+	ufs_spacemit_dme_set(hba, setup_attrs, ARRAY_SIZE(setup_attrs));
+
+	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)
+{
+	static const struct ufshcd_dme_attr_val setup_attrs[] = {
+		/* LCC_DISABLE */
+		{ UIC_ARG_MIB_SEL(TX_LCC_ENABLE, UIC_ARG_MPHY_TX_GEN_SEL_INDEX(0)), 0 },
+		{ UIC_ARG_MIB_SEL(TX_LCC_ENABLE, UIC_ARG_MPHY_TX_GEN_SEL_INDEX(1)), 0 },
+		/* TX_Min_ActivateTime */
+		{ UIC_ARG_MIB_SEL(TX_MIN_ACTIVATETIME, UIC_ARG_MPHY_TX_GEN_SEL_INDEX(0)), 0x0 },
+		{ UIC_ARG_MIB_SEL(TX_MIN_ACTIVATETIME, UIC_ARG_MPHY_TX_GEN_SEL_INDEX(1)), 0x0 },
+		{ UIC_ARG_MIB(ANA_HSGEAR_CTRL_ATTR), 0x25 },
+	};
+
+	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;
+
+	ufs_spacemit_dme_set(hba, setup_attrs, ARRAY_SIZE(setup_attrs));
+
+	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_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;
+	struct reset_control *rst;
+
+	host = devm_kzalloc(dev, sizeof(*host), GFP_KERNEL);
+	if (!host)
+		return -ENOMEM;
+
+	rst = devm_reset_control_get_exclusive_deasserted(dev, NULL);
+	if (IS_ERR(rst))
+		return dev_err_probe(dev, PTR_ERR(rst), "Failed to get reset control\n");
+
+	host->hba = hba;
+	ufshcd_set_variant(hba, host);
+	ufs_spacemit_advertise_quirks(hba);
+
+	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);
+	usleep_range(10, 15);
+
+	/* Enable device ref_clk & de-asserted ufs device reset */
+	ufshcd_writel(hba, MPHY_DEVICE_RESET_DEASSERT, UFS_PHY_MNG_BASE + UFS_DEVICE_IO_CTRL);
+	usleep_range(10, 15);
+
+	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)
+{
+	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);
+
+		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);
+
+		break;
+	case UFS_EVT_ABORT:
+		dev_warn(hba->dev, "Abort event, INT errors:0x%x\n", hba->errors);
+		break;
+	default:
+		break;
+	}
+}
+
+static void ufs_spacemit_pre_hibern8(struct ufs_hba *hba, enum uic_cmd_dme cmd)
+{
+	static const struct ufshcd_dme_attr_val setup_attrs[] = {
+		{ UIC_ARG_MIB_SEL(0xf1, UIC_ARG_MPHY_RX_GEN_SEL_INDEX(0)), 0x84 },
+		{ UIC_ARG_MIB_SEL(0xf1, UIC_ARG_MPHY_RX_GEN_SEL_INDEX(1)), 0x84 },
+		{ UIC_ARG_MIB_SEL(0xf1, UIC_ARG_MPHY_RX_GEN_SEL_INDEX(0)), 0x85 },
+		{ UIC_ARG_MIB_SEL(0xf1, UIC_ARG_MPHY_RX_GEN_SEL_INDEX(1)), 0x85 },
+	};
+	int ret;
+
+	if (cmd == UIC_CMD_DME_HIBER_EXIT) {
+		/* Enable reference clock */
+		ufshcd_writel(hba, MPHY_DEVICE_RESET_DEASSERT,
+			      UFS_PHY_MNG_BASE + UFS_DEVICE_IO_CTRL);
+
+		/* Power up all */
+		ufshcd_writel(hba, MPHY_PU_ALL, UFS_PHY_MNG_BASE + UFS_MPHY_PU_CTRL);
+
+		/* Assert ana_rx_hb8_reset */
+		ufshcd_writel(hba, MPHY_PU_WITH_HB8_RESET,
+			      UFS_PHY_MNG_BASE + UFS_MPHY_PU_CTRL);
+		fsleep(500);
+
+		/* 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;
+
+		ufshcd_dme_set(hba, UIC_ARG_MIB(0xdd), 0x57);
+		ufshcd_dme_set(hba, UIC_ARG_MIB(0xe8), 0x57);
+	}
+
+	if (cmd == UIC_CMD_DME_HIBER_ENTER)
+		ufs_spacemit_dme_set(hba, setup_attrs, ARRAY_SIZE(setup_attrs));
+}
+
+static void ufs_spacemit_post_hibern8(struct ufs_hba *hba, enum uic_cmd_dme cmd)
+{
+	static const struct ufshcd_dme_attr_val setup_attrs[] = {
+		{ UIC_ARG_MIB_SEL(0xf1, UIC_ARG_MPHY_RX_GEN_SEL_INDEX(0)), 0x84 },
+		{ UIC_ARG_MIB_SEL(0xf1, UIC_ARG_MPHY_RX_GEN_SEL_INDEX(1)), 0x84 },
+		{ UIC_ARG_MIB_SEL(0xf1, UIC_ARG_MPHY_RX_GEN_SEL_INDEX(0)), 0x80 },
+		{ UIC_ARG_MIB_SEL(0xf1, UIC_ARG_MPHY_RX_GEN_SEL_INDEX(1)), 0x80 },
+		{ UIC_ARG_MIB(0xdd), 0x57 },
+		{ UIC_ARG_MIB(0xdd), 0xd7 },
+		{ UIC_ARG_MIB(0xe8), 0x57 },
+		{ UIC_ARG_MIB(0xe8), 0xd7 },
+	};
+
+	if (cmd == UIC_CMD_DME_HIBER_ENTER) {
+		ufs_spacemit_check_hibern8(hba);
+
+		ufs_spacemit_dme_set(hba, setup_attrs, ARRAY_SIZE(setup_attrs));
+
+		/* Power down M-PHY */
+		ufshcd_writel(hba, 0x0, UFS_PHY_MNG_BASE + UFS_MPHY_PU_CTRL);
+
+		/* Keep reference clock enabled, assert device reset */
+		ufshcd_writel(hba, MPHY_DEVICE_RESET_ASSERT,
+			      UFS_PHY_MNG_BASE + UFS_DEVICE_IO_CTRL);
+	}
+}
+
+/**
+ * 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)
+{
+	if (status == PRE_CHANGE)
+		ufs_spacemit_pre_hibern8(hba, cmd);
+
+	if (status == POST_CHANGE)
+		ufs_spacemit_post_hibern8(hba, cmd);
+}
+
+/**
+ * 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,
+};
+
+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..5e459f6ba243
--- /dev/null
+++ b/drivers/ufs/host/ufs-spacemit.h
@@ -0,0 +1,79 @@
+/* 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/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 ufshcd_dme_attr_val {
+	u32 attr_sel;
+	u32 mib_val;
+};
+#endif /* _UFS_SPACEMIT_H_ */

-- 
2.54.0


_______________________________________________
linux-riscv mailing list
linux-riscv@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-riscv

^ permalink raw reply related	[flat|nested] 13+ messages in thread

* [PATCH v2 3/3] riscv: dts: spacemit: k3: Add UFS support
  2026-07-25 10:37 ` Yixun Lan
@ 2026-07-25 10:37   ` Yixun Lan
  -1 siblings, 0 replies; 13+ messages in thread
From: Yixun Lan @ 2026-07-25 10:37 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: Anirudh Srinivasan, Aurelien Jarno, Jennifer Berringer,
	linux-scsi, devicetree, linux-riscv, spacemit, linux-kernel,
	Yixun Lan

Add UFS Host Controller support for SpacemiT K3 SoC, and enable
it both on Pico-ITX and CoM260-IFX boards.

Signed-off-by: Yixun Lan <dlan@kernel.org>
---
 arch/riscv/boot/dts/spacemit/k3-com260.dtsi  |  4 ++++
 arch/riscv/boot/dts/spacemit/k3-pico-itx.dts |  4 ++++
 arch/riscv/boot/dts/spacemit/k3.dtsi         | 27 +++++++++++++++++++++++++++
 3 files changed, 35 insertions(+)

diff --git a/arch/riscv/boot/dts/spacemit/k3-com260.dtsi b/arch/riscv/boot/dts/spacemit/k3-com260.dtsi
index a38d7b738258..a68a55c8e73d 100644
--- a/arch/riscv/boot/dts/spacemit/k3-com260.dtsi
+++ b/arch/riscv/boot/dts/spacemit/k3-com260.dtsi
@@ -188,3 +188,7 @@ &uart0 {
 	pinctrl-0 = <&uart0_0_cfg>;
 	status = "okay";
 };
+
+&ufshc {
+	status = "okay";
+};
diff --git a/arch/riscv/boot/dts/spacemit/k3-pico-itx.dts b/arch/riscv/boot/dts/spacemit/k3-pico-itx.dts
index b89c1521e664..eab8c430f2d1 100644
--- a/arch/riscv/boot/dts/spacemit/k3-pico-itx.dts
+++ b/arch/riscv/boot/dts/spacemit/k3-pico-itx.dts
@@ -206,6 +206,10 @@ &uart0 {
 	status = "okay";
 };
 
+&ufshc {
+	status = "okay";
+};
+
 &usb2_host {
 	#address-cells = <1>;
 	#size-cells = <0>;
diff --git a/arch/riscv/boot/dts/spacemit/k3.dtsi b/arch/riscv/boot/dts/spacemit/k3.dtsi
index 19fc9b49668e..a287bb55d13f 100644
--- a/arch/riscv/boot/dts/spacemit/k3.dtsi
+++ b/arch/riscv/boot/dts/spacemit/k3.dtsi
@@ -469,6 +469,33 @@ usb2_phy: phy@c0a20000 {
 			status = "disabled";
 		};
 
+		ufshc: ufshc@c0e00000 {
+			compatible = "spacemit,k3-ufshc";
+			reg = <0x0 0xc0e00000 0x0 0x40000>;
+			clocks = <&syscon_apmu CLK_APMU_UFS_ACLK>,
+				 <&syscon_apmu CLK_APMU_UFS_REFCLK>;
+			clock-names = "aclk", "ref_clk";
+			resets = <&syscon_apmu RESET_APMU_UFS_ACLK>;
+			interrupts = <135 IRQ_TYPE_LEVEL_HIGH>;
+			lanes-per-direction = <2>;
+			operating-points-v2 = <&ufs_opp_table>;
+			status = "disabled";
+
+			ufs_opp_table: opp-table {
+				compatible = "operating-points-v2";
+
+				opp-1 {
+					opp-hz = /bits/ 64 <491520000>,
+						 /bits/ 64 <19200000>;
+				};
+
+				opp-2 {
+					opp-hz = /bits/ 64 <491520000>,
+						 /bits/ 64 <192000000>;
+				};
+			};
+		};
+
 		eth0: ethernet@cac80000 {
 			compatible = "spacemit,k3-dwmac", "snps,dwmac-5.40a";
 			reg = <0x0 0xcac80000 0x0 0x2000>;

-- 
2.54.0


^ permalink raw reply related	[flat|nested] 13+ messages in thread

* [PATCH v2 3/3] riscv: dts: spacemit: k3: Add UFS support
@ 2026-07-25 10:37   ` Yixun Lan
  0 siblings, 0 replies; 13+ messages in thread
From: Yixun Lan @ 2026-07-25 10:37 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: Anirudh Srinivasan, Aurelien Jarno, Jennifer Berringer,
	linux-scsi, devicetree, linux-riscv, spacemit, linux-kernel,
	Yixun Lan

Add UFS Host Controller support for SpacemiT K3 SoC, and enable
it both on Pico-ITX and CoM260-IFX boards.

Signed-off-by: Yixun Lan <dlan@kernel.org>
---
 arch/riscv/boot/dts/spacemit/k3-com260.dtsi  |  4 ++++
 arch/riscv/boot/dts/spacemit/k3-pico-itx.dts |  4 ++++
 arch/riscv/boot/dts/spacemit/k3.dtsi         | 27 +++++++++++++++++++++++++++
 3 files changed, 35 insertions(+)

diff --git a/arch/riscv/boot/dts/spacemit/k3-com260.dtsi b/arch/riscv/boot/dts/spacemit/k3-com260.dtsi
index a38d7b738258..a68a55c8e73d 100644
--- a/arch/riscv/boot/dts/spacemit/k3-com260.dtsi
+++ b/arch/riscv/boot/dts/spacemit/k3-com260.dtsi
@@ -188,3 +188,7 @@ &uart0 {
 	pinctrl-0 = <&uart0_0_cfg>;
 	status = "okay";
 };
+
+&ufshc {
+	status = "okay";
+};
diff --git a/arch/riscv/boot/dts/spacemit/k3-pico-itx.dts b/arch/riscv/boot/dts/spacemit/k3-pico-itx.dts
index b89c1521e664..eab8c430f2d1 100644
--- a/arch/riscv/boot/dts/spacemit/k3-pico-itx.dts
+++ b/arch/riscv/boot/dts/spacemit/k3-pico-itx.dts
@@ -206,6 +206,10 @@ &uart0 {
 	status = "okay";
 };
 
+&ufshc {
+	status = "okay";
+};
+
 &usb2_host {
 	#address-cells = <1>;
 	#size-cells = <0>;
diff --git a/arch/riscv/boot/dts/spacemit/k3.dtsi b/arch/riscv/boot/dts/spacemit/k3.dtsi
index 19fc9b49668e..a287bb55d13f 100644
--- a/arch/riscv/boot/dts/spacemit/k3.dtsi
+++ b/arch/riscv/boot/dts/spacemit/k3.dtsi
@@ -469,6 +469,33 @@ usb2_phy: phy@c0a20000 {
 			status = "disabled";
 		};
 
+		ufshc: ufshc@c0e00000 {
+			compatible = "spacemit,k3-ufshc";
+			reg = <0x0 0xc0e00000 0x0 0x40000>;
+			clocks = <&syscon_apmu CLK_APMU_UFS_ACLK>,
+				 <&syscon_apmu CLK_APMU_UFS_REFCLK>;
+			clock-names = "aclk", "ref_clk";
+			resets = <&syscon_apmu RESET_APMU_UFS_ACLK>;
+			interrupts = <135 IRQ_TYPE_LEVEL_HIGH>;
+			lanes-per-direction = <2>;
+			operating-points-v2 = <&ufs_opp_table>;
+			status = "disabled";
+
+			ufs_opp_table: opp-table {
+				compatible = "operating-points-v2";
+
+				opp-1 {
+					opp-hz = /bits/ 64 <491520000>,
+						 /bits/ 64 <19200000>;
+				};
+
+				opp-2 {
+					opp-hz = /bits/ 64 <491520000>,
+						 /bits/ 64 <192000000>;
+				};
+			};
+		};
+
 		eth0: ethernet@cac80000 {
 			compatible = "spacemit,k3-dwmac", "snps,dwmac-5.40a";
 			reg = <0x0 0xcac80000 0x0 0x2000>;

-- 
2.54.0


_______________________________________________
linux-riscv mailing list
linux-riscv@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-riscv

^ permalink raw reply related	[flat|nested] 13+ messages in thread

* Re: [PATCH v2 1/3] scsi: ufs: spacemit: dt-bindings: Add UFS controller for K3 SoC
  2026-07-25 10:37   ` Yixun Lan
  (?)
@ 2026-07-25 10:46   ` sashiko-bot
  -1 siblings, 0 replies; 13+ messages in thread
From: sashiko-bot @ 2026-07-25 10:46 UTC (permalink / raw)
  To: Yixun Lan; +Cc: conor+dt, robh, linux-scsi, devicetree

Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [Low] The DT binding example incorrectly formats the deprecated `freq-table-hz` property as a flat array, causing a schema validation failure.
- [Low] The `compatible` property is omitted from the `required` list in the binding schema.
--

--- Patch [1]: [PATCH v2 1/3] scsi: ufs: spacemit: dt-bindings: Add UFS controller for K3 SoC ---
commit b7ba054d14f9257aed59f8435e45924091cd034f
Author: Yixun Lan <dlan@kernel.org>

scsi: ufs: spacemit: dt-bindings: Add UFS controller for K3 SoC

This commit documents the device tree bindings for the UFS (Universal Flash
Storage) Host Controller found on the SpacemiT K3 SoC.

> diff --git a/Documentation/devicetree/bindings/ufs/spacemit,k3-ufshc.yaml b/Documentation/devicetree/bindings/ufs/spacemit,k3-ufshc.yaml
> new file mode 100644
> index 0000000000000..45cd7837c3f76
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/ufs/spacemit,k3-ufshc.yaml

[ ... ]

> +required:
> +  - reg
> +  - clocks
> +  - clock-names
> +  - resets

[Severity: Low]
Is the compatible property missing from this list? Standard schema guidelines
generally require leaf device bindings to explicitly include compatible in
their required array to properly enforce its presence during node validation.

> +
> +allOf:
> +  - $ref: ufs-common.yaml

[ ... ]

> +    ufs@c0e00000 {
> +        compatible = "spacemit,k3-ufshc";
> +        reg = <0xc0e00000 0x40000>;
> +        clocks = <&syscon_apmu CLK_APMU_UFS_ACLK>,
> +        <&syscon_apmu CLK_APMU_UFS_REFCLK>;
> +        clock-names = "aclk", "ref_clk";
> +        resets = <&syscon_apmu RESET_APMU_UFS_ACLK>;
> +        interrupts = <135 IRQ_TYPE_LEVEL_HIGH>;
> +        freq-table-hz = <491520000 491520000 19200000 19200000>;

[Severity: Low]
Will this fail schema validation?

The ufs-common.yaml schema defines freq-table-hz as an array of arrays, so
this flat array might need to be formatted as tuples, for example:
<491520000 491520000>, <19200000 19200000>;

Also, freq-table-hz is explicitly marked as deprecated in the common schema
and replaced by operating-points-v2. Does this new binding need to use a
deprecated property in its example?

> +        lanes-per-direction = <2>;
> +    };

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260725-08-k3-ufs-support-v2-0-45036a2a2418@kernel.org?part=1

^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [PATCH v2 2/3] scsi: ufs: spacemit: k3: Add UFS Host Controller driver
  2026-07-25 10:37   ` Yixun Lan
  (?)
@ 2026-07-25 10:48   ` sashiko-bot
  -1 siblings, 0 replies; 13+ messages in thread
From: sashiko-bot @ 2026-07-25 10:48 UTC (permalink / raw)
  To: Yixun Lan; +Cc: devicetree, robh, linux-scsi, conor+dt

Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:
- [High] The host driver asserts the physical device reset pin during Hibern8 (runtime PM suspend), wiping the UFS device state.
- [High] Failure error codes from essential DME configurations and M-PHY initialization are silently ignored.
- [High] Missing Power Management (PM) operations in the platform driver.
- [Medium] Bitwise OR is used on negative integer error codes from `ufshcd_dme_get`.
--

--- Patch [2]: [PATCH v2 2/3] scsi: ufs: spacemit: k3: Add UFS Host Controller driver ---
commit 7cc317e95f9d6ed1949a4f8f4ae915ca4f1bb1f3
Author: Yixun Lan <dlan@kernel.org>

scsi: ufs: spacemit: k3: Add UFS Host Controller driver

This commit adds a new UFS Host Controller driver for the SpacemiT K3 SoC.
The controller supports features compatible with JEDEC UFS 2.2, MIPI UniPro
v1.61 and M-PHY v3.0 standards.

> diff --git a/drivers/ufs/host/ufs-spacemit.c b/drivers/ufs/host/ufs-spacemit.c
> new file mode 100644
> index 0000000000000..30e5092071af8
> --- /dev/null
> +++ b/drivers/ufs/host/ufs-spacemit.c

[ ... ]

> +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);

[Severity: Medium]
Does using the bitwise OR operator here corrupt the error codes?

ufshcd_dme_get() returns standard negative error codes like -EINVAL. Performing
a bitwise OR on two negative two's complement integers produces a mathematically
meaningless negative value, which will destroy the original error values.

[ ... ]

> +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;
> +}

[Severity: High]
Is it safe to ignore the return values from the link startup phases?

If M-PHY initialization fails (e.g., PLL lock timeout) or DME configurations
fail during ufs_spacemit_link_startup_pre_change() or post_change(), the
failure is masked from the core because this function always returns 0.

Could this cause obscure link failures or timeouts later when the hardware
is in an uninitialized or inconsistent state?

[ ... ]

> +static void ufs_spacemit_post_hibern8(struct ufs_hba *hba, enum uic_cmd_dme cmd)
> +{
> +	static const struct ufshcd_dme_attr_val setup_attrs[] = {
> +		{ UIC_ARG_MIB_SEL(0xf1, UIC_ARG_MPHY_RX_GEN_SEL_INDEX(0)), 0x84 },
> +		{ UIC_ARG_MIB_SEL(0xf1, UIC_ARG_MPHY_RX_GEN_SEL_INDEX(1)), 0x84 },
> +		{ UIC_ARG_MIB_SEL(0xf1, UIC_ARG_MPHY_RX_GEN_SEL_INDEX(0)), 0x80 },
> +		{ UIC_ARG_MIB_SEL(0xf1, UIC_ARG_MPHY_RX_GEN_SEL_INDEX(1)), 0x80 },
> +		{ UIC_ARG_MIB(0xdd), 0x57 },
> +		{ UIC_ARG_MIB(0xdd), 0xd7 },
> +		{ UIC_ARG_MIB(0xe8), 0x57 },
> +		{ UIC_ARG_MIB(0xe8), 0xd7 },
> +	};
> +
> +	if (cmd == UIC_CMD_DME_HIBER_ENTER) {
> +		ufs_spacemit_check_hibern8(hba);
> +
> +		ufs_spacemit_dme_set(hba, setup_attrs, ARRAY_SIZE(setup_attrs));
> +
> +		/* Power down M-PHY */
> +		ufshcd_writel(hba, 0x0, UFS_PHY_MNG_BASE + UFS_MPHY_PU_CTRL);
> +
> +		/* Keep reference clock enabled, assert device reset */
> +		ufshcd_writel(hba, MPHY_DEVICE_RESET_ASSERT,
> +			      UFS_PHY_MNG_BASE + UFS_DEVICE_IO_CTRL);

[Severity: High]
Does asserting the hardware reset pin during Hibern8 wipe the device state?

Looking at ufs_spacemit_device_reset(), UFS_DEVICE_IO_CTRL controls the physical
UFS device hardware reset pin (RST_n). Asserting a hard reset during Hibern8
(such as during runtime suspend) wipes the device's internal state and LUN
contexts.

Will the device fail to respond when the core attempts to exit Hibern8
without a full LinkStartup sequence?

[ ... ]

> +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);

[Severity: High]
Are we missing the power management operations for this platform driver?

Without assigning the .pm field (e.g., to &ufshcd_pltfrm_pm_ops), the UFS host
controller will not participate in system suspend/resume or runtime PM, which
could lead to failure to transition power states or battery drain.

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260725-08-k3-ufs-support-v2-0-45036a2a2418@kernel.org?part=2

^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [PATCH v2 3/3] riscv: dts: spacemit: k3: Add UFS support
  2026-07-25 10:37   ` Yixun Lan
  (?)
@ 2026-07-25 10:53   ` sashiko-bot
  -1 siblings, 0 replies; 13+ messages in thread
From: sashiko-bot @ 2026-07-25 10:53 UTC (permalink / raw)
  To: Yixun Lan; +Cc: conor+dt, robh, linux-scsi, devicetree

Thank you for your contribution! Sashiko AI review found 5 potential issue(s) to consider:
- [High] Typo in the UFS reference clock frequency in opp-2 (192 MHz instead of 19.2 MHz).
- [High] UFS device is hard-reset upon entering the Hibern8 suspend state.
- [Low] The device tree references an undefined clock macro CLK_APMU_UFS_REFCLK, which will cause a fatal compilation error.
- [Low] Invalid use of bitwise OR to combine negative integer error codes.
- [Low] Missing FSM state dump in workqueue context as claimed by the event handler.
--

--- Patch [3]: [PATCH v2 3/3] riscv: dts: spacemit: k3: Add UFS support ---
commit c3b7015d657d8f4204038196ab74adb7966ad486
Author: Yixun Lan <dlan@kernel.org>

riscv: dts: spacemit: k3: Add UFS support

This commit adds UFS Host Controller support for the SpacemiT K3 SoC. It also
enables the UFS functionality on the Pico-ITX and CoM260-IFX boards.

> diff --git a/arch/riscv/boot/dts/spacemit/k3.dtsi b/arch/riscv/boot/dts/spacemit/k3.dtsi
> --- a/arch/riscv/boot/dts/spacemit/k3.dtsi
> +++ b/arch/riscv/boot/dts/spacemit/k3.dtsi
> @@ -469,6 +469,33 @@ usb2_phy: phy@c0a20000 {
>  			status = "disabled";
>  		};
>  
> +		ufshc: ufshc@c0e00000 {
> +			compatible = "spacemit,k3-ufshc";
> +			reg = <0x0 0xc0e00000 0x0 0x40000>;
> +			clocks = <&syscon_apmu CLK_APMU_UFS_ACLK>,
> +				 <&syscon_apmu CLK_APMU_UFS_REFCLK>;

[Severity: Low]
Does this code reference an undefined clock macro? CLK_APMU_UFS_REFCLK is not
defined in the included headers, which will cause a fatal syntax error during
device tree compilation.

> +			clock-names = "aclk", "ref_clk";
> +			resets = <&syscon_apmu RESET_APMU_UFS_ACLK>;
> +			interrupts = <135 IRQ_TYPE_LEVEL_HIGH>;
> +			lanes-per-direction = <2>;
> +			operating-points-v2 = <&ufs_opp_table>;
> +			status = "disabled";
> +
> +			ufs_opp_table: opp-table {
> +				compatible = "operating-points-v2";
> +
> +				opp-1 {
> +					opp-hz = /bits/ 64 <491520000>,
> +						 /bits/ 64 <19200000>;
> +				};
> +
> +				opp-2 {
> +					opp-hz = /bits/ 64 <491520000>,
> +						 /bits/ 64 <192000000>;

[Severity: High]
Is there a typo in the UFS reference clock frequency for opp-2? The frequency
is set to 192000000 (192 MHz) instead of the standard 19200000 (19.2 MHz) used
in opp-1. This will cause the OPP framework to incorrectly configure the
reference clock, likely preventing the PHY from locking or causing the
controller to fail.

> +				};
> +			};
> +		};

[ ... ]

> diff --git a/drivers/ufs/host/ufs-spacemit.c b/drivers/ufs/host/ufs-spacemit.c
> new file mode 100644
> --- /dev/null
> +++ b/drivers/ufs/host/ufs-spacemit.c

[ ... ]

> +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);

[Severity: Low]
Does this bitwise OR corrupt the error code if ufshcd_dme_get() fails? Since
ufshcd_dme_get() returns standard negative integer error codes on failure,
combining them with a bitwise OR will result in a nonsensical value, which is
later printed in the kernel log.

[ ... ]

> +/**
> + * 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.
> + */

[Severity: Low]
Can we update the documentation or the implementation? The docstring states this
function schedules an FSM state dump for later execution in a workqueue context,
but the implementation only prints a warning message and takes no further action.

> +static void ufs_spacemit_event_notify(struct ufs_hba *hba, enum ufs_event_type evt, void *data)

[ ... ]

> +static void ufs_spacemit_post_hibern8(struct ufs_hba *hba, enum uic_cmd_dme cmd)
> +{
> +	static const struct ufshcd_dme_attr_val setup_attrs[] = {
> +		{ UIC_ARG_MIB_SEL(0xf1, UIC_ARG_MPHY_RX_GEN_SEL_INDEX(0)), 0x84 },
> +		{ UIC_ARG_MIB_SEL(0xf1, UIC_ARG_MPHY_RX_GEN_SEL_INDEX(1)), 0x84 },
> +		{ UIC_ARG_MIB_SEL(0xf1, UIC_ARG_MPHY_RX_GEN_SEL_INDEX(0)), 0x80 },
> +		{ UIC_ARG_MIB_SEL(0xf1, UIC_ARG_MPHY_RX_GEN_SEL_INDEX(1)), 0x80 },
> +		{ UIC_ARG_MIB(0xdd), 0x57 },
> +		{ UIC_ARG_MIB(0xdd), 0xd7 },
> +		{ UIC_ARG_MIB(0xe8), 0x57 },
> +		{ UIC_ARG_MIB(0xe8), 0xd7 },
> +	};
> +
> +	if (cmd == UIC_CMD_DME_HIBER_ENTER) {
> +		ufs_spacemit_check_hibern8(hba);
> +
> +		ufs_spacemit_dme_set(hba, setup_attrs, ARRAY_SIZE(setup_attrs));
> +
> +		/* Power down M-PHY */
> +		ufshcd_writel(hba, 0x0, UFS_PHY_MNG_BASE + UFS_MPHY_PU_CTRL);
> +
> +		/* Keep reference clock enabled, assert device reset */
> +		ufshcd_writel(hba, MPHY_DEVICE_RESET_ASSERT,
> +			      UFS_PHY_MNG_BASE + UFS_DEVICE_IO_CTRL);

[Severity: High]
Does this code actively hard-reset the UFS device when entering the Hibern8
suspend state? Writing MPHY_DEVICE_RESET_ASSERT asserts the RST_N pin, which
wipes all internal state in the hardware.

When exiting Hibern8, the core will issue a DME_HIBER_EXIT command, but since
the device has been reset, it will require a full DME_LINK_STARTUP sequence
instead. This mismatch will cause the resume process to fail.

> +	}
> +}

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260725-08-k3-ufs-support-v2-0-45036a2a2418@kernel.org?part=3

^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [PATCH v2 1/3] scsi: ufs: spacemit: dt-bindings: Add UFS controller for K3 SoC
  2026-07-25 10:37   ` Yixun Lan
@ 2026-07-25 13:27     ` Rob Herring (Arm)
  -1 siblings, 0 replies; 13+ messages in thread
From: Rob Herring (Arm) @ 2026-07-25 13:27 UTC (permalink / raw)
  To: Yixun Lan
  Cc: Bart Van Assche, Alexandre Ghiti, Albert Ou, Alim Akhtar,
	Conor Dooley, linux-kernel, spacemit, Aurelien Jarno,
	Paul Walmsley, James E.J. Bottomley, Jennifer Berringer,
	Palmer Dabbelt, devicetree, Martin K. Petersen,
	Anirudh Srinivasan, linux-riscv, Avri Altman, Philipp Zabel,
	linux-scsi, Krzysztof Kozlowski


On Sat, 25 Jul 2026 10:37:44 +0000, Yixun Lan wrote:
> Document the compatible for UFS (Universal Flash Storage) Host Controller
> which found in SpacemiT K3 SoC chip. Its features are listed below:
> 
> - Compliant with MIPI UniPro v1.61 specification.
> - Compliant with M-PHY v3.0 specification.
> - Compliant with UFS HCI v2.1 specification.
> - Supports up to 2 Tx and 2 Rx lanes, up to HS-GEAR3 5.8 Gbps per lane.
> - Supports standard low-power hibernate to reduce power consumption.
> 
> Signed-off-by: Yixun Lan <dlan@kernel.org>
> ---
>  .../devicetree/bindings/ufs/spacemit,k3-ufshc.yaml | 56 ++++++++++++++++++++++
>  1 file changed, 56 insertions(+)
> 

My bot found errors running 'make dt_binding_check' on your patch:

yamllint warnings/errors:

dtschema/dtc warnings/errors:
Lexical error: Documentation/devicetree/bindings/ufs/spacemit,k3-ufshc.example.dts:31.27-46 Unexpected 'CLK_APMU_UFS_REFCLK'
FATAL ERROR: Syntax error parsing input tree
make[2]: *** [scripts/Makefile.dtbs:140: Documentation/devicetree/bindings/ufs/spacemit,k3-ufshc.example.dtb] Error 1
make[2]: *** Waiting for unfinished jobs....
make[1]: *** [/builds/robherring/dt-review-ci/linux/Makefile:1669: dt_binding_check] Error 2
make: *** [Makefile:248: __sub-make] Error 2

doc reference errors (make refcheckdocs):

See https://patchwork.kernel.org/project/devicetree/patch/20260725-08-k3-ufs-support-v2-1-45036a2a2418@kernel.org

The base for the series is generally the latest rc1. A different dependency
should be noted in *this* patch.

If you already ran 'make dt_binding_check' and didn't see the above
error(s), then make sure 'yamllint' is installed and dt-schema is up to
date:

pip3 install dtschema --upgrade

Please check and re-submit after running the above command yourself. Note
that DT_SCHEMA_FILES can be set to your schema file to speed up checking
your schema. However, it must be unset to test all examples with your schema.


^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [PATCH v2 1/3] scsi: ufs: spacemit: dt-bindings: Add UFS controller for K3 SoC
@ 2026-07-25 13:27     ` Rob Herring (Arm)
  0 siblings, 0 replies; 13+ messages in thread
From: Rob Herring (Arm) @ 2026-07-25 13:27 UTC (permalink / raw)
  To: Yixun Lan
  Cc: Bart Van Assche, Alexandre Ghiti, Albert Ou, Alim Akhtar,
	Conor Dooley, linux-kernel, spacemit, Aurelien Jarno,
	Paul Walmsley, James E.J. Bottomley, Jennifer Berringer,
	Palmer Dabbelt, devicetree, Martin K. Petersen,
	Anirudh Srinivasan, linux-riscv, Avri Altman, Philipp Zabel,
	linux-scsi, Krzysztof Kozlowski


On Sat, 25 Jul 2026 10:37:44 +0000, Yixun Lan wrote:
> Document the compatible for UFS (Universal Flash Storage) Host Controller
> which found in SpacemiT K3 SoC chip. Its features are listed below:
> 
> - Compliant with MIPI UniPro v1.61 specification.
> - Compliant with M-PHY v3.0 specification.
> - Compliant with UFS HCI v2.1 specification.
> - Supports up to 2 Tx and 2 Rx lanes, up to HS-GEAR3 5.8 Gbps per lane.
> - Supports standard low-power hibernate to reduce power consumption.
> 
> Signed-off-by: Yixun Lan <dlan@kernel.org>
> ---
>  .../devicetree/bindings/ufs/spacemit,k3-ufshc.yaml | 56 ++++++++++++++++++++++
>  1 file changed, 56 insertions(+)
> 

My bot found errors running 'make dt_binding_check' on your patch:

yamllint warnings/errors:

dtschema/dtc warnings/errors:
Lexical error: Documentation/devicetree/bindings/ufs/spacemit,k3-ufshc.example.dts:31.27-46 Unexpected 'CLK_APMU_UFS_REFCLK'
FATAL ERROR: Syntax error parsing input tree
make[2]: *** [scripts/Makefile.dtbs:140: Documentation/devicetree/bindings/ufs/spacemit,k3-ufshc.example.dtb] Error 1
make[2]: *** Waiting for unfinished jobs....
make[1]: *** [/builds/robherring/dt-review-ci/linux/Makefile:1669: dt_binding_check] Error 2
make: *** [Makefile:248: __sub-make] Error 2

doc reference errors (make refcheckdocs):

See https://patchwork.kernel.org/project/devicetree/patch/20260725-08-k3-ufs-support-v2-1-45036a2a2418@kernel.org

The base for the series is generally the latest rc1. A different dependency
should be noted in *this* patch.

If you already ran 'make dt_binding_check' and didn't see the above
error(s), then make sure 'yamllint' is installed and dt-schema is up to
date:

pip3 install dtschema --upgrade

Please check and re-submit after running the above command yourself. Note
that DT_SCHEMA_FILES can be set to your schema file to speed up checking
your schema. However, it must be unset to test all examples with your schema.


_______________________________________________
linux-riscv mailing list
linux-riscv@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-riscv

^ permalink raw reply	[flat|nested] 13+ messages in thread

end of thread, other threads:[~2026-07-25 13:27 UTC | newest]

Thread overview: 13+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-07-25 10:37 [PATCH v2 0/3] Add UFS Host driver support for SpacemiT K3 SoC Yixun Lan
2026-07-25 10:37 ` Yixun Lan
2026-07-25 10:37 ` [PATCH v2 1/3] scsi: ufs: spacemit: dt-bindings: Add UFS controller for " Yixun Lan
2026-07-25 10:37   ` Yixun Lan
2026-07-25 10:46   ` sashiko-bot
2026-07-25 13:27   ` Rob Herring (Arm)
2026-07-25 13:27     ` Rob Herring (Arm)
2026-07-25 10:37 ` [PATCH v2 2/3] scsi: ufs: spacemit: k3: Add UFS Host Controller driver Yixun Lan
2026-07-25 10:37   ` Yixun Lan
2026-07-25 10:48   ` sashiko-bot
2026-07-25 10:37 ` [PATCH v2 3/3] riscv: dts: spacemit: k3: Add UFS support Yixun Lan
2026-07-25 10:37   ` Yixun Lan
2026-07-25 10:53   ` sashiko-bot

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.