Received: by 2002:a05:6a10:22f:0:0:0:0 with SMTP id 15csp1273299pxk; Fri, 2 Oct 2020 05:43:10 -0700 (PDT) X-Google-Smtp-Source: ABdhPJzt0bKwSJ9ieFyLV4i9/WSeTBpNxV6D0Rm7qWby2WkXZJQsT4w6ikvhwDdDkk9TqZoJJByQ X-Received: by 2002:a05:6402:395:: with SMTP id o21mr2060046edv.2.1601642590385; Fri, 02 Oct 2020 05:43:10 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1601642590; cv=none; d=google.com; s=arc-20160816; b=zIBh9ptCLkbm6xLqZed3k9hjXppx4QH/aMxl8llkZJ32Cuzzub7ycVUImVBg3DGWYc izvmrsPonXN6fdfUd0TlM6FUNKkiPKJGg71W72uNgWsERPC2XxzrIlkHJbnVTkZJKC9T 8gYDBoj0aJn4KvxrN4RB4TYIUfOHvHX9/e/k4nNNDE+BeGYYL1nkBhUuSQK9fl7lyJof gbMa5SXluPdA2Jr7vrUuIoruKm9J3U2XwT0tFTQFSCyEWrpwcs/Sb9ngLSAZTzT2389L jHxvfvjIP9zNQ/CC54mh/JrM+64Ap5BAtZkyUY3mnQV0/r1KE+154xbRjh5TEJMShHpV hymQ== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=list-id:precedence:organization:references:in-reply-to:message-id :date:subject:cc:to:from:ironport-sdr:ironport-sdr; bh=x1Z1nYRneg3L+xyeJ27DuKj80VHHhs0cKAtzBlKqJ0A=; b=JNJOZycUolr488qfrxGbzs/lb7+53iK9k/+3FE+SNIGdiOaEkz3ThnszMt/sA7l9N4 B5NWrTQ8VIzS1CzzYt9qRIxg4Qfecbn4dXTKglC02fW8sSPc12FPaUmwClCFIV5w5z89 b9H6Gehhp6uUA45bl+bttDu2c+YDyz+H8NjYSN9Io3j3LgZ6kHnn8mZPBAB0vxGfIcA2 fV+HkrtLxe3kuDxF9CipIKJszMwNAiqqXd/ZKFJELJ1ScmR1n/7CFJ1wzcgN5FS2SaOe McL8OVTCD2oRYBIlFK0fJgC+kp7JaZZruRxCOIDEfnvQKsJe1uscv7SoL196fGm1Rwmu S28g== ARC-Authentication-Results: i=1; mx.google.com; spf=pass (google.com: domain of linux-kernel-owner@vger.kernel.org designates 23.128.96.18 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org; dmarc=fail (p=NONE sp=NONE dis=NONE) header.from=intel.com Return-Path: Received: from vger.kernel.org (vger.kernel.org. [23.128.96.18]) by mx.google.com with ESMTP id c11si1072027edy.448.2020.10.02.05.42.47; Fri, 02 Oct 2020 05:43:10 -0700 (PDT) Received-SPF: pass (google.com: domain of linux-kernel-owner@vger.kernel.org designates 23.128.96.18 as permitted sender) client-ip=23.128.96.18; Authentication-Results: mx.google.com; spf=pass (google.com: domain of linux-kernel-owner@vger.kernel.org designates 23.128.96.18 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org; dmarc=fail (p=NONE sp=NONE dis=NONE) header.from=intel.com Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S2387863AbgJBMlY (ORCPT + 99 others); Fri, 2 Oct 2020 08:41:24 -0400 Received: from mga01.intel.com ([192.55.52.88]:36769 "EHLO mga01.intel.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1725964AbgJBMlX (ORCPT ); Fri, 2 Oct 2020 08:41:23 -0400 IronPort-SDR: 2ekbzXZ6l9InIQ+Kn7cR0YyZ1c3DuAf+5xkaN8pwiHvsg5waiSZ81/tLSO/l23AluvpqRJOFll 2W7xzj9x6Y5Q== X-IronPort-AV: E=McAfee;i="6000,8403,9761"; a="181105707" X-IronPort-AV: E=Sophos;i="5.77,327,1596524400"; d="scan'208";a="181105707" X-Amp-Result: SKIPPED(no attachment in message) X-Amp-File-Uploaded: False Received: from orsmga007.jf.intel.com ([10.7.209.58]) by fmsmga101.fm.intel.com with ESMTP/TLS/ECDHE-RSA-AES256-GCM-SHA384; 02 Oct 2020 05:41:20 -0700 IronPort-SDR: BSqpaLl8rVR+R0J7T4+xfGBYifncVlshf5hJXCTxxiSc/Hrfgr3GQbOdtedbmNpaWjBOqjVM2Q MsYoTDm335EA== X-ExtLoop1: 1 X-IronPort-AV: E=Sophos;i="5.77,327,1596524400"; d="scan'208";a="352362853" Received: from ahunter-desktop.fi.intel.com ([10.237.72.190]) by orsmga007.jf.intel.com with ESMTP; 02 Oct 2020 05:41:18 -0700 From: Adrian Hunter To: "Martin K . Petersen" , "James E . J . Bottomley" Cc: linux-scsi@vger.kernel.org, linux-kernel@vger.kernel.org, Alim Akhtar , Avri Altman Subject: [PATCH 1/2] scsi: ufs: Add DeepSleep feature Date: Fri, 2 Oct 2020 15:40:42 +0300 Message-Id: <20201002124043.25394-2-adrian.hunter@intel.com> X-Mailer: git-send-email 2.17.1 In-Reply-To: <20201002124043.25394-1-adrian.hunter@intel.com> References: <20201002124043.25394-1-adrian.hunter@intel.com> Organization: Intel Finland Oy, Registered Address: PL 281, 00181 Helsinki, Business Identity Code: 0357606 - 4, Domiciled in Helsinki Precedence: bulk List-ID: X-Mailing-List: linux-kernel@vger.kernel.org DeepSleep is a UFS v3.1 feature that achieves the lowest power consumption of the device, apart from power off. In DeepSleep mode, no commands are accepted, and the only way to exit is using a hardware reset or power cycle. This patch assumes that if a power cycle was an option, then power off would be preferable, so only exit via a hardware reset is supported. Drivers that wish to support DeepSleep need to set a new capability flag UFSHCD_CAP_DEEPSLEEP and provide a hardware reset via the existing ->device_reset() callback. It is assumed that UFS devices with wspecversion >= 0x310 support DeepSleep. The UFS specification says to set the IMMED (immediate) flag for the Start/Stop Unit command when entering DeepSleep. However some UFS devices object to that, which is addressed in a subsequent patch. Signed-off-by: Adrian Hunter --- drivers/scsi/ufs/ufs-sysfs.c | 7 ++++++ drivers/scsi/ufs/ufs.h | 1 + drivers/scsi/ufs/ufshcd.c | 45 ++++++++++++++++++++++++++++++++++-- drivers/scsi/ufs/ufshcd.h | 17 +++++++++++++- include/trace/events/ufs.h | 3 ++- 5 files changed, 69 insertions(+), 4 deletions(-) diff --git a/drivers/scsi/ufs/ufs-sysfs.c b/drivers/scsi/ufs/ufs-sysfs.c index bdcd27faa054..08e72b7eef6a 100644 --- a/drivers/scsi/ufs/ufs-sysfs.c +++ b/drivers/scsi/ufs/ufs-sysfs.c @@ -28,6 +28,7 @@ static const char *ufschd_ufs_dev_pwr_mode_to_string( case UFS_ACTIVE_PWR_MODE: return "ACTIVE"; case UFS_SLEEP_PWR_MODE: return "SLEEP"; case UFS_POWERDOWN_PWR_MODE: return "POWERDOWN"; + case UFS_DEEPSLEEP_PWR_MODE: return "DEEPSLEEP"; default: return "UNKNOWN"; } } @@ -38,6 +39,7 @@ static inline ssize_t ufs_sysfs_pm_lvl_store(struct device *dev, bool rpm) { struct ufs_hba *hba = dev_get_drvdata(dev); + struct ufs_dev_info *dev_info = &hba->dev_info; unsigned long flags, value; if (kstrtoul(buf, 0, &value)) @@ -46,6 +48,11 @@ static inline ssize_t ufs_sysfs_pm_lvl_store(struct device *dev, if (value >= UFS_PM_LVL_MAX) return -EINVAL; + if (ufs_pm_lvl_states[value].dev_state == UFS_DEEPSLEEP_PWR_MODE && + (!(hba->caps & UFSHCD_CAP_DEEPSLEEP) || + !(dev_info->wspecversion >= 0x310))) + return -EINVAL; + spin_lock_irqsave(hba->host->host_lock, flags); if (rpm) hba->rpm_lvl = value; diff --git a/drivers/scsi/ufs/ufs.h b/drivers/scsi/ufs/ufs.h index f8ab16f30fdc..d593edb48767 100644 --- a/drivers/scsi/ufs/ufs.h +++ b/drivers/scsi/ufs/ufs.h @@ -442,6 +442,7 @@ enum ufs_dev_pwr_mode { UFS_ACTIVE_PWR_MODE = 1, UFS_SLEEP_PWR_MODE = 2, UFS_POWERDOWN_PWR_MODE = 3, + UFS_DEEPSLEEP_PWR_MODE = 4, }; #define UFS_WB_BUF_REMAIN_PERCENT(val) ((val) / 10) diff --git a/drivers/scsi/ufs/ufshcd.c b/drivers/scsi/ufs/ufshcd.c index b8f573a02713..d072b0c80bd8 100644 --- a/drivers/scsi/ufs/ufshcd.c +++ b/drivers/scsi/ufs/ufshcd.c @@ -163,6 +163,11 @@ struct ufs_pm_lvl_states ufs_pm_lvl_states[] = { {UFS_SLEEP_PWR_MODE, UIC_LINK_HIBERN8_STATE}, {UFS_POWERDOWN_PWR_MODE, UIC_LINK_HIBERN8_STATE}, {UFS_POWERDOWN_PWR_MODE, UIC_LINK_OFF_STATE}, + /* + * For DeepSleep, the link is first put in hibern8 and then off. + * Leaving the link in hibern8 is not supported. + */ + {UFS_DEEPSLEEP_PWR_MODE, UIC_LINK_OFF_STATE}, }; static inline enum ufs_dev_pwr_mode @@ -8246,6 +8251,12 @@ static int ufshcd_set_dev_pwr_mode(struct ufs_hba *hba, hba->wlun_dev_clr_ua = false; } + /* + * DeepSleep requires the Immediate flag. DeepSleep state is actually + * entered when the link state goes to Hibern8. + */ + if (pwr_mode == UFS_DEEPSLEEP_PWR_MODE) + cmd[1] = 1; cmd[4] = pwr_mode << 4; /* @@ -8292,7 +8303,8 @@ static int ufshcd_link_state_transition(struct ufs_hba *hba, } /* * If autobkops is enabled, link can't be turned off because - * turning off the link would also turn off the device. + * turning off the link would also turn off the device, except in the + * case of DeepSleep where the device is expected to remain powered. */ else if ((req_link_state == UIC_LINK_OFF_STATE) && (!check_for_bkops || !hba->auto_bkops_enabled)) { @@ -8302,6 +8314,9 @@ static int ufshcd_link_state_transition(struct ufs_hba *hba, * put the link in low power mode is to send the DME end point * to device and then send the DME reset command to local * unipro. But putting the link in hibern8 is much faster. + * + * Note also that putting the link in Hibern8 is a requirement + * for entering DeepSleep. */ ret = ufshcd_uic_hibern8_enter(hba); if (ret) { @@ -8434,6 +8449,7 @@ static void ufshcd_hba_vreg_set_hpm(struct ufs_hba *hba) static int ufshcd_suspend(struct ufs_hba *hba, enum ufs_pm_op pm_op) { int ret = 0; + int check_for_bkops; enum ufs_pm_level pm_lvl; enum ufs_dev_pwr_mode req_dev_pwr_mode; enum uic_link_state req_link_state; @@ -8519,7 +8535,13 @@ static int ufshcd_suspend(struct ufs_hba *hba, enum ufs_pm_op pm_op) } flush_work(&hba->eeh_work); - ret = ufshcd_link_state_transition(hba, req_link_state, 1); + + /* + * In the case of DeepSleep, the device is expected to remain powered + * with the link off, so do not check for bkops. + */ + check_for_bkops = !ufshcd_is_ufs_dev_deepsleep(hba); + ret = ufshcd_link_state_transition(hba, req_link_state, check_for_bkops); if (ret) goto set_dev_active; @@ -8560,11 +8582,25 @@ static int ufshcd_suspend(struct ufs_hba *hba, enum ufs_pm_op pm_op) if (hba->clk_scaling.is_allowed) ufshcd_resume_clkscaling(hba); ufshcd_vreg_set_hpm(hba); + /* + * Device hardware reset is required to exit DeepSleep. Also, for + * DeepSleep, the link is off so host reset and restore will be done + * further below. + */ + if (ufshcd_is_ufs_dev_deepsleep(hba)) { + ufshcd_vops_device_reset(hba); + WARN_ON(!ufshcd_is_link_off(hba)); + } 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: + /* Can also get here needing to exit DeepSleep */ + if (ufshcd_is_ufs_dev_deepsleep(hba)) { + ufshcd_vops_device_reset(hba); + ufshcd_host_reset_and_restore(hba); + } if (!ufshcd_set_dev_pwr_mode(hba, UFS_ACTIVE_PWR_MODE)) ufshcd_disable_auto_bkops(hba); enable_gating: @@ -8626,6 +8662,9 @@ static int ufshcd_resume(struct ufs_hba *hba, enum ufs_pm_op pm_op) if (ret) goto disable_vreg; + /* For DeepSleep, the only supported option is to have the link off */ + WARN_ON(ufshcd_is_ufs_dev_deepsleep(hba) && !ufshcd_is_link_off(hba)); + if (ufshcd_is_link_hibern8(hba)) { ret = ufshcd_uic_hibern8_exit(hba); if (!ret) { @@ -8639,6 +8678,8 @@ static int ufshcd_resume(struct ufs_hba *hba, enum ufs_pm_op pm_op) /* * A full initialization of the host and the device is * required since the link was put to off during suspend. + * Note, in the case of DeepSleep, the device will exit + * DeepSleep due to device reset. */ ret = ufshcd_reset_and_restore(hba); /* diff --git a/drivers/scsi/ufs/ufshcd.h b/drivers/scsi/ufs/ufshcd.h index 6663325ed8a0..8c6094fb35f4 100644 --- a/drivers/scsi/ufs/ufshcd.h +++ b/drivers/scsi/ufs/ufshcd.h @@ -114,16 +114,22 @@ enum uic_link_state { ((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_set_ufs_dev_deepsleep(h) \ + ((h)->curr_dev_pwr_mode = UFS_DEEPSLEEP_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) +#define ufshcd_is_ufs_dev_deepsleep(h) \ + ((h)->curr_dev_pwr_mode == UFS_DEEPSLEEP_PWR_MODE) /* * UFS Power management levels. - * Each level is in increasing order of power savings. + * Each level is in increasing order of power savings, except DeepSleep + * which is lower than PowerDown with power on but not PowerDown with + * power off. */ enum ufs_pm_level { UFS_PM_LVL_0, /* UFS_ACTIVE_PWR_MODE, UIC_LINK_ACTIVE_STATE */ @@ -132,6 +138,7 @@ enum ufs_pm_level { UFS_PM_LVL_3, /* UFS_SLEEP_PWR_MODE, UIC_LINK_HIBERN8_STATE */ UFS_PM_LVL_4, /* UFS_POWERDOWN_PWR_MODE, UIC_LINK_HIBERN8_STATE */ UFS_PM_LVL_5, /* UFS_POWERDOWN_PWR_MODE, UIC_LINK_OFF_STATE */ + UFS_PM_LVL_6, /* UFS_DEEPSLEEP_PWR_MODE, UIC_LINK_OFF_STATE */ UFS_PM_LVL_MAX }; @@ -591,6 +598,14 @@ enum ufshcd_caps { * inline crypto engine, if it is present */ UFSHCD_CAP_CRYPTO = 1 << 8, + + /* + * This capability allows the host controller driver to use DeepSleep, + * if it is supported by the UFS device. The host controller driver must + * support device hardware reset via the hba->device_reset() callback, + * in order to exit DeepSleep state. + */ + UFSHCD_CAP_DEEPSLEEP = 1 << 9, }; struct ufs_hba_variant_params { diff --git a/include/trace/events/ufs.h b/include/trace/events/ufs.h index 84841b3a7ffd..2362244c2a9e 100644 --- a/include/trace/events/ufs.h +++ b/include/trace/events/ufs.h @@ -19,7 +19,8 @@ #define UFS_PWR_MODES \ EM(UFS_ACTIVE_PWR_MODE) \ EM(UFS_SLEEP_PWR_MODE) \ - EMe(UFS_POWERDOWN_PWR_MODE) + EM(UFS_POWERDOWN_PWR_MODE) \ + EMe(UFS_DEEPSLEEP_PWR_MODE) #define UFSCHD_CLK_GATING_STATES \ EM(CLKS_OFF) \ -- 2.17.1