Skip to contentSkip to conversation

UFS: Power management support

PATCH Superseded Dolev Raviv 5 participants 31 messages Sep 24, 2014 – Sep 25, 2014

Subsystems (4)

v5 of 6 Superseded Latest is v6 →

lore
Share & tools
lore
#0 lore raw

v5 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 V4:
- Restored "[01/16] scsi: support well known logical units" to fix type issue" and renamed "[01/17] scsi: fixing the "type" for well known LUs"
- Add "[02/17] scsi: sysfs: don't add scsi_device if its already added" to help addressing Chris comments.
- Squashed "[10/17] scsi: ufs: introduce well known logical unit in ufs" and "[09/17] scsi: ufs: manually add well known logical" and addressed most of Chris comments here.
- Cleanup in "[11/16] scsi: ufs: add UFS power management support" to align it with comments made on previous patches.

Changes from V3:
- 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: fixing the "type" for well known LUs
scsi: sysfs: don't add scsi_device if its already added
scsi: ufs: refactor query descriptor API support
scsi: ufs: manually add well known logical units
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_scan.c                           |    8 +
 drivers/scsi/scsi_sysfs.c                          |    3 +
 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                          | 2519 ++++++++++++++++++--
 drivers/scsi/ufs/ufshcd.h                          |  280 ++-
 drivers/scsi/ufs/ufshci.h                          |    9 +-
 drivers/scsi/ufs/unipro.h                          |   56 +
 include/scsi/scsi.h                                |    1 +
 12 files changed, 3095 insertions(+), 302 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
#1 lore raw

PATCH v5 1/17 scsi: fixing the "type" for well known LUs

From: Subhash Jadavani <[email protected]>

Some devices may respond with wrong type for well-known logical units. This patch forces well-known type for devices which doesn't report it correct.

drivers/scsi/scsi_scan.c +8 -0
index 56675db..a34db9e 100644
@@ -805,6 +805,14 @@ static int scsi_add_lun(struct scsi_device *sdev, unsigned char *inq_result,
 	} else {
 		sdev->type = (inq_result[0] & 0x1f);
 		sdev->removable = (inq_result[1] & 0x80) >> 7;
+
+		/*
+		 * some devices may respond with wrong type for
+		 * well-known logical units. Force well-known type
+		 * to enumerate them correctly.
+		 */
+		if (scsi_is_wlun(sdev->lun))
+			sdev->type = TYPE_WLUN;
 	}
 
 	if (sdev->type == TYPE_RBC || sdev->type == TYPE_ROM) {
include/scsi/scsi.h +1 -0
index 261e708..d17178e 100644
@@ -333,6 +333,7 @@ static inline int scsi_status_is_good(int status)
 #define TYPE_RBC	    0x0e
 #define TYPE_OSD            0x11
 #define TYPE_ZBC            0x14
+#define TYPE_WLUN           0x1e    /* well-known logical unit */
 #define TYPE_NO_LUN         0x7f
 
 /* SCSI protocols; these are taken from SPC-3 section 7.5 */
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
#2 lore raw

Re: [PATCH V5 01/17] scsi: fixing the "type" for well known LUs

Quoted 5 lines · Dolev Raviv

From: Subhash Jadavani <[email protected]>

Some devices may respond with wrong type for well-known logical units. This patch forces well-known type for devices which doesn't report it correct.

This looks fine to me, as the well known LUN addresses seem to be nailed down nicely in t10, but let's see if Martin or Robert disagree..

Quoted 45 lines
drivers/scsi/scsi_scan.c +8 -0
index 56675db..a34db9e 100644
@@ -805,6 +805,14 @@ static int scsi_add_lun(struct scsi_device *sdev, unsigned char *inq_result,
 	} else {
 		sdev->type = (inq_result[0] & 0x1f);
 		sdev->removable = (inq_result[1] & 0x80) >> 7;
+
+		/*
+		 * some devices may respond with wrong type for
+		 * well-known logical units. Force well-known type
+		 * to enumerate them correctly.
+		 */
+		if (scsi_is_wlun(sdev->lun))
+			sdev->type = TYPE_WLUN;
 	}
 
 	if (sdev->type == TYPE_RBC || sdev->type == TYPE_ROM) {
include/scsi/scsi.h +1 -0
index 261e708..d17178e 100644
@@ -333,6 +333,7 @@ static inline int scsi_status_is_good(int status)
 #define TYPE_RBC	    0x0e
 #define TYPE_OSD            0x11
 #define TYPE_ZBC            0x14
+#define TYPE_WLUN           0x1e    /* well-known logical unit */
 #define TYPE_NO_LUN         0x7f
 
 /* SCSI protocols; these are taken from SPC-3 section 7.5 */
Signature · 9 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

--
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

---end quoted text---

#3 lore raw

RE: [PATCH V5 01/17] scsi: fixing the "type" for well known LUs

Quoted 2 lines

From: Christoph Hellwig [mailto:[email protected]]

...

Quoted 10 lines · Dolev Raviv
Quoted 5 lines

From: Subhash Jadavani <[email protected]>

Some devices may respond with wrong type for well-known logical units. This patch forces well-known type for devices which doesn't report it correct.

This looks fine to me, as the well known LUN addresses seem to be nailed down nicely in t10, but let's see if Martin or Robert disagree..

...

Quoted 6 lines

drivers/scsi/scsi_scan.c static int scsi_add_lun(struct scsi_device *sdev,

Quoted 5 lines
drivers/scsi/scsi_scan.c +0 -0
index 56675db..a34db9e 100644
@@ -805,6 +805,14 @@ static int scsi_add_lun(struct scsi_device *sdev,

unsigned char *inq_result,

Quoted 12 lines

drivers/scsi/scsi_scan.c static int scsi_add_lun(struct scsi_device *sdev, unsigned char *inq_result, · line 816

Quoted 12 lines
drivers/scsi/scsi_scan.c +8 -0
 	} else {
 		sdev->type = (inq_result[0] & 0x1f);
 		sdev->removable = (inq_result[1] & 0x80) >> 7;
+
+		/*
+		 * some devices may respond with wrong type for
+		 * well-known logical units. Force well-known type
+		 * to enumerate them correctly.
+		 */
+		if (scsi_is_wlun(sdev->lun))
+			sdev->type = TYPE_WLUN;
 	}

...

My only concern is that the peripheral device type was included at the outset in spc3r01 in 2001, so a design that can't get this right might have other problems. A print might be justified to report something is amiss:

	if (scsi_is_wlun(sdev->lun) && sdev->type != TYPE_WLUN) {
		sdev_printk(KERN_WARNING, sdev,
			"%s: correcting incorrect peripheral device type 0x%x for W-LUN 0x%16phN\n",
			__func__, sdev->type, sdev->lun);
		sdev->type = TYPE_WLUN;
	}

---
Rob Elliott HP Server Storage

#4 lore raw

RE: [PATCH V5 01/17] scsi: fixing the "type" for well known LUs

Quoted 10 lines
Quoted 7 lines

drivers/scsi/scsi_scan.c static int scsi_add_lun(struct scsi_device *sdev, unsigned char *inq_result, · line 816

Quoted 7 lines
drivers/scsi/scsi_scan.c +6 -0
+		 * some devices may respond with wrong type for
+		 * well-known logical units. Force well-known type
+		 * to enumerate them correctly.
+		 */
+		if (scsi_is_wlun(sdev->lun))
+			sdev->type = TYPE_WLUN;
 	}

...

My only concern is that the peripheral device type was included at the

outset in spc3r01 in 2001, so a design that can't get this right might have other problems. A print might be justified to report something is amiss:

Quoted 4 lines
	if (scsi_is_wlun(sdev->lun) && sdev->type != TYPE_WLUN) {
		sdev_printk(KERN_WARNING, sdev,
			"%s: correcting incorrect peripheral device type

0x%x for W-LUN 0x%16phN\n",

Quoted 3 lines
			__func__, sdev->type, sdev->lun);
		sdev->type = TYPE_WLUN;
	}

Agreed, will take care of this in next patch revision.

-----Original Message-----
From: [email protected] [mailto:[email protected]] On Behalf Of Elliott, Robert (Server Storage)
Sent: Wednesday, September 24, 2014 1:50 PM
To: Christoph Hellwig; Dolev Raviv
Cc: [email protected]; [email protected]; [email protected]; [email protected]; [email protected]; Subhash Jadavani; Sujit Reddy Thumma; Martin K. Petersen
Subject: RE: [PATCH V5 01/17] scsi: fixing the "type" for well known LUs

Quoted 1 line

From: Christoph Hellwig [mailto:[email protected]]

...

Quoted 10 lines · Dolev Raviv
Quoted 5 lines

From: Subhash Jadavani <[email protected]>

Some devices may respond with wrong type for well-known logical units. This patch forces well-known type for devices which doesn't report it correct.

This looks fine to me, as the well known LUN addresses seem to be nailed down nicely in t10, but let's see if Martin or Robert disagree..

...

Quoted 7 lines

drivers/scsi/scsi_scan.c static int scsi_add_lun(struct scsi_device · line 805

Quoted 6 lines
drivers/scsi/scsi_scan.c +0 -0
index 56675db..a34db9e 100644
@@ -805,6 +805,14 @@ static int scsi_add_lun(struct scsi_device 
 *sdev,

unsigned char *inq_result,

Quoted 12 lines

drivers/scsi/scsi_scan.c static int scsi_add_lun(struct scsi_device *sdev, unsigned char *inq_result, · line 816

Quoted 12 lines
drivers/scsi/scsi_scan.c +8 -0
 	} else {
 		sdev->type = (inq_result[0] & 0x1f);
 		sdev->removable = (inq_result[1] & 0x80) >> 7;
+
+		/*
+		 * some devices may respond with wrong type for
+		 * well-known logical units. Force well-known type
+		 * to enumerate them correctly.
+		 */
+		if (scsi_is_wlun(sdev->lun))
+			sdev->type = TYPE_WLUN;
 	}

...

My only concern is that the peripheral device type was included at the outset in spc3r01 in 2001, so a design that can't get this right might have other problems. A print might be justified to report something is amiss:

	if (scsi_is_wlun(sdev->lun) && sdev->type != TYPE_WLUN) {
		sdev_printk(KERN_WARNING, sdev,
			"%s: correcting incorrect peripheral device type
0x%x for W-LUN 0x%16phN\n",
			__func__, sdev->type, sdev->lun);
		sdev->type = TYPE_WLUN;
	}

---
Rob Elliott HP Server Storage

--
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

#5 lore raw

PATCH v5 2/17 scsi: sysfs: don't add scsi_device if its already added

In reply to Dolev Raviv

From: Subhash Jadavani <[email protected]>

If LLD has added scsi device (by calling scsi_add_device) before scheduling async scsi_scan_host then scsi_finish_async_scan() will end up calling scsi_sysfs_add_sdev for scsi device which was already added by LLD. This patch fixes this issue by adding a check at the start of scsi_sysfs_add_sdev() to skip the device add if it's already visible to rest of the kernel.

drivers/scsi/scsi_sysfs.c +3 -0
index 3524b68..00890b3 100644
@@ -1027,6 +1027,9 @@ int scsi_sysfs_add_sdev(struct scsi_device *sdev)
 	struct request_queue *rq = sdev->request_queue;
 	struct scsi_target *starget = sdev->sdev_target;
 
+	if (sdev->is_visible)
+		return 0;
+
 	error = scsi_device_set_state(sdev, SDEV_RUNNING);
 	if (error)
 		return error;
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
#6 lore raw

Re: [PATCH V5 02/17] scsi: sysfs: don't add scsi_device if its already added

Quoted 8 lines · Dolev Raviv

From: Subhash Jadavani <[email protected]>

If LLD has added scsi device (by calling scsi_add_device) before scheduling async scsi_scan_host then scsi_finish_async_scan() will end up calling scsi_sysfs_add_sdev for scsi device which was already added by LLD. This patch fixes this issue by adding a check at the start of scsi_sysfs_add_sdev() to skip the device add if it's already visible to rest of the kernel.

This looks wrong to me. I guess this happens for the case where you
added the well known lun explicitly, and then have one of the devices that also report it from REPORT LUNS? I guess the right answer is to explicitly ignore wluns in scsi_report_lun_scan.

#7 lore raw

RE: [PATCH V5 02/17] scsi: sysfs: don't add scsi_device if its already added

Quoted 3 lines
Quoted 3 lines

If LLD has added scsi device (by calling scsi_add_device) before scheduling async scsi_scan_host then scsi_finish_async_scan() will end up calling scsi_sysfs_add_sdev for scsi device which was already added by

LLD.

Quoted 6 lines
Quoted 3 lines

This patch fixes this issue by adding a check at the start of scsi_sysfs_add_sdev() to skip the device add if it's already visible to rest of the kernel.

This looks wrong to me. I guess this happens for the case where you
added the well known lun explicitly, and then have one of the devices that

also report it from REPORT LUNS? I guess the right answer is to explicitly ignore wluns in scsi_report_lun_scan.

No, It happens in this sequence of events:
1. LLD calls the __scsi_add_device() for well known logical units before scsi_scan_host() (This is done as part of [PATCH V5 10/17] scsi: ufs:
manually add well known logical units). __scsi_add_device() will also add the scsi device to sysfs by calling scsi_sysfs_add_sdev()
2. Now LDD calls the scsi_scan_host() (Note that CONFIG_SCSI_SCAN_ASYNC is enabled) which will schedule the async scan. Device reports only normal LUs (no w-lus reported here) when REPORT LUNs in sent with SELECT REPORT cleared. At the end of async scan, scsi_finish_async_scan() calls scsi_sysfs_add_devices(). scsi_sysfs_add_devices() iterates through all the scsi_device instances attached to Scsi_Host hence end up calling scsi_sysfs_add_sdev() 2nd time for the scsi_device which were already added explicitly by LDD.

BTW, none of the UFS devices are reporting the W-LUs when SELECT REPORT is cleared. I was confused with the crash (which is fixed by this patch) seen with scenario mentioned above and thought that REPORT LUNs (with SELECT REPORT=00h) is reporting W-LUs as well but I confirmed that that's not the case with any of the UFS device vendors.

-----Original Message-----
From: [email protected] [mailto:[email protected]] On Behalf Of Christoph Hellwig
Sent: Wednesday, September 24, 2014 9:08 AM
To: Dolev Raviv
Cc: [email protected]; [email protected]; [email protected]; [email protected]; [email protected]; [email protected]; Subhash Jadavani
Subject: Re: [PATCH V5 02/17] scsi: sysfs: don't add scsi_device if its already added

Quoted 5 lines · Dolev Raviv

From: Subhash Jadavani <[email protected]>

If LLD has added scsi device (by calling scsi_add_device) before scheduling async scsi_scan_host then scsi_finish_async_scan() will end up calling scsi_sysfs_add_sdev for scsi device which was already added by

LLD.

Quoted 3 lines

This patch fixes this issue by adding a check at the start of scsi_sysfs_add_sdev() to skip the device add if it's already visible to rest of the kernel.

This looks wrong to me. I guess this happens for the case where you
added the well known lun explicitly, and then have one of the devices that also report it from REPORT LUNS? I guess the right answer is to explicitly ignore wluns in scsi_report_lun_scan.

--
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

#8 lore raw

Re: [PATCH V5 02/17] scsi: sysfs: don't add scsi_device if its already added

Quoted 5 lines · Subhash Jadavani

No, It happens in this sequence of events:
1. LLD calls the __scsi_add_device() for well known logical units before scsi_scan_host() (This is done as part of [PATCH V5 10/17] scsi: ufs:
manually add well known logical units). __scsi_add_device() will also add the scsi device to sysfs by calling scsi_sysfs_add_sdev()

Quoted 8 lines

2. Now LDD calls the scsi_scan_host() (Note that CONFIG_SCSI_SCAN_ASYNC is enabled) which will schedule the async scan. Device reports only normal LUs (no w-lus reported here) when REPORT LUNs in sent with SELECT REPORT cleared. At the end of async scan, scsi_finish_async_scan() calls scsi_sysfs_add_devices(). scsi_sysfs_add_devices() iterates through all the scsi_device instances attached to Scsi_Host hence end up calling scsi_sysfs_add_sdev() 2nd time for the scsi_device which were already added explicitly by LDD.

Ok. Can you move the is_visible check to scsi_sysfs_add_devices, should be fine to place it just after the sdev_state check.

#9 lore raw

RE: [PATCH V5 02/17] scsi: sysfs: don't add scsi_device if its already added

Quoted 1 line

Ok. Can you move the is_visible check to scsi_sysfs_add_devices, should

be fine to place it just after the sdev_state check.

Sure, will move it in next patch.

-----Original Message-----
From: 'Christoph Hellwig' [mailto:[email protected]]
Sent: Wednesday, September 24, 2014 9:38 AM
To: Subhash Jadavani
Cc: 'Christoph Hellwig'; 'Dolev Raviv'; [email protected]; [email protected]; [email protected]; [email protected]; [email protected]
Subject: Re: [PATCH V5 02/17] scsi: sysfs: don't add scsi_device if its already added

Quoted 7 lines · Subhash Jadavani

No, It happens in this sequence of events:
1. LLD calls the __scsi_add_device() for well known logical units before
scsi_scan_host() (This is done as part of [PATCH V5 10/17] scsi: ufs:
manually add well known logical units). __scsi_add_device() will also add the scsi device to sysfs by calling scsi_sysfs_add_sdev() 2. Now LDD calls the scsi_scan_host() (Note that CONFIG_SCSI_SCAN_ASYNC is

Quoted 8 lines

enabled) which will schedule the async scan. Device reports only normal LUs (no w-lus reported here) when REPORT LUNs in sent with SELECT REPORT cleared. At the end of async scan, scsi_finish_async_scan() calls scsi_sysfs_add_devices(). scsi_sysfs_add_devices() iterates through all the scsi_device instances attached to Scsi_Host hence end up calling scsi_sysfs_add_sdev() 2nd time for the scsi_device which were already added explicitly by LDD.

Ok. Can you move the is_visible check to scsi_sysfs_add_devices, should be fine to place it just after the sdev_state check.

#10 lore raw

PATCH v5 3/17 scsi: ufs: Allow vendor specific initialization

In reply to Dolev Raviv

From: Sujit Reddy Thumma <[email protected]>

Some vendor specific controller versions might need to configure vendor specific - registers, clocks, voltage regulators etc. to initialize the host controller UTP layer and Uni-Pro stack. Provide some common initialization operations that can be used to configure vendor specifics. The methods can be extended in future, for example, for power mode transitions.

The operations are vendor/board specific and hence determined with the help of compatible property in device tree.

drivers/scsi/ufs/ufshcd-pci.c +7 -1
index afaabe2..7a6edbc 100644
@@ -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
index 5e46232..d727b1a 100644
@@ -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
index acf318e..8c6bec0 100644
@@ -68,6 +68,8 @@
 #define UFSHCD "ufshcd"
 #define UFSHCD_DRIVER_VERSION "0.2"
 
+struct ufs_hba;
+
 enum dev_cmd_type {
 	DEV_CMD_TYPE_NOP		= 0x0,
 	DEV_CMD_TYPE_QUERY		= 0x1,
@@ -152,6 +154,30 @@ struct ufs_dev_cmd {
 	struct ufs_query query;
 };
 
+#define PRE_CHANGE      0
+#define POST_CHANGE     1
+/**
+ * struct ufs_hba_variant_ops - variant specific callbacks
+ * @name: variant name
+ * @init: called when the driver is initialized
+ * @exit: called to cleanup everything done in init
+ * @setup_clocks: called before touching any of the controller registers
+ * @setup_regulators: called before accessing the host controller
+ * @hce_enable_notify: called before and after HCE enable bit is set to allow
+ *                     variant specific Uni-Pro initialization.
+ * @link_startup_notify: called before and after Link startup is carried out
+ *                       to allow variant specific Uni-Pro initialization.
+ */
+struct ufs_hba_variant_ops {
+	const char *name;
+	int	(*init)(struct ufs_hba *);
+	void    (*exit)(struct ufs_hba *);
+	int     (*setup_clocks)(struct ufs_hba *, bool);
+	int     (*setup_regulators)(struct ufs_hba *, bool);
+	int     (*hce_enable_notify)(struct ufs_hba *, bool);
+	int     (*link_startup_notify)(struct ufs_hba *, bool);
+};
+
 /**
  * struct ufs_hba - per adapter private structure
  * @mmio_base: UFSHCI base register address
@@ -171,6 +197,8 @@ struct ufs_dev_cmd {
  * @nutrs: Transfer Request Queue depth supported by controller
  * @nutmrs: Task Management Queue depth supported by controller
  * @ufs_version: UFS Version to which controller complies
+ * @vops: pointer to variant specific operations
+ * @priv: pointer to variant specific private data
  * @irq: Irq number of the controller
  * @active_uic_cmd: handle of active UIC command
  * @uic_cmd_mutex: mutex for uic command
@@ -218,6 +246,8 @@ struct ufs_hba {
 	int nutrs;
 	int nutmrs;
 	u32 ufs_version;
+	struct ufs_hba_variant_ops *vops;
+	void *priv;
 	unsigned int irq;
 
 	struct uic_command *active_uic_cmd;
@@ -256,8 +286,8 @@ struct ufs_hba {
 #define ufshcd_readl(hba, reg)	\
 	readl((hba)->mmio_base + (reg))
 
-int ufshcd_init(struct device *, struct ufs_hba ** , void __iomem * ,
-			unsigned int);
+int ufshcd_alloc_host(struct device *, struct ufs_hba **);
+int ufshcd_init(struct ufs_hba * , void __iomem * , unsigned int);
 void ufshcd_remove(struct ufs_hba *);
 
 /**
Signature · 4 lines
1.8.5.2
-- 
QUALCOMM ISRAEL, on behalf of Qualcomm Innovation Center, Inc. is a member
of Code Aurora Forum, hosted by The Linux Foundation
#11 lore raw

PATCH v5 4/17 scsi: ufs: Add regulator enable support

In reply to Dolev Raviv

From: 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
index 20468b2..65e3117 100644
@@ -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
index fafcf5e..729ce7d 100644
@@ -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
index 8c6bec0..c0232f9 100644
@@ -52,6 +52,7 @@
 #include <linux/pm_runtime.h>
 #include <linux/clk.h>
 #include <linux/completion.h>
+#include <linux/regulator/consumer.h>
 
 #include <asm/irq.h>
 #include <asm/byteorder.h>
@@ -219,6 +220,7 @@ struct ufs_hba_variant_ops {
  * @saved_uic_err: sticky UIC error mask
  * @dev_cmd: ufs device management command information
  * @auto_bkops_enabled: to track whether bkops is enabled in device
+ * @vreg_info: UFS device voltage regulator information
  */
 struct ufs_hba {
 	void __iomem *mmio_base;
@@ -279,6 +281,7 @@ struct ufs_hba {
 	struct ufs_dev_cmd dev_cmd;
 
 	bool auto_bkops_enabled;
+	struct ufs_vreg_info vreg_info;
 };
 
 #define ufshcd_writel(hba, val, reg)	\
Signature · 4 lines
1.8.5.2
-- 
QUALCOMM ISRAEL, on behalf of Qualcomm Innovation Center, Inc. is a member
of Code Aurora Forum, hosted by The Linux Foundation
#12 lore raw

PATCH v5 5/17 scsi: ufs: Add clock initialization support

In reply to Dolev Raviv

From: 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
index 65e3117..b0f791a 100644
@@ -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
index 7a6edbc..2a26faa 100644
@@ -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
index c0232f9..bc0f7ed 100644
@@ -155,6 +155,22 @@ struct ufs_dev_cmd {
 	struct ufs_query query;
 };
 
+/**
+ * struct ufs_clk_info - UFS clock related info
+ * @list: list headed by hba->clk_list_head
+ * @clk: clock node
+ * @name: clock name
+ * @max_freq: maximum frequency supported by the clock
+ * @enabled: variable to check against multiple enable/disable
+ */
+struct ufs_clk_info {
+	struct list_head list;
+	struct clk *clk;
+	const char *name;
+	u32 max_freq;
+	bool enabled;
+};
+
 #define PRE_CHANGE      0
 #define POST_CHANGE     1
 /**
@@ -221,6 +237,7 @@ struct ufs_hba_variant_ops {
  * @dev_cmd: ufs device management command information
  * @auto_bkops_enabled: to track whether bkops is enabled in device
  * @vreg_info: UFS device voltage regulator information
+ * @clk_list_head: UFS host controller clocks list node head
  */
 struct ufs_hba {
 	void __iomem *mmio_base;
@@ -282,6 +299,7 @@ struct ufs_hba {
 
 	bool auto_bkops_enabled;
 	struct ufs_vreg_info vreg_info;
+	struct list_head clk_list_head;
 };
 
 #define ufshcd_writel(hba, val, reg)	\
Signature · 4 lines
1.8.5.2
-- 
QUALCOMM ISRAEL, on behalf of Qualcomm Innovation Center, Inc. is a member
of Code Aurora Forum, hosted by The Linux Foundation
#13 lore raw

PATCH v5 6/17 scsi: ufs: add voting support for host controller power

In reply to Dolev Raviv

From: 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
index b0f791a..fb1234e 100644
@@ -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
index 729ce7d..9bb6919 100644
@@ -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
index 642d80f..dde4e6e 100644
@@ -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 · 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
#14 lore raw

PATCH v5 7/17 scsi: ufs: refactor query descriptor API support

In reply to Dolev Raviv

From: 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
index 9bb6919..f76a304 100644
@@ -50,6 +50,8 @@
 			cpu_to_be32((byte3 << 24) | (byte2 << 16) |\
 			 (byte1 << 8) | (byte0))
 
+#define UFS_UPIU_MAX_GENERAL_LUN	8
+
 /*
  * UFS Protocol Information Unit related definitions
  */
@@ -129,10 +131,29 @@ enum desc_idn {
 	QUERY_DESC_IDN_RFU_1		= 0x6,
 	QUERY_DESC_IDN_GEOMETRY		= 0x7,
 	QUERY_DESC_IDN_POWER		= 0x8,
-	QUERY_DESC_IDN_RFU_2		= 0x9,
+	QUERY_DESC_IDN_MAX,
+};
+
+enum desc_header_offset {
+	QUERY_DESC_LENGTH_OFFSET	= 0x00,
+	QUERY_DESC_DESC_TYPE_OFFSET	= 0x01,
+};
+
+enum ufs_desc_max_size {
+	QUERY_DESC_DEVICE_MAX_SIZE		= 0x1F,
+	QUERY_DESC_CONFIGURAION_MAX_SIZE	= 0x90,
+	QUERY_DESC_UNIT_MAX_SIZE		= 0x23,
+	QUERY_DESC_INTERCONNECT_MAX_SIZE	= 0x06,
+	/*
+	 * Max. 126 UNICODE characters (2 bytes per character) plus 2 bytes
+	 * of descriptor header.
+	 */
+	QUERY_DESC_STRING_MAX_SIZE		= 0xFE,
+	QUERY_DESC_GEOMETRY_MAZ_SIZE		= 0x44,
+	QUERY_DESC_POWER_MAX_SIZE		= 0x62,
+	QUERY_DESC_RFU_MAX_SIZE			= 0x00,
 };
 
-#define UNIT_DESC_MAX_SIZE       0x22
 /* Unit descriptor parameters offsets in bytes*/
 enum unit_desc_param {
 	UNIT_DESC_PARAM_LEN			= 0x0,
drivers/scsi/ufs/ufshcd.c +130 -37

203 lines not on this page · raw message

Signature · 4 lines
1.8.5.2
-- 
QUALCOMM ISRAEL, on behalf of Qualcomm Innovation Center, Inc. is a member
of Code Aurora Forum, hosted by The Linux Foundation
#15 lore raw

PATCH v5 8/17 scsi: ufs: improve init sequence

In reply to Dolev Raviv

From: 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
index 3f2b30d..af29d4c 100644
@@ -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
index bc0f7ed..eddb3f3 100644
@@ -228,6 +228,7 @@ struct ufs_hba_variant_ops {
  * @eh_flags: Error handling flags
  * @intr_mask: Interrupt Mask Bits
  * @ee_ctrl_mask: Exception event control mask
+ * @is_powered: flag to check if HBA is powered
  * @eh_work: Worker to handle UFS errors that require s/w attention
  * @eeh_work: Worker to handle exception events
  * @errors: HBA errors
@@ -283,6 +284,7 @@ struct ufs_hba {
 	u32 eh_flags;
 	u32 intr_mask;
 	u16 ee_ctrl_mask;
+	bool is_powered;
 
 	/* Work Queues */
 	struct work_struct eh_work;
Signature · 4 lines
1.8.5.2
-- 
QUALCOMM ISRAEL, on behalf of Qualcomm Innovation Center, Inc. is a member
of Code Aurora Forum, hosted by The Linux Foundation
#16 lore raw

PATCH v5 9/17 scsi: ufs: Active Power Mode - configuring bActiveICCLevel

In reply to Dolev Raviv

From: 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
index f76a304..4ca99ed 100644
@@ -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
index eddb3f3..8365ad4 100644
@@ -196,6 +196,15 @@ struct ufs_hba_variant_ops {
 };
 
 /**
+ * struct ufs_init_prefetch - contains data that is pre-fetched once during
+ * initialization
+ * @icc_level: icc level which was read during initialization
+ */
+struct ufs_init_prefetch {
+	u32 icc_level;
+};
+
+/**
  * struct ufs_hba - per adapter private structure
  * @mmio_base: UFSHCI base register address
  * @ucdl_base_addr: UFS Command Descriptor base address
@@ -229,6 +238,8 @@ struct ufs_hba_variant_ops {
  * @intr_mask: Interrupt Mask Bits
  * @ee_ctrl_mask: Exception event control mask
  * @is_powered: flag to check if HBA is powered
+ * @is_init_prefetch: flag to check if data was pre-fetched in initialization
+ * @init_prefetch_data: data pre-fetched during initialization
  * @eh_work: Worker to handle UFS errors that require s/w attention
  * @eeh_work: Worker to handle exception events
  * @errors: HBA errors
@@ -285,6 +296,8 @@ struct ufs_hba {
 	u32 intr_mask;
 	u16 ee_ctrl_mask;
 	bool is_powered;
+	bool is_init_prefetch;
+	struct ufs_init_prefetch init_prefetch_data;
 
 	/* Work Queues */
 	struct work_struct eh_work;
Signature · 4 lines
1.8.5.2
-- 
QUALCOMM ISRAEL, on behalf of Qualcomm Innovation Center, Inc. is a member
of Code Aurora Forum, hosted by The Linux Foundation
#17 lore raw

PATCH v5 10/17 scsi: ufs: manually add well known logical units

In reply to Dolev Raviv

From: Subhash Jadavani <[email protected]>

UFS device specification requires the UFS devices to support 4 well known logical units:

	"REPORT_LUNS" (address: 01h)
	"UFS Device" (address: 50h)
	"RPMB" (address: 44h)
	"BOOT" (address: 30h)

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 akso 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.

UFS device's power management needs to be controlled by "POWER CONDITION" field of SSU (START STOP UNIT) command. But this "power condition" field will take effect only when its sent to "UFS device" well known logical unit hence we require the scsi_device instance to represent this logical unit in order for the UFS host driver to send the SSU command for power management.

We also require the scsi_device instance for "RPMB" (Replay Protected Memory Block) LU so user space process can control this LU. User space may also want to have access to BOOT LU.

This patch adds the scsi device instances for each of all well known LUs (except "REPORT LUNS" LU).

drivers/scsi/ufs/ufs.h +20 -1
index 4ca99ed..37d64c1 100644
@@ -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 +139 -20

226 lines not on this page · raw message

drivers/scsi/ufs/ufshcd.h +8 -1
index 8365ad4..5c25337 100644
@@ -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,13 @@ 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 scsi_device *sdev_rpmb;
+	struct scsi_device *sdev_boot;
 
 	struct ufshcd_lrb *lrb;
 	unsigned long lrb_in_use;
Signature · 4 lines
1.8.5.2
-- 
QUALCOMM ISRAEL, on behalf of Qualcomm Innovation Center, Inc. is a member
of Code Aurora Forum, hosted by The Linux Foundation
#18 lore raw

Re: [PATCH V5 10/17] scsi: ufs: manually add well known logical units

Quoted 10 lines

/**
+ * ufshcd_set_queue_depth - set lun queue depth
+ * @sdev: pointer to SCSI device
+ *
+ * Read bLUQueueDepth value and activate scsi tagged command
+ * queueing. For WLUN, queue depth is set to 1. For best-effort
+ * cases (bLUQueueDepth = 0) the queue depth is set to a maximum
+ * value that host can queue.
+ */
+static void ufshcd_set_queue_depth(struct scsi_device *sdev)

I don't think this refactoring belong in here..

Quoted 20 lines
drivers/scsi/ufs/ufshcd.c +0 -9
@@ -2152,8 +2215,6 @@ static int ufshcd_verify_dev_init(struct ufs_hba *hba)
 static int ufshcd_slave_alloc(struct scsi_device *sdev)
 {
 	struct ufs_hba *hba;
-	u8 lun_qdepth;
-	int ret;
 
 	hba = shost_priv(sdev->host);
 	sdev->tagged_supported = 1;
@@ -2168,20 +2229,8 @@ static int ufshcd_slave_alloc(struct scsi_device *sdev)
 	/* REPORT SUPPORTED OPERATION CODES is not supported */
 	sdev->no_report_opcodes = 1;
 
-	ret = ufshcd_read_unit_desc_param(hba,
-					  sdev->lun,
-					  UNIT_DESC_PARAM_LU_Q_DEPTH,
-					  &lun_qdepth,
-					  sizeof(lun_qdepth));
-	if (!ret || !lun_qdepth)
-		/* eventually, we can figure out the real queue depth */

drivers/scsi/ufs/ufshcd.c static int ufshcd_slave_alloc(struct scsi_device *sdev) · line 2235

Quoted 10 lines
drivers/scsi/ufs/ufshcd.c +1 -6
-		lun_qdepth = hba->nutrs;
-	else
-		lun_qdepth = min_t(int, lun_qdepth, hba->nutrs);
 
-	dev_dbg(hba->dev, "%s: activate tcq with queue depth %d\n",
-			__func__, lun_qdepth);
-	scsi_activate_tcq(sdev, lun_qdepth);
+	ufshcd_set_queue_depth(sdev);
 
 	return 0;

Addinitional all this setup really belongs into ->slave_configure. ->slave_alloc is just for allocating any needed per-LUN data structure before scanning, while ->slave_configure is called after a LUN has finished scanning and is made available. That should be left for a separate patch, though.

drivers/scsi/ufs/ufshcd.c static void ufshcd_init_icc_levels(struct ufs_hba *hba) · line 3474

Quoted 11 lines
drivers/scsi/ufs/ufshcd.c +11 -0
+static int ufshcd_scsi_add_wlus(struct ufs_hba *hba)
+{
+	int ret = 0;
+
+	hba->sdev_ufs_device = __scsi_add_device(hba->host, 0, 0,
+		ufshcd_upiu_wlun_to_scsi_wlun(UFS_UPIU_UFS_DEVICE_WLUN), NULL);
+	if (IS_ERR(hba->sdev_ufs_device)) {
+		ret = PTR_ERR(hba->sdev_ufs_device);
+		hba->sdev_ufs_device = NULL;
+		goto out;
+	}

Where do you release these references again? It seems like they are never released on the device removal path.

#19 lore raw

RE: [PATCH V5 10/17] scsi: ufs: manually add well known logical units

Comments inline:

-----Original Message-----
From: Christoph Hellwig [mailto:[email protected]]
Sent: Wednesday, September 24, 2014 9:25 AM
To: Dolev Raviv
Cc: [email protected]; [email protected]; [email protected]; [email protected]; [email protected]; [email protected]; Subhash Jadavani; Sujit Reddy Thumma
Subject: Re: [PATCH V5 10/17] scsi: ufs: manually add well known logical units

Quoted 10 lines

/**
+ * ufshcd_set_queue_depth - set lun queue depth
+ * @sdev: pointer to SCSI device
+ *
+ * Read bLUQueueDepth value and activate scsi tagged command
+ * queueing. For WLUN, queue depth is set to 1. For best-effort
+ * cases (bLUQueueDepth = 0) the queue depth is set to a maximum
+ * value that host can queue.
+ */
+static void ufshcd_set_queue_depth(struct scsi_device *sdev)

I don't think this refactoring belong in here..

[Subhash] I agree, This shouldn't belong here. Will fix it in next patch.

drivers/scsi/ufs/ufshcd.c static int ufshcd_slave_alloc(struct scsi_device

Quoted 9 lines
drivers/scsi/ufs/ufshcd.c +0 -2
@@ -2152,8 +2215,6 @@ static int ufshcd_verify_dev_init(struct ufs_hba 
 *hba)  static int ufshcd_slave_alloc(struct scsi_device *sdev)  {
 	struct ufs_hba *hba;
-	u8 lun_qdepth;
-	int ret;
 
 	hba = shost_priv(sdev->host);
 	sdev->tagged_supported = 1;
@@ -2168,20 +2229,8 @@ static int ufshcd_slave_alloc(struct scsi_device

*sdev)

Quoted 10 lines
drivers/scsi/ufs/ufshcd.c +0 -7
 	/* REPORT SUPPORTED OPERATION CODES is not supported */
 	sdev->no_report_opcodes = 1;
 
-	ret = ufshcd_read_unit_desc_param(hba,
-					  sdev->lun,
-					  UNIT_DESC_PARAM_LU_Q_DEPTH,
-					  &lun_qdepth,
-					  sizeof(lun_qdepth));
-	if (!ret || !lun_qdepth)
-		/* eventually, we can figure out the real queue depth */

drivers/scsi/ufs/ufshcd.c static int ufshcd_slave_alloc(struct scsi_device *sdev) · line 2235

Quoted 10 lines
drivers/scsi/ufs/ufshcd.c +1 -6
-		lun_qdepth = hba->nutrs;
-	else
-		lun_qdepth = min_t(int, lun_qdepth, hba->nutrs);
 
-	dev_dbg(hba->dev, "%s: activate tcq with queue depth %d\n",
-			__func__, lun_qdepth);
-	scsi_activate_tcq(sdev, lun_qdepth);
+	ufshcd_set_queue_depth(sdev);
 
 	return 0;

Addinitional all this setup really belongs into ->slave_configure. ->slave_alloc is just for allocating any needed per-LUN data structure before scanning, while ->slave_configure is called after a LUN has finished scanning and is made available. That should be left for a separate patch, though.

[Subhash] Ok, will fix it and I agree it shouldn't be part of this patch.

drivers/scsi/ufs/ufshcd.c static void ufshcd_init_icc_levels(struct ufs_hba *hba)

Quoted 5 lines
drivers/scsi/ufs/ufshcd.c +5 -0
+static int ufshcd_scsi_add_wlus(struct ufs_hba *hba) {
+	int ret = 0;
+
+	hba->sdev_ufs_device = __scsi_add_device(hba->host, 0, 0,
+		ufshcd_upiu_wlun_to_scsi_wlun(UFS_UPIU_UFS_DEVICE_WLUN),

NULL);

drivers/scsi/ufs/ufshcd.c static void ufshcd_init_icc_levels(struct ufs_hba *hba) · line 3474

Quoted 5 lines
drivers/scsi/ufs/ufshcd.c +5 -0
+	if (IS_ERR(hba->sdev_ufs_device)) {
+		ret = PTR_ERR(hba->sdev_ufs_device);
+		hba->sdev_ufs_device = NULL;
+		goto out;
+	}

Where do you release these references again? It seems like they are never released on the device removal path.

[Subhash] That's because these are embedded/non-removable UFS devices which are always present on the board and never get removed. Do you have any suggestion on how we should handle this?

#20 lore raw

Re: [PATCH V5 10/17] scsi: ufs: manually add well known logical units

Quoted 6 lines · Subhash Jadavani

Where do you release these references again? It seems like they are never released on the device removal path.

[Subhash] That's because these are embedded/non-removable UFS devices which are always present on the board and never get removed. Do you have any suggestion on how we should handle this?

Just after the call to scsi_remove_host sounds right to me. scsi_remove_host already removes all regularly scanned devices, but because __scsi_add_device keeps and additional reference it doesn't free those that you added manually.

#21 lore raw

RE: [PATCH V5 10/17] scsi: ufs: manually add well known logical units

Quoted 2 lines

Just after the call to scsi_remove_host sounds right to me. scsi_remove_host already removes all regularly scanned devices, but

because __scsi_add_device keeps and additional reference it doesn't free those that you added manually.

Ok, we are calling scsi_remove_host() as part of ufs driver removal so we can remove these manually added devices immediately after it. Next patch revision should fix it.

-----Original Message-----
From: 'Christoph Hellwig' [mailto:[email protected]]
Sent: Wednesday, September 24, 2014 9:41 AM
To: Subhash Jadavani
Cc: 'Christoph Hellwig'; 'Dolev Raviv'; [email protected]; [email protected]; [email protected]; [email protected]; [email protected]; 'Sujit Reddy Thumma'
Subject: Re: [PATCH V5 10/17] scsi: ufs: manually add well known logical units

Quoted 6 lines · Subhash Jadavani

Where do you release these references again? It seems like they are never released on the device removal path.

[Subhash] That's because these are embedded/non-removable UFS devices which are always present on the board and never get removed. Do you have any suggestion on how we should handle this?

Just after the call to scsi_remove_host sounds right to me. scsi_remove_host already removes all regularly scanned devices, but because __scsi_add_device keeps and additional reference it doesn't free those that you added manually.

#22 lore raw

PATCH v5 11/17 scsi: ufs: add UFS power management support

In reply to Dolev Raviv

From: 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
index 37d64c1..42c459a 100644
@@ -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 +844 -84

1,160 lines not on this page · raw message

drivers/scsi/ufs/ufshcd.h +71 -5

131 lines not on this page · raw message

drivers/scsi/ufs/ufshci.h +6 -3
index e1b844b..d572119 100644
@@ -124,8 +124,11 @@ enum {
 #define CONTROLLER_FATAL_ERROR			UFS_BIT(16)
 #define SYSTEM_BUS_FATAL_ERROR			UFS_BIT(17)
 
-#define UFSHCD_UIC_MASK		(UIC_COMMAND_COMPL |\
-				 UIC_POWER_MODE)
+#define UFSHCD_UIC_PWR_MASK	(UIC_HIBERNATE_ENTER |\
+				UIC_HIBERNATE_EXIT |\
+				UIC_POWER_MODE)
+
+#define UFSHCD_UIC_MASK		(UIC_COMMAND_COMPL | UFSHCD_UIC_PWR_MASK)
 
 #define UFSHCD_ERROR_MASK	(UIC_ERROR |\
 				DEVICE_FATAL_ERROR |\
@@ -210,7 +213,7 @@ enum {
 #define UIC_GET_ATTR_ID(v)		(((v) >> 16) & 0xFFFF)
 
 /* UIC Commands */
-enum {
+enum uic_cmd_dme {
 	UIC_CMD_DME_GET			= 0x01,
 	UIC_CMD_DME_SET			= 0x02,
 	UIC_CMD_DME_PEER_GET		= 0x03,
Signature · 4 lines
1.8.5.2
-- 
QUALCOMM ISRAEL, on behalf of Qualcomm Innovation Center, Inc. is a member
of Code Aurora Forum, hosted by The Linux Foundation
#23 lore raw

Re: [PATCH V5 11/17] scsi: ufs: add UFS power management support

2014-09-25 0:14 GMT+09:00 Dolev Raviv <[email protected]>:

Quoted 12 lines
drivers/scsi/ufs/ufshcd.c +2 -5
+int ufshcd_uic_pwr_ctrl(struct ufs_hba *hba, struct uic_command *cmd)
 {
-       struct uic_command uic_cmd = {0};
-       struct completion pwr_done;
+       struct completion uic_async_done;
        unsigned long flags;
        u8 status;
        int ret;
 
-       uic_cmd.command = UIC_CMD_DME_SET;
-       uic_cmd.argument1 = UIC_ARG_MIB(PA_PWRMODE);
-       uic_cmd.argument3 = mode;

drivers/scsi/ufs/ufshcd.c out: · line 1879

Quoted 10 lines
drivers/scsi/ufs/ufshcd.c +3 -3
-       init_completion(&pwr_done);
-
        mutex_lock(&hba->uic_cmd_mutex);
+       init_completion(&uic_async_done);
 
        spin_lock_irqsave(hba->host->host_lock, flags);
-       hba->pwr_done = &pwr_done;
+       hba->uic_async_done = &uic_async_done;
+       ret = __ufshcd_send_uic_cmd(hba, cmd);
        spin_unlock_irqrestore(hba->host->host_lock, flags);

__ufshcd_send_uic_cmd() is called with host_lock held here, but host_lock is acquired again in __ufshcd_send_uic_cmd(). So it causes recursive deadlock.

drivers/scsi/ufs/ufshcd.c out: · line 1886

Quoted 10 lines
drivers/scsi/ufs/ufshcd.c +5 -3
-       ret = __ufshcd_send_uic_cmd(hba, &uic_cmd);
        if (ret) {
                dev_err(hba->dev,
-                       "pwr mode change with mode 0x%x uic error %d\n",
-                       mode, ret);
+                       "pwr ctrl cmd 0x%x with mode 0x%x uic error %d\n",
+                       cmd->command, cmd->argument3, ret);
+               goto out;
+       }
+       ret = ufshcd_wait_for_uic_cmd(hba, cmd);

ufshcd_wait_for_uic_cmd() is already called in the previous __ufshcd_send_uic_cmd() call.

These two issues don't exist in v3.

drivers/scsi/ufs/ufshcd.c static struct scsi_host_template ufshcd_driver_template = { · line 3840

Quoted 11 lines
drivers/scsi/ufs/ufshcd.c +11 -0
+static inline int ufshcd_config_vreg_lpm(struct ufs_hba *hba,
+                                        struct ufs_vreg *vreg)
+{
+       return ufshcd_config_vreg_load(hba->dev, vreg, UFS_VREG_LPM_LOAD_UA);
+}
+
+static inline int ufshcd_config_vreg_hpm(struct ufs_hba *hba,
+                                        struct ufs_vreg *vreg)
+{
+       return ufshcd_config_vreg_load(hba->dev, vreg, vreg->max_uA);
+}

I was still seeing null pointer derefence with v4 which I was reported
(http://marc.info/?l=linux-scsi&m=141087506802548) and nothing changed in v5. Could you check if the fix is needed?

#24 lore raw

Re: [PATCH V5 11/17] scsi: ufs: add UFS power management support

Thanks Mita,
You are right these are careless mistakes.
I will fix all of them and upload a new version shortly.

Quoted 3 lines

__ufshcd_send_uic_cmd() is called with host_lock held here, but host_lock is acquired again in __ufshcd_send_uic_cmd(). So it causes recursive deadlock.

Correct I forgot to complete the fix.

Quoted 5 lines

ufshcd_wait_for_uic_cmd() is already called in the previous __ufshcd_send_uic_cmd() call.

These two issues don't exist in v3.

Same fix I forgot to complete from earlier comment.

Quoted 5 lines

I was still seeing null pointer derefence with v4 which I was reported
(http://marc.info/?l=linux-scsi&m=141087506802548) and nothing changed in v5. Could you check if the fix is needed?

Sure, my mistake. I'll defiantly add a null pointer check in ufshcd_config_vreg_hpm & ufshcd_config_vreg_lpm, to reduce overhead when vregs are not initialized.

Thanks,
Dolev

Signature · 2 lines
QUALCOMM ISRAEL, on behalf of Qualcomm Innovation Center, Inc. is a member
of Code Aurora Forum, hosted by The Linux Foundation
#25 lore raw

PATCH v5 12/17 scsi: ufs: refactor configuring power mode

In reply to Dolev Raviv

Sometimes, 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
index 11c6320..6bc4996 100644
@@ -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;
 }
@@ -3731,7 +3820,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
@@ -4893,6 +4991,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
index e1bde05..343b18a 100644
@@ -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;
@@ -387,6 +411,9 @@ struct ufs_hba {
 	struct list_head clk_list_head;
 
 	bool wlun_dev_clr_ua;
+
+	struct ufs_pa_layer_attr pwr_info;
+	struct ufs_pwr_mode_info max_pwr_info;
 };
 
 #define ufshcd_writel(hba, val, reg)	\
Signature · 4 lines
1.8.5.2
-- 
QUALCOMM ISRAEL, on behalf of Qualcomm Innovation Center, Inc. is a member
of Code Aurora Forum, hosted by The Linux Foundation
#26 lore raw

PATCH v5 13/17 scsi: ufs: Add support for clock gating

In reply to Dolev Raviv

From: 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
index 6bc4996..ff889723 100644
@@ -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);
@@ -2762,6 +3015,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);
@@ -3046,6 +3300,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) {
@@ -3099,6 +3354,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);
 }
 
@@ -3282,6 +3538,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;
@@ -3333,6 +3590,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;
 }
 
@@ -3415,6 +3673,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;
@@ -3479,6 +3738,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;
@@ -3487,6 +3747,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;
 }
 
@@ -3571,6 +3836,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
@@ -3604,6 +3870,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;
 }
 
@@ -3899,6 +4166,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,
@@ -4101,6 +4369,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;
@@ -4125,12 +4394,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;
 }
@@ -4191,23 +4467,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);
@@ -4529,6 +4796,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;
@@ -4551,7 +4821,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) &&
@@ -4561,7 +4831,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);
@@ -4594,6 +4864,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.
@@ -4615,6 +4886,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;
@@ -4644,12 +4918,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)
@@ -4693,6 +4961,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:
@@ -4704,9 +4976,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;
@@ -4875,6 +5144,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);
@@ -5010,11 +5280,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;
 	}
@@ -5023,13 +5294,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 */
@@ -5054,6 +5325,8 @@ int ufshcd_init(struct ufs_hba *hba, void __iomem *mmio_base, unsigned int irq)
 
 out_remove_scsi_host:
 	scsi_remove_host(hba->host);
+exit_gating:
+	ufshcd_exit_clk_gating(hba);
 out_disable:
 	hba->is_irq_enabled = false;
 	scsi_host_put(host);
drivers/scsi/ufs/ufshcd.h +51 -0

69 lines not on this page · raw message

Signature · 4 lines
1.8.5.2
-- 
QUALCOMM ISRAEL, on behalf of Qualcomm Innovation Center, Inc. is a member
of Code Aurora Forum, hosted by The Linux Foundation
#27 lore raw

PATCH v5 14/17 scsi: ufs: Add freq-table-hz property for UFS device

In reply to Dolev Raviv

From: 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
index fb1234e..5357919 100644
@@ -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
index 29d34d3..ac72a6d 100644
@@ -209,6 +209,7 @@ struct ufs_dev_cmd {
  * @clk: clock node
  * @name: clock name
  * @max_freq: maximum frequency supported by the clock
+ * @min_freq: min frequency that can be used for clock scaling
  * @enabled: variable to check against multiple enable/disable
  */
 struct ufs_clk_info {
@@ -216,6 +217,7 @@ struct ufs_clk_info {
 	struct clk *clk;
 	const char *name;
 	u32 max_freq;
+	u32 min_freq;
 	bool enabled;
 };
 
Signature · 4 lines
1.8.5.2
-- 
QUALCOMM ISRAEL, on behalf of Qualcomm Innovation Center, Inc. is a member
of Code Aurora Forum, hosted by The Linux Foundation
#28 lore raw

PATCH v5 15/17 scsi: ufs: Add support for clock scaling using devfreq framework

In reply to Dolev Raviv

From: 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
index f07f901..6e07b2a 100644
@@ -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
index ac72a6d..908db3e 100644
@@ -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
@@ -456,6 +467,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 */
@@ -467,6 +483,10 @@ static inline bool ufshcd_can_hibern8_during_gating(struct ufs_hba *hba)
 {
 	return hba->caps & UFSHCD_CAP_HIBERN8_WITH_CLK_GATING;
 }
+static inline int ufshcd_is_clkscaling_enabled(struct ufs_hba *hba)
+{
+	return hba->caps & UFSHCD_CAP_CLK_SCALING;
+}
 #define ufshcd_writel(hba, val, reg)	\
 	writel((val), (hba)->mmio_base + (reg))
 #define ufshcd_readl(hba, reg)	\
Signature · 4 lines
1.8.5.2
-- 
QUALCOMM ISRAEL, on behalf of Qualcomm Innovation Center, Inc. is a member
of Code Aurora Forum, hosted by The Linux Foundation
#29 lore raw

PATCH v5 16/17 scsi: ufs: tune bkops while power managment events

In reply to Dolev Raviv

From: 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
index dcab003..d35719d 100644
@@ -4858,13 +4858,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) &&
@@ -5012,7 +5018,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
index 908db3e..d7fec86 100644
@@ -469,6 +469,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;
@@ -487,6 +489,11 @@ static inline int ufshcd_is_clkscaling_enabled(struct ufs_hba *hba)
 {
 	return hba->caps & UFSHCD_CAP_CLK_SCALING;
 }
+static inline bool ufshcd_can_autobkops_during_suspend(struct ufs_hba *hba)
+{
+	return hba->caps & UFSHCD_CAP_AUTO_BKOPS_SUSPEND;
+}
+
 #define ufshcd_writel(hba, val, reg)	\
 	writel((val), (hba)->mmio_base + (reg))
 #define ufshcd_readl(hba, reg)	\
Signature · 4 lines
1.8.5.2
-- 
QUALCOMM ISRAEL, on behalf of Qualcomm Innovation Center, Inc. is a member
of Code Aurora Forum, hosted by The Linux Foundation
#30 lore raw

PATCH v5 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
index d35719d..91c7adc 100644
@@ -5088,6 +5088,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
index d7fec86..58ecdff 100644
@@ -474,6 +474,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 */
@@ -499,6 +500,23 @@ static inline bool ufshcd_can_autobkops_during_suspend(struct ufs_hba *hba)
 #define ufshcd_readl(hba, reg)	\
 	readl((hba)->mmio_base + (reg))
 
+/**
+ * ufshcd_rmwl - read modify write into a register
+ * @hba - per adapter instance
+ * @mask - mask to apply on read value
+ * @val - actual value to write
+ * @reg - register address
+ */
+static inline void ufshcd_rmwl(struct ufs_hba *hba, u32 mask, u32 val, u32 reg)
+{
+	u32 tmp;
+
+	tmp = ufshcd_readl(hba, reg);
+	tmp &= ~mask;
+	tmp |= (val & mask);
+	ufshcd_writel(hba, tmp, reg);
+}
+
 int ufshcd_alloc_host(struct device *, struct ufs_hba **);
 int ufshcd_init(struct ufs_hba * , void __iomem * , unsigned int);
 void ufshcd_remove(struct ufs_hba *);
drivers/scsi/ufs/unipro.h +56 -0

68 lines not on this page · raw message

Signature · 4 lines
1.8.5.2
-- 
QUALCOMM ISRAEL, on behalf of Qualcomm Innovation Center, Inc. is a member
of Code Aurora Forum, hosted by The Linux Foundation