Skip to content

Commit 31b048b

Browse files
PCI/bwctrl: Set host bridge OPP and optionally disable ASPM around link retraining
PCIe host bridge controllers may need their operating point raised before retraining to a higher link speed so that hardware resources (e.g., RPMh votes on Qualcomm platforms) are available at the requested data rate. After retraining, the operating point must be updated to reflect the actual negotiated speed. Add pcie_set_opp() to look up an OPP on the host bridge parent device using a key of (per-lane frequency in kHz, LNKCTL2 Target Link Speed level). Keying by generation rather than total bandwidth lets OPP tables remain width-independent. In pcie_set_target_speed(), call pcie_set_opp() before retraining only when upscaling (speed_req > cur_bus_speed), since only raising the operating point requires pre-staging hardware. After retraining, call pcie_set_opp() unconditionally with the actual cur_bus_speed to settle the votes. Both calls are skipped for downstream ports of PCIe switches, as those are outside the host controller's scope. Some controllers also require ASPM to be disabled around link retraining. Add a disable_aspm_for_retrain flag to pci_host_bridge; when set, pcie_set_target_speed() saves the child device's ASPM state, disables all ASPM link states before retraining, and restores them afterward. Signed-off-by: Krishna Chaitanya Chundru <krishna.chundru@oss.qualcomm.com>
1 parent b670fd8 commit 31b048b

2 files changed

Lines changed: 62 additions & 1 deletion

File tree

drivers/pci/pcie/bwctrl.c

Lines changed: 61 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
#include <linux/slab.h>
4040
#include <linux/spinlock.h>
4141
#include <linux/types.h>
42+
#include <linux/units.h>
4243

4344
#include "../pci.h"
4445
#include "portdrv.h"
@@ -162,6 +163,38 @@ static int pcie_bwctrl_change_speed(struct pci_dev *port, u16 target_speed, bool
162163
return pcie_retrain_link(port, use_lt);
163164
}
164165

166+
static int pcie_set_opp(struct pci_dev *pdev, struct pci_host_bridge *host,
167+
enum pci_bus_speed speed)
168+
{
169+
struct device *dev = host->dev.parent;
170+
struct dev_pm_opp_key key = {};
171+
int ret, freq_mbps, width;
172+
unsigned long freq_kbps;
173+
struct dev_pm_opp *opp;
174+
u16 lnksta;
175+
176+
pcie_capability_read_word(pdev, PCI_EXP_LNKSTA, &lnksta);
177+
width = FIELD_GET(PCI_EXP_LNKSTA_NLW, lnksta);
178+
179+
freq_mbps = pcie_dev_speed_mbps(speed);
180+
if (freq_mbps < 0)
181+
return -EINVAL;
182+
183+
freq_kbps = freq_mbps * KILO;
184+
key.freq = freq_kbps;
185+
key.level = pci_bus_speed2lnkctl2(speed);
186+
key.bw = 0;
187+
opp = dev_pm_opp_find_key_exact(dev, &key, true);
188+
if (!IS_ERR(opp)) {
189+
ret = dev_pm_opp_set_opp(dev, opp);
190+
if (ret)
191+
dev_err(dev, "Failed to set OPP for freq (%lu): %d\n",
192+
freq_kbps * width, ret);
193+
dev_pm_opp_put(opp);
194+
}
195+
return 0;
196+
}
197+
165198
/**
166199
* pcie_set_target_speed - Set downstream Link Speed for PCIe Port
167200
* @port: PCIe Port
@@ -182,9 +215,12 @@ static int pcie_bwctrl_change_speed(struct pci_dev *port, u16 target_speed, bool
182215
int pcie_set_target_speed(struct pci_dev *port, enum pci_bus_speed speed_req,
183216
bool use_lt)
184217
{
218+
struct pci_host_bridge *host = pci_find_host_bridge(port->bus);
219+
bool is_rootbus = pci_is_root_bus(port->bus);
185220
struct pci_bus *bus = port->subordinate;
221+
struct pci_dev *child = NULL;
222+
int aspm_state = 0, ret;
186223
u16 target_speed;
187-
int ret;
188224

189225
if (WARN_ON_ONCE(!pcie_valid_speed(speed_req)))
190226
return -EINVAL;
@@ -194,6 +230,24 @@ int pcie_set_target_speed(struct pci_dev *port, enum pci_bus_speed speed_req,
194230

195231
target_speed = pcie_bwctrl_select_speed(port, speed_req);
196232

233+
/*
234+
* The host bridge driver may need to be scaled for targeted speed
235+
* otherwise link might not come up at requested speed.
236+
*/
237+
if (is_rootbus && host && bus) {
238+
/* Get function 0 of downstream device */
239+
list_for_each_entry(child, &bus->devices, bus_list)
240+
if (PCI_FUNC(child->devfn) == 0)
241+
break;
242+
243+
if (child && host->disable_aspm_for_retrain) {
244+
aspm_state = pcie_aspm_enabled(child);
245+
// pci_disable_link_state_locked(child, PCIE_LINK_STATE_ALL);
246+
}
247+
if (speed_req > bus->cur_bus_speed)
248+
pcie_set_opp(port, host, speed_req);
249+
}
250+
197251
scoped_guard(rwsem_read, &pcie_bwctrl_setspeed_rwsem) {
198252
struct pcie_bwctrl_data *data = port->link_bwctrl;
199253

@@ -218,6 +272,12 @@ int pcie_set_target_speed(struct pci_dev *port, enum pci_bus_speed speed_req,
218272
!list_empty(&bus->devices))
219273
ret = -EAGAIN;
220274

275+
if (bus && is_rootbus && host) {
276+
// if (child && host->disable_aspm_for_retrain)
277+
// pci_enable_link_state_locked(child, aspm_state);
278+
pcie_set_opp(port, host, bus->cur_bus_speed);
279+
}
280+
221281
return ret;
222282
}
223283
EXPORT_SYMBOL_GPL(pcie_set_target_speed);

include/linux/pci.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -665,6 +665,7 @@ struct pci_host_bridge {
665665
unsigned int msi_domain:1; /* Bridge wants MSI domain */
666666
unsigned int broken_l1ss_resume:1; /* Resuming from L1SS during
667667
system suspend is broken */
668+
unsigned int disable_aspm_for_retrain:1; /* Disable ASPM before link retain */
668669

669670
/* Resource alignment requirements */
670671
resource_size_t (*align_resource)(struct pci_dev *dev,

0 commit comments

Comments
 (0)