* [PATCH v5 1/6] PCI/bwctrl: Set host bridge OPP and optionally disable ASPM around link retraining
2026-08-19 13:25 [PATCH v5 0/6] bus: mhi: host: Add support for mhi bus bw Krishna Chaitanya Chundru
@ 2026-08-19 13:25 ` Krishna Chaitanya Chundru
2026-08-19 13:25 ` [PATCH v5 2/6] PCI: Export pci_set_target_speed() Krishna Chaitanya Chundru
` (4 subsequent siblings)
5 siblings, 0 replies; 7+ messages in thread
From: Krishna Chaitanya Chundru @ 2026-08-19 13:25 UTC (permalink / raw)
To: Bjorn Helgaas, Ilpo Järvinen, Jingoo Han, Lorenzo Pieralisi,
Rob Herring, Jeff Johnson, Bartosz Golaszewski,
Manivannan Sadhasivam, Krzysztof Wilczyński
Cc: linux-pci, linux-kernel, linux-arm-msm, mhi, linux-wireless,
ath11k, qiang.yu, Krishna Chaitanya Chundru
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>
---
drivers/pci/pcie/bwctrl.c | 63 ++++++++++++++++++++++++++++++++++++++++++++++-
include/linux/pci.h | 1 +
2 files changed, 63 insertions(+), 1 deletion(-)
diff --git a/drivers/pci/pcie/bwctrl.c b/drivers/pci/pcie/bwctrl.c
index c4c8d260bf96..6fa1522c47db 100644
--- a/drivers/pci/pcie/bwctrl.c
+++ b/drivers/pci/pcie/bwctrl.c
@@ -28,9 +28,11 @@
#include <linux/mutex.h>
#include <linux/pci.h>
#include <linux/pci-bwctrl.h>
+#include <linux/pm_opp.h>
#include <linux/rwsem.h>
#include <linux/slab.h>
#include <linux/types.h>
+#include <linux/units.h>
#include "../pci.h"
#include "portdrv.h"
@@ -120,6 +122,38 @@ static int pcie_bwctrl_change_speed(struct pci_dev *port, u16 target_speed, bool
return pcie_retrain_link(port, use_lt);
}
+static int pcie_set_opp(struct pci_dev *pdev, struct pci_host_bridge *host,
+ enum pci_bus_speed speed)
+{
+ struct device *dev = host->dev.parent;
+ struct dev_pm_opp_key key = {};
+ int ret, freq_mbps, width;
+ unsigned long freq_kbps;
+ struct dev_pm_opp *opp;
+ u16 lnksta;
+
+ pcie_capability_read_word(pdev, PCI_EXP_LNKSTA, &lnksta);
+ width = FIELD_GET(PCI_EXP_LNKSTA_NLW, lnksta);
+
+ freq_mbps = pcie_dev_speed_mbps(speed);
+ if (freq_mbps < 0)
+ return -EINVAL;
+
+ freq_kbps = freq_mbps * KILO;
+ key.freq = freq_kbps;
+ key.level = pci_bus_speed2lnkctl2(speed);
+ key.bw = 0;
+ opp = dev_pm_opp_find_key_exact(dev, &key, true);
+ if (!IS_ERR(opp)) {
+ ret = dev_pm_opp_set_opp(dev, opp);
+ if (ret)
+ dev_err(dev, "Failed to set OPP for freq (%lu): %d\n",
+ freq_kbps * width, ret);
+ dev_pm_opp_put(opp);
+ }
+ return 0;
+}
+
/**
* pcie_set_target_speed - Set downstream Link Speed for PCIe Port
* @port: PCIe Port
@@ -140,9 +174,12 @@ static int pcie_bwctrl_change_speed(struct pci_dev *port, u16 target_speed, bool
int pcie_set_target_speed(struct pci_dev *port, enum pci_bus_speed speed_req,
bool use_lt)
{
+ struct pci_host_bridge *host = pci_find_host_bridge(port->bus);
+ bool is_rootbus = pci_is_root_bus(port->bus);
struct pci_bus *bus = port->subordinate;
+ struct pci_dev *child = NULL;
+ int aspm_state = 0, ret;
u16 target_speed;
- int ret;
if (WARN_ON_ONCE(!pcie_valid_speed(speed_req)))
return -EINVAL;
@@ -152,6 +189,24 @@ int pcie_set_target_speed(struct pci_dev *port, enum pci_bus_speed speed_req,
target_speed = pcie_bwctrl_select_speed(port, speed_req);
+ /*
+ * The host bridge driver may need to be scaled for targeted speed
+ * otherwise link might not come up at requested speed.
+ */
+ if (is_rootbus && host && bus) {
+ /* Get function 0 of downstream device */
+ list_for_each_entry(child, &bus->devices, bus_list)
+ if (PCI_FUNC(child->devfn) == 0)
+ break;
+
+ if (child && host->disable_aspm_for_retrain) {
+ aspm_state = pcie_aspm_enabled(child);
+ pci_disable_link_state_locked(child, PCIE_LINK_STATE_ALL);
+ }
+ if (speed_req > bus->cur_bus_speed)
+ pcie_set_opp(port, host, speed_req);
+ }
+
scoped_guard(rwsem_read, &pcie_bwctrl_setspeed_rwsem) {
struct pcie_bwctrl_data *data = port->link_bwctrl;
@@ -176,6 +231,12 @@ int pcie_set_target_speed(struct pci_dev *port, enum pci_bus_speed speed_req,
!list_empty(&bus->devices))
ret = -EAGAIN;
+ if (bus && is_rootbus && host) {
+ if (child && host->disable_aspm_for_retrain)
+ pci_enable_link_state_locked(child, aspm_state);
+ pcie_set_opp(port, host, bus->cur_bus_speed);
+ }
+
return ret;
}
diff --git a/include/linux/pci.h b/include/linux/pci.h
index 18e814064b51..d1f2d382189d 100644
--- a/include/linux/pci.h
+++ b/include/linux/pci.h
@@ -662,6 +662,7 @@ struct pci_host_bridge {
unsigned int msi_domain:1; /* Bridge wants MSI domain */
unsigned int broken_l1ss_resume:1; /* Resuming from L1SS during
system suspend is broken */
+ unsigned int disable_aspm_for_retrain:1; /* Disable ASPM before link retain */
/* Resource alignment requirements */
resource_size_t (*align_resource)(struct pci_dev *dev,
--
2.34.1
^ permalink raw reply related [flat|nested] 7+ messages in thread* [PATCH v5 2/6] PCI: Export pci_set_target_speed()
2026-08-19 13:25 [PATCH v5 0/6] bus: mhi: host: Add support for mhi bus bw Krishna Chaitanya Chundru
2026-08-19 13:25 ` [PATCH v5 1/6] PCI/bwctrl: Set host bridge OPP and optionally disable ASPM around link retraining Krishna Chaitanya Chundru
@ 2026-08-19 13:25 ` Krishna Chaitanya Chundru
2026-08-19 13:25 ` [PATCH v5 3/6] PCI: Add pci_lnkctl2_bus_speed() to convert lnkctl2speed to pci_bus_speed Krishna Chaitanya Chundru
` (3 subsequent siblings)
5 siblings, 0 replies; 7+ messages in thread
From: Krishna Chaitanya Chundru @ 2026-08-19 13:25 UTC (permalink / raw)
To: Bjorn Helgaas, Ilpo Järvinen, Jingoo Han, Lorenzo Pieralisi,
Rob Herring, Jeff Johnson, Bartosz Golaszewski,
Manivannan Sadhasivam, Krzysztof Wilczyński
Cc: linux-pci, linux-kernel, linux-arm-msm, mhi, linux-wireless,
ath11k, qiang.yu, Krishna Chaitanya Chundru
Export pci_set_target_speed() so that other kernel drivers can use it
to change the PCIe data rate.
Signed-off-by: Krishna Chaitanya Chundru <krishna.chundru@oss.qualcomm.com>
Acked-by: Ilpo Järvinen <ilpo.jarvinen@linux.intel.com>
---
drivers/pci/pcie/bwctrl.c | 1 +
1 file changed, 1 insertion(+)
diff --git a/drivers/pci/pcie/bwctrl.c b/drivers/pci/pcie/bwctrl.c
index 6fa1522c47db..a337f166465e 100644
--- a/drivers/pci/pcie/bwctrl.c
+++ b/drivers/pci/pcie/bwctrl.c
@@ -239,6 +239,7 @@ int pcie_set_target_speed(struct pci_dev *port, enum pci_bus_speed speed_req,
return ret;
}
+EXPORT_SYMBOL_GPL(pcie_set_target_speed);
static void pcie_bwnotif_enable(struct pcie_device *srv)
{
--
2.34.1
^ permalink raw reply related [flat|nested] 7+ messages in thread* [PATCH v5 3/6] PCI: Add pci_lnkctl2_bus_speed() to convert lnkctl2speed to pci_bus_speed
2026-08-19 13:25 [PATCH v5 0/6] bus: mhi: host: Add support for mhi bus bw Krishna Chaitanya Chundru
2026-08-19 13:25 ` [PATCH v5 1/6] PCI/bwctrl: Set host bridge OPP and optionally disable ASPM around link retraining Krishna Chaitanya Chundru
2026-08-19 13:25 ` [PATCH v5 2/6] PCI: Export pci_set_target_speed() Krishna Chaitanya Chundru
@ 2026-08-19 13:25 ` Krishna Chaitanya Chundru
2026-08-19 13:25 ` [PATCH v5 4/6] bus: mhi: host: Add support for Bandwidth scale Krishna Chaitanya Chundru
` (2 subsequent siblings)
5 siblings, 0 replies; 7+ messages in thread
From: Krishna Chaitanya Chundru @ 2026-08-19 13:25 UTC (permalink / raw)
To: Bjorn Helgaas, Ilpo Järvinen, Jingoo Han, Lorenzo Pieralisi,
Rob Herring, Jeff Johnson, Bartosz Golaszewski,
Manivannan Sadhasivam, Krzysztof Wilczyński
Cc: linux-pci, linux-kernel, linux-arm-msm, mhi, linux-wireless,
ath11k, qiang.yu, Krishna Chaitanya Chundru
Add a exported function to convert lnkctl2speed to enum pci_bus_speed,
so that other kernel drivers can use it.
Signed-off-by: Krishna Chaitanya Chundru <krishna.chundru@oss.qualcomm.com>
---
drivers/pci/pci.c | 15 +++++++++++++++
include/linux/pci.h | 1 +
2 files changed, 16 insertions(+)
diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c
index 77b17b13ee61..cda7d210ddbf 100644
--- a/drivers/pci/pci.c
+++ b/drivers/pci/pci.c
@@ -5939,6 +5939,21 @@ int pcie_link_speed_mbps(struct pci_dev *pdev)
}
EXPORT_SYMBOL(pcie_link_speed_mbps);
+/**
+ * pci_lnkctl2_bus_speed - convert Link Control 2 Target Link Speed to pci_bus_speed
+ * @speed: Target Link Speed (TLS) value from Link Control 2 register (0-4 for Gen1-Gen5)
+ *
+ * Convert the Target Link Speed (TLS) field value from the Link Control 2 register
+ * to the corresponding enum pci_bus_speed.
+ *
+ * Return: pci_bus_speed corresponding to the TLS value
+ */
+enum pci_bus_speed pci_lnkctl2_bus_speed(u32 speed)
+{
+ return pcie_link_speed[speed];
+}
+EXPORT_SYMBOL(pci_lnkctl2_bus_speed);
+
/**
* pcie_bandwidth_available - determine minimum link settings of a PCIe
* device and its bandwidth limitation
diff --git a/include/linux/pci.h b/include/linux/pci.h
index d1f2d382189d..244c145af04f 100644
--- a/include/linux/pci.h
+++ b/include/linux/pci.h
@@ -1727,6 +1727,7 @@ int pci_cfg_space_size(struct pci_dev *dev);
unsigned char pci_bus_max_busnr(struct pci_bus *bus);
resource_size_t pcibios_window_alignment(struct pci_bus *bus,
unsigned long type);
+enum pci_bus_speed pci_lnkctl2_bus_speed(u32 speed);
#define PCI_VGA_STATE_CHANGE_BRIDGE (1 << 0)
#define PCI_VGA_STATE_CHANGE_DECODES (1 << 1)
--
2.34.1
^ permalink raw reply related [flat|nested] 7+ messages in thread* [PATCH v5 4/6] bus: mhi: host: Add support for Bandwidth scale
2026-08-19 13:25 [PATCH v5 0/6] bus: mhi: host: Add support for mhi bus bw Krishna Chaitanya Chundru
` (2 preceding siblings ...)
2026-08-19 13:25 ` [PATCH v5 3/6] PCI: Add pci_lnkctl2_bus_speed() to convert lnkctl2speed to pci_bus_speed Krishna Chaitanya Chundru
@ 2026-08-19 13:25 ` Krishna Chaitanya Chundru
2026-08-19 13:25 ` [PATCH v5 5/6] wifi: ath11k: Add support for MHI bandwidth scaling Krishna Chaitanya Chundru
2026-08-19 13:25 ` [PATCH v5 6/6] PCI: qcom: Enable ASPM disabling during link retraining Krishna Chaitanya Chundru
5 siblings, 0 replies; 7+ messages in thread
From: Krishna Chaitanya Chundru @ 2026-08-19 13:25 UTC (permalink / raw)
To: Bjorn Helgaas, Ilpo Järvinen, Jingoo Han, Lorenzo Pieralisi,
Rob Herring, Jeff Johnson, Bartosz Golaszewski,
Manivannan Sadhasivam, Krzysztof Wilczyński
Cc: linux-pci, linux-kernel, linux-arm-msm, mhi, linux-wireless,
ath11k, qiang.yu, Krishna Chaitanya Chundru
As per MHI spec v1.2, sec 14, MHI supports bandwidth scaling to reduce
power consumption. MHI bandwidth scaling is advertised by devices that
contain the bandwidth scaling capability registers. If enabled, the device
aggregates bandwidth requirements and sends them to the host through
dedicated mhi event ring. After the host performs the bandwidth switch,
it sends an acknowledgment by ringing a doorbell.
if the host supports bandwidth scaling events, then it must set
BW_CFG.ENABLED bit, set BW_CFG.DB_CHAN_ID to the channel ID to the
doorbell that will be used by the host to communicate the bandwidth
scaling status and BW_CFG.ER_INDEX to the index for the event ring
to which the device should send bandwidth scaling request in the
bandwidth scaling capability register.
As part of mmio init check if the bw scale capability is present or not,
if present advertise host supports bw scale by setting all the required
fields.
MHI layer will only forward the bw scaling request to the controller
driver since MHI doesn't have any idea about transport layer used by
the controller, it is responsibility of the controller driver to do actual
bw scaling and then pass status to the MHI. MHI will response back to the
device based up on the status of the bw scale received.
Add a new get_misc_doorbell() to get doorbell for misc capabilities to
use the doorbell with mhi events like MHI BW scale etc.
Use workqueue & mutex for the bw scale events as the pci_set_target_speed()
which will called by the mhi controller driver can sleep.
Co-developed-by: Qiang Yu <qiang.yu@oss.qualcomm.com>
Signed-off-by: Qiang Yu <qiang.yu@oss.qualcomm.com>
Signed-off-by: Krishna Chaitanya Chundru <krishna.chundru@oss.qualcomm.com>
---
drivers/bus/mhi/common.h | 13 +++++
drivers/bus/mhi/host/init.c | 93 +++++++++++++++++++++++++++++++--
drivers/bus/mhi/host/internal.h | 6 ++-
drivers/bus/mhi/host/main.c | 113 +++++++++++++++++++++++++++++++++++++++-
drivers/bus/mhi/host/pm.c | 10 +++-
drivers/bus/mhi/host/trace.h | 7 +++
include/linux/mhi.h | 13 +++++
7 files changed, 247 insertions(+), 8 deletions(-)
diff --git a/drivers/bus/mhi/common.h b/drivers/bus/mhi/common.h
index 4c316f3d5a68..267260901a41 100644
--- a/drivers/bus/mhi/common.h
+++ b/drivers/bus/mhi/common.h
@@ -215,6 +215,19 @@
#define MHI_CAP_ID_MAX_TRB_LEN 0x5
#define MHI_CAP_ID_MAX 0x6
+/* MHI Bandwidth scaling offsets */
+#define MHI_BW_SCALE_CFG_OFFSET 0x4
+#define MHI_BW_SCALE_CAP_ID (3)
+#define MHI_BW_SCALE_DB_CHAN_ID GENMASK(31, 25)
+#define MHI_BW_SCALE_ENABLED BIT(24)
+#define MHI_BW_SCALE_ER_INDEX GENMASK(23, 19)
+
+#define MHI_TRE_GET_EV_BW_REQ_SEQ(tre) FIELD_GET(GENMASK(15, 8), (MHI_TRE_GET_DWORD(tre, 0)))
+
+#define MHI_BW_SCALE_RESULT(status, seq) (FIELD_PREP(GENMASK(11, 8), status) | \
+ FIELD_PREP(GENMASK(7, 0), seq))
+#define MHI_BW_SCALE_NACK 0xF
+
enum mhi_pkt_type {
MHI_PKT_TYPE_INVALID = 0x0,
MHI_PKT_TYPE_NOOP_CMD = 0x1,
diff --git a/drivers/bus/mhi/host/init.c b/drivers/bus/mhi/host/init.c
index 32db4aa4bbe8..d0975b6c2df0 100644
--- a/drivers/bus/mhi/host/init.c
+++ b/drivers/bus/mhi/host/init.c
@@ -497,10 +497,66 @@ static int mhi_find_capability(struct mhi_controller *mhi_cntrl, u32 capability)
return 0;
}
+static int mhi_get_er_index(struct mhi_controller *mhi_cntrl,
+ enum mhi_er_data_type type)
+{
+ struct mhi_event *mhi_event = mhi_cntrl->mhi_event;
+ int i;
+
+ /* Find event ring for requested type */
+ for (i = 0; i < mhi_cntrl->total_ev_rings; i++, mhi_event++) {
+ if (mhi_event->data_type == type)
+ return mhi_event->er_index;
+ }
+
+ return -ENOENT;
+}
+
+static int mhi_init_bw_scale(struct mhi_controller *mhi_cntrl,
+ int bw_scale_db)
+{
+ struct device *dev = &mhi_cntrl->mhi_dev->dev;
+ struct mhi_event *mhi_event;
+ u32 bw_cfg_offset, val;
+ int er_index, i;
+
+ bw_cfg_offset = mhi_find_capability(mhi_cntrl, MHI_BW_SCALE_CAP_ID);
+ if (!bw_cfg_offset)
+ return 0;
+
+ er_index = mhi_get_er_index(mhi_cntrl, MHI_ER_BW_SCALE);
+ if (er_index < 0)
+ return er_index;
+
+ /* Initialize BW scale event ring resources */
+ mhi_event = mhi_cntrl->mhi_event;
+ for (i = 0; i < mhi_cntrl->total_ev_rings; i++, mhi_event++) {
+ if (mhi_event->data_type == MHI_ER_BW_SCALE) {
+ INIT_WORK(&mhi_event->work, mhi_process_ev_work);
+ mutex_init(&mhi_event->mutex);
+ break;
+ }
+ }
+
+ bw_cfg_offset += MHI_BW_SCALE_CFG_OFFSET;
+
+ /* Advertise host support */
+ val = FIELD_PREP(MHI_BW_SCALE_DB_CHAN_ID, bw_scale_db) |
+ FIELD_PREP(MHI_BW_SCALE_ER_INDEX, er_index) |
+ MHI_BW_SCALE_ENABLED;
+
+ mhi_write_reg(mhi_cntrl, mhi_cntrl->regs, bw_cfg_offset, val);
+
+ dev_dbg(dev, "Bandwidth scaling setup complete with event ring: %d\n",
+ er_index);
+
+ return 0;
+}
+
int mhi_init_mmio(struct mhi_controller *mhi_cntrl)
{
- u32 val;
- int i, ret;
+ u32 val, chdb_offset;
+ int i, ret, doorbell = 0;
struct mhi_chan *mhi_chan;
struct mhi_event *mhi_event;
void __iomem *base = mhi_cntrl->regs;
@@ -581,6 +637,8 @@ int mhi_init_mmio(struct mhi_controller *mhi_cntrl)
return -ERANGE;
}
+ chdb_offset = val;
+
/* Setup wake db */
mhi_cntrl->wake_db = base + val + (8 * MHI_DEV_WAKE_DB);
mhi_cntrl->wake_set = false;
@@ -634,6 +692,17 @@ int mhi_init_mmio(struct mhi_controller *mhi_cntrl)
return ret;
}
+ if (mhi_cntrl->get_misc_doorbell)
+ doorbell = mhi_cntrl->get_misc_doorbell(mhi_cntrl, MHI_ER_BW_SCALE);
+
+ if (doorbell > 0) {
+ ret = mhi_init_bw_scale(mhi_cntrl, doorbell);
+ if (!ret)
+ mhi_cntrl->bw_scale_db = base + chdb_offset + (8 * doorbell);
+ else
+ dev_warn(dev, "Failed to setup bandwidth scaling: %d\n", ret);
+ }
+
return 0;
}
@@ -778,6 +847,9 @@ static int parse_ev_cfg(struct mhi_controller *mhi_cntrl,
case MHI_ER_CTRL:
mhi_event->process_event = mhi_process_ctrl_ev_ring;
break;
+ case MHI_ER_BW_SCALE:
+ mhi_event->process_event = mhi_process_bw_scale_ev_ring;
+ break;
default:
dev_err(dev, "Event Ring type not supported\n");
goto error_ev_cfg;
@@ -1001,10 +1073,12 @@ int mhi_register_controller(struct mhi_controller *mhi_cntrl,
mhi_event->mhi_cntrl = mhi_cntrl;
spin_lock_init(&mhi_event->lock);
- if (mhi_event->data_type == MHI_ER_CTRL)
+ if (mhi_event->data_type == MHI_ER_CTRL) {
tasklet_init(&mhi_event->task, mhi_ctrl_ev_task,
(ulong)mhi_event);
- else
+ } else if (mhi_event->data_type == MHI_ER_BW_SCALE) {
+ /* BW scale resources will be initialized in mhi_init_mmio() if capability exists */
+ } else
tasklet_init(&mhi_event->task, mhi_ev_task,
(ulong)mhi_event);
}
@@ -1092,6 +1166,7 @@ void mhi_unregister_controller(struct mhi_controller *mhi_cntrl)
{
struct mhi_device *mhi_dev = mhi_cntrl->mhi_dev;
struct mhi_chan *mhi_chan = mhi_cntrl->mhi_chan;
+ struct mhi_event *mhi_event;
unsigned int i;
mhi_deinit_free_irq(mhi_cntrl);
@@ -1101,6 +1176,16 @@ void mhi_unregister_controller(struct mhi_controller *mhi_cntrl)
sysfs_remove_file(&mhi_dev->dev.kobj, &dev_attr_trigger_edl.attr);
destroy_workqueue(mhi_cntrl->hiprio_wq);
+
+ /* Clean up BW scale resources */
+ mhi_event = mhi_cntrl->mhi_event;
+ for (i = 0; i < mhi_cntrl->total_ev_rings; i++, mhi_event++) {
+ if (mhi_event->data_type == MHI_ER_BW_SCALE) {
+ cancel_work_sync(&mhi_event->work);
+ mutex_destroy(&mhi_event->mutex);
+ }
+ }
+
kfree(mhi_cntrl->mhi_cmd);
kfree(mhi_cntrl->mhi_event);
diff --git a/drivers/bus/mhi/host/internal.h b/drivers/bus/mhi/host/internal.h
index 7b0ee5e3a12d..71d9c1fc5b29 100644
--- a/drivers/bus/mhi/host/internal.h
+++ b/drivers/bus/mhi/host/internal.h
@@ -250,6 +250,8 @@ struct mhi_event {
struct mhi_ring ring;
struct db_cfg db_cfg;
struct tasklet_struct task;
+ struct work_struct work;
+ struct mutex mutex; /* serializes bw_scale event processing */
spinlock_t lock;
int (*process_event)(struct mhi_controller *mhi_cntrl,
struct mhi_event *mhi_event,
@@ -404,6 +406,8 @@ int mhi_process_ctrl_ev_ring(struct mhi_controller *mhi_cntrl,
struct mhi_event *mhi_event, u32 event_quota);
void mhi_uevent_notify(struct mhi_controller *mhi_cntrl, enum mhi_ee_type ee);
+int mhi_process_bw_scale_ev_ring(struct mhi_controller *mhi_cntrl,
+ struct mhi_event *mhi_event, u32 event_quota);
/* ISR handlers */
irqreturn_t mhi_irq_handler(int irq_number, void *dev);
irqreturn_t mhi_intvec_threaded_handler(int irq_number, void *dev);
@@ -419,5 +423,5 @@ void mhi_unmap_single_no_bb(struct mhi_controller *mhi_cntrl,
struct mhi_buf_info *buf_info);
void mhi_unmap_single_use_bb(struct mhi_controller *mhi_cntrl,
struct mhi_buf_info *buf_info);
-
+void mhi_process_ev_work(struct work_struct *work);
#endif /* _MHI_INT_H */
diff --git a/drivers/bus/mhi/host/main.c b/drivers/bus/mhi/host/main.c
index 53c0ffe30070..7b4d2aeb03db 100644
--- a/drivers/bus/mhi/host/main.c
+++ b/drivers/bus/mhi/host/main.c
@@ -472,7 +472,10 @@ irqreturn_t mhi_irq_handler(int irq_number, void *dev)
if (mhi_dev)
mhi_notify(mhi_dev, MHI_CB_PENDING_DATA);
} else {
- tasklet_schedule(&mhi_event->task);
+ if (mhi_event->data_type == MHI_ER_BW_SCALE)
+ queue_work(mhi_cntrl->hiprio_wq, &mhi_event->work);
+ else
+ tasklet_schedule(&mhi_event->task);
}
return IRQ_HANDLED;
@@ -1043,6 +1046,114 @@ int mhi_process_data_event_ring(struct mhi_controller *mhi_cntrl,
return count;
}
+int mhi_process_bw_scale_ev_ring(struct mhi_controller *mhi_cntrl,
+ struct mhi_event *mhi_event, u32 event_quota)
+{
+ struct mhi_event_ctxt *er_ctxt = &mhi_cntrl->mhi_ctxt->er_ctxt[mhi_event->er_index];
+ struct device *dev = &mhi_cntrl->mhi_dev->dev;
+ struct mhi_ring *ev_ring = &mhi_event->ring;
+ dma_addr_t ptr = le64_to_cpu(er_ctxt->rp);
+ u32 response = MHI_BW_SCALE_NACK;
+ struct mhi_ring_element *dev_rp;
+ struct mhi_link_info link_info;
+ bool dev_wake_held = false;
+ int ret = 0;
+
+ if (unlikely(MHI_EVENT_ACCESS_INVALID(mhi_cntrl->pm_state)))
+ return -EIO;
+
+ if (!MHI_IN_MISSION_MODE(mhi_cntrl->ee))
+ return -EINVAL;
+
+ if (!is_valid_ring_ptr(ev_ring, ptr)) {
+ dev_err(dev,
+ "Event ring rp points outside of the event ring\n");
+ return -EIO;
+ }
+
+ dev_rp = mhi_to_virtual(ev_ring, ptr);
+ if (ev_ring->rp == dev_rp)
+ return 0;
+
+ /* If rp points to base, we need to wrap it around */
+ if (dev_rp == ev_ring->base)
+ dev_rp = ev_ring->base + ev_ring->len;
+ dev_rp--;
+
+ /* Fast forward to currently processed element and recycle er */
+ ev_ring->rp = dev_rp;
+ ev_ring->wp = dev_rp - 1;
+ if (ev_ring->wp < ev_ring->base)
+ ev_ring->wp = ev_ring->base + ev_ring->len - ev_ring->el_size;
+ mhi_recycle_ev_ring_element(mhi_cntrl, ev_ring);
+
+ if (WARN_ON(MHI_TRE_GET_EV_TYPE(dev_rp) != MHI_PKT_TYPE_BW_REQ_EVENT)) {
+ dev_err(dev, "Unexpected event type for BW scale event ring\n");
+ return -EINVAL;
+ }
+
+ trace_mhi_bw_scale_event(mhi_cntrl, dev_rp);
+
+ link_info.target_link_speed = MHI_TRE_GET_EV_LINKSPEED(dev_rp);
+ link_info.target_link_width = MHI_TRE_GET_EV_LINKWIDTH(dev_rp);
+ link_info.sequence_num = MHI_TRE_GET_EV_BW_REQ_SEQ(dev_rp);
+
+ dev_dbg(dev, "Received BW_REQ with seq:%d link speed:0x%x width:0x%x\n",
+ link_info.sequence_num,
+ link_info.target_link_speed,
+ link_info.target_link_width);
+
+ read_lock_bh(&mhi_cntrl->pm_lock);
+ if (likely(MHI_DB_ACCESS_VALID(mhi_cntrl)))
+ mhi_ring_er_db(mhi_event);
+ read_unlock_bh(&mhi_cntrl->pm_lock);
+
+ /* Bring host and device out of suspended states */
+ ret = mhi_device_get_sync(mhi_cntrl->mhi_dev);
+ if (ret)
+ goto send_bw_scale_resp;
+
+ mhi_cntrl->runtime_get(mhi_cntrl);
+ dev_wake_held = true;
+
+ ret = mhi_cntrl->bw_scale(mhi_cntrl, &link_info);
+ if (!ret)
+ response = 0;
+
+send_bw_scale_resp:
+ response = MHI_BW_SCALE_RESULT(response, link_info.sequence_num);
+
+ if (ret)
+ dev_err(dev, "BW_REQ seq:%d speed:0x%x width:0x%x NACKed, err:%d\n",
+ link_info.sequence_num, link_info.target_link_speed,
+ link_info.target_link_width, ret);
+
+ write_lock_bh(&mhi_cntrl->pm_lock);
+ mhi_write_reg(mhi_cntrl, mhi_cntrl->bw_scale_db, 0, response);
+ write_unlock_bh(&mhi_cntrl->pm_lock);
+
+ if (dev_wake_held) {
+ mhi_cntrl->runtime_put(mhi_cntrl);
+ mhi_device_put(mhi_cntrl->mhi_dev);
+ }
+
+ return ret;
+}
+
+void mhi_process_ev_work(struct work_struct *work)
+{
+ struct mhi_event *mhi_event = container_of(work, struct mhi_event,
+ work);
+
+ struct mhi_controller *mhi_cntrl = mhi_event->mhi_cntrl;
+
+ if (unlikely(MHI_EVENT_ACCESS_INVALID(mhi_cntrl->pm_state)))
+ return;
+
+ guard(mutex)(&mhi_event->mutex);
+ mhi_event->process_event(mhi_cntrl, mhi_event, U32_MAX);
+}
+
void mhi_ev_task(unsigned long data)
{
struct mhi_event *mhi_event = (struct mhi_event *)data;
diff --git a/drivers/bus/mhi/host/pm.c b/drivers/bus/mhi/host/pm.c
index f799503c8f36..1023fdd699b9 100644
--- a/drivers/bus/mhi/host/pm.c
+++ b/drivers/bus/mhi/host/pm.c
@@ -524,7 +524,10 @@ static void mhi_pm_disable_transition(struct mhi_controller *mhi_cntrl,
if (mhi_event->offload_ev)
continue;
disable_irq(mhi_cntrl->irq[mhi_event->irq]);
- tasklet_kill(&mhi_event->task);
+ if (mhi_event->data_type == MHI_ER_BW_SCALE)
+ cancel_work_sync(&mhi_event->work);
+ else
+ tasklet_kill(&mhi_event->task);
}
/* Release lock and wait for all pending threads to complete */
@@ -689,7 +692,10 @@ static void mhi_pm_sys_error_transition(struct mhi_controller *mhi_cntrl)
for (i = 0; i < mhi_cntrl->total_ev_rings; i++, mhi_event++) {
if (mhi_event->offload_ev)
continue;
- tasklet_kill(&mhi_event->task);
+ if (mhi_event->data_type == MHI_ER_BW_SCALE)
+ cancel_work_sync(&mhi_event->work);
+ else
+ tasklet_kill(&mhi_event->task);
}
/* Release lock and wait for all pending threads to complete */
diff --git a/drivers/bus/mhi/host/trace.h b/drivers/bus/mhi/host/trace.h
index 3e0c41777429..f7cc3470bcdb 100644
--- a/drivers/bus/mhi/host/trace.h
+++ b/drivers/bus/mhi/host/trace.h
@@ -212,6 +212,13 @@ DEFINE_EVENT(mhi_process_event_ring, mhi_ctrl_event,
TP_ARGS(mhi_cntrl, rp)
);
+DEFINE_EVENT(mhi_process_event_ring, mhi_bw_scale_event,
+
+ TP_PROTO(struct mhi_controller *mhi_cntrl, struct mhi_ring_element *rp),
+
+ TP_ARGS(mhi_cntrl, rp)
+);
+
DECLARE_EVENT_CLASS(mhi_update_channel_state,
TP_PROTO(struct mhi_controller *mhi_cntrl, struct mhi_chan *mhi_chan, int state,
diff --git a/include/linux/mhi.h b/include/linux/mhi.h
index 4b86ae6f6a82..22a8d05cf44d 100644
--- a/include/linux/mhi.h
+++ b/include/linux/mhi.h
@@ -119,10 +119,12 @@ struct image_info {
* struct mhi_link_info - BW requirement
* target_link_speed - Link speed as defined by TLS bits in LinkControl reg
* target_link_width - Link width as defined by NLW bits in LinkStatus reg
+ * sequence_num - used by device to track bw requests sent to host
*/
struct mhi_link_info {
unsigned int target_link_speed;
unsigned int target_link_width;
+ int sequence_num;
};
/**
@@ -200,10 +202,12 @@ enum mhi_ch_ee_mask {
* enum mhi_er_data_type - Event ring data types
* @MHI_ER_DATA: Only client data over this ring
* @MHI_ER_CTRL: MHI control data and client data
+ * @MHI_ER_BW_SCALE: MHI controller bandwidth scale functionality
*/
enum mhi_er_data_type {
MHI_ER_DATA,
MHI_ER_CTRL,
+ MHI_ER_BW_SCALE,
};
/**
@@ -314,6 +318,7 @@ struct mhi_controller_config {
* @bhi: Points to base of MHI BHI register space
* @bhie: Points to base of MHI BHIe register space
* @wake_db: MHI WAKE doorbell register address
+ * @bw_scale_db: MHI BW_SCALE doorbell register address
* @iova_start: IOMMU starting address for data (required)
* @iova_stop: IOMMU stop address for data (required)
* @fw_image: Firmware image name for normal booting (optional)
@@ -370,6 +375,8 @@ struct mhi_controller_config {
* @write_reg: Write a MHI register via the physical link (required)
* @reset: Controller specific reset function (optional)
* @edl_trigger: CB function to trigger EDL mode (optional)
+ * @get_misc_doobell: function to get doorbell used for MISC feature like BW scale etc (optional)
+ * @bw_scale: CB function for passing BW scale info (optional)
* @buffer_len: Bounce buffer length
* @index: Index of the MHI controller instance
* @bounce_buf: Use of bounce buffer
@@ -391,6 +398,7 @@ struct mhi_controller {
void __iomem *bhi;
void __iomem *bhie;
void __iomem *wake_db;
+ void __iomem *bw_scale_db;
dma_addr_t iova_start;
dma_addr_t iova_stop;
@@ -455,6 +463,11 @@ struct mhi_controller {
void (*reset)(struct mhi_controller *mhi_cntrl);
int (*edl_trigger)(struct mhi_controller *mhi_cntrl);
+ int (*get_misc_doorbell)(struct mhi_controller *mhi_cntrl,
+ enum mhi_er_data_type type);
+ int (*bw_scale)(struct mhi_controller *mhi_cntrl,
+ struct mhi_link_info *link_info);
+
size_t buffer_len;
int index;
bool bounce_buf;
--
2.34.1
^ permalink raw reply related [flat|nested] 7+ messages in thread* [PATCH v5 5/6] wifi: ath11k: Add support for MHI bandwidth scaling
2026-08-19 13:25 [PATCH v5 0/6] bus: mhi: host: Add support for mhi bus bw Krishna Chaitanya Chundru
` (3 preceding siblings ...)
2026-08-19 13:25 ` [PATCH v5 4/6] bus: mhi: host: Add support for Bandwidth scale Krishna Chaitanya Chundru
@ 2026-08-19 13:25 ` Krishna Chaitanya Chundru
2026-08-19 13:25 ` [PATCH v5 6/6] PCI: qcom: Enable ASPM disabling during link retraining Krishna Chaitanya Chundru
5 siblings, 0 replies; 7+ messages in thread
From: Krishna Chaitanya Chundru @ 2026-08-19 13:25 UTC (permalink / raw)
To: Bjorn Helgaas, Ilpo Järvinen, Jingoo Han, Lorenzo Pieralisi,
Rob Herring, Jeff Johnson, Bartosz Golaszewski,
Manivannan Sadhasivam, Krzysztof Wilczyński
Cc: linux-pci, linux-kernel, linux-arm-msm, mhi, linux-wireless,
ath11k, qiang.yu, Krishna Chaitanya Chundru, Miaoqing Pan
From: Miaoqing Pan <quic_miaoqing@quicinc.com>
Add support for MHI bandwidth scaling, which will reduce power consumption
if WLAN operates with lower bandwidth. This feature is only enabled for
QCA6390.
Bandwidth scaling is initiated by the endpoint firmware based upon the
bandwidth requirements, if there is high bandwidth data endpoint requests
for higher data rates or if there is less bandwidth they request for lower
data rates to reduce power. Endpoint initiates this through MHI protocol.
Tested-on: WCN6855 hw2.1 PCI WLAN.HSP.1.1-04546-QCAHSPSWPL_V1_V2_SILICONZ_IOE-1
Signed-off-by: Miaoqing Pan <quic_miaoqing@quicinc.com>
Signed-off-by: Krishna Chaitanya Chundru <krishna.chundru@oss.qualcomm.com>
---
drivers/net/wireless/ath/ath11k/mhi.c | 38 +++++++++++++++++++++++++++++++++++
1 file changed, 38 insertions(+)
diff --git a/drivers/net/wireless/ath/ath11k/mhi.c b/drivers/net/wireless/ath/ath11k/mhi.c
index a6c9ff112c68..9bff4c4bca3b 100644
--- a/drivers/net/wireless/ath/ath11k/mhi.c
+++ b/drivers/net/wireless/ath/ath11k/mhi.c
@@ -20,6 +20,7 @@
#define MHI_TIMEOUT_DEFAULT_MS 20000
#define RDDM_DUMP_SIZE 0x420000
#define MHI_CB_INVALID 0xff
+#define MHI_BW_SCALE_CHAN_DB 126
static const struct mhi_channel_config ath11k_mhi_channels_qca6390[] = {
{
@@ -71,6 +72,17 @@ static struct mhi_event_config ath11k_mhi_events_qca6390[] = {
.client_managed = false,
.offload_channel = false,
},
+ {
+ .num_elements = 8,
+ .irq_moderation_ms = 0,
+ .irq = 1,
+ .mode = MHI_DB_BRST_DISABLE,
+ .data_type = MHI_ER_BW_SCALE,
+ .priority = 2,
+ .hardware_event = false,
+ .client_managed = false,
+ .offload_channel = false,
+ },
};
static const struct mhi_controller_config ath11k_mhi_config_qca6390 = {
@@ -311,6 +323,30 @@ static void ath11k_mhi_op_write_reg(struct mhi_controller *mhi_cntrl,
writel(val, addr);
}
+static int ath11k_mhi_op_get_misc_doorbell(struct mhi_controller *mhi_cntrl,
+ enum mhi_er_data_type type)
+{
+ if (type == MHI_ER_BW_SCALE)
+ return MHI_BW_SCALE_CHAN_DB;
+
+ return -EOPNOTSUPP;
+}
+
+static int ath11k_mhi_op_bw_scale(struct mhi_controller *mhi_cntrl,
+ struct mhi_link_info *link_info)
+{
+ enum pci_bus_speed speed = pci_lnkctl2_bus_speed(link_info->target_link_speed);
+ struct ath11k_base *ab = dev_get_drvdata(mhi_cntrl->cntrl_dev);
+ struct pci_dev *pci_dev = to_pci_dev(ab->dev);
+ struct pci_dev *pdev;
+
+ pdev = pci_upstream_bridge(pci_dev);
+ if (!pdev)
+ return -ENODEV;
+
+ return pcie_set_target_speed(pdev, speed, true);
+}
+
static int ath11k_mhi_read_addr_from_dt(struct mhi_controller *mhi_ctrl)
{
struct device_node *np;
@@ -387,6 +423,8 @@ int ath11k_mhi_register(struct ath11k_pci *ab_pci)
mhi_ctrl->status_cb = ath11k_mhi_op_status_cb;
mhi_ctrl->read_reg = ath11k_mhi_op_read_reg;
mhi_ctrl->write_reg = ath11k_mhi_op_write_reg;
+ mhi_ctrl->bw_scale = ath11k_mhi_op_bw_scale;
+ mhi_ctrl->get_misc_doorbell = ath11k_mhi_op_get_misc_doorbell;
switch (ab->hw_rev) {
case ATH11K_HW_QCN9074_HW10:
--
2.34.1
^ permalink raw reply related [flat|nested] 7+ messages in thread* [PATCH v5 6/6] PCI: qcom: Enable ASPM disabling during link retraining
2026-08-19 13:25 [PATCH v5 0/6] bus: mhi: host: Add support for mhi bus bw Krishna Chaitanya Chundru
` (4 preceding siblings ...)
2026-08-19 13:25 ` [PATCH v5 5/6] wifi: ath11k: Add support for MHI bandwidth scaling Krishna Chaitanya Chundru
@ 2026-08-19 13:25 ` Krishna Chaitanya Chundru
5 siblings, 0 replies; 7+ messages in thread
From: Krishna Chaitanya Chundru @ 2026-08-19 13:25 UTC (permalink / raw)
To: Bjorn Helgaas, Ilpo Järvinen, Jingoo Han, Lorenzo Pieralisi,
Rob Herring, Jeff Johnson, Bartosz Golaszewski,
Manivannan Sadhasivam, Krzysztof Wilczyński
Cc: linux-pci, linux-kernel, linux-arm-msm, mhi, linux-wireless,
ath11k, qiang.yu, Krishna Chaitanya Chundru,
Krishna Chaitanya Chundru
Set the disable_aspm_for_retrain flag in qcom_pcie_host_init() to ensure
ASPM is disabled during link retraining operations. This prevents potential
issues with link state transitions during bandwidth scaling on Qualcomm
PCIe controllers.
Signed-off-by: Krishna Chaitanya Chundru <krishnac@codeaurora.org>
---
drivers/pci/controller/dwc/pcie-qcom.c | 2 ++
1 file changed, 2 insertions(+)
diff --git a/drivers/pci/controller/dwc/pcie-qcom.c b/drivers/pci/controller/dwc/pcie-qcom.c
index 45f4caeb0814..0237f747b69a 100644
--- a/drivers/pci/controller/dwc/pcie-qcom.c
+++ b/drivers/pci/controller/dwc/pcie-qcom.c
@@ -1395,6 +1395,8 @@ static int qcom_pcie_host_init(struct dw_pcie_rp *pp)
qcom_pcie_perst_deassert(pcie);
+ pp->bridge->disable_aspm_for_retrain = true;
+
if (pcie->cfg->ops->config_sid) {
ret = pcie->cfg->ops->config_sid(pcie);
if (ret)
--
2.34.1
^ permalink raw reply related [flat|nested] 7+ messages in thread