Loading drivers/scsi/ufs/ufs.h +16 −0 Original line number Diff line number Diff line Loading @@ -62,6 +62,14 @@ #define UFS_MAX_LUNS (SCSI_W_LUN_BASE + UFS_UPIU_MAX_UNIT_NUM_ID) #define UFS_UPIU_WLUN_ID (1 << 7) /* Well known logical unit id in LUN field of UPIU */ enum { UFS_UPIU_REPORT_LUNS_WLUN = 0x81, UFS_UPIU_UFS_DEVICE_WLUN = 0xD0, UFS_UPIU_BOOT_WLUN = 0xB0, UFS_UPIU_RPMB_WLUN = 0xC4, }; /* * UFS Protocol Information Unit related definitions */ Loading Loading @@ -264,6 +272,14 @@ enum { UPIU_TASK_MANAGEMENT_FUNC_FAILED = 0x05, UPIU_INCORRECT_LOGICAL_UNIT_NO = 0x09, }; /* UFS device power modes */ enum ufs_dev_pwr_mode { UFS_ACTIVE_PWR_MODE = 1, UFS_SLEEP_PWR_MODE = 2, UFS_POWERDOWN_PWR_MODE = 3, }; /** * struct utp_upiu_header - UPIU header structure * @dword_0: UPIU header DW-0 Loading drivers/scsi/ufs/ufshcd-pltfrm.c +9 −45 Original line number Diff line number Diff line Loading @@ -215,45 +215,24 @@ out: * ufshcd_pltfrm_suspend - suspend power management function * @dev: pointer to device handle * * * Returns 0 * Returns 0 if successful * Returns non-zero otherwise */ static int ufshcd_pltfrm_suspend(struct device *dev) { struct platform_device *pdev = to_platform_device(dev); struct ufs_hba *hba = platform_get_drvdata(pdev); /* * TODO: * 1. Call ufshcd_suspend * 2. Do bus specific power management */ disable_irq(hba->irq); return 0; return ufshcd_system_suspend(dev_get_drvdata(dev)); } /** * ufshcd_pltfrm_resume - resume power management function * @dev: pointer to device handle * * Returns 0 * Returns 0 if successful * Returns non-zero otherwise */ static int ufshcd_pltfrm_resume(struct device *dev) { struct platform_device *pdev = to_platform_device(dev); struct ufs_hba *hba = platform_get_drvdata(pdev); /* * TODO: * 1. Call ufshcd_resume. * 2. Do bus specific wake up */ enable_irq(hba->irq); return 0; return ufshcd_system_resume(dev_get_drvdata(dev)); } #else #define ufshcd_pltfrm_suspend NULL Loading @@ -263,30 +242,15 @@ static int ufshcd_pltfrm_resume(struct device *dev) #ifdef CONFIG_PM_RUNTIME static int ufshcd_pltfrm_runtime_suspend(struct device *dev) { struct ufs_hba *hba = dev_get_drvdata(dev); if (!hba) return 0; return ufshcd_runtime_suspend(hba); return ufshcd_runtime_suspend(dev_get_drvdata(dev)); } static int ufshcd_pltfrm_runtime_resume(struct device *dev) { struct ufs_hba *hba = dev_get_drvdata(dev); if (!hba) return 0; return ufshcd_runtime_resume(hba); return ufshcd_runtime_resume(dev_get_drvdata(dev)); } static int ufshcd_pltfrm_runtime_idle(struct device *dev) { struct ufs_hba *hba = dev_get_drvdata(dev); if (!hba) return 0; return ufshcd_runtime_idle(hba); return ufshcd_runtime_idle(dev_get_drvdata(dev)); } #else /* !CONFIG_PM_RUNTIME */ #define ufshcd_pltfrm_runtime_suspend NULL Loading drivers/scsi/ufs/ufshcd.c +446 −36 Original line number Diff line number Diff line Loading @@ -139,6 +139,39 @@ enum { #define ufshcd_clear_eh_in_progress(h) \ (h->eh_flags &= ~UFSHCD_EH_IN_PROGRESS) #define ufshcd_set_ufs_dev_active(h) \ ((h)->curr_dev_pwr_mode = UFS_ACTIVE_PWR_MODE) #define ufshcd_set_ufs_dev_sleep(h) \ ((h)->curr_dev_pwr_mode = UFS_SLEEP_PWR_MODE) #define ufshcd_set_ufs_dev_poweroff(h) \ ((h)->curr_dev_pwr_mode = UFS_POWERDOWN_PWR_MODE) #define ufshcd_is_ufs_dev_active(h) \ ((h)->curr_dev_pwr_mode == UFS_ACTIVE_PWR_MODE) #define ufshcd_is_ufs_dev_sleep(h) \ ((h)->curr_dev_pwr_mode == UFS_SLEEP_PWR_MODE) #define ufshcd_is_ufs_dev_poweroff(h) \ ((h)->curr_dev_pwr_mode == UFS_POWERDOWN_PWR_MODE) static struct ufs_pm_lvl_states ufs_pm_lvl_states[] = { {UFS_ACTIVE_PWR_MODE, UIC_LINK_ACTIVE_STATE}, {UFS_ACTIVE_PWR_MODE, UIC_LINK_HIBERN8_STATE}, {UFS_SLEEP_PWR_MODE, UIC_LINK_ACTIVE_STATE}, {UFS_SLEEP_PWR_MODE, UIC_LINK_HIBERN8_STATE}, {UFS_POWERDOWN_PWR_MODE, UIC_LINK_OFF_STATE}, }; static inline enum ufs_dev_pwr_mode ufs_get_pm_lvl_to_dev_pwr_mode(enum ufs_pm_level lvl) { return ufs_pm_lvl_states[lvl].dev_state; } static inline enum uic_link_state ufs_get_pm_lvl_to_link_pwr_state(enum ufs_pm_level lvl) { return ufs_pm_lvl_states[lvl].link_state; } static void ufshcd_tmc_handler(struct ufs_hba *hba); static void ufshcd_async_scan(void *data, async_cookie_t cookie); static int ufshcd_reset_and_restore(struct ufs_hba *hba); Loading @@ -147,6 +180,22 @@ static int ufshcd_read_sdev_qdepth(struct ufs_hba *hba, struct scsi_device *sdev); static void ufshcd_hba_exit(struct ufs_hba *hba); static inline void ufshcd_enable_irq(struct ufs_hba *hba) { if (!hba->is_irq_enabled) { enable_irq(hba->irq); hba->is_irq_enabled = true; } } static inline void ufshcd_disable_irq(struct ufs_hba *hba) { if (hba->is_irq_enabled) { disable_irq(hba->irq); hba->is_irq_enabled = false; } } /* * ufshcd_wait_for_register - wait for register value to change * @hba - per-adapter interface Loading Loading @@ -1773,7 +1822,7 @@ static int ufshcd_uic_change_pwr_mode(struct ufs_hba *hba, u8 mode) return ufshcd_uic_pwr_ctrl(hba, &uic_cmd); } int ufshcd_uic_hibern8_enter(struct ufs_hba *hba) static int ufshcd_uic_hibern8_enter(struct ufs_hba *hba) { struct uic_command uic_cmd = {0}; Loading @@ -1782,7 +1831,7 @@ int ufshcd_uic_hibern8_enter(struct ufs_hba *hba) return ufshcd_uic_pwr_ctrl(hba, &uic_cmd); } int ufshcd_uic_hibern8_exit(struct ufs_hba *hba) static int ufshcd_uic_hibern8_exit(struct ufs_hba *hba) { struct uic_command uic_cmd = {0}; Loading Loading @@ -1996,6 +2045,9 @@ static int ufshcd_hba_enable(struct ufs_hba *hba) msleep(5); } /* UniPro link is disabled at this point */ ufshcd_set_link_off(hba); if (hba->vops && hba->vops->hce_enable_notify) hba->vops->hce_enable_notify(hba, PRE_CHANGE); Loading Loading @@ -2157,6 +2209,19 @@ static int ufshcd_slave_alloc(struct scsi_device *sdev) __func__, lun_qdepth); scsi_activate_tcq(sdev, lun_qdepth); /* * For selecting the UFS device power mode (Active / UFS_Sleep / * UFS_PowerDown), SCSI power management command (START STOP UNIT) * needs to be sent to a "UFS device" Well known Logical Unit (W-LU). * As this command would be sent during the UFS host controller * runtime/system PM callbacks, we need a reference to "scsi_device" * associated to "UFS device" W-LU. This change saves the "scsi_device" * reference for "UFS device" W-LU during slave_configure() callback * from SCSI mid layer. */ if (ufshcd_scsi_to_upiu_lun(sdev->lun) == UFS_UPIU_UFS_DEVICE_WLUN) hba->sdev_ufs_device = sdev; return 0; } Loading Loading @@ -2219,6 +2284,9 @@ static void ufshcd_slave_destroy(struct scsi_device *sdev) hba = shost_priv(sdev->host); scsi_deactivate_tcq(sdev, hba->nutrs); /* Drop the reference as it won't be needed anymore */ if (ufshcd_scsi_to_upiu_lun(sdev->lun) == UFS_UPIU_UFS_DEVICE_WLUN) hba->sdev_ufs_device = NULL; } /** Loading Loading @@ -3429,6 +3497,8 @@ static void ufshcd_async_scan(void *data, async_cookie_t cookie) if (ret) goto out; /* UniPro link is active now */ ufshcd_set_link_active(hba); ufshcd_config_max_pwr_mode(hba); ret = ufshcd_verify_dev_init(hba); Loading @@ -3439,11 +3509,16 @@ static void ufshcd_async_scan(void *data, async_cookie_t cookie) if (ret) goto out; /* UFS device is also active now */ ufshcd_set_ufs_dev_active(hba); ufshcd_force_reset_auto_bkops(hba); hba->ufshcd_state = UFSHCD_STATE_OPERATIONAL; /* If we are in error handling context no need to scan the host */ if (!ufshcd_eh_in_progress(hba)) { /* * If we are in error handling context or in power management callbacks * context, no need to scan the host */ if (!ufshcd_eh_in_progress(hba) && !hba->pm_op_in_progress) { ufshcd_init_icc_levels(hba); scsi_scan_host(hba->host); pm_runtime_put_sync(hba->dev); Loading @@ -3453,7 +3528,7 @@ out: * If we failed to initialize the device or the device is not * present, turn off the power/clocks etc. */ if (ret && !ufshcd_eh_in_progress(hba)) if (ret && !ufshcd_eh_in_progress(hba) && !hba->pm_op_in_progress) ufshcd_hba_exit(hba); return; Loading Loading @@ -3799,71 +3874,403 @@ static void ufshcd_hba_exit(struct ufs_hba *hba) } /** * ufshcd_suspend - suspend power management function * ufshcd_set_dev_pwr_mode - sends START STOP UNIT command to set device * power mode * @hba: per adapter instance * @state: power state * @pwr_mode: device power mode to set * * Returns -ENOSYS * Returns 0 if requested power mode is set successfully * Returns non-zero if failed to set the requested power mode */ int ufshcd_suspend(struct ufs_hba *hba, pm_message_t state) static int ufshcd_set_dev_pwr_mode(struct ufs_hba *hba, enum ufs_dev_pwr_mode pwr_mode) { unsigned char cmd[6] = { START_STOP }; struct scsi_sense_hdr sshdr; struct scsi_device *sdp = hba->sdev_ufs_device; int ret; if (!sdp || !scsi_device_online(sdp)) return -ENODEV; cmd[4] = pwr_mode << 4; /* * TODO: * 1. Block SCSI requests from SCSI midlayer * 2. Change the internal driver state to non operational * 3. Set UTRLRSR and UTMRLRSR bits to zero * 4. Wait until outstanding commands are completed * 5. Set HCE to zero to send the UFS host controller to reset state * Current function would be generally called from the power management * callbacks hence set the REQ_PM flag so that it doesn't resume the * already suspended childs. */ ret = scsi_execute_req_flags(sdp, cmd, DMA_NONE, NULL, 0, &sshdr, START_STOP_TIMEOUT, 0, NULL, REQ_PM); if (ret) { sdev_printk(KERN_WARNING, sdp, "START_STOP failed for power mode: %d\n", pwr_mode); scsi_show_result(ret); if (driver_byte(ret) & DRIVER_SENSE) { scsi_show_sense_hdr(&sshdr); scsi_show_extd_sense(sshdr.asc, sshdr.ascq); } } if (!ret) hba->curr_dev_pwr_mode = pwr_mode; return -ENOSYS; return ret; } static int ufshcd_link_state_transition(struct ufs_hba *hba, enum uic_link_state req_link_state, int check_for_bkops) { int ret = 0; if (req_link_state == hba->uic_link_state) return 0; if (req_link_state == UIC_LINK_HIBERN8_STATE) { ret = ufshcd_uic_hibern8_enter(hba); if (!ret) ufshcd_set_link_hibern8(hba); else goto out; } /* * If autobkops is enabled, link can't be turned off because * turning off the link would also turn off the device. */ else if ((req_link_state == UIC_LINK_OFF_STATE) && (!check_for_bkops || (check_for_bkops && !hba->auto_bkops_enabled))) { /* * Change controller state to "reset state" which * should also put the link in off/reset state */ ufshcd_hba_stop(hba); /* * TODO: Check if we need any delay to make sure that * controller is reset */ ufshcd_set_link_off(hba); } out: return ret; } EXPORT_SYMBOL_GPL(ufshcd_suspend); /** * ufshcd_resume - resume power management function * ufshcd_suspend - helper function for suspend operations * @hba: per adapter instance * @pm_op: runtime PM or system PM * * This is common function called by both ufshcd_system_suspend() and * ufshcd_runtime_suspend(). * * This function will try to put the UFS device and link into low power * mode based on the "rpm_lvl" (Runtime PM level) or "spm_lvl" * (System PM level). * * Returns -ENOSYS * NOTE: UFS device & link must be active before we enter in this function. * * Returns 0 for success and non-zero for failure */ int ufshcd_resume(struct ufs_hba *hba) static int ufshcd_suspend(struct ufs_hba *hba, enum ufs_pm_op pm_op) { int ret = 0; enum ufs_pm_level pm_lvl; enum ufs_dev_pwr_mode req_dev_pwr_mode; enum uic_link_state req_link_state; if (!hba) return 0; hba->pm_op_in_progress = 1; pm_lvl = ufshcd_is_runtime_pm(pm_op) ? hba->rpm_lvl : hba->spm_lvl; req_dev_pwr_mode = ufs_get_pm_lvl_to_dev_pwr_mode(pm_lvl); req_link_state = ufs_get_pm_lvl_to_link_pwr_state(pm_lvl); /* * TODO: * 1. Set HCE to 1, to start the UFS host controller * initialization process * 2. Set UTRLRSR and UTMRLRSR bits to 1 * 3. Change the internal driver state to operational * 4. Unblock SCSI requests from SCSI midlayer * If we can't transition into any of the low power modes * just gate the clocks. */ if (req_dev_pwr_mode == UFS_ACTIVE_PWR_MODE && req_link_state == UIC_LINK_ACTIVE_STATE) { goto disable_clks; } return -ENOSYS; if ((req_dev_pwr_mode == hba->curr_dev_pwr_mode) && (req_link_state == hba->uic_link_state)) goto out; /* UFS device & link must be active before we enter in this function */ if (!ufshcd_is_ufs_dev_active(hba) || !ufshcd_is_link_active(hba)) { ret = -EINVAL; goto out; } EXPORT_SYMBOL_GPL(ufshcd_resume); int ufshcd_runtime_suspend(struct ufs_hba *hba) if (ufshcd_is_runtime_pm(pm_op)) { /* * The device is idle with no requests in the queue, * allow background operations if needed. */ ret = ufshcd_bkops_ctrl(hba, BKOPS_STATUS_NON_CRITICAL); if (ret) goto out; } if ((req_dev_pwr_mode != hba->curr_dev_pwr_mode) && ((ufshcd_is_runtime_pm(pm_op) && !hba->auto_bkops_enabled) || ufshcd_is_system_pm(pm_op))) { /* ensure that bkops is disabled */ ufshcd_disable_auto_bkops(hba); ret = ufshcd_set_dev_pwr_mode(hba, req_dev_pwr_mode); if (ret) goto out; } ret = ufshcd_link_state_transition(hba, req_link_state, 1); if (ret) goto set_dev_active; /* * If UFS device is either in UFS_Sleep turn off VCC rail to * save some power. * If UFS device is in UFS_Poweroff state, all power supplies * (VCC, VCCQ, VCCQ2) can be turned off. * Ignore the error returned by ufshcd_toggle_vreg() as device * is anyway in low power state which would save some power. */ if (ufshcd_is_ufs_dev_poweroff(hba)) ufshcd_setup_vreg(hba, false); else if (ufshcd_is_ufs_dev_sleep(hba)) ufshcd_toggle_vreg(hba->dev, hba->vreg_info.vcc, false); disable_clks: /* * Call vendor specific suspend callback. As these callbacks may access * vendor specific host controller register space call them before the * host clocks are ON. */ if (hba->vops && hba->vops->suspend) { ret = hba->vops->suspend(hba, pm_op); if (ret) goto set_link_active; } /* freeze the hardware by turning off the clocks */ ufshcd_setup_clocks(hba, false); /* * Disable the host irq as host controller as there won't be any * host controller trasanction expected till resume. */ ufshcd_disable_irq(hba); goto out; set_link_active: if (ufshcd_is_ufs_dev_poweroff(hba)) ufshcd_setup_vreg(hba, true); else if (ufshcd_is_ufs_dev_sleep(hba)) ufshcd_toggle_vreg(hba->dev, hba->vreg_info.vcc, true); if (ufshcd_is_link_hibern8(hba) && !ufshcd_uic_hibern8_exit(hba)) ufshcd_set_link_active(hba); else if (ufshcd_is_link_off(hba)) ufshcd_host_reset_and_restore(hba); set_dev_active: if (!ufshcd_set_dev_pwr_mode(hba, UFS_ACTIVE_PWR_MODE)) ufshcd_disable_auto_bkops(hba); out: hba->pm_op_in_progress = 0; return ret; } /** * ufshcd_resume - helper function for resume operations * @hba: per adapter instance * @pm_op: runtime PM or system PM * * This function basically brings the UFS device, UniPro link and controller * to active state. * * Returns 0 for success and non-zero for failure */ static int ufshcd_resume(struct ufs_hba *hba, enum ufs_pm_op pm_op) { int ret; enum uic_link_state old_link_state; if (!hba) return 0; hba->pm_op_in_progress = 1; old_link_state = hba->uic_link_state; /* Make sure clocks are enabled before accessing controller */ ret = ufshcd_setup_clocks(hba, true); if (ret) goto out; /* enable the host irq as host controller would be active soon */ ufshcd_enable_irq(hba); /* Bring regulators back online if its turned off during suspend. */ if (ufshcd_is_ufs_dev_poweroff(hba)) ret = ufshcd_setup_vreg(hba, true); else if (ufshcd_is_ufs_dev_sleep(hba)) ret = ufshcd_toggle_vreg(hba->dev, hba->vreg_info.vcc, true); if (ret) goto disable_irq_and_clks; /* * The device is idle with no requests in the queue, * allow background operations. * Call vendor specific resume callback. As these callbacks may access * vendor specific host controller register space call them when the * host clocks are ON. */ ret = ufshcd_bkops_ctrl(hba, BKOPS_STATUS_NON_CRITICAL); if (hba->vops && hba->vops->resume) { ret = hba->vops->resume(hba, pm_op); if (ret) goto disable_vreg; } if (ufshcd_is_link_hibern8(hba)) { ret = ufshcd_uic_hibern8_exit(hba); if (!ret) ufshcd_set_link_active(hba); else goto vendor_suspend; } else if (ufshcd_is_link_off(hba)) { ret = ufshcd_host_reset_and_restore(hba); /* * ufshcd_host_reset_and_restore() should have already * set the link state as active */ if (ret || !ufshcd_is_link_active(hba)) goto vendor_suspend; } if (!ufshcd_is_ufs_dev_active(hba)) { ret = ufshcd_set_dev_pwr_mode(hba, UFS_ACTIVE_PWR_MODE); if (ret) goto set_old_link_state; } ufshcd_disable_auto_bkops(hba); goto out; set_old_link_state: ufshcd_link_state_transition(hba, old_link_state, 0); vendor_suspend: if (hba->vops && hba->vops->suspend) hba->vops->suspend(hba, pm_op); disable_vreg: if (ufshcd_is_ufs_dev_poweroff(hba)) ufshcd_setup_vreg(hba, false); else if (ufshcd_is_ufs_dev_sleep(hba)) ufshcd_toggle_vreg(hba->dev, hba->vreg_info.vcc, false); disable_irq_and_clks: ufshcd_disable_irq(hba); ufshcd_setup_clocks(hba, false); out: hba->pm_op_in_progress = 0; return ret; } EXPORT_SYMBOL(ufshcd_runtime_suspend); int ufshcd_runtime_resume(struct ufs_hba *hba) /** * ufshcd_system_suspend - system suspend routine * @hba: per adapter instance * @pm_op: runtime PM or system PM * * Check the description of ufshcd_suspend() function for more details. * * Returns 0 for success and non-zero for failure */ int ufshcd_system_suspend(struct ufs_hba *hba) { if (!hba) int ret = 0; if (pm_runtime_suspended(hba->dev)) { if (hba->rpm_lvl == hba->spm_lvl) /* * There is possibility that device may still be in * active state during the runtime suspend. */ if ((ufs_get_pm_lvl_to_dev_pwr_mode(hba->spm_lvl) == hba->curr_dev_pwr_mode) && !hba->auto_bkops_enabled) goto out; /* * UFS device and/or UFS link low power states during runtime * suspend seems to be different than what is expected during * system suspend. Hence runtime resume the devic & link and * let the system suspend low power states to take effect. * TODO: If resume takes longer time, we might have optimize * it in future by not resuming everything if possible. */ ret = ufshcd_runtime_resume(hba); if (ret) goto out; } ret = ufshcd_suspend(hba, UFS_SYSTEM_PM); out: return ret; } EXPORT_SYMBOL(ufshcd_system_suspend); /** * ufshcd_system_resume - system resume routine * @hba: per adapter instance * * Returns 0 for success and non-zero for failure */ int ufshcd_system_resume(struct ufs_hba *hba) { if (pm_runtime_suspended(hba->dev)) /* Let the runtime resume take care of resuming it */ return 0; else return ufshcd_resume(hba, UFS_SYSTEM_PM); } EXPORT_SYMBOL(ufshcd_system_resume); /** * ufshcd_runtime_suspend - runtime suspend routine * @hba: per adapter instance * * Check the description of ufshcd_suspend() function for more details. * * Returns 0 for success and non-zero for failure */ int ufshcd_runtime_suspend(struct ufs_hba *hba) { return ufshcd_suspend(hba, UFS_RUNTIME_PM); } EXPORT_SYMBOL(ufshcd_runtime_suspend); return ufshcd_disable_auto_bkops(hba); /** * ufshcd_runtime_resume - runtime resume routine * @hba: per adapter instance * * This function basically brings the UFS device, UniPro link and controller * to active state. Following operations are done in this function: * * 1. Turn on all the controller related clocks * 2. Bring the UniPro link out of Hibernate state * 3. If UFS device is in sleep state, turn ON VCC rail and bring the UFS device * to active state. * 4. If auto-bkops is enabled on the device, disable it. * * So following would be the possible power state after this function return * successfully: * S1: UFS device in Active state with VCC rail ON * UniPro link in Active state * All the UFS/UniPro controller clocks are ON * * Returns 0 for success and non-zero for failure */ int ufshcd_runtime_resume(struct ufs_hba *hba) { return ufshcd_resume(hba, UFS_RUNTIME_PM); } EXPORT_SYMBOL(ufshcd_runtime_resume); Loading Loading @@ -4037,6 +4444,8 @@ int ufshcd_init(struct ufs_hba *hba, void __iomem *mmio_base, unsigned int irq) if (err) { dev_err(hba->dev, "request irq failed\n"); goto out_disable; } else { hba->is_irq_enabled = true; } /* Enable SCSI tag mapping */ Loading Loading @@ -4071,6 +4480,7 @@ int ufshcd_init(struct ufs_hba *hba, void __iomem *mmio_base, unsigned int irq) out_remove_scsi_host: scsi_remove_host(hba->host); out_disable: hba->is_irq_enabled = false; scsi_host_put(host); UFSDBG_REMOVE_DEBUGFS(hba) ufshcd_hba_exit(hba); Loading drivers/scsi/ufs/ufshcd.h +66 −3 Original line number Diff line number Diff line Loading @@ -96,6 +96,51 @@ struct uic_command { struct completion done; }; /* Used to differentiate the power management options */ enum ufs_pm_op { UFS_RUNTIME_PM, UFS_SYSTEM_PM, }; #define ufshcd_is_runtime_pm(op) ((op) == UFS_RUNTIME_PM) #define ufshcd_is_system_pm(op) ((op) == UFS_SYSTEM_PM) /* Host <-> Device UniPro Link state */ enum uic_link_state { UIC_LINK_OFF_STATE = 0, /* Link powered down or disabled */ UIC_LINK_ACTIVE_STATE = 1, /* Link is in Fast/Slow/Sleep state */ UIC_LINK_HIBERN8_STATE = 2, /* Link is in Hibernate state */ }; #define ufshcd_is_link_off(hba) ((hba)->uic_link_state & UIC_LINK_OFF_STATE) #define ufshcd_is_link_active(hba) ((hba)->uic_link_state & \ UIC_LINK_ACTIVE_STATE) #define ufshcd_is_link_hibern8(hba) ((hba)->uic_link_state & \ UIC_LINK_HIBERN8_STATE) #define ufshcd_set_link_off(hba) ((hba)->uic_link_state = UIC_LINK_OFF_STATE) #define ufshcd_set_link_active(hba) ((hba)->uic_link_state = \ UIC_LINK_ACTIVE_STATE) #define ufshcd_set_link_hibern8(hba) ((hba)->uic_link_state = \ UIC_LINK_HIBERN8_STATE) /* * UFS Power management levels. * Each level is in increasing order of power savings. */ enum ufs_pm_level { UFS_PM_LVL_0, /* UFS_ACTIVE_PWR_MODE, UIC_LINK_ACTIVE_STATE */ UFS_PM_LVL_1, /* UFS_ACTIVE_PWR_MODE, UIC_LINK_HIBERN8_STATE */ UFS_PM_LVL_2, /* UFS_SLEEP_PWR_MODE, UIC_LINK_ACTIVE_STATE */ UFS_PM_LVL_3, /* UFS_SLEEP_PWR_MODE, UIC_LINK_HIBERN8_STATE */ UFS_PM_LVL_4, /* UFS_POWERDOWN_PWR_MODE, UIC_LINK_OFF_STATE */ UFS_PM_LVL_MAX }; struct ufs_pm_lvl_states { enum ufs_dev_pwr_mode dev_state; enum uic_link_state link_state; }; /** * struct ufshcd_lrb - local reference block * @utr_descriptor_ptr: UTRD address of the command Loading Loading @@ -197,6 +242,8 @@ struct ufs_clk_info { * variant specific Uni-Pro initialization. * @link_startup_notify: called before and after Link startup is carried out * to allow variant specific Uni-Pro initialization. * @suspend: called during host controller PM callback * @resume: called during host controller PM callback */ struct ufs_hba_variant_ops { const char *name; Loading @@ -206,6 +253,8 @@ struct ufs_hba_variant_ops { int (*setup_regulators)(struct ufs_hba *, bool); int (*hce_enable_notify)(struct ufs_hba *, bool); int (*link_startup_notify)(struct ufs_hba *, bool); int (*suspend)(struct ufs_hba *, enum ufs_pm_op); int (*resume)(struct ufs_hba *, enum ufs_pm_op); }; /** Loading Loading @@ -270,6 +319,18 @@ struct ufs_hba { struct Scsi_Host *host; struct device *dev; /* * This field is to keep a reference to "scsi_device" corresponding to * "UFS device" W-LU. */ struct scsi_device *sdev_ufs_device; enum ufs_dev_pwr_mode curr_dev_pwr_mode; enum uic_link_state uic_link_state; /* Desired UFS power management level during runtime PM */ enum ufs_pm_level rpm_lvl; /* Desired UFS power management level during system PM */ enum ufs_pm_level spm_lvl; int pm_op_in_progress; struct ufshcd_lrb *lrb; unsigned long lrb_in_use; Loading @@ -284,6 +345,7 @@ struct ufs_hba { struct ufs_hba_variant_ops *vops; void *priv; unsigned int irq; bool is_irq_enabled; unsigned int quirks; /* Deviations from standard UFSHCI spec. */ Loading Loading @@ -313,14 +375,13 @@ struct ufs_hba { */ #define UFSHCD_QUIRK_BROKEN_PWR_MODE_CHANGE (1 << 5) struct uic_command *active_uic_cmd; struct mutex uic_cmd_mutex; wait_queue_head_t tm_wq; wait_queue_head_t tm_tag_wq; unsigned long tm_condition; unsigned long tm_slots_in_use; struct uic_command *active_uic_cmd; struct mutex uic_cmd_mutex; struct completion *uic_async_done; u32 ufshcd_state; Loading Loading @@ -396,6 +457,8 @@ static inline void check_upiu_size(void) extern int ufshcd_runtime_suspend(struct ufs_hba *hba); extern int ufshcd_runtime_resume(struct ufs_hba *hba); extern int ufshcd_runtime_idle(struct ufs_hba *hba); extern int ufshcd_system_suspend(struct ufs_hba *hba); extern int ufshcd_system_resume(struct ufs_hba *hba); extern int ufshcd_dme_set_attr(struct ufs_hba *hba, u32 attr_sel, u8 attr_set, u32 mib_val, u8 peer); extern int ufshcd_dme_get_attr(struct ufs_hba *hba, u32 attr_sel, Loading Loading
drivers/scsi/ufs/ufs.h +16 −0 Original line number Diff line number Diff line Loading @@ -62,6 +62,14 @@ #define UFS_MAX_LUNS (SCSI_W_LUN_BASE + UFS_UPIU_MAX_UNIT_NUM_ID) #define UFS_UPIU_WLUN_ID (1 << 7) /* Well known logical unit id in LUN field of UPIU */ enum { UFS_UPIU_REPORT_LUNS_WLUN = 0x81, UFS_UPIU_UFS_DEVICE_WLUN = 0xD0, UFS_UPIU_BOOT_WLUN = 0xB0, UFS_UPIU_RPMB_WLUN = 0xC4, }; /* * UFS Protocol Information Unit related definitions */ Loading Loading @@ -264,6 +272,14 @@ enum { UPIU_TASK_MANAGEMENT_FUNC_FAILED = 0x05, UPIU_INCORRECT_LOGICAL_UNIT_NO = 0x09, }; /* UFS device power modes */ enum ufs_dev_pwr_mode { UFS_ACTIVE_PWR_MODE = 1, UFS_SLEEP_PWR_MODE = 2, UFS_POWERDOWN_PWR_MODE = 3, }; /** * struct utp_upiu_header - UPIU header structure * @dword_0: UPIU header DW-0 Loading
drivers/scsi/ufs/ufshcd-pltfrm.c +9 −45 Original line number Diff line number Diff line Loading @@ -215,45 +215,24 @@ out: * ufshcd_pltfrm_suspend - suspend power management function * @dev: pointer to device handle * * * Returns 0 * Returns 0 if successful * Returns non-zero otherwise */ static int ufshcd_pltfrm_suspend(struct device *dev) { struct platform_device *pdev = to_platform_device(dev); struct ufs_hba *hba = platform_get_drvdata(pdev); /* * TODO: * 1. Call ufshcd_suspend * 2. Do bus specific power management */ disable_irq(hba->irq); return 0; return ufshcd_system_suspend(dev_get_drvdata(dev)); } /** * ufshcd_pltfrm_resume - resume power management function * @dev: pointer to device handle * * Returns 0 * Returns 0 if successful * Returns non-zero otherwise */ static int ufshcd_pltfrm_resume(struct device *dev) { struct platform_device *pdev = to_platform_device(dev); struct ufs_hba *hba = platform_get_drvdata(pdev); /* * TODO: * 1. Call ufshcd_resume. * 2. Do bus specific wake up */ enable_irq(hba->irq); return 0; return ufshcd_system_resume(dev_get_drvdata(dev)); } #else #define ufshcd_pltfrm_suspend NULL Loading @@ -263,30 +242,15 @@ static int ufshcd_pltfrm_resume(struct device *dev) #ifdef CONFIG_PM_RUNTIME static int ufshcd_pltfrm_runtime_suspend(struct device *dev) { struct ufs_hba *hba = dev_get_drvdata(dev); if (!hba) return 0; return ufshcd_runtime_suspend(hba); return ufshcd_runtime_suspend(dev_get_drvdata(dev)); } static int ufshcd_pltfrm_runtime_resume(struct device *dev) { struct ufs_hba *hba = dev_get_drvdata(dev); if (!hba) return 0; return ufshcd_runtime_resume(hba); return ufshcd_runtime_resume(dev_get_drvdata(dev)); } static int ufshcd_pltfrm_runtime_idle(struct device *dev) { struct ufs_hba *hba = dev_get_drvdata(dev); if (!hba) return 0; return ufshcd_runtime_idle(hba); return ufshcd_runtime_idle(dev_get_drvdata(dev)); } #else /* !CONFIG_PM_RUNTIME */ #define ufshcd_pltfrm_runtime_suspend NULL Loading
drivers/scsi/ufs/ufshcd.c +446 −36 Original line number Diff line number Diff line Loading @@ -139,6 +139,39 @@ enum { #define ufshcd_clear_eh_in_progress(h) \ (h->eh_flags &= ~UFSHCD_EH_IN_PROGRESS) #define ufshcd_set_ufs_dev_active(h) \ ((h)->curr_dev_pwr_mode = UFS_ACTIVE_PWR_MODE) #define ufshcd_set_ufs_dev_sleep(h) \ ((h)->curr_dev_pwr_mode = UFS_SLEEP_PWR_MODE) #define ufshcd_set_ufs_dev_poweroff(h) \ ((h)->curr_dev_pwr_mode = UFS_POWERDOWN_PWR_MODE) #define ufshcd_is_ufs_dev_active(h) \ ((h)->curr_dev_pwr_mode == UFS_ACTIVE_PWR_MODE) #define ufshcd_is_ufs_dev_sleep(h) \ ((h)->curr_dev_pwr_mode == UFS_SLEEP_PWR_MODE) #define ufshcd_is_ufs_dev_poweroff(h) \ ((h)->curr_dev_pwr_mode == UFS_POWERDOWN_PWR_MODE) static struct ufs_pm_lvl_states ufs_pm_lvl_states[] = { {UFS_ACTIVE_PWR_MODE, UIC_LINK_ACTIVE_STATE}, {UFS_ACTIVE_PWR_MODE, UIC_LINK_HIBERN8_STATE}, {UFS_SLEEP_PWR_MODE, UIC_LINK_ACTIVE_STATE}, {UFS_SLEEP_PWR_MODE, UIC_LINK_HIBERN8_STATE}, {UFS_POWERDOWN_PWR_MODE, UIC_LINK_OFF_STATE}, }; static inline enum ufs_dev_pwr_mode ufs_get_pm_lvl_to_dev_pwr_mode(enum ufs_pm_level lvl) { return ufs_pm_lvl_states[lvl].dev_state; } static inline enum uic_link_state ufs_get_pm_lvl_to_link_pwr_state(enum ufs_pm_level lvl) { return ufs_pm_lvl_states[lvl].link_state; } static void ufshcd_tmc_handler(struct ufs_hba *hba); static void ufshcd_async_scan(void *data, async_cookie_t cookie); static int ufshcd_reset_and_restore(struct ufs_hba *hba); Loading @@ -147,6 +180,22 @@ static int ufshcd_read_sdev_qdepth(struct ufs_hba *hba, struct scsi_device *sdev); static void ufshcd_hba_exit(struct ufs_hba *hba); static inline void ufshcd_enable_irq(struct ufs_hba *hba) { if (!hba->is_irq_enabled) { enable_irq(hba->irq); hba->is_irq_enabled = true; } } static inline void ufshcd_disable_irq(struct ufs_hba *hba) { if (hba->is_irq_enabled) { disable_irq(hba->irq); hba->is_irq_enabled = false; } } /* * ufshcd_wait_for_register - wait for register value to change * @hba - per-adapter interface Loading Loading @@ -1773,7 +1822,7 @@ static int ufshcd_uic_change_pwr_mode(struct ufs_hba *hba, u8 mode) return ufshcd_uic_pwr_ctrl(hba, &uic_cmd); } int ufshcd_uic_hibern8_enter(struct ufs_hba *hba) static int ufshcd_uic_hibern8_enter(struct ufs_hba *hba) { struct uic_command uic_cmd = {0}; Loading @@ -1782,7 +1831,7 @@ int ufshcd_uic_hibern8_enter(struct ufs_hba *hba) return ufshcd_uic_pwr_ctrl(hba, &uic_cmd); } int ufshcd_uic_hibern8_exit(struct ufs_hba *hba) static int ufshcd_uic_hibern8_exit(struct ufs_hba *hba) { struct uic_command uic_cmd = {0}; Loading Loading @@ -1996,6 +2045,9 @@ static int ufshcd_hba_enable(struct ufs_hba *hba) msleep(5); } /* UniPro link is disabled at this point */ ufshcd_set_link_off(hba); if (hba->vops && hba->vops->hce_enable_notify) hba->vops->hce_enable_notify(hba, PRE_CHANGE); Loading Loading @@ -2157,6 +2209,19 @@ static int ufshcd_slave_alloc(struct scsi_device *sdev) __func__, lun_qdepth); scsi_activate_tcq(sdev, lun_qdepth); /* * For selecting the UFS device power mode (Active / UFS_Sleep / * UFS_PowerDown), SCSI power management command (START STOP UNIT) * needs to be sent to a "UFS device" Well known Logical Unit (W-LU). * As this command would be sent during the UFS host controller * runtime/system PM callbacks, we need a reference to "scsi_device" * associated to "UFS device" W-LU. This change saves the "scsi_device" * reference for "UFS device" W-LU during slave_configure() callback * from SCSI mid layer. */ if (ufshcd_scsi_to_upiu_lun(sdev->lun) == UFS_UPIU_UFS_DEVICE_WLUN) hba->sdev_ufs_device = sdev; return 0; } Loading Loading @@ -2219,6 +2284,9 @@ static void ufshcd_slave_destroy(struct scsi_device *sdev) hba = shost_priv(sdev->host); scsi_deactivate_tcq(sdev, hba->nutrs); /* Drop the reference as it won't be needed anymore */ if (ufshcd_scsi_to_upiu_lun(sdev->lun) == UFS_UPIU_UFS_DEVICE_WLUN) hba->sdev_ufs_device = NULL; } /** Loading Loading @@ -3429,6 +3497,8 @@ static void ufshcd_async_scan(void *data, async_cookie_t cookie) if (ret) goto out; /* UniPro link is active now */ ufshcd_set_link_active(hba); ufshcd_config_max_pwr_mode(hba); ret = ufshcd_verify_dev_init(hba); Loading @@ -3439,11 +3509,16 @@ static void ufshcd_async_scan(void *data, async_cookie_t cookie) if (ret) goto out; /* UFS device is also active now */ ufshcd_set_ufs_dev_active(hba); ufshcd_force_reset_auto_bkops(hba); hba->ufshcd_state = UFSHCD_STATE_OPERATIONAL; /* If we are in error handling context no need to scan the host */ if (!ufshcd_eh_in_progress(hba)) { /* * If we are in error handling context or in power management callbacks * context, no need to scan the host */ if (!ufshcd_eh_in_progress(hba) && !hba->pm_op_in_progress) { ufshcd_init_icc_levels(hba); scsi_scan_host(hba->host); pm_runtime_put_sync(hba->dev); Loading @@ -3453,7 +3528,7 @@ out: * If we failed to initialize the device or the device is not * present, turn off the power/clocks etc. */ if (ret && !ufshcd_eh_in_progress(hba)) if (ret && !ufshcd_eh_in_progress(hba) && !hba->pm_op_in_progress) ufshcd_hba_exit(hba); return; Loading Loading @@ -3799,71 +3874,403 @@ static void ufshcd_hba_exit(struct ufs_hba *hba) } /** * ufshcd_suspend - suspend power management function * ufshcd_set_dev_pwr_mode - sends START STOP UNIT command to set device * power mode * @hba: per adapter instance * @state: power state * @pwr_mode: device power mode to set * * Returns -ENOSYS * Returns 0 if requested power mode is set successfully * Returns non-zero if failed to set the requested power mode */ int ufshcd_suspend(struct ufs_hba *hba, pm_message_t state) static int ufshcd_set_dev_pwr_mode(struct ufs_hba *hba, enum ufs_dev_pwr_mode pwr_mode) { unsigned char cmd[6] = { START_STOP }; struct scsi_sense_hdr sshdr; struct scsi_device *sdp = hba->sdev_ufs_device; int ret; if (!sdp || !scsi_device_online(sdp)) return -ENODEV; cmd[4] = pwr_mode << 4; /* * TODO: * 1. Block SCSI requests from SCSI midlayer * 2. Change the internal driver state to non operational * 3. Set UTRLRSR and UTMRLRSR bits to zero * 4. Wait until outstanding commands are completed * 5. Set HCE to zero to send the UFS host controller to reset state * Current function would be generally called from the power management * callbacks hence set the REQ_PM flag so that it doesn't resume the * already suspended childs. */ ret = scsi_execute_req_flags(sdp, cmd, DMA_NONE, NULL, 0, &sshdr, START_STOP_TIMEOUT, 0, NULL, REQ_PM); if (ret) { sdev_printk(KERN_WARNING, sdp, "START_STOP failed for power mode: %d\n", pwr_mode); scsi_show_result(ret); if (driver_byte(ret) & DRIVER_SENSE) { scsi_show_sense_hdr(&sshdr); scsi_show_extd_sense(sshdr.asc, sshdr.ascq); } } if (!ret) hba->curr_dev_pwr_mode = pwr_mode; return -ENOSYS; return ret; } static int ufshcd_link_state_transition(struct ufs_hba *hba, enum uic_link_state req_link_state, int check_for_bkops) { int ret = 0; if (req_link_state == hba->uic_link_state) return 0; if (req_link_state == UIC_LINK_HIBERN8_STATE) { ret = ufshcd_uic_hibern8_enter(hba); if (!ret) ufshcd_set_link_hibern8(hba); else goto out; } /* * If autobkops is enabled, link can't be turned off because * turning off the link would also turn off the device. */ else if ((req_link_state == UIC_LINK_OFF_STATE) && (!check_for_bkops || (check_for_bkops && !hba->auto_bkops_enabled))) { /* * Change controller state to "reset state" which * should also put the link in off/reset state */ ufshcd_hba_stop(hba); /* * TODO: Check if we need any delay to make sure that * controller is reset */ ufshcd_set_link_off(hba); } out: return ret; } EXPORT_SYMBOL_GPL(ufshcd_suspend); /** * ufshcd_resume - resume power management function * ufshcd_suspend - helper function for suspend operations * @hba: per adapter instance * @pm_op: runtime PM or system PM * * This is common function called by both ufshcd_system_suspend() and * ufshcd_runtime_suspend(). * * This function will try to put the UFS device and link into low power * mode based on the "rpm_lvl" (Runtime PM level) or "spm_lvl" * (System PM level). * * Returns -ENOSYS * NOTE: UFS device & link must be active before we enter in this function. * * Returns 0 for success and non-zero for failure */ int ufshcd_resume(struct ufs_hba *hba) static int ufshcd_suspend(struct ufs_hba *hba, enum ufs_pm_op pm_op) { int ret = 0; enum ufs_pm_level pm_lvl; enum ufs_dev_pwr_mode req_dev_pwr_mode; enum uic_link_state req_link_state; if (!hba) return 0; hba->pm_op_in_progress = 1; pm_lvl = ufshcd_is_runtime_pm(pm_op) ? hba->rpm_lvl : hba->spm_lvl; req_dev_pwr_mode = ufs_get_pm_lvl_to_dev_pwr_mode(pm_lvl); req_link_state = ufs_get_pm_lvl_to_link_pwr_state(pm_lvl); /* * TODO: * 1. Set HCE to 1, to start the UFS host controller * initialization process * 2. Set UTRLRSR and UTMRLRSR bits to 1 * 3. Change the internal driver state to operational * 4. Unblock SCSI requests from SCSI midlayer * If we can't transition into any of the low power modes * just gate the clocks. */ if (req_dev_pwr_mode == UFS_ACTIVE_PWR_MODE && req_link_state == UIC_LINK_ACTIVE_STATE) { goto disable_clks; } return -ENOSYS; if ((req_dev_pwr_mode == hba->curr_dev_pwr_mode) && (req_link_state == hba->uic_link_state)) goto out; /* UFS device & link must be active before we enter in this function */ if (!ufshcd_is_ufs_dev_active(hba) || !ufshcd_is_link_active(hba)) { ret = -EINVAL; goto out; } EXPORT_SYMBOL_GPL(ufshcd_resume); int ufshcd_runtime_suspend(struct ufs_hba *hba) if (ufshcd_is_runtime_pm(pm_op)) { /* * The device is idle with no requests in the queue, * allow background operations if needed. */ ret = ufshcd_bkops_ctrl(hba, BKOPS_STATUS_NON_CRITICAL); if (ret) goto out; } if ((req_dev_pwr_mode != hba->curr_dev_pwr_mode) && ((ufshcd_is_runtime_pm(pm_op) && !hba->auto_bkops_enabled) || ufshcd_is_system_pm(pm_op))) { /* ensure that bkops is disabled */ ufshcd_disable_auto_bkops(hba); ret = ufshcd_set_dev_pwr_mode(hba, req_dev_pwr_mode); if (ret) goto out; } ret = ufshcd_link_state_transition(hba, req_link_state, 1); if (ret) goto set_dev_active; /* * If UFS device is either in UFS_Sleep turn off VCC rail to * save some power. * If UFS device is in UFS_Poweroff state, all power supplies * (VCC, VCCQ, VCCQ2) can be turned off. * Ignore the error returned by ufshcd_toggle_vreg() as device * is anyway in low power state which would save some power. */ if (ufshcd_is_ufs_dev_poweroff(hba)) ufshcd_setup_vreg(hba, false); else if (ufshcd_is_ufs_dev_sleep(hba)) ufshcd_toggle_vreg(hba->dev, hba->vreg_info.vcc, false); disable_clks: /* * Call vendor specific suspend callback. As these callbacks may access * vendor specific host controller register space call them before the * host clocks are ON. */ if (hba->vops && hba->vops->suspend) { ret = hba->vops->suspend(hba, pm_op); if (ret) goto set_link_active; } /* freeze the hardware by turning off the clocks */ ufshcd_setup_clocks(hba, false); /* * Disable the host irq as host controller as there won't be any * host controller trasanction expected till resume. */ ufshcd_disable_irq(hba); goto out; set_link_active: if (ufshcd_is_ufs_dev_poweroff(hba)) ufshcd_setup_vreg(hba, true); else if (ufshcd_is_ufs_dev_sleep(hba)) ufshcd_toggle_vreg(hba->dev, hba->vreg_info.vcc, true); if (ufshcd_is_link_hibern8(hba) && !ufshcd_uic_hibern8_exit(hba)) ufshcd_set_link_active(hba); else if (ufshcd_is_link_off(hba)) ufshcd_host_reset_and_restore(hba); set_dev_active: if (!ufshcd_set_dev_pwr_mode(hba, UFS_ACTIVE_PWR_MODE)) ufshcd_disable_auto_bkops(hba); out: hba->pm_op_in_progress = 0; return ret; } /** * ufshcd_resume - helper function for resume operations * @hba: per adapter instance * @pm_op: runtime PM or system PM * * This function basically brings the UFS device, UniPro link and controller * to active state. * * Returns 0 for success and non-zero for failure */ static int ufshcd_resume(struct ufs_hba *hba, enum ufs_pm_op pm_op) { int ret; enum uic_link_state old_link_state; if (!hba) return 0; hba->pm_op_in_progress = 1; old_link_state = hba->uic_link_state; /* Make sure clocks are enabled before accessing controller */ ret = ufshcd_setup_clocks(hba, true); if (ret) goto out; /* enable the host irq as host controller would be active soon */ ufshcd_enable_irq(hba); /* Bring regulators back online if its turned off during suspend. */ if (ufshcd_is_ufs_dev_poweroff(hba)) ret = ufshcd_setup_vreg(hba, true); else if (ufshcd_is_ufs_dev_sleep(hba)) ret = ufshcd_toggle_vreg(hba->dev, hba->vreg_info.vcc, true); if (ret) goto disable_irq_and_clks; /* * The device is idle with no requests in the queue, * allow background operations. * Call vendor specific resume callback. As these callbacks may access * vendor specific host controller register space call them when the * host clocks are ON. */ ret = ufshcd_bkops_ctrl(hba, BKOPS_STATUS_NON_CRITICAL); if (hba->vops && hba->vops->resume) { ret = hba->vops->resume(hba, pm_op); if (ret) goto disable_vreg; } if (ufshcd_is_link_hibern8(hba)) { ret = ufshcd_uic_hibern8_exit(hba); if (!ret) ufshcd_set_link_active(hba); else goto vendor_suspend; } else if (ufshcd_is_link_off(hba)) { ret = ufshcd_host_reset_and_restore(hba); /* * ufshcd_host_reset_and_restore() should have already * set the link state as active */ if (ret || !ufshcd_is_link_active(hba)) goto vendor_suspend; } if (!ufshcd_is_ufs_dev_active(hba)) { ret = ufshcd_set_dev_pwr_mode(hba, UFS_ACTIVE_PWR_MODE); if (ret) goto set_old_link_state; } ufshcd_disable_auto_bkops(hba); goto out; set_old_link_state: ufshcd_link_state_transition(hba, old_link_state, 0); vendor_suspend: if (hba->vops && hba->vops->suspend) hba->vops->suspend(hba, pm_op); disable_vreg: if (ufshcd_is_ufs_dev_poweroff(hba)) ufshcd_setup_vreg(hba, false); else if (ufshcd_is_ufs_dev_sleep(hba)) ufshcd_toggle_vreg(hba->dev, hba->vreg_info.vcc, false); disable_irq_and_clks: ufshcd_disable_irq(hba); ufshcd_setup_clocks(hba, false); out: hba->pm_op_in_progress = 0; return ret; } EXPORT_SYMBOL(ufshcd_runtime_suspend); int ufshcd_runtime_resume(struct ufs_hba *hba) /** * ufshcd_system_suspend - system suspend routine * @hba: per adapter instance * @pm_op: runtime PM or system PM * * Check the description of ufshcd_suspend() function for more details. * * Returns 0 for success and non-zero for failure */ int ufshcd_system_suspend(struct ufs_hba *hba) { if (!hba) int ret = 0; if (pm_runtime_suspended(hba->dev)) { if (hba->rpm_lvl == hba->spm_lvl) /* * There is possibility that device may still be in * active state during the runtime suspend. */ if ((ufs_get_pm_lvl_to_dev_pwr_mode(hba->spm_lvl) == hba->curr_dev_pwr_mode) && !hba->auto_bkops_enabled) goto out; /* * UFS device and/or UFS link low power states during runtime * suspend seems to be different than what is expected during * system suspend. Hence runtime resume the devic & link and * let the system suspend low power states to take effect. * TODO: If resume takes longer time, we might have optimize * it in future by not resuming everything if possible. */ ret = ufshcd_runtime_resume(hba); if (ret) goto out; } ret = ufshcd_suspend(hba, UFS_SYSTEM_PM); out: return ret; } EXPORT_SYMBOL(ufshcd_system_suspend); /** * ufshcd_system_resume - system resume routine * @hba: per adapter instance * * Returns 0 for success and non-zero for failure */ int ufshcd_system_resume(struct ufs_hba *hba) { if (pm_runtime_suspended(hba->dev)) /* Let the runtime resume take care of resuming it */ return 0; else return ufshcd_resume(hba, UFS_SYSTEM_PM); } EXPORT_SYMBOL(ufshcd_system_resume); /** * ufshcd_runtime_suspend - runtime suspend routine * @hba: per adapter instance * * Check the description of ufshcd_suspend() function for more details. * * Returns 0 for success and non-zero for failure */ int ufshcd_runtime_suspend(struct ufs_hba *hba) { return ufshcd_suspend(hba, UFS_RUNTIME_PM); } EXPORT_SYMBOL(ufshcd_runtime_suspend); return ufshcd_disable_auto_bkops(hba); /** * ufshcd_runtime_resume - runtime resume routine * @hba: per adapter instance * * This function basically brings the UFS device, UniPro link and controller * to active state. Following operations are done in this function: * * 1. Turn on all the controller related clocks * 2. Bring the UniPro link out of Hibernate state * 3. If UFS device is in sleep state, turn ON VCC rail and bring the UFS device * to active state. * 4. If auto-bkops is enabled on the device, disable it. * * So following would be the possible power state after this function return * successfully: * S1: UFS device in Active state with VCC rail ON * UniPro link in Active state * All the UFS/UniPro controller clocks are ON * * Returns 0 for success and non-zero for failure */ int ufshcd_runtime_resume(struct ufs_hba *hba) { return ufshcd_resume(hba, UFS_RUNTIME_PM); } EXPORT_SYMBOL(ufshcd_runtime_resume); Loading Loading @@ -4037,6 +4444,8 @@ int ufshcd_init(struct ufs_hba *hba, void __iomem *mmio_base, unsigned int irq) if (err) { dev_err(hba->dev, "request irq failed\n"); goto out_disable; } else { hba->is_irq_enabled = true; } /* Enable SCSI tag mapping */ Loading Loading @@ -4071,6 +4480,7 @@ int ufshcd_init(struct ufs_hba *hba, void __iomem *mmio_base, unsigned int irq) out_remove_scsi_host: scsi_remove_host(hba->host); out_disable: hba->is_irq_enabled = false; scsi_host_put(host); UFSDBG_REMOVE_DEBUGFS(hba) ufshcd_hba_exit(hba); Loading
drivers/scsi/ufs/ufshcd.h +66 −3 Original line number Diff line number Diff line Loading @@ -96,6 +96,51 @@ struct uic_command { struct completion done; }; /* Used to differentiate the power management options */ enum ufs_pm_op { UFS_RUNTIME_PM, UFS_SYSTEM_PM, }; #define ufshcd_is_runtime_pm(op) ((op) == UFS_RUNTIME_PM) #define ufshcd_is_system_pm(op) ((op) == UFS_SYSTEM_PM) /* Host <-> Device UniPro Link state */ enum uic_link_state { UIC_LINK_OFF_STATE = 0, /* Link powered down or disabled */ UIC_LINK_ACTIVE_STATE = 1, /* Link is in Fast/Slow/Sleep state */ UIC_LINK_HIBERN8_STATE = 2, /* Link is in Hibernate state */ }; #define ufshcd_is_link_off(hba) ((hba)->uic_link_state & UIC_LINK_OFF_STATE) #define ufshcd_is_link_active(hba) ((hba)->uic_link_state & \ UIC_LINK_ACTIVE_STATE) #define ufshcd_is_link_hibern8(hba) ((hba)->uic_link_state & \ UIC_LINK_HIBERN8_STATE) #define ufshcd_set_link_off(hba) ((hba)->uic_link_state = UIC_LINK_OFF_STATE) #define ufshcd_set_link_active(hba) ((hba)->uic_link_state = \ UIC_LINK_ACTIVE_STATE) #define ufshcd_set_link_hibern8(hba) ((hba)->uic_link_state = \ UIC_LINK_HIBERN8_STATE) /* * UFS Power management levels. * Each level is in increasing order of power savings. */ enum ufs_pm_level { UFS_PM_LVL_0, /* UFS_ACTIVE_PWR_MODE, UIC_LINK_ACTIVE_STATE */ UFS_PM_LVL_1, /* UFS_ACTIVE_PWR_MODE, UIC_LINK_HIBERN8_STATE */ UFS_PM_LVL_2, /* UFS_SLEEP_PWR_MODE, UIC_LINK_ACTIVE_STATE */ UFS_PM_LVL_3, /* UFS_SLEEP_PWR_MODE, UIC_LINK_HIBERN8_STATE */ UFS_PM_LVL_4, /* UFS_POWERDOWN_PWR_MODE, UIC_LINK_OFF_STATE */ UFS_PM_LVL_MAX }; struct ufs_pm_lvl_states { enum ufs_dev_pwr_mode dev_state; enum uic_link_state link_state; }; /** * struct ufshcd_lrb - local reference block * @utr_descriptor_ptr: UTRD address of the command Loading Loading @@ -197,6 +242,8 @@ struct ufs_clk_info { * variant specific Uni-Pro initialization. * @link_startup_notify: called before and after Link startup is carried out * to allow variant specific Uni-Pro initialization. * @suspend: called during host controller PM callback * @resume: called during host controller PM callback */ struct ufs_hba_variant_ops { const char *name; Loading @@ -206,6 +253,8 @@ struct ufs_hba_variant_ops { int (*setup_regulators)(struct ufs_hba *, bool); int (*hce_enable_notify)(struct ufs_hba *, bool); int (*link_startup_notify)(struct ufs_hba *, bool); int (*suspend)(struct ufs_hba *, enum ufs_pm_op); int (*resume)(struct ufs_hba *, enum ufs_pm_op); }; /** Loading Loading @@ -270,6 +319,18 @@ struct ufs_hba { struct Scsi_Host *host; struct device *dev; /* * This field is to keep a reference to "scsi_device" corresponding to * "UFS device" W-LU. */ struct scsi_device *sdev_ufs_device; enum ufs_dev_pwr_mode curr_dev_pwr_mode; enum uic_link_state uic_link_state; /* Desired UFS power management level during runtime PM */ enum ufs_pm_level rpm_lvl; /* Desired UFS power management level during system PM */ enum ufs_pm_level spm_lvl; int pm_op_in_progress; struct ufshcd_lrb *lrb; unsigned long lrb_in_use; Loading @@ -284,6 +345,7 @@ struct ufs_hba { struct ufs_hba_variant_ops *vops; void *priv; unsigned int irq; bool is_irq_enabled; unsigned int quirks; /* Deviations from standard UFSHCI spec. */ Loading Loading @@ -313,14 +375,13 @@ struct ufs_hba { */ #define UFSHCD_QUIRK_BROKEN_PWR_MODE_CHANGE (1 << 5) struct uic_command *active_uic_cmd; struct mutex uic_cmd_mutex; wait_queue_head_t tm_wq; wait_queue_head_t tm_tag_wq; unsigned long tm_condition; unsigned long tm_slots_in_use; struct uic_command *active_uic_cmd; struct mutex uic_cmd_mutex; struct completion *uic_async_done; u32 ufshcd_state; Loading Loading @@ -396,6 +457,8 @@ static inline void check_upiu_size(void) extern int ufshcd_runtime_suspend(struct ufs_hba *hba); extern int ufshcd_runtime_resume(struct ufs_hba *hba); extern int ufshcd_runtime_idle(struct ufs_hba *hba); extern int ufshcd_system_suspend(struct ufs_hba *hba); extern int ufshcd_system_resume(struct ufs_hba *hba); extern int ufshcd_dme_set_attr(struct ufs_hba *hba, u32 attr_sel, u8 attr_set, u32 mib_val, u8 peer); extern int ufshcd_dme_get_attr(struct ufs_hba *hba, u32 attr_sel, Loading