From 7fb4a288356d6b31e0fa2ee7346bd7e9b9340769 Mon Sep 17 00:00:00 2001 From: Tony Truong Date: Tue, 24 Sep 2019 14:19:35 -0700 Subject: [PATCH 1/2] msm: pcie: provide APIs to prevent and allow PCIe ASPM L1 Provide clients an API to exit and prevent the link from active state power management (ASPM) L1. Also, give clients the control to allow the link to enter ASPM L1. Change-Id: Iedabb4eed51adb981f0eb1f204b468e7b7fbc678 Signed-off-by: Tony Truong --- drivers/pci/controller/pci-msm.c | 123 ++++++++++++++++++++++++++----- include/linux/msm_pcie.h | 29 ++++++++ 2 files changed, 132 insertions(+), 20 deletions(-) diff --git a/drivers/pci/controller/pci-msm.c b/drivers/pci/controller/pci-msm.c index 6160dd1581d8..719dde839eb5 100644 --- a/drivers/pci/controller/pci-msm.c +++ b/drivers/pci/controller/pci-msm.c @@ -718,6 +718,8 @@ struct msm_pcie_dev_t { struct mutex recovery_lock; spinlock_t wakeup_lock; spinlock_t irq_lock; + struct mutex aspm_lock; + int prevent_l1; ulong linkdown_counter; ulong link_turned_on_counter; ulong link_turned_off_counter; @@ -1321,6 +1323,8 @@ static void msm_pcie_show_status(struct msm_pcie_dev_t *dev) dev->wake_counter); PCIE_DBG_FS(dev, "link_check_max_count: %u\n", dev->link_check_max_count); + PCIE_DBG_FS(dev, "prevent_l1: %d\n", + dev->prevent_l1); PCIE_DBG_FS(dev, "target_link_speed: 0x%x\n", dev->target_link_speed); PCIE_DBG_FS(dev, "link_turned_on_counter: %lu\n", @@ -5997,20 +6001,6 @@ static int msm_pcie_link_retrain(struct msm_pcie_dev_t *pcie_dev, u32 cnt_max = 1000; /* 100ms timeout */ u32 link_status_lbms_mask = PCI_EXP_LNKSTA_LBMS << PCI_EXP_LNKCTL; - cnt = 0; - /* confirm link is in L0 */ - while (((readl_relaxed(pcie_dev->parf + PCIE20_PARF_LTSSM) & - MSM_PCIE_LTSSM_MASK)) != MSM_PCIE_LTSSM_L0) { - if (unlikely(cnt++ >= cnt_max)) { - PCIE_ERR(pcie_dev, - "PCIe: RC%d: failed to transition to L0\n", - pcie_dev->rc_idx); - return -EIO; - } - - usleep_range(100, 105); - } - /* link retrain */ msm_pcie_config_clear_set_dword(pci_dev, pci_dev->pcie_cap + PCI_EXP_LNKCTL, @@ -6059,6 +6049,99 @@ static int msm_pcie_set_link_width(struct msm_pcie_dev_t *pcie_dev, return 0; } +void msm_pcie_allow_l1(struct pci_dev *pci_dev) +{ + struct pci_dev *root_pci_dev; + struct msm_pcie_dev_t *pcie_dev; + + root_pci_dev = pci_find_pcie_root_port(pci_dev); + if (!root_pci_dev) + return; + + pcie_dev = PCIE_BUS_PRIV_DATA(root_pci_dev->bus); + + mutex_lock(&pcie_dev->aspm_lock); + if (unlikely(--pcie_dev->prevent_l1 < 0)) + PCIE_ERR(pcie_dev, + "PCIe: RC%d: %02x:%02x.%01x: unbalanced prevent_l1: %d < 0\n", + pcie_dev->rc_idx, pci_dev->bus->number, + PCI_SLOT(pci_dev->devfn), PCI_FUNC(pci_dev->devfn), + pcie_dev->prevent_l1); + + if (pcie_dev->prevent_l1) { + mutex_unlock(&pcie_dev->aspm_lock); + return; + } + + msm_pcie_write_mask(pcie_dev->parf + PCIE20_PARF_PM_CTRL, BIT(5), 0); + /* enable L1 */ + msm_pcie_write_mask(pcie_dev->dm_core + + (root_pci_dev->pcie_cap + PCI_EXP_LNKCTL), + 0, PCI_EXP_LNKCTL_ASPM_L1); + + PCIE_DBG2(pcie_dev, "PCIe: RC%d: %02x:%02x.%01x: exit\n", + pcie_dev->rc_idx, pci_dev->bus->number, + PCI_SLOT(pci_dev->devfn), PCI_FUNC(pci_dev->devfn)); + mutex_unlock(&pcie_dev->aspm_lock); +} +EXPORT_SYMBOL(msm_pcie_allow_l1); + +int msm_pcie_prevent_l1(struct pci_dev *pci_dev) +{ + struct pci_dev *root_pci_dev; + struct msm_pcie_dev_t *pcie_dev; + u32 cnt = 0; + u32 cnt_max = 1000; /* 100ms timeout */ + int ret = 0; + + root_pci_dev = pci_find_pcie_root_port(pci_dev); + if (!root_pci_dev) + return -ENODEV; + + pcie_dev = PCIE_BUS_PRIV_DATA(root_pci_dev->bus); + + /* disable L1 */ + mutex_lock(&pcie_dev->aspm_lock); + if (pcie_dev->prevent_l1++) { + mutex_unlock(&pcie_dev->aspm_lock); + return 0; + } + + msm_pcie_write_mask(pcie_dev->dm_core + + (root_pci_dev->pcie_cap + PCI_EXP_LNKCTL), + PCI_EXP_LNKCTL_ASPM_L1, 0); + msm_pcie_write_mask(pcie_dev->parf + PCIE20_PARF_PM_CTRL, 0, BIT(5)); + + /* confirm link is in L0 */ + while (((readl_relaxed(pcie_dev->parf + PCIE20_PARF_LTSSM) & + MSM_PCIE_LTSSM_MASK)) != MSM_PCIE_LTSSM_L0) { + if (unlikely(cnt++ >= cnt_max)) { + PCIE_ERR(pcie_dev, + "PCIe: RC%d: %02x:%02x.%01x: failed to transition to L0\n", + pcie_dev->rc_idx, pci_dev->bus->number, + PCI_SLOT(pci_dev->devfn), + PCI_FUNC(pci_dev->devfn)); + ret = -EIO; + goto err; + } + + usleep_range(100, 105); + } + + PCIE_DBG2(pcie_dev, "PCIe: RC%d: %02x:%02x.%01x: exit\n", + pcie_dev->rc_idx, pci_dev->bus->number, + PCI_SLOT(pci_dev->devfn), PCI_FUNC(pci_dev->devfn)); + mutex_unlock(&pcie_dev->aspm_lock); + + return 0; +err: + mutex_unlock(&pcie_dev->aspm_lock); + msm_pcie_allow_l1(pci_dev); + + return ret; +} +EXPORT_SYMBOL(msm_pcie_prevent_l1); + int msm_pcie_set_link_bandwidth(struct pci_dev *pci_dev, u16 target_link_speed, u16 target_link_width) { @@ -6108,9 +6191,10 @@ int msm_pcie_set_link_bandwidth(struct pci_dev *pci_dev, u16 target_link_speed, PCI_EXP_LNKSTA_CLS, target_link_speed); - /* disable link L1. Need to be in L0 for gen switch */ - msm_pcie_config_l1(pcie_dev, root_pci_dev, false); - msm_pcie_write_mask(pcie_dev->parf + PCIE20_PARF_PM_CTRL, 0, BIT(5)); + /* need to be in L0 for gen switch */ + ret = msm_pcie_prevent_l1(root_pci_dev); + if (ret) + return ret; if (target_link_speed > current_link_speed) msm_pcie_scale_link_bandwidth(pcie_dev, target_link_speed); @@ -6133,9 +6217,7 @@ int msm_pcie_set_link_bandwidth(struct pci_dev *pci_dev, u16 target_link_speed, if (target_link_speed < current_link_speed) msm_pcie_scale_link_bandwidth(pcie_dev, target_link_speed); out: - /* re-enable link L1 */ - msm_pcie_write_mask(pcie_dev->parf + PCIE20_PARF_PM_CTRL, BIT(5), 0); - msm_pcie_config_l1(pcie_dev, root_pci_dev, true); + msm_pcie_allow_l1(root_pci_dev); return ret; } @@ -6444,6 +6526,7 @@ static int __init pcie_init(void) mutex_init(&msm_pcie_dev[i].setup_lock); mutex_init(&msm_pcie_dev[i].clk_lock); mutex_init(&msm_pcie_dev[i].recovery_lock); + mutex_init(&msm_pcie_dev[i].aspm_lock); spin_lock_init(&msm_pcie_dev[i].wakeup_lock); spin_lock_init(&msm_pcie_dev[i].irq_lock); msm_pcie_dev[i].drv_ready = false; diff --git a/include/linux/msm_pcie.h b/include/linux/msm_pcie.h index b358db554db3..8d0824719047 100644 --- a/include/linux/msm_pcie.h +++ b/include/linux/msm_pcie.h @@ -58,6 +58,26 @@ int msm_msi_init(struct device *dev); #if IS_ENABLED(CONFIG_PCI_MSM) +/** + * msm_pcie_allow_l1 - allow PCIe link to re-enter L1 + * @pci_dev: client's pci device structure + * + * This function gives PCIe clients the control to allow the link to re-enter + * L1. Should only be used after msm_pcie_prevent_l1 has been called. + */ +void msm_pcie_allow_l1(struct pci_dev *pci_dev); + +/** + * msm_pcie_prevent_l1 - keeps PCIe link out of L1 + * @pci_dev: client's pci device structure + * + * This function gives PCIe clients the control to exit and prevent the link + * from entering L1. + * + * Return 0 on success, negative value on error + */ +int msm_pcie_prevent_l1(struct pci_dev *pci_dev); + /** * msm_pcie_set_link_bandwidth - updates the number of lanes and speed of PCIe * link. @@ -195,6 +215,15 @@ static inline int msm_pcie_pm_control(enum msm_pcie_pm_opt pm_opt, u32 busnr, return -ENODEV; } +static inline void msm_pcie_allow_l1(struct pci_dev *pci_dev) +{ +} + +static inline int msm_pcie_prevent_l1(struct pci_dev *pci_dev) +{ + return -ENODEV; +} + static inline int msm_pcie_l1ss_timeout_disable(struct pci_dev *pci_dev) { return -ENODEV; From 40eb3e3bbb4ca4154b5986298ea4a2576bcea9c5 Mon Sep 17 00:00:00 2001 From: Yue Ma Date: Fri, 7 Feb 2020 12:02:12 -0800 Subject: [PATCH 2/2] cnss2: Expose APIs to prevent and allow PCIe L1 state WLAN driver can use the APIs to control if PCIe can enter L1 and L1ss states. Change-Id: Iad784b1ca66199ad61c471635a67542afd34480e Signed-off-by: Yue Ma --- drivers/net/wireless/cnss2/pci.c | 16 ++++++++++++++++ include/net/cnss2.h | 4 +++- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/drivers/net/wireless/cnss2/pci.c b/drivers/net/wireless/cnss2/pci.c index 5054a0dd78f6..8bd5cfab2345 100644 --- a/drivers/net/wireless/cnss2/pci.c +++ b/drivers/net/wireless/cnss2/pci.c @@ -804,6 +804,22 @@ out: return ret; } +int cnss_pci_prevent_l1(struct device *dev) +{ + struct pci_dev *pci_dev = to_pci_dev(dev); + + return msm_pcie_prevent_l1(pci_dev); +} +EXPORT_SYMBOL(cnss_pci_prevent_l1); + +void cnss_pci_allow_l1(struct device *dev) +{ + struct pci_dev *pci_dev = to_pci_dev(dev); + + msm_pcie_allow_l1(pci_dev); +} +EXPORT_SYMBOL(cnss_pci_allow_l1); + int cnss_pci_link_down(struct device *dev) { unsigned long flags; diff --git a/include/net/cnss2.h b/include/net/cnss2.h index ee6cbb7894b7..78143ea5a701 100644 --- a/include/net/cnss2.h +++ b/include/net/cnss2.h @@ -1,5 +1,5 @@ /* SPDX-License-Identifier: GPL-2.0-only */ -/* Copyright (c) 2016-2019, The Linux Foundation. All rights reserved. */ +/* Copyright (c) 2016-2020, The Linux Foundation. All rights reserved. */ #ifndef _NET_CNSS2_H #define _NET_CNSS2_H @@ -158,6 +158,8 @@ enum cnss_recovery_reason { extern int cnss_wlan_register_driver(struct cnss_wlan_driver *driver); extern void cnss_wlan_unregister_driver(struct cnss_wlan_driver *driver); extern void cnss_device_crashed(struct device *dev); +extern int cnss_pci_prevent_l1(struct device *dev); +extern void cnss_pci_allow_l1(struct device *dev); extern int cnss_pci_link_down(struct device *dev); extern int cnss_pci_is_device_down(struct device *dev); extern void cnss_schedule_recovery(struct device *dev,