git: bec77e3a160d - main - ufshci: Support UIC hibernation enter/exit
- Go to: [ bottom of page ] [ top of archives ] [ this month ]
Date: Wed, 03 Dec 2025 04:06:42 UTC
The branch main has been updated by jaeyoon:
URL: https://cgit.FreeBSD.org/src/commit/?id=bec77e3a160dba10cbc30416f53a506ca2b9301f
commit bec77e3a160dba10cbc30416f53a506ca2b9301f
Author: Jaeyoon Choi <jaeyoon@FreeBSD.org>
AuthorDate: 2025-12-03 04:05:51 +0000
Commit: Jaeyoon Choi <jaeyoon@FreeBSD.org>
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));
+}