UFS: Power management support
Subsystems (5)
v4 cover UFS: Power management support
This patch seies introduces support for power management in the driver as well as vendor specific initialization - registers, clocks, voltage regulators etc.
It includes also a rework for the init sequence and other PM pre-requisite such as write protection support, handling well-known LUN, error handling (retries), bkops, START_STOP unit command, and ICC levels settings.
--
Changes from V2:
- Replaced "[01/16] scsi: support well known logical units" with "[09/17] scsi: ufs: manually add well known logical
- add patch "[PATCH V4 05/17] scsi: ufs: add voting support for host"
- Fix couple of compilation issues introduced in V3
- Add a NULL pointer test to hba->vops before accessing it
- Changed disable/enable irq with request/free irq
Changes from V2:
- Reordered scsi core patches
- add patch "[PATCH V3 02/16] scsi: balance out autopm get/put calls in"
- Minor changes/fixes to the patches:
* "[PATCH V3 10/16] scsi: ufs: add UFS power management support"
* "[PATCH V3 12/16] scsi: ufs: Add support for clock gating"
* "[PATCH V3 16/16] scsi: ufs: definitions for phy interface"
In order to address community concerns, and as a result of further development and testing.
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
Raviv Shvili (1):
scsi: ufs: add voting support for host controller power
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 (6):
scsi: balance out autopm get/put calls in scsi_sysfs_add_sdev()
scsi: ufs: refactor query descriptor API support
scsi: ufs: manually add well known logical units
scsi: ufs: introduce well known logical unit in ufs
scsi: ufs: add UFS power management support
scsi: ufs: tune bkops while power managment events
Sujit Reddy Thumma (4):
scsi: ufs: Allow vendor specific initialization
scsi: ufs: Add regulator enable support
scsi: ufs: Add clock initialization support
scsi: ufs: improve init sequence
Yaniv Gardi (1):
scsi: ufs: Active Power Mode - configuring bActiveICCLevel
.../devicetree/bindings/ufs/ufshcd-pltfrm.txt | 41 + drivers/scsi/scsi_sysfs.c | 5 +- drivers/scsi/sd.c | 2 + drivers/scsi/sr.c | 2 + drivers/scsi/st.c | 2 + drivers/scsi/ufs/Kconfig | 2 + drivers/scsi/ufs/ufs.h | 132 +- drivers/scsi/ufs/ufshcd-pci.c | 55 +- drivers/scsi/ufs/ufshcd-pltfrm.c | 291 ++- drivers/scsi/ufs/ufshcd.c | 2481 ++++++++++++++++++-- drivers/scsi/ufs/ufshcd.h | 277 ++- drivers/scsi/ufs/ufshci.h | 9 +- drivers/scsi/ufs/unipro.h | 56 + 13 files changed, 3047 insertions(+), 308 deletions(-)
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 v4 1/17 scsi: balance out autopm get/put calls in scsi_sysfs_add_sdev()
From: Subhash Jadavani <[email protected]>
SCSI Well-known logical units generally don't have any scsi driver associated with it which means no one will call scsi_autopm_put_device() on these wlun scsi devices and this would result in keeping the corresponding scsi device always active (hence LLD can't be suspended as well). Same exact problem can be seen for other scsi device representing normal logical unit whose driver is yet to be loaded. This patch fixes the above problem with this approach:
- make the scsi_autopm_put_device call at the end of scsi_sysfs_add_sdev to make it balance out the get earlier in the function.
- let drivers do paired get/put calls in their probe methods.
drivers/scsi/scsi_sysfs.c +1 -4
@@ -1044,10 +1044,6 @@ int scsi_sysfs_add_sdev(struct scsi_device *sdev)pm_runtime_enable (&sdev->sdev_gendev);scsi_autopm_put_target (starget); -/* The following call will keep sdev active indefinitely, until -* its driver does a corresponding scsi_autopm_pm_device(). Only -* drivers supporting autosuspend will do this. -*/ scsi_autopm_get_device (sdev); error =device_add (&sdev->sdev_gendev);
@@ -1085,6 +1081,7 @@ int scsi_sysfs_add_sdev(struct scsi_device *sdev) } } +scsi_autopm_put_device (sdev);return error; }
drivers/scsi/sd.c +2 -0
@@ -2965,6 +2965,7 @@ static int sd_probe(struct device *dev)int index;int error; +scsi_autopm_get_device (sdp); error = -ENODEV ;if (sdp->type !=TYPE_DISK && sdp->type !=TYPE_MOD && sdp->type !=TYPE_RBC ) goto out;
@@ -3041,6 +3042,7 @@ static int sd_probe(struct device *dev) out_free:kfree (sdkp); out: +scsi_autopm_put_device (sdp);return error; }
drivers/scsi/sr.c +2 -0
@@ -657,6 +657,7 @@ static int sr_probe(struct device *dev)struct scsi_cd *cd;int minor, error; +scsi_autopm_get_device (sdev); error = -ENODEV ;if (sdev->type !=TYPE_ROM && sdev->type !=TYPE_WORM ) goto fail;
@@ -744,6 +745,7 @@ fail_put: fail_free:kfree (cd); fail: +scsi_autopm_put_device (sdev);return error; }
drivers/scsi/st.c +2 -0
@@ -4105,6 +4105,7 @@ static int st_probe(struct device *dev)return -ENODEV ; } +scsi_autopm_get_device (SDp); i =queue_max_segments (SDp->request_queue);if (st_max_sg_segs < i) i = st_max_sg_segs;
@@ -4244,6 +4245,7 @@ out_put_disk: out_buffer_free:kfree (buffer); out: +scsi_autopm_put_device (SDp);return -ENODEV ; };
Signature · 5 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 v4 2/17 scsi: ufs: Allow vendor specific initialization
In reply to Dolev RavivFrom: 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 · 5 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 v4 3/17 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 · 5 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 v4 4/17 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 · 5 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 v4 5/17 scsi: ufs: add voting support for host controller power
In reply to Dolev RavivFrom: Raviv Shvili <[email protected]>
Add the support for voting of the regulator powering the host controller logic.
Documentation/devicetree/bindings/ufs/ufshcd-pltfrm.txt +4 -0
@@ -9,6 +9,7 @@ Required properties: - reg : <registers mapping> Optional properties: +- vdd-hba-supply : phandle to UFS host controller supply regulator node - vcc-supply : phandle to VCC supply regulator node - vccq-supply : phandle to VCCQ supply regulator node - vccq2-supply : phandle to VCCQ2 supply regulator node
@@ -20,6 +21,7 @@ Optional properties: - 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 +- <name>-fixed-regulator : boolean property specifying that <name>-supply is a fixed regulator - clocks : List of phandle and clock specifier pairs - clock-names : List of clock input name strings sorted in the same
@@ -39,6 +41,8 @@ Example: reg = <0xfc598000 0x800>; interrupts = <0 28 0>; + vdd-hba-supply = <&xxx_reg0>; + vdd-hba-fixed-regulator; vcc-supply = <&xxx_reg1>; vcc-supply-1p8; vccq-supply = <&xxx_reg2>;
drivers/scsi/ufs/ufs.h +1 -0
@@ -385,6 +385,7 @@ struct ufs_vreg_info {struct ufs_vreg *vcc;struct ufs_vreg *vccq;struct ufs_vreg *vccq2; + struct ufs_vreg *vdd_hba; };#endif /* End of Header */
drivers/scsi/ufs/ufshcd-pltfrm.c +9 -0
@@ -147,6 +147,11 @@ static int ufshcd_populate_vreg(struct device *dev, const char *name, vreg->name =kstrdup (name,GFP_KERNEL ); +/* if fixed regulator no need further initialization */ +snprintf (prop_name,MAX_PROP_SIZE ,"%s-fixed-regulator" , name); +if (of_property_read_bool (np, prop_name)) + goto out; +snprintf (prop_name,MAX_PROP_SIZE ,"%s-max-microamp" , name); ret =of_property_read_u32 (np, prop_name, &vreg->max_uA);if (ret) {
@@ -198,6 +203,10 @@ static int ufshcd_parse_regulator_info(struct ufs_hba *hba)struct device *dev = hba->dev;struct ufs_vreg_info *info = &hba->vreg_info; + err =ufshcd_populate_vreg (dev,"vdd-hba" , &info->vdd_hba); +if (err) + goto out; + err =ufshcd_populate_vreg (dev,"vcc" , &info->vcc);if (err) goto out;
drivers/scsi/ufs/ufshcd.c +40 -2
78 lines not on this page · raw message
Signature · 5 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 v4 6/17 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 · 5 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 v4 7/17 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 +73 -28
@@ -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; + } -/* 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 ; +/* +* 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--); + +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 = {
@@ -3631,6 +3672,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:
@@ -3645,10 +3687,13 @@ 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); -ufshcd_setup_hba_vreg (hba, false); +if (hba->is_powered) { +ufshcd_variant_hba_exit (hba); +ufshcd_setup_vreg (hba, false); +ufshcd_setup_clocks (hba, false); +ufshcd_setup_hba_vreg (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 · 5 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 v4 8/17 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 · 5 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 v4 9/17 scsi: ufs: manually add well known logical units
In reply to Dolev RavivFrom: Subhash Jadavani <[email protected]>
According to SELECT REPORT field (of REPORT LUNS command) from SPC-4 (SCSI Primary Commands) specification, device should report all the LUNs (which support any of the addressing method specified in the description). Note that UFS W-LUs follow the extended logical unit addressing mothod hence according to SPC specification all the UFS W-LUs should be reported when SELECT REPORT field is to 00h.
But it seems UFS specification has modified the meaning of SELECT REPORT field when its set to 00h. When SELECT REPORT field is cleared, device should only report LUs which support peripheral device addressing method. Even UFS Test specification seems to confirm this diversion from the spec. Hence it's reasonable for the UFS devices adhering to the UFS device specification to not report the W-LUs when SELECT REPORT is 00h.
scsi_scan_host() would only scan LUs with SELECT REPORT set to 00h hence this patch manually add the UFS well known logical units.
drivers/scsi/ufs/ufshcd.c +20 -0
@@ -902,6 +902,17 @@ static int ufshcd_compose_upiu(struct ufs_hba *hba, struct ufshcd_lrb *lrbp) }/** +* ufshcd_upiu_wlun_to_scsi_wlun - maps UPIU W-LUN id to SCSI W-LUN ID +* @scsi_lun: UPIU W-LUN id +* +* Returns SCSI W-LUN id +*/ +static inline u16 ufshcd_upiu_wlun_to_scsi_wlun (u8 upiu_wlun_id) +{ +return (upiu_wlun_id & ~UFS_UPIU_WLUN_ID ) |SCSI_W_LUN_BASE ; +} + +/** * ufshcd_queuecommand - main entry point for SCSI requests * @cmd: command from SCSI Midlayer * @done: call back function
@@ -3415,6 +3426,15 @@ static int ufshcd_probe_hba(struct ufs_hba *hba)if (!hba->is_init_prefetch)ufshcd_init_icc_levels (hba); +scsi_add_device (hba->host,0 ,0 , +ufshcd_upiu_wlun_to_scsi_wlun ( +UFS_UPIU_UFS_DEVICE_WLUN )); +scsi_add_device (hba->host,0 ,0 , +ufshcd_upiu_wlun_to_scsi_wlun ( +UFS_UPIU_BOOT_WLUN )); +scsi_add_device (hba->host,0 ,0 , +ufshcd_upiu_wlun_to_scsi_wlun ( +UFS_UPIU_RPMB_WLUN ));scsi_scan_host (hba->host);pm_runtime_put_sync (hba->dev); }
Signature · 5 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 V4 09/17] scsi: ufs: manually add well known logical units
None of the REPORT LUNS language makes sense as we're not using it.
Can you respon the patch with a better description, and a little comment in the code on why you're adding these wluns.
Also can I assume none of the later patches relies on their existance? If you do you need to check the error return from scsi_add_device, if not said comment should mention why it's fine to not actually find any of these.
Also if you do use one of them from kernel space later it might make more sense to use __scsi_add_device and store a pointer to the scsi_device instead of looking it up later.
Re: [PATCH V4 09/17] scsi: ufs: manually add well known logical units
Quoted 5 lines
None of the REPORT LUNS language makes sense as we're not using it.
Can you respon the patch with a better description, and a little comment in the code on why you're adding these wluns.
ok.
Quoted 2 lines
Also can I assume none of the later patches relies on their existance?
In UFS power management commands such as SSU, are sent to "device w- lun". Leaving it out should only affect the ability to suspend the device.
Other patches are not directly affected by it.
Quoted 4 lines
If you do you need to check the error return from
scsi_add_device, if not said comment should mention why it's fine to not actually find any of these.
Will discus it further with Subhash.
Quoted 4 lines
Also if you do use one of them from kernel space later it might make more sense to use
__scsi_add_deviceand store a pointer to thescsi_deviceinstead of looking it up later.
I'll review all you suggestions with Subhash.
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 V4 09/17] scsi: ufs: manually add well known logical units
Thanks Chris. Agreed with all your suggestion, next revision will address your comments.
-----Original Message-----
From: [email protected] [mailto:[email protected]] On Behalf Of Dolev Raviv
Sent: Tuesday, September 23, 2014 4:49 AM
To: Christoph Hellwig
Cc: Dolev Raviv; [email protected]; [email protected]; [email protected]; [email protected]; [email protected]; [email protected]; Subhash Jadavani
Subject: Re: [PATCH V4 09/17] scsi: ufs: manually add well known logical units
Quoted 4 lines
None of the REPORT LUNS language makes sense as we're not using it.
Can you respon the patch with a better description, and a little comment in the code on why you're adding these wluns.
ok.
Quoted 2 lines
Also can I assume none of the later patches relies on their existance?
In UFS power management commands such as SSU, are sent to "device w- lun". Leaving it out should only affect the ability to suspend the device. Other patches are not directly affected by it.
Quoted 4 lines
If you do you need to check the error return from
scsi_add_device, if not said comment should mention why it's fine to not actually find any of these.
Will discus it further with Subhash.
Quoted 4 lines
Also if you do use one of them from kernel space later it might make more sense to use
__scsi_add_deviceand store a pointer to thescsi_deviceinstead of looking it up later.
I'll review all you suggestions with Subhash.
--
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
PATCH v4 10/17 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 +64 -20
145 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 · 5 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 v4 11/17 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.
Low power mode:
If UFS link is in OFF state then UFS host controller can be power collapsed to avoid leakage current from it. Note that if UFS host controller is power collapsed, full UFS reinitialization will be required on resume to re-establish the link between host and device.
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;
@@ -454,4 +482,10 @@ struct ufs_vreg_info { struct ufs_vreg *vdd_hba; }; +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 +857 -84
1,173 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 · 5 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 V4 11/17] scsi: ufs: add UFS power management support
Quoted 12 lines
/**
*ufshcd_slave_alloc- handle initial SCSI device configurations
* @sdev: pointer to SCSI devicedrivers/scsi/ufs/ufshcd.c +5
-0@@ -2232,6 +2403,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)
drivers/scsi/ufs/ufshcd.c static int ufshcd_slave_alloc(struct scsi_device *sdev) · line 2420
Quoted 10 lines
drivers/scsi/ufs/ufshcd.c +10
-0+* 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; +
Storing the pointer in slave_alloc is not safe as you don't known if the probing was successful. In addition you really need a reference to a scsi_device that you store somewhere as a user could easily delete the device through sysfs, which any access to it invalid.
As mention earlier you should just add this wlun using __scsi_device_add which gives you a pointer and a reference to the fully probed device.
Quoted 18 lines
drivers/scsi/ufs/ufshcd.c +18
-0+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; + } +
drivers/scsi/ufs/ufshcd.c static void ufshcd_hba_exit(struct ufs_hba *hba) · line 4186
Quoted 10 lines
drivers/scsi/ufs/ufshcd.c +10
-0+ 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; +}
It would be good to have an explanation why you need to call REQUEST SENSE from the driver. OR your own START STOP later one. This all seems very much against the normal model of operation for SCSI devices.
Re: [PATCH V4 11/17] scsi: ufs: add UFS power management support
Quoted 31 lines
Quoted 19 lines
/**
*ufshcd_slave_alloc- handle initial SCSI device configurations
* @sdev: pointer to SCSI devicedrivers/scsi/ufs/ufshcd.c +9
-0@@ -2232,6 +2403,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"drivers/scsi/ufs/ufshcd.c static int ufshcd_slave_alloc(struct scsi_device *sdev) · line 2420
Quoted 6 lines
drivers/scsi/ufs/ufshcd.c +6
-0+* 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; +Storing the pointer in
slave_allocis not safe as you don't known if the probing was successful. In addition you really need a reference to ascsi_devicethat you store somewhere as a user could easily delete the device through sysfs, which any access to it invalid.
Quoted 2 lines
As mention earlier you should just add this wlun using
__scsi_device_addwhich gives you a pointer and a reference to the fully probed device.
Sounds reasonable, I'll coordinate it with the previous patch.
Quoted 26 lines
Quoted 26 lines
drivers/scsi/ufs/ufshcd.c +25
-0+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);
Quoted 9 lines
drivers/scsi/ufs/ufshcd.c static void ufshcd_hba_exit(struct ufs_hba *hba) · line 4186
Quoted 3 lines
drivers/scsi/ufs/ufshcd.c +3
-0+out: +return ret; +}It would be good to have an explanation why you need to call REQUEST SENSE from the driver. OR your own START STOP later one. This all seems very much against the normal model of operation for SCSI devices.
Let me rephrase the explanation from the commit message:
We issue request sense 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. Otherwise START_STOP_UNIT will fail.
As mentioned in my previous mail (in response to [9/17] patch comments), UFS driver power management is controlled via device W-LUN during the suspend process.
As documented in the comment above: "
* 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, ....
"
Do you have another suggestion for making it compatible to other scsi drivers?
Would you prefer this logic would go into core scsi?
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 V4 11/17] scsi: ufs: add UFS power management support
Quoted 8 lines
Quoted 5 lines
It would be good to have an explanation why you need to call REQUEST SENSE from the driver. OR your own START STOP later one. This all seems very much against the normal model of operation for SCSI devices.
Let me rephrase the explanation from the commit message:
We issue request sense 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. Otherwise START_STOP_UNIT will fail.
Yes, we need to clear the unit attention condition by sending request sense command once.
Quoted 2 lines
As mentioned in my previous mail (in response to [9/17] patch comments),
UFS driver power management is controlled via device W-LUN during the suspend process.
Quoted 8 lines
As documented in the comment above: "
* 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, ....
"
Do you have another suggestion for making it compatible to other scsi
drivers?
Quoted 1 line
Would you prefer this logic would go into core scsi?
Meaning of PC (Power Condition) field in SSU command is modified by UFS device specification:
1. PC=1 means Active power mode, PC=2 means UFS_Sleep mode compared to IDLE mode in SBC spec & PC=3 means UFS_PowerDown compared to Standby mode in SBC spec.
2. PC values from 1 to 3 only make sense if its send the "UFS device" well known logical unit and this power condition is applied to all the logical units (normal & well known) hence putting the entire UFS device into power mode described by PC
We didn't find any similar implementation in scsi core power management and as meaning of PC field is quite specific to UFS device, it's better to keep it local at UFS driver which can take advantage of these modes better. Now, given above explaination, we had 2 ways to send the SSU (and request sense command) to device. One is to queue it to scsi mid layer and let it issue to LLD (the way we are doing it now) and other one was to directly send the command within UFS driver (rather than going through SCSI mid layer) but we choose the first approach as it looks cleaner.
Regards,
Subhash
-----Original Message-----
From: [email protected] [mailto:[email protected]] On Behalf Of Dolev Raviv
Sent: Tuesday, September 23, 2014 5:58 AM
To: Christoph Hellwig
Cc: Dolev Raviv; [email protected]; [email protected]; [email protected]; [email protected]; [email protected]; [email protected]; Subhash Jadavani; Sujit Reddy Thumma
Subject: Re: [PATCH V4 11/17] scsi: ufs: add UFS power management support
Quoted 7 lines
Quoted 7 lines
/**
*ufshcd_slave_alloc- handle initial SCSI device configurations
* @sdev: pointer to SCSI device(quoted diff) +0
-0@@ -2232,6 +2403,21 @@ static int ufshcd_slave_alloc(struct
scsi_device
*sdev)
Quoted 8 lines
drivers/scsi/ufs/ufshcd.c static int ufshcd_slave_alloc(struct scsi_device *sdev)
Quoted 8 lines
drivers/scsi/ufs/ufshcd.c +6
-0ufshcd_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).
Quoted 16 lines
drivers/scsi/ufs/ufshcd.c static int ufshcd_slave_alloc(struct scsi_device *sdev) · line 2420
Quoted 10 lines
drivers/scsi/ufs/ufshcd.c +3
-0+* 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"drivers/scsi/ufs/ufshcd.c +6
-0+* 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; +Storing the pointer in
slave_allocis not safe as you don't known if the probing was successful. In addition you really need a reference to ascsi_devicethat you store somewhere as a user could easily delete the device through sysfs, which any access to it invalid.
Quoted 2 lines
As mention earlier you should just add this wlun using
__scsi_device_addwhich gives you a pointer and a reference to the fully
probed device.
Sounds reasonable, I'll coordinate it with the previous patch.
Quoted 26 lines
Quoted 26 lines
drivers/scsi/ufs/ufshcd.c +25
-0+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);
Quoted 9 lines
drivers/scsi/ufs/ufshcd.c static void ufshcd_hba_exit(struct ufs_hba *hba) · line 4186
Quoted 3 lines
drivers/scsi/ufs/ufshcd.c +3
-0+out: +return ret; +}It would be good to have an explanation why you need to call REQUEST SENSE from the driver. OR your own START STOP later one. This all seems very much against the normal model of operation for SCSI devices.
Let me rephrase the explanation from the commit message:
We issue request sense 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. Otherwise START_STOP_UNIT will fail.
As mentioned in my previous mail (in response to [9/17] patch comments), UFS driver power management is controlled via device W-LUN during the suspend process.
As documented in the comment above: "
* 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, ....
"
Do you have another suggestion for making it compatible to other scsi drivers?
Would you prefer this logic would go into core scsi?
--
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
PATCH v4 12/17 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 +133 -33
@@ -179,6 +179,8 @@ 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 irqreturn_t ufshcd_intr (int irq,void *__hba); +static int ufshcd_config_pwr_mode (struct ufs_hba *hba, +struct ufs_pa_layer_attr *desired_pwr_mode);static inline int ufshcd_enable_irq (struct ufs_hba *hba) {
@@ -1959,40 +1961,83 @@ 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 ; +} + +int ufshcd_change_power_mode (struct ufs_hba *hba, +struct ufs_pa_layer_attr *pwr_mode) +{ +int ret; + +/* if already configured to the requested pwr_mode */ +if (pwr_mode->gear_rx == hba->pwr_info.gear_rx && + pwr_mode->gear_tx == hba->pwr_info.gear_tx && + pwr_mode->lane_rx == hba->pwr_info.lane_rx && + pwr_mode->lane_tx == hba->pwr_info.lane_tx && + pwr_mode->pwr_rx == hba->pwr_info.pwr_rx && + pwr_mode->pwr_tx == hba->pwr_info.pwr_tx && + pwr_mode->hs_rate == hba->pwr_info.hs_rate) { +dev_dbg (hba->dev,"%s: power already configured\n" , __func__); +return 0 ; }/*
@@ -2001,23 +2046,67 @@ static int ufshcd_config_max_pwr_mode(struct ufs_hba *hba)* - 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 ), pwr_mode->gear_rx); +ufshcd_dme_set (hba,UIC_ARG_MIB (PA_ACTIVERXDATALANES ), + pwr_mode->lane_rx); +if (pwr_mode->pwr_rx ==FASTAUTO_MODE || + pwr_mode->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 ), pwr_mode->gear_tx); +ufshcd_dme_set (hba,UIC_ARG_MIB (PA_ACTIVETXDATALANES ), + pwr_mode->lane_tx); +if (pwr_mode->pwr_tx ==FASTAUTO_MODE || + pwr_mode->pwr_tx ==FAST_MODE )ufshcd_dme_set (hba,UIC_ARG_MIB (PA_TXTERMINATION ), TRUE); +else +ufshcd_dme_set (hba,UIC_ARG_MIB (PA_TXTERMINATION ), FALSE); -if (pwr[RX ] ==FASTAUTO_MODE || pwr[TX ] ==FASTAUTO_MODE ) -ufshcd_dme_set (hba,UIC_ARG_MIB (PA_HSSERIES ),PA_HS_MODE_B ); +if (pwr_mode->pwr_rx ==FASTAUTO_MODE || + pwr_mode->pwr_tx ==FASTAUTO_MODE || + pwr_mode->pwr_rx ==FAST_MODE || + pwr_mode->pwr_tx ==FAST_MODE ) +ufshcd_dme_set (hba,UIC_ARG_MIB (PA_HSSERIES ), + pwr_mode->hs_rate); - ret =ufshcd_uic_change_pwr_mode (hba, pwr[RX ] <<4 | pwr[TX ]); -if (ret) + ret =ufshcd_uic_change_pwr_mode (hba, pwr_mode->pwr_rx <<4 + | pwr_mode->pwr_tx); + +if (ret) {dev_err (hba->dev, -"pwr_mode: power mode change failed %d\n" , ret); +"%s: power mode change failed %d\n" , __func__, ret); + }else { +if (hba->vops && hba->vops->pwr_change_notify) + hba->vops->pwr_change_notify (hba, +POST_CHANGE ,NULL , pwr_mode); + +memcpy (&hba->pwr_info, pwr_mode, +sizeof (struct ufs_pa_layer_attr )); + } + +return ret; +} + +/** +* 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 && 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)); + + ret =ufshcd_change_power_mode (hba, &final_params);return ret; }
@@ -3684,7 +3773,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
@@ -4851,6 +4949,8 @@ int ufshcd_init(struct ufs_hba *hba, void __iomem *mmio_base, unsigned int irq) host->unique_id = host->host_no; host->max_cmd_len =MAX_CDB_SIZE ; + 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 · 5 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 v4 13/17 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 +298 -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 irqreturn_t ufshcd_intr (int irq,void *__hba); static int ufshcd_config_pwr_mode(struct ufs_hba *hba,
@@ -507,6 +512,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 ; + } + +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
@@ -708,10 +938,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; }
@@ -1038,6 +1270,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);
@@ -1313,6 +1553,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);
@@ -1356,6 +1597,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; }
@@ -1379,6 +1621,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);
@@ -1418,6 +1661,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; }
@@ -1445,6 +1689,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);
@@ -1494,6 +1739,7 @@ static int ufshcd_query_descriptor(struct ufs_hba *hba, out_unlock:mutex_unlock (&hba->dev_cmd.lock); out: +ufshcd_release (hba);return err; }
@@ -1914,6 +2160,7 @@ out: hba->uic_async_done =NULL ;spin_unlock_irqrestore (hba->host->host_lock, flags);mutex_unlock (&hba->uic_cmd_mutex); +return ret; }
@@ -1928,12 +2175,16 @@ out:static int ufshcd_uic_change_pwr_mode (struct ufs_hba *hba,u8 mode) {struct uic_command uic_cmd = {0 }; +int ret; uic_cmd.command =UIC_CMD_DME_SET ; uic_cmd.argument1 =UIC_ARG_MIB (PA_PWRMODE ); uic_cmd.argument3 = mode; +ufshcd_hold (hba, false); + ret =ufshcd_uic_pwr_ctrl (hba, &uic_cmd); +ufshcd_release (hba); -return ufshcd_uic_pwr_ctrl (hba, &uic_cmd); +return ret; }static int ufshcd_uic_hibern8_enter (struct ufs_hba *hba)
@@ -2355,6 +2606,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,
@@ -2366,6 +2618,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);
@@ -2778,6 +3031,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);
@@ -3062,6 +3316,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 ) {
@@ -3115,6 +3370,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); }
@@ -3298,6 +3554,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;
@@ -3349,6 +3606,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; }
@@ -3431,6 +3689,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;
@@ -3495,6 +3754,7 @@ static int ufshcd_abort(struct scsi_cmnd *cmd)clear_bit_unlock (tag, &hba->lrb_in_use);wake_up (&hba->dev_cmd.tag_wq); + out:if (!err) { err =SUCCESS ;
@@ -3503,6 +3763,11 @@ out: err =FAILED ; } +/* +* 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);return err; }
@@ -3587,6 +3852,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
@@ -3620,6 +3886,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; }
@@ -3857,6 +4124,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,
@@ -4059,6 +4327,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;
@@ -4083,12 +4352,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; }
@@ -4149,23 +4425,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);
@@ -4487,6 +4754,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;
@@ -4509,7 +4779,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) &&
@@ -4519,7 +4789,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 );
@@ -4552,6 +4822,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.
@@ -4573,6 +4844,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;
@@ -4602,12 +4876,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 */ ret =ufshcd_enable_irq (hba);if (ret)
@@ -4651,6 +4919,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:
@@ -4662,9 +4934,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 ;
@@ -4833,6 +5102,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);
@@ -4968,11 +5238,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; }
@@ -4981,13 +5252,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 */
@@ -5012,6 +5283,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 · 5 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 v4 14/17 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
@@ -26,11 +26,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.
@@ -53,5 +53,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 · 5 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 v4 15/17 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 +177 -1
271 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 · 5 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 v4 16/17 scsi: ufs: tune bkops while power managment events
In reply to Dolev RavivFrom: Subhash Jadavani <[email protected]>
Add capability to control the auto bkops during suspend.
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. Since we don't want to keep the device active to perform the non critical bkops, host will enable urgent bkops only.
Keep auto-bkops enabled after resume if urgent bkops needed. If device bkops status shows that its in critical need of executing background operations, host should allow the device to continue doing background operations.
drivers/scsi/ufs/ufshcd.c +18 -8
@@ -4816,13 +4816,19 @@ 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 bkops status shows +* that performance might be impacted. +*/ + ret =ufshcd_urgent_bkops (hba); +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) &&
@@ -4970,7 +4976,11 @@ static int ufshcd_resume(struct ufs_hba *hba, enum ufs_pm_op pm_op) goto set_old_link_state; } -ufshcd_disable_auto_bkops (hba); +/* +* If BKOPs operations are urgently needed at this moment then +* keep auto-bkops enabled or else disable it. +*/ +ufshcd_urgent_bkops (hba); hba->clk_gating.is_suspended = false;if (ufshcd_is_clkscaling_enabled (hba))
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 · 5 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 v4 17/17 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
@@ -5046,6 +5046,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 · 5 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
Just from a quick glance this is missing the wlun type fixup we recently talked about, do you plan to send this as a separate series?
Also I've already merged the first patch, in case the new one differs from the merged one in any way (doesn't seem so), please send an incremental diff for it.