UFS: Power managment support
Subsystems (4)
v2 cover UFS: Power managment support
This patch seies introduces support for power managment in the driver as well as vendor specific initialization - registers, clocks, voltage regulators etc.
It includes also a rework for the init sequnce and other PM pre-requisit such as write protection support, handling well-known LUN, error handling (retries), bkops, START_STOP unit command, and ICC levels settings.
--
Changes from V1:
- 6 new patches apended at the end
- Allow overriding power configuration with controller support and preferences/capabilities <Dolev Raviv>
- Allow overriding power choice with controller capabilities <Dolev Raviv>
- Add support for clock gating and clock scaling <Sahitya Tummala>
- Add capability to control the auto bkops during suspend <Subhash Jadavani>
- Add misc changes for phy/unipro driver usage <Dolev Raviv>
Dolev Raviv (2):
scsi: ufs: refactor configuring power mode
scsi: ufs: definitions for phy interface
Sahitya Tummala (3):
scsi: ufs: Add support for clock gating
scsi: ufs: Add freq-table-hz property for UFS device
scsi: ufs: Add support for clock scaling using devfreq framework
Subhash Jadavani (5):
scsi: ufs: refactor query descriptor API support
scsi: support well known logical units
scsi: ufs: introduce well known logical unit in ufs
scsi: ufs: add UFS power management support
scsi: ufs: add capability to control the auto bkops during suspend
Sujit Reddy Thumma (5):
scsi: ufs: Allow vendor specific initialization
scsi: ufs: Add regulator enable support
scsi: ufs: Add clock initialization support
scsi: ufs: improve init sequence
scsi: sd: Avoid sending medium write commands if device is write
protected
Yaniv Gardi (1):
scsi: ufs: Active Power Mode - configuring bActiveICCLevel
.../devicetree/bindings/ufs/ufshcd-pltfrm.txt | 37 + drivers/scsi/scsi_scan.c | 16 + drivers/scsi/scsi_sysfs.c | 7 + drivers/scsi/sd.c | 6 +- drivers/scsi/ufs/Kconfig | 2 + drivers/scsi/ufs/ufs.h | 131 +- drivers/scsi/ufs/ufshcd-pci.c | 55 +- drivers/scsi/ufs/ufshcd-pltfrm.c | 282 ++- drivers/scsi/ufs/ufshcd.c | 2400 ++++++++++++++++++-- drivers/scsi/ufs/ufshcd.h | 277 ++- drivers/scsi/ufs/ufshci.h | 9 +- drivers/scsi/ufs/unipro.h | 56 + include/scsi/scsi.h | 1 + include/scsi/scsi_host.h | 5 + 14 files changed, 2959 insertions(+), 325 deletions(-)
Signature · 1 line
1.8.5.2
PATCH v2 1/16 scsi: ufs: Allow vendor specific initialization
From: Sujit Reddy Thumma <[email protected]>
Some vendor specific controller versions might need to configure vendor specific - registers, clocks, voltage regulators etc. to initialize the host controller UTP layer and Uni-Pro stack. Provide some common initialization operations that can be used to configure vendor specifics. The methods can be extended in future, for example, for power mode transitions.
The operations are vendor/board specific and hence determined with the help of compatible property in device tree.
drivers/scsi/ufs/ufshcd-pci.c +7 -1
@@ -164,7 +164,13 @@ ufshcd_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id) mmio_base =pcim_iomap_table (pdev)[0 ]; - err =ufshcd_init (&pdev->dev, &hba, mmio_base, pdev->irq); + err =ufshcd_alloc_host (&pdev->dev, &hba); +if (err) { +dev_err (&pdev->dev,"Allocation failed\n" ); +return err; + } + + err =ufshcd_init (hba, mmio_base, pdev->irq);if (err) {dev_err (&pdev->dev,"Initialization failed\n" );return err;
drivers/scsi/ufs/ufshcd-pltfrm.c +26 -3
@@ -35,9 +35,24 @@#include <linux/platform_device.h> #include <linux/pm_runtime.h> +#include <linux/of.h> #include "ufshcd.h" +static const struct of_device_id ufs_of_match[]; +static struct ufs_hba_variant_ops *get_variant_ops (struct device *dev) +{ +if (dev->of_node) { +const struct of_device_id *match; + + match =of_match_node (ufs_of_match, dev->of_node); +if (match) +return (struct ufs_hba_variant_ops *)match->data; + } + +return NULL ; +} +#ifdef CONFIG_PM /** * ufshcd_pltfrm_suspend - suspend power management function
@@ -138,8 +153,8 @@ static int ufshcd_pltfrm_probe(struct platform_device *pdev) mem_res =platform_get_resource (pdev,IORESOURCE_MEM ,0 ); mmio_base =devm_ioremap_resource (dev, mem_res); -if (IS_ERR (mmio_base)) { - err =PTR_ERR (mmio_base); +if (IS_ERR (*(void **)&mmio_base)) { + err =PTR_ERR (*(void **)&mmio_base); goto out; }
@@ -150,10 +165,18 @@ static int ufshcd_pltfrm_probe(struct platform_device *pdev) goto out; } + err =ufshcd_alloc_host (dev, &hba); +if (err) { +dev_err (&pdev->dev,"Allocation failed\n" ); + goto out; + } + + hba->vops =get_variant_ops (&pdev->dev); +pm_runtime_set_active (&pdev->dev);pm_runtime_enable (&pdev->dev); - err =ufshcd_init (dev, &hba, mmio_base, irq); + err =ufshcd_init (hba, mmio_base, irq);if (err) {dev_err (dev,"Intialization failed\n" ); goto out_disable_rpm;
drivers/scsi/ufs/ufshcd.c +129 -32
276 lines not on this page · raw message
drivers/scsi/ufs/ufshcd.h +32 -2
@@ -68,6 +68,8 @@#define UFSHCD "ufshcd"#define UFSHCD_DRIVER_VERSION "0.2" +struct ufs_hba ; +enum dev_cmd_type {DEV_CMD_TYPE_NOP =0x0 ,DEV_CMD_TYPE_QUERY =0x1 ,
@@ -152,6 +154,30 @@ struct ufs_dev_cmd {struct ufs_query query; }; +#define PRE_CHANGE 0 +#define POST_CHANGE 1 +/** +* struct ufs_hba_variant_ops - variant specific callbacks +* @name: variant name +* @init: called when the driver is initialized +* @exit: called to cleanup everything done in init +* @setup_clocks: called before touching any of the controller registers +* @setup_regulators: called before accessing the host controller +* @hce_enable_notify: called before and after HCE enable bit is set to allow +* variant specific Uni-Pro initialization. +* @link_startup_notify: called before and after Link startup is carried out +* to allow variant specific Uni-Pro initialization. +*/ +struct ufs_hba_variant_ops { +const char *name; +int (*init)(struct ufs_hba *); +void (*exit)(struct ufs_hba *); +int (*setup_clocks)(struct ufs_hba *,bool ); +int (*setup_regulators)(struct ufs_hba *,bool ); +int (*hce_enable_notify)(struct ufs_hba *,bool ); +int (*link_startup_notify)(struct ufs_hba *,bool ); +}; +/** * struct ufs_hba - per adapter private structure * @mmio_base: UFSHCI base register address
@@ -171,6 +197,8 @@ struct ufs_dev_cmd {* @nutrs: Transfer Request Queue depth supported by controller * @nutmrs: Task Management Queue depth supported by controller * @ufs_version: UFS Version to which controller complies +* @vops: pointer to variant specific operations +* @priv: pointer to variant specific private data * @irq: Irq number of the controller * @active_uic_cmd: handle of active UIC command * @uic_cmd_mutex: mutex for uic command
@@ -218,6 +246,8 @@ struct ufs_hba {int nutrs;int nutmrs;u32 ufs_version; +struct ufs_hba_variant_ops *vops; +void *priv;unsigned int irq;struct uic_command *active_uic_cmd;
@@ -256,8 +286,8 @@ struct ufs_hba {#define ufshcd_readl (hba, reg) \ readl((hba)->mmio_base + (reg)) -int ufshcd_init (struct device *,struct ufs_hba ** ,void __iomem * , -unsigned int ); +int ufshcd_alloc_host (struct device *,struct ufs_hba **); +int ufshcd_init (struct ufs_hba * ,void __iomem * ,unsigned int );void ufshcd_remove (struct ufs_hba *);/**
Signature · 4 lines
1.8.5.2 -- QUALCOMM ISRAEL, on behalf of Qualcomm Innovation Center, Inc. is a member of Code Aurora Forum, hosted by The Linux Foundation
PATCH v2 2/16 scsi: ufs: Add regulator enable support
In reply to Dolev RavivFrom: Sujit Reddy Thumma <[email protected]>
UFS devices are powered by at most three external power supplies -
- VCC - The flash memory core power supply, 2.7V to 3.6V or 1.70V to 1.95V
- VCCQ - The controller and I/O power supply, 1.1V to 1.3V
- VCCQ2 - Secondary controller and/or I/O power supply, 1.65V to 1.95V
For some devices VCCQ or VCCQ2 are optional as they can be generated using internal LDO inside the UFS device.
Add DT bindings for voltage regulators that can be controlled from host driver.
Documentation/devicetree/bindings/ufs/ufshcd-pltfrm.txt +24 -0
@@ -8,9 +8,33 @@ Required properties: - interrupts : <interrupt mapping for UFS host controller IRQ> - reg : <registers mapping> +Optional properties: +- vcc-supply : phandle to VCC supply regulator node +- vccq-supply : phandle to VCCQ supply regulator node +- vccq2-supply : phandle to VCCQ2 supply regulator node +- vcc-supply-1p8 : For embedded UFS devices, valid VCC range is 1.7-1.95V + or 2.7-3.6V. This boolean property when set, specifies + to use low voltage range of 1.7-1.95V. Note for external + UFS cards this property is invalid and valid VCC range is + always 2.7-3.6V. +- vcc-max-microamp : specifies max. load that can be drawn from vcc supply +- vccq-max-microamp : specifies max. load that can be drawn from vccq supply +- vccq2-max-microamp : specifies max. load that can be drawn from vccq2 supply + +Note: If above properties are not defined it can be assumed that the supply +regulators are always on. + Example: ufshc@0xfc598000 { compatible = "jedec,ufs-1.1"; reg = <0xfc598000 0x800>; interrupts = <0 28 0>; + + vcc-supply = <&xxx_reg1>; + vcc-supply-1p8; + vccq-supply = <&xxx_reg2>; + vccq2-supply = <&xxx_reg3>; + vcc-max-microamp = 500000; + vccq-max-microamp = 200000; + vccq2-max-microamp = 200000; };
drivers/scsi/ufs/ufs.h +25 -0
@@ -362,4 +362,29 @@ struct ufs_query_res { struct utp_upiu_query upiu_res; }; +#define UFS_VREG_VCC_MIN_UV 2700000/* uV */ +#define UFS_VREG_VCC_MAX_UV 3600000/* uV */ +#define UFS_VREG_VCC_1P8_MIN_UV 1700000/* uV */ +#define UFS_VREG_VCC_1P8_MAX_UV 1950000/* uV */ +#define UFS_VREG_VCCQ_MIN_UV 1100000/* uV */ +#define UFS_VREG_VCCQ_MAX_UV 1300000/* uV */ +#define UFS_VREG_VCCQ2_MIN_UV 1650000/* uV */ +#define UFS_VREG_VCCQ2_MAX_UV 1950000/* uV */ + +struct ufs_vreg { +struct regulator *reg; +const char *name; +bool enabled; +int min_uV; +int max_uV; +int min_uA; +int max_uA; +}; + +struct ufs_vreg_info { +struct ufs_vreg *vcc; +struct ufs_vreg *vccq; +struct ufs_vreg *vccq2; +}; +#endif /* End of Header */
drivers/scsi/ufs/ufshcd-pltfrm.c +100 -0
112 lines not on this page · raw message
drivers/scsi/ufs/ufshcd.c +190 -3
229 lines not on this page · raw message
drivers/scsi/ufs/ufshcd.h +3 -0
@@ -52,6 +52,7 @@#include <linux/pm_runtime.h> #include <linux/clk.h> #include <linux/completion.h> +#include <linux/regulator/consumer.h> #include <asm/irq.h> #include <asm/byteorder.h>
@@ -219,6 +220,7 @@ struct ufs_hba_variant_ops {* @saved_uic_err: sticky UIC error mask * @dev_cmd: ufs device management command information * @auto_bkops_enabled: to track whether bkops is enabled in device +* @vreg_info: UFS device voltage regulator information */ struct ufs_hba {void __iomem *mmio_base;
@@ -279,6 +281,7 @@ struct ufs_hba {struct ufs_dev_cmd dev_cmd;bool auto_bkops_enabled; +struct ufs_vreg_info vreg_info; };#define ufshcd_writel (hba, val, reg) \
Signature · 4 lines
1.8.5.2 -- QUALCOMM ISRAEL, on behalf of Qualcomm Innovation Center, Inc. is a member of Code Aurora Forum, hosted by The Linux Foundation
PATCH v2 3/16 scsi: ufs: Add clock initialization support
In reply to Dolev RavivFrom: Sujit Reddy Thumma <[email protected]>
Add generic clock initialization support for UFSHCD platform driver. The clock info is read from device tree using standard clock bindings. A generic max-clock-frequency-hz property is defined to save information on maximum operating clock frequency the h/w supports.
Documentation/devicetree/bindings/ufs/ufshcd-pltfrm.txt +14 -1
@@ -21,8 +21,17 @@ Optional properties: - vccq-max-microamp : specifies max. load that can be drawn from vccq supply - vccq2-max-microamp : specifies max. load that can be drawn from vccq2 supply +- clocks : List of phandle and clock specifier pairs +- clock-names : List of clock input name strings sorted in the same + order as the clocks property. +- max-clock-frequency-hz : List of maximum operating frequency stored in the same + order as the clocks property. If this property is not + defined or a value in the array is "0" then it is assumed + that the frequency is set by the parent clock or a + fixed rate clock source. + Note: If above properties are not defined it can be assumed that the supply -regulators are always on. +regulators or clocks are always on. Example: ufshc@0xfc598000 {
@@ -37,4 +46,8 @@ Example: vcc-max-microamp = 500000; vccq-max-microamp = 200000; vccq2-max-microamp = 200000; + + clocks = <&core 0>, <&ref 0>, <&iface 0>; + clock-names = "core_clk", "ref_clk", "iface_clk"; + max-clock-frequency-hz = <100000000 19200000 0>; };
drivers/scsi/ufs/ufshcd-pci.c +2 -0
@@ -170,6 +170,8 @@ ufshcd_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id)return err; } +INIT_LIST_HEAD (&hba->clk_list_head); + err =ufshcd_init (hba, mmio_base, pdev->irq);if (err) {dev_err (&pdev->dev,"Initialization failed\n" );
drivers/scsi/ufs/ufshcd-pltfrm.c +71 -0
83 lines not on this page · raw message
drivers/scsi/ufs/ufshcd.c +87 -2
119 lines not on this page · raw message
drivers/scsi/ufs/ufshcd.h +18 -0
@@ -155,6 +155,22 @@ struct ufs_dev_cmd {struct ufs_query query; }; +/** +* struct ufs_clk_info - UFS clock related info +* @list: list headed by hba->clk_list_head +* @clk: clock node +* @name: clock name +* @max_freq: maximum frequency supported by the clock +* @enabled: variable to check against multiple enable/disable +*/ +struct ufs_clk_info { +struct list_head list; +struct clk *clk; +const char *name; +u32 max_freq; +bool enabled; +}; +#define PRE_CHANGE 0#define POST_CHANGE 1/**
@@ -221,6 +237,7 @@ struct ufs_hba_variant_ops {* @dev_cmd: ufs device management command information * @auto_bkops_enabled: to track whether bkops is enabled in device * @vreg_info: UFS device voltage regulator information +* @clk_list_head: UFS host controller clocks list node head */ struct ufs_hba {void __iomem *mmio_base;
@@ -282,6 +299,7 @@ struct ufs_hba {bool auto_bkops_enabled;struct ufs_vreg_info vreg_info; +struct list_head clk_list_head; };#define ufshcd_writel (hba, val, reg) \
Signature · 4 lines
1.8.5.2 -- QUALCOMM ISRAEL, on behalf of Qualcomm Innovation Center, Inc. is a member of Code Aurora Forum, hosted by The Linux Foundation
PATCH v2 4/16 scsi: ufs: refactor query descriptor API support
In reply to Dolev RavivFrom: Subhash Jadavani <[email protected]>
Currently reading query descriptor is more tightened to each descriptor type. This patch generalize the approach and allows reading any parameter from any query descriptor.
drivers/scsi/ufs/ufs.h +23 -2
@@ -50,6 +50,8 @@cpu_to_be32 ((byte3 <<24 ) | (byte2 <<16 ) |\ (byte1 << 8) | (byte0)) +#define UFS_UPIU_MAX_GENERAL_LUN 8 +/* * UFS Protocol Information Unit related definitions */
@@ -129,10 +131,29 @@ enum desc_idn {QUERY_DESC_IDN_RFU_1 =0x6 ,QUERY_DESC_IDN_GEOMETRY =0x7 ,QUERY_DESC_IDN_POWER =0x8 , - QUERY_DESC_IDN_RFU_2 = 0x9, + QUERY_DESC_IDN_MAX, +}; + +enum desc_header_offset { +QUERY_DESC_LENGTH_OFFSET =0x00 , +QUERY_DESC_DESC_TYPE_OFFSET =0x01 , +}; + +enum ufs_desc_max_size { +QUERY_DESC_DEVICE_MAX_SIZE =0x1F , +QUERY_DESC_CONFIGURAION_MAX_SIZE =0x90 , +QUERY_DESC_UNIT_MAX_SIZE =0x23 , +QUERY_DESC_INTERCONNECT_MAX_SIZE =0x06 , +/* +* Max. 126 UNICODE characters (2 bytes per character) plus 2 bytes +* of descriptor header. +*/ +QUERY_DESC_STRING_MAX_SIZE =0xFE , +QUERY_DESC_GEOMETRY_MAZ_SIZE =0x44 , +QUERY_DESC_POWER_MAX_SIZE =0x62 , +QUERY_DESC_RFU_MAX_SIZE =0x00 , }; -#define UNIT_DESC_MAX_SIZE 0x22/* Unit descriptor parameters offsets in bytes*/ enum unit_desc_param { UNIT_DESC_PARAM_LEN = 0x0,
drivers/scsi/ufs/ufshcd.c +130 -37
203 lines not on this page · raw message
Signature · 4 lines
1.8.5.2 -- QUALCOMM ISRAEL, on behalf of Qualcomm Innovation Center, Inc. is a member of Code Aurora Forum, hosted by The Linux Foundation
PATCH v2 5/16 scsi: ufs: improve init sequence
In reply to Dolev RavivFrom: Sujit Reddy Thumma <[email protected]>
In ->hce_enable_notify() callback the vendor specific initialization may carry out additional DME configuration using UIC commands and hence the UIC command completion interrupt enable bit should be set before the post reset notification.
Add retries if the link-startup fails. This is required since due to hardware timing issues, the Uni-Pro link-startup might fail. The UFS HCI recovery procedure contradicts the Uni-Pro sequence. The UFS HCI specifies to resend DME_LINKSTARTUP command after IS.ULLS (link-lost interrupt) is received. The Uni-Pro specifies that if link-startup fails the link is in "down" state. The link-lost is indicated to the DME user only when the link is up. Hence, the UFS HCI recovery procedure of waiting for IS.ULLS and retrying link-startup may not work properly.
At the end, if detection fails, power off (disable clocks, regulators, phy) if the UFS device detection fails. This saves power while UFS device is not embedded into the system.
drivers/scsi/ufs/ufshcd.c +72 -27
@@ -62,6 +62,12 @@/* Task management command timeout */ #define TM_CMD_TIMEOUT 100/* msecs */ +/* maximum number of link-startup retries */ +#define DME_LINKSTARTUP_RETRIES 3 + +/* maximum number of reset retries before giving up */ +#define MAX_HOST_RESET_RETRIES 5 +/* Expose the flag value from utp_upiu_query.value */ #define MASK_QUERY_UPIU_FLAG_LOC 0xFF
@@ -137,6 +143,8 @@ 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);static int ufshcd_clear_tm_cmd (struct ufs_hba *hba,int tag); +static void ufshcd_hba_exit (struct ufs_hba *hba); +static int ufshcd_probe_hba (struct ufs_hba *hba);/* * ufshcd_wait_for_register - wait for register value to change
@@ -2043,6 +2051,9 @@ static int ufshcd_hba_enable(struct ufs_hba *hba)msleep (5 ); } +/* enable UIC related interrupts */ +ufshcd_enable_intr (hba,UIC_COMMAND_COMPL ); +if (hba->vops && hba->vops->hce_enable_notify) hba->vops->hce_enable_notify (hba,POST_CHANGE );
@@ -2058,23 +2069,33 @@ static int ufshcd_hba_enable(struct ufs_hba *hba)static int ufshcd_link_startup (struct ufs_hba *hba) {int ret; +int retries =DME_LINKSTARTUP_RETRIES ; -/* enable UIC related interrupts */ -ufshcd_enable_intr (hba,UIC_COMMAND_COMPL ); +do { +if (hba->vops && hba->vops->link_startup_notify) + hba->vops->link_startup_notify (hba,PRE_CHANGE ); -if (hba->vops && hba->vops->link_startup_notify) - hba->vops->link_startup_notify (hba,PRE_CHANGE ); + ret =ufshcd_dme_link_startup (hba); - ret =ufshcd_dme_link_startup (hba); -if (ret) - goto out; +/* check if device is detected by inter-connect layer */ +if (!ret && !ufshcd_is_device_present (hba)) { +dev_err (hba->dev,"%s: Device not present\n" , __func__); + ret = -ENXIO ; + goto out; + } + +/* +* DME link lost indication is only received when link is up, +* but we can't be sure if the link is up until link startup +* succeeds. So reset the local Uni-Pro and try again. +*/ +if (ret &&ufshcd_hba_enable (hba)) + goto out; + }while (ret && retries--); -/* check if device is detected by inter-connect layer */ -if (!ufshcd_is_device_present (hba)) { -dev_err (hba->dev,"%s: Device not present\n" , __func__); - ret = -ENXIO ; +if (ret) +/* failed to get the link up... retire */ goto out; - }/* Include any host controller configuration via UIC commands */ if (hba->vops && hba->vops->link_startup_notify) {
@@ -3139,7 +3160,6 @@ out:static int ufshcd_host_reset_and_restore (struct ufs_hba *hba) {int err; -async_cookie_t cookie;unsigned long flags;/* Reset the host controller */
@@ -3152,10 +3172,9 @@ static int ufshcd_host_reset_and_restore(struct ufs_hba *hba) goto out;/* Establish the link again and restore the device */ - cookie =async_schedule (ufshcd_async_scan, hba); -/* wait for async scan to be completed */ -async_synchronize_cookie (++cookie); -if (hba->ufshcd_state !=UFSHCD_STATE_OPERATIONAL ) + err =ufshcd_probe_hba (hba); + +if (!err && (hba->ufshcd_state !=UFSHCD_STATE_OPERATIONAL )) err = -EIO ; out:if (err)
@@ -3177,8 +3196,11 @@ static int ufshcd_reset_and_restore(struct ufs_hba *hba) {int err =0 ;unsigned long flags; +int retries =MAX_HOST_RESET_RETRIES ; - err =ufshcd_host_reset_and_restore (hba); +do { + err =ufshcd_host_reset_and_restore (hba); + }while (err && --retries);/* * After reset the door-bell might be cleared, complete
@@ -3243,13 +3265,13 @@ static int ufshcd_eh_host_reset_handler(struct scsi_cmnd *cmd) }/** -* ufshcd_async_scan - asynchronous execution for link startup -* @data: data pointer to pass to this function -* @cookie: cookie data +* ufshcd_probe_hba - probe hba to detect device and initialize +* @hba: per-adapter instance +* +* Execute link-startup and verify device initialization */ -static void ufshcd_async_scan (void *data,async_cookie_t cookie) +static int ufshcd_probe_hba (struct ufs_hba *hba) { -struct ufs_hba *hba = (struct ufs_hba *)data;int ret; ret =ufshcd_link_startup (hba);
@@ -3275,7 +3297,26 @@ static void ufshcd_async_scan(void *data, async_cookie_t cookie)pm_runtime_put_sync (hba->dev); } out: -return ; +/* +* 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)) +ufshcd_hba_exit (hba); + +return ret; +} + +/** +* ufshcd_async_scan - asynchronous execution for probing hba +* @data: data pointer to pass to this function +* @cookie: cookie data +*/ +static void ufshcd_async_scan (void *data,async_cookie_t cookie) +{ +struct ufs_hba *hba = (struct ufs_hba *)data; + +ufshcd_probe_hba (hba); }static struct scsi_host_template ufshcd_driver_template = {
@@ -3596,6 +3637,7 @@ static int ufshcd_hba_init(struct ufs_hba *hba)if (err) goto out_disable_vreg; + hba->is_powered = true; goto out; out_disable_vreg:
@@ -3608,9 +3650,12 @@ out:static void ufshcd_hba_exit (struct ufs_hba *hba) { -ufshcd_variant_hba_exit (hba); -ufshcd_setup_vreg (hba, false); -ufshcd_setup_clocks (hba, false); +if (hba->is_powered) { +ufshcd_variant_hba_exit (hba); +ufshcd_setup_vreg (hba, false); +ufshcd_setup_clocks (hba, false); + hba->is_powered = false; + } }/**
drivers/scsi/ufs/ufshcd.h +2 -0
@@ -228,6 +228,7 @@ struct ufs_hba_variant_ops {* @eh_flags: Error handling flags * @intr_mask: Interrupt Mask Bits * @ee_ctrl_mask: Exception event control mask +* @is_powered: flag to check if HBA is powered * @eh_work: Worker to handle UFS errors that require s/w attention * @eeh_work: Worker to handle exception events * @errors: HBA errors
@@ -283,6 +284,7 @@ struct ufs_hba {u32 eh_flags;u32 intr_mask;u16 ee_ctrl_mask; +bool is_powered;/* Work Queues */ struct work_struct eh_work;
Signature · 4 lines
1.8.5.2 -- QUALCOMM ISRAEL, on behalf of Qualcomm Innovation Center, Inc. is a member of Code Aurora Forum, hosted by The Linux Foundation
PATCH v2 6/16 scsi: ufs: Active Power Mode - configuring bActiveICCLevel
In reply to Dolev RavivFrom: Yaniv Gardi <[email protected]>
The maximum power consumption in active is determined by bActiveICCLevel. The configuration is done by reading max current supported by the regulators connected to VCC, VCCQ and VCCQ2 rails on the boards, and reading the current consumption levels from the device for each rails (vcc/vccq/vccq2) using power descriptor.
We configure the bActiveICCLevel attribute, with the max value that correspond to the minimum-of(VCC-current-level,VCCQ-current-level, VCCQ2-current-level).
In order to minimize resume latency, pre-fetch icc levels and reference clock during initialization and avoid reading them each link startup during resume.
drivers/scsi/ufs/ufs.h +26 -0
@@ -115,6 +115,7 @@ enum flag_idn {/* Attribute idn for Query requests */ enum attr_idn { +QUERY_ATTR_IDN_ACTIVE_ICC_LVL =0x03 ,QUERY_ATTR_IDN_BKOPS_STATUS =0x05 ,QUERY_ATTR_IDN_EE_CONTROL =0x0D ,QUERY_ATTR_IDN_EE_STATUS =0x0E ,
@@ -174,6 +175,31 @@ enum unit_desc_param { UNIT_DESC_PARAM_LARGE_UNIT_SIZE_M1 = 0x22, }; +/* bActiveICCLevel parameter current units */ +enum { +UFSHCD_NANO_AMP =0 , +UFSHCD_MICRO_AMP =1 , +UFSHCD_MILI_AMP =2 , +UFSHCD_AMP =3 , +}; + +#define POWER_DESC_MAX_SIZE 0x62 +#define POWER_DESC_MAX_ACTV_ICC_LVLS 16 + +/* Attribute bActiveICCLevel parameter bit masks definitions */ +#define ATTR_ICC_LVL_UNIT_OFFSET 14 +#define ATTR_ICC_LVL_UNIT_MASK (0x3 << ATTR_ICC_LVL_UNIT_OFFSET) +#define ATTR_ICC_LVL_VALUE_MASK 0x3FF + +/* Power descriptor parameters offsets in bytes */ +enum power_desc_param_offset { +PWR_DESC_LEN =0x0 , +PWR_DESC_TYPE =0x1 , +PWR_DESC_ACTIVE_LVLS_VCC_0 =0x2 , +PWR_DESC_ACTIVE_LVLS_VCCQ_0 =0x22 , +PWR_DESC_ACTIVE_LVLS_VCCQ2_0 =0x42 , +}; +/* Exception event mask values */ enum {MASK_EE_STATUS =0xFFFF ,
drivers/scsi/ufs/ufshcd.c +126 -0
141 lines not on this page · raw message
drivers/scsi/ufs/ufshcd.h +13 -0
@@ -196,6 +196,15 @@ struct ufs_hba_variant_ops { };/** +* struct ufs_init_prefetch - contains data that is pre-fetched once during +* initialization +* @icc_level: icc level which was read during initialization +*/ +struct ufs_init_prefetch { +u32 icc_level; +}; + +/** * struct ufs_hba - per adapter private structure * @mmio_base: UFSHCI base register address * @ucdl_base_addr: UFS Command Descriptor base address
@@ -229,6 +238,8 @@ struct ufs_hba_variant_ops {* @intr_mask: Interrupt Mask Bits * @ee_ctrl_mask: Exception event control mask * @is_powered: flag to check if HBA is powered +* @is_init_prefetch: flag to check if data was pre-fetched in initialization +* @init_prefetch_data: data pre-fetched during initialization * @eh_work: Worker to handle UFS errors that require s/w attention * @eeh_work: Worker to handle exception events * @errors: HBA errors
@@ -285,6 +296,8 @@ struct ufs_hba {u32 intr_mask;u16 ee_ctrl_mask;bool is_powered; +bool is_init_prefetch; +struct ufs_init_prefetch init_prefetch_data;/* Work Queues */ struct work_struct eh_work;
Signature · 4 lines
1.8.5.2 -- QUALCOMM ISRAEL, on behalf of Qualcomm Innovation Center, Inc. is a member of Code Aurora Forum, hosted by The Linux Foundation
PATCH v2 7/16 scsi: support well known logical units
In reply to Dolev RavivFrom: Subhash Jadavani <[email protected]>
REPORT LUNS command has "SELECT REPORT" field which controls what type of logical units to be reported by device server. According to UFS device standard, if this field is set to 0, REPORT LUNS would report only report standard logical units. If it's set to 1 then it would report only well known logical unit and if it's set to 2 then device would report both standard and well known logical units.
Some well-known logical units might not have scsi upper-layer drivers. In such cases, the runtime PM reference count increased during device enumeration will not be decreased, causing the parent device (host) to always be on even during idle.
This change allows the SCSI LLD (Low Level Driver) to choose which type of logical units should be detected.
drivers/scsi/scsi_scan.c +16 -0
@@ -802,6 +802,14 @@ static int scsi_add_lun(struct scsi_device *sdev, unsigned char *inq_result, }else { sdev->type = (inq_result[0 ] &0x1f ); sdev->removable = (inq_result[1 ] &0x80 ) >>7 ; + +/* +* some devices may respond with wrong type for +* well-known logical units. Force well-known type +* to enumerate them correctly. +*/ +if (scsi_is_wlun (sdev->lun) && (sdev->type !=TYPE_WLUN )) + sdev->type =TYPE_WLUN ; }switch (sdev->type) {
@@ -817,6 +825,7 @@ static int scsi_add_lun(struct scsi_device *sdev, unsigned char *inq_result,case TYPE_COMM :case TYPE_RAID :case TYPE_OSD : +case TYPE_WLUN : sdev->writeable =1 ;break ;case TYPE_ROM :
@@ -1412,6 +1421,13 @@ static int scsi_report_lun_scan(struct scsi_target *starget, int bflags,*/ memset (&scsi_cmd[1 ],0 ,5 ); +if (shost->report_wlus) +/* +* Set "SELECT REPORT" field to 0x2 which will make device to +* report well known logical units along with standard LUs. +*/ + scsi_cmd[2 ] =0x2 ; +/* * bytes 6 - 9: length of the command. */
drivers/scsi/scsi_sysfs.c +7 -0
@@ -1060,6 +1060,13 @@ int scsi_sysfs_add_sdev(struct scsi_device *sdev) } } +/* +* put runtime pm reference for well-known logical units, +* drivers are expected to _get_* again during probe. +*/ +if (scsi_is_wlun (sdev->lun)) +scsi_autopm_put_device (sdev); +return error; }
include/scsi/scsi.h +1 -0
@@ -332,6 +332,7 @@ static inline int scsi_status_is_good(int status)#define TYPE_ENCLOSURE 0x0d/* Enclosure Services Device */ #define TYPE_RBC 0x0e#define TYPE_OSD 0x11 +#define TYPE_WLUN 0x1e/* well-known logical unit */ #define TYPE_NO_LUN 0x7f/* SCSI protocols; these are taken from SPC-3 section 7.5 */
include/scsi/scsi_host.h +5 -0
@@ -675,6 +675,11 @@ struct Scsi_Host { unsigned no_write_same:1;/* +* Set "SELECT REPORT" field to allow detection of well known logical +* units along with standard LUs. +*/ + unsigned report_wlus:1; +/* * Optional work queue to be utilized by the transport */ char work_q_name[20 ];
Signature · 4 lines
1.8.5.2 -- QUALCOMM ISRAEL, on behalf of Qualcomm Innovation Center, Inc. is a member of Code Aurora Forum, hosted by The Linux Foundation
Re: [PATCH/RFC V2 07/16] scsi: support well known logical units
Quoted 26 lines · Dolev Raviv
From: Subhash Jadavani <[email protected]>
REPORT LUNS command has "SELECT REPORT" field which controls what type of logical units to be reported by device server. According to UFS device standard, if this field is set to 0, REPORT LUNS would report only report standard logical units. If it's set to 1 then it would report only well known logical unit and if it's set to 2 then device would report both standard and well known logical units.
Some well-known logical units might not have scsi upper-layer drivers. In such cases, the runtime PM reference count increased during device enumeration will not be decreased, causing the parent device (host) to always be on even during idle.This change allows the SCSI LLD (Low Level Driver) to choose which type of logical units should be detected.
Signed-off-by Subhash Jadavani <[email protected]>Signed-off-by Sujit Reddy Thumma <[email protected]>Signed-off-by Dolev Raviv <[email protected]>drivers/scsi/scsi_scan.c +0
-0@@ -802,6 +802,14 @@ static int scsi_add_lun(struct scsi_device *sdev, unsigned char *inq_result, }else {
drivers/scsi/scsi_scan.c static int scsi_add_lun(struct scsi_device *sdev, unsigned char *inq_result, · line 812
Quoted 10 lines
drivers/scsi/scsi_scan.c +8
-0sdev->type = (inq_result[0 ] &0x1f ); sdev->removable = (inq_result[1 ] &0x80 ) >>7 ; + +/* +* some devices may respond with wrong type for +* well-known logical units. Force well-known type +* to enumerate them correctly. +*/ +if (scsi_is_wlun (sdev->lun) && (sdev->type !=TYPE_WLUN )) + sdev->type =TYPE_WLUN ;
no need to test for TYPE_WLUN here.
drivers/scsi/scsi_scan.c static int scsi_add_lun(struct scsi_device *sdev, unsigned char *inq_result, · line 831
Quoted 9 lines
drivers/scsi/scsi_scan.c +1
-0switch (sdev->type) {@@ -817,6 +825,7 @@ static int scsi_add_lun(struct scsi_device *sdev, unsigned char *inq_result,case TYPE_COMM :case TYPE_RAID :case TYPE_OSD : +case TYPE_WLUN : sdev->writeable =1 ;break ;case TYPE_ROM :
This switch statements has been removed in 3.17-rc1, please rebase your series on top of it. While you're at it please make this patch, which introduceѕ core scsi changes first in the series.
drivers/scsi/scsi_scan.c static int scsi_report_lun_scan(struct scsi_target *starget, int bflags, · line 1429
Quoted 10 lines
drivers/scsi/scsi_scan.c +6
-0@@ -1412,6 +1421,13 @@ static int scsi_report_lun_scan(struct scsi_target *starget, int bflags,*/ memset (&scsi_cmd[1 ],0 ,5 ); +if (shost->report_wlus) +/* +* Set "SELECT REPORT" field to 0x2 which will make device to +* report well known logical units along with standard LUs. +*/ + scsi_cmd[2 ] =0x2 ;
So we're finally getting well known LUN support. I think we should key this off the SBC compliance level and not have the drivers opt in.
Quoted 5 lines
drivers/scsi/scsi_sysfs.c +0
-0
drivers/scsi/scsi_sysfs.c int scsi_sysfs_add_sdev(struct scsi_device *sdev) · line 1068
Quoted 9 lines
drivers/scsi/scsi_sysfs.c +6
-0@@ -1060,6 +1060,13 @@ int scsi_sysfs_add_sdev(struct scsi_device *sdev) } } +/* +* put runtime pm reference for well-known logical units, +* drivers are expected to _get_* again during probe. +*/ +if (scsi_is_wlun (sdev->lun)) +scsi_autopm_put_device (sdev);
Special casing the well known LUNs here seems wrong. Shouldn't we do this for any devices that don't get a driver attached to them?
Re: [PATCH/RFC V2 07/16] scsi: support well known logical units
Quoted 8 lines
Quoted 8 lines
Quoted 1 line
Quoted 1 line
Quoted 1 line
"Christoph" == Christoph Hellwig <[email protected]> writes:
+ /*
+ * put runtime pm reference for well-known logical units,
+ * drivers are expected to _get_* again during probe.
+ */
+ if (scsi_is_wlun(sdev->lun))
+scsi_autopm_put_device(sdev);
Christoph> Special casing the well known LUNs here seems wrong. Christoph> Shouldn't we do this for any devices that don't get a driver Christoph> attached to them?
That would also be my preference. Relying on WLU for this seems like a kludge.
Signature · 1 line
Martin K. Petersen Oracle Linux Engineering
PATCH v2 8/16 scsi: ufs: introduce well known logical unit in ufs
In reply to Dolev RavivFrom: Subhash Jadavani <[email protected]>
UFS device may have standard LUs and LUN id could be from 0x00 to 0x7F. UFS device specification use "Peripheral Device Addressing Format" (SCSI SAM-5) for standard LUs.
UFS device may also have the Well Known LUs (also referred as W-LU) which again could be from 0x00 to 0x7F. For W-LUs, UFS device specification only allows the "Extended Addressing Format" (SCSI SAM-5) which means the W-LUNs would start from 0xC100 onwards.
This means max. LUN number reported from UFS device could be 0xC17F hence this patch advertise the "max_lun" as 0xC17F which will allow SCSI mid layer to detect the W-LUs as well.
But once the W-LUs are detected, UFSHCD driver may get the commands with SCSI LUN id upto 0xC17F but UPIU LUN id field is only 8-bit wide so it requires the mapping of SCSI LUN id to UPIU LUN id. This patch also add support for this mapping.
drivers/scsi/ufs/ufs.h +20 -1
@@ -49,9 +49,28 @@#define UPIU_HEADER_DWORD (byte3, byte2, byte1, byte0)\ cpu_to_be32((byte3 << 24) | (byte2 << 16) |\ (byte1 << 8) | (byte0)) - +/* +* UFS device may have standard LUs and LUN id could be from 0x00 to +* 0x7F. Standard LUs use "Peripheral Device Addressing Format". +* UFS device may also have the Well Known LUs (also referred as W-LU) +* which again could be from 0x00 to 0x7F. For W-LUs, device only use +* the "Extended Addressing Format" which means the W-LUNs would be +* from 0xc100 (SCSI_W_LUN_BASE) onwards. +* This means max. LUN number reported from UFS device could be 0xC17F. +*/ +#define UFS_UPIU_MAX_UNIT_NUM_ID 0x7F +#define UFS_MAX_LUNS (SCSI_W_LUN_BASE + UFS_UPIU_MAX_UNIT_NUM_ID) +#define UFS_UPIU_WLUN_ID (1 << 7)#define UFS_UPIU_MAX_GENERAL_LUN 8 +/* 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 */
drivers/scsi/ufs/ufshcd.c +65 -20
149 lines not on this page · raw message
drivers/scsi/ufs/ufshcd.h +6 -1
@@ -124,7 +124,7 @@ struct ufshcd_lrb {int command_type;int task_tag; -unsigned int lun; +u8 lun;/* UPIU LUN id field is only 8-bit wide */ bool intr_cmd; };
@@ -266,6 +266,11 @@ 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;struct ufshcd_lrb *lrb;unsigned long lrb_in_use;
Signature · 4 lines
1.8.5.2 -- QUALCOMM ISRAEL, on behalf of Qualcomm Innovation Center, Inc. is a member of Code Aurora Forum, hosted by The Linux Foundation
PATCH v2 9/16 scsi: sd: Avoid sending medium write commands if device is write protected
In reply to Dolev RavivFrom: Sujit Reddy Thumma <[email protected]>
The SYNCHRONIZE_CACHE command is a medium write command and hence can fail when the device is write protected. Avoid sending such commands by making sure that write-cache-enable is disabled even though the device claim to support it.
drivers/scsi/sd.c +5 -1
@@ -185,7 +185,7 @@ cache_type_store(struct device *dev, struct device_attribute *attr,if (ct <0 )return -EINVAL ; rcd = ct &0x01 ?1 :0 ; - wce = ct &0x02 ?1 :0 ; + wce = (ct &0x02 ) && !sdkp->write_prot ?1 :0 ;if (sdkp->cache_override) { sdkp->WCE = wce;
@@ -2493,6 +2493,10 @@ sd_read_cache_type(struct scsi_disk *sdkp, unsigned char *buffer) sdkp->DPOFUA =0 ; } +/* No cache flush allowed for write protected devices */ +if (sdkp->WCE && sdkp->write_prot) + sdkp->WCE =0 ; +if (sdkp->first_scan || old_wce != sdkp->WCE || old_rcd != sdkp->RCD || old_dpofua != sdkp->DPOFUA) sd_printk(KERN_NOTICE, sdkp,
Signature · 4 lines
1.8.5.2 -- QUALCOMM ISRAEL, on behalf of Qualcomm Innovation Center, Inc. is a member of Code Aurora Forum, hosted by The Linux Foundation
PATCH v2 10/16 scsi: ufs: add UFS power management support
In reply to Dolev RavivFrom: Subhash Jadavani <[email protected]>
This patch adds support for UFS device and UniPro link power management during runtime/system PM.
Main idea is to define multiple UFS low power levels based on UFS device and UFS link power states. This would allow any specific platform or pci driver to choose the best suited low power level during runtime and system suspend based on their power goals.
bkops handlig:
To put the UFS device in sleep state when bkops is disabled, first query the bkops status from the device and enable bkops on device only if device needs time to perform the bkops.
START_STOP handling:
Before sending START_STOP_UNIT to the device well-known logical unit (w-lun) to make sure that the device w-lun unit attention condition is cleared.
Write protection:
UFS device specification allows LUs to be write protected, either permanently or power on write protected. If any LU is power on write protected and if the card is power cycled (by powering off VCCQ and/or VCC rails), LU's write protect status would be lost. So this means those LUs can be written now. To ensures that UFS device is power cycled only if the power on protect is not set for any of the LUs, check if power on write protect is set and if device is in sleep/power-off state & link in inactive state (Hibern8 or OFF state).
If none of the Logical Units on UFS device is power on write protected then all UFS device power rails (VCC, VCCQ & VCCQ2) can be turned off if UFS device is in power-off state and UFS link is in OFF state. But current implementation would disable all device power rails even if UFS link is not in OFF state.
drivers/scsi/ufs/ufs.h +35 -1
@@ -129,6 +129,7 @@ enum {/* Flag idn for Query Requests*/ enum flag_idn {QUERY_FLAG_IDN_FDEVICEINIT =0x01 , +QUERY_FLAG_IDN_PWR_ON_WPE =0x03 ,QUERY_FLAG_IDN_BKOPS_EN =0x04 , };
@@ -194,6 +195,18 @@ enum unit_desc_param { UNIT_DESC_PARAM_LARGE_UNIT_SIZE_M1 = 0x22, }; +/* +* Logical Unit Write Protect +* 00h: LU not write protected +* 01h: LU write protected when fPowerOnWPEn =1 +* 02h: LU permanently write protected when fPermanentWPEn =1 +*/ +enum ufs_lu_wp_type { +UFS_LU_NO_WP =0x00 , +UFS_LU_POWER_ON_WP =0x01 , +UFS_LU_PERM_WP =0x02 , +}; +/* bActiveICCLevel parameter current units */ enum {UFSHCD_NANO_AMP =0 ,
@@ -226,11 +239,12 @@ enum { };/* Background operation status */ -enum { +enum bkops_status {BKOPS_STATUS_NO_OP =0x0 ,BKOPS_STATUS_NON_CRITICAL =0x1 ,BKOPS_STATUS_PERF_IMPACT =0x2 ,BKOPS_STATUS_CRITICAL =0x3 , +BKOPS_STATUS_MAX =BKOPS_STATUS_CRITICAL , };/* UTP QUERY Transaction Specific Fields OpCode */
@@ -291,6 +305,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
@@ -437,6 +459,12 @@ struct ufs_query_res {#define UFS_VREG_VCCQ2_MIN_UV 1650000/* uV */ #define UFS_VREG_VCCQ2_MAX_UV 1950000/* uV */ +/* +* VCCQ & VCCQ2 current requirement when UFS device is in sleep state +* and link is in Hibern8 state. +*/ +#define UFS_VREG_LPM_LOAD_UA 1000/* uA */ +struct ufs_vreg {struct regulator *reg;const char *name;
@@ -453,4 +481,10 @@ struct ufs_vreg_info { struct ufs_vreg *vccq2; }; +struct ufs_dev_info { +bool f_power_on_wp_en; +/* Keeps information if any of the LU is power on write protected */ +bool is_lu_power_on_wp; +}; +#endif /* End of Header */
drivers/scsi/ufs/ufshcd-pci.c +10 -35
81 lines not on this page · raw message
drivers/scsi/ufs/ufshcd-pltfrm.c +15 -45
102 lines not on this page · raw message
drivers/scsi/ufs/ufshcd.c +824 -85
1,143 lines not on this page · raw message
drivers/scsi/ufs/ufshcd.h +70 -5
130 lines not on this page · raw message
drivers/scsi/ufs/ufshci.h +6 -3
@@ -124,8 +124,11 @@ enum {#define CONTROLLER_FATAL_ERROR UFS_BIT(16)#define SYSTEM_BUS_FATAL_ERROR UFS_BIT(17) -#define UFSHCD_UIC_MASK (UIC_COMMAND_COMPL |\ - UIC_POWER_MODE) +#define UFSHCD_UIC_PWR_MASK (UIC_HIBERNATE_ENTER |\ + UIC_HIBERNATE_EXIT |\ + UIC_POWER_MODE) + +#define UFSHCD_UIC_MASK (UIC_COMMAND_COMPL | UFSHCD_UIC_PWR_MASK)#define UFSHCD_ERROR_MASK (UIC_ERROR |\ DEVICE_FATAL_ERROR |\
@@ -210,7 +213,7 @@ enum {#define UIC_GET_ATTR_ID (v) (((v) >> 16) & 0xFFFF)/* UIC Commands */ -enum { +enum uic_cmd_dme {UIC_CMD_DME_GET =0x01 ,UIC_CMD_DME_SET =0x02 ,UIC_CMD_DME_PEER_GET =0x03 ,
Signature · 4 lines
1.8.5.2 -- QUALCOMM ISRAEL, on behalf of Qualcomm Innovation Center, Inc. is a member of Code Aurora Forum, hosted by The Linux Foundation
RE: [PATCH/RFC V2 10/16] scsi: ufs: add UFS power management support
Quoted 784 lines
-----Original Message-----
From: [email protected] [mailto:[email protected]] On Behalf Of Dolev Raviv
Sent: Thursday, August 14, 2014 9:31 PM
To: [email protected]; [email protected]
Cc: [email protected]; [email protected]; [email protected]; [email protected]; Subhash Jadavani; Dolev Raviv; Sujit Reddy Thumma
Subject: [PATCH/RFC V2 10/16] scsi: ufs: add UFS power management supportFrom: Subhash Jadavani <[email protected]>
This patch adds support for UFS device and UniPro link power management during runtime/system PM.
Main idea is to define multiple UFS low power levels based on UFS device and UFS link power states. This would allow any specific platform or pci driver to choose the best suited low power level during runtime and system suspend based on their power goals.
bkops handlig:
To put the UFS device in sleep state when bkops is disabled, first query the bkops status from the device and enable bkops on device only if device needs time to perform the bkops.
START_STOPhandling:
Before sendingSTART_STOP_UNITto the device well-known logical unit (w-lun) to make sure that the device w-lun unit attention condition is cleared.Write protection:
UFS device specification allows LUs to be write protected, either permanently or power on write protected. If any LU is power on write protected and if the card is power cycled (by powering off VCCQ and/or VCC rails), LU's write protect status would be lost. So this means those LUs can be written now. To ensures that UFS device is power cycled only if the power on protect is not set for any of the LUs, check if power on write protect is set and if device is in sleep/power-off state & link in inactive state (Hibern8 or OFF state). If none of the Logical Units on UFS device is power on write protected then all UFS device power rails (VCC, VCCQ & VCCQ2) can be turned off if UFS device is in power-off state and UFS link is in OFF state. But current implementation would disable all device power rails even if UFS link is not in OFF state.Signed-off-by Subhash Jadavani <[email protected]>Signed-off-by Dolev Raviv <[email protected]>Signed-off-by Sujit Reddy Thumma <[email protected]>drivers/scsi/ufs/ufs.h index renamed from drivers/scsi/ufs/ufs.h +0
-0bcc3a7f..2a82959 100644
drivers/scsi/ufs/ufs.h +14
-1@@ -129,6 +129,7 @@ enum {/* Flag idn for Query Requests*/ enum flag_idn {QUERY_FLAG_IDN_FDEVICEINIT =0x01 , +QUERY_FLAG_IDN_PWR_ON_WPE =0x03 ,QUERY_FLAG_IDN_BKOPS_EN =0x04 , };@@ -194,6 +195,18 @@ enum unit_desc_param { UNIT_DESC_PARAM_LARGE_UNIT_SIZE_M1 = 0x22, }; +/* +* Logical Unit Write Protect +* 00h: LU not write protected +* 01h: LU write protected when fPowerOnWPEn =1 +* 02h: LU permanently write protected when fPermanentWPEn =1 */ enum +ufs_lu_wp_type { +UFS_LU_NO_WP =0x00 , +UFS_LU_POWER_ON_WP =0x01 , +UFS_LU_PERM_WP =0x02 , +}; +/* bActiveICCLevel parameter current units */ enum {UFSHCD_NANO_AMP =0 ,@@ -226,11 +239,12 @@ enum { };/* Background operation status */ -enum { +enum bkops_status {BKOPS_STATUS_NO_OP =0x0 ,BKOPS_STATUS_NON_CRITICAL =0x1 ,BKOPS_STATUS_PERF_IMPACT =0x2 ,BKOPS_STATUS_CRITICAL =0x3 , +BKOPS_STATUS_MAX =BKOPS_STATUS_CRITICAL , }; /* UTP QUERY Transaction Specific Fields OpCode */ @@ -291,6 +305,14 @@enum {
drivers/scsi/ufs/ufs.h +20
-0UPIU_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 @@ -437,6 +459,12 @@ struct ufs_query_res {#define UFS_VREG_VCCQ2_MIN_UV 1650000/* uV */ #define UFS_VREG_VCCQ2_MAX_UV 1950000/* uV */ +/* +* VCCQ & VCCQ2 current requirement when UFS device is in sleep state +* and link is in Hibern8 state. +*/ +#define UFS_VREG_LPM_LOAD_UA 1000/* uA */ +struct ufs_vreg {struct regulator *reg;const char *name;@@ -453,4 +481,10 @@ struct ufs_vreg_info { struct ufs_vreg *vccq2; }; +struct ufs_dev_info { +bool f_power_on_wp_en; +/* Keeps information if any of the LU is power on write protected */ +bool is_lu_power_on_wp; +}; +#endif /* End of Header */ drivers/scsi/ufs/ufshcd-pci.c index renamed from drivers/scsi/ufs/ufshcd-pci.c +0
-01aac2ef..b8f7774 100644
drivers/scsi/ufs/ufshcd-pci.c +10
-35@@ -43,34 +43,24 @@* @pdev: pointer to PCI device handle * @state: power state * -* Returns -ENOSYS +* Returns 0 if successful +* Returns non-zero otherwise */ static int ufshcd_pci_suspend (struct device *dev) { -/* -* TODO: -* 1. Call ufshcd_suspend -* 2. Do bus specific power management -*/ - -return -ENOSYS ; +return ufshcd_system_suspend (dev_get_drvdata (dev)); }/** * ufshcd_pci_resume - resume power management function * @pdev: pointer to PCI device handle * -* Returns -ENOSYS +* Returns 0 if successful +* Returns non-zero otherwise */ static int ufshcd_pci_resume (struct device *dev) { -/* -* TODO: -* 1. Call ufshcd_resume. -* 2. Do bus specific wake up -*/ - -return -ENOSYS ; +return ufshcd_system_resume (dev_get_drvdata (dev)); }#else #define ufshcd_pci_suspend NULL@@ -80,30 +70,15 @@ static int ufshcd_pci_resume(struct device *dev)#ifdef CONFIG_PM_RUNTIME static int ufshcd_pci_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_pci_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_pci_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_pci_runtime_suspend NULL@@ -117,7 +92,7 @@ static int ufshcd_pci_runtime_idle(struct device *dev)*/ static void ufshcd_pci_shutdown (struct pci_dev *pdev) { -ufshcd_hba_stop ((struct ufs_hba *)pci_get_drvdata (pdev)); +ufshcd_shutdown ((struct ufs_hba *)pci_get_drvdata (pdev)); }/** drivers/scsi/ufs/ufshcd-pltfrm.c +12
-45@@ -216,45 +216,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@@ -264,30 +243,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@@ -295,6 +259,11 @@ static int ufshcd_pltfrm_runtime_idle(struct device *dev)#define ufshcd_pltfrm_runtime_idle NULL#endif /* CONFIG_PM_RUNTIME */ +static void ufshcd_pltfrm_shutdown (struct platform_device *pdev) { +ufshcd_shutdown ((struct ufs_hba *)platform_get_drvdata (pdev)); } +/** * ufshcd_pltfrm_probe - probe routine of the driver * @pdev: pointer to Platform device handle @@ -395,6 +364,7 @@ static const struct
dev_pm_opsufshcd_dev_pm_ops= { static structplatform_driverufshcd_pltfrm_driver= {drivers/scsi/ufs/ufshcd-pltfrm.c +1
-0.probe = ufshcd_pltfrm_probe, .remove = ufshcd_pltfrm_remove, + .shutdown = ufshcd_pltfrm_shutdown, .driver = { .name = "ufshcd", .owner = THIS_MODULE,drivers/scsi/ufs/ufshcd.c index renamed from drivers/scsi/ufs/ufshcd.c +0
-0876d210..fac71c9 100644
drivers/scsi/ufs/ufshcd.c +30
-1@@ -44,7 +44,6 @@#define UFSHCD_ENABLE_INTRS (UTP_TRANSFER_REQ_COMPL |\ UTP_TASK_REQ_COMPL |\ - UIC_POWER_MODE |\ UFSHCD_ERROR_MASK)/* UIC command timeout, unit: ms */ #define UIC_CMD_TIMEOUT 500@@ -138,12 +137,63 @@ 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_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 intufshcd_reset_and_restore(struct ufs_hba *hba); static intufshcd_clear_tm_cmd(struct ufs_hba *hba, int tag); static voidufshcd_hba_exit(struct ufs_hba *hba); static intufshcd_probe_hba(struct ufs_hba *hba);drivers/scsi/ufs/ufshcd.c +23
-3+static int ufshcd_host_reset_and_restore (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 @@ -1778,44 +1828,48 @@ out: EXPORT_SYMBOL_GPL(ufshcd_dme_get_attr); /** -* ufshcd_uic_change_pwr_mode - Perform the UIC power mode chage -* using DME_SET primitives. +* ufshcd_uic_pwr_ctrl - executes UIC commands (which affects the link +power +* state) and waits for it to take effect. +* * @hba: per adapter instance -* @mode: powr mode value +* @cmd: UIC command to execute +* +* DME operations like DME_SET(PA_PWRMODE), DME_HIBERNATE_ENTER &
+ *DME_HIBERNATE_EXITcommands take some time to take its effect on
+ both host
+ * and device UniPro link and hence it's final completion would be
+ indicated by
+ * dedicated status bits in Interrupt Status register (UPMS, UHES,
+ UHXS) in
+ * addition to normal UIC command completion Status (UCCS). This
+ function only
+ * returns after the relevant status bits indicate the completion. *
* Returns 0 on success, non-zero value on failure
*/drivers/scsi/ufs/ufshcd.c +130
-19-static int ufshcd_uic_change_pwr_mode (struct ufs_hba *hba,u8 mode) +int ufshcd_uic_pwr_ctrl (struct ufs_hba *hba,struct uic_command *cmd) { -struct uic_command uic_cmd = {0 }; -struct completion pwr_done; +struct completion uic_async_done;unsigned long flags;u8 status;int ret; - uic_cmd.command =UIC_CMD_DME_SET ; - uic_cmd.argument1 =UIC_ARG_MIB (PA_PWRMODE ); - uic_cmd.argument3 = mode; -init_completion (&pwr_done); -mutex_lock (&hba->uic_cmd_mutex); +init_completion (&uic_async_done);spin_lock_irqsave (hba->host->host_lock, flags); - hba->pwr_done = &pwr_done; + hba->uic_async_done = &uic_async_done;spin_unlock_irqrestore (hba->host->host_lock, flags); - ret =__ufshcd_send_uic_cmd (hba, &uic_cmd); + + ret =__ufshcd_send_uic_cmd (hba, cmd);if (ret) {dev_err (hba->dev, -"pwr mode change with mode 0x%x uic error %d\n" , - mode, ret); +"pwr ctrl cmd 0x%x with mode 0x%x uic error %d\n" , + cmd->command, cmd->argument3, ret); goto out; } -if (!wait_for_completion_timeout (hba->pwr_done, +if (!wait_for_completion_timeout (hba->uic_async_done,msecs_to_jiffies (UIC_CMD_TIMEOUT ))) {dev_err (hba->dev, -"pwr mode change with mode 0x%x completion timeout\n" , - mode); +"pwr ctrl cmd 0x%x with mode 0x%x completion timeout\n" , + cmd->command, cmd->argument3); ret = -ETIMEDOUT ; goto out; }@@ -1823,19 +1877,62 @@ static int ufshcd_uic_change_pwr_mode(struct ufs_hba *hba, u8 mode) status =ufshcd_get_upmcrs (hba);if (status !=PWR_LOCAL ) {dev_err (hba->dev, -"pwr mode change failed, host umpcrs:0x%x\n" , - status); +"pwr ctrl cmd 0x%0x failed, host umpcrs:0x%x\n" , + cmd->command, status); ret = (status !=PWR_OK ) ? status :-1 ; } out:spin_lock_irqsave (hba->host->host_lock, flags); - hba->pwr_done =NULL ; + hba->uic_async_done =NULL ;spin_unlock_irqrestore (hba->host->host_lock, flags);mutex_unlock (&hba->uic_cmd_mutex);return ret; }/** +* ufshcd_uic_change_pwr_mode - Perform the UIC power mode chage +* using DME_SET primitives. +* @hba: per adapter instance +* @mode: powr mode value +* +* Returns 0 on success, non-zero value on failure */ static int +ufshcd_uic_change_pwr_mode (struct ufs_hba *hba,u8 mode) { +struct uic_command uic_cmd = {0 }; + + uic_cmd.command =UIC_CMD_DME_SET ; + uic_cmd.argument1 =UIC_ARG_MIB (PA_PWRMODE ); + uic_cmd.argument3 = mode; + +return ufshcd_uic_pwr_ctrl (hba, &uic_cmd); } + +static int ufshcd_uic_hibern8_enter (struct ufs_hba *hba) { +struct uic_command uic_cmd = {0 }; + + uic_cmd.command =UIC_CMD_DME_HIBER_ENTER ; + +return ufshcd_uic_pwr_ctrl (hba, &uic_cmd); } + +static int ufshcd_uic_hibern8_exit (struct ufs_hba *hba) { +struct uic_command uic_cmd = {0 }; +int ret; + + uic_cmd.command =UIC_CMD_DME_HIBER_EXIT ; + ret =ufshcd_uic_pwr_ctrl (hba, &uic_cmd); +if (ret) { +ufshcd_set_link_off (hba); + ret =ufshcd_host_reset_and_restore (hba); + } + +return ret; +} + +/** * ufshcd_config_max_pwr_mode - Set & Change power mode with * maximum capability attribute information. * @hba: per adapter instance @@ -2034,6 +2131,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 );@@ -2066,7 +2166,7 @@ static int ufshcd_hba_enable(struct ufs_hba *hba) }/* enable UIC related interrupts */ -ufshcd_enable_intr (hba,UIC_COMMAND_COMPL ); +ufshcd_enable_intr (hba,UFSHCD_UIC_MASK );if (hba->vops && hba->vops->hce_enable_notify) hba->vops->hce_enable_notify(hba, POST_CHANGE); @@ -2195,6 +2295,62 @@ static void ufshcd_set_queue_depth(struct scsi_device *sdev) scsi_activate_tcq(sdev, lun_qdepth); } +/* +* ufshcd_get_lu_wp - returns the "b_lu_write_protect" from UNIT +DESCRIPTOR +* @hba: per-adapter instance +* @lun: UFS device lun id +* @b_lu_write_protect: pointer to buffer to hold the LU's write +protect info +* +* Returns 0 in case of success and b_lu_write_protect status would be +returned +* @b_lu_write_protect parameter. +* Returns -ENOTSUPP if reading b_lu_write_protect is not supported. +* Returns -EINVAL in case of invalid parameters passed to this function. +*/ +static int ufshcd_get_lu_wp (struct ufs_hba *hba, +u8 lun, +u8 *b_lu_write_protect) +{ +int ret; + +if (!b_lu_write_protect) + ret = -EINVAL ; +/* +* According to UFS device spec, RPMB LU can't be write +* protected so skip reading bLUWriteProtect parameter for +* it. For other W-LUs, UNIT DESCRIPTOR is not available. +*/ +else if (lun >=UFS_UPIU_MAX_GENERAL_LUN ) + ret = -ENOTSUPP ; +else + ret =ufshcd_read_unit_desc_param (hba, + lun, +UNIT_DESC_PARAM_LU_WR_PROTECT , + b_lu_write_protect, +sizeof (*b_lu_write_protect)); +return ret; +} + +/** +* ufshcd_get_lu_power_on_wp_status - get LU's power on write protect +* status +* @hba: per-adapter instance +* @sdev: pointer to SCSI device +* +*/ +static inline void ufshcd_get_lu_power_on_wp_status (struct ufs_hba *hba, +struct scsi_device *sdev) +{ +if (hba->dev_info.f_power_on_wp_en && + !hba->dev_info.is_lu_power_on_wp) { +u8 b_lu_write_protect; + +if (!ufshcd_get_lu_wp (hba,ufshcd_scsi_to_upiu_lun (sdev->lun), + &b_lu_write_protect) && + (b_lu_write_protect ==UFS_LU_POWER_ON_WP )) + hba->dev_info.is_lu_power_on_wp = true; + } +} +/** * ufshcd_slave_alloc - handle initial SCSI device configurations * @sdev: pointer to SCSI device @@ -2221,6 +2377,21 @@ static int ufshcd_slave_alloc(struct scsi_device *sdev)ufshcd_set_queue_depth (sdev); +ufshcd_get_lu_power_on_wp_status (hba, sdev); + +/* +* 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 ;}
drivers/scsi/ufs/ufshcd.c +2
-2@@ -2451,8 +2622,8 @@ static void ufshcd_uic_cmd_compl(struct ufs_hba *hba, u32 intr_status)complete (&hba->active_uic_cmd->done); } -if ((intr_status &UIC_POWER_MODE ) && hba->pwr_done) -complete (hba->pwr_done); +if ((intr_status &UFSHCD_UIC_PWR_MASK ) && hba->uic_async_done) +complete (hba->uic_async_done); }/**
drivers/scsi/ufs/ufshcd.c +42
-11@@ -2664,33 +2835,62 @@ static inline int ufshcd_get_bkops_status(struct ufs_hba *hba, u32 *status) }/** -* ufshcd_urgent_bkops - handle urgent bkops exception event +* ufshcd_bkops_ctrl - control the auto bkops based on current bkops +status * @hba: per-adapter instance +* @status: bkops_status value * -* Enable fBackgroundOpsEn flag in the device to permit background -* operations. +* Read the bkops_status from the UFS device and Enable +fBackgroundOpsEn +* flag in the device to permit background operations if the device +* bkops_status is greater than or equal to "status" argument passed to +* this function, disable otherwise. +* +* Returns 0 for success, non-zero in case of failure. +* +* NOTE: Caller of this function can check the +"hba->auto_bkops_enabled" flag +* to know whether auto bkops is enabled or disabled after this +function +* returns control to it. */ -static int ufshcd_urgent_bkops (struct ufs_hba *hba) +static int ufshcd_bkops_ctrl (struct ufs_hba *hba, +enum bkops_status status) {int err; -u32 status =0 ; +u32 curr_status =0 ; - err =ufshcd_get_bkops_status (hba, &status); + err =ufshcd_get_bkops_status (hba, &curr_status);if (err) {dev_err (hba->dev,"%s: failed to get BKOPS status %d\n" , __func__, err); goto out; + }else if (curr_status >BKOPS_STATUS_MAX ) { +dev_err (hba->dev,"%s: invalid BKOPS status %d\n" , + __func__, curr_status); + err = -EINVAL ; + goto out; } - status = status &0xF ; - -/* handle only if status indicates performance impact or critical */ -if (status >=BKOPS_STATUS_PERF_IMPACT ) +if (curr_status >= status) err =ufshcd_enable_auto_bkops (hba); +else + err =ufshcd_disable_auto_bkops (hba); out:return err; } +/** +* ufshcd_urgent_bkops - handle urgent bkops exception event +* @hba: per-adapter instance +* +* Enable fBackgroundOpsEn flag in the device to permit background +* operations. +* +* If BKOPs is enabled, this function returns 0, 1 if the bkops in not +enabled +* and negative error value for any other failure. +*/ +static int ufshcd_urgent_bkops (struct ufs_hba *hba) { +return ufshcd_bkops_ctrl (hba,BKOPS_STATUS_PERF_IMPACT ); } +static inline int ufshcd_get_ee_status (struct ufs_hba *hba,u32 *status) { return ufshcd_query_attr(hba, UPIU_QUERY_OPCODE_READ_ATTR, @@ -2722,7 +2922,7 @@ static void ufshcd_exception_event_handler(struct
work_struct*work)drivers/scsi/ufs/ufshcd.c +24
-6status &= hba->ee_ctrl_mask;if (status &MASK_EE_URGENT_BKOPS ) { err =ufshcd_urgent_bkops (hba); -if (err) +if (err <0 )dev_err (hba->dev,"%s: failed to handle urgent bkops %d\n" , __func__, err); }@@ -3441,7 +3641,8 @@ static int ufshcd_probe_hba(struct ufs_hba *hba)if (ret) goto out; -ufshcd_config_max_pwr_mode (hba); +/* UniPro link is active now */ +ufshcd_set_link_active (hba); ret =ufshcd_verify_dev_init (hba);if (ret)@@ -3451,14 +3652,29 @@ static int ufshcd_probe_hba(struct ufs_hba *hba)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 ; + hba->wlun_dev_clr_ua = true; + +ufshcd_config_max_pwr_mode (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) { +bool flag; + +/* clear any previous UFS device information */ +memset (&hba->dev_info,0 ,sizeof (hba->dev_info)); +if (!ufshcd_query_flag (hba,UPIU_QUERY_OPCODE_READ_FLAG , +QUERY_FLAG_IDN_PWR_ON_WPE , &flag)) + hba->dev_info.f_power_on_wp_en = flag; -/* If we are in error handling context no need to scan the host */ -if (!ufshcd_eh_in_progress (hba)) {if (!hba->is_init_prefetch)ufshcd_init_icc_levels (hba); -scsi_scan_host (hba->host);pm_runtime_put_sync (hba->dev); }@@ -3471,8 +3687,10 @@ 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) { +pm_runtime_put_sync (hba->dev);ufshcd_hba_exit (hba); + }return ret; }@@ -3507,6 +3725,42 @@ static struct scsi_host_template ufshcd_driver_template = { .can_queue = UFSHCD_CAN_QUEUE,
drivers/scsi/ufs/ufshcd.c static struct scsi_host_template · line 3736
Quoted 10 lines
drivers/scsi/ufs/ufshcd.c +8
-0}; +static int ufshcd_config_vreg_load (struct device *dev,struct ufs_vreg *vreg, +int ua) +{ +int ret =0 ; +struct regulator *reg = vreg->reg; +const char *name = vreg->name; + +BUG_ON (!vreg);
For the UFS host controller driver, which doesn't have the ufs_vreg structure for vcc and vccq, this function can cause kernel panic due to this BUG_ON(). Any reason to put a BUG_ON here instead of a NULL pointer checking?
Thanks
Chuanxiao
Quoted 877 lines
drivers/scsi/ufs/ufshcd.c +29
-11+ + ret =regulator_set_optimum_mode (reg, ua); +if (ret >=0 ) { +/* +* regulator_set_optimum_mode() returns new regulator +* mode upon success. +*/ + ret =0 ; + }else { +dev_err (dev,"%s: %s set optimum mode(ua=%d) failed, err=%d\n" , + __func__, name, ua, ret); + } + +return ret; +} + +static inline int ufshcd_config_vreg_lpm (struct ufs_hba *hba, +struct ufs_vreg *vreg) +{ +return ufshcd_config_vreg_load (hba->dev, vreg,UFS_VREG_LPM_LOAD_UA ); +} + +static inline int ufshcd_config_vreg_hpm (struct ufs_hba *hba, +struct ufs_vreg *vreg) +{ +return ufshcd_config_vreg_load (hba->dev, vreg, vreg->max_uA); } +static int ufshcd_config_vreg (struct device *dev,struct ufs_vreg *vreg,bool on) {@@ -3527,18 +3781,9 @@ static int ufshcd_config_vreg(struct device *dev, } uA_load = on ? vreg->max_uA :0 ; - ret =regulator_set_optimum_mode (reg, uA_load); -if (ret >=0 ) { -/* -* regulator_set_optimum_mode() returns new regulator -* mode upon success. -*/ - ret =0 ; - }else { -dev_err (dev,"%s: %s set optimum mode(uA_load=%d) failed, err=%d\n", - __func__, name, uA_load, ret); + ret = ufshcd_config_vreg_load(dev, vreg, uA_load); +if (ret) goto out; - } } out:drivers/scsi/ufs/ufshcd.c +5
-1return ret;@@ -3654,7 +3899,8 @@ out:return ret; } -static int ufshcd_setup_clocks (struct ufs_hba *hba,bool on) +static int __ufshcd_setup_clocks (struct ufs_hba *hba,bool on, +bool skip_ref_clk) {int ret =0 ;struct ufs_clk_info *clki;@@ -3665,6 +3911,9 @@ static int ufshcd_setup_clocks(struct ufs_hba *hba, bool on) list_for_each_entry(clki, head, list) {if (!IS_ERR_OR_NULL (clki->clk)) { +if (skip_ref_clk && !strcmp (clki->name,"ref_clk" )) +continue ; +if (on && !clki->enabled) { ret =clk_prepare_enable (clki->clk);drivers/scsi/ufs/ufshcd.c +33
-1if (ret) {@@ -3690,6 +3939,11 @@ out:return ret; } +static int ufshcd_setup_clocks (struct ufs_hba *hba,bool on) { +return __ufshcd_setup_clocks (hba, on, false); } +static int ufshcd_init_clocks (struct ufs_hba *hba) {int ret =0 ;@@ -3828,68 +4082,514 @@ static void ufshcd_hba_exit(struct ufs_hba *hba) } } +static int +ufshcd_send_request_sense (struct ufs_hba *hba,struct scsi_device *sdp) +{ +unsigned char cmd[6 ] = {REQUEST_SENSE , +0 , +0 , +0 , +SCSI_SENSE_BUFFERSIZE , +0 }; +char *buffer; +int ret; + + buffer =kzalloc (SCSI_SENSE_BUFFERSIZE ,GFP_KERNEL ); +if (!buffer) { + ret = -ENOMEM ; + goto out; + } + + ret =scsi_execute_req_flags (sdp, cmd,DMA_FROM_DEVICE , buffer, +SCSI_SENSE_BUFFERSIZE ,NULL , +msecs_to_jiffies (1000 ),3 ,NULL ,REQ_PM ); +if (ret) +pr_err ("%s: failed with err %d\n" , __func__, ret); + +kfree (buffer); +out: +return ret; +} +/** -* 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 */drivers/scsi/ufs/ufshcd.c +28
-7-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 ; + +/* +* If scsi commands fail, the scsi mid-layer schedules scsi error- +* handling, which would wait for host to be resumed. Since we know +* we are functional while we are here, skip host resume in error +* handling context. +*/ + hba->host->eh_noresume =1 ; +if (hba->wlun_dev_clr_ua) { + ret =ufshcd_send_request_sense (hba, sdp); +if (ret) + goto out; +/* Unit attention condition is cleared now */ + hba->wlun_dev_clr_ua = false; + } + + 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
drivers/scsi/ufs/ufshcd.c +333
-19+* 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); + } + } -return -ENOSYS ; +if (!ret) + hba->curr_dev_pwr_mode = pwr_mode; +out: + hba->host->eh_noresume =0 ; +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; +} + +static void ufshcd_vreg_set_lpm (struct ufs_hba *hba) { +/* +* If UFS device is either in UFS_Sleep turn off VCC rail to save some +* power. +* +* If UFS device and link is in OFF state, all power supplies (VCC, +* VCCQ, VCCQ2) can be turned off if power on write protect is not +* required. If UFS link is inactive (Hibern8 or OFF state) and device +* is in sleep state, put VCCQ & VCCQ2 rails in LPM mode. +* +* 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_is_link_off (hba) && + !hba->dev_info.is_lu_power_on_wp) { +ufshcd_setup_vreg (hba, false); + }else if (!ufshcd_is_ufs_dev_active (hba)) { +ufshcd_toggle_vreg (hba->dev, hba->vreg_info.vcc, false); +if (!ufshcd_is_link_active (hba)) { +ufshcd_config_vreg_lpm (hba, hba->vreg_info.vccq); +ufshcd_config_vreg_lpm (hba, hba->vreg_info.vccq2); + } + } +} + +static int ufshcd_vreg_set_hpm (struct ufs_hba *hba) { +int ret =0 ; + +if (ufshcd_is_ufs_dev_poweroff (hba) &&ufshcd_is_link_off (hba) && + !hba->dev_info.is_lu_power_on_wp) { + ret =ufshcd_setup_vreg (hba, true); + }else if (!ufshcd_is_ufs_dev_active (hba)) { + ret =ufshcd_toggle_vreg (hba->dev, hba->vreg_info.vcc, true); +if (!ret && !ufshcd_is_link_active (hba)) { + ret =ufshcd_config_vreg_hpm (hba, hba->vreg_info.vccq); +if (ret) + goto vcc_disable; + ret =ufshcd_config_vreg_hpm (hba, hba->vreg_info.vccq2); +if (ret) + goto vccq_lpm; + } + } + goto out; + +vccq_lpm: +ufshcd_config_vreg_lpm (hba, hba->vreg_info.vccq); +vcc_disable: +ufshcd_toggle_vreg (hba->dev, hba->vreg_info.vcc, false); +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: desired low power operation type * -* Returns -ENOSYS +* 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). +* +* If this function is called during shutdown, it will make sure that +* both UFS device and UFS link is powered off. +* +* 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; + + hba->pm_op_in_progress =1 ; +if (!ufshcd_is_shutdown_pm (pm_op)) { + 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); + }else { + req_dev_pwr_mode =UFS_POWERDOWN_PWR_MODE ; + req_link_state =UIC_LINK_OFF_STATE ; + } + +/* +* 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; + } + +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; + } + +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_runtime_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; + +ufshcd_vreg_set_lpm (hba); + +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; + } + +if (hba->vops && hba->vops->setup_clocks) { + ret = hba->vops->setup_clocks (hba, false); +if (ret) + goto vops_resume; + } + +if (!ufshcd_is_link_active (hba)) +ufshcd_setup_clocks (hba, false); +else +/* If link is active, device ref_clk can't be switched off */ +__ufshcd_setup_clocks (hba, false, true); +/* -* 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 +* 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; -return -ENOSYS ; +vops_resume: +if (hba->vops && hba->vops->resume) + hba->vops->resume (hba, pm_op); +set_link_active: +ufshcd_vreg_set_hpm (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: +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; } -EXPORT_SYMBOL_GPL (ufshcd_resume); -int ufshcd_runtime_suspend (struct ufs_hba *hba) +/** +* 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) { -if (!hba) -return 0 ; +int ret; +enum uic_link_state old_link_state; + + 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; + +if (hba->vops && hba->vops->setup_clocks) { + ret = hba->vops->setup_clocks (hba, true); +if (ret) + goto disable_clks; + } + +/* enable the host irq as host controller would be active soon */ +ufshcd_enable_irq (hba); + + ret =ufshcd_vreg_set_hpm (hba); +if (ret) + goto disable_irq_and_vops_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. */ -return ufshcd_enable_auto_bkops (hba); +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: +ufshcd_vreg_set_lpm (hba); +disable_irq_and_vops_clks: +ufshcd_disable_irq (hba); +if (hba->vops && hba->vops->setup_clocks) + ret = hba->vops->setup_clocks (hba, false); +disable_clks: +ufshcd_setup_clocks (hba, false); +out: + hba->pm_op_in_progress =0 ; +return ret; +} + +/** +* 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) { +int ret =0 ; + +if (!hba || !hba->is_powered) + goto out; + +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)
drivers/scsi/ufs/ufshcd.c +106
-3+ 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 (!hba || !hba->is_powered ||pm_runtime_suspended (hba->dev)) +/* +* Let the runtime resume take care of resuming +* if runtime suspended. +*/ +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) { +if (!hba || !hba->is_powered) +return 0 ; +else +return ufshcd_suspend (hba,UFS_RUNTIME_PM ); }EXPORT_SYMBOL (ufshcd_runtime_suspend); +/** +* 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) { -if (!hba) +if (!hba || !hba->is_powered)return 0 ; - -return ufshcd_disable_auto_bkops (hba); +else +return ufshcd_resume (hba,UFS_RUNTIME_PM ); }EXPORT_SYMBOL (ufshcd_runtime_resume);@@ -3900,6 +4600,36 @@ int ufshcd_runtime_idle(struct ufs_hba *hba)EXPORT_SYMBOL (ufshcd_runtime_idle);/** +* ufshcd_shutdown - shutdown routine +* @hba: per adapter instance +* +* This function would power off both UFS device and UFS link. +* +* Returns 0 always to allow force shutdown even in case of errors. +*/ +int ufshcd_shutdown (struct ufs_hba *hba) { +int ret =0 ; + +if (ufshcd_is_ufs_dev_poweroff (hba) &&ufshcd_is_link_off (hba)) + goto out; + +if (pm_runtime_suspended (hba->dev)) { + ret =ufshcd_runtime_resume (hba); +if (ret) + goto out; + } + + ret =ufshcd_suspend (hba,UFS_SHUTDOWN_PM ); +out: +if (ret) +dev_err (hba->dev,"%s failed, err %d\n" , __func__, ret); +/* allow force shutdown even in case of errors */ +return 0 ; +} +EXPORT_SYMBOL (ufshcd_shutdown); + +/** * ufshcd_remove - de-allocate SCSI host and host memory space * data structure memory * @hba - per adapter instance @@ -4052,6 +4782,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; }drivers/scsi/ufs/ufshcd.c +6
-0/* Enable SCSI tag mapping */ @@ -4077,6 +4809,12 @@ int ufshcd_init(struct ufs_hba *hba, void __iomem *mmio_base, unsigned int irq)/* Hold auto suspend until async scan completes */ pm_runtime_get_sync (dev ); +/* +* The device-initialize-sequence hasn't been invoked yet. +* Set the device to power-off state +*/ +ufshcd_set_ufs_dev_poweroff (hba); +async_schedule (ufshcd_async_scan, hba);drivers/scsi/ufs/ufshcd.c +1
-0return 0 ;@@ -4084,6 +4822,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);ufshcd_hba_exit (hba);
out_error:drivers/scsi/ufs/ufshcd.h index renamed from drivers/scsi/ufs/ufshcd.h +0
-09b5f77f..1ee429d 100644
drivers/scsi/ufs/ufshcd.h +35
-0@@ -96,6 +96,54 @@ struct uic_command {struct completion done; }; +/* Used to differentiate the power management options */ enum ufs_pm_op +{ +UFS_RUNTIME_PM , +UFS_SYSTEM_PM , +UFS_SHUTDOWN_PM , +}; + +#define ufshcd_is_runtime_pm (op) ((op) == UFS_RUNTIME_PM) #define +ufshcd_is_system_pm(op) ((op) == UFS_SYSTEM_PM) #define +ufshcd_is_shutdown_pm (op) ((op) == UFS_SHUTDOWN_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 */
drivers/scsi/ufs/ufshcd.h +2
-0+UFS_PM_LVL_2 ,/* UFS_SLEEP_PWR_MODE, UIC_LINK_ACTIVE_STATE */ + UFS_PM_LVL_3, /* UFS_SLEEP_PWR_MODE, UIC_LINK_HIBERN8_STATE*/
drivers/scsi/ufs/ufshcd.h +1
-0+ UFS_PM_LVL_4, /* UFS_POWERDOWN_PWR_MODE,
UIC_LINK_HIBERN8_STATE*/drivers/scsi/ufs/ufshcd.h +1
-0+ UFS_PM_LVL_5, /* UFS_POWERDOWN_PWR_MODE,
UIC_LINK_OFF_STATE*/drivers/scsi/ufs/ufshcd.h +8
-0+ 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 @@ -184,6 +232,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
*/
structufs_hba_variant_ops{drivers/scsi/ufs/ufshcd.h +17
-5const char *name;@@ -193,6 +243,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 ); };/** @@ -271,6 +323,13 @@ struct ufs_hba {* "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;@@ -285,16 +344,17 @@ struct ufs_hba {struct ufs_hba_variant_ops *vops;void *priv;unsigned int irq; +bool is_irq_enabled; -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 completion *pwr_done; +struct uic_command *active_uic_cmd; +struct mutex uic_cmd_mutex; +struct completion *uic_async_done;u32 ufshcd_state;u32 eh_flags;@@ -317,9 +377,13 @@ struct ufs_hba {/* Device management request data */ struct ufs_dev_cmd dev_cmd; +/* Keeps information of the UFS device connected to this host */ +struct ufs_dev_info dev_info;bool auto_bkops_enabled;struct ufs_vreg_info vreg_info;struct list_head clk_list_head; + +bool wlun_dev_clr_ua; };#define ufshcd_writel (hba, val, reg) \@@ -346,11 +410,12 @@ static inline void check_upiu_size(void) GENERAL_UPIU_REQUEST_SIZE + QUERY_DESC_MAX_SIZE); } -extern int ufshcd_suspend (struct ufs_hba *hba,pm_message_t state); -extern int ufshcd_resume(struct ufs_hba *hba); extern int
ufshcd_runtime_suspend(struct ufs_hba *hba); extern intufshcd_runtime_resume(struct ufs_hba *hba); extern intufshcd_runtime_idle(struct ufs_hba *hba);drivers/scsi/ufs/ufshcd.h +3
-0+extern int ufshcd_system_suspend (struct ufs_hba *hba);extern int +ufshcd_system_resume (struct ufs_hba *hba);extern int +ufshcd_shutdown (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 intufshcd_dme_get_attr(struct
ufs_hba*hba, u32attr_sel, diff --gita/drivers/scsi/ufs/ufshci.hb/drivers/scsi/ufs/ufshci.hindex e1b844b..d572119 100644drivers/scsi/ufs/ufshci.h +5
-2@@ -124,8 +124,11 @@ enum {#define CONTROLLER_FATAL_ERROR UFS_BIT(16)#define SYSTEM_BUS_FATAL_ERROR UFS_BIT(17) -#define UFSHCD_UIC_MASK (UIC_COMMAND_COMPL |\ - UIC_POWER_MODE) +#define UFSHCD_UIC_PWR_MASK (UIC_HIBERNATE_ENTER |\ + UIC_HIBERNATE_EXIT |\ + UIC_POWER_MODE) + +#define UFSHCD_UIC_MASK (UIC_COMMAND_COMPL | UFSHCD_UIC_PWR_MASK)#define UFSHCD_ERROR_MASK (UIC_ERROR |\drivers/scsi/ufs/ufshci.h +1
-2DEVICE_FATAL_ERROR |\@@ -210,7 +213,7 @@ enum {#define UIC_GET_ATTR_ID (v) (((v) >> 16) & 0xFFFF)/* UIC Commands */ -enum { +enum uic_cmd_dme {UIC_CMD_DME_GET =0x01 ,UIC_CMD_DME_SET =0x02 ,UIC_CMD_DME_PEER_GET =0x03 , --1.8.5.2
--
QUALCOMM ISRAEL, on behalf of Qualcomm Innovation Center, Inc. is a member of Code Aurora Forum, hosted by The Linux Foundation--
To unsubscribe from this list: send the line "unsubscribe linux-scsi" in the body of a message to [email protected] More majordomo info at
http://vger.kernel.org/majordomo-info.html
RE: [PATCH/RFC V2 10/16] scsi: ufs: add UFS power management support
Quoted 1,312 lines
-----Original Message-----
From: [email protected] [mailto:[email protected]] On Behalf Of Dong, Chuanxiao
Sent: Wednesday, August 20, 2014 3:21 PM
To: Dolev Raviv; [email protected]; [email protected]
Cc: [email protected]; [email protected]; [email protected]; [email protected]; Subhash Jadavani; Sujit Reddy Thumma
Subject: RE: [PATCH/RFC V2 10/16] scsi: ufs: add UFS power management supportQuoted 29 lines
-----Original Message-----
From: [email protected] [mailto:[email protected]] On Behalf Of Dolev Raviv
Sent: Thursday, August 14, 2014 9:31 PM
To: [email protected]; [email protected]
Cc: [email protected]; [email protected]; [email protected]; [email protected]; Subhash Jadavani; Dolev Raviv; Sujit Reddy Thumma
Subject: [PATCH/RFC V2 10/16] scsi: ufs: add UFS power management supportFrom: Subhash Jadavani <[email protected]>
This patch adds support for UFS device and UniPro link power management during runtime/system PM.
Main idea is to define multiple UFS low power levels based on UFS device and UFS link power states. This would allow any specific platform or pci driver to choose the best suited low power level during runtime and system suspend based on their power goals.
bkops handlig:
To put the UFS device in sleep state when bkops is disabled, first query the bkops status from the device and enable bkops on device only if device needs time to perform the bkops.
START_STOPhandling:
Before sendingSTART_STOP_UNITto the device well-known logical unit (w-lun) to make sure that the device w-lun unit attention condition is cleared.Quoted 10 lines
Write protection:
UFS device specification allows LUs to be write protected, either permanently or power on write protected. If any LU is power on write protected and if the card is power cycled (by powering off VCCQ and/or VCC rails), LU's write protect status would be lost. So this means those LUs can be written now. To ensures that UFS device is power cycled only if the power on protect is not set for any of the LUs, check if power on write protect is set and if device is in sleep/power-off state & link in inactive state (Hibern8 or OFF state). If none of the Logical Units on UFS device is power on write protected then all UFS device power rails (VCC, VCCQ & VCCQ2) can be turned off if UFS deviceis
Quoted 20 lines
in power-off state and UFS link is in OFF state. But current implementation would disable all device power rails even if UFS link is not in OFF state.
Signed-off-by Subhash Jadavani <[email protected]>Signed-off-by Dolev Raviv <[email protected]>Signed-off-by Sujit Reddy Thumma <[email protected]>drivers/scsi/ufs/ufs.h index renamed from drivers/scsi/ufs/ufs.h +0
-0bcc3a7f..2a82959 100644
drivers/scsi/ufs/ufs.h +1
-0@@ -129,6 +129,7 @@ enum {/* Flag idn for Query Requests*/ enum flag_idn {QUERY_FLAG_IDN_FDEVICEINIT =0x01 , +QUERY_FLAG_IDN_PWR_ON_WPE =0x03 ,QUERY_FLAG_IDN_BKOPS_EN =0x04 , };drivers/scsi/ufs/ufs.h enum unit_desc_param { · line 202
Quoted 8 lines
drivers/scsi/ufs/ufs.h +5
-0@@ -194,6 +195,18 @@ enum unit_desc_param { UNIT_DESC_PARAM_LARGE_UNIT_SIZE_M1 = 0x22, }; +/* +* Logical Unit Write Protect +* 00h: LU not write protected +* 01h: LU write protected when fPowerOnWPEn =1 +* 02h: LU permanently write protected when fPermanentWPEn =1 */ enum
Quoted 12 lines
drivers/scsi/ufs/ufs.h +6
-0+ufs_lu_wp_type { +UFS_LU_NO_WP =0x00 , +UFS_LU_POWER_ON_WP =0x01 , + UFS_LU_PERM_WP = 0x02, +}; +/* bActiveICCLevel parameter current units */ enum {UFSHCD_NANO_AMP =0 ,@@ -226,11 +239,12 @@ enum { };/* Background operation status */ drivers/scsi/ufs/ufs.h enum { · line 250
Quoted 10 lines
drivers/scsi/ufs/ufs.h +2
-1-enum { +enum bkops_status {BKOPS_STATUS_NO_OP =0x0 ,BKOPS_STATUS_NON_CRITICAL =0x1 ,BKOPS_STATUS_PERF_IMPACT =0x2 ,BKOPS_STATUS_CRITICAL =0x3 , +BKOPS_STATUS_MAX =BKOPS_STATUS_CRITICAL , }; /* UTP QUERY Transaction Specific Fields OpCode */ @@ -291,6 +305,14@@
Quoted 83 lines
enum {
drivers/scsi/ufs/ufs.h +20
-0UPIU_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 @@ -437,6 +459,12 @@ struct ufs_query_res {#define UFS_VREG_VCCQ2_MIN_UV 1650000/* uV */ #define UFS_VREG_VCCQ2_MAX_UV 1950000/* uV */ +/* +* VCCQ & VCCQ2 current requirement when UFS device is in sleep state +* and link is in Hibern8 state. +*/ +#define UFS_VREG_LPM_LOAD_UA 1000/* uA */ +struct ufs_vreg {struct regulator *reg;const char *name;@@ -453,4 +481,10 @@ struct ufs_vreg_info { struct ufs_vreg *vccq2; }; +struct ufs_dev_info { +bool f_power_on_wp_en; +/* Keeps information if any of the LU is power on write protected */ +bool is_lu_power_on_wp; +}; +#endif /* End of Header */ drivers/scsi/ufs/ufshcd-pci.c index renamed from drivers/scsi/ufs/ufshcd-pci.c +0
-01aac2ef..b8f7774 100644
drivers/scsi/ufs/ufshcd-pci.c +6
-16@@ -43,34 +43,24 @@* @pdev: pointer to PCI device handle * @state: power state * -* Returns -ENOSYS +* Returns 0 if successful +* Returns non-zero otherwise */ static int ufshcd_pci_suspend (struct device *dev) { -/* -* TODO: -* 1. Call ufshcd_suspend -* 2. Do bus specific power management -*/ - -return -ENOSYS ; +return ufshcd_system_suspend (dev_get_drvdata (dev)); }/** * ufshcd_pci_resume - resume power management function * @pdev: pointer to PCI device handle * -* Returns -ENOSYS +* Returns 0 if successful +* Returns non-zero otherwise */ static int ufshcd_pci_resume (struct device *dev) { -/* -* TODO: -* 1. Call ufshcd_resume. -* 2. Do bus specific wake up -*/ - -return -ENOSYS ; +return ufshcd_system_resume (dev_get_drvdata (dev)); }#else #define ufshcd_pci_suspend NULLdrivers/scsi/ufs/ufshcd-pci.c static int ufshcd_pci_resume(struct device *dev) · line 70
Quoted 1 line
drivers/scsi/ufs/ufshcd-pci.c +0
-0@@ -80,30 +70,15 @@ static int ufshcd_pci_resume(struct device *dev) #ifdef CONFIG_PM_RUNTIME static intufshcd_pci_runtime_suspend(struct
drivers/scsi/ufs/ufshcd-pltfrm.c static int ufshcd_pltfrm_resume(struct device
Quoted 92 lines
drivers/scsi/ufs/ufshcd-pci.c +4
-19device *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_pci_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_pci_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_pci_runtime_suspend NULL@@ -117,7 +92,7 @@ static int ufshcd_pci_runtime_idle(struct device *dev)*/ static void ufshcd_pci_shutdown (struct pci_dev *pdev) { -ufshcd_hba_stop ((struct ufs_hba *)pci_get_drvdata (pdev)); +ufshcd_shutdown ((struct ufs_hba *)pci_get_drvdata (pdev)); }/** drivers/scsi/ufs/ufshcd-pltfrm.c +6
-27@@ -216,45 +216,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@@ -264,30 +243,15 @@ static int ufshcd_pltfrm_resume(struct device*dev)
Quoted 109 lines
drivers/scsi/ufs/ufshcd-pltfrm.c +6
-18#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@@ -295,6 +259,11 @@ static int ufshcd_pltfrm_runtime_idle(struct device *dev)#define ufshcd_pltfrm_runtime_idle NULL#endif /* CONFIG_PM_RUNTIME */ +static void ufshcd_pltfrm_shutdown (struct platform_device *pdev) { +ufshcd_shutdown ((struct ufs_hba *)platform_get_drvdata (pdev)); } +/** * ufshcd_pltfrm_probe - probe routine of the driver * @pdev: pointer to Platform device handle @@ -395,6 +364,7 @@ static const struct
dev_pm_opsufshcd_dev_pm_ops= { static structplatform_driverufshcd_pltfrm_driver= {drivers/scsi/ufs/ufshcd-pltfrm.c +1
-0.probe = ufshcd_pltfrm_probe, .remove = ufshcd_pltfrm_remove, + .shutdown = ufshcd_pltfrm_shutdown, .driver = { .name = "ufshcd", .owner = THIS_MODULE,drivers/scsi/ufs/ufshcd.c index renamed from drivers/scsi/ufs/ufshcd.c +0
-0876d210..fac71c9 100644
drivers/scsi/ufs/ufshcd.c +30
-1@@ -44,7 +44,6 @@#define UFSHCD_ENABLE_INTRS (UTP_TRANSFER_REQ_COMPL |\ UTP_TASK_REQ_COMPL |\ - UIC_POWER_MODE |\ UFSHCD_ERROR_MASK)/* UIC command timeout, unit: ms */ #define UIC_CMD_TIMEOUT 500@@ -138,12 +137,63 @@ 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_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 intufshcd_reset_and_restore(struct ufs_hba *hba); static intufshcd_clear_tm_cmd(struct ufs_hba *hba, int tag); static voidufshcd_hba_exit(struct ufs_hba *hba); static intufshcd_probe_hba(struct ufs_hba *hba);drivers/scsi/ufs/ufshcd.c +8
-0+static int ufshcd_host_reset_and_restore (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; + } +}drivers/scsi/ufs/ufshcd.c enum {
Quoted 10 lines
drivers/scsi/ufs/ufshcd.c +7
-0+ +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 @@ -1778,44
drivers/scsi/ufs/ufshcd.c static int ufshcd_probe_hba(struct ufs_hba
Quoted 351 lines
+1828,48 @@ out:
EXPORT_SYMBOL_GPL(ufshcd_dme_get_attr);/**
- *ufshcd_uic_change_pwr_mode- Perform the UIC power mode chage
- * usingDME_SETprimitives.
+ *ufshcd_uic_pwr_ctrl- executes UIC commands (which affects the link
+ power
+ * state) and waits for it to take effect.
+ *
* @hba: per adapter instance
- * @mode: powr mode value
+ * @cmd: UIC command to execute
+ *
+ * DME operations likeDME_SET(PA_PWRMODE),DME_HIBERNATE_ENTER&
+ *DME_HIBERNATE_EXITcommands take some time to take its effect on
+ both host
+ * and device UniPro link and hence it's final completion would be
+ indicated by
+ * dedicated status bits in Interrupt Status register (UPMS, UHES,
+ UHXS) in
+ * addition to normal UIC command completion Status (UCCS). This
+ function only
+ * returns after the relevant status bits indicate the completion. *
* Returns 0 on success, non-zero value on failure
*/drivers/scsi/ufs/ufshcd.c +130
-19-static int ufshcd_uic_change_pwr_mode (struct ufs_hba *hba,u8 mode) +int ufshcd_uic_pwr_ctrl (struct ufs_hba *hba,struct uic_command *cmd) { -struct uic_command uic_cmd = {0 }; -struct completion pwr_done; +struct completion uic_async_done;unsigned long flags;u8 status;int ret; - uic_cmd.command =UIC_CMD_DME_SET ; - uic_cmd.argument1 =UIC_ARG_MIB (PA_PWRMODE ); - uic_cmd.argument3 = mode; -init_completion (&pwr_done); -mutex_lock (&hba->uic_cmd_mutex); +init_completion (&uic_async_done);spin_lock_irqsave (hba->host->host_lock, flags); - hba->pwr_done = &pwr_done; + hba->uic_async_done = &uic_async_done;spin_unlock_irqrestore (hba->host->host_lock, flags); - ret =__ufshcd_send_uic_cmd (hba, &uic_cmd); + + ret =__ufshcd_send_uic_cmd (hba, cmd);if (ret) {dev_err (hba->dev, -"pwr mode change with mode 0x%x uic error %d\n" , - mode, ret); +"pwr ctrl cmd 0x%x with mode 0x%x uic error %d\n" , + cmd->command, cmd->argument3, ret); goto out; } -if (!wait_for_completion_timeout (hba->pwr_done, +if (!wait_for_completion_timeout (hba->uic_async_done,msecs_to_jiffies (UIC_CMD_TIMEOUT ))) {dev_err (hba->dev, -"pwr mode change with mode 0x%x completion timeout\n" , - mode); +"pwr ctrl cmd 0x%x with mode 0x%x completion timeout\n" , + cmd->command, cmd->argument3); ret = -ETIMEDOUT ; goto out; }@@ -1823,19 +1877,62 @@ static int ufshcd_uic_change_pwr_mode(struct ufs_hba *hba, u8 mode) status =ufshcd_get_upmcrs (hba);if (status !=PWR_LOCAL ) {dev_err (hba->dev, -"pwr mode change failed, host umpcrs:0x%x\n" , - status); +"pwr ctrl cmd 0x%0x failed, host umpcrs:0x%x\n" , + cmd->command, status); ret = (status !=PWR_OK ) ? status :-1 ; } out:spin_lock_irqsave (hba->host->host_lock, flags); - hba->pwr_done =NULL ; + hba->uic_async_done =NULL ;spin_unlock_irqrestore (hba->host->host_lock, flags);mutex_unlock (&hba->uic_cmd_mutex);return ret; }/** +* ufshcd_uic_change_pwr_mode - Perform the UIC power mode chage +* using DME_SET primitives. +* @hba: per adapter instance +* @mode: powr mode value +* +* Returns 0 on success, non-zero value on failure */ static int +ufshcd_uic_change_pwr_mode (struct ufs_hba *hba,u8 mode) { +struct uic_command uic_cmd = {0 }; + + uic_cmd.command =UIC_CMD_DME_SET ; + uic_cmd.argument1 =UIC_ARG_MIB (PA_PWRMODE ); + uic_cmd.argument3 = mode; + +return ufshcd_uic_pwr_ctrl (hba, &uic_cmd); } + +static int ufshcd_uic_hibern8_enter (struct ufs_hba *hba) { +struct uic_command uic_cmd = {0 }; + + uic_cmd.command =UIC_CMD_DME_HIBER_ENTER ; + +return ufshcd_uic_pwr_ctrl (hba, &uic_cmd); } + +static int ufshcd_uic_hibern8_exit (struct ufs_hba *hba) { +struct uic_command uic_cmd = {0 }; +int ret; + + uic_cmd.command =UIC_CMD_DME_HIBER_EXIT ; + ret =ufshcd_uic_pwr_ctrl (hba, &uic_cmd); +if (ret) { +ufshcd_set_link_off (hba); + ret =ufshcd_host_reset_and_restore (hba); + } + +return ret; +} + +/** * ufshcd_config_max_pwr_mode - Set & Change power mode with * maximum capability attribute information. * @hba: per adapter instance @@ -2034,6 +2131,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 );@@ -2066,7 +2166,7 @@ static int ufshcd_hba_enable(struct ufs_hba *hba) }/* enable UIC related interrupts */ -ufshcd_enable_intr (hba,UIC_COMMAND_COMPL ); +ufshcd_enable_intr (hba,UFSHCD_UIC_MASK );if (hba->vops && hba->vops->hce_enable_notify) hba->vops->hce_enable_notify(hba, POST_CHANGE); @@ -2195,6 +2295,62 @@ static void ufshcd_set_queue_depth(struct scsi_device *sdev) scsi_activate_tcq(sdev, lun_qdepth); } +/* +* ufshcd_get_lu_wp - returns the "b_lu_write_protect" from UNIT +DESCRIPTOR +* @hba: per-adapter instance +* @lun: UFS device lun id +* @b_lu_write_protect: pointer to buffer to hold the LU's write +protect info +* +* Returns 0 in case of success and b_lu_write_protect status would be +returned +* @b_lu_write_protect parameter. +* Returns -ENOTSUPP if reading b_lu_write_protect is not supported. +* Returns -EINVAL in case of invalid parameters passed to this function. +*/ +static int ufshcd_get_lu_wp (struct ufs_hba *hba, +u8 lun, +u8 *b_lu_write_protect) +{ +int ret; + +if (!b_lu_write_protect) + ret = -EINVAL ; +/* +* According to UFS device spec, RPMB LU can't be write +* protected so skip reading bLUWriteProtect parameter for +* it. For other W-LUs, UNIT DESCRIPTOR is not available. +*/ +else if (lun >=UFS_UPIU_MAX_GENERAL_LUN ) + ret = -ENOTSUPP ; +else + ret =ufshcd_read_unit_desc_param (hba, + lun, +UNIT_DESC_PARAM_LU_WR_PROTECT , + b_lu_write_protect, +sizeof (*b_lu_write_protect)); +return ret; +} + +/** +* ufshcd_get_lu_power_on_wp_status - get LU's power on write protect +* status +* @hba: per-adapter instance +* @sdev: pointer to SCSI device +* +*/ +static inline void ufshcd_get_lu_power_on_wp_status (struct ufs_hba *hba, +struct scsi_device *sdev) +{ +if (hba->dev_info.f_power_on_wp_en && + !hba->dev_info.is_lu_power_on_wp) { +u8 b_lu_write_protect; + +if (!ufshcd_get_lu_wp (hba,ufshcd_scsi_to_upiu_lun (sdev->lun), + &b_lu_write_protect) && + (b_lu_write_protect ==UFS_LU_POWER_ON_WP )) + hba->dev_info.is_lu_power_on_wp = true; + } +} +/** * ufshcd_slave_alloc - handle initial SCSI device configurations * @sdev: pointer to SCSI device @@ -2221,6 +2377,21 @@ static int ufshcd_slave_alloc(struct scsi_device *sdev)ufshcd_set_queue_depth (sdev); +ufshcd_get_lu_power_on_wp_status (hba, sdev); + +/* +* 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 ;}
drivers/scsi/ufs/ufshcd.c +2
-2@@ -2451,8 +2622,8 @@ static void ufshcd_uic_cmd_compl(struct ufs_hba *hba, u32 intr_status)complete (&hba->active_uic_cmd->done); } -if ((intr_status &UIC_POWER_MODE ) && hba->pwr_done) -complete (hba->pwr_done); +if ((intr_status &UFSHCD_UIC_PWR_MASK ) && hba->uic_async_done) +complete (hba->uic_async_done); }/**
drivers/scsi/ufs/ufshcd.c +42
-11@@ -2664,33 +2835,62 @@ static inline int ufshcd_get_bkops_status(struct ufs_hba *hba, u32 *status) }/** -* ufshcd_urgent_bkops - handle urgent bkops exception event +* ufshcd_bkops_ctrl - control the auto bkops based on current bkops +status * @hba: per-adapter instance +* @status: bkops_status value * -* Enable fBackgroundOpsEn flag in the device to permit background -* operations. +* Read the bkops_status from the UFS device and Enable +fBackgroundOpsEn +* flag in the device to permit background operations if the device +* bkops_status is greater than or equal to "status" argument passed to +* this function, disable otherwise. +* +* Returns 0 for success, non-zero in case of failure. +* +* NOTE: Caller of this function can check the +"hba->auto_bkops_enabled" flag +* to know whether auto bkops is enabled or disabled after this +function +* returns control to it. */ -static int ufshcd_urgent_bkops (struct ufs_hba *hba) +static int ufshcd_bkops_ctrl (struct ufs_hba *hba, +enum bkops_status status) {int err; -u32 status =0 ; +u32 curr_status =0 ; - err =ufshcd_get_bkops_status (hba, &status); + err =ufshcd_get_bkops_status (hba, &curr_status);if (err) {dev_err (hba->dev,"%s: failed to get BKOPS status %d\n" , __func__, err); goto out; + }else if (curr_status >BKOPS_STATUS_MAX ) { +dev_err (hba->dev,"%s: invalid BKOPS status %d\n" , + __func__, curr_status); + err = -EINVAL ; + goto out; } - status = status &0xF ; - -/* handle only if status indicates performance impact or critical */ -if (status >=BKOPS_STATUS_PERF_IMPACT ) +if (curr_status >= status) err =ufshcd_enable_auto_bkops (hba); +else + err =ufshcd_disable_auto_bkops (hba); out:return err; } +/** +* ufshcd_urgent_bkops - handle urgent bkops exception event +* @hba: per-adapter instance +* +* Enable fBackgroundOpsEn flag in the device to permit background +* operations. +* +* If BKOPs is enabled, this function returns 0, 1 if the bkops in not +enabled +* and negative error value for any other failure. +*/ +static int ufshcd_urgent_bkops (struct ufs_hba *hba) { +return ufshcd_bkops_ctrl (hba,BKOPS_STATUS_PERF_IMPACT ); } +static inline int ufshcd_get_ee_status (struct ufs_hba *hba,u32 *status) { return ufshcd_query_attr(hba, UPIU_QUERY_OPCODE_READ_ATTR, @@ -2722,7 +2922,7 @@ static void ufshcd_exception_event_handler(struct
work_struct*work)drivers/scsi/ufs/ufshcd.c +3
-2status &= hba->ee_ctrl_mask;if (status &MASK_EE_URGENT_BKOPS ) { err =ufshcd_urgent_bkops (hba); -if (err) +if (err <0 )dev_err (hba->dev,"%s: failed to handle urgent bkops %d\n" , __func__, err); }@@ -3441,7 +3641,8 @@ static int ufshcd_probe_hba(struct ufs_hba *hba)if (ret) goto out; -ufshcd_config_max_pwr_mode (hba); +/* UniPro link is active now */ +ufshcd_set_link_active (hba); ret =ufshcd_verify_dev_init (hba);if (ret)@@ -3451,14 +3652,29 @@ static int ufshcd_probe_hba(struct ufs_hba*hba)
Quoted 47 lines
drivers/scsi/ufs/ufshcd.c +21
-4if (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 ; + hba->wlun_dev_clr_ua = true; + +ufshcd_config_max_pwr_mode (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) { +bool flag; + +/* clear any previous UFS device information */ +memset (&hba->dev_info,0 ,sizeof (hba->dev_info)); +if (!ufshcd_query_flag (hba,UPIU_QUERY_OPCODE_READ_FLAG , +QUERY_FLAG_IDN_PWR_ON_WPE , &flag)) + hba->dev_info.f_power_on_wp_en = flag; -/* If we are in error handling context no need to scan the host */ -if (!ufshcd_eh_in_progress (hba)) {if (!hba->is_init_prefetch)ufshcd_init_icc_levels (hba); -scsi_scan_host (hba->host);pm_runtime_put_sync (hba->dev); }@@ -3471,8 +3687,10 @@ 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) { +pm_runtime_put_sync (hba->dev);ufshcd_hba_exit (hba); + }return ret; }@@ -3507,6 +3725,42 @@ static struct scsi_host_template ufshcd_driver_template = { .can_queue = UFSHCD_CAN_QUEUE,drivers/scsi/ufs/ufshcd.c static struct scsi_host_template · line 3736
Quoted 10 lines
drivers/scsi/ufs/ufshcd.c +8
-0}; +static int ufshcd_config_vreg_load (struct device *dev,struct ufs_vreg *vreg, +int ua) +{ +int ret =0 ; +struct regulator *reg = vreg->reg; +const char *name = vreg->name; + +BUG_ON (!vreg);For the UFS host controller driver, which doesn't have the
ufs_vregstructure for vcc and vccq, this function can cause kernel panic due to thisBUG_ON(). Any reason to put aBUG_ONhere instead of a NULL pointer checking?Thanks
Chuanxiaodrivers/scsi/ufs/ufshcd.c static int ufshcd_setup_clocks(struct ufs_hba
Quoted 64 lines
drivers/scsi/ufs/ufshcd.c +29
-11+ + ret =regulator_set_optimum_mode (reg, ua); +if (ret >=0 ) { +/* +* regulator_set_optimum_mode() returns new regulator +* mode upon success. +*/ + ret =0 ; + }else { +dev_err (dev,"%s: %s set optimum mode(ua=%d) failed, err=%d\n" , + __func__, name, ua, ret); + } + +return ret; +} + +static inline int ufshcd_config_vreg_lpm (struct ufs_hba *hba, +struct ufs_vreg *vreg) +{ +return ufshcd_config_vreg_load (hba->dev, vreg,UFS_VREG_LPM_LOAD_UA ); +} + +static inline int ufshcd_config_vreg_hpm (struct ufs_hba *hba, +struct ufs_vreg *vreg) +{ +return ufshcd_config_vreg_load (hba->dev, vreg, vreg->max_uA); } +static int ufshcd_config_vreg (struct device *dev,struct ufs_vreg *vreg,bool on) {@@ -3527,18 +3781,9 @@ static int ufshcd_config_vreg(struct device *dev, } uA_load = on ? vreg->max_uA :0 ; - ret =regulator_set_optimum_mode (reg, uA_load); -if (ret >=0 ) { -/* -* regulator_set_optimum_mode() returns new regulator -* mode upon success. -*/ - ret =0 ; - }else { -dev_err (dev,"%s: %s set optimum mode(uA_load=%d) failed, err=%d\n", - __func__, name, uA_load, ret); + ret = ufshcd_config_vreg_load(dev, vreg, uA_load); +if (ret) goto out; - } } out:drivers/scsi/ufs/ufshcd.c +2
-1return ret;@@ -3654,7 +3899,8 @@ out:return ret; } -static int ufshcd_setup_clocks (struct ufs_hba *hba,bool on) +static int __ufshcd_setup_clocks (struct ufs_hba *hba,bool on, +bool skip_ref_clk) {int ret =0 ;struct ufs_clk_info *clki;@@ -3665,6 +3911,9 @@ static int ufshcd_setup_clocks(struct ufs_hba*hba,
drivers/scsi/ufs/ufshcd.c static void ufshcd_hba_exit(struct ufs_hba
Quoted 20 lines
drivers/scsi/ufs/ufshcd.c +3
-0bool on) list_for_each_entry(clki, head, list) {if (!IS_ERR_OR_NULL (clki->clk)) { +if (skip_ref_clk && !strcmp (clki->name,"ref_clk" )) +continue ; +if (on && !clki->enabled) { ret =clk_prepare_enable (clki->clk);drivers/scsi/ufs/ufshcd.c +3
-0if (ret) {@@ -3690,6 +3939,11 @@ out:return ret; } +static int ufshcd_setup_clocks (struct ufs_hba *hba,bool on) { +return __ufshcd_setup_clocks (hba, on, false); } +static int ufshcd_init_clocks (struct ufs_hba *hba) {int ret =0 ;@@ -3828,68 +4082,514 @@ static void ufshcd_hba_exit(struct ufs_hba*hba)
Quoted 403 lines
drivers/scsi/ufs/ufshcd.c +30
-1} } +static int +ufshcd_send_request_sense (struct ufs_hba *hba,struct scsi_device *sdp) +{ +unsigned char cmd[6 ] = {REQUEST_SENSE , +0 , +0 , +0 , +SCSI_SENSE_BUFFERSIZE , +0 }; +char *buffer; +int ret; + + buffer =kzalloc (SCSI_SENSE_BUFFERSIZE ,GFP_KERNEL ); +if (!buffer) { + ret = -ENOMEM ; + goto out; + } + + ret =scsi_execute_req_flags (sdp, cmd,DMA_FROM_DEVICE , buffer, +SCSI_SENSE_BUFFERSIZE ,NULL , +msecs_to_jiffies (1000 ),3 ,NULL ,REQ_PM ); +if (ret) +pr_err ("%s: failed with err %d\n" , __func__, ret); + +kfree (buffer); +out: +return ret; +} +/** -* 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 */drivers/scsi/ufs/ufshcd.c +28
-7-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 ; + +/* +* If scsi commands fail, the scsi mid-layer schedules scsi error- +* handling, which would wait for host to be resumed. Since we know +* we are functional while we are here, skip host resume in error +* handling context. +*/ + hba->host->eh_noresume =1 ; +if (hba->wlun_dev_clr_ua) { + ret =ufshcd_send_request_sense (hba, sdp); +if (ret) + goto out; +/* Unit attention condition is cleared now */ + hba->wlun_dev_clr_ua = false; + } + + 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
drivers/scsi/ufs/ufshcd.c +283
-19+* 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); + } + } -return -ENOSYS ; +if (!ret) + hba->curr_dev_pwr_mode = pwr_mode; +out: + hba->host->eh_noresume =0 ; +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; +} + +static void ufshcd_vreg_set_lpm (struct ufs_hba *hba) { +/* +* If UFS device is either in UFS_Sleep turn off VCC rail to save some +* power. +* +* If UFS device and link is in OFF state, all power supplies (VCC, +* VCCQ, VCCQ2) can be turned off if power on write protect is not +* required. If UFS link is inactive (Hibern8 or OFF state) and device +* is in sleep state, put VCCQ & VCCQ2 rails in LPM mode. +* +* 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_is_link_off (hba) && + !hba->dev_info.is_lu_power_on_wp) { +ufshcd_setup_vreg (hba, false); + }else if (!ufshcd_is_ufs_dev_active (hba)) { +ufshcd_toggle_vreg (hba->dev, hba->vreg_info.vcc, false); +if (!ufshcd_is_link_active (hba)) { +ufshcd_config_vreg_lpm (hba, hba->vreg_info.vccq); +ufshcd_config_vreg_lpm (hba, hba->vreg_info.vccq2); + } + } +} + +static int ufshcd_vreg_set_hpm (struct ufs_hba *hba) { +int ret =0 ; + +if (ufshcd_is_ufs_dev_poweroff (hba) &&ufshcd_is_link_off (hba) && + !hba->dev_info.is_lu_power_on_wp) { + ret =ufshcd_setup_vreg (hba, true); + }else if (!ufshcd_is_ufs_dev_active (hba)) { + ret =ufshcd_toggle_vreg (hba->dev, hba->vreg_info.vcc, true); +if (!ret && !ufshcd_is_link_active (hba)) { + ret =ufshcd_config_vreg_hpm (hba, hba->vreg_info.vccq); +if (ret) + goto vcc_disable; + ret =ufshcd_config_vreg_hpm (hba, hba->vreg_info.vccq2); +if (ret) + goto vccq_lpm; + } + } + goto out; + +vccq_lpm: +ufshcd_config_vreg_lpm (hba, hba->vreg_info.vccq); +vcc_disable: +ufshcd_toggle_vreg (hba->dev, hba->vreg_info.vcc, false); +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: desired low power operation type * -* Returns -ENOSYS +* 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). +* +* If this function is called during shutdown, it will make sure that +* both UFS device and UFS link is powered off. +* +* 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; + + hba->pm_op_in_progress =1 ; +if (!ufshcd_is_shutdown_pm (pm_op)) { + 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); + }else { + req_dev_pwr_mode =UFS_POWERDOWN_PWR_MODE ; + req_link_state =UIC_LINK_OFF_STATE ; + } + +/* +* 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; + } + +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; + } + +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_runtime_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; + +ufshcd_vreg_set_lpm (hba); + +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; + } + +if (hba->vops && hba->vops->setup_clocks) { + ret = hba->vops->setup_clocks (hba, false); +if (ret) + goto vops_resume; + } + +if (!ufshcd_is_link_active (hba)) +ufshcd_setup_clocks (hba, false); +else +/* If link is active, device ref_clk can't be switched off */ +__ufshcd_setup_clocks (hba, false, true); +/* -* 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 +* 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; -return -ENOSYS ; +vops_resume: +if (hba->vops && hba->vops->resume) + hba->vops->resume (hba, pm_op); +set_link_active: +ufshcd_vreg_set_hpm (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: +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; } -EXPORT_SYMBOL_GPL (ufshcd_resume); -int ufshcd_runtime_suspend (struct ufs_hba *hba) +/** +* 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) { -if (!hba) -return 0 ; +int ret; +enum uic_link_state old_link_state; + + 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; + +if (hba->vops && hba->vops->setup_clocks) { + ret = hba->vops->setup_clocks (hba, true); +if (ret) + goto disable_clks; + } + +/* enable the host irq as host controller would be active soon */ +ufshcd_enable_irq (hba); + + ret =ufshcd_vreg_set_hpm (hba); +if (ret) + goto disable_irq_and_vops_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. */ -return ufshcd_enable_auto_bkops (hba); +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; + }
Quoted 8 lines
drivers/scsi/ufs/ufshcd.c static void ufshcd_hba_exit(struct ufs_hba *hba) · line 4463
Quoted 8 lines
drivers/scsi/ufs/ufshcd.c +8
-0+ +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);
Should be auto background operation enabled again in ufshcd_resume()? It is disabled during ufshcd_suspend().
Thanks
Chuanxiao
Quoted 391 lines
drivers/scsi/ufs/ufshcd.c int ufshcd_init(struct ufs_hba *hba, void
Quoted 176 lines
drivers/scsi/ufs/ufshcd.c +42
-0+ 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: +ufshcd_vreg_set_lpm (hba); +disable_irq_and_vops_clks: +ufshcd_disable_irq (hba); +if (hba->vops && hba->vops->setup_clocks) + ret = hba->vops->setup_clocks (hba, false); +disable_clks: +ufshcd_setup_clocks (hba, false); +out: + hba->pm_op_in_progress =0 ; +return ret; +} + +/** +* 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) { +int ret =0 ; + +if (!hba || !hba->is_powered) + goto out; + +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)
drivers/scsi/ufs/ufshcd.c +106
-3+ 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 (!hba || !hba->is_powered ||pm_runtime_suspended (hba->dev)) +/* +* Let the runtime resume take care of resuming +* if runtime suspended. +*/ +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) { +if (!hba || !hba->is_powered) +return 0 ; +else +return ufshcd_suspend (hba,UFS_RUNTIME_PM ); }EXPORT_SYMBOL (ufshcd_runtime_suspend); +/** +* 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) { -if (!hba) +if (!hba || !hba->is_powered)return 0 ; - -return ufshcd_disable_auto_bkops (hba); +else +return ufshcd_resume (hba,UFS_RUNTIME_PM ); }EXPORT_SYMBOL (ufshcd_runtime_resume);@@ -3900,6 +4600,36 @@ int ufshcd_runtime_idle(struct ufs_hba *hba)EXPORT_SYMBOL (ufshcd_runtime_idle);/** +* ufshcd_shutdown - shutdown routine +* @hba: per adapter instance +* +* This function would power off both UFS device and UFS link. +* +* Returns 0 always to allow force shutdown even in case of errors. +*/ +int ufshcd_shutdown (struct ufs_hba *hba) { +int ret =0 ; + +if (ufshcd_is_ufs_dev_poweroff (hba) &&ufshcd_is_link_off (hba)) + goto out; + +if (pm_runtime_suspended (hba->dev)) { + ret =ufshcd_runtime_resume (hba); +if (ret) + goto out; + } + + ret =ufshcd_suspend (hba,UFS_SHUTDOWN_PM ); +out: +if (ret) +dev_err (hba->dev,"%s failed, err %d\n" , __func__, ret); +/* allow force shutdown even in case of errors */ +return 0 ; +} +EXPORT_SYMBOL (ufshcd_shutdown); + +/** * ufshcd_remove - de-allocate SCSI host and host memory space * data structure memory * @hba - per adapter instance @@ -4052,6 +4782,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; }drivers/scsi/ufs/ufshcd.c +0
-0/* Enable SCSI tag mapping */ @@ -4077,6 +4809,12 @@ int ufshcd_init(struct ufs_hba *hba, void
__iomemQuoted 27 lines
drivers/scsi/ufs/ufshcd.c +6
-0*mmio_base, unsigned int irq)/* Hold auto suspend until async scan completes */ pm_runtime_get_sync (dev ); +/* +* The device-initialize-sequence hasn't been invoked yet. +* Set the device to power-off state +*/ +ufshcd_set_ufs_dev_poweroff (hba); +async_schedule (ufshcd_async_scan, hba);drivers/scsi/ufs/ufshcd.c +1
-0return 0 ;@@ -4084,6 +4822,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);ufshcd_hba_exit (hba);
out_error:drivers/scsi/ufs/ufshcd.h index renamed from drivers/scsi/ufs/ufshcd.h +0
-09b5f77f..1ee429d 100644
drivers/scsi/ufs/ufshcd.h struct uic_command { · line 99
Quoted 4 lines
drivers/scsi/ufs/ufshcd.h +1
-0@@ -96,6 +96,54 @@ struct uic_command {struct completion done; }; +/* Used to differentiate the power management options */ enum
ufs_pm_opQuoted 46 lines
drivers/scsi/ufs/ufshcd.h +34
-0+{ +UFS_RUNTIME_PM , +UFS_SYSTEM_PM , + UFS_SHUTDOWN_PM, +}; + +#define ufshcd_is_runtime_pm (op) ((op) == UFS_RUNTIME_PM) #define +ufshcd_is_system_pm(op) ((op) == UFS_SYSTEM_PM) #define +ufshcd_is_shutdown_pm (op) ((op) == UFS_SHUTDOWN_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 */
drivers/scsi/ufs/ufshcd.h +2
-0+UFS_PM_LVL_2 ,/* UFS_SLEEP_PWR_MODE, UIC_LINK_ACTIVE_STATE */ + UFS_PM_LVL_3, /* UFS_SLEEP_PWR_MODE, UIC_LINK_HIBERN8_STATE*/
drivers/scsi/ufs/ufshcd.h +1
-0+ UFS_PM_LVL_4, /* UFS_POWERDOWN_PWR_MODE,
UIC_LINK_HIBERN8_STATE*/drivers/scsi/ufs/ufshcd.h +1
-0+ UFS_PM_LVL_5, /* UFS_POWERDOWN_PWR_MODE,
UIC_LINK_OFF_STATE*/drivers/scsi/ufs/ufshcd.h +1
-0+UFS_PM_LVL_MAX drivers/scsi/ufs/ufshcd.h struct uic_command {
Quoted 10 lines
drivers/scsi/ufs/ufshcd.h +7
-0+}; + +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 @@ -184,6 +232,8
Quoted 4 lines
@@ 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-Proinitialization.
Quoted 76 lines
+ * @suspend: called during host controller PM callback
+ * @resume: called during host controller PM callback */
structufs_hba_variant_ops{drivers/scsi/ufs/ufshcd.h +17
-5const char *name;@@ -193,6 +243,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 ); };/** @@ -271,6 +323,13 @@ struct ufs_hba {* "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;@@ -285,16 +344,17 @@ struct ufs_hba {struct ufs_hba_variant_ops *vops;void *priv;unsigned int irq; +bool is_irq_enabled; -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 completion *pwr_done; +struct uic_command *active_uic_cmd; +struct mutex uic_cmd_mutex; +struct completion *uic_async_done;u32 ufshcd_state;u32 eh_flags;@@ -317,9 +377,13 @@ struct ufs_hba {/* Device management request data */ struct ufs_dev_cmd dev_cmd; +/* Keeps information of the UFS device connected to this host */ +struct ufs_dev_info dev_info;bool auto_bkops_enabled;struct ufs_vreg_info vreg_info;struct list_head clk_list_head; + +bool wlun_dev_clr_ua; };#define ufshcd_writel (hba, val, reg) \@@ -346,11 +410,12 @@ static inline void check_upiu_size(void) GENERAL_UPIU_REQUEST_SIZE + QUERY_DESC_MAX_SIZE); } -extern int ufshcd_suspend (struct ufs_hba *hba,pm_message_t state); -extern int ufshcd_resume(struct ufs_hba *hba); extern int
ufshcd_runtime_suspend(struct ufs_hba *hba); extern intufshcd_runtime_resume(struct ufs_hba *hba); extern intufshcd_runtime_idle(struct ufs_hba *hba);drivers/scsi/ufs/ufshcd.h +3
-0+extern int ufshcd_system_suspend (struct ufs_hba *hba);extern int +ufshcd_system_resume (struct ufs_hba *hba);extern int +ufshcd_shutdown (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 intQuoted 2 lines
ufshcd_dme_get_attr(struct
ufs_hba*hba, u32attr_sel, diff --gita/drivers/scsi/ufs/ufshci.hb/drivers/scsi/ufs/ufshci.hindexe1b844b..d572119
Quoted 33 lines
100644
drivers/scsi/ufs/ufshci.h +5
-2@@ -124,8 +124,11 @@ enum {#define CONTROLLER_FATAL_ERROR UFS_BIT(16)#define SYSTEM_BUS_FATAL_ERROR UFS_BIT(17) -#define UFSHCD_UIC_MASK (UIC_COMMAND_COMPL |\ - UIC_POWER_MODE) +#define UFSHCD_UIC_PWR_MASK (UIC_HIBERNATE_ENTER |\ + UIC_HIBERNATE_EXIT |\ + UIC_POWER_MODE) + +#define UFSHCD_UIC_MASK (UIC_COMMAND_COMPL | UFSHCD_UIC_PWR_MASK)#define UFSHCD_ERROR_MASK (UIC_ERROR |\drivers/scsi/ufs/ufshci.h +1
-2DEVICE_FATAL_ERROR |\@@ -210,7 +213,7 @@ enum {#define UIC_GET_ATTR_ID (v) (((v) >> 16) & 0xFFFF)/* UIC Commands */ -enum { +enum uic_cmd_dme {UIC_CMD_DME_GET =0x01 ,UIC_CMD_DME_SET =0x02 ,UIC_CMD_DME_PEER_GET =0x03 , --1.8.5.2
--
QUALCOMM ISRAEL, on behalf of Qualcomm Innovation Center, Inc. is a member of Code Aurora Forum, hosted by The Linux FoundationQuoted 4 lines
--
To unsubscribe from this list: send the line "unsubscribe linux-scsi" in the body of a message to [email protected] More majordomo info at
http://vger.kernel.org/majordomo-info.html--
To unsubscribe from this list: send the line "unsubscribe linux-scsi" in the body of a message to [email protected]
More majordomo info at http://vger.kernel.org/majordomo-info.html
Re: [PATCH/RFC V2 10/16] scsi: ufs: add UFS power management support
FYI: I won't read your mails until you actually make them readable by trimming the quotes back to provide just enough context.
Consider this mail (and the one you replied to) unread.
RE: [PATCH/RFC V2 10/16] scsi: ufs: add UFS power management support
In reply to Dong, ChuanxiaoHi all,
Thanks for reviewing the patches, I'm sorry for the late replay. I was waiting for some comments before I address all at once and re-send the series.
Quoted 79 lines
Quoted 67 lines
Quoted 31 lines
drivers/scsi/ufs/ufshcd.c +28
-2+/** +* 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) { -if (!hba) -return 0 ; +int ret; +enum uic_link_state old_link_state; + + 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; + +if (hba->vops && hba->vops->setup_clocks) { + ret = hba->vops->setup_clocks (hba, true); +if (ret) + goto disable_clks; + } + +/* enable the host irq as host controller would be active soon */ drivers/scsi/ufs/ufshcd.c static void ufshcd_hba_exit(struct ufs_hba *hba)
Quoted 10 lines
drivers/scsi/ufs/ufshcd.c +6
-2+ufshcd_enable_irq (hba); + + ret =ufshcd_vreg_set_hpm (hba); +if (ret) + goto disable_irq_and_vops_clks;/* -* The device is idle with no requests in the queue, -* allow background operations. +* Call vendor specific resume callback. As these callbacks may access
Quoted 25 lines
drivers/scsi/ufs/ufshcd.c +23
-1+* vendor specific host controller register space call them when the +* host clocks are ON. */ -return ufshcd_enable_auto_bkops (hba); +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; + }Quoted 8 lines
drivers/scsi/ufs/ufshcd.c static void ufshcd_hba_exit(struct ufs_hba *hba) · line 4463
Quoted 8 lines
drivers/scsi/ufs/ufshcd.c +8
-0+ +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);Should be auto background operation enabled again in
ufshcd_resume()? It is disabled duringufshcd_suspend().
Quoted 2 lines
Thanks
Chuanxiao
Chuanxiao,
If the system is going for runtime suspend, the bkops are enabled if necessary, and in any case disabled before the suspend. On resume we make sure, bkops are disabled.
Thanks,
Dolev
Quoted 14 lines
Quoted 9 lines
Quoted 2 lines
--
To unsubscribe from this list: send the line "unsubscribe linux-scsi"in the body
Quoted 2 lines
of a message to [email protected] More majordomo info at
http://vger.kernel.org/majordomo-info.html--
To unsubscribe from this list: send the line "unsubscribe linux-scsi" in the body of a message to [email protected]
More majordomo info at http://vger.kernel.org/majordomo-info.html--
To unsubscribe from this list: send the line "unsubscribe linux-scsi" in the body of a message to [email protected]
More majordomo info at http://vger.kernel.org/majordomo-info.html
Signature · 2 lines
QUALCOMM ISRAEL, on behalf of Qualcomm Innovation Center, Inc. is a member of Code Aurora Forum, hosted by The Linux Foundation
RE: [PATCH/RFC V2 10/16] scsi: ufs: add UFS power management support
In reply to Dong, ChuanxiaoHi all,
Thanks for reviewing the patches, I'm sorry for the late replay. I was waiting for some comments before I address all at once and re-send the series.
Quoted 16 lines
drivers/scsi/ufs/ufshcd.c static struct scsi_host_template ufshcd_driver_template = { · line 3735
Quoted 10 lines
+static int ufshcd_config_vreg_load(struct device *dev, struct
ufs_vreg*vreg,drivers/scsi/ufs/ufshcd.c +7
-0+ int ua) +{ +int ret =0 ; +struct regulator *reg = vreg->reg; +const char *name = vreg->name; + +BUG_ON (!vreg);For the UFS host controller driver, which doesn't have the
ufs_vregstructure for vcc and vccq, this function can cause kernel panic due to thisBUG_ON(). Any reason to put aBUG_ONhere instead of a NULL pointer checking?
Quoted 3 lines
Thanks
Chuanxiao
Sure, I'll fix this.
Thanks,
Dolev
Quoted 5 lines
--
To unsubscribe from this list: send the line "unsubscribe linux-scsi" in the body of a message to [email protected]
More majordomo info at http://vger.kernel.org/majordomo-info.html
Signature · 2 lines
QUALCOMM ISRAEL, on behalf of Qualcomm Innovation Center, Inc. is a member of Code Aurora Forum, hosted by The Linux Foundation
PATCH v2 11/16 scsi: ufs: refactor configuring power mode
In reply to Dolev RavivSometimes, the device shall report its maximum power and speed capabilities, but we might not wish to configure it to use those maximum capabilities.
This change adds support for the vendor specific host driver to implement power change notify callback.
To enable configuring different power modes (number of lanes, gear number and fast/slow modes) it is necessary to split the configuration stage from the stage that reads the device max power mode. In addition, it is not required to read the configuration more than once, thus the configuration is stored after reading it once.
drivers/scsi/ufs/ufshcd.c +111 -34
@@ -178,6 +178,8 @@ static int ufshcd_clear_tm_cmd(struct ufs_hba *hba, int tag);static void ufshcd_hba_exit (struct ufs_hba *hba);static int ufshcd_probe_hba (struct ufs_hba *hba);static int ufshcd_host_reset_and_restore (struct ufs_hba *hba); +static int ufshcd_config_pwr_mode (struct ufs_hba *hba, +struct ufs_pa_layer_attr *desired_pwr_mode);static inline void ufshcd_enable_irq (struct ufs_hba *hba) {
@@ -1933,65 +1935,129 @@ static int ufshcd_uic_hibern8_exit(struct ufs_hba *hba) }/** -* ufshcd_config_max_pwr_mode - Set & Change power mode with -* maximum capability attribute information. -* @hba: per adapter instance -* -* Returns 0 on success, non-zero value on failure +* ufshcd_get_max_pwr_mode - reads the max power mode negotiated with device +* @hba: per-adapter instance */ -static int ufshcd_config_max_pwr_mode (struct ufs_hba *hba) +static int ufshcd_get_max_pwr_mode (struct ufs_hba *hba) { -enum {RX =0 ,TX =1 }; -u32 lanes[] = {1 ,1 }; -u32 gear[] = {1 ,1 }; -u8 pwr[] = {FASTAUTO_MODE ,FASTAUTO_MODE }; -int ret; +struct ufs_pa_layer_attr *pwr_info = &hba->max_pwr_info.info; + +if (hba->max_pwr_info.is_valid) +return 0 ; + + pwr_info->pwr_tx =FASTAUTO_MODE ; + pwr_info->pwr_rx =FASTAUTO_MODE ; + pwr_info->hs_rate =PA_HS_MODE_B ;/* Get the connected lane count */ -ufshcd_dme_get (hba,UIC_ARG_MIB (PA_CONNECTEDRXDATALANES ), &lanes[RX ]); -ufshcd_dme_get (hba,UIC_ARG_MIB (PA_CONNECTEDTXDATALANES ), &lanes[TX ]); +ufshcd_dme_get (hba,UIC_ARG_MIB (PA_CONNECTEDRXDATALANES ), + &pwr_info->lane_rx); +ufshcd_dme_get (hba,UIC_ARG_MIB (PA_CONNECTEDTXDATALANES ), + &pwr_info->lane_tx); + +if (!pwr_info->lane_rx || !pwr_info->lane_tx) { +dev_err (hba->dev,"%s: invalid connected lanes value. rx=%d, tx=%d\n" , + __func__, + pwr_info->lane_rx, + pwr_info->lane_tx); +return -EINVAL ; + }/* * First, get the maximum gears of HS speed. * If a zero value, it means there is no HSGEAR capability. * Then, get the maximum gears of PWM speed. */ -ufshcd_dme_get (hba,UIC_ARG_MIB (PA_MAXRXHSGEAR ), &gear[RX ]); -if (!gear[RX ]) { -ufshcd_dme_get (hba,UIC_ARG_MIB (PA_MAXRXPWMGEAR ), &gear[RX ]); - pwr[RX ] =SLOWAUTO_MODE ; +ufshcd_dme_get (hba,UIC_ARG_MIB (PA_MAXRXHSGEAR ), &pwr_info->gear_rx); +if (!pwr_info->gear_rx) { +ufshcd_dme_get (hba,UIC_ARG_MIB (PA_MAXRXPWMGEAR ), + &pwr_info->gear_rx); +if (!pwr_info->gear_rx) { +dev_err (hba->dev,"%s: invalid max pwm rx gear read = %d\n" , + __func__, pwr_info->gear_rx); +return -EINVAL ; + } + pwr_info->pwr_rx =SLOWAUTO_MODE ; } -ufshcd_dme_peer_get (hba,UIC_ARG_MIB (PA_MAXRXHSGEAR ), &gear[TX ]); -if (!gear[TX ]) { +ufshcd_dme_peer_get (hba,UIC_ARG_MIB (PA_MAXRXHSGEAR ), + &pwr_info->gear_tx); +if (!pwr_info->gear_tx) {ufshcd_dme_peer_get (hba,UIC_ARG_MIB (PA_MAXRXPWMGEAR ), - &gear[TX ]); - pwr[TX ] =SLOWAUTO_MODE ; + &pwr_info->gear_tx); +if (!pwr_info->gear_tx) { +dev_err (hba->dev,"%s: invalid max pwm tx gear read = %d\n" , + __func__, pwr_info->gear_tx); +return -EINVAL ; + } + pwr_info->pwr_tx =SLOWAUTO_MODE ; } + hba->max_pwr_info.is_valid = true; +return 0 ; +} + +/** +* ufshcd_config_pwr_mode - configure a new power mode +* @hba: per-adapter instance +* @desired_pwr_mode: desired power configuration +*/ +static int ufshcd_config_pwr_mode (struct ufs_hba *hba, +struct ufs_pa_layer_attr *desired_pwr_mode) +{ +struct ufs_pa_layer_attr final_params = {0 }; +int ret; + +if (hba->vops->pwr_change_notify) + hba->vops->pwr_change_notify (hba, +PRE_CHANGE , desired_pwr_mode, &final_params); +else +memcpy (&final_params, desired_pwr_mode,sizeof (final_params)); +/* * Configure attributes for power mode change with below. * - PA_RXGEAR, PA_ACTIVERXDATALANES, PA_RXTERMINATION, * - PA_TXGEAR, PA_ACTIVETXDATALANES, PA_TXTERMINATION, * - PA_HSSERIES */ -ufshcd_dme_set (hba,UIC_ARG_MIB (PA_RXGEAR ), gear[RX ]); -ufshcd_dme_set (hba,UIC_ARG_MIB (PA_ACTIVERXDATALANES ), lanes[RX ]); -if (pwr[RX ] ==FASTAUTO_MODE ) +ufshcd_dme_set (hba,UIC_ARG_MIB (PA_RXGEAR ), final_params.gear_rx); +ufshcd_dme_set (hba,UIC_ARG_MIB (PA_ACTIVERXDATALANES ), + final_params.lane_rx); +if (final_params.pwr_rx ==FASTAUTO_MODE || + final_params.pwr_rx ==FAST_MODE )ufshcd_dme_set (hba,UIC_ARG_MIB (PA_RXTERMINATION ), TRUE); +else +ufshcd_dme_set (hba,UIC_ARG_MIB (PA_RXTERMINATION ), FALSE); -ufshcd_dme_set (hba,UIC_ARG_MIB (PA_TXGEAR ), gear[TX ]); -ufshcd_dme_set (hba,UIC_ARG_MIB (PA_ACTIVETXDATALANES ), lanes[TX ]); -if (pwr[TX ] ==FASTAUTO_MODE ) +ufshcd_dme_set (hba,UIC_ARG_MIB (PA_TXGEAR ), final_params.gear_tx); +ufshcd_dme_set (hba,UIC_ARG_MIB (PA_ACTIVETXDATALANES ), + final_params.lane_tx); +if (final_params.pwr_tx ==FASTAUTO_MODE || + final_params.pwr_tx ==FAST_MODE )ufshcd_dme_set (hba,UIC_ARG_MIB (PA_TXTERMINATION ), TRUE); - -if (pwr[RX ] ==FASTAUTO_MODE || pwr[TX ] ==FASTAUTO_MODE ) -ufshcd_dme_set (hba,UIC_ARG_MIB (PA_HSSERIES ),PA_HS_MODE_B ); - - ret =ufshcd_uic_change_pwr_mode (hba, pwr[RX ] <<4 | pwr[TX ]); -if (ret) +else +ufshcd_dme_set (hba,UIC_ARG_MIB (PA_TXTERMINATION ), FALSE); + +if ((final_params.pwr_rx ==FASTAUTO_MODE || + final_params.pwr_tx ==FASTAUTO_MODE || + final_params.pwr_rx ==FAST_MODE || + final_params.pwr_tx ==FAST_MODE ) && + final_params.hs_rate ==PA_HS_MODE_B ) +ufshcd_dme_set (hba,UIC_ARG_MIB (PA_HSSERIES ), + final_params.hs_rate); + + ret =ufshcd_uic_change_pwr_mode (hba, final_params.pwr_rx <<4 + | final_params.pwr_tx); +if (ret) {dev_err (hba->dev,"pwr_mode: power mode change failed %d\n" , ret); + }else { +if (hba->vops->pwr_change_notify) + hba->vops->pwr_change_notify (hba, +POST_CHANGE ,NULL , &final_params); + +memcpy (&hba->pwr_info, &final_params,sizeof (final_params)); + }return ret; }
@@ -3658,7 +3724,16 @@ static int ufshcd_probe_hba(struct ufs_hba *hba) hba->ufshcd_state =UFSHCD_STATE_OPERATIONAL ; hba->wlun_dev_clr_ua = true; -ufshcd_config_max_pwr_mode (hba); +if (ufshcd_get_max_pwr_mode (hba)) { +dev_err (hba->dev, +"%s: Failed getting max supported power mode\n" , + __func__); + }else { + ret =ufshcd_config_pwr_mode (hba, &hba->max_pwr_info.info); +if (ret) +dev_err (hba->dev,"%s: Failed setting power mode, err = %d\n" , + __func__, ret); + }/* * If we are in error handling context or in power management callbacks
@@ -4760,6 +4835,8 @@ int ufshcd_init(struct ufs_hba *hba, void __iomem *mmio_base, unsigned int irq) host->max_cmd_len =MAX_CDB_SIZE ; host->report_wlus =1 ; + hba->max_pwr_info.is_valid = false; +/* Initailize wait queue for task management */ init_waitqueue_head (&hba->tm_wq);init_waitqueue_head (&hba->tm_tag_wq);
drivers/scsi/ufs/ufshcd.h +27 -0
@@ -221,6 +221,22 @@ struct ufs_clk_info {#define PRE_CHANGE 0#define POST_CHANGE 1 + +struct ufs_pa_layer_attr { +u32 gear_rx; +u32 gear_tx; +u32 lane_rx; +u32 lane_tx; +u32 pwr_rx; +u32 pwr_tx; +u32 hs_rate; +}; + +struct ufs_pwr_mode_info { +bool is_valid; +struct ufs_pa_layer_attr info; +}; +/** * struct ufs_hba_variant_ops - variant specific callbacks * @name: variant name
@@ -232,6 +248,9 @@ 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. +* @pwr_change_notify: called before and after a power mode change +* is carried out to allow vendor spesific capabilities +* to be set. * @suspend: called during host controller PM callback * @resume: called during host controller PM callback */
@@ -243,6 +262,9 @@ 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 (*pwr_change_notify)(struct ufs_hba *, +bool ,struct ufs_pa_layer_attr *, +struct ufs_pa_layer_attr *);int (*suspend)(struct ufs_hba *,enum ufs_pm_op );int (*resume)(struct ufs_hba *,enum ufs_pm_op ); };
@@ -302,6 +324,8 @@ struct ufs_init_prefetch {* @auto_bkops_enabled: to track whether bkops is enabled in device * @vreg_info: UFS device voltage regulator information * @clk_list_head: UFS host controller clocks list node head +* @pwr_info: holds current power mode +* @max_pwr_info: keeps the device max valid pwm */ struct ufs_hba {void __iomem *mmio_base;
@@ -384,6 +408,9 @@ struct ufs_hba {struct list_head clk_list_head;bool wlun_dev_clr_ua; + +struct ufs_pa_layer_attr pwr_info; +struct ufs_pwr_mode_info max_pwr_info; };#define ufshcd_writel (hba, val, reg) \
Signature · 4 lines
1.8.5.2 -- QUALCOMM ISRAEL, on behalf of Qualcomm Innovation Center, Inc. is a member of Code Aurora Forum, hosted by The Linux Foundation
PATCH v2 12/16 scsi: ufs: Add support for clock gating
In reply to Dolev RavivFrom: Sahitya Tummala <[email protected]>
The UFS controller clocks can be gated after certain period of inactivity, which is typically less than runtime suspend timeout. In addition to clocks the link will also be put into Hibern8 mode to save more power.
The clock gating can be turned on by enabling the capability UFSHCD_CAP_CLK_GATING. To enable entering into Hibern8 mode as part of clock gating, set the capability UFSHCD_CAP_HIBERN8_WITH_CLK_GATING.
The tracing events for clock gating can be enabled through debugfs as:
echo 1 > /sys/kernel/debug/tracing/events/ufs/ufshcd_clk_gating/enable cat /sys/kernel/debug/tracing/trace_pipe
drivers/scsi/ufs/ufshcd.c +295 -25
@@ -177,6 +177,11 @@ static int ufshcd_reset_and_restore(struct ufs_hba *hba);static int ufshcd_clear_tm_cmd (struct ufs_hba *hba,int tag);static void ufshcd_hba_exit (struct ufs_hba *hba);static int ufshcd_probe_hba (struct ufs_hba *hba); +static int __ufshcd_setup_clocks (struct ufs_hba *hba,bool on, +bool skip_ref_clk); +static int ufshcd_setup_clocks (struct ufs_hba *hba,bool on); +static int ufshcd_uic_hibern8_exit (struct ufs_hba *hba); +static int ufshcd_uic_hibern8_enter (struct ufs_hba *hba);static int ufshcd_host_reset_and_restore (struct ufs_hba *hba);static int ufshcd_config_pwr_mode (struct ufs_hba *hba,struct ufs_pa_layer_attr *desired_pwr_mode);
@@ -498,6 +503,231 @@ static inline int ufshcd_is_hba_active(struct ufs_hba *hba)return (ufshcd_readl (hba,REG_CONTROLLER_ENABLE ) &0x1 ) ?0 :1 ; } +static void ufshcd_ungate_work (struct work_struct *work) +{ +int ret; +unsigned long flags; + struct ufs_hba *hba = container_of(work, struct ufs_hba, + clk_gating.ungate_work); + +cancel_delayed_work_sync (&hba->clk_gating.gate_work); + +spin_lock_irqsave (hba->host->host_lock, flags); +if (hba->clk_gating.state ==CLKS_ON ) { +spin_unlock_irqrestore (hba->host->host_lock, flags); + goto unblock_reqs; + } + +spin_unlock_irqrestore (hba->host->host_lock, flags); +ufshcd_setup_clocks (hba, true); + +/* Exit from hibern8 */ +if (ufshcd_can_hibern8_during_gating (hba)) { +/* Prevent gating in this path */ + hba->clk_gating.is_suspended = true; +if (ufshcd_is_link_hibern8 (hba)) { + ret =ufshcd_uic_hibern8_exit (hba); +if (ret) +dev_err (hba->dev,"%s: hibern8 exit failed %d\n" , + __func__, ret); +else +ufshcd_set_link_active (hba); + } + hba->clk_gating.is_suspended = false; + } +unblock_reqs: +scsi_unblock_requests (hba->host); +} + +/** +* ufshcd_hold - Enable clocks that were gated earlier due to ufshcd_release. +* Also, exit from hibern8 mode and set the link as active. +* @hba: per adapter instance +* @async: This indicates whether caller should ungate clocks asynchronously. +*/ +int ufshcd_hold (struct ufs_hba *hba,bool async) +{ +int rc =0 ; +unsigned long flags; + +if (!ufshcd_is_clkgating_allowed (hba)) + goto out; +start: +spin_lock_irqsave (hba->host->host_lock, flags); + hba->clk_gating.active_reqs++; + +switch (hba->clk_gating.state) { +case CLKS_ON : +break ; +case REQ_CLKS_OFF : +if (cancel_delayed_work (&hba->clk_gating.gate_work)) { + hba->clk_gating.state =CLKS_ON ; +break ; + } +/* +* If we here, it means gating work is either done or +* currently running. Hence, fall through to cancel gating +* work and to enable clocks. +*/ +case CLKS_OFF : +scsi_block_requests (hba->host); + hba->clk_gating.state =REQ_CLKS_ON ; +schedule_work (&hba->clk_gating.ungate_work); +/* +* fall through to check if we should wait for this +* work to be done or not. +*/ +case REQ_CLKS_ON : +if (async) { + rc = -EAGAIN ; + hba->clk_gating.active_reqs--; +break ; + }else { +spin_unlock_irqrestore (hba->host->host_lock, flags); +flush_work (&hba->clk_gating.ungate_work); +/* Make sure state is CLKS_ON before returning */ + goto start; + } +default : +dev_err (hba->dev,"%s: clk gating is in invalid state %d\n" , + __func__, hba->clk_gating.state); +break ; + } +spin_unlock_irqrestore (hba->host->host_lock, flags); +out: +return rc; +} + +static void ufshcd_gate_work (struct work_struct *work) +{ + struct ufs_hba *hba = container_of(work, struct ufs_hba, + clk_gating.gate_work.work); +unsigned long flags; + +spin_lock_irqsave (hba->host->host_lock, flags); +if (hba->clk_gating.is_suspended) { + hba->clk_gating.state =CLKS_ON ; + goto rel_lock; + } + +if (hba->clk_gating.active_reqs + || hba->ufshcd_state !=UFSHCD_STATE_OPERATIONAL + || hba->lrb_in_use || hba->outstanding_tasks + || hba->active_uic_cmd || hba->uic_async_done) + goto rel_lock; + +spin_unlock_irqrestore (hba->host->host_lock, flags); + +/* put the link into hibern8 mode before turning off clocks */ +if (ufshcd_can_hibern8_during_gating (hba)) { +if (ufshcd_uic_hibern8_enter (hba)) { + hba->clk_gating.state =CLKS_ON ; + goto out; + } +ufshcd_set_link_hibern8 (hba); + } + +if (!ufshcd_is_link_active (hba)) +ufshcd_setup_clocks (hba, false); +else +/* If link is active, device ref_clk can't be switched off */ +__ufshcd_setup_clocks (hba, false, true); + +/* +* In case you are here to cancel this work the gating state +* would be marked as REQ_CLKS_ON. In this case keep the state +* as REQ_CLKS_ON which would anyway imply that clocks are off +* and a request to turn them on is pending. By doing this way, +* we keep the state machine in tact and this would ultimately +* prevent from doing cancel work multiple times when there are +* new requests arriving before the current cancel work is done. +*/ +spin_lock_irqsave (hba->host->host_lock, flags); +if (hba->clk_gating.state ==REQ_CLKS_OFF ) { + hba->clk_gating.state =CLKS_OFF ; + } +rel_lock: +spin_unlock_irqrestore (hba->host->host_lock, flags); +out: +return ; +} + +/* host lock must be held before calling this variant */ +static void __ufshcd_release (struct ufs_hba *hba) +{ +if (!ufshcd_is_clkgating_allowed (hba)) +return ; + + hba->clk_gating.active_reqs--; + +if (hba->clk_gating.active_reqs || hba->clk_gating.is_suspended + || hba->ufshcd_state !=UFSHCD_STATE_OPERATIONAL + || hba->lrb_in_use || hba->outstanding_tasks + || hba->active_uic_cmd || hba->uic_async_done) +return ; + + hba->clk_gating.state =REQ_CLKS_OFF ; +schedule_delayed_work (&hba->clk_gating.gate_work, +msecs_to_jiffies (hba->clk_gating.delay_ms)); +} + +void ufshcd_release (struct ufs_hba *hba) +{ +unsigned long flags; + +spin_lock_irqsave (hba->host->host_lock, flags); +__ufshcd_release (hba); +spin_unlock_irqrestore (hba->host->host_lock, flags); +} + +static ssize_t ufshcd_clkgate_delay_show (struct device *dev, +struct device_attribute *attr,char *buf) +{ +struct ufs_hba *hba =dev_get_drvdata (dev); + +return snprintf (buf,PAGE_SIZE ,"%lu\n" , hba->clk_gating.delay_ms); +} + +static ssize_t ufshcd_clkgate_delay_store (struct device *dev, +struct device_attribute *attr,const char *buf,size_t count) +{ +struct ufs_hba *hba =dev_get_drvdata (dev); +unsigned long flags, value; + +if (kstrtoul (buf,0 , &value)) +return -EINVAL ; + +spin_lock_irqsave (hba->host->host_lock, flags); + hba->clk_gating.delay_ms = value; +spin_unlock_irqrestore (hba->host->host_lock, flags); +return count; +} + +static void ufshcd_init_clk_gating (struct ufs_hba *hba) +{ +if (!ufshcd_is_clkgating_allowed (hba)) +return ; + + hba->clk_gating.delay_ms =150 ; +INIT_DELAYED_WORK (&hba->clk_gating.gate_work, ufshcd_gate_work); +INIT_WORK (&hba->clk_gating.ungate_work, ufshcd_ungate_work); + + hba->clk_gating.delay_attr.show = ufshcd_clkgate_delay_show; + hba->clk_gating.delay_attr.store = ufshcd_clkgate_delay_store; +sysfs_attr_init (&hba->clk_gating.delay_attr.attr); + hba->clk_gating.delay_attr.attr.name ="clkgate_delay_ms" ; + hba->clk_gating.delay_attr.attr.mode =S_IRUGO |S_IWUSR ; +if (device_create_file (hba->dev, &hba->clk_gating.delay_attr)) +dev_err (hba->dev,"Failed to create sysfs for clkgate_delay\n" ); +} + +static void ufshcd_exit_clk_gating (struct ufs_hba *hba) +{ +if (!ufshcd_is_clkgating_allowed (hba)) +return ; +device_remove_file (hba->dev, &hba->clk_gating.delay_attr); +} +/** * ufshcd_send_command - Send SCSI or device management commands * @hba: per adapter instance
@@ -699,10 +929,12 @@ ufshcd_send_uic_cmd(struct ufs_hba *hba, struct uic_command *uic_cmd) {int ret; +ufshcd_hold (hba, false);mutex_lock (&hba->uic_cmd_mutex); ret =__ufshcd_send_uic_cmd (hba, uic_cmd);mutex_unlock (&hba->uic_cmd_mutex); +ufshcd_release (hba);return ret; }
@@ -1018,6 +1250,14 @@ static int ufshcd_queuecommand(struct Scsi_Host *host, struct scsi_cmnd *cmd) goto out; } + err =ufshcd_hold (hba, true); +if (err) { + err =SCSI_MLQUEUE_HOST_BUSY ; +clear_bit_unlock (tag, &hba->lrb_in_use); + goto out; + } +WARN_ON (hba->clk_gating.state !=CLKS_ON ); + lrbp = &hba->lrb[tag];WARN_ON (lrbp->cmd);
@@ -1293,6 +1533,7 @@ static int ufshcd_query_flag(struct ufs_hba *hba, enum query_opcode opcode,BUG_ON (!hba); +ufshcd_hold (hba, false);mutex_lock (&hba->dev_cmd.lock);ufshcd_init_query (hba, &request, &response, opcode, idn, index, selector);
@@ -1336,6 +1577,7 @@ static int ufshcd_query_flag(struct ufs_hba *hba, enum query_opcode opcode, out_unlock:mutex_unlock (&hba->dev_cmd.lock); +ufshcd_release (hba);return err; }
@@ -1359,6 +1601,7 @@ static int ufshcd_query_attr(struct ufs_hba *hba, enum query_opcode opcode,BUG_ON (!hba); +ufshcd_hold (hba, false);if (!attr_val) {dev_err (hba->dev,"%s: attribute value required for opcode 0x%x\n" , __func__, opcode);
@@ -1398,6 +1641,7 @@ static int ufshcd_query_attr(struct ufs_hba *hba, enum query_opcode opcode, out_unlock:mutex_unlock (&hba->dev_cmd.lock); out: +ufshcd_release (hba);return err; }
@@ -1425,6 +1669,7 @@ static int ufshcd_query_descriptor(struct ufs_hba *hba,BUG_ON (!hba); +ufshcd_hold (hba, false);if (!desc_buf) {dev_err (hba->dev,"%s: descriptor buffer required for opcode 0x%x\n" , __func__, opcode);
@@ -1474,6 +1719,7 @@ static int ufshcd_query_descriptor(struct ufs_hba *hba, out_unlock:mutex_unlock (&hba->dev_cmd.lock); out: +ufshcd_release (hba);return err; }
@@ -1852,6 +2098,7 @@ int ufshcd_uic_pwr_ctrl(struct ufs_hba *hba, struct uic_command *cmd)u8 status;int ret; +ufshcd_hold (hba, false);mutex_lock (&hba->uic_cmd_mutex);init_completion (&uic_async_done);
@@ -1888,6 +2135,8 @@ out: hba->uic_async_done =NULL ;spin_unlock_irqrestore (hba->host->host_lock, flags);mutex_unlock (&hba->uic_cmd_mutex); + +ufshcd_release (hba);return ret; }
@@ -2306,6 +2555,7 @@ static int ufshcd_verify_dev_init(struct ufs_hba *hba)int err =0 ;int retries; +ufshcd_hold (hba, false);mutex_lock (&hba->dev_cmd.lock);for (retries =NOP_OUT_RETRIES ; retries >0 ; retries--) { err = ufshcd_exec_dev_cmd(hba, DEV_CMD_TYPE_NOP,
@@ -2317,6 +2567,7 @@ static int ufshcd_verify_dev_init(struct ufs_hba *hba)dev_dbg (hba->dev,"%s: error %d retrying\n" , __func__, err); }mutex_unlock (&hba->dev_cmd.lock); +ufshcd_release (hba);if (err)dev_err (hba->dev,"%s: NOP OUT failed %d\n" , __func__, err);
@@ -2729,6 +2980,7 @@ static void ufshcd_transfer_req_compl(struct ufs_hba *hba)clear_bit_unlock (index, &hba->lrb_in_use);/* Do not touch lrbp after scsi done */ cmd->scsi_done (cmd); +__ufshcd_release (hba); }else if (lrbp->command_type ==UTP_CMD_TYPE_DEV_MANAGE ) {if (hba->dev_cmd.complete)complete (hba->dev_cmd.complete);
@@ -3013,6 +3265,7 @@ static void ufshcd_err_handler(struct work_struct *work) hba = container_of(work, struct ufs_hba, eh_work);pm_runtime_get_sync (hba->dev); +ufshcd_hold (hba, false);spin_lock_irqsave (hba->host->host_lock, flags);if (hba->ufshcd_state ==UFSHCD_STATE_RESET ) {
@@ -3066,6 +3319,7 @@ static void ufshcd_err_handler(struct work_struct *work) out:scsi_unblock_requests (hba->host); +ufshcd_release (hba);pm_runtime_put_sync (hba->dev); }
@@ -3249,6 +3503,7 @@ static int ufshcd_issue_tm_cmd(struct ufs_hba *hba, int lun_id, int task_id,* the maximum wait time is bounded by %TM_CMD_TIMEOUT. */ wait_event (hba->tm_tag_wq,ufshcd_get_tm_free_slot (hba, &free_slot)); +ufshcd_hold (hba, false);spin_lock_irqsave (host->host_lock, flags); task_req_descp = hba->utmrdl_base_addr;
@@ -3300,6 +3555,7 @@ static int ufshcd_issue_tm_cmd(struct ufs_hba *hba, int lun_id, int task_id,ufshcd_put_tm_slot (hba, free_slot);wake_up (&hba->tm_tag_wq); +ufshcd_release (hba);return err; }
@@ -3382,6 +3638,7 @@ static int ufshcd_abort(struct scsi_cmnd *cmd) hba =shost_priv (host); tag = cmd->request->tag; +ufshcd_hold (hba, false);/* If command is already aborted/completed, return SUCCESS */ if (!(test_bit (tag, &hba->outstanding_reqs))) goto out;
@@ -3446,6 +3703,11 @@ static int ufshcd_abort(struct scsi_cmnd *cmd)clear_bit_unlock (tag, &hba->lrb_in_use);wake_up (&hba->dev_cmd.tag_wq); +/* +* This ufshcd_release() corresponds to the original scsi cmd that got +* aborted here (as we won't get any IRQ for it). +*/ +ufshcd_release (hba); out:if (!err) { err =SUCCESS ;
@@ -3453,7 +3715,7 @@ out:dev_err (hba->dev,"%s: failed with err %d\n" , __func__, err); err =FAILED ; } - +ufshcd_release (hba);return err; }
@@ -3538,6 +3800,7 @@ static int ufshcd_eh_host_reset_handler(struct scsi_cmnd *cmd) hba =shost_priv (cmd->device->host); +ufshcd_hold (hba, false);/* * Check if there is any race with fatal error handling. * If so, wait for it to complete. Even though fatal error
@@ -3571,6 +3834,7 @@ static int ufshcd_eh_host_reset_handler(struct scsi_cmnd *cmd)ufshcd_clear_eh_in_progress (hba);spin_unlock_irqrestore (hba->host->host_lock, flags); +ufshcd_release (hba);return err; }
@@ -3798,6 +4062,7 @@ static struct scsi_host_template ufshcd_driver_template = { .sg_tablesize = SG_ALL, .cmd_per_lun = UFSHCD_CMD_PER_LUN, .can_queue = UFSHCD_CAN_QUEUE, + .max_host_blocked = 1, }; static int ufshcd_config_vreg_load(struct device *dev, struct ufs_vreg *vreg,
@@ -3980,6 +4245,7 @@ static int __ufshcd_setup_clocks(struct ufs_hba *hba, bool on,int ret =0 ;struct ufs_clk_info *clki;struct list_head *head = &hba->clk_list_head; +unsigned long flags;if (!head ||list_empty (head)) goto out;
@@ -4004,12 +4270,19 @@ static int __ufshcd_setup_clocks(struct ufs_hba *hba, bool on, clki->name, on ? "en" : "dis"); } } + +if (hba->vops && hba->vops->setup_clocks) + ret = hba->vops->setup_clocks (hba, on); out:if (ret) {list_for_each_entry (clki, head, list) {if (!IS_ERR_OR_NULL (clki->clk) && clki->enabled)clk_disable_unprepare (clki->clk); } + }else if (!ret && on) { +spin_lock_irqsave (hba->host->host_lock, flags); + hba->clk_gating.state =CLKS_ON ; +spin_unlock_irqrestore (hba->host->host_lock, flags); }return ret; }
@@ -4070,23 +4343,14 @@ static int ufshcd_variant_hba_init(struct ufs_hba *hba) goto out; } -if (hba->vops->setup_clocks) { - err = hba->vops->setup_clocks (hba, true); -if (err) - goto out_exit; - } -if (hba->vops->setup_regulators) { err = hba->vops->setup_regulators (hba, true);if (err) - goto out_clks; + goto out_exit; } goto out; -out_clks: -if (hba->vops->setup_clocks) - hba->vops->setup_clocks (hba, false); out_exit:if (hba->vops->exit) hba->vops->exit (hba);
@@ -4378,6 +4642,9 @@ static int ufshcd_suspend(struct ufs_hba *hba, enum ufs_pm_op pm_op)* If we can't transition into any of the low power modes * just gate the clocks. */ +ufshcd_hold (hba, false); + hba->clk_gating.is_suspended = true; +if (req_dev_pwr_mode ==UFS_ACTIVE_PWR_MODE && req_link_state ==UIC_LINK_ACTIVE_STATE ) { goto disable_clks;
@@ -4400,7 +4667,7 @@ static int ufshcd_suspend(struct ufs_hba *hba, enum ufs_pm_op pm_op)*/ ret =ufshcd_bkops_ctrl (hba,BKOPS_STATUS_NON_CRITICAL );if (ret) - goto out; + goto enable_gating; } if ((req_dev_pwr_mode != hba->curr_dev_pwr_mode) &&
@@ -4410,7 +4677,7 @@ static int ufshcd_suspend(struct ufs_hba *hba, enum ufs_pm_op pm_op)ufshcd_disable_auto_bkops (hba); ret =ufshcd_set_dev_pwr_mode (hba, req_dev_pwr_mode);if (ret) - goto out; + goto enable_gating; } ret =ufshcd_link_state_transition (hba, req_link_state,1 );
@@ -4443,6 +4710,7 @@ disable_clks:/* If link is active, device ref_clk can't be switched off */ __ufshcd_setup_clocks (hba, false, true); + hba->clk_gating.state =CLKS_OFF ;/* * Disable the host irq as host controller as there won't be any * host controller trasanction expected till resume.
@@ -4462,6 +4730,9 @@ set_link_active: set_dev_active:if (!ufshcd_set_dev_pwr_mode (hba,UFS_ACTIVE_PWR_MODE ))ufshcd_disable_auto_bkops (hba); +enable_gating: + hba->clk_gating.is_suspended = false; +ufshcd_release (hba); out: hba->pm_op_in_progress =0 ;return ret;
@@ -4489,12 +4760,6 @@ static int ufshcd_resume(struct ufs_hba *hba, enum ufs_pm_op pm_op)if (ret) goto out; -if (hba->vops && hba->vops->setup_clocks) { - ret = hba->vops->setup_clocks (hba, true); -if (ret) - goto disable_clks; - } -/* enable the host irq as host controller would be active soon */ ufshcd_enable_irq (hba);
@@ -4536,6 +4801,10 @@ static int ufshcd_resume(struct ufs_hba *hba, enum ufs_pm_op pm_op) }ufshcd_disable_auto_bkops (hba); + hba->clk_gating.is_suspended = false; + +/* Schedule clock gating in case of no access to UFS device yet */ +ufshcd_release (hba); goto out; set_old_link_state:
@@ -4547,9 +4816,6 @@ disable_vreg:ufshcd_vreg_set_lpm (hba); disable_irq_and_vops_clks:ufshcd_disable_irq (hba); -if (hba->vops && hba->vops->setup_clocks) - ret = hba->vops->setup_clocks (hba, false); -disable_clks:ufshcd_setup_clocks (hba, false); out: hba->pm_op_in_progress =0 ;
@@ -4718,6 +4984,7 @@ void ufshcd_remove(struct ufs_hba *hba)scsi_host_put (hba->host); +ufshcd_exit_clk_gating (hba);ufshcd_hba_exit (hba); }EXPORT_SYMBOL_GPL (ufshcd_remove);
@@ -4854,11 +5121,12 @@ int ufshcd_init(struct ufs_hba *hba, void __iomem *mmio_base, unsigned int irq)/* Initialize device management tag acquire wait queue */ init_waitqueue_head (&hba->dev_cmd.tag_wq); +ufshcd_init_clk_gating (hba);/* IRQ registration */ err =devm_request_irq (dev, irq, ufshcd_intr,IRQF_SHARED ,UFSHCD , hba);if (err) {dev_err (hba->dev,"request irq failed\n" ); - goto out_disable; + goto exit_gating; }else { hba->is_irq_enabled = true; }
@@ -4867,13 +5135,13 @@ int ufshcd_init(struct ufs_hba *hba, void __iomem *mmio_base, unsigned int irq) err =scsi_init_shared_tag_map (host, host->can_queue);if (err) {dev_err (hba->dev,"init shared queue failed\n" ); - goto out_disable; + goto exit_gating; } err =scsi_add_host (host, hba->dev);if (err) {dev_err (hba->dev,"scsi_add_host failed\n" ); - goto out_disable; + goto exit_gating; }/* Host controller enable */
@@ -4898,6 +5166,8 @@ int ufshcd_init(struct ufs_hba *hba, void __iomem *mmio_base, unsigned int irq) out_remove_scsi_host:scsi_remove_host (hba->host); +exit_gating: +ufshcd_exit_clk_gating (hba); out_disable: hba->is_irq_enabled = false;scsi_host_put (host);
drivers/scsi/ufs/ufshcd.h +51 -0
69 lines not on this page · raw message
Signature · 4 lines
1.8.5.2 -- QUALCOMM ISRAEL, on behalf of Qualcomm Innovation Center, Inc. is a member of Code Aurora Forum, hosted by The Linux Foundation
PATCH v2 13/16 scsi: ufs: Add freq-table-hz property for UFS device
In reply to Dolev RavivFrom: Sahitya Tummala <[email protected]>
Add freq-table-hz propery for UFS device to keep track of <min max> frequencies supported by UFS clocks.
Documentation/devicetree/bindings/ufs/ufshcd-pltfrm.txt +6 -6
@@ -24,11 +24,11 @@ Optional properties: - clocks : List of phandle and clock specifier pairs - clock-names : List of clock input name strings sorted in the same order as the clocks property. -- max-clock-frequency-hz : List of maximum operating frequency stored in the same - order as the clocks property. If this property is not - defined or a value in the array is "0" then it is assumed - that the frequency is set by the parent clock or a - fixed rate clock source. +- freq-table-hz : Array of <min max> operating frequencies stored in the same + order as the clocks property. If this property is not + defined or a value in the array is "0" then it is assumed + that the frequency is set by the parent clock or a + fixed rate clock source. Note: If above properties are not defined it can be assumed that the supply regulators or clocks are always on.
@@ -49,5 +49,5 @@ Example: clocks = <&core 0>, <&ref 0>, <&iface 0>; clock-names = "core_clk", "ref_clk", "iface_clk"; - max-clock-frequency-hz = <100000000 19200000 0>; + freq-table-hz = <100000000 200000000>, <0 0>, <0 0>; };
drivers/scsi/ufs/ufshcd-pltfrm.c +35 -13
80 lines not on this page · raw message
drivers/scsi/ufs/ufshcd.h +2 -0
@@ -209,6 +209,7 @@ struct ufs_dev_cmd {* @clk: clock node * @name: clock name * @max_freq: maximum frequency supported by the clock +* @min_freq: min frequency that can be used for clock scaling * @enabled: variable to check against multiple enable/disable */ struct ufs_clk_info {
@@ -216,6 +217,7 @@ struct ufs_clk_info {struct clk *clk;const char *name;u32 max_freq; +u32 min_freq;bool enabled; };
Signature · 4 lines
1.8.5.2 -- QUALCOMM ISRAEL, on behalf of Qualcomm Innovation Center, Inc. is a member of Code Aurora Forum, hosted by The Linux Foundation
PATCH v2 14/16 scsi: ufs: Add support for clock scaling using devfreq framework
In reply to Dolev RavivFrom: Sahitya Tummala <[email protected]>
The clocks for UFS device will be managed by generic DVFS (Dynamic Voltage and Frequency Scaling) framework within kernel. This devfreq framework works with different governors to scale the clocks. By default, UFS devices uses simple_ondemand governor which scales the clocks up if the load is more than upthreshold and scales down if the load is less than downthreshold.
drivers/scsi/ufs/Kconfig +2 -0
@@ -35,6 +35,8 @@ config SCSI_UFSHCD tristate "Universal Flash Storage Controller Driver Core" depends on SCSI && SCSI_DMA + select PM_DEVFREQ + select DEVFREQ_GOV_SIMPLE_ONDEMAND ---help--- This selects the support for UFS devices in Linux, say Y and make sure that you know the name of your UFS host adapter (the card
drivers/scsi/ufs/ufshcd.c +178 -1
278 lines not on this page · raw message
drivers/scsi/ufs/ufshcd.h +20 -0
@@ -210,6 +210,7 @@ struct ufs_dev_cmd {* @name: clock name * @max_freq: maximum frequency supported by the clock * @min_freq: min frequency that can be used for clock scaling +* @curr_freq: indicates the current frequency that it is set to * @enabled: variable to check against multiple enable/disable */ struct ufs_clk_info {
@@ -218,6 +219,7 @@ struct ufs_clk_info {const char *name;u32 max_freq;u32 min_freq; +u32 curr_freq;bool enabled; };
@@ -244,6 +246,7 @@ struct ufs_pwr_mode_info {* @name: variant name * @init: called when the driver is initialized * @exit: called to cleanup everything done in init +* @clk_scale_notify: notifies that clks are scaled up/down * @setup_clocks: called before touching any of the controller registers * @setup_regulators: called before accessing the host controller * @hce_enable_notify: called before and after HCE enable bit is set to allow
@@ -260,6 +263,7 @@ struct ufs_hba_variant_ops {const char *name;int (*init)(struct ufs_hba *);void (*exit)(struct ufs_hba *); +void (*clk_scale_notify)(struct ufs_hba *);int (*setup_clocks)(struct ufs_hba *,bool );int (*setup_regulators)(struct ufs_hba *,bool );int (*hce_enable_notify)(struct ufs_hba *,bool );
@@ -303,6 +307,13 @@ struct ufs_clk_gating {int active_reqs; }; +struct ufs_clk_scaling { +ktime_t busy_start_t; +bool is_busy_started; +unsigned long tot_busy_t; +unsigned long window_start_t; +}; +/** * struct ufs_init_prefetch - contains data that is pre-fetched once during * initialization
@@ -453,6 +464,11 @@ struct ufs_hba {#define UFSHCD_CAP_CLK_GATING (1 << 0)/* Allow hiberb8 with clk gating */ #define UFSHCD_CAP_HIBERN8_WITH_CLK_GATING (1 << 1) +/* Allow dynamic clk scaling */ +#define UFSHCD_CAP_CLK_SCALING (1 << 2) + +struct devfreq *devfreq; +struct ufs_clk_scaling clk_scaling; };/* Returns true if clocks can be gated. Otherwise false */
@@ -464,6 +480,10 @@ static inline bool ufshcd_can_hibern8_during_gating(struct ufs_hba *hba) {return hba->caps &UFSHCD_CAP_HIBERN8_WITH_CLK_GATING ; } +static inline int ufshcd_is_clkscaling_enabled (struct ufs_hba *hba) +{ +return hba->caps &UFSHCD_CAP_CLK_SCALING ; +}#define ufshcd_writel (hba, val, reg) \ writel((val), (hba)->mmio_base + (reg))#define ufshcd_readl (hba, reg) \
Signature · 4 lines
1.8.5.2 -- QUALCOMM ISRAEL, on behalf of Qualcomm Innovation Center, Inc. is a member of Code Aurora Forum, hosted by The Linux Foundation
PATCH v2 15/16 scsi: ufs: add capability to control the auto bkops during suspend
In reply to Dolev RavivFrom: Subhash Jadavani <[email protected]>
If host explicitly enables the auto bkops (background operation) on device then only device would perform the bkops on its own. If auto bkops is not enabled explicitly and if the device reaches to state where it must do background operation, device would raise the urgent bkops exception event to host and then host will enable the auto bkops on device. This patch adds the option to choose whether auto bkops should be enabled during runtime suspend or not.
drivers/scsi/ufs/ufshcd.c +12 -7
@@ -4704,13 +4704,18 @@ static int ufshcd_suspend(struct ufs_hba *hba, enum ufs_pm_op pm_op) }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 enable_gating; +if (ufshcd_can_autobkops_during_suspend (hba)) { +/* +* 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 enable_gating; + }else { +/* make sure that auto bkops is disabled */ +ufshcd_disable_auto_bkops (hba); + } } if ((req_dev_pwr_mode != hba->curr_dev_pwr_mode) &&
drivers/scsi/ufs/ufshcd.h +7 -0
@@ -466,6 +466,8 @@ struct ufs_hba {#define UFSHCD_CAP_HIBERN8_WITH_CLK_GATING (1 << 1)/* Allow dynamic clk scaling */ #define UFSHCD_CAP_CLK_SCALING (1 << 2) +/* Allow auto bkops to enabled during runtime suspend */ +#define UFSHCD_CAP_AUTO_BKOPS_SUSPEND (1 << 3)struct devfreq *devfreq;struct ufs_clk_scaling clk_scaling;
@@ -484,6 +486,11 @@ static inline int ufshcd_is_clkscaling_enabled(struct ufs_hba *hba) {return hba->caps &UFSHCD_CAP_CLK_SCALING ; } +static inline bool ufshcd_can_autobkops_during_suspend (struct ufs_hba *hba) +{ +return hba->caps &UFSHCD_CAP_AUTO_BKOPS_SUSPEND ; +} +#define ufshcd_writel (hba, val, reg) \ writel((val), (hba)->mmio_base + (reg))#define ufshcd_readl (hba, reg) \
Signature · 4 lines
1.8.5.2 -- QUALCOMM ISRAEL, on behalf of Qualcomm Innovation Center, Inc. is a member of Code Aurora Forum, hosted by The Linux Foundation
PATCH v2 16/16 scsi: ufs: definitions for phy interface
In reply to Dolev Raviv- Adding some of the definitions missing in unipro.h, including power enumeration.
- Read Modify Write Line helper function
- Indication for the type of suspend
drivers/scsi/ufs/ufshcd.c +2 -0
@@ -4924,6 +4924,8 @@ int ufshcd_system_suspend(struct ufs_hba *hba) ret =ufshcd_suspend (hba,UFS_SYSTEM_PM ); out: +if (!ret) + hba->is_sys_suspended = true;return ret; }EXPORT_SYMBOL (ufshcd_system_suspend);
drivers/scsi/ufs/ufshcd.h +18 -0
@@ -471,6 +471,7 @@ struct ufs_hba {struct devfreq *devfreq;struct ufs_clk_scaling clk_scaling; +bool is_sys_suspended; };/* Returns true if clocks can be gated. Otherwise false */
@@ -496,6 +497,23 @@ static inline bool ufshcd_can_autobkops_during_suspend(struct ufs_hba *hba)#define ufshcd_readl (hba, reg) \ readl((hba)->mmio_base + (reg)) +/** +* ufshcd_rmwl - read modify write into a register +* @hba - per adapter instance +* @mask - mask to apply on read value +* @val - actual value to write +* @reg - register address +*/ +static inline void ufshcd_rmwl (struct ufs_hba *hba,u32 mask,u32 val,u32 reg) +{ +u32 tmp; + + tmp =ufshcd_readl (hba, reg); + tmp &= ~mask; + tmp |= (val & mask); +ufshcd_writel (hba, tmp, reg); +} +int ufshcd_alloc_host (struct device *,struct ufs_hba **);int ufshcd_init (struct ufs_hba * ,void __iomem * ,unsigned int );void ufshcd_remove (struct ufs_hba *);
drivers/scsi/ufs/unipro.h +56 -0
68 lines not on this page · raw message
Signature · 4 lines
1.8.5.2 -- QUALCOMM ISRAEL, on behalf of Qualcomm Innovation Center, Inc. is a member of Code Aurora Forum, hosted by The Linux Foundation