diff mbox series

[v1] PCI: qcom: Add sysfs entry to change link speed dynamically

Message ID 1692239684-12697-1-git-send-email-quic_krichai@quicinc.com
State New
Headers show
Series [v1] PCI: qcom: Add sysfs entry to change link speed dynamically | expand

Commit Message

Krishna Chaitanya Chundru Aug. 17, 2023, 2:34 a.m. UTC
PCIe can operate on lower GEN speed if client decided based upon
the bandwidth & latency requirements. To support dynamic GEN speed
switch adding this sysfs support.

To change the GEN speed the link should be in L0, so first disable
L0s & L1.

L0s needs to be disabled at both RC & EP because L0s entry is
independent. For enabling L0s both ends of the link needs to support
it, so first check if L0s is supported on both ends and then enable
L0s.

This patch is dependent on "PCI: qcom: Add support for OPP"
https://lore.kernel.org/linux-arm-msm/1692192264-18515-1-git-send-email-quic_krichai@quicinc.com/T/#t

Signed-off-by: Krishna chaitanya chundru <quic_krichai@quicinc.com>
---
 drivers/pci/controller/dwc/pcie-qcom.c | 141 +++++++++++++++++++++++++++++++++
 1 file changed, 141 insertions(+)

Comments

kernel test robot Aug. 22, 2023, 3:54 p.m. UTC | #1
Hi Krishna,

kernel test robot noticed the following build errors:

[auto build test ERROR on pci/next]
[also build test ERROR on pci/for-linus linus/master v6.5-rc7 next-20230822]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch#_base_tree_information]

url:    https://github.com/intel-lab-lkp/linux/commits/Krishna-chaitanya-chundru/PCI-qcom-Add-sysfs-entry-to-change-link-speed-dynamically/20230817-103734
base:   https://git.kernel.org/pub/scm/linux/kernel/git/pci/pci.git next
patch link:    https://lore.kernel.org/r/1692239684-12697-1-git-send-email-quic_krichai%40quicinc.com
patch subject: [PATCH v1] PCI: qcom: Add sysfs entry to change link speed dynamically
config: loongarch-randconfig-r005-20230822 (https://download.01.org/0day-ci/archive/20230822/202308222302.9EPeENqh-lkp@intel.com/config)
compiler: loongarch64-linux-gcc (GCC) 13.2.0
reproduce: (https://download.01.org/0day-ci/archive/20230822/202308222302.9EPeENqh-lkp@intel.com/reproduce)

If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202308222302.9EPeENqh-lkp@intel.com/

All errors (new ones prefixed by >>):

   loongarch64-linux-ld: drivers/pci/controller/dwc/pcie-qcom.o: in function `qcom_pcie_speed_change_store':
>> drivers/pci/controller/dwc/pcie-qcom.c:375:(.text+0xc14): undefined reference to `qcom_pcie_opp_update'


vim +375 drivers/pci/controller/dwc/pcie-qcom.c

   303	
   304	static ssize_t qcom_pcie_speed_change_store(struct device *dev,
   305				       struct device_attribute *attr,
   306				       const char *buf,
   307				       size_t count)
   308	{
   309		unsigned int current_speed, target_speed, max_speed;
   310		struct qcom_pcie *pcie = dev_get_drvdata(dev);
   311		struct pci_bus *child, *root_bus = NULL;
   312		struct dw_pcie_rp *pp = &pcie->pci->pp;
   313		struct dw_pcie *pci = pcie->pci;
   314		struct pci_dev *pdev;
   315		u16 offset;
   316		u32 val;
   317		int ret;
   318	
   319		list_for_each_entry(child, &pp->bridge->bus->children, node) {
   320			if (child->parent == pp->bridge->bus) {
   321				root_bus = child;
   322				break;
   323			}
   324		}
   325	
   326		pdev = root_bus->self;
   327	
   328		offset = dw_pcie_find_capability(pci, PCI_CAP_ID_EXP);
   329	
   330		val = readl(pci->dbi_base + offset + PCI_EXP_LNKCAP);
   331		max_speed = FIELD_GET(PCI_EXP_LNKCAP_SLS, val);
   332	
   333		val = readw(pci->dbi_base + offset + PCI_EXP_LNKSTA);
   334		current_speed = FIELD_GET(PCI_EXP_LNKSTA_CLS, val);
   335	
   336		ret = kstrtouint(buf, 10, &target_speed);
   337		if (ret)
   338			return ret;
   339	
   340		if (target_speed > max_speed)
   341			return -EINVAL;
   342	
   343		if (current_speed == target_speed)
   344			return count;
   345	
   346		pci_walk_bus(pp->bridge->bus, qcom_pcie_disable_l0s, pcie);
   347	
   348		/* Disable L1 */
   349		val = dw_pcie_readl_dbi(pci, offset + PCI_EXP_LNKCTL);
   350		val &= ~(PCI_EXP_LNKCTL_ASPM_L1);
   351		dw_pcie_writel_dbi(pci, offset + PCI_EXP_LNKCTL, val);
   352	
   353		/* Set target GEN speed */
   354		val = dw_pcie_readl_dbi(pci, offset + PCI_EXP_LNKCTL2);
   355		val &= ~PCI_EXP_LNKCTL2_TLS;
   356		dw_pcie_writel_dbi(pci, offset + PCI_EXP_LNKCTL2, val | target_speed);
   357	
   358		ret = pcie_retrain_link(pdev, true);
   359		if (ret)
   360			dev_err(dev, "Link retrain failed %d\n", ret);
   361	
   362		/* Enable L1 */
   363		val = dw_pcie_readl_dbi(pci, offset + PCI_EXP_LNKCTL);
   364		val |= (PCI_EXP_LNKCTL_ASPM_L1);
   365		dw_pcie_writel_dbi(pci, offset + PCI_EXP_LNKCTL, val);
   366	
   367		pcie->l0s_supported = true;
   368		pci_walk_bus(pp->bridge->bus, qcom_pcie_check_l0s_support, pcie);
   369	
   370		if (pcie->l0s_supported)
   371			pci_walk_bus(pp->bridge->bus, qcom_pcie_enable_l0s, pcie);
   372	
   373		qcom_pcie_icc_update(pcie);
   374	
 > 375		qcom_pcie_opp_update(pcie);
   376	
   377		return count;
   378	}
   379	static DEVICE_ATTR_WO(qcom_pcie_speed_change);
   380
diff mbox series

Patch

diff --git a/drivers/pci/controller/dwc/pcie-qcom.c b/drivers/pci/controller/dwc/pcie-qcom.c
index 831d158..ad67d17 100644
--- a/drivers/pci/controller/dwc/pcie-qcom.c
+++ b/drivers/pci/controller/dwc/pcie-qcom.c
@@ -241,10 +241,150 @@  struct qcom_pcie {
 	const struct qcom_pcie_cfg *cfg;
 	struct dentry *debugfs;
 	bool suspended;
+	bool l0s_supported;
 };
 
 #define to_qcom_pcie(x)		dev_get_drvdata((x)->dev)
 
+static void qcom_pcie_icc_update(struct qcom_pcie *pcie);
+static void qcom_pcie_opp_update(struct qcom_pcie *pcie);
+
+static int qcom_pcie_disable_l0s(struct pci_dev *pdev, void *userdata)
+{
+	int lnkctl;
+
+	pci_read_config_dword(pdev, pci_pcie_cap(pdev) + PCI_EXP_LNKCTL, &lnkctl);
+	lnkctl &= ~(PCI_EXP_LNKCTL_ASPM_L0S);
+	pci_write_config_word(pdev, pci_pcie_cap(pdev) + PCI_EXP_LNKCTL, lnkctl);
+
+	return 0;
+}
+
+static int qcom_pcie_check_l0s_support(struct pci_dev *pdev, void *userdata)
+{
+	struct pci_dev *parent = pdev->bus->self;
+	struct qcom_pcie *pcie = userdata;
+	struct dw_pcie *pci = pcie->pci;
+	int lnkcap;
+
+	 /* check parent supports L0s */
+	if (parent) {
+		dev_err(pci->dev, "parent\n");
+		pci_read_config_dword(parent, pci_pcie_cap(parent) + PCI_EXP_LNKCAP,
+				  &lnkcap);
+		if (!(lnkcap & PCI_EXP_LNKCAP_ASPM_L0S)) {
+			dev_info(pci->dev, "Parent does not support L0s\n");
+			pcie->l0s_supported = false;
+			return 0;
+		}
+	}
+
+	pci_read_config_dword(pdev, pci_pcie_cap(pdev) + PCI_EXP_LNKCAP,
+			  &lnkcap);
+	dev_err(pci->dev, "child %x\n", lnkcap);
+	if (!(lnkcap & PCI_EXP_LNKCAP_ASPM_L0S)) {
+		dev_info(pci->dev, "Device does not support L0s\n");
+		pcie->l0s_supported = false;
+		return 0;
+	}
+
+	return 0;
+}
+
+static int qcom_pcie_enable_l0s(struct pci_dev *pdev, void *userdata)
+{
+	int lnkctl;
+
+	pci_read_config_dword(pdev, pci_pcie_cap(pdev) + PCI_EXP_LNKCTL, &lnkctl);
+	lnkctl |= (PCI_EXP_LNKCTL_ASPM_L0S);
+	pci_write_config_word(pdev, pci_pcie_cap(pdev) + PCI_EXP_LNKCTL, lnkctl);
+
+	return 0;
+}
+
+static ssize_t qcom_pcie_speed_change_store(struct device *dev,
+			       struct device_attribute *attr,
+			       const char *buf,
+			       size_t count)
+{
+	unsigned int current_speed, target_speed, max_speed;
+	struct qcom_pcie *pcie = dev_get_drvdata(dev);
+	struct pci_bus *child, *root_bus = NULL;
+	struct dw_pcie_rp *pp = &pcie->pci->pp;
+	struct dw_pcie *pci = pcie->pci;
+	struct pci_dev *pdev;
+	u16 offset;
+	u32 val;
+	int ret;
+
+	list_for_each_entry(child, &pp->bridge->bus->children, node) {
+		if (child->parent == pp->bridge->bus) {
+			root_bus = child;
+			break;
+		}
+	}
+
+	pdev = root_bus->self;
+
+	offset = dw_pcie_find_capability(pci, PCI_CAP_ID_EXP);
+
+	val = readl(pci->dbi_base + offset + PCI_EXP_LNKCAP);
+	max_speed = FIELD_GET(PCI_EXP_LNKCAP_SLS, val);
+
+	val = readw(pci->dbi_base + offset + PCI_EXP_LNKSTA);
+	current_speed = FIELD_GET(PCI_EXP_LNKSTA_CLS, val);
+
+	ret = kstrtouint(buf, 10, &target_speed);
+	if (ret)
+		return ret;
+
+	if (target_speed > max_speed)
+		return -EINVAL;
+
+	if (current_speed == target_speed)
+		return count;
+
+	pci_walk_bus(pp->bridge->bus, qcom_pcie_disable_l0s, pcie);
+
+	/* Disable L1 */
+	val = dw_pcie_readl_dbi(pci, offset + PCI_EXP_LNKCTL);
+	val &= ~(PCI_EXP_LNKCTL_ASPM_L1);
+	dw_pcie_writel_dbi(pci, offset + PCI_EXP_LNKCTL, val);
+
+	/* Set target GEN speed */
+	val = dw_pcie_readl_dbi(pci, offset + PCI_EXP_LNKCTL2);
+	val &= ~PCI_EXP_LNKCTL2_TLS;
+	dw_pcie_writel_dbi(pci, offset + PCI_EXP_LNKCTL2, val | target_speed);
+
+	ret = pcie_retrain_link(pdev, true);
+	if (ret)
+		dev_err(dev, "Link retrain failed %d\n", ret);
+
+	/* Enable L1 */
+	val = dw_pcie_readl_dbi(pci, offset + PCI_EXP_LNKCTL);
+	val |= (PCI_EXP_LNKCTL_ASPM_L1);
+	dw_pcie_writel_dbi(pci, offset + PCI_EXP_LNKCTL, val);
+
+	pcie->l0s_supported = true;
+	pci_walk_bus(pp->bridge->bus, qcom_pcie_check_l0s_support, pcie);
+
+	if (pcie->l0s_supported)
+		pci_walk_bus(pp->bridge->bus, qcom_pcie_enable_l0s, pcie);
+
+	qcom_pcie_icc_update(pcie);
+
+	qcom_pcie_opp_update(pcie);
+
+	return count;
+}
+static DEVICE_ATTR_WO(qcom_pcie_speed_change);
+
+static struct attribute *qcom_pcie_attrs[] = {
+	&dev_attr_qcom_pcie_speed_change.attr,
+	NULL,
+};
+ATTRIBUTE_GROUPS(qcom_pcie);
+
 static void qcom_ep_reset_assert(struct qcom_pcie *pcie)
 {
 	gpiod_set_value_cansleep(pcie->reset, 1);
@@ -1716,6 +1856,7 @@  static struct platform_driver qcom_pcie_driver = {
 		.of_match_table = qcom_pcie_match,
 		.pm = &qcom_pcie_pm_ops,
 		.probe_type = PROBE_PREFER_ASYNCHRONOUS,
+		.dev_groups = qcom_pcie_groups,
 	},
 };
 builtin_platform_driver(qcom_pcie_driver);