The branch main has been updated by jaeyoon:

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

commit bec77e3a160dba10cbc30416f53a506ca2b9301f
Author:     Jaeyoon Choi <[email protected]>
AuthorDate: 2025-12-03 04:05:51 +0000
Commit:     Jaeyoon Choi <[email protected]>
CommitDate: 2025-12-03 04:05:51 +0000

    ufshci: Support UIC hibernation enter/exit
    
    Add helpers to issue DME_HIBERNATE_ENTER/DME_HIBERNATE_EXIT and
    call them during suspend/resume.
    
    Reviewed by:            imp (mentor)
    Sponsored by:           Samsung Electronics
    Differential Revision:  https://reviews.freebsd.org/D54003
---
 sys/dev/ufshci/ufshci_ctrlr.c   | 22 ++++++++++--
 sys/dev/ufshci/ufshci_dev.c     | 77 +++++++++++++++++++++++++++++++++++++++--
 sys/dev/ufshci/ufshci_private.h | 33 ++++++++++++++----
 sys/dev/ufshci/ufshci_uic_cmd.c | 77 ++++++++++++++++++++++++++++++++++++++++-
 4 files changed, 196 insertions(+), 13 deletions(-)

diff --git a/sys/dev/ufshci/ufshci_ctrlr.c b/sys/dev/ufshci/ufshci_ctrlr.c
index f0cb08b34823..494313df95de 100644
--- a/sys/dev/ufshci/ufshci_ctrlr.c
+++ b/sys/dev/ufshci/ufshci_ctrlr.c
@@ -79,6 +79,8 @@ ufshci_ctrlr_start(struct ufshci_controller *ctrlr, bool 
resetting)
                return;
        }
 
+       ufshci_dev_init_uic_link_state(ctrlr);
+
        /* Read Controller Descriptor (Device, Geometry) */
        if (ufshci_dev_get_descriptor(ctrlr) != 0) {
                ufshci_ctrlr_fail(ctrlr);
@@ -187,7 +189,7 @@ ufshci_ctrlr_enable_host_ctrlr(struct ufshci_controller 
*ctrlr)
        return (0);
 }
 
-static int
+int
 ufshci_ctrlr_disable(struct ufshci_controller *ctrlr)
 {
        int error;
@@ -632,7 +634,14 @@ ufshci_ctrlr_suspend(struct ufshci_controller *ctrlr, enum 
power_stype stype)
                }
        }
 
-       /* TODO: Change the link state to Hibernate if necessary. */
+       /* Change the link state */
+       error = ufshci_dev_link_state_transition(ctrlr,
+           power_map[stype].link_state);
+       if (error) {
+               ufshci_printf(ctrlr,
+                   "Failed to transition link state in suspend handler\n");
+               return (error);
+       }
 
        return (0);
 }
@@ -645,7 +654,14 @@ ufshci_ctrlr_resume(struct ufshci_controller *ctrlr, enum 
power_stype stype)
        if (!ctrlr->ufs_dev.power_mode_supported)
                return (0);
 
-       /* TODO: Change the link state to Active if necessary. */
+       /* Change the link state */
+       error = ufshci_dev_link_state_transition(ctrlr,
+           power_map[stype].link_state);
+       if (error) {
+               ufshci_printf(ctrlr,
+                   "Failed to transition link state in resume handler\n");
+               return (error);
+       }
 
        if (ctrlr->ufs_device_wlun_periph) {
                ctrlr->ufs_dev.power_mode = power_map[stype].dev_pwr;
diff --git a/sys/dev/ufshci/ufshci_dev.c b/sys/dev/ufshci/ufshci_dev.c
index 136823523fd3..c4a5bda9c79a 100644
--- a/sys/dev/ufshci/ufshci_dev.c
+++ b/sys/dev/ufshci/ufshci_dev.c
@@ -433,9 +433,6 @@ ufshci_dev_init_uic_power_mode(struct ufshci_controller 
*ctrlr)
                return (ENXIO);
        }
 
-       /* Clear 'Power Mode completion status' */
-       ufshci_mmio_write_4(ctrlr, is, UFSHCIM(UFSHCI_IS_REG_UPMS));
-
        if (ctrlr->quirks & UFSHCI_QUIRK_WAIT_AFTER_POWER_MODE_CHANGE) {
                /*
                 * Intel Lake-field UFSHCI has a quirk.
@@ -452,6 +449,12 @@ ufshci_dev_init_uic_power_mode(struct ufshci_controller 
*ctrlr)
        return (0);
 }
 
+void
+ufshci_dev_init_uic_link_state(struct ufshci_controller *ctrlr)
+{
+       ctrlr->ufs_dev.link_state = UFSHCI_UIC_LINK_STATE_ACTIVE;
+}
+
 int
 ufshci_dev_init_ufs_power_mode(struct ufshci_controller *ctrlr)
 {
@@ -805,3 +808,71 @@ ufshci_dev_get_current_power_mode(struct ufshci_controller 
*ctrlr,
 
        return (0);
 }
+
+static int
+ufshci_dev_hibernate_enter(struct ufshci_controller *ctrlr)
+{
+       int error;
+
+       error = ufshci_uic_send_dme_hibernate_enter(ctrlr);
+       if (error)
+               return (error);
+
+       return (ufshci_uic_hibernation_ready(ctrlr));
+}
+
+static int
+ufshci_dev_hibernate_exit(struct ufshci_controller *ctrlr)
+{
+       int error;
+
+       error = ufshci_uic_send_dme_hibernate_exit(ctrlr);
+       if (error)
+               return (error);
+
+       return (ufshci_uic_hibernation_ready(ctrlr));
+}
+
+int
+ufshci_dev_link_state_transition(struct ufshci_controller *ctrlr,
+    enum ufshci_uic_link_state target_state)
+{
+       struct ufshci_device *dev = &ctrlr->ufs_dev;
+       int error = 0;
+
+       if (dev->link_state == target_state)
+               return (0);
+
+       switch (target_state) {
+       case UFSHCI_UIC_LINK_STATE_OFF:
+               error = ufshci_dev_hibernate_enter(ctrlr);
+               if (error)
+                       break;
+               error = ufshci_ctrlr_disable(ctrlr);
+               break;
+       case UFSHCI_UIC_LINK_STATE_ACTIVE:
+               if (dev->link_state == UFSHCI_UIC_LINK_STATE_HIBERNATE)
+                       error = ufshci_dev_hibernate_exit(ctrlr);
+               else
+                       error = EINVAL;
+               break;
+       case UFSHCI_UIC_LINK_STATE_HIBERNATE:
+               if (dev->link_state == UFSHCI_UIC_LINK_STATE_ACTIVE)
+                       error = ufshci_dev_hibernate_enter(ctrlr);
+               else
+                       error = EINVAL;
+               break;
+       case UFSHCI_UIC_LINK_STATE_BROKEN:
+               break;
+       default:
+               error = EINVAL;
+               break;
+       }
+
+       if (error)
+               return (error);
+
+       dev->link_state = target_state;
+
+       return (0);
+}
diff --git a/sys/dev/ufshci/ufshci_private.h b/sys/dev/ufshci/ufshci_private.h
index fa5caf87196c..8a49c2a9bc2b 100644
--- a/sys/dev/ufshci/ufshci_private.h
+++ b/sys/dev/ufshci/ufshci_private.h
@@ -243,20 +243,33 @@ enum ufshci_dev_pwr {
        UFSHCI_DEV_PWR_COUNT,
 };
 
+enum ufshci_uic_link_state {
+       UFSHCI_UIC_LINK_STATE_OFF = 0,
+       UFSHCI_UIC_LINK_STATE_ACTIVE,
+       UFSHCI_UIC_LINK_STATE_HIBERNATE,
+       UFSHCI_UIC_LINK_STATE_BROKEN,
+};
+
 struct ufshci_power_entry {
        enum ufshci_dev_pwr dev_pwr;
        uint8_t ssu_pc; /* SSU Power Condition */
+       enum ufshci_uic_link_state link_state;
 };
 
 /* SSU Power Condition 0x40 is defined in the UFS specification */
 static const struct ufshci_power_entry power_map[POWER_STYPE_COUNT] = {
-       [POWER_STYPE_AWAKE] = { UFSHCI_DEV_PWR_ACTIVE, SSS_PC_ACTIVE },
-       [POWER_STYPE_STANDBY] = { UFSHCI_DEV_PWR_SLEEP, SSS_PC_IDLE },
+       [POWER_STYPE_AWAKE] = { UFSHCI_DEV_PWR_ACTIVE, SSS_PC_ACTIVE,
+           UFSHCI_UIC_LINK_STATE_ACTIVE },
+       [POWER_STYPE_STANDBY] = { UFSHCI_DEV_PWR_SLEEP, SSS_PC_IDLE,
+           UFSHCI_UIC_LINK_STATE_HIBERNATE },
        [POWER_STYPE_SUSPEND_TO_MEM] = { UFSHCI_DEV_PWR_POWERDOWN,
-           SSS_PC_STANDBY },
-       [POWER_STYPE_SUSPEND_TO_IDLE] = { UFSHCI_DEV_PWR_SLEEP, SSS_PC_IDLE },
-       [POWER_STYPE_HIBERNATE] = { UFSHCI_DEV_PWR_DEEPSLEEP, 0x40 },
-       [POWER_STYPE_POWEROFF] = { UFSHCI_DEV_PWR_POWERDOWN, SSS_PC_STANDBY },
+           SSS_PC_STANDBY, UFSHCI_UIC_LINK_STATE_HIBERNATE },
+       [POWER_STYPE_SUSPEND_TO_IDLE] = { UFSHCI_DEV_PWR_SLEEP, SSS_PC_IDLE,
+           UFSHCI_UIC_LINK_STATE_HIBERNATE },
+       [POWER_STYPE_HIBERNATE] = { UFSHCI_DEV_PWR_DEEPSLEEP, 0x40,
+           UFSHCI_UIC_LINK_STATE_OFF },
+       [POWER_STYPE_POWEROFF] = { UFSHCI_DEV_PWR_POWERDOWN, SSS_PC_STANDBY,
+           UFSHCI_UIC_LINK_STATE_OFF },
 };
 
 struct ufshci_device {
@@ -279,6 +292,7 @@ struct ufshci_device {
        /* Power mode */
        bool power_mode_supported;
        enum ufshci_dev_pwr power_mode;
+       enum ufshci_uic_link_state link_state;
 };
 
 /*
@@ -423,6 +437,7 @@ int ufshci_ctrlr_suspend(struct ufshci_controller *ctrlr,
     enum power_stype stype);
 int ufshci_ctrlr_resume(struct ufshci_controller *ctrlr,
     enum power_stype stype);
+int ufshci_ctrlr_disable(struct ufshci_controller *ctrlr);
 /* ctrlr defined as void * to allow use with config_intrhook. */
 void ufshci_ctrlr_start_config_hook(void *arg);
 void ufshci_ctrlr_poll(struct ufshci_controller *ctrlr);
@@ -443,11 +458,14 @@ int ufshci_dev_reset(struct ufshci_controller *ctrlr);
 int ufshci_dev_init_reference_clock(struct ufshci_controller *ctrlr);
 int ufshci_dev_init_unipro(struct ufshci_controller *ctrlr);
 int ufshci_dev_init_uic_power_mode(struct ufshci_controller *ctrlr);
+void ufshci_dev_init_uic_link_state(struct ufshci_controller *ctrlr);
 int ufshci_dev_init_ufs_power_mode(struct ufshci_controller *ctrlr);
 int ufshci_dev_get_descriptor(struct ufshci_controller *ctrlr);
 int ufshci_dev_config_write_booster(struct ufshci_controller *ctrlr);
 int ufshci_dev_get_current_power_mode(struct ufshci_controller *ctrlr,
     uint8_t *power_mode);
+int ufshci_dev_link_state_transition(struct ufshci_controller *ctrlr,
+    enum ufshci_uic_link_state target_state);
 
 /* Controller Command */
 void ufshci_ctrlr_cmd_send_task_mgmt_request(struct ufshci_controller *ctrlr,
@@ -508,6 +526,7 @@ int ufshci_req_sdb_get_inflight_io(struct ufshci_controller 
*ctrlr);
 
 /* UIC Command */
 int ufshci_uic_power_mode_ready(struct ufshci_controller *ctrlr);
+int ufshci_uic_hibernation_ready(struct ufshci_controller *ctrlr);
 int ufshci_uic_cmd_ready(struct ufshci_controller *ctrlr);
 int ufshci_uic_send_dme_link_startup(struct ufshci_controller *ctrlr);
 int ufshci_uic_send_dme_get(struct ufshci_controller *ctrlr, uint16_t 
attribute,
@@ -519,6 +538,8 @@ int ufshci_uic_send_dme_peer_get(struct ufshci_controller 
*ctrlr,
 int ufshci_uic_send_dme_peer_set(struct ufshci_controller *ctrlr,
     uint16_t attribute, uint32_t value);
 int ufshci_uic_send_dme_endpoint_reset(struct ufshci_controller *ctrlr);
+int ufshci_uic_send_dme_hibernate_enter(struct ufshci_controller *ctrlr);
+int ufshci_uic_send_dme_hibernate_exit(struct ufshci_controller *ctrlr);
 
 /* SYSCTL */
 void ufshci_sysctl_initialize_ctrlr(struct ufshci_controller *ctrlr);
diff --git a/sys/dev/ufshci/ufshci_uic_cmd.c b/sys/dev/ufshci/ufshci_uic_cmd.c
index b9c867ff7065..29c143cec52c 100644
--- a/sys/dev/ufshci/ufshci_uic_cmd.c
+++ b/sys/dev/ufshci/ufshci_uic_cmd.c
@@ -23,6 +23,7 @@ ufshci_uic_power_mode_ready(struct ufshci_controller *ctrlr)
        while (1) {
                is = ufshci_mmio_read_4(ctrlr, is);
                if (UFSHCIV(UFSHCI_IS_REG_UPMS, is)) {
+                       /* Clear 'Power Mode completion status' */
                        ufshci_mmio_write_4(ctrlr, is,
                            UFSHCIM(UFSHCI_IS_REG_UPMS));
                        break;
@@ -32,7 +33,7 @@ ufshci_uic_power_mode_ready(struct ufshci_controller *ctrlr)
                        ufshci_printf(ctrlr,
                            "Power mode is not changed "
                            "within %d ms\n",
-                           ctrlr->uic_cmd_timeout_in_ms);
+                           ctrlr->device_init_timeout_in_ms);
                        return (ENXIO);
                }
 
@@ -52,6 +53,54 @@ ufshci_uic_power_mode_ready(struct ufshci_controller *ctrlr)
        return (0);
 }
 
+int
+ufshci_uic_hibernation_ready(struct ufshci_controller *ctrlr)
+{
+       uint32_t is, hcs;
+       int timeout;
+
+       /* Wait for the IS flag to change */
+       timeout = ticks + MSEC_2_TICKS(ctrlr->uic_cmd_timeout_in_ms);
+
+       while (1) {
+               is = ufshci_mmio_read_4(ctrlr, is);
+               if (UFSHCIV(UFSHCI_IS_REG_UHES, is)) {
+                       /* Clear 'UIC Hibernate Enter Status' */
+                       ufshci_mmio_write_4(ctrlr, is,
+                           UFSHCIM(UFSHCI_IS_REG_UHES));
+                       break;
+               }
+               if (UFSHCIV(UFSHCI_IS_REG_UHXS, is)) {
+                       /* Clear 'UIC Hibernate Exit Status' */
+                       ufshci_mmio_write_4(ctrlr, is,
+                           UFSHCIM(UFSHCI_IS_REG_UHXS));
+                       break;
+               }
+
+               if (timeout - ticks < 0) {
+                       ufshci_printf(ctrlr,
+                           "Hibernation enter/exit are not completed "
+                           "within %d ms\n",
+                           ctrlr->uic_cmd_timeout_in_ms);
+                       return (ENXIO);
+               }
+
+               /* TODO: Replace busy-wait with interrupt-based pause. */
+               DELAY(10);
+       }
+
+       /* Check HCS power mode change request status */
+       hcs = ufshci_mmio_read_4(ctrlr, hcs);
+       if (UFSHCIV(UFSHCI_HCS_REG_UPMCRS, hcs) != 0x01) {
+               ufshci_printf(ctrlr,
+                   "Hibernation enter/exit request status error: 0x%x\n",
+                   UFSHCIV(UFSHCI_HCS_REG_UPMCRS, hcs));
+               return (ENXIO);
+       }
+
+       return (0);
+}
+
 int
 ufshci_uic_cmd_ready(struct ufshci_controller *ctrlr)
 {
@@ -239,3 +288,29 @@ ufshci_uic_send_dme_endpoint_reset(struct 
ufshci_controller *ctrlr)
 
        return (ufshci_uic_send_cmd(ctrlr, &uic_cmd, NULL));
 }
+
+int
+ufshci_uic_send_dme_hibernate_enter(struct ufshci_controller *ctrlr)
+{
+       struct ufshci_uic_cmd uic_cmd;
+
+       uic_cmd.opcode = UFSHCI_DME_HIBERNATE_ENTER;
+       uic_cmd.argument1 = 0;
+       uic_cmd.argument2 = 0;
+       uic_cmd.argument3 = 0;
+
+       return (ufshci_uic_send_cmd(ctrlr, &uic_cmd, NULL));
+}
+
+int
+ufshci_uic_send_dme_hibernate_exit(struct ufshci_controller *ctrlr)
+{
+       struct ufshci_uic_cmd uic_cmd;
+
+       uic_cmd.opcode = UFSHCI_DME_HIBERNATE_EXIT;
+       uic_cmd.argument1 = 0;
+       uic_cmd.argument2 = 0;
+       uic_cmd.argument3 = 0;
+
+       return (ufshci_uic_send_cmd(ctrlr, &uic_cmd, NULL));
+}

Reply via email to