Message ID | 20221102090705.23634-3-johan+linaro@kernel.org (mailing list archive) |
---|---|
State | Accepted |
Delegated to: | Lorenzo Pieralisi |
Headers | show |
Series | PCI: qcom: Add basic interconnect support | expand |
On Wed, Nov 02, 2022 at 10:07:05AM +0100, Johan Hovold wrote: > On Qualcomm platforms like SC8280XP and SA8540P, interconnect bandwidth > must be requested before enabling interconnect clocks. > > Add basic support for managing an optional "pcie-mem" interconnect path > by setting a low constraint before enabling clocks and updating it after > the link is up. > > Note that it is not possible for a controller driver to set anything but > a maximum peak bandwidth as expected average bandwidth will vary with > use case and actual use (and power policy?). This very much remains an > unresolved problem with the interconnect framework. > > Also note that no constraint is set for the SC8280XP/SA8540P "cpu-pcie" > path for now as it is not clear what an appropriate constraint would be > (and the system does not crash when left unspecified). > > Fixes: 70574511f3fc ("PCI: qcom: Add support for SC8280XP") > Reviewed-by: Brian Masney <bmasney@redhat.com> > Acked-by: Georgi Djakov <djakov@kernel.org> > Signed-off-by: Johan Hovold <johan+linaro@kernel.org> Reviewed-by: Manivannan Sadhasivam <mani@kernel.org> Thanks, Mani > --- > drivers/pci/controller/dwc/pcie-qcom.c | 76 ++++++++++++++++++++++++++ > 1 file changed, 76 insertions(+) > > diff --git a/drivers/pci/controller/dwc/pcie-qcom.c b/drivers/pci/controller/dwc/pcie-qcom.c > index 7db94a22238d..91b113d0c02a 100644 > --- a/drivers/pci/controller/dwc/pcie-qcom.c > +++ b/drivers/pci/controller/dwc/pcie-qcom.c > @@ -12,6 +12,7 @@ > #include <linux/crc8.h> > #include <linux/delay.h> > #include <linux/gpio/consumer.h> > +#include <linux/interconnect.h> > #include <linux/interrupt.h> > #include <linux/io.h> > #include <linux/iopoll.h> > @@ -224,6 +225,7 @@ struct qcom_pcie { > union qcom_pcie_resources res; > struct phy *phy; > struct gpio_desc *reset; > + struct icc_path *icc_mem; > const struct qcom_pcie_cfg *cfg; > }; > > @@ -1644,6 +1646,74 @@ static const struct dw_pcie_ops dw_pcie_ops = { > .start_link = qcom_pcie_start_link, > }; > > +static int qcom_pcie_icc_init(struct qcom_pcie *pcie) > +{ > + struct dw_pcie *pci = pcie->pci; > + int ret; > + > + pcie->icc_mem = devm_of_icc_get(pci->dev, "pcie-mem"); > + if (IS_ERR(pcie->icc_mem)) > + return PTR_ERR(pcie->icc_mem); > + > + /* > + * Some Qualcomm platforms require interconnect bandwidth constraints > + * to be set before enabling interconnect clocks. > + * > + * Set an initial peak bandwidth corresponding to single-lane Gen 1 > + * for the pcie-mem path. > + */ > + ret = icc_set_bw(pcie->icc_mem, 0, MBps_to_icc(250)); > + if (ret) { > + dev_err(pci->dev, "failed to set interconnect bandwidth: %d\n", > + ret); > + return ret; > + } > + > + return 0; > +} > + > +static void qcom_pcie_icc_update(struct qcom_pcie *pcie) > +{ > + struct dw_pcie *pci = pcie->pci; > + u32 offset, status, bw; > + int speed, width; > + int ret; > + > + if (!pcie->icc_mem) > + return; > + > + offset = dw_pcie_find_capability(pci, PCI_CAP_ID_EXP); > + status = readw(pci->dbi_base + offset + PCI_EXP_LNKSTA); > + > + /* Only update constraints if link is up. */ > + if (!(status & PCI_EXP_LNKSTA_DLLLA)) > + return; > + > + speed = FIELD_GET(PCI_EXP_LNKSTA_CLS, status); > + width = FIELD_GET(PCI_EXP_LNKSTA_NLW, status); > + > + switch (speed) { > + case 1: > + bw = MBps_to_icc(250); > + break; > + case 2: > + bw = MBps_to_icc(500); > + break; > + default: > + WARN_ON_ONCE(1); > + fallthrough; > + case 3: > + bw = MBps_to_icc(985); > + break; > + } > + > + ret = icc_set_bw(pcie->icc_mem, 0, width * bw); > + if (ret) { > + dev_err(pci->dev, "failed to set interconnect bandwidth: %d\n", > + ret); > + } > +} > + > static int qcom_pcie_probe(struct platform_device *pdev) > { > struct device *dev = &pdev->dev; > @@ -1704,6 +1774,10 @@ static int qcom_pcie_probe(struct platform_device *pdev) > goto err_pm_runtime_put; > } > > + ret = qcom_pcie_icc_init(pcie); > + if (ret) > + goto err_pm_runtime_put; > + > ret = pcie->cfg->ops->get_resources(pcie); > if (ret) > goto err_pm_runtime_put; > @@ -1722,6 +1796,8 @@ static int qcom_pcie_probe(struct platform_device *pdev) > goto err_phy_exit; > } > > + qcom_pcie_icc_update(pcie); > + > return 0; > > err_phy_exit: > -- > 2.37.3 >
diff --git a/drivers/pci/controller/dwc/pcie-qcom.c b/drivers/pci/controller/dwc/pcie-qcom.c index 7db94a22238d..91b113d0c02a 100644 --- a/drivers/pci/controller/dwc/pcie-qcom.c +++ b/drivers/pci/controller/dwc/pcie-qcom.c @@ -12,6 +12,7 @@ #include <linux/crc8.h> #include <linux/delay.h> #include <linux/gpio/consumer.h> +#include <linux/interconnect.h> #include <linux/interrupt.h> #include <linux/io.h> #include <linux/iopoll.h> @@ -224,6 +225,7 @@ struct qcom_pcie { union qcom_pcie_resources res; struct phy *phy; struct gpio_desc *reset; + struct icc_path *icc_mem; const struct qcom_pcie_cfg *cfg; }; @@ -1644,6 +1646,74 @@ static const struct dw_pcie_ops dw_pcie_ops = { .start_link = qcom_pcie_start_link, }; +static int qcom_pcie_icc_init(struct qcom_pcie *pcie) +{ + struct dw_pcie *pci = pcie->pci; + int ret; + + pcie->icc_mem = devm_of_icc_get(pci->dev, "pcie-mem"); + if (IS_ERR(pcie->icc_mem)) + return PTR_ERR(pcie->icc_mem); + + /* + * Some Qualcomm platforms require interconnect bandwidth constraints + * to be set before enabling interconnect clocks. + * + * Set an initial peak bandwidth corresponding to single-lane Gen 1 + * for the pcie-mem path. + */ + ret = icc_set_bw(pcie->icc_mem, 0, MBps_to_icc(250)); + if (ret) { + dev_err(pci->dev, "failed to set interconnect bandwidth: %d\n", + ret); + return ret; + } + + return 0; +} + +static void qcom_pcie_icc_update(struct qcom_pcie *pcie) +{ + struct dw_pcie *pci = pcie->pci; + u32 offset, status, bw; + int speed, width; + int ret; + + if (!pcie->icc_mem) + return; + + offset = dw_pcie_find_capability(pci, PCI_CAP_ID_EXP); + status = readw(pci->dbi_base + offset + PCI_EXP_LNKSTA); + + /* Only update constraints if link is up. */ + if (!(status & PCI_EXP_LNKSTA_DLLLA)) + return; + + speed = FIELD_GET(PCI_EXP_LNKSTA_CLS, status); + width = FIELD_GET(PCI_EXP_LNKSTA_NLW, status); + + switch (speed) { + case 1: + bw = MBps_to_icc(250); + break; + case 2: + bw = MBps_to_icc(500); + break; + default: + WARN_ON_ONCE(1); + fallthrough; + case 3: + bw = MBps_to_icc(985); + break; + } + + ret = icc_set_bw(pcie->icc_mem, 0, width * bw); + if (ret) { + dev_err(pci->dev, "failed to set interconnect bandwidth: %d\n", + ret); + } +} + static int qcom_pcie_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; @@ -1704,6 +1774,10 @@ static int qcom_pcie_probe(struct platform_device *pdev) goto err_pm_runtime_put; } + ret = qcom_pcie_icc_init(pcie); + if (ret) + goto err_pm_runtime_put; + ret = pcie->cfg->ops->get_resources(pcie); if (ret) goto err_pm_runtime_put; @@ -1722,6 +1796,8 @@ static int qcom_pcie_probe(struct platform_device *pdev) goto err_phy_exit; } + qcom_pcie_icc_update(pcie); + return 0; err_phy_exit: