diff --git a/sys/dev/ufshci/ufshci_ctrlr.c b/sys/dev/ufshci/ufshci_ctrlr.c --- a/sys/dev/ufshci/ufshci_ctrlr.c +++ b/sys/dev/ufshci/ufshci_ctrlr.c @@ -79,6 +79,8 @@ 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 @@ return (0); } -static int +int ufshci_ctrlr_disable(struct ufshci_controller *ctrlr) { int error; @@ -632,7 +634,14 @@ } } - /* 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 @@ 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 --- a/sys/dev/ufshci/ufshci_dev.c +++ b/sys/dev/ufshci/ufshci_dev.c @@ -433,9 +433,6 @@ 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 @@ 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 @@ 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 --- a/sys/dev/ufshci/ufshci_private.h +++ b/sys/dev/ufshci/ufshci_private.h @@ -243,20 +243,33 @@ 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 @@ /* Power mode */ bool power_mode_supported; enum ufshci_dev_pwr power_mode; + enum ufshci_uic_link_state link_state; }; /* @@ -423,6 +437,7 @@ 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_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 @@ /* 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_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 --- a/sys/dev/ufshci/ufshci_uic_cmd.c +++ b/sys/dev/ufshci/ufshci_uic_cmd.c @@ -23,6 +23,7 @@ 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_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 @@ 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 @@ 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)); +}