This commit is contained in:
Mark Brown
2026-07-31 16:35:02 +01:00
9 changed files with 128 additions and 44 deletions
+1
View File
@@ -17569,6 +17569,7 @@ F: arch/arm64/boot/dts/marvell/armada-3720-uDPU.*
MHI BUS
M: Manivannan Sadhasivam <mani@kernel.org>
R: Jeff Hugo <jeff.hugo@oss.qualcomm.com>
L: mhi@lists.linux.dev
L: linux-arm-msm@vger.kernel.org
S: Maintained
+44 -12
View File
@@ -1026,26 +1026,37 @@ static void mhi_ep_abort_transfer(struct mhi_ep_cntrl *mhi_cntrl)
struct mhi_ep_chan *mhi_chan;
int i;
/* Stop all the channels */
/* Disable all the channels to prevent new transfers */
for (i = 0; i < mhi_cntrl->max_chan; i++) {
mhi_chan = &mhi_cntrl->mhi_chan[i];
if (!mhi_chan->ring.started)
continue;
mutex_lock(&mhi_chan->lock);
mhi_chan->state = MHI_CH_STATE_DISABLED;
mutex_unlock(&mhi_chan->lock);
}
/* Drain ring workers and in-flight transfers before notifying disconnect */
flush_workqueue(mhi_cntrl->wq);
if (mhi_cntrl->flush_async)
mhi_cntrl->flush_async(mhi_cntrl);
/* Send channel disconnect status to client drivers */
for (i = 0; i < mhi_cntrl->max_chan; i++) {
mhi_chan = &mhi_cntrl->mhi_chan[i];
if (!mhi_chan->ring.started)
continue;
mutex_lock(&mhi_chan->lock);
/* Send channel disconnect status to client drivers */
if (mhi_chan->xfer_cb) {
result.transaction_status = -ENOTCONN;
result.bytes_xferd = 0;
mhi_chan->xfer_cb(mhi_chan->mhi_dev, &result);
}
mhi_chan->state = MHI_CH_STATE_DISABLED;
mutex_unlock(&mhi_chan->lock);
}
flush_workqueue(mhi_cntrl->wq);
/* Destroy devices associated with all channels */
device_for_each_child(&mhi_cntrl->mhi_dev->dev, NULL, mhi_ep_destroy_device);
@@ -1340,14 +1351,19 @@ static int mhi_ep_create_device(struct mhi_ep_cntrl *mhi_cntrl, u32 ch_id)
ret = dev_set_name(&mhi_dev->dev, "%s_%s",
dev_name(&mhi_cntrl->mhi_dev->dev),
mhi_dev->name);
if (ret) {
put_device(&mhi_dev->dev);
return ret;
}
if (ret)
goto err_put_channels;
ret = device_add(&mhi_dev->dev);
if (ret)
put_device(&mhi_dev->dev);
goto err_put_channels;
return 0;
err_put_channels:
put_device(&mhi_dev->dev); /* DL channel reference */
put_device(&mhi_dev->dev); /* UL channel reference */
put_device(&mhi_dev->dev); /* device_initialize() reference */
return ret;
}
@@ -1614,6 +1630,7 @@ static void mhi_ep_remove(struct device *dev)
{
struct mhi_ep_device *mhi_dev = to_mhi_ep_device(dev);
struct mhi_ep_driver *mhi_drv = to_mhi_ep_driver(dev->driver);
struct mhi_ep_cntrl *mhi_cntrl = mhi_dev->mhi_cntrl;
struct mhi_result result = {};
struct mhi_ep_chan *mhi_chan;
int dir;
@@ -1622,6 +1639,22 @@ static void mhi_ep_remove(struct device *dev)
if (mhi_dev->dev_type == MHI_DEVICE_CONTROLLER)
return;
/* Disable the channels to prevent new transfers */
for (dir = 0; dir < 2; dir++) {
mhi_chan = dir ? mhi_dev->ul_chan : mhi_dev->dl_chan;
if (!mhi_chan)
continue;
mutex_lock(&mhi_chan->lock);
mhi_chan->state = MHI_CH_STATE_DISABLED;
mutex_unlock(&mhi_chan->lock);
}
/* Flush in-flight transfers before notifying disconnect */
if (mhi_cntrl->flush_async)
mhi_cntrl->flush_async(mhi_cntrl);
/* Disconnect the channels associated with the driver */
for (dir = 0; dir < 2; dir++) {
mhi_chan = dir ? mhi_dev->ul_chan : mhi_dev->dl_chan;
@@ -1637,7 +1670,6 @@ static void mhi_ep_remove(struct device *dev)
mhi_chan->xfer_cb(mhi_chan->mhi_dev, &result);
}
mhi_chan->state = MHI_CH_STATE_DISABLED;
mhi_chan->xfer_cb = NULL;
mutex_unlock(&mhi_chan->lock);
}
+3 -1
View File
@@ -1029,7 +1029,7 @@ int mhi_register_controller(struct mhi_controller *mhi_cntrl,
if (mhi_cntrl->edl_trigger) {
ret = sysfs_create_file(&mhi_dev->dev.kobj, &dev_attr_trigger_edl.attr);
if (ret)
goto err_release_dev;
goto err_del_dev;
}
mhi_cntrl->mhi_dev = mhi_dev;
@@ -1038,6 +1038,8 @@ int mhi_register_controller(struct mhi_controller *mhi_cntrl,
return 0;
err_del_dev:
device_del(&mhi_dev->dev);
err_release_dev:
put_device(&mhi_dev->dev);
error_setup_irq:
+6
View File
@@ -170,6 +170,9 @@ EXPORT_SYMBOL_GPL(mhi_get_mhi_state);
void mhi_soc_reset(struct mhi_controller *mhi_cntrl)
{
int __maybe_unused ret;
u32 tmp;
if (mhi_cntrl->reset) {
mhi_cntrl->reset(mhi_cntrl);
return;
@@ -178,6 +181,9 @@ void mhi_soc_reset(struct mhi_controller *mhi_cntrl)
/* Generic MHI SoC reset */
mhi_write_reg(mhi_cntrl, mhi_cntrl->regs, MHI_SOC_RESET_REQ_OFFSET,
MHI_SOC_RESET_REQ);
/* Flush the posted write to the device (ignore return value) */
ret = mhi_read_reg(mhi_cntrl, mhi_cntrl->regs, MHI_SOC_RESET_REQ_OFFSET,
&tmp);
}
EXPORT_SYMBOL_GPL(mhi_soc_reset);
+5
View File
@@ -491,6 +491,8 @@ static const struct mhi_pci_dev_info mhi_quectel_rm5xx_info = {
static const struct mhi_channel_config mhi_foxconn_sdx55_channels[] = {
MHI_CHANNEL_CONFIG_UL(0, "LOOPBACK", 32, 0),
MHI_CHANNEL_CONFIG_DL(1, "LOOPBACK", 32, 0),
MHI_CHANNEL_CONFIG_UL_SBL(2, "SAHARA", 32, 0),
MHI_CHANNEL_CONFIG_DL_SBL(3, "SAHARA", 32, 0),
MHI_CHANNEL_CONFIG_UL(4, "DIAG", 32, 1),
MHI_CHANNEL_CONFIG_DL(5, "DIAG", 32, 1),
MHI_CHANNEL_CONFIG_UL(12, "MBIM", 32, 0),
@@ -506,6 +508,8 @@ static const struct mhi_channel_config mhi_foxconn_sdx55_channels[] = {
static const struct mhi_channel_config mhi_foxconn_sdx61_channels[] = {
MHI_CHANNEL_CONFIG_UL(0, "LOOPBACK", 32, 0),
MHI_CHANNEL_CONFIG_DL(1, "LOOPBACK", 32, 0),
MHI_CHANNEL_CONFIG_UL_SBL(2, "SAHARA", 32, 0),
MHI_CHANNEL_CONFIG_DL_SBL(3, "SAHARA", 32, 0),
MHI_CHANNEL_CONFIG_UL(4, "DIAG", 32, 1),
MHI_CHANNEL_CONFIG_DL(5, "DIAG", 32, 1),
MHI_CHANNEL_CONFIG_UL(12, "MBIM", 32, 0),
@@ -1395,6 +1399,7 @@ static int mhi_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id)
mhi_cntrl->iova_stop = (dma_addr_t)DMA_BIT_MASK(dma_data_width);
mhi_cntrl->fw_image = info->fw;
mhi_cntrl->edl_image = info->edl;
mhi_cntrl->no_m3 = info->no_m3;
mhi_cntrl->read_reg = mhi_pci_read_reg;
mhi_cntrl->write_reg = mhi_pci_write_reg;
+45 -26
View File
@@ -651,21 +651,13 @@ static void mhi_pm_sys_error_transition(struct mhi_controller *mhi_cntrl)
/* Trigger MHI RESET so that the device will not access host memory */
if (reset_device) {
u32 in_reset = -1;
unsigned long timeout = msecs_to_jiffies(mhi_cntrl->timeout_ms);
dev_dbg(dev, "Triggering MHI Reset in device\n");
mhi_set_mhi_state(mhi_cntrl, MHI_STATE_RESET);
/* Wait for the reset bit to be cleared by the device */
ret = wait_event_timeout(mhi_cntrl->state_event,
mhi_read_reg_field(mhi_cntrl,
mhi_cntrl->regs,
MHICTRL,
MHICTRL_RESET_MASK,
&in_reset) ||
!in_reset, timeout);
if (!ret || in_reset) {
ret = mhi_poll_reg_field(mhi_cntrl, mhi_cntrl->regs, MHICTRL,
MHICTRL_RESET_MASK, 0, 25000, mhi_cntrl->timeout_ms);
if (ret) {
dev_err(dev, "Device failed to exit MHI Reset state\n");
write_lock_irq(&mhi_cntrl->pm_lock);
cur_state = mhi_tryset_pm_state(mhi_cntrl,
@@ -922,22 +914,39 @@ int mhi_pm_suspend(struct mhi_controller *mhi_cntrl)
return -EIO;
}
/* Set MHI to M3 and wait for completion */
mhi_set_mhi_state(mhi_cntrl, MHI_STATE_M3);
write_unlock_irq(&mhi_cntrl->pm_lock);
dev_dbg(dev, "Waiting for M3 completion\n");
/*
* For devices without M3 support, just set the host state to M3. This
* host transition is needed to prevent the client drivers from
* accessing the device during suspend.
*/
if (mhi_cntrl->no_m3) {
new_state = mhi_tryset_pm_state(mhi_cntrl, MHI_PM_M3);
write_unlock_irq(&mhi_cntrl->pm_lock);
if (new_state != MHI_PM_M3) {
dev_err(dev,
"Error setting to PM state: %s from: %s\n",
to_mhi_pm_state_str(MHI_PM_M3),
to_mhi_pm_state_str(mhi_cntrl->pm_state));
return -EIO;
}
} else {
/* Set MHI to M3 and wait for completion */
mhi_set_mhi_state(mhi_cntrl, MHI_STATE_M3);
write_unlock_irq(&mhi_cntrl->pm_lock);
dev_dbg(dev, "Waiting for M3 completion\n");
ret = wait_event_timeout(mhi_cntrl->state_event,
mhi_cntrl->dev_state == MHI_STATE_M3 ||
MHI_PM_IN_ERROR_STATE(mhi_cntrl->pm_state),
msecs_to_jiffies(mhi_cntrl->timeout_ms));
ret = wait_event_timeout(mhi_cntrl->state_event,
mhi_cntrl->dev_state == MHI_STATE_M3 ||
MHI_PM_IN_ERROR_STATE(mhi_cntrl->pm_state),
msecs_to_jiffies(mhi_cntrl->timeout_ms));
if (!ret || MHI_PM_IN_ERROR_STATE(mhi_cntrl->pm_state)) {
dev_err(dev,
"Did not enter M3 state, MHI state: %s, PM state: %s\n",
mhi_state_str(mhi_cntrl->dev_state),
to_mhi_pm_state_str(mhi_cntrl->pm_state));
return -EIO;
if (!ret || MHI_PM_IN_ERROR_STATE(mhi_cntrl->pm_state)) {
dev_err(dev,
"Did not enter M3 state, MHI state: %s, PM state: %s\n",
mhi_state_str(mhi_cntrl->dev_state),
to_mhi_pm_state_str(mhi_cntrl->pm_state));
return -EIO;
}
}
/* Notify clients about entering LPM */
@@ -969,7 +978,8 @@ static int __mhi_pm_resume(struct mhi_controller *mhi_cntrl, bool force)
if (MHI_PM_IN_ERROR_STATE(mhi_cntrl->pm_state))
return -EIO;
if (mhi_get_mhi_state(mhi_cntrl) != MHI_STATE_M3) {
if (!mhi_cntrl->no_m3 &&
mhi_get_mhi_state(mhi_cntrl) != MHI_STATE_M3) {
dev_warn(dev, "Resuming from non M3 state (%s)\n",
mhi_state_str(mhi_get_mhi_state(mhi_cntrl)));
if (!force)
@@ -995,6 +1005,15 @@ static int __mhi_pm_resume(struct mhi_controller *mhi_cntrl, bool force)
return -EIO;
}
/*
* For devices without M3 support, just move the host back to M0
* directly.
*/
if (mhi_cntrl->no_m3) {
write_unlock_irq(&mhi_cntrl->pm_lock);
return mhi_pm_m0_transition(mhi_cntrl);
}
/* Set MHI to M0 and wait for completion */
mhi_set_mhi_state(mhi_cntrl, MHI_STATE_M0);
write_unlock_irq(&mhi_cntrl->pm_lock);
@@ -624,6 +624,15 @@ err_unlock:
return ret;
}
static void pci_epf_mhi_edma_flush_async(struct mhi_ep_cntrl *mhi_cntrl)
{
struct pci_epf_mhi *epf_mhi = to_epf_mhi(mhi_cntrl);
dmaengine_synchronize(epf_mhi->dma_chan_rx);
dmaengine_synchronize(epf_mhi->dma_chan_tx);
flush_workqueue(epf_mhi->dma_wq);
}
struct epf_dma_filter {
struct device *dev;
u32 dma_mask;
@@ -792,6 +801,7 @@ static int pci_epf_mhi_link_up(struct pci_epf *epf)
mhi_cntrl->write_sync = pci_epf_mhi_edma_write;
mhi_cntrl->read_async = pci_epf_mhi_edma_read_async;
mhi_cntrl->write_async = pci_epf_mhi_edma_write_async;
mhi_cntrl->flush_async = pci_epf_mhi_edma_flush_async;
}
/* Register the MHI EP controller */
+12 -5
View File
@@ -117,8 +117,8 @@ 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
* @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
*/
struct mhi_link_info {
unsigned int target_link_speed;
@@ -173,6 +173,7 @@ enum mhi_state {
MHI_STATE_M3_FAST = 0x6,
MHI_STATE_BHI = 0x7,
MHI_STATE_SYS_ERR = 0xFF,
/* private: */
MHI_STATE_MAX,
};
@@ -227,12 +228,12 @@ enum mhi_db_brst_mode {
* @type: Channel type
* @ee_mask: Execution Environment mask for this channel
* @pollcfg: Polling configuration for burst mode. 0 is default. milliseconds
for UL channels, multiple of 8 ring elements for DL channels
* for UL channels, multiple of 8 ring elements for DL channels
* @doorbell: Doorbell mode
* @lpm_notify: The channel master requires low power mode notifications
* @offload_channel: The client manages the channel completely
* @doorbell_mode_switch: Channel switches to doorbell mode on M0 transition
* @wake-capable: Channel capable of waking up the system
* @wake_capable: Channel capable of waking up the system
*/
struct mhi_channel_config {
char *name;
@@ -350,7 +351,9 @@ struct mhi_controller_config {
* @dev_state: MHI device state
* @dev_wake: Device wakeup count
* @pending_pkts: Pending packets for the controller
* @M0, M2, M3: Counters to track number of device MHI state changes
* @M0: Counter to track number of device MHI state changes
* @M2: Counter to track number of device MHI state changes
* @M3: Counter to track number of device MHI state changes
* @transition_list: List of MHI state transitions
* @transition_lock: Lock for protecting MHI state transition list
* @wlock: Lock for protecting device wakeup
@@ -375,6 +378,7 @@ struct mhi_controller_config {
* @bounce_buf: Use of bounce buffer
* @fbc_download: MHI host needs to do complete image transfer (optional)
* @wake_set: Device wakeup set flag
* @no_m3: Device doesn't support M3 state
* @irq_flags: irq flags passed to request_irq (optional)
* @mru: the default MRU for the MHI device
*
@@ -460,6 +464,7 @@ struct mhi_controller {
bool bounce_buf;
bool fbc_download;
bool wake_set;
bool no_m3;
unsigned long irq_flags;
u32 mru;
};
@@ -507,6 +512,7 @@ struct mhi_result {
/**
* struct mhi_driver - Structure representing a MHI client driver
* @id_table: table of MHI channel names that a driver supports
* @probe: CB function for client driver probe function
* @remove: CB function for client driver remove function
* @ul_xfer_cb: CB function for UL data transfer
@@ -538,6 +544,7 @@ struct mhi_controller *mhi_alloc_controller(void);
/**
* mhi_free_controller - Free the MHI Controller structure
* @mhi_cntrl: MHI controller to free
* Free the mhi_controller structure which was previously allocated
*/
void mhi_free_controller(struct mhi_controller *mhi_cntrl);
+2
View File
@@ -107,6 +107,7 @@ struct mhi_ep_buf_info {
* @write_sync: CB function for writing to host memory synchronously
* @read_async: CB function for reading from host memory asynchronously
* @write_async: CB function for writing to host memory asynchronously
* @flush_async: CB function for flushing asynchronous read/writes
* @mhi_state: MHI Endpoint state
* @max_chan: Maximum channels supported by the endpoint controller
* @mru: MRU (Maximum Receive Unit) value of the endpoint controller
@@ -164,6 +165,7 @@ struct mhi_ep_cntrl {
int (*write_sync)(struct mhi_ep_cntrl *mhi_cntrl, struct mhi_ep_buf_info *buf_info);
int (*read_async)(struct mhi_ep_cntrl *mhi_cntrl, struct mhi_ep_buf_info *buf_info);
int (*write_async)(struct mhi_ep_cntrl *mhi_cntrl, struct mhi_ep_buf_info *buf_info);
void (*flush_async)(struct mhi_ep_cntrl *mhi_cntrl);
enum mhi_state mhi_state;