[PATCH v12 1/2] scsi: ufs: Enable power management for wlun
Asutosh Das (asd)
asutoshd at codeaurora.org
Tue Mar 23 15:13:29 GMT 2021
On 3/22/2021 11:12 PM, Adrian Hunter wrote:
> On 22/03/21 9:53 pm, Asutosh Das (asd) wrote:
>> On 3/19/2021 10:47 AM, Adrian Hunter wrote:
>>> On 19/03/21 2:35 am, Asutosh Das wrote:
>>>> During runtime-suspend of ufs host, the scsi devices are
>>>> already suspended and so are the queues associated with them.
>>>> But the ufs host sends SSU to wlun during its runtime-suspend.
>>>> During the process blk_queue_enter checks if the queue is not in
>>>> suspended state. If so, it waits for the queue to resume, and never
>>>> comes out of it.
>>>> The commit
>>>> (d55d15a33: scsi: block: Do not accept any requests while suspended)
>>>> adds the check if the queue is in suspended state in blk_queue_enter().
>>>>
>>>> Call trace:
>>>> __switch_to+0x174/0x2c4
>>>> __schedule+0x478/0x764
>>>> schedule+0x9c/0xe0
>>>> blk_queue_enter+0x158/0x228
>>>> blk_mq_alloc_request+0x40/0xa4
>>>> blk_get_request+0x2c/0x70
>>>> __scsi_execute+0x60/0x1c4
>>>> ufshcd_set_dev_pwr_mode+0x124/0x1e4
>>>> ufshcd_suspend+0x208/0x83c
>>>> ufshcd_runtime_suspend+0x40/0x154
>>>> ufshcd_pltfrm_runtime_suspend+0x14/0x20
>>>> pm_generic_runtime_suspend+0x28/0x3c
>>>> __rpm_callback+0x80/0x2a4
>>>> rpm_suspend+0x308/0x614
>>>> rpm_idle+0x158/0x228
>>>> pm_runtime_work+0x84/0xac
>>>> process_one_work+0x1f0/0x470
>>>> worker_thread+0x26c/0x4c8
>>>> kthread+0x13c/0x320
>>>> ret_from_fork+0x10/0x18
>>>>
>>>> Fix this by registering ufs device wlun as a scsi driver and
>>>> registering it for block runtime-pm. Also make this as a
>>>> supplier for all other luns. That way, this device wlun
>>>> suspends after all the consumers and resumes after
>>>> hba resumes.
>>>>
>>>> Co-developed-by: Can Guo <cang at codeaurora.org>
>>>> Signed-off-by: Can Guo <cang at codeaurora.org>
>>>> Signed-off-by: Asutosh Das <asutoshd at codeaurora.org>
>>>
>>> I have some more comments that may help straighten things out.
>>>
>>> Also please look at ufs_debugfs_get_user_access() and
>>> ufs_debugfs_put_user_access() that now need to scsi_autopm_get/put_device
>>> sdev_ufs_device.
>>>
>>> It would also be good if you could re-base on linux-next.
>>>
>>
>> Hi Adrian
>> Thanks for the comments.
>>
>> I agree moving the code to wlun probe and other changes.
>> But it looks to me that it may not fully solve the issue.
>>
>> Please let me explain my understanding on this:
>>
>> (Please refer to the logs in v10)
>> scsi_autopm_*() are invoked on a sdev.
>> pm_runtime_get_suppliers()/rpm_put_suppliers() are on the supplier device.
>>
>> For the device wlun:
>> slave_configure():
>> - doesn't set the rpm_autosuspend
>> - pm_runtime_getnoresume()
>> scsi_sysfs_add_sdev():
>> - pm_runtime_forbid()
>> - scsi_autopm_get_device()
>> - device_add()
>> - ufshcd_wl_probe()
>> - scsi_autopm_put_device()
>>
>> For all other scsi devices:
>> slave_alloc():
>> - ufshcd_setup_links()
>> Say all link_add: pm_runtime_put(&hba->sdev_ufs_device->sdev_gendev);
>
> With DL_FLAG_RPM_ACTIVE, links will 'get' not 'put'
>
I'm referring to the pm_runtime_put(sdev_ufs_device) after all the links
are setup, that you suggested to add.
>> slave_configure():
>> - set rpm_autosuspend
>> scsi_sysfs_add_sdev():
>> - scsi_autopm_get_device()
>> - device_add() -> schedules an async probe()
>> - scsi_autopm_put_device() - (1)
>>
>> Now the rpm_put_suppliers() can be invoked *after* pm_runtime_get_suppliers() of the async probe(), since both are running in different contexts.
>
> Only if the sd device suspends.
>
Correct. What'd stop the sd device from suspending?
We should be stopping the sd device from suspending here - imho.
>> In that case, the usage_count of supplier would be decremented until rpm_active of this link becomes 1.
>
> Right, because the sd device suspended.
>
>> Now the pm_runtime_get_suppliers() expects the link_active to be more than 1.
>
> Not sure what you mean here. pm_runtime_*put*_suppliers() won't do anything if the link count is 1.
I'm referring to the logs that I pasted before:
[ 6.941267][ T7] scsi 0:0:0:4: rpm_put_suppliers: [BEF] Supp
(0:0:0:49488) usage_count: 4 rpm_active: 3
------ T196 Context comes in while T7 is running ----------
[ 6.941466][ T196] scsi 0:0:0:4: pm_runtime_get_suppliers:
(0:0:0:49488): supp: usage_count: 5 rpm_active: 4
--------------------------------------------------------------
[ 7.788397][ T7] scsi 0:0:0:4: rpm_put_suppliers: [AFT] Supp
(0:0:0:49488) usage_count: 2 rpm_active: 1
I meant to say that, if the rpm_put_suppliers() is invoked after the
pm_runtime_get_suppliers() as is seen above then the link_active may
become 1 even *after* pm_runtime_get_suppliers() is invoked.
I'm referring to the pm_runtime_get_suppliers() invoked from:
driver_probe_device() - say for, sd 0:0:0:x
|- pm_runtime_get_suppliers() - for sd 0:0:0:49488
>
>> Now then, there comes a time, that when sd_probe() schedules a suspend, the supplier usage_count becomes 0 and the link_active becomes 1.
>> And the supplier suspends before the consumer.
>
> sd probe first resumes the sd device which will resume the supplier.
>
Correct, but it'd again schedule a suspend (since autosuspend is enabled
now) at the end of the sd_probe().
Thereafter, pm_runtime_put_suppliers()(sd 0:0:0:49488) is invoked from
driver_probe_device() which had actually invoked sd_probe().
That'd make the link_active to 1 even when sd 0:0:0:x is active.
>>
>> So I was wondering, what'd make sure that the pm_runtime_get_suppliers() from driver_probe_device() is invoked after scsi_autopm_put_device() (1) finishes the rpm_put_suppliers().
>>
>> Am not sure if I'm missing something in this.
>> Do you think, the current changes alone can fix the above issue?
>
> Yes, but let's see.
>
Essentially, we should stop the sd device from runtime suspending until
it's probe is done. Then allow the same. Does it make sense?
Please let me know what you think.
-asd
>>
>> -asd
>>
>>>> ---
>>>> drivers/scsi/ufs/cdns-pltfrm.c | 2 +
>>>> drivers/scsi/ufs/tc-dwc-g210-pci.c | 2 +
>>>> drivers/scsi/ufs/ufs-debugfs.c | 2 +-
>>>> drivers/scsi/ufs/ufs-debugfs.h | 2 +-
>>>> drivers/scsi/ufs/ufs-exynos.c | 2 +
>>>> drivers/scsi/ufs/ufs-hisi.c | 2 +
>>>> drivers/scsi/ufs/ufs-mediatek.c | 12 +-
>>>> drivers/scsi/ufs/ufs-qcom.c | 2 +
>>>> drivers/scsi/ufs/ufs_bsg.c | 6 +-
>>>> drivers/scsi/ufs/ufshcd-pci.c | 36 +--
>>>> drivers/scsi/ufs/ufshcd.c | 622 ++++++++++++++++++++++++++-----------
>>>> drivers/scsi/ufs/ufshcd.h | 6 +
>>>> include/trace/events/ufs.h | 20 ++
>>>> 13 files changed, 491 insertions(+), 225 deletions(-)
>>>>
>>>> diff --git a/drivers/scsi/ufs/cdns-pltfrm.c b/drivers/scsi/ufs/cdns-pltfrm.c
>>>> index 149391f..3e70c23 100644
>>>> --- a/drivers/scsi/ufs/cdns-pltfrm.c
>>>> +++ b/drivers/scsi/ufs/cdns-pltfrm.c
>>>> @@ -319,6 +319,8 @@ static const struct dev_pm_ops cdns_ufs_dev_pm_ops = {
>>>> .runtime_suspend = ufshcd_pltfrm_runtime_suspend,
>>>> .runtime_resume = ufshcd_pltfrm_runtime_resume,
>>>> .runtime_idle = ufshcd_pltfrm_runtime_idle,
>>>> + .prepare = ufshcd_suspend_prepare,
>>>> + .complete = ufshcd_resume_complete,
>>>> };
>>>> static struct platform_driver cdns_ufs_pltfrm_driver = {
>>>> diff --git a/drivers/scsi/ufs/tc-dwc-g210-pci.c b/drivers/scsi/ufs/tc-dwc-g210-pci.c
>>>> index 67a6a61..b01db12 100644
>>>> --- a/drivers/scsi/ufs/tc-dwc-g210-pci.c
>>>> +++ b/drivers/scsi/ufs/tc-dwc-g210-pci.c
>>>> @@ -148,6 +148,8 @@ static const struct dev_pm_ops tc_dwc_g210_pci_pm_ops = {
>>>> .runtime_suspend = tc_dwc_g210_pci_runtime_suspend,
>>>> .runtime_resume = tc_dwc_g210_pci_runtime_resume,
>>>> .runtime_idle = tc_dwc_g210_pci_runtime_idle,
>>>> + .prepare = ufshcd_suspend_prepare,
>>>> + .complete = ufshcd_resume_complete,
>>>> };
>>>> static const struct pci_device_id tc_dwc_g210_pci_tbl[] = {
>>>> diff --git a/drivers/scsi/ufs/ufs-debugfs.c b/drivers/scsi/ufs/ufs-debugfs.c
>>>> index dee98dc..06457d5 100644
>>>> --- a/drivers/scsi/ufs/ufs-debugfs.c
>>>> +++ b/drivers/scsi/ufs/ufs-debugfs.c
>>>> @@ -13,7 +13,7 @@ void __init ufs_debugfs_init(void)
>>>> ufs_debugfs_root = debugfs_create_dir("ufshcd", NULL);
>>>> }
>>>> -void __exit ufs_debugfs_exit(void)
>>>> +void ufs_debugfs_exit(void)
>>>> {
>>>> debugfs_remove_recursive(ufs_debugfs_root);
>>>> }
>>>> diff --git a/drivers/scsi/ufs/ufs-debugfs.h b/drivers/scsi/ufs/ufs-debugfs.h
>>>> index f35b39c..12c2730 100644
>>>> --- a/drivers/scsi/ufs/ufs-debugfs.h
>>>> +++ b/drivers/scsi/ufs/ufs-debugfs.h
>>>> @@ -9,7 +9,7 @@ struct ufs_hba;
>>>> #ifdef CONFIG_DEBUG_FS
>>>> void __init ufs_debugfs_init(void);
>>>> -void __exit ufs_debugfs_exit(void);
>>>> +void ufs_debugfs_exit(void);
>>>> void ufs_debugfs_hba_init(struct ufs_hba *hba);
>>>> void ufs_debugfs_hba_exit(struct ufs_hba *hba);
>>>> #else
>>>> diff --git a/drivers/scsi/ufs/ufs-exynos.c b/drivers/scsi/ufs/ufs-exynos.c
>>>> index 267943a1..45c0b02 100644
>>>> --- a/drivers/scsi/ufs/ufs-exynos.c
>>>> +++ b/drivers/scsi/ufs/ufs-exynos.c
>>>> @@ -1268,6 +1268,8 @@ static const struct dev_pm_ops exynos_ufs_pm_ops = {
>>>> .runtime_suspend = ufshcd_pltfrm_runtime_suspend,
>>>> .runtime_resume = ufshcd_pltfrm_runtime_resume,
>>>> .runtime_idle = ufshcd_pltfrm_runtime_idle,
>>>> + .prepare = ufshcd_suspend_prepare,
>>>> + .complete = ufshcd_resume_complete,
>>>> };
>>>> static struct platform_driver exynos_ufs_pltform = {
>>>> diff --git a/drivers/scsi/ufs/ufs-hisi.c b/drivers/scsi/ufs/ufs-hisi.c
>>>> index 0aa5813..d463b44 100644
>>>> --- a/drivers/scsi/ufs/ufs-hisi.c
>>>> +++ b/drivers/scsi/ufs/ufs-hisi.c
>>>> @@ -574,6 +574,8 @@ static const struct dev_pm_ops ufs_hisi_pm_ops = {
>>>> .runtime_suspend = ufshcd_pltfrm_runtime_suspend,
>>>> .runtime_resume = ufshcd_pltfrm_runtime_resume,
>>>> .runtime_idle = ufshcd_pltfrm_runtime_idle,
>>>> + .prepare = ufshcd_suspend_prepare,
>>>> + .complete = ufshcd_resume_complete,
>>>> };
>>>> static struct platform_driver ufs_hisi_pltform = {
>>>> diff --git a/drivers/scsi/ufs/ufs-mediatek.c b/drivers/scsi/ufs/ufs-mediatek.c
>>>> index c55202b..51c82e3 100644
>>>> --- a/drivers/scsi/ufs/ufs-mediatek.c
>>>> +++ b/drivers/scsi/ufs/ufs-mediatek.c
>>>> @@ -810,12 +810,10 @@ static int ufs_mtk_post_link(struct ufs_hba *hba)
>>>> /* enable unipro clock gating feature */
>>>> ufs_mtk_cfg_unipro_cg(hba, true);
>>>> - /* configure auto-hibern8 timer to 10ms */
>>>> - if (ufshcd_is_auto_hibern8_supported(hba)) {
>>>> - ufshcd_auto_hibern8_update(hba,
>>>> - FIELD_PREP(UFSHCI_AHIBERN8_TIMER_MASK, 10) |
>>>> - FIELD_PREP(UFSHCI_AHIBERN8_SCALE_MASK, 3));
>>>> - }
>>>> + /* will be configured during probe hba */
>>>> + if (ufshcd_is_auto_hibern8_supported(hba))
>>>> + hba->ahit = FIELD_PREP(UFSHCI_AHIBERN8_TIMER_MASK, 10) |
>>>> + FIELD_PREP(UFSHCI_AHIBERN8_SCALE_MASK, 3);
>>>> ufs_mtk_setup_clk_gating(hba);
>>>> @@ -1097,6 +1095,8 @@ static const struct dev_pm_ops ufs_mtk_pm_ops = {
>>>> .runtime_suspend = ufshcd_pltfrm_runtime_suspend,
>>>> .runtime_resume = ufshcd_pltfrm_runtime_resume,
>>>> .runtime_idle = ufshcd_pltfrm_runtime_idle,
>>>> + .prepare = ufshcd_suspend_prepare,
>>>> + .complete = ufshcd_resume_complete,
>>>> };
>>>> static struct platform_driver ufs_mtk_pltform = {
>>>> diff --git a/drivers/scsi/ufs/ufs-qcom.c b/drivers/scsi/ufs/ufs-qcom.c
>>>> index f97d7b0..9aa098a 100644
>>>> --- a/drivers/scsi/ufs/ufs-qcom.c
>>>> +++ b/drivers/scsi/ufs/ufs-qcom.c
>>>> @@ -1546,6 +1546,8 @@ static const struct dev_pm_ops ufs_qcom_pm_ops = {
>>>> .runtime_suspend = ufshcd_pltfrm_runtime_suspend,
>>>> .runtime_resume = ufshcd_pltfrm_runtime_resume,
>>>> .runtime_idle = ufshcd_pltfrm_runtime_idle,
>>>> + .prepare = ufshcd_suspend_prepare,
>>>> + .complete = ufshcd_resume_complete,
>>>> };
>>>> static struct platform_driver ufs_qcom_pltform = {
>>>> diff --git a/drivers/scsi/ufs/ufs_bsg.c b/drivers/scsi/ufs/ufs_bsg.c
>>>> index 5b2bc1a..cbb5a90 100644
>>>> --- a/drivers/scsi/ufs/ufs_bsg.c
>>>> +++ b/drivers/scsi/ufs/ufs_bsg.c
>>>> @@ -97,7 +97,7 @@ static int ufs_bsg_request(struct bsg_job *job)
>>>> bsg_reply->reply_payload_rcv_len = 0;
>>>> - pm_runtime_get_sync(hba->dev);
>>>> + scsi_autopm_get_device(hba->sdev_ufs_device);
>>>> msgcode = bsg_request->msgcode;
>>>> switch (msgcode) {
>>>> @@ -106,7 +106,7 @@ static int ufs_bsg_request(struct bsg_job *job)
>>>> ret = ufs_bsg_alloc_desc_buffer(hba, job, &desc_buff,
>>>> &desc_len, desc_op);
>>>> if (ret) {
>>>> - pm_runtime_put_sync(hba->dev);
>>>> + scsi_autopm_put_device(hba->sdev_ufs_device);
>>>> goto out;
>>>> }
>>>> @@ -138,7 +138,7 @@ static int ufs_bsg_request(struct bsg_job *job)
>>>> break;
>>>> }
>>>> - pm_runtime_put_sync(hba->dev);
>>>> + scsi_autopm_put_device(hba->sdev_ufs_device);
>>>> if (!desc_buff)
>>>> goto out;
>>>> diff --git a/drivers/scsi/ufs/ufshcd-pci.c b/drivers/scsi/ufs/ufshcd-pci.c
>>>> index fadd566..5d4ffd2 100644
>>>> --- a/drivers/scsi/ufs/ufshcd-pci.c
>>>> +++ b/drivers/scsi/ufs/ufshcd-pci.c
>>>> @@ -247,29 +247,6 @@ static int ufshcd_pci_resume(struct device *dev)
>>>> return ufshcd_system_resume(dev_get_drvdata(dev));
>>>> }
>>>> -/**
>>>> - * ufshcd_pci_poweroff - suspend-to-disk poweroff function
>>>> - * @dev: pointer to PCI device handle
>>>> - *
>>>> - * Returns 0 if successful
>>>> - * Returns non-zero otherwise
>>>> - */
>>>> -static int ufshcd_pci_poweroff(struct device *dev)
>>>> -{
>>>> - struct ufs_hba *hba = dev_get_drvdata(dev);
>>>> - int spm_lvl = hba->spm_lvl;
>>>> - int ret;
>>>> -
>>>> - /*
>>>> - * For poweroff we need to set the UFS device to PowerDown mode.
>>>> - * Force spm_lvl to ensure that.
>>>> - */
>>>> - hba->spm_lvl = 5;
>>>> - ret = ufshcd_system_suspend(hba);
>>>> - hba->spm_lvl = spm_lvl;
>>>> - return ret;
>>>> -}
>>>> -
>>>> #endif /* !CONFIG_PM_SLEEP */
>>>> #ifdef CONFIG_PM
>>>> @@ -365,17 +342,14 @@ ufshcd_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id)
>>>> }
>>>> static const struct dev_pm_ops ufshcd_pci_pm_ops = {
>>>> -#ifdef CONFIG_PM_SLEEP
>>>> - .suspend = ufshcd_pci_suspend,
>>>> - .resume = ufshcd_pci_resume,
>>>> - .freeze = ufshcd_pci_suspend,
>>>> - .thaw = ufshcd_pci_resume,
>>>> - .poweroff = ufshcd_pci_poweroff,
>>>> - .restore = ufshcd_pci_resume,
>>>> -#endif
>>>> SET_RUNTIME_PM_OPS(ufshcd_pci_runtime_suspend,
>>>> ufshcd_pci_runtime_resume,
>>>> ufshcd_pci_runtime_idle)
>>>> + SET_SYSTEM_SLEEP_PM_OPS(ufshcd_pci_suspend, ufshcd_pci_resume)
>>>> +#ifdef CONFIG_PM_SLEEP
>>>> + .prepare = ufshcd_suspend_prepare,
>>>> + .complete = ufshcd_resume_complete,
>>>> +#endif
>>>> };
>>>> static const struct pci_device_id ufshcd_pci_tbl[] = {
>>>> diff --git a/drivers/scsi/ufs/ufshcd.c b/drivers/scsi/ufs/ufshcd.c
>>>> index 45624c7..7106cb8 100644
>>>> --- a/drivers/scsi/ufs/ufshcd.c
>>>> +++ b/drivers/scsi/ufs/ufshcd.c
>>>> @@ -16,6 +16,7 @@
>>>> #include <linux/bitfield.h>
>>>> #include <linux/blk-pm.h>
>>>> #include <linux/blkdev.h>
>>>> +#include <scsi/scsi_driver.h>
>>>> #include "ufshcd.h"
>>>> #include "ufs_quirks.h"
>>>> #include "unipro.h"
>>>> @@ -78,6 +79,8 @@
>>>> /* Polling time to wait for fDeviceInit */
>>>> #define FDEVICEINIT_COMPL_TIMEOUT 1500 /* millisecs */
>>>> +#define wlun_dev_to_hba(dv) shost_priv(to_scsi_device(dv)->host)
>>>> +
>>>> #define ufshcd_toggle_vreg(_dev, _vreg, _on) \
>>>> ({ \
>>>> int _ret; \
>>>> @@ -1556,7 +1559,7 @@ static ssize_t ufshcd_clkscale_enable_store(struct device *dev,
>>>> if (value == hba->clk_scaling.is_enabled)
>>>> goto out;
>>>> - pm_runtime_get_sync(hba->dev);
>>>> + scsi_autopm_get_device(hba->sdev_ufs_device);
>>>> ufshcd_hold(hba, false);
>>>> hba->clk_scaling.is_enabled = value;
>>>> @@ -1572,7 +1575,7 @@ static ssize_t ufshcd_clkscale_enable_store(struct device *dev,
>>>> }
>>>> ufshcd_release(hba);
>>>> - pm_runtime_put_sync(hba->dev);
>>>> + scsi_autopm_put_device(hba->sdev_ufs_device);
>>>> out:
>>>> up(&hba->host_sem);
>>>> return err ? err : count;
>>>> @@ -2572,6 +2575,17 @@ static inline u16 ufshcd_upiu_wlun_to_scsi_wlun(u8 upiu_wlun_id)
>>>> return (upiu_wlun_id & ~UFS_UPIU_WLUN_ID) | SCSI_W_LUN_BASE;
>>>> }
>>>> +static inline bool is_rpmb_wlun(struct scsi_device *sdev)
>>>> +{
>>>> + return (sdev->lun == ufshcd_upiu_wlun_to_scsi_wlun(UFS_UPIU_RPMB_WLUN));
>>>> +}
>>>> +
>>>> +static inline bool is_device_wlun(struct scsi_device *sdev)
>>>> +{
>>>> + return (sdev->lun ==
>>>> + ufshcd_upiu_wlun_to_scsi_wlun(UFS_UPIU_UFS_DEVICE_WLUN));
>>>> +}
>>>> +
>>>> static void ufshcd_init_lrb(struct ufs_hba *hba, struct ufshcd_lrb *lrb, int i)
>>>> {
>>>> struct utp_transfer_cmd_desc *cmd_descp = hba->ucdl_base_addr;
>>>> @@ -4106,11 +4120,11 @@ void ufshcd_auto_hibern8_update(struct ufs_hba *hba, u32 ahit)
>>>> spin_unlock_irqrestore(hba->host->host_lock, flags);
>>>> if (update && !pm_runtime_suspended(hba->dev)) {
>>>> - pm_runtime_get_sync(hba->dev);
>>>> + scsi_autopm_get_device(hba->sdev_ufs_device);
>>>> ufshcd_hold(hba, false);
>>>> ufshcd_auto_hibern8_enable(hba);
>>>> ufshcd_release(hba);
>>>> - pm_runtime_put(hba->dev);
>>>> + scsi_autopm_put_device(hba->sdev_ufs_device);
>>>> }
>>>> }
>>>> EXPORT_SYMBOL_GPL(ufshcd_auto_hibern8_update);
>>>> @@ -4808,6 +4822,41 @@ static inline void ufshcd_get_lu_power_on_wp_status(struct ufs_hba *hba,
>>>> }
>>>> /**
>>>> + * ufshcd_setup_links - associate link b/w device wlun and other luns
>>>> + * @sdev: pointer to SCSI device
>>>> + * @hba: pointer to ufs hba
>>>> + */
>>>> +static void ufshcd_setup_links(struct ufs_hba *hba, struct scsi_device *sdev)
>>>> +{
>>>> + struct device_link *link;
>>>> +
>>>> + /*
>>>> + * device wlun is the supplier & rest of the luns are consumers
>>>> + * This ensures that device wlun suspends after all other luns.
>>>> + */
>>>> + if (hba->sdev_ufs_device) {
>>>> + link = device_link_add(&sdev->sdev_gendev,
>>>> + &hba->sdev_ufs_device->sdev_gendev,
>>>> + DL_FLAG_PM_RUNTIME|DL_FLAG_RPM_ACTIVE);
>>>> + if (!link) {
>>>> + dev_err(&sdev->sdev_gendev, "Failed establishing link - %s\n",
>>>> + dev_name(&hba->sdev_ufs_device->sdev_gendev));
>>>> + return;
>>>> + }
>>>> + hba->luns_avail--;
>>>> + /* Ignore REPORT_LUN wlun probing */
>>>> + if (hba->luns_avail != 1) {
>>>
>>> The condition here is the wrong way around. i.e. should be
>>>
>>> if (hba->luns_avail == 1) {
>>>
>>>> + pm_runtime_put_noidle(&hba->sdev_ufs_device->sdev_gendev);
>>>
>>> If the usage_count is zero here, then we should let it suspend i.e. make this:
>>>
>>> pm_runtime_put(&hba->sdev_ufs_device->sdev_gendev);
>>>
>>> Note this pairs with the pm_runtime_get_noresume() that needs to be added
>>> to ufshcd_slave_configure. Please see comments for ufshcd_slave_configure().
>>>
>>>
>>>> + pm_runtime_allow(&hba->sdev_ufs_device->sdev_gendev);
>>>
>>> This pm_runtime_allow should not be here. It should be in ufshcd_wl_probe().
>>> Please see comments for ufshcd_wl_probe().
>>>
>>>> + return;
>>>> + }
>>>> + } else {
>>>> + /* device wlun is probed */
>>>> + hba->luns_avail--;
>>>> + }
>>>> +}
>>>> +
>>>> +/**
>>>> * ufshcd_slave_alloc - handle initial SCSI device configurations
>>>> * @sdev: pointer to SCSI device
>>>> *
>>>> @@ -4838,6 +4887,8 @@ static int ufshcd_slave_alloc(struct scsi_device *sdev)
>>>> ufshcd_get_lu_power_on_wp_status(hba, sdev);
>>>> + ufshcd_setup_links(hba, sdev);
>>>> +
>>>> return 0;
>>>> }
>>>> @@ -4869,8 +4920,13 @@ static int ufshcd_slave_configure(struct scsi_device *sdev)
>>>> blk_queue_update_dma_pad(q, PRDT_DATA_BYTE_COUNT_PAD - 1);
>>>> if (hba->quirks & UFSHCD_QUIRK_ALIGN_SG_WITH_PAGE_SIZE)
>>>> blk_queue_update_dma_alignment(q, PAGE_SIZE - 1);
>>>> -
>>>> - if (ufshcd_is_rpm_autosuspend_allowed(hba))
>>>> + /*
>>>> + * Block runtime-pm until all consumers are added.
>>>> + * Refer ufshcd_setup_links().
>>>> + */
>>>> + if (is_device_wlun(sdev))
>>>> + pm_runtime_forbid(&sdev->sdev_gendev);
>>>
>>> Since rpm_autosuspend is not being set for sdev_ufs_device, scsi_sysfs_add_sdev()
>>> will pm_runtime_forbid() it anyway. We should increase the usage_count instead
>>> i.e.
>>>
>>> pm_runtime_get_noresume(&sdev->sdev_gendev);
>>>
>>>> + else if (ufshcd_is_rpm_autosuspend_allowed(hba))
>>>> sdev->rpm_autosuspend = 1;
>>>> ufshcd_crypto_setup_rq_keyslot_manager(hba, q);
>>>> @@ -4985,15 +5041,9 @@ ufshcd_transfer_rsp_status(struct ufs_hba *hba, struct ufshcd_lrb *lrbp)
>>>> * UFS device needs urgent BKOPs.
>>>> */
>>>> if (!hba->pm_op_in_progress &&
>>>> - ufshcd_is_exception_event(lrbp->ucd_rsp_ptr) &&
>>>> - schedule_work(&hba->eeh_work)) {
>>>> - /*
>>>> - * Prevent suspend once eeh_work is scheduled
>>>> - * to avoid deadlock between ufshcd_suspend
>>>> - * and exception event handler.
>>>> - */
>>>> - pm_runtime_get_noresume(hba->dev);
>>>> - }
>>>> + ufshcd_is_exception_event(lrbp->ucd_rsp_ptr))
>>>> + /* Flushed in suspend */
>>>> + schedule_work(&hba->eeh_work);
>>>> break;
>>>> case UPIU_TRANSACTION_REJECT_UPIU:
>>>> /* TODO: handle Reject UPIU Response */
>>>> @@ -5589,8 +5639,8 @@ static void ufshcd_rpm_dev_flush_recheck_work(struct work_struct *work)
>>>> * after a certain delay to recheck the threshold by next runtime
>>>> * suspend.
>>>> */
>>>> - pm_runtime_get_sync(hba->dev);
>>>> - pm_runtime_put_sync(hba->dev);
>>>> + scsi_autopm_get_device(hba->sdev_ufs_device);
>>>> + scsi_autopm_put_device(hba->sdev_ufs_device);
>>>> }
>>>> /**
>>>> @@ -5607,7 +5657,6 @@ static void ufshcd_exception_event_handler(struct work_struct *work)
>>>> u32 status = 0;
>>>> hba = container_of(work, struct ufs_hba, eeh_work);
>>>> - pm_runtime_get_sync(hba->dev);
>>>> ufshcd_scsi_block_requests(hba);
>>>> err = ufshcd_get_ee_status(hba, &status);
>>>> if (err) {
>>>> @@ -5623,14 +5672,6 @@ static void ufshcd_exception_event_handler(struct work_struct *work)
>>>> out:
>>>> ufshcd_scsi_unblock_requests(hba);
>>>> - /*
>>>> - * pm_runtime_get_noresume is called while scheduling
>>>> - * eeh_work to avoid suspend racing with exception work.
>>>> - * Hence decrement usage counter using pm_runtime_put_noidle
>>>> - * to allow suspend on completion of exception event handler.
>>>> - */
>>>> - pm_runtime_put_noidle(hba->dev);
>>>> - pm_runtime_put(hba->dev);
>>>> return;
>>>> }
>>>> @@ -5755,12 +5796,13 @@ static void ufshcd_clk_scaling_suspend(struct ufs_hba *hba, bool suspend)
>>>> static void ufshcd_err_handling_prepare(struct ufs_hba *hba)
>>>> {
>>>> - pm_runtime_get_sync(hba->dev);
>>>> - if (pm_runtime_status_suspended(hba->dev) || hba->is_sys_suspended) {
>>>> + scsi_autopm_get_device(hba->sdev_ufs_device);
>>>> + if (pm_runtime_status_suspended(&hba->sdev_ufs_device->sdev_gendev) ||
>>>> + hba->is_sys_suspended) {
>>>> enum ufs_pm_op pm_op;
>>>> /*
>>>> - * Don't assume anything of pm_runtime_get_sync(), if
>>>> + * Don't assume anything of resume, if
>>>> * resume fails, irq and clocks can be OFF, and powers
>>>> * can be OFF or in LPM.
>>>> */
>>>> @@ -5789,7 +5831,7 @@ static void ufshcd_err_handling_unprepare(struct ufs_hba *hba)
>>>> ufshcd_release(hba);
>>>> if (ufshcd_is_clkscaling_supported(hba))
>>>> ufshcd_clk_scaling_suspend(hba, false);
>>>> - pm_runtime_put(hba->dev);
>>>> + pm_runtime_put(&hba->sdev_ufs_device->sdev_gendev);
>>>> }
>>>> static inline bool ufshcd_err_handling_should_stop(struct ufs_hba *hba)
>>>> @@ -5810,14 +5852,14 @@ static void ufshcd_recover_pm_error(struct ufs_hba *hba)
>>>> hba->is_sys_suspended = false;
>>>> /*
>>>> - * Set RPM status of hba device to RPM_ACTIVE,
>>>> + * Set RPM status of wlun device to RPM_ACTIVE,
>>>> * this also clears its runtime error.
>>>> */
>>>> - ret = pm_runtime_set_active(hba->dev);
>>>> + ret = pm_runtime_set_active(&hba->sdev_ufs_device->sdev_gendev);
>>>> /*
>>>> - * If hba device had runtime error, we also need to resume those
>>>> - * scsi devices under hba in case any of them has failed to be
>>>> - * resumed due to hba runtime resume failure. This is to unblock
>>>> + * If wlun device had runtime error, we also need to resume those
>>>> + * consumer scsi devices in case any of them has failed to be
>>>> + * resumed due to supplier runtime resume failure. This is to unblock
>>>> * blk_queue_enter in case there are bios waiting inside it.
>>>> */
>>>> if (!ret) {
>>>> @@ -7207,11 +7249,12 @@ static void ufshcd_set_active_icc_lvl(struct ufs_hba *hba)
>>>> static inline void ufshcd_blk_pm_runtime_init(struct scsi_device *sdev)
>>>> {
>>>> + int dly = is_device_wlun(sdev) ? 0:RPM_AUTOSUSPEND_DELAY_MS;
>>>> +
>>>> scsi_autopm_get_device(sdev);
>>>> blk_pm_runtime_init(sdev->request_queue, &sdev->sdev_gendev);
>>>> if (sdev->rpm_autosuspend)
>>>> - pm_runtime_set_autosuspend_delay(&sdev->sdev_gendev,
>>>> - RPM_AUTOSUSPEND_DELAY_MS);
>>>> + pm_runtime_set_autosuspend_delay(&sdev->sdev_gendev, dly);
>>>> scsi_autopm_put_device(sdev);
>>>> }
>>>> @@ -7253,6 +7296,7 @@ static int ufshcd_scsi_add_wlus(struct ufs_hba *hba)
>>>> hba->sdev_ufs_device = NULL;
>>>> goto out;
>>>> }
>>>> + scsi_autopm_get_device(hba->sdev_ufs_device);
>>>> ufshcd_blk_pm_runtime_init(hba->sdev_ufs_device);
>>>
>>> Let's drop both scsi_autopm_get_device() and ufshcd_blk_pm_runtime_init()
>>> here and instead do things in ufshcd_wl_probe() - see comments for ufshcd_wl_probe()
>>>
>>>> scsi_device_put(hba->sdev_ufs_device);
>>>> @@ -7417,6 +7461,9 @@ static int ufs_get_device_desc(struct ufs_hba *hba)
>>>> goto out;
>>>> }
>>>> + hba->luns_avail = desc_buf[DEVICE_DESC_PARAM_NUM_LU] +
>>>> + desc_buf[DEVICE_DESC_PARAM_NUM_WLU];
>>>> +
>>>> ufs_fixup_device_setup(hba);
>>>> ufshcd_wb_probe(hba, desc_buf);
>>>> @@ -7892,6 +7939,7 @@ static int ufshcd_probe_hba(struct ufs_hba *hba, bool async)
>>>> ufshcd_set_ufs_dev_active(hba);
>>>> ufshcd_force_reset_auto_bkops(hba);
>>>> hba->wlun_dev_clr_ua = true;
>>>> + hba->wlun_rpmb_clr_ua = true;
>>>> /* Gear up to HS gear if supported */
>>>> if (hba->max_pwr_info.is_valid) {
>>>> @@ -8475,7 +8523,8 @@ static int ufshcd_set_dev_pwr_mode(struct ufs_hba *hba,
>>>> * handling context.
>>>> */
>>>> hba->host->eh_noresume = 1;
>>>> - ufshcd_clear_ua_wluns(hba);
>>>> + if (hba->wlun_dev_clr_ua)
>>>> + ufshcd_clear_ua_wlun(hba, UFS_UPIU_UFS_DEVICE_WLUN);
>>>> cmd[4] = pwr_mode << 4;
>>>> @@ -8650,23 +8699,7 @@ static void ufshcd_hba_vreg_set_hpm(struct ufs_hba *hba)
>>>> ufshcd_setup_hba_vreg(hba, true);
>>>> }
>>>> -/**
>>>> - * ufshcd_suspend - helper function for suspend operations
>>>> - * @hba: per adapter instance
>>>> - * @pm_op: desired low power operation type
>>>> - *
>>>> - * This function will try to put the UFS device and link into low power
>>>> - * mode based on the "rpm_lvl" (Runtime PM level) or "spm_lvl"
>>>> - * (System PM level).
>>>> - *
>>>> - * If this function is called during shutdown, it will make sure that
>>>> - * both UFS device and UFS link is powered off.
>>>> - *
>>>> - * NOTE: UFS device & link must be active before we enter in this function.
>>>> - *
>>>> - * Returns 0 for success and non-zero for failure
>>>> - */
>>>> -static int ufshcd_suspend(struct ufs_hba *hba, enum ufs_pm_op pm_op)
>>>> +static int __ufshcd_wl_suspend(struct ufs_hba *hba, enum ufs_pm_op pm_op)
>>>> {
>>>> int ret = 0;
>>>> int check_for_bkops;
>>>> @@ -8674,7 +8707,7 @@ static int ufshcd_suspend(struct ufs_hba *hba, enum ufs_pm_op pm_op)
>>>> enum ufs_dev_pwr_mode req_dev_pwr_mode;
>>>> enum uic_link_state req_link_state;
>>>> - hba->pm_op_in_progress = 1;
>>>> + hba->pm_op_in_progress = true;
>>>> if (!ufshcd_is_shutdown_pm(pm_op)) {
>>>> pm_lvl = ufshcd_is_runtime_pm(pm_op) ?
>>>> hba->rpm_lvl : hba->spm_lvl;
>>>> @@ -8697,17 +8730,17 @@ static int ufshcd_suspend(struct ufs_hba *hba, enum ufs_pm_op pm_op)
>>>> if (req_dev_pwr_mode == UFS_ACTIVE_PWR_MODE &&
>>>> req_link_state == UIC_LINK_ACTIVE_STATE) {
>>>> - goto disable_clks;
>>>> + goto enable_scaling;
>>>> }
>>>> if ((req_dev_pwr_mode == hba->curr_dev_pwr_mode) &&
>>>> (req_link_state == hba->uic_link_state))
>>>> - goto enable_gating;
>>>> + goto enable_scaling;
>>>> /* UFS device & link must be active before we enter in this function */
>>>> if (!ufshcd_is_ufs_dev_active(hba) || !ufshcd_is_link_active(hba)) {
>>>> ret = -EINVAL;
>>>> - goto enable_gating;
>>>> + goto enable_scaling;
>>>> }
>>>> if (ufshcd_is_runtime_pm(pm_op)) {
>>>> @@ -8719,7 +8752,7 @@ static int ufshcd_suspend(struct ufs_hba *hba, enum ufs_pm_op pm_op)
>>>> */
>>>> ret = ufshcd_urgent_bkops(hba);
>>>> if (ret)
>>>> - goto enable_gating;
>>>> + goto enable_scaling;
>>>> } else {
>>>> /* make sure that auto bkops is disabled */
>>>> ufshcd_disable_auto_bkops(hba);
>>>> @@ -8747,7 +8780,7 @@ static int ufshcd_suspend(struct ufs_hba *hba, enum ufs_pm_op pm_op)
>>>> if (!hba->dev_info.b_rpm_dev_flush_capable) {
>>>> ret = ufshcd_set_dev_pwr_mode(hba, req_dev_pwr_mode);
>>>> if (ret)
>>>> - goto enable_gating;
>>>> + goto enable_scaling;
>>>> }
>>>> }
>>>> @@ -8760,7 +8793,6 @@ static int ufshcd_suspend(struct ufs_hba *hba, enum ufs_pm_op pm_op)
>>>> if (ret)
>>>> goto set_dev_active;
>>>> -disable_clks:
>>>> /*
>>>> * Call vendor specific suspend callback. As these callbacks may access
>>>> * vendor specific host controller register space call them before the
>>>> @@ -8769,28 +8801,9 @@ static int ufshcd_suspend(struct ufs_hba *hba, enum ufs_pm_op pm_op)
>>>> ret = ufshcd_vops_suspend(hba, pm_op);
>>>> if (ret)
>>>> goto set_link_active;
>>>> - /*
>>>> - * Disable the host irq as host controller as there won't be any
>>>> - * host controller transaction expected till resume.
>>>> - */
>>>> - ufshcd_disable_irq(hba);
>>>> -
>>>> - ufshcd_setup_clocks(hba, false);
>>>> -
>>>> - if (ufshcd_is_clkgating_allowed(hba)) {
>>>> - hba->clk_gating.state = CLKS_OFF;
>>>> - trace_ufshcd_clk_gating(dev_name(hba->dev),
>>>> - hba->clk_gating.state);
>>>> - }
>>>> -
>>>> - ufshcd_vreg_set_lpm(hba);
>>>> -
>>>> - /* Put the host controller in low power mode if possible */
>>>> - ufshcd_hba_vreg_set_lpm(hba);
>>>> goto out;
>>>> set_link_active:
>>>> - ufshcd_vreg_set_hpm(hba);
>>>> /*
>>>> * Device hardware reset is required to exit DeepSleep. Also, for
>>>> * DeepSleep, the link is off so host reset and restore will be done
>>>> @@ -8812,57 +8825,32 @@ static int ufshcd_suspend(struct ufs_hba *hba, enum ufs_pm_op pm_op)
>>>> }
>>>> if (!ufshcd_set_dev_pwr_mode(hba, UFS_ACTIVE_PWR_MODE))
>>>> ufshcd_disable_auto_bkops(hba);
>>>> -enable_gating:
>>>> +enable_scaling:
>>>> if (ufshcd_is_clkscaling_supported(hba))
>>>> ufshcd_clk_scaling_suspend(hba, false);
>>>> - hba->clk_gating.is_suspended = false;
>>>> hba->dev_info.b_rpm_dev_flush_capable = false;
>>>> - ufshcd_clear_ua_wluns(hba);
>>>> - ufshcd_release(hba);
>>>> out:
>>>> if (hba->dev_info.b_rpm_dev_flush_capable) {
>>>> schedule_delayed_work(&hba->rpm_dev_flush_recheck_work,
>>>> msecs_to_jiffies(RPM_DEV_FLUSH_RECHECK_WORK_DELAY_MS));
>>>> }
>>>> - hba->pm_op_in_progress = 0;
>>>> -
>>>> - if (ret)
>>>> - ufshcd_update_evt_hist(hba, UFS_EVT_SUSPEND_ERR, (u32)ret);
>>>> + if (ret) {
>>>> + ufshcd_update_evt_hist(hba, UFS_EVT_WL_SUSP_ERR, (u32)ret);
>>>> + hba->clk_gating.is_suspended = false;
>>>> + ufshcd_release(hba);
>>>> + }
>>>> + hba->pm_op_in_progress = false;
>>>> return ret;
>>>> }
>>>> -/**
>>>> - * ufshcd_resume - helper function for resume operations
>>>> - * @hba: per adapter instance
>>>> - * @pm_op: runtime PM or system PM
>>>> - *
>>>> - * This function basically brings the UFS device, UniPro link and controller
>>>> - * to active state.
>>>> - *
>>>> - * Returns 0 for success and non-zero for failure
>>>> - */
>>>> -static int ufshcd_resume(struct ufs_hba *hba, enum ufs_pm_op pm_op)
>>>> +static int __ufshcd_wl_resume(struct ufs_hba *hba, enum ufs_pm_op pm_op)
>>>> {
>>>> int ret;
>>>> - enum uic_link_state old_link_state;
>>>> + enum uic_link_state old_link_state = hba->uic_link_state;
>>>> - hba->pm_op_in_progress = 1;
>>>> - old_link_state = hba->uic_link_state;
>>>> -
>>>> - ufshcd_hba_vreg_set_hpm(hba);
>>>> - ret = ufshcd_vreg_set_hpm(hba);
>>>> - if (ret)
>>>> - goto out;
>>>> -
>>>> - /* Make sure clocks are enabled before accessing controller */
>>>> - ret = ufshcd_setup_clocks(hba, true);
>>>> - if (ret)
>>>> - goto disable_vreg;
>>>> -
>>>> - /* enable the host irq as host controller would be active soon */
>>>> - ufshcd_enable_irq(hba);
>>>> + hba->pm_op_in_progress = true;
>>>> /*
>>>> * Call vendor specific resume callback. As these callbacks may access
>>>> @@ -8871,7 +8859,7 @@ static int ufshcd_resume(struct ufs_hba *hba, enum ufs_pm_op pm_op)
>>>> */
>>>> ret = ufshcd_vops_resume(hba, pm_op);
>>>> if (ret)
>>>> - goto disable_irq_and_vops_clks;
>>>> + goto out;
>>>> /* For DeepSleep, the only supported option is to have the link off */
>>>> WARN_ON(ufshcd_is_ufs_dev_deepsleep(hba) && !ufshcd_is_link_off(hba));
>>>> @@ -8916,31 +8904,147 @@ static int ufshcd_resume(struct ufs_hba *hba, enum ufs_pm_op pm_op)
>>>> */
>>>> ufshcd_urgent_bkops(hba);
>>>> - hba->clk_gating.is_suspended = false;
>>>> -
>>>> - if (ufshcd_is_clkscaling_supported(hba))
>>>> - ufshcd_clk_scaling_suspend(hba, false);
>>>> -
>>>> - /* Enable Auto-Hibernate if configured */
>>>> - ufshcd_auto_hibern8_enable(hba);
>>>> + if (hba->clk_scaling.is_allowed)
>>>> + ufshcd_resume_clkscaling(hba);
>>>> if (hba->dev_info.b_rpm_dev_flush_capable) {
>>>> hba->dev_info.b_rpm_dev_flush_capable = false;
>>>> cancel_delayed_work(&hba->rpm_dev_flush_recheck_work);
>>>> }
>>>> - ufshcd_clear_ua_wluns(hba);
>>>> -
>>>> - /* Schedule clock gating in case of no access to UFS device yet */
>>>> - ufshcd_release(hba);
>>>> -
>>>> + /* Enable Auto-Hibernate if configured */
>>>> + ufshcd_auto_hibern8_enable(hba);
>>>> goto out;
>>>> set_old_link_state:
>>>> ufshcd_link_state_transition(hba, old_link_state, 0);
>>>> vendor_suspend:
>>>> ufshcd_vops_suspend(hba, pm_op);
>>>> -disable_irq_and_vops_clks:
>>>> +out:
>>>> + if (ret)
>>>> + ufshcd_update_evt_hist(hba, UFS_EVT_WL_RES_ERR, (u32)ret);
>>>> + hba->clk_gating.is_suspended = false;
>>>> + ufshcd_release(hba);
>>>> + hba->pm_op_in_progress = false;
>>>> + return ret;
>>>> +}
>>>> +
>>>> +static int ufshcd_wl_runtime_suspend(struct device *dev)
>>>> +{
>>>> + struct scsi_device *sdev = to_scsi_device(dev);
>>>> + struct ufs_hba *hba;
>>>> + int ret;
>>>> + ktime_t start = ktime_get();
>>>> +
>>>> + hba = shost_priv(sdev->host);
>>>> +
>>>> + ret = __ufshcd_wl_suspend(hba, UFS_RUNTIME_PM);
>>>> + if (ret)
>>>> + dev_err(&sdev->sdev_gendev, "%s failed: %d\n", __func__, ret);
>>>> +
>>>> + trace_ufshcd_wl_runtime_suspend(dev_name(dev), ret,
>>>> + ktime_to_us(ktime_sub(ktime_get(), start)),
>>>> + hba->curr_dev_pwr_mode, hba->uic_link_state);
>>>> +
>>>> + return ret;
>>>> +}
>>>> +
>>>> +static int ufshcd_wl_runtime_resume(struct device *dev)
>>>> +{
>>>> + struct scsi_device *sdev = to_scsi_device(dev);
>>>> + struct ufs_hba *hba;
>>>> + int ret = 0;
>>>> + ktime_t start = ktime_get();
>>>> +
>>>> + hba = shost_priv(sdev->host);
>>>> +
>>>> + ret = __ufshcd_wl_resume(hba, UFS_RUNTIME_PM);
>>>> + if (ret)
>>>> + dev_err(&sdev->sdev_gendev, "%s failed: %d\n", __func__, ret);
>>>> +
>>>> + trace_ufshcd_wl_runtime_resume(dev_name(dev), ret,
>>>> + ktime_to_us(ktime_sub(ktime_get(), start)),
>>>> + hba->curr_dev_pwr_mode, hba->uic_link_state);
>>>> +
>>>> + return ret;
>>>> +}
>>>> +
>>>> +#ifdef CONFIG_PM_SLEEP
>>>> +static int ufshcd_wl_suspend(struct device *dev)
>>>> +{
>>>> + struct scsi_device *sdev = to_scsi_device(dev);
>>>> + struct ufs_hba *hba;
>>>> + int ret;
>>>> + ktime_t start = ktime_get();
>>>> +
>>>> + hba = shost_priv(sdev->host);
>>>> + ret = __ufshcd_wl_suspend(hba, UFS_SYSTEM_PM);
>>>> + if (ret)
>>>> + dev_err(&sdev->sdev_gendev, "%s failed: %d\n", __func__, ret);
>>>> +
>>>> + trace_ufshcd_wl_suspend(dev_name(dev), ret,
>>>> + ktime_to_us(ktime_sub(ktime_get(), start)),
>>>> + hba->curr_dev_pwr_mode, hba->uic_link_state);
>>>> +
>>>> + return ret;
>>>> +}
>>>> +
>>>> +static int ufshcd_wl_resume(struct device *dev)
>>>> +{
>>>> + struct scsi_device *sdev = to_scsi_device(dev);
>>>> + struct ufs_hba *hba;
>>>> + int ret = 0;
>>>> + ktime_t start = ktime_get();
>>>> +
>>>> + if (pm_runtime_suspended(dev))
>>>> + return 0;
>>>> + hba = shost_priv(sdev->host);
>>>> +
>>>> + ret = __ufshcd_wl_resume(hba, UFS_SYSTEM_PM);
>>>> + if (ret)
>>>> + dev_err(&sdev->sdev_gendev, "%s failed: %d\n", __func__, ret);
>>>> +
>>>> + trace_ufshcd_wl_resume(dev_name(dev), ret,
>>>> + ktime_to_us(ktime_sub(ktime_get(), start)),
>>>> + hba->curr_dev_pwr_mode, hba->uic_link_state);
>>>> +
>>>> + return ret;
>>>> +}
>>>> +#endif
>>>> +
>>>> +static void ufshcd_wl_shutdown(struct device *dev)
>>>> +{
>>>> + struct scsi_device *sdev = to_scsi_device(dev);
>>>> + struct ufs_hba *hba;
>>>> +
>>>> + hba = shost_priv(sdev->host);
>>>> + /* Turn on everything while shutting down */
>>>> + scsi_autopm_get_device(sdev);
>>>> + scsi_device_quiesce(sdev);
>>>> + shost_for_each_device(sdev, hba->host) {
>>>> + if (sdev == hba->sdev_ufs_device)
>>>> + continue;
>>>> + scsi_device_quiesce(sdev);
>>>> + }
>>>> + __ufshcd_wl_suspend(hba, UFS_SHUTDOWN_PM);
>>>> +}
>>>> +
>>>> +/**
>>>> + * ufshcd_suspend - helper function for suspend operations
>>>> + * @hba: per adapter instance
>>>> + *
>>>> + * This function will put disable irqs, turn off clocks
>>>> + * and set vreg and hba-vreg in lpm mode.
>>>> + * Also check the description of __ufshcd_wl_suspend().
>>>> + */
>>>> +static void ufshcd_suspend(struct ufs_hba *hba)
>>>> +{
>>>> + hba->pm_op_in_progress = 1;
>>>> +
>>>> + /*
>>>> + * Disable the host irq as host controller as there won't be any
>>>> + * host controller transaction expected till resume.
>>>> + */
>>>> ufshcd_disable_irq(hba);
>>>> ufshcd_setup_clocks(hba, false);
>>>> if (ufshcd_is_clkgating_allowed(hba)) {
>>>> @@ -8948,6 +9052,43 @@ static int ufshcd_resume(struct ufs_hba *hba, enum ufs_pm_op pm_op)
>>>> trace_ufshcd_clk_gating(dev_name(hba->dev),
>>>> hba->clk_gating.state);
>>>> }
>>>> +
>>>> + ufshcd_vreg_set_lpm(hba);
>>>> + /* Put the host controller in low power mode if possible */
>>>> + ufshcd_hba_vreg_set_lpm(hba);
>>>> + hba->pm_op_in_progress = 0;
>>>> +}
>>>> +
>>>> +/**
>>>> + * ufshcd_resume - helper function for resume operations
>>>> + * @hba: per adapter instance
>>>> + *
>>>> + * This function basically turns on the regulators, clocks and
>>>> + * irqs of the hba.
>>>> + * Also check the description of __ufshcd_wl_resume().
>>>> + *
>>>> + * Returns 0 for success and non-zero for failure
>>>> + */
>>>> +static int ufshcd_resume(struct ufs_hba *hba)
>>>> +{
>>>> + int ret;
>>>> +
>>>> + hba->pm_op_in_progress = 1;
>>>> +
>>>> + ufshcd_hba_vreg_set_hpm(hba);
>>>> + ret = ufshcd_vreg_set_hpm(hba);
>>>> + if (ret)
>>>> + goto out;
>>>> +
>>>> + /* Make sure clocks are enabled before accessing controller */
>>>> + ret = ufshcd_setup_clocks(hba, true);
>>>> + if (ret)
>>>> + goto disable_vreg;
>>>> +
>>>> + /* enable the host irq as host controller would be active soon */
>>>> + ufshcd_enable_irq(hba);
>>>> + goto out;
>>>> +
>>>> disable_vreg:
>>>> ufshcd_vreg_set_lpm(hba);
>>>> out:
>>>> @@ -8962,6 +9103,7 @@ static int ufshcd_resume(struct ufs_hba *hba, enum ufs_pm_op pm_op)
>>>> * @hba: per adapter instance
>>>> *
>>>> * Check the description of ufshcd_suspend() function for more details.
>>>> + * Also check the description of __ufshcd_wl_suspend().
>>>> *
>>>> * Returns 0 for success and non-zero for failure
>>>> */
>>>> @@ -8987,21 +9129,7 @@ int ufshcd_system_suspend(struct ufs_hba *hba)
>>>> !hba->dev_info.b_rpm_dev_flush_capable)
>>>> goto out;
>>>> - if (pm_runtime_suspended(hba->dev)) {
>>>> - /*
>>>> - * UFS device and/or UFS link low power states during runtime
>>>> - * suspend seems to be different than what is expected during
>>>> - * system suspend. Hence runtime resume the devic & link and
>>>> - * let the system suspend low power states to take effect.
>>>> - * TODO: If resume takes longer time, we might have optimize
>>>> - * it in future by not resuming everything if possible.
>>>> - */
>>>> - ret = ufshcd_runtime_resume(hba);
>>>> - if (ret)
>>>> - goto out;
>>>> - }
>>>> -
>>>> - ret = ufshcd_suspend(hba, UFS_SYSTEM_PM);
>>>> + ufshcd_suspend(hba);
>>>> out:
>>>> trace_ufshcd_system_suspend(dev_name(hba->dev), ret,
>>>> ktime_to_us(ktime_sub(ktime_get(), start)),
>>>> @@ -9023,7 +9151,6 @@ EXPORT_SYMBOL(ufshcd_system_suspend);
>>>> int ufshcd_system_resume(struct ufs_hba *hba)
>>>> {
>>>> - int ret = 0;
>>>> ktime_t start = ktime_get();
>>>> if (!hba)
>>>> @@ -9034,22 +9161,18 @@ int ufshcd_system_resume(struct ufs_hba *hba)
>>>> down(&hba->host_sem);
>>>> }
>>>> - if (!hba->is_powered || pm_runtime_suspended(hba->dev))
>>>> - /*
>>>> - * Let the runtime resume take care of resuming
>>>> - * if runtime suspended.
>>>> - */
>>>> + if (!hba->is_powered)
>>>> goto out;
>>>> else
>>>> - ret = ufshcd_resume(hba, UFS_SYSTEM_PM);
>>>> + ufshcd_resume(hba);
>>>> out:
>>>> - trace_ufshcd_system_resume(dev_name(hba->dev), ret,
>>>> + trace_ufshcd_system_resume(dev_name(hba->dev), 0,
>>>> ktime_to_us(ktime_sub(ktime_get(), start)),
>>>> hba->curr_dev_pwr_mode, hba->uic_link_state);
>>>> - if (!ret)
>>>> - hba->is_sys_suspended = false;
>>>> +
>>>> + hba->is_sys_suspended = false;
>>>> up(&hba->host_sem);
>>>> - return ret;
>>>> + return 0;
>>>> }
>>>> EXPORT_SYMBOL(ufshcd_system_resume);
>>>> @@ -9058,12 +9181,12 @@ EXPORT_SYMBOL(ufshcd_system_resume);
>>>> * @hba: per adapter instance
>>>> *
>>>> * Check the description of ufshcd_suspend() function for more details.
>>>> + * Also check the description of __ufshcd_wl_suspend().
>>>> *
>>>> * Returns 0 for success and non-zero for failure
>>>> */
>>>> int ufshcd_runtime_suspend(struct ufs_hba *hba)
>>>> {
>>>> - int ret = 0;
>>>> ktime_t start = ktime_get();
>>>> if (!hba)
>>>> @@ -9072,12 +9195,12 @@ int ufshcd_runtime_suspend(struct ufs_hba *hba)
>>>> if (!hba->is_powered)
>>>> goto out;
>>>> else
>>>> - ret = ufshcd_suspend(hba, UFS_RUNTIME_PM);
>>>> + ufshcd_suspend(hba);
>>>> out:
>>>> - trace_ufshcd_runtime_suspend(dev_name(hba->dev), ret,
>>>> + trace_ufshcd_runtime_suspend(dev_name(hba->dev), 0,
>>>> ktime_to_us(ktime_sub(ktime_get(), start)),
>>>> hba->curr_dev_pwr_mode, hba->uic_link_state);
>>>> - return ret;
>>>> + return 0;
>>>> }
>>>> EXPORT_SYMBOL(ufshcd_runtime_suspend);
>>>> @@ -9085,26 +9208,14 @@ EXPORT_SYMBOL(ufshcd_runtime_suspend);
>>>> * ufshcd_runtime_resume - runtime resume routine
>>>> * @hba: per adapter instance
>>>> *
>>>> - * This function basically brings the UFS device, UniPro link and controller
>>>> + * This function basically brings controller
>>>> * to active state. Following operations are done in this function:
>>>> *
>>>> * 1. Turn on all the controller related clocks
>>>> - * 2. Bring the UniPro link out of Hibernate state
>>>> - * 3. If UFS device is in sleep state, turn ON VCC rail and bring the UFS device
>>>> - * to active state.
>>>> - * 4. If auto-bkops is enabled on the device, disable it.
>>>> - *
>>>> - * So following would be the possible power state after this function return
>>>> - * successfully:
>>>> - * S1: UFS device in Active state with VCC rail ON
>>>> - * UniPro link in Active state
>>>> - * All the UFS/UniPro controller clocks are ON
>>>> - *
>>>> - * Returns 0 for success and non-zero for failure
>>>> + * 2. Turn ON VCC rail
>>>> */
>>>> int ufshcd_runtime_resume(struct ufs_hba *hba)
>>>> {
>>>> - int ret = 0;
>>>> ktime_t start = ktime_get();
>>>> if (!hba)
>>>> @@ -9113,12 +9224,12 @@ int ufshcd_runtime_resume(struct ufs_hba *hba)
>>>> if (!hba->is_powered)
>>>> goto out;
>>>> else
>>>> - ret = ufshcd_resume(hba, UFS_RUNTIME_PM);
>>>> + ufshcd_resume(hba);
>>>> out:
>>>> - trace_ufshcd_runtime_resume(dev_name(hba->dev), ret,
>>>> + trace_ufshcd_runtime_resume(dev_name(hba->dev), 0,
>>>> ktime_to_us(ktime_sub(ktime_get(), start)),
>>>> hba->curr_dev_pwr_mode, hba->uic_link_state);
>>>> - return ret;
>>>> + return 0;
>>>> }
>>>> EXPORT_SYMBOL(ufshcd_runtime_resume);
>>>> @@ -9132,14 +9243,13 @@ EXPORT_SYMBOL(ufshcd_runtime_idle);
>>>> * ufshcd_shutdown - shutdown routine
>>>> * @hba: per adapter instance
>>>> *
>>>> - * This function would power off both UFS device and UFS link.
>>>> + * This function would turn off both UFS device and UFS hba
>>>> + * regulators. It would also disable clocks.
>>>> *
>>>> * Returns 0 always to allow force shutdown even in case of errors.
>>>> */
>>>> int ufshcd_shutdown(struct ufs_hba *hba)
>>>> {
>>>> - int ret = 0;
>>>> -
>>>> down(&hba->host_sem);
>>>> hba->shutting_down = true;
>>>> up(&hba->host_sem);
>>>> @@ -9152,10 +9262,8 @@ int ufshcd_shutdown(struct ufs_hba *hba)
>>>> pm_runtime_get_sync(hba->dev);
>>>> - ret = ufshcd_suspend(hba, UFS_SHUTDOWN_PM);
>>>> + ufshcd_suspend(hba);
>>>> out:
>>>> - if (ret)
>>>> - dev_err(hba->dev, "%s failed, err %d\n", __func__, ret);
>>>> hba->is_powered = false;
>>>> /* allow force shutdown even in case of errors */
>>>> return 0;
>>>> @@ -9477,15 +9585,163 @@ int ufshcd_init(struct ufs_hba *hba, void __iomem *mmio_base, unsigned int irq)
>>>> }
>>>> EXPORT_SYMBOL_GPL(ufshcd_init);
>>>> +void ufshcd_resume_complete(struct device *dev)
>>>> +{
>>>> + struct ufs_hba *hba = dev_get_drvdata(dev);
>>>> +
>>>> + pm_runtime_put_noidle(&hba->sdev_ufs_device->sdev_gendev);
>>>> +}
>>>> +EXPORT_SYMBOL_GPL(ufshcd_resume_complete);
>>>> +
>>>> +int ufshcd_suspend_prepare(struct device *dev)
>>>> +{
>>>> + struct ufs_hba *hba = dev_get_drvdata(dev);
>>>> +
>>>> + /*
>>>> + * SCSI assumes that runtime-pm and system-pm for scsi drivers
>>>> + * are same. And it doesn't wake up the device for system-suspend
>>>> + * if it's runtime suspended. But ufs doesn't follow that.
>>>> + * The rpm-lvl and spm-lvl can be different in ufs.
>>>> + * Force it to honor system-suspend.
>>>> + */
>>>> + scsi_autopm_get_device(hba->sdev_ufs_device);
>>>> + /* Refer ufshcd_resume_complete() */
>>>> + pm_runtime_get_noresume(&hba->sdev_ufs_device->sdev_gendev);
>>>> + scsi_autopm_put_device(hba->sdev_ufs_device);
>>>> + return 0;
>>>> +}
>>>> +EXPORT_SYMBOL_GPL(ufshcd_suspend_prepare);
>>>> +
>>>> +#ifdef CONFIG_PM_SLEEP
>>>> +static int ufshcd_wl_poweroff(struct device *dev)
>>>> +{
>>>> + ufshcd_wl_shutdown(dev);
>>>> + return 0;
>>>> +}
>>>> +#endif
>>>> +
>>>> +static int ufshcd_wl_probe(struct device *dev)
>>>> +{
>>>> + return is_device_wlun(to_scsi_device(dev)) ? 0 : -ENODEV;
>>>
>>> The "ufs_device_wlun" driver owns PM for sdev_ufs_device because it
>>> provides the dev_pm_ops. This is where the pm_runtime_allow() belongs
>>> and it is symmetrical with the pm_runtime_forbid() in ufshcd_wl_remove().
>>>
>>> if (!is_device_wlun(to_scsi_device(dev)))
>>> return -ENODEV;
>>> blk_pm_runtime_init(dev);
>>> pm_runtime_set_autosuspend_delay(dev, 0);
>>> pm_runtime_allow(dev);
>>>
>>> Note sdev_ufs_device will not runtime suspend here unless the
>>> device links have been added and all consumers have suspended.
>>> That is handled separately by the pm_runtime_get_noresume() in
>>> ufshcd_slave_configure() and the pm_runtime_put() when luns_avail == 1
>>> in ufshcd_setup_links().
>>>
>>>> +}
>>>> +
>>>> +static int ufshcd_wl_remove(struct device *dev)
>>>> +{
>>>> + pm_runtime_forbid(dev);
>>>> + return 0;
>>>> +}
>>>> +
>>>> +static const struct dev_pm_ops ufshcd_wl_pm_ops = {
>>>> +#ifdef CONFIG_PM_SLEEP
>>>> + .suspend = ufshcd_wl_suspend,
>>>> + .resume = ufshcd_wl_resume,
>>>> + .freeze = ufshcd_wl_suspend,
>>>> + .thaw = ufshcd_wl_resume,
>>>> + .poweroff = ufshcd_wl_poweroff,
>>>> + .restore = ufshcd_wl_resume,
>>>> +#endif
>>>> + SET_RUNTIME_PM_OPS(ufshcd_wl_runtime_suspend, ufshcd_wl_runtime_resume, NULL)
>>>> +};
>>>> +
>>>> +/**
>>>> + * ufs_dev_wlun_template - describes ufs device wlun
>>>> + * ufs-device wlun - used to send pm commands
>>>> + * All luns are consumers of ufs-device wlun.
>>>> + *
>>>> + * Currently, no sd driver is present for wluns.
>>>> + * Hence the no specific pm operations are performed.
>>>> + * With ufs design, SSU should be sent to ufs-device wlun.
>>>> + * Hence register a scsi driver for ufs wluns only.
>>>> + */
>>>> +static struct scsi_driver ufs_dev_wlun_template = {
>>>> + .gendrv = {
>>>> + .name = "ufs_device_wlun",
>>>> + .owner = THIS_MODULE,
>>>> + .probe = ufshcd_wl_probe,
>>>> + .remove = ufshcd_wl_remove,
>>>> + .pm = &ufshcd_wl_pm_ops,
>>>> + .shutdown = ufshcd_wl_shutdown,
>>>> + },
>>>> +};
>>>> +
>>>> +static int ufshcd_rpmb_probe(struct device *dev)
>>>> +{
>>>> + return is_rpmb_wlun(to_scsi_device(dev)) ? 0 : -ENODEV;
>>>> +}
>>>> +
>>>> +static inline int ufshcd_clear_rpmb_uac(struct ufs_hba *hba)
>>>> +{
>>>> + int ret = 0;
>>>> +
>>>> + if (!hba->wlun_rpmb_clr_ua)
>>>> + return 0;
>>>> + ret = ufshcd_clear_ua_wlun(hba, UFS_UPIU_RPMB_WLUN);
>>>> + if (!ret)
>>>> + hba->wlun_rpmb_clr_ua = 0;
>>>> + return ret;
>>>> +}
>>>> +
>>>> +static int ufshcd_rpmb_runtime_resume(struct device *dev)
>>>> +{
>>>> + struct ufs_hba *hba = wlun_dev_to_hba(dev);
>>>> +
>>>> + if (hba->sdev_rpmb)
>>>> + return ufshcd_clear_rpmb_uac(hba);
>>>> + return 0;
>>>> +}
>>>> +
>>>> +static int ufshcd_rpmb_resume(struct device *dev)
>>>> +{
>>>> + struct ufs_hba *hba = wlun_dev_to_hba(dev);
>>>> +
>>>> + if (hba->sdev_rpmb && !pm_runtime_suspended(dev))
>>>> + return ufshcd_clear_rpmb_uac(hba);
>>>> + return 0;
>>>> +}
>>>> +
>>>> +static const struct dev_pm_ops ufs_rpmb_pm_ops = {
>>>> + SET_RUNTIME_PM_OPS(NULL, ufshcd_rpmb_runtime_resume, NULL)
>>>> + SET_SYSTEM_SLEEP_PM_OPS(NULL, ufshcd_rpmb_resume)
>>>> +};
>>>> +
>>>> +/**
>>>> + * Describes the ufs rpmb wlun.
>>>> + * Used only to send uac.
>>>> + */
>>>> +static struct scsi_driver ufs_rpmb_wlun_template = {
>>>> + .gendrv = {
>>>> + .name = "ufs_rpmb_wlun",
>>>> + .owner = THIS_MODULE,
>>>> + .probe = ufshcd_rpmb_probe,
>>>> + .pm = &ufs_rpmb_pm_ops,
>>>> + },
>>>> +};
>>>> +
>>>> static int __init ufshcd_core_init(void)
>>>> {
>>>> + int ret;
>>>> +
>>>> ufs_debugfs_init();
>>>> +
>>>> + ret = scsi_register_driver(&ufs_dev_wlun_template.gendrv);
>>>> + if (ret) {
>>>> + ufs_debugfs_exit();
>>>> + return ret;
>>>> + }
>>>> + ret = scsi_register_driver(&ufs_rpmb_wlun_template.gendrv);
>>>> + if (ret) {
>>>> + ufs_debugfs_exit();
>>>> + scsi_unregister_driver(&ufs_dev_wlun_template.gendrv);
>>>> + return ret;
>>>> + }
>>>> return 0;
>>>> }
>>>> static void __exit ufshcd_core_exit(void)
>>>> {
>>>> ufs_debugfs_exit();
>>>> + scsi_unregister_driver(&ufs_dev_wlun_template.gendrv);
>>>> + scsi_unregister_driver(&ufs_rpmb_wlun_template.gendrv);
>>>> }
>>>> module_init(ufshcd_core_init);
>>>> diff --git a/drivers/scsi/ufs/ufshcd.h b/drivers/scsi/ufs/ufshcd.h
>>>> index ee61f82..7841b2a 100644
>>>> --- a/drivers/scsi/ufs/ufshcd.h
>>>> +++ b/drivers/scsi/ufs/ufshcd.h
>>>> @@ -72,6 +72,8 @@ enum ufs_event_type {
>>>> UFS_EVT_LINK_STARTUP_FAIL,
>>>> UFS_EVT_RESUME_ERR,
>>>> UFS_EVT_SUSPEND_ERR,
>>>> + UFS_EVT_WL_SUSP_ERR,
>>>> + UFS_EVT_WL_RES_ERR,
>>>> /* abnormal events */
>>>> UFS_EVT_DEV_RESET,
>>>> @@ -804,6 +806,7 @@ struct ufs_hba {
>>>> struct list_head clk_list_head;
>>>> bool wlun_dev_clr_ua;
>>>> + bool wlun_rpmb_clr_ua;
>>>> /* Number of requests aborts */
>>>> int req_abort_count;
>>>> @@ -841,6 +844,7 @@ struct ufs_hba {
>>>> #ifdef CONFIG_DEBUG_FS
>>>> struct dentry *debugfs_root;
>>>> #endif
>>>> + u32 luns_avail;
>>>> };
>>>> /* Returns true if clocks can be gated. Otherwise false */
>>>> @@ -1100,6 +1104,8 @@ int ufshcd_exec_raw_upiu_cmd(struct ufs_hba *hba,
>>>> enum query_opcode desc_op);
>>>> int ufshcd_wb_ctrl(struct ufs_hba *hba, bool enable);
>>>> +int ufshcd_suspend_prepare(struct device *dev);
>>>> +void ufshcd_resume_complete(struct device *dev);
>>>> /* Wrapper functions for safely calling variant operations */
>>>> static inline const char *ufshcd_get_var_name(struct ufs_hba *hba)
>>>> diff --git a/include/trace/events/ufs.h b/include/trace/events/ufs.h
>>>> index e151477..d9d233b 100644
>>>> --- a/include/trace/events/ufs.h
>>>> +++ b/include/trace/events/ufs.h
>>>> @@ -246,6 +246,26 @@ DEFINE_EVENT(ufshcd_template, ufshcd_init,
>>>> int dev_state, int link_state),
>>>> TP_ARGS(dev_name, err, usecs, dev_state, link_state));
>>>> +DEFINE_EVENT(ufshcd_template, ufshcd_wl_suspend,
>>>> + TP_PROTO(const char *dev_name, int err, s64 usecs,
>>>> + int dev_state, int link_state),
>>>> + TP_ARGS(dev_name, err, usecs, dev_state, link_state));
>>>> +
>>>> +DEFINE_EVENT(ufshcd_template, ufshcd_wl_resume,
>>>> + TP_PROTO(const char *dev_name, int err, s64 usecs,
>>>> + int dev_state, int link_state),
>>>> + TP_ARGS(dev_name, err, usecs, dev_state, link_state));
>>>> +
>>>> +DEFINE_EVENT(ufshcd_template, ufshcd_wl_runtime_suspend,
>>>> + TP_PROTO(const char *dev_name, int err, s64 usecs,
>>>> + int dev_state, int link_state),
>>>> + TP_ARGS(dev_name, err, usecs, dev_state, link_state));
>>>> +
>>>> +DEFINE_EVENT(ufshcd_template, ufshcd_wl_runtime_resume,
>>>> + TP_PROTO(const char *dev_name, int err, s64 usecs,
>>>> + int dev_state, int link_state),
>>>> + TP_ARGS(dev_name, err, usecs, dev_state, link_state));
>>>> +
>>>> TRACE_EVENT(ufshcd_command,
>>>> TP_PROTO(const char *dev_name, enum ufs_trace_str_t str_t,
>>>> unsigned int tag, u32 doorbell, int transfer_len, u32 intr,
>>>>
>>>
>>
>>
>
--
The Qualcomm Innovation Center, Inc. is a member of the Code Aurora Forum,
Linux Foundation Collaborative Project
More information about the Linux-mediatek
mailing list