git: c09daa33d6ff - main - ufshci: set HS series per platform and adapt type per gear

From: Jaeyoon Choi <jaeyoon_at_FreeBSD.org>
Date: Wed, 02 Sep 2026 02:07:59 UTC
The branch main has been updated by jaeyoon:

URL: https://cgit.FreeBSD.org/src/commit/?id=c09daa33d6ffbbd1956bfe4dcd9ca746192d6940

commit c09daa33d6ffbbd1956bfe4dcd9ca746192d6940
Author:     Jaeyoon Choi <jaeyoon@FreeBSD.org>
AuthorDate: 2026-09-02 01:50:09 +0000
Commit:     Jaeyoon Choi <jaeyoon@FreeBSD.org>
CommitDate: 2026-09-02 02:05:10 +0000

    ufshci: set HS series per platform and adapt type per gear
    
    The driver always asked for Rate-B. It never set the adaptation
    type. The Snapdragon X Elite firmware tunes the PHY for Rate-A.
    A Rate-B link dies at every gear there. HS-G4 and above need
    initial adaptation. This is a UniPro rule. It applies to
    every host.
    
    Add an hs_series field to the device tables. Use Rate-A on the
    Snapdragon X Elite. Keep Rate-B on the PCI hosts. A table entry
    without an HS series fails to attach. Set PA_TxHsAdaptType to
    initial adaptation at HS-G4 and above. Leave it alone below
    that. Hosts before UniPro 1.8 do not have it. The Galaxy Book
    4 Edge now links at HS-G5 Rate-A.
    
    fio results (128k sequential, 4k random, posixaio):
    
    QD  | SEQ_R(MiB/s) | SEQ_W(MiB/s) | RND_R(kIOPS) | RND_W(kIOPS)
    ----+--------------+--------------+--------------+-------------
    1   |         1357 |         1221 |         12.1 |         27.2
    4   |         3103 |         3234 |         46.5 |         92.9
    32  |         3508 |         3238 |        176.4 |        125.0
    
    Sequential writes land in the WriteBooster buffer. Sustained
    writes drop to 556 MiB/s once the buffer runs out.
    
    Reviewed by:            imp (mentor)
    Sponsored by:           Samsung Electronics
    Differential Revision:  https://reviews.freebsd.org/D59298
---
 sys/dev/ufshci/ufshci.h         |  4 ++++
 sys/dev/ufshci/ufshci_acpi.c    |  9 +++++++--
 sys/dev/ufshci/ufshci_ctrlr.c   |  7 +++++++
 sys/dev/ufshci/ufshci_dev.c     | 11 +++++++----
 sys/dev/ufshci/ufshci_pci.c     | 10 +++++++---
 sys/dev/ufshci/ufshci_private.h |  1 +
 6 files changed, 33 insertions(+), 9 deletions(-)

diff --git a/sys/dev/ufshci/ufshci.h b/sys/dev/ufshci/ufshci.h
index c2bef5c9a807..ab55c0af938f 100644
--- a/sys/dev/ufshci/ufshci.h
+++ b/sys/dev/ufshci/ufshci.h
@@ -41,6 +41,8 @@
 #define PA_TxTermination		 0x1569
 #define PA_RxTermination		 0x1584
 #define PA_HSSeries			 0x156A
+#define   UFSHCI_HS_SERIES_A		 1
+#define   UFSHCI_HS_SERIES_B		 2
 #define PA_PWRModeUserData0		 0x15B0
 #define PA_PWRModeUserData1		 0x15B1
 #define PA_PWRModeUserData2		 0x15B2
@@ -49,6 +51,8 @@
 #define PA_PWRModeUserData5		 0x15B5
 
 #define PA_TxHsAdaptType		 0x15D4
+#define   PA_INITIAL_ADAPT		 1
+#define   PA_NO_ADAPT			 3
 #define PA_PWRMode			 0x1571
 
 #define DME_LocalFC0ProtectionTimeOutVal 0xD041
diff --git a/sys/dev/ufshci/ufshci_acpi.c b/sys/dev/ufshci/ufshci_acpi.c
index f96a4c29e15e..00078a47dec3 100644
--- a/sys/dev/ufshci/ufshci_acpi.c
+++ b/sys/dev/ufshci/ufshci_acpi.c
@@ -49,17 +49,21 @@ static struct ufshci_acpi_device {
 	const char *hid;
 	const char *desc;
 	uint32_t ref_clk;
+	uint32_t hs_series;
 	uint32_t quirks;
 } ufshci_acpi_devices[] = {
 	/*
 	 * The SoC feeds the device 38.4 MHz from its CXO. The firmware
 	 * has no property for it. Verified on the Galaxy Book 4 Edge.
+	 *
+	 * The firmware calibrates the PHY for Rate-A. A Rate-B link
+	 * comes up dead at every gear.
 	 */
 	{ "QCOM24A5", "Qualcomm Snapdragon X Elite UFS Host Controller",
-	    UFSHCI_REF_CLK_38_4MHz,
+	    UFSHCI_REF_CLK_38_4MHz, UFSHCI_HS_SERIES_A,
 	    UFSHCI_QUIRK_REINIT_AFTER_MAX_GEAR_SWITCH |
 			UFSHCI_QUIRK_BROKEN_LSDBS_MCQS_CAP },
-	{ 0x00000000, NULL, 0, 0 }
+	{ 0x00000000, NULL, 0, 0, 0 }
 };
 
 static char *ufshci_acpi_ids[] = { "QCOM24A5", NULL };
@@ -97,6 +101,7 @@ ufshci_acpi_probe(device_t dev)
 	if (acpi_dev->hid) {
 		ctrlr->quirks = acpi_dev->quirks;
 		ctrlr->ref_clk = acpi_dev->ref_clk;
+		ctrlr->hs_series = acpi_dev->hs_series;
 	}
 
 	if (acpi_dev->desc) {
diff --git a/sys/dev/ufshci/ufshci_ctrlr.c b/sys/dev/ufshci/ufshci_ctrlr.c
index 69b6869e98e2..b3b9abbe867c 100644
--- a/sys/dev/ufshci/ufshci_ctrlr.c
+++ b/sys/dev/ufshci/ufshci_ctrlr.c
@@ -375,6 +375,13 @@ ufshci_ctrlr_construct(struct ufshci_controller *ctrlr, device_t dev)
 	if (!(ctrlr->is_single_db_supported || ctrlr->is_mcq_supported))
 		return (ENXIO);
 
+	/* Every device table entry must name the HS series. */
+	if (ctrlr->hs_series == 0) {
+		ufshci_printf(ctrlr,
+		    "hs_series is missing from the device table\n");
+		return (ENXIO);
+	}
+
 	/*
 	 * The maximum transfer size supported by UFSHCI spec is 65535 * 256 KiB
 	 * However, we limit the maximum transfer size to 1MiB(256 * 4KiB) for
diff --git a/sys/dev/ufshci/ufshci_dev.c b/sys/dev/ufshci/ufshci_dev.c
index dd467f85c795..28c91828a925 100644
--- a/sys/dev/ufshci/ufshci_dev.c
+++ b/sys/dev/ufshci/ufshci_dev.c
@@ -320,8 +320,6 @@ ufshci_dev_init_unipro(struct ufshci_controller *ctrlr)
 int
 ufshci_dev_init_uic_power_mode(struct ufshci_controller *ctrlr)
 {
-	/* HSSerise: A = 1, B = 2 */
-	const uint32_t hs_series = 2;
 	/*
 	 * TX/RX PWRMode:
 	 * - TX[3:0], RX[7:4]
@@ -386,8 +384,13 @@ ufshci_dev_init_uic_power_mode(struct ufshci_controller *ctrlr)
 	if (ufshci_uic_send_dme_set(ctrlr, PA_RxTermination, true))
 		return (ENXIO);
 
-	/* Set HSSerise (A = 1, B = 2) */
-	if (ufshci_uic_send_dme_set(ctrlr, PA_HSSeries, hs_series))
+	/* Set HSSeries */
+	if (ufshci_uic_send_dme_set(ctrlr, PA_HSSeries, ctrlr->hs_series))
+		return (ENXIO);
+
+	/* HS-G4 and above need initial adaptation. */
+	if (ctrlr->hs_gear >= 4 &&
+	    ufshci_uic_send_dme_set(ctrlr, PA_TxHsAdaptType, PA_INITIAL_ADAPT))
 		return (ENXIO);
 
 	/* Set Timeout values */
diff --git a/sys/dev/ufshci/ufshci_pci.c b/sys/dev/ufshci/ufshci_pci.c
index b6b8124bc3a6..3cc184bef714 100644
--- a/sys/dev/ufshci/ufshci_pci.c
+++ b/sys/dev/ufshci/ufshci_pci.c
@@ -49,20 +49,23 @@ static struct _pcsid {
 	uint32_t devid;
 	const char *desc;
 	uint32_t ref_clk;
+	uint32_t hs_series;
 	uint32_t quirks;
 } pci_ids[] = { { 0x131b36, "QEMU UFS Host Controller", UFSHCI_REF_CLK_19_2MHz,
+		    UFSHCI_HS_SERIES_B,
 		    UFSHCI_QUIRK_IGNORE_UIC_POWER_MODE |
 			UFSHCI_QUIRK_NOT_SUPPORT_ABORT_TASK |
 			UFSHCI_QUIRK_SKIP_WELL_KNOWN_LUNS },
 	{ 0x98fa8086, "Intel Lakefield UFS Host Controller",
-	    UFSHCI_REF_CLK_19_2MHz,
+	    UFSHCI_REF_CLK_19_2MHz, UFSHCI_HS_SERIES_B,
 	    UFSHCI_QUIRK_LONG_PEER_PA_TACTIVATE |
 		UFSHCI_QUIRK_WAIT_AFTER_POWER_MODE_CHANGE |
 		UFSHCI_QUIRK_CHANGE_LANE_AND_GEAR_SEPARATELY |
 		UFSHCI_QUIRK_BROKEN_AUTO_HIBERNATE },
 	{ 0x54ff8086, "Intel Alder Lake-N UFS Host Controller",
-	    UFSHCI_REF_CLK_19_2MHz, UFSHCI_QUIRK_BROKEN_AUTO_HIBERNATE },
-	{ 0x00000000, NULL } };
+	    UFSHCI_REF_CLK_19_2MHz, UFSHCI_HS_SERIES_B,
+	    UFSHCI_QUIRK_BROKEN_AUTO_HIBERNATE },
+	{ 0x00000000, NULL, 0, 0, 0 } };
 
 static int
 ufshci_pci_probe(device_t device)
@@ -77,6 +80,7 @@ ufshci_pci_probe(device_t device)
 	if (ep->devid) {
 		ctrlr->quirks = ep->quirks;
 		ctrlr->ref_clk = ep->ref_clk;
+		ctrlr->hs_series = ep->hs_series;
 	}
 
 	if (ep->desc) {
diff --git a/sys/dev/ufshci/ufshci_private.h b/sys/dev/ufshci/ufshci_private.h
index d65c1b126cd6..f9c6ce2bed98 100644
--- a/sys/dev/ufshci/ufshci_private.h
+++ b/sys/dev/ufshci/ufshci_private.h
@@ -326,6 +326,7 @@ struct ufshci_controller {
 	256 /* Some controllers have their LSDB and MCQS fields reset to 0. */
 
 	uint32_t ref_clk;
+	uint32_t hs_series;
 
 	struct cam_sim *ufshci_sim;
 	struct cam_path *ufshci_path;