mirror of
https://github.com/freebsd/freebsd-src.git
synced 2026-10-28 11:25:21 +01:00
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:
1 parent
3a99f31fdb
commit
bec77e3a16
4 files changed
+196
-13
No files matched your search
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
Reference in new issue
Block a user