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
This commit is contained in:
Jaeyoon Choi committed 2025-12-03 04:05:51 +00:00
1 parent 3a99f31fdb
commit bec77e3a16
4 files changed
+196 -13

No files matched your search

+19 -3
View File
@@ -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;
+74 -3
View File
@@ -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);
}
+27 -6
View File
@@ -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);
+76 -1
View File
@@ -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,6 +33,54 @@ ufshci_uic_power_mode_ready(struct ufshci_controller *ctrlr)
ufshci_printf(ctrlr,
"Power mode is not changed "
"within %d ms\n",
ctrlr->device_init_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,
"Power mode change request status error: 0x%x\n",
UFSHCIV(UFSHCI_HCS_REG_UPMCRS, hcs));
return (ENXIO);
}
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);
}
@@ -44,7 +93,7 @@ ufshci_uic_power_mode_ready(struct ufshci_controller *ctrlr)
hcs = ufshci_mmio_read_4(ctrlr, hcs);
if (UFSHCIV(UFSHCI_HCS_REG_UPMCRS, hcs) != 0x01) {
ufshci_printf(ctrlr,
"Power mode change request status error: 0x%x\n",
"Hibernation enter/exit request status error: 0x%x\n",
UFSHCIV(UFSHCI_HCS_REG_UPMCRS, hcs));
return (ENXIO);
}
@@ -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));
}