summaryrefslogtreecommitdiff
path: root/drivers/bus
diff options
context:
space:
mode:
authorGreg Kroah-Hartman <gregkh@linuxfoundation.org>2020-12-04 18:25:17 +0300
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>2020-12-04 18:25:17 +0300
commit888982a847520a5abfd036a0cc6426c55e71f76c (patch)
tree86675213e170111597b1eaf8f7240e26e6f47cbd /drivers/bus
parent9fb3b4cae4e64ed2520cf52fb6820f01185ba965 (diff)
parent4ea6fa2cb921cb17812501a27c3761037d64a217 (diff)
downloadlinux-888982a847520a5abfd036a0cc6426c55e71f76c.tar.xz
Merge tag 'mhi-for-v5.11' of git://git.kernel.org/pub/scm/linux/kernel/git/mani/mhi into char-misc-next
Manivannan writes: MHI patches for v5.11 Here is the MHI patch set for v5.11. Most of the patches are cleanups and fixes but there are some noticeable changes too: 1. Loic finally removed the auto-start option from the channel parameters of the MHI controller. It is the duty of the client drivers like qrtr to start/stop the channels when required, so we decided to remove this option. As a side effect, we changed the qrtr driver to start the channels during its probe and removed the auto-start option from ath11k controller. **NOTE** Since these changes spawns both MHI and networking trees, the patches are maintained in an immutable branch [1] and pulled into both mhi-next and ath11k-next branches. The networking patches got acks from ath11k and networking maintainers as well. 2. Loic added a generic MHI pci controller driver. This driver will be used by the PCI based Qualcomm modems like SDX55 and exposes channels such as QMI, IP_HW0, IPCR etc... 3. Loic fixed the MHI device hierarchy by maintaining the correct parent child relationships. Earlier all MHI devices lived in the same level under the parent device like PCIe. But now, the MHI devices belonging to channels will become the children of controller MHI device. 4. Finally Loic also improved the MHI device naming by using indexed names such as mhi0, mhi1, etc... This will break the userspace applications depending on the old naming convention but since the only one user so far is Jeff Hugo's AI accelerator apps, we decided to make this change now itself with his agreement. 5. Bhaumik fixed the qrtr driver by stopping the channels during remove. This patch also got ack from networking maintainer and we decided to take it through MHI tree (via immutable branch) since we already had a qrtr change. [1] https://git.kernel.org/pub/scm/linux/kernel/git/mani/mhi.git/log/?h=mhi-ath11k-immutable * tag 'mhi-for-v5.11' of git://git.kernel.org/pub/scm/linux/kernel/git/mani/mhi: (30 commits) mhi: pci_generic: Fix implicit conversion warning bus: mhi: core: Fix error handling in mhi_register_controller() bus: mhi: core: Fix device hierarchy bus: mhi: core: Indexed MHI controller name net: qrtr: Unprepare MHI channels during remove bus: mhi: core: Remove MHI event ring IRQ handlers when powering down bus: mhi: core: Mark and maintain device states early on after power down bus: mhi: core: Separate system error and power down handling bus: mhi: core: Check for IRQ availability during registration bus: mhi: core: Move to an error state on mission mode failure bus: mhi: core: Use appropriate label in firmware load handler API bus: mhi: core: Move to an error state on any firmware load failure bus: mhi: core: Prevent sending multiple RDDM entry callbacks bus: mhi: core: Move to SYS_ERROR regardless of RDDM capability bus: mhi: core: Skip device wake in error or shutdown states bus: mhi: core: Move to using high priority workqueue bus: mhi: core: Use appropriate names for firmware load functions bus: mhi: core: Skip RDDM download for unknown execution environment bus: mhi: core: Rename RDDM download function to use proper words bus: mhi: core: Remove unused mhi_fw_load_worker() declaration ...
Diffstat (limited to 'drivers/bus')
-rw-r--r--drivers/bus/mhi/Kconfig9
-rw-r--r--drivers/bus/mhi/Makefile4
-rw-r--r--drivers/bus/mhi/core/boot.c75
-rw-r--r--drivers/bus/mhi/core/debugfs.c4
-rw-r--r--drivers/bus/mhi/core/init.c73
-rw-r--r--drivers/bus/mhi/core/internal.h7
-rw-r--r--drivers/bus/mhi/core/main.c16
-rw-r--r--drivers/bus/mhi/core/pm.c238
-rw-r--r--drivers/bus/mhi/pci_generic.c345
9 files changed, 634 insertions, 137 deletions
diff --git a/drivers/bus/mhi/Kconfig b/drivers/bus/mhi/Kconfig
index e841c1097fb4..da5cd0c9fc62 100644
--- a/drivers/bus/mhi/Kconfig
+++ b/drivers/bus/mhi/Kconfig
@@ -20,3 +20,12 @@ config MHI_BUS_DEBUG
Enable debugfs support for use with the MHI transport. Allows
reading and/or modifying some values within the MHI controller
for debug and test purposes.
+
+config MHI_BUS_PCI_GENERIC
+ tristate "MHI PCI controller driver"
+ depends on MHI_BUS
+ depends on PCI
+ help
+ This driver provides MHI PCI controller driver for devices such as
+ Qualcomm SDX55 based PCIe modems.
+
diff --git a/drivers/bus/mhi/Makefile b/drivers/bus/mhi/Makefile
index 19e6443b72df..0a2d778d6fb4 100644
--- a/drivers/bus/mhi/Makefile
+++ b/drivers/bus/mhi/Makefile
@@ -1,2 +1,6 @@
# core layer
obj-y += core/
+
+obj-$(CONFIG_MHI_BUS_PCI_GENERIC) += mhi_pci_generic.o
+mhi_pci_generic-y += pci_generic.o
+
diff --git a/drivers/bus/mhi/core/boot.c b/drivers/bus/mhi/core/boot.c
index 24422f5c3d80..c2546bf229fb 100644
--- a/drivers/bus/mhi/core/boot.c
+++ b/drivers/bus/mhi/core/boot.c
@@ -92,6 +92,9 @@ static int __mhi_download_rddm_in_panic(struct mhi_controller *mhi_cntrl)
* image download completion.
*/
ee = mhi_get_exec_env(mhi_cntrl);
+ if (ee == MHI_EE_MAX)
+ goto error_exit_rddm;
+
if (ee != MHI_EE_RDDM) {
dev_dbg(dev, "Trigger device into RDDM mode using SYS ERR\n");
mhi_set_mhi_state(mhi_cntrl, MHI_STATE_SYS_ERR);
@@ -139,15 +142,17 @@ static int __mhi_download_rddm_in_panic(struct mhi_controller *mhi_cntrl)
ee = mhi_get_exec_env(mhi_cntrl);
ret = mhi_read_reg(mhi_cntrl, base, BHIE_RXVECSTATUS_OFFS, &rx_status);
- dev_err(dev, "Did not complete RDDM transfer\n");
- dev_err(dev, "Current EE: %s\n", TO_MHI_EXEC_STR(ee));
dev_err(dev, "RXVEC_STATUS: 0x%x\n", rx_status);
+error_exit_rddm:
+ dev_err(dev, "RDDM transfer failed. Current EE: %s\n",
+ TO_MHI_EXEC_STR(ee));
+
return -EIO;
}
/* Download RDDM image from device */
-int mhi_download_rddm_img(struct mhi_controller *mhi_cntrl, bool in_panic)
+int mhi_download_rddm_image(struct mhi_controller *mhi_cntrl, bool in_panic)
{
void __iomem *base = mhi_cntrl->bhie;
struct device *dev = &mhi_cntrl->mhi_dev->dev;
@@ -169,9 +174,9 @@ int mhi_download_rddm_img(struct mhi_controller *mhi_cntrl, bool in_panic)
return (rx_status == BHIE_RXVECSTATUS_STATUS_XFER_COMPL) ? 0 : -EIO;
}
-EXPORT_SYMBOL_GPL(mhi_download_rddm_img);
+EXPORT_SYMBOL_GPL(mhi_download_rddm_image);
-static int mhi_fw_load_amss(struct mhi_controller *mhi_cntrl,
+static int mhi_fw_load_bhie(struct mhi_controller *mhi_cntrl,
const struct mhi_buf *mhi_buf)
{
void __iomem *base = mhi_cntrl->bhie;
@@ -187,7 +192,7 @@ static int mhi_fw_load_amss(struct mhi_controller *mhi_cntrl,
}
sequence_id = MHI_RANDOM_U32_NONZERO(BHIE_TXVECSTATUS_SEQNUM_BMSK);
- dev_dbg(dev, "Starting AMSS download via BHIe. Sequence ID:%u\n",
+ dev_dbg(dev, "Starting image download via BHIe. Sequence ID: %u\n",
sequence_id);
mhi_write_reg(mhi_cntrl, base, BHIE_TXVECADDR_HIGH_OFFS,
upper_32_bits(mhi_buf->dma_addr));
@@ -218,7 +223,7 @@ static int mhi_fw_load_amss(struct mhi_controller *mhi_cntrl,
return (!ret) ? -ETIMEDOUT : 0;
}
-static int mhi_fw_load_sbl(struct mhi_controller *mhi_cntrl,
+static int mhi_fw_load_bhi(struct mhi_controller *mhi_cntrl,
dma_addr_t dma_addr,
size_t size)
{
@@ -245,7 +250,7 @@ static int mhi_fw_load_sbl(struct mhi_controller *mhi_cntrl,
}
session_id = MHI_RANDOM_U32_NONZERO(BHI_TXDB_SEQNUM_BMSK);
- dev_dbg(dev, "Starting SBL download via BHI. Session ID:%u\n",
+ dev_dbg(dev, "Starting image download via BHI. Session ID: %u\n",
session_id);
mhi_write_reg(mhi_cntrl, base, BHI_STATUS, 0);
mhi_write_reg(mhi_cntrl, base, BHI_IMGADDR_HIGH,
@@ -365,7 +370,6 @@ static void mhi_firmware_copy(struct mhi_controller *mhi_cntrl,
size_t remainder = firmware->size;
size_t to_cpy;
const u8 *buf = firmware->data;
- int i = 0;
struct mhi_buf *mhi_buf = img_info->mhi_buf;
struct bhi_vec_entry *bhi_vec = img_info->bhi_vec;
@@ -377,7 +381,6 @@ static void mhi_firmware_copy(struct mhi_controller *mhi_cntrl,
buf += to_cpy;
remainder -= to_cpy;
- i++;
bhi_vec++;
mhi_buf++;
}
@@ -425,13 +428,13 @@ void mhi_fw_load_handler(struct mhi_controller *mhi_cntrl)
!mhi_cntrl->seg_len))) {
dev_err(dev,
"No firmware image defined or !sbl_size || !seg_len\n");
- return;
+ goto error_fw_load;
}
ret = request_firmware(&firmware, fw_name, dev);
if (ret) {
dev_err(dev, "Error loading firmware: %d\n", ret);
- return;
+ goto error_fw_load;
}
size = (mhi_cntrl->fbc_download) ? mhi_cntrl->sbl_size : firmware->size;
@@ -443,25 +446,25 @@ void mhi_fw_load_handler(struct mhi_controller *mhi_cntrl)
buf = mhi_alloc_coherent(mhi_cntrl, size, &dma_addr, GFP_KERNEL);
if (!buf) {
release_firmware(firmware);
- return;
+ goto error_fw_load;
}
- /* Download SBL image */
+ /* Download image using BHI */
memcpy(buf, firmware->data, size);
- ret = mhi_fw_load_sbl(mhi_cntrl, dma_addr, size);
+ ret = mhi_fw_load_bhi(mhi_cntrl, dma_addr, size);
mhi_free_coherent(mhi_cntrl, size, buf, dma_addr);
- if (!mhi_cntrl->fbc_download || ret || mhi_cntrl->ee == MHI_EE_EDL)
- release_firmware(firmware);
-
/* Error or in EDL mode, we're done */
if (ret) {
- dev_err(dev, "MHI did not load SBL, ret:%d\n", ret);
- return;
+ dev_err(dev, "MHI did not load image over BHI, ret: %d\n", ret);
+ release_firmware(firmware);
+ goto error_fw_load;
}
- if (mhi_cntrl->ee == MHI_EE_EDL)
+ if (mhi_cntrl->ee == MHI_EE_EDL) {
+ release_firmware(firmware);
return;
+ }
write_lock_irq(&mhi_cntrl->pm_lock);
mhi_cntrl->dev_state = MHI_STATE_RESET;
@@ -474,13 +477,17 @@ void mhi_fw_load_handler(struct mhi_controller *mhi_cntrl)
if (mhi_cntrl->fbc_download) {
ret = mhi_alloc_bhie_table(mhi_cntrl, &mhi_cntrl->fbc_image,
firmware->size);
- if (ret)
- goto error_alloc_fw_table;
+ if (ret) {
+ release_firmware(firmware);
+ goto error_fw_load;
+ }
/* Load the firmware into BHIE vec table */
mhi_firmware_copy(mhi_cntrl, firmware, mhi_cntrl->fbc_image);
}
+ release_firmware(firmware);
+
fw_load_ee_pthru:
/* Transitioning into MHI RESET->READY state */
ret = mhi_ready_state_transition(mhi_cntrl);
@@ -490,7 +497,7 @@ fw_load_ee_pthru:
if (ret) {
dev_err(dev, "MHI did not enter READY state\n");
- goto error_read;
+ goto error_ready_state;
}
/* Wait for the SBL event */
@@ -501,25 +508,27 @@ fw_load_ee_pthru:
if (!ret || MHI_PM_IN_ERROR_STATE(mhi_cntrl->pm_state)) {
dev_err(dev, "MHI did not enter SBL\n");
- goto error_read;
+ goto error_ready_state;
}
/* Start full firmware image download */
image_info = mhi_cntrl->fbc_image;
- ret = mhi_fw_load_amss(mhi_cntrl,
+ ret = mhi_fw_load_bhie(mhi_cntrl,
/* Vector table is the last entry */
&image_info->mhi_buf[image_info->entries - 1]);
- if (ret)
- dev_err(dev, "MHI did not load AMSS, ret:%d\n", ret);
-
- release_firmware(firmware);
+ if (ret) {
+ dev_err(dev, "MHI did not load image over BHIe, ret: %d\n",
+ ret);
+ goto error_fw_load;
+ }
return;
-error_read:
+error_ready_state:
mhi_free_bhie_table(mhi_cntrl, mhi_cntrl->fbc_image);
mhi_cntrl->fbc_image = NULL;
-error_alloc_fw_table:
- release_firmware(firmware);
+error_fw_load:
+ mhi_cntrl->pm_state = MHI_PM_FW_DL_ERR;
+ wake_up_all(&mhi_cntrl->state_event);
}
diff --git a/drivers/bus/mhi/core/debugfs.c b/drivers/bus/mhi/core/debugfs.c
index 3a48801e01f4..7d43138ce66d 100644
--- a/drivers/bus/mhi/core/debugfs.c
+++ b/drivers/bus/mhi/core/debugfs.c
@@ -159,7 +159,9 @@ static int mhi_debugfs_devices_show(struct seq_file *m, void *d)
return -ENODEV;
}
- device_for_each_child(mhi_cntrl->cntrl_dev, m, mhi_device_info_show);
+ /* Show controller and client(s) info */
+ mhi_device_info_show(&mhi_cntrl->mhi_dev->dev, m);
+ device_for_each_child(&mhi_cntrl->mhi_dev->dev, m, mhi_device_info_show);
return 0;
}
diff --git a/drivers/bus/mhi/core/init.c b/drivers/bus/mhi/core/init.c
index 0ffdebde8265..f0697f433c2f 100644
--- a/drivers/bus/mhi/core/init.c
+++ b/drivers/bus/mhi/core/init.c
@@ -8,6 +8,7 @@
#include <linux/device.h>
#include <linux/dma-direction.h>
#include <linux/dma-mapping.h>
+#include <linux/idr.h>
#include <linux/interrupt.h>
#include <linux/list.h>
#include <linux/mhi.h>
@@ -18,6 +19,8 @@
#include <linux/wait.h>
#include "internal.h"
+static DEFINE_IDA(mhi_controller_ida);
+
const char * const mhi_ee_str[MHI_EE_MAX] = {
[MHI_EE_PBL] = "PBL",
[MHI_EE_SBL] = "SBL",
@@ -610,7 +613,7 @@ static int parse_ev_cfg(struct mhi_controller *mhi_cntrl,
{
struct mhi_event *mhi_event;
const struct mhi_event_config *event_cfg;
- struct device *dev = &mhi_cntrl->mhi_dev->dev;
+ struct device *dev = mhi_cntrl->cntrl_dev;
int i, num;
num = config->num_events;
@@ -692,7 +695,7 @@ static int parse_ch_cfg(struct mhi_controller *mhi_cntrl,
const struct mhi_controller_config *config)
{
const struct mhi_channel_config *ch_cfg;
- struct device *dev = &mhi_cntrl->mhi_dev->dev;
+ struct device *dev = mhi_cntrl->cntrl_dev;
int i;
u32 chan;
@@ -758,7 +761,6 @@ static int parse_ch_cfg(struct mhi_controller *mhi_cntrl,
mhi_chan->offload_ch = ch_cfg->offload_channel;
mhi_chan->db_cfg.reset_req = ch_cfg->doorbell_mode_switch;
mhi_chan->pre_alloc = ch_cfg->auto_queue;
- mhi_chan->auto_start = ch_cfg->auto_start;
/*
* If MHI host allocates buffers, then the channel direction
@@ -858,7 +860,7 @@ int mhi_register_controller(struct mhi_controller *mhi_cntrl,
if (!mhi_cntrl->runtime_get || !mhi_cntrl->runtime_put ||
!mhi_cntrl->status_cb || !mhi_cntrl->read_reg ||
- !mhi_cntrl->write_reg)
+ !mhi_cntrl->write_reg || !mhi_cntrl->nr_irqs)
return -EINVAL;
ret = parse_config(mhi_cntrl, config);
@@ -869,7 +871,7 @@ int mhi_register_controller(struct mhi_controller *mhi_cntrl,
sizeof(*mhi_cntrl->mhi_cmd), GFP_KERNEL);
if (!mhi_cntrl->mhi_cmd) {
ret = -ENOMEM;
- goto error_alloc_cmd;
+ goto err_free_event;
}
INIT_LIST_HEAD(&mhi_cntrl->transition_list);
@@ -880,6 +882,14 @@ int mhi_register_controller(struct mhi_controller *mhi_cntrl,
INIT_WORK(&mhi_cntrl->st_worker, mhi_pm_st_worker);
init_waitqueue_head(&mhi_cntrl->state_event);
+ mhi_cntrl->hiprio_wq = alloc_ordered_workqueue
+ ("mhi_hiprio_wq", WQ_MEM_RECLAIM | WQ_HIGHPRI);
+ if (!mhi_cntrl->hiprio_wq) {
+ dev_err(mhi_cntrl->cntrl_dev, "Failed to allocate workqueue\n");
+ ret = -ENOMEM;
+ goto err_free_cmd;
+ }
+
mhi_cmd = mhi_cntrl->mhi_cmd;
for (i = 0; i < NR_OF_CMD_RINGS; i++, mhi_cmd++)
spin_lock_init(&mhi_cmd->lock);
@@ -923,7 +933,7 @@ int mhi_register_controller(struct mhi_controller *mhi_cntrl,
ret = mhi_read_reg(mhi_cntrl, mhi_cntrl->regs,
SOC_HW_VERSION_OFFS, &soc_info);
if (ret)
- goto error_alloc_dev;
+ goto err_destroy_wq;
mhi_cntrl->family_number = (soc_info & SOC_HW_VERSION_FAM_NUM_BMSK) >>
SOC_HW_VERSION_FAM_NUM_SHFT;
@@ -934,25 +944,31 @@ int mhi_register_controller(struct mhi_controller *mhi_cntrl,
mhi_cntrl->minor_version = (soc_info & SOC_HW_VERSION_MINOR_VER_BMSK) >>
SOC_HW_VERSION_MINOR_VER_SHFT;
+ mhi_cntrl->index = ida_alloc(&mhi_controller_ida, GFP_KERNEL);
+ if (mhi_cntrl->index < 0) {
+ ret = mhi_cntrl->index;
+ goto err_destroy_wq;
+ }
+
/* Register controller with MHI bus */
mhi_dev = mhi_alloc_device(mhi_cntrl);
if (IS_ERR(mhi_dev)) {
dev_err(mhi_cntrl->cntrl_dev, "Failed to allocate MHI device\n");
ret = PTR_ERR(mhi_dev);
- goto error_alloc_dev;
+ goto err_ida_free;
}
mhi_dev->dev_type = MHI_DEVICE_CONTROLLER;
mhi_dev->mhi_cntrl = mhi_cntrl;
- dev_set_name(&mhi_dev->dev, "%s", dev_name(mhi_cntrl->cntrl_dev));
- mhi_dev->name = dev_name(mhi_cntrl->cntrl_dev);
+ dev_set_name(&mhi_dev->dev, "mhi%d", mhi_cntrl->index);
+ mhi_dev->name = dev_name(&mhi_dev->dev);
/* Init wakeup source */
device_init_wakeup(&mhi_dev->dev, true);
ret = device_add(&mhi_dev->dev);
if (ret)
- goto error_add_dev;
+ goto err_release_dev;
mhi_cntrl->mhi_dev = mhi_dev;
@@ -960,15 +976,17 @@ int mhi_register_controller(struct mhi_controller *mhi_cntrl,
return 0;
-error_add_dev:
+err_release_dev:
put_device(&mhi_dev->dev);
-
-error_alloc_dev:
+err_ida_free:
+ ida_free(&mhi_controller_ida, mhi_cntrl->index);
+err_destroy_wq:
+ destroy_workqueue(mhi_cntrl->hiprio_wq);
+err_free_cmd:
kfree(mhi_cntrl->mhi_cmd);
-
-error_alloc_cmd:
- vfree(mhi_cntrl->mhi_chan);
+err_free_event:
kfree(mhi_cntrl->mhi_event);
+ vfree(mhi_cntrl->mhi_chan);
return ret;
}
@@ -982,6 +1000,7 @@ void mhi_unregister_controller(struct mhi_controller *mhi_cntrl)
mhi_destroy_debugfs(mhi_cntrl);
+ destroy_workqueue(mhi_cntrl->hiprio_wq);
kfree(mhi_cntrl->mhi_cmd);
kfree(mhi_cntrl->mhi_event);
@@ -996,6 +1015,8 @@ void mhi_unregister_controller(struct mhi_controller *mhi_cntrl)
device_del(&mhi_dev->dev);
put_device(&mhi_dev->dev);
+
+ ida_free(&mhi_controller_ida, mhi_cntrl->index);
}
EXPORT_SYMBOL_GPL(mhi_unregister_controller);
@@ -1122,7 +1143,15 @@ struct mhi_device *mhi_alloc_device(struct mhi_controller *mhi_cntrl)
device_initialize(dev);
dev->bus = &mhi_bus_type;
dev->release = mhi_release_device;
- dev->parent = mhi_cntrl->cntrl_dev;
+
+ if (mhi_cntrl->mhi_dev) {
+ /* for MHI client devices, parent is the MHI controller device */
+ dev->parent = &mhi_cntrl->mhi_dev->dev;
+ } else {
+ /* for MHI controller device, parent is the bus device (e.g. pci device) */
+ dev->parent = mhi_cntrl->cntrl_dev;
+ }
+
mhi_dev->mhi_cntrl = mhi_cntrl;
mhi_dev->dev_wake = 0;
@@ -1160,11 +1189,6 @@ static int mhi_driver_probe(struct device *dev)
goto exit_probe;
ul_chan->xfer_cb = mhi_drv->ul_xfer_cb;
- if (ul_chan->auto_start) {
- ret = mhi_prepare_channel(mhi_cntrl, ul_chan);
- if (ret)
- goto exit_probe;
- }
}
ret = -EINVAL;
@@ -1198,9 +1222,6 @@ static int mhi_driver_probe(struct device *dev)
if (ret)
goto exit_probe;
- if (dl_chan && dl_chan->auto_start)
- mhi_prepare_channel(mhi_cntrl, dl_chan);
-
mhi_device_put(mhi_dev);
return ret;
@@ -1276,10 +1297,8 @@ static int mhi_driver_remove(struct device *dev)
mutex_unlock(&mhi_chan->mutex);
}
- read_lock_bh(&mhi_cntrl->pm_lock);
while (mhi_dev->dev_wake)
mhi_device_put(mhi_dev);
- read_unlock_bh(&mhi_cntrl->pm_lock);
return 0;
}
diff --git a/drivers/bus/mhi/core/internal.h b/drivers/bus/mhi/core/internal.h
index 7989269ddd96..6f80ec30c0cd 100644
--- a/drivers/bus/mhi/core/internal.h
+++ b/drivers/bus/mhi/core/internal.h
@@ -153,8 +153,8 @@ extern struct bus_type mhi_bus_type;
#define BHI_SERIALNU (0x40)
#define BHI_SBLANTIROLLVER (0x44)
#define BHI_NUMSEG (0x48)
-#define BHI_MSMHWID(n) (0x4C + (0x4 * n))
-#define BHI_OEMPKHASH(n) (0x64 + (0x4 * n))
+#define BHI_MSMHWID(n) (0x4C + (0x4 * (n)))
+#define BHI_OEMPKHASH(n) (0x64 + (0x4 * (n)))
#define BHI_RSVD5 (0xC4)
#define BHI_STATUS_MASK (0xC0000000)
#define BHI_STATUS_SHIFT (30)
@@ -563,7 +563,6 @@ struct mhi_chan {
bool configured;
bool offload_ch;
bool pre_alloc;
- bool auto_start;
bool wake_capable;
};
@@ -609,12 +608,10 @@ enum mhi_pm_state __must_check mhi_tryset_pm_state(
struct mhi_controller *mhi_cntrl,
enum mhi_pm_state state);
const char *to_mhi_pm_state_str(enum mhi_pm_state state);
-enum mhi_ee_type mhi_get_exec_env(struct mhi_controller *mhi_cntrl);
int mhi_queue_state_transition(struct mhi_controller *mhi_cntrl,
enum dev_st_transition state);
void mhi_pm_st_worker(struct work_struct *work);
void mhi_pm_sys_err_handler(struct mhi_controller *mhi_cntrl);
-void mhi_fw_load_worker(struct work_struct *work);
int mhi_ready_state_transition(struct mhi_controller *mhi_cntrl);
int mhi_pm_m0_transition(struct mhi_controller *mhi_cntrl);
void mhi_pm_m1_transition(struct mhi_controller *mhi_cntrl);
diff --git a/drivers/bus/mhi/core/main.c b/drivers/bus/mhi/core/main.c
index 2cff5ddff225..702c31b6aefa 100644
--- a/drivers/bus/mhi/core/main.c
+++ b/drivers/bus/mhi/core/main.c
@@ -123,6 +123,7 @@ enum mhi_ee_type mhi_get_exec_env(struct mhi_controller *mhi_cntrl)
return (ret) ? MHI_EE_MAX : exec;
}
+EXPORT_SYMBOL_GPL(mhi_get_exec_env);
enum mhi_state mhi_get_mhi_state(struct mhi_controller *mhi_cntrl)
{
@@ -132,6 +133,7 @@ enum mhi_state mhi_get_mhi_state(struct mhi_controller *mhi_cntrl)
MHISTATUS_MHISTATE_SHIFT, &state);
return ret ? MHI_STATE_MAX : state;
}
+EXPORT_SYMBOL_GPL(mhi_get_mhi_state);
int mhi_map_single_no_bb(struct mhi_controller *mhi_cntrl,
struct mhi_buf_info *buf_info)
@@ -329,7 +331,7 @@ void mhi_create_devices(struct mhi_controller *mhi_cntrl)
/* Channel name is same for both UL and DL */
mhi_dev->name = mhi_chan->name;
dev_set_name(&mhi_dev->dev, "%s_%s",
- dev_name(mhi_cntrl->cntrl_dev),
+ dev_name(&mhi_cntrl->mhi_dev->dev),
mhi_dev->name);
/* Init wakeup source if available */
@@ -399,6 +401,10 @@ irqreturn_t mhi_intvec_threaded_handler(int irq_number, void *priv)
/* If device supports RDDM don't bother processing SYS error */
if (mhi_cntrl->rddm_image) {
+ /* host may be performing a device power down already */
+ if (!mhi_is_active(mhi_cntrl))
+ goto exit_intvec;
+
if (mhi_cntrl->ee == MHI_EE_RDDM && mhi_cntrl->ee != ee) {
mhi_cntrl->status_cb(mhi_cntrl, MHI_CB_EE_RDDM);
wake_up_all(&mhi_cntrl->state_event);
@@ -735,11 +741,6 @@ int mhi_process_ctrl_ev_ring(struct mhi_controller *mhi_cntrl,
{
enum mhi_pm_state new_state;
- /* skip SYS_ERROR handling if RDDM supported */
- if (mhi_cntrl->ee == MHI_EE_RDDM ||
- mhi_cntrl->rddm_image)
- break;
-
dev_dbg(dev, "System error detected\n");
write_lock_irq(&mhi_cntrl->pm_lock);
new_state = mhi_tryset_pm_state(mhi_cntrl,
@@ -1224,7 +1225,8 @@ static void __mhi_unprepare_channel(struct mhi_controller *mhi_cntrl,
/* no more processing events for this channel */
mutex_lock(&mhi_chan->mutex);
write_lock_irq(&mhi_chan->lock);
- if (mhi_chan->ch_state != MHI_CH_STATE_ENABLED) {
+ if (mhi_chan->ch_state != MHI_CH_STATE_ENABLED &&
+ mhi_chan->ch_state != MHI_CH_STATE_SUSPENDED) {
write_unlock_irq(&mhi_chan->lock);
mutex_unlock(&mhi_chan->mutex);
return;
diff --git a/drivers/bus/mhi/core/pm.c b/drivers/bus/mhi/core/pm.c
index 3de7b1639ec6..681960c72d2a 100644
--- a/drivers/bus/mhi/core/pm.c
+++ b/drivers/bus/mhi/core/pm.c
@@ -37,9 +37,10 @@
* M0 -> FW_DL_ERR
* M0 -> M3_ENTER -> M3 -> M3_EXIT --> M0
* L1: SYS_ERR_DETECT -> SYS_ERR_PROCESS --> POR
- * L2: SHUTDOWN_PROCESS -> DISABLE
+ * L2: SHUTDOWN_PROCESS -> LD_ERR_FATAL_DETECT
+ * SHUTDOWN_PROCESS -> DISABLE
* L3: LD_ERR_FATAL_DETECT <--> LD_ERR_FATAL_DETECT
- * LD_ERR_FATAL_DETECT -> SHUTDOWN_PROCESS
+ * LD_ERR_FATAL_DETECT -> DISABLE
*/
static struct mhi_pm_transitions const dev_state_transitions[] = {
/* L0 States */
@@ -72,7 +73,7 @@ static struct mhi_pm_transitions const dev_state_transitions[] = {
{
MHI_PM_M3,
MHI_PM_M3_EXIT | MHI_PM_SYS_ERR_DETECT |
- MHI_PM_SHUTDOWN_PROCESS | MHI_PM_LD_ERR_FATAL_DETECT
+ MHI_PM_LD_ERR_FATAL_DETECT
},
{
MHI_PM_M3_EXIT,
@@ -103,7 +104,7 @@ static struct mhi_pm_transitions const dev_state_transitions[] = {
/* L3 States */
{
MHI_PM_LD_ERR_FATAL_DETECT,
- MHI_PM_LD_ERR_FATAL_DETECT | MHI_PM_SHUTDOWN_PROCESS
+ MHI_PM_LD_ERR_FATAL_DETECT | MHI_PM_DISABLE
},
};
@@ -383,10 +384,14 @@ static int mhi_pm_mission_mode_transition(struct mhi_controller *mhi_cntrl)
write_lock_irq(&mhi_cntrl->pm_lock);
if (MHI_REG_ACCESS_VALID(mhi_cntrl->pm_state))
mhi_cntrl->ee = mhi_get_exec_env(mhi_cntrl);
- write_unlock_irq(&mhi_cntrl->pm_lock);
- if (!MHI_IN_MISSION_MODE(mhi_cntrl->ee))
+ if (!MHI_IN_MISSION_MODE(mhi_cntrl->ee)) {
+ mhi_cntrl->pm_state = MHI_PM_LD_ERR_FATAL_DETECT;
+ write_unlock_irq(&mhi_cntrl->pm_lock);
+ wake_up_all(&mhi_cntrl->state_event);
return -EIO;
+ }
+ write_unlock_irq(&mhi_cntrl->pm_lock);
wake_up_all(&mhi_cntrl->state_event);
@@ -440,9 +445,119 @@ error_mission_mode:
return ret;
}
-/* Handle SYS_ERR and Shutdown transitions */
-static void mhi_pm_disable_transition(struct mhi_controller *mhi_cntrl,
- enum mhi_pm_state transition_state)
+/* Handle shutdown transitions */
+static void mhi_pm_disable_transition(struct mhi_controller *mhi_cntrl)
+{
+ enum mhi_pm_state cur_state;
+ struct mhi_event *mhi_event;
+ struct mhi_cmd_ctxt *cmd_ctxt;
+ struct mhi_cmd *mhi_cmd;
+ struct mhi_event_ctxt *er_ctxt;
+ struct device *dev = &mhi_cntrl->mhi_dev->dev;
+ int ret, i;
+
+ dev_dbg(dev, "Processing disable transition with PM state: %s\n",
+ to_mhi_pm_state_str(mhi_cntrl->pm_state));
+
+ mutex_lock(&mhi_cntrl->pm_mutex);
+
+ /* Trigger MHI RESET so that the device will not access host memory */
+ if (!MHI_PM_IN_FATAL_STATE(mhi_cntrl->pm_state)) {
+ 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,
+ MHICTRL_RESET_SHIFT,
+ &in_reset) ||
+ !in_reset, timeout);
+ if (!ret || in_reset)
+ dev_err(dev, "Device failed to exit MHI Reset state\n");
+
+ /*
+ * Device will clear BHI_INTVEC as a part of RESET processing,
+ * hence re-program it
+ */
+ mhi_write_reg(mhi_cntrl, mhi_cntrl->bhi, BHI_INTVEC, 0);
+ }
+
+ dev_dbg(dev,
+ "Waiting for all pending event ring processing to complete\n");
+ mhi_event = mhi_cntrl->mhi_event;
+ for (i = 0; i < mhi_cntrl->total_ev_rings; i++, mhi_event++) {
+ if (mhi_event->offload_ev)
+ continue;
+ free_irq(mhi_cntrl->irq[mhi_event->irq], mhi_event);
+ tasklet_kill(&mhi_event->task);
+ }
+
+ /* Release lock and wait for all pending threads to complete */
+ mutex_unlock(&mhi_cntrl->pm_mutex);
+ dev_dbg(dev, "Waiting for all pending threads to complete\n");
+ wake_up_all(&mhi_cntrl->state_event);
+
+ dev_dbg(dev, "Reset all active channels and remove MHI devices\n");
+ device_for_each_child(&mhi_cntrl->mhi_dev->dev, NULL, mhi_destroy_device);
+
+ mutex_lock(&mhi_cntrl->pm_mutex);
+
+ WARN_ON(atomic_read(&mhi_cntrl->dev_wake));
+ WARN_ON(atomic_read(&mhi_cntrl->pending_pkts));
+
+ /* Reset the ev rings and cmd rings */
+ dev_dbg(dev, "Resetting EV CTXT and CMD CTXT\n");
+ mhi_cmd = mhi_cntrl->mhi_cmd;
+ cmd_ctxt = mhi_cntrl->mhi_ctxt->cmd_ctxt;
+ for (i = 0; i < NR_OF_CMD_RINGS; i++, mhi_cmd++, cmd_ctxt++) {
+ struct mhi_ring *ring = &mhi_cmd->ring;
+
+ ring->rp = ring->base;
+ ring->wp = ring->base;
+ cmd_ctxt->rp = cmd_ctxt->rbase;
+ cmd_ctxt->wp = cmd_ctxt->rbase;
+ }
+
+ mhi_event = mhi_cntrl->mhi_event;
+ er_ctxt = mhi_cntrl->mhi_ctxt->er_ctxt;
+ for (i = 0; i < mhi_cntrl->total_ev_rings; i++, er_ctxt++,
+ mhi_event++) {
+ struct mhi_ring *ring = &mhi_event->ring;
+
+ /* Skip offload events */
+ if (mhi_event->offload_ev)
+ continue;
+
+ ring->rp = ring->base;
+ ring->wp = ring->base;
+ er_ctxt->rp = er_ctxt->rbase;
+ er_ctxt->wp = er_ctxt->rbase;
+ }
+
+ /* Move to disable state */
+ write_lock_irq(&mhi_cntrl->pm_lock);
+ cur_state = mhi_tryset_pm_state(mhi_cntrl, MHI_PM_DISABLE);
+ write_unlock_irq(&mhi_cntrl->pm_lock);
+ if (unlikely(cur_state != MHI_PM_DISABLE))
+ dev_err(dev, "Error moving from PM state: %s to: %s\n",
+ to_mhi_pm_state_str(cur_state),
+ to_mhi_pm_state_str(MHI_PM_DISABLE));
+
+ dev_dbg(dev, "Exiting with PM state: %s, MHI state: %s\n",
+ to_mhi_pm_state_str(mhi_cntrl->pm_state),
+ TO_MHI_STATE_STR(mhi_cntrl->dev_state));
+
+ mutex_unlock(&mhi_cntrl->pm_mutex);
+}
+
+/* Handle system error transitions */
+static void mhi_pm_sys_error_transition(struct mhi_controller *mhi_cntrl)
{
enum mhi_pm_state cur_state, prev_state;
struct mhi_event *mhi_event;
@@ -454,33 +569,30 @@ static void mhi_pm_disable_transition(struct mhi_controller *mhi_cntrl,
dev_dbg(dev, "Transitioning from PM state: %s to: %s\n",
to_mhi_pm_state_str(mhi_cntrl->pm_state),
- to_mhi_pm_state_str(transition_state));
+ to_mhi_pm_state_str(MHI_PM_SYS_ERR_PROCESS));
/* We must notify MHI control driver so it can clean up first */
- if (transition_state == MHI_PM_SYS_ERR_PROCESS)
- mhi_cntrl->status_cb(mhi_cntrl, MHI_CB_SYS_ERROR);
+ mhi_cntrl->status_cb(mhi_cntrl, MHI_CB_SYS_ERROR);
mutex_lock(&mhi_cntrl->pm_mutex);
write_lock_irq(&mhi_cntrl->pm_lock);
prev_state = mhi_cntrl->pm_state;
- cur_state = mhi_tryset_pm_state(mhi_cntrl, transition_state);
- if (cur_state == transition_state) {
- mhi_cntrl->ee = MHI_EE_DISABLE_TRANSITION;
- mhi_cntrl->dev_state = MHI_STATE_RESET;
- }
+ cur_state = mhi_tryset_pm_state(mhi_cntrl, MHI_PM_SYS_ERR_PROCESS);
write_unlock_irq(&mhi_cntrl->pm_lock);
+ if (cur_state != MHI_PM_SYS_ERR_PROCESS) {
+ dev_err(dev, "Failed to transition from PM state: %s to: %s\n",
+ to_mhi_pm_state_str(cur_state),
+ to_mhi_pm_state_str(MHI_PM_SYS_ERR_PROCESS));
+ goto exit_sys_error_transition;
+ }
+
+ mhi_cntrl->ee = MHI_EE_DISABLE_TRANSITION;
+ mhi_cntrl->dev_state = MHI_STATE_RESET;
+
/* Wake up threads waiting for state transition */
wake_up_all(&mhi_cntrl->state_event);
- if (cur_state != transition_state) {
- dev_err(dev, "Failed to transition to state: %s from: %s\n",
- to_mhi_pm_state_str(transition_state),
- to_mhi_pm_state_str(cur_state));
- mutex_unlock(&mhi_cntrl->pm_mutex);
- return;
- }
-
/* Trigger MHI RESET so that the device will not access host memory */
if (MHI_REG_ACCESS_VALID(prev_state)) {
u32 in_reset = -1;
@@ -498,10 +610,9 @@ static void mhi_pm_disable_transition(struct mhi_controller *mhi_cntrl,
MHICTRL_RESET_SHIFT,
&in_reset) ||
!in_reset, timeout);
- if ((!ret || in_reset) && cur_state == MHI_PM_SYS_ERR_PROCESS) {
+ if (!ret || in_reset) {
dev_err(dev, "Device failed to exit MHI Reset state\n");
- mutex_unlock(&mhi_cntrl->pm_mutex);
- return;
+ goto exit_sys_error_transition;
}
/*
@@ -512,7 +623,7 @@ static void mhi_pm_disable_transition(struct mhi_controller *mhi_cntrl,
}
dev_dbg(dev,
- "Waiting for all pending event ring processing to complete\n");
+ "Waiting for all pending event ring processing to complete\n");
mhi_event = mhi_cntrl->mhi_event;
for (i = 0; i < mhi_cntrl->total_ev_rings; i++, mhi_event++) {
if (mhi_event->offload_ev)
@@ -526,7 +637,7 @@ static void mhi_pm_disable_transition(struct mhi_controller *mhi_cntrl,
wake_up_all(&mhi_cntrl->state_event);
dev_dbg(dev, "Reset all active channels and remove MHI devices\n");
- device_for_each_child(mhi_cntrl->cntrl_dev, NULL, mhi_destroy_device);
+ device_for_each_child(&mhi_cntrl->mhi_dev->dev, NULL, mhi_destroy_device);
mutex_lock(&mhi_cntrl->pm_mutex);
@@ -549,7 +660,7 @@ static void mhi_pm_disable_transition(struct mhi_controller *mhi_cntrl,
mhi_event = mhi_cntrl->mhi_event;
er_ctxt = mhi_cntrl->mhi_ctxt->er_ctxt;
for (i = 0; i < mhi_cntrl->total_ev_rings; i++, er_ctxt++,
- mhi_event++) {
+ mhi_event++) {
struct mhi_ring *ring = &mhi_event->ring;
/* Skip offload events */
@@ -562,19 +673,9 @@ static void mhi_pm_disable_transition(struct mhi_controller *mhi_cntrl,
er_ctxt->wp = er_ctxt->rbase;
}
- if (cur_state == MHI_PM_SYS_ERR_PROCESS) {
- mhi_ready_state_transition(mhi_cntrl);
- } else {
- /* Move to disable state */
- write_lock_irq(&mhi_cntrl->pm_lock);
- cur_state = mhi_tryset_pm_state(mhi_cntrl, MHI_PM_DISABLE);
- write_unlock_irq(&mhi_cntrl->pm_lock);
- if (unlikely(cur_state != MHI_PM_DISABLE))
- dev_err(dev, "Error moving from PM state: %s to: %s\n",
- to_mhi_pm_state_str(cur_state),
- to_mhi_pm_state_str(MHI_PM_DISABLE));
- }
+ mhi_ready_state_transition(mhi_cntrl);
+exit_sys_error_transition:
dev_dbg(dev, "Exiting with PM state: %s, MHI state: %s\n",
to_mhi_pm_state_str(mhi_cntrl->pm_state),
TO_MHI_STATE_STR(mhi_cntrl->dev_state));
@@ -597,7 +698,7 @@ int mhi_queue_state_transition(struct mhi_controller *mhi_cntrl,
list_add_tail(&item->node, &mhi_cntrl->transition_list);
spin_unlock_irqrestore(&mhi_cntrl->transition_lock, flags);
- schedule_work(&mhi_cntrl->st_worker);
+ queue_work(mhi_cntrl->hiprio_wq, &mhi_cntrl->st_worker);
return 0;
}
@@ -662,12 +763,10 @@ void mhi_pm_st_worker(struct work_struct *work)
mhi_ready_state_transition(mhi_cntrl);
break;
case DEV_ST_TRANSITION_SYS_ERR:
- mhi_pm_disable_transition
- (mhi_cntrl, MHI_PM_SYS_ERR_PROCESS);
+ mhi_pm_sys_error_transition(mhi_cntrl);
break;
case DEV_ST_TRANSITION_DISABLE:
- mhi_pm_disable_transition
- (mhi_cntrl, MHI_PM_SHUTDOWN_PROCESS);
+ mhi_pm_disable_transition(mhi_cntrl);
break;
default:
break;
@@ -827,6 +926,10 @@ int __mhi_device_get_sync(struct mhi_controller *mhi_cntrl)
/* Wake up the device */
read_lock_bh(&mhi_cntrl->pm_lock);
+ if (MHI_PM_IN_ERROR_STATE(mhi_cntrl->pm_state)) {
+ read_unlock_bh(&mhi_cntrl->pm_lock);
+ return -EIO;
+ }
mhi_cntrl->wake_get(mhi_cntrl, true);
if (MHI_PM_IN_SUSPEND_STATE(mhi_cntrl->pm_state))
mhi_trigger_resume(mhi_cntrl);
@@ -918,9 +1021,6 @@ int mhi_async_power_up(struct mhi_controller *mhi_cntrl)
dev_info(dev, "Requested to power ON\n");
- if (mhi_cntrl->nr_irqs < 1)
- return -EINVAL;
-
/* Supply default wake routines if not provided by controller driver */
if (!mhi_cntrl->wake_get || !mhi_cntrl->wake_put ||
!mhi_cntrl->wake_toggle) {
@@ -1033,29 +1133,39 @@ EXPORT_SYMBOL_GPL(mhi_async_power_up);
void mhi_power_down(struct mhi_controller *mhi_cntrl, bool graceful)
{
- enum mhi_pm_state cur_state;
+ enum mhi_pm_state cur_state, transition_state;
struct device *dev = &mhi_cntrl->mhi_dev->dev;
/* If it's not a graceful shutdown, force MHI to linkdown state */
- if (!graceful) {
- mutex_lock(&mhi_cntrl->pm_mutex);
- write_lock_irq(&mhi_cntrl->pm_lock);
- cur_state = mhi_tryset_pm_state(mhi_cntrl,
- MHI_PM_LD_ERR_FATAL_DETECT);
- write_unlock_irq(&mhi_cntrl->pm_lock);
- mutex_unlock(&mhi_cntrl->pm_mutex);
- if (cur_state != MHI_PM_LD_ERR_FATAL_DETECT)
- dev_dbg(dev, "Failed to move to state: %s from: %s\n",
- to_mhi_pm_state_str(MHI_PM_LD_ERR_FATAL_DETECT),
- to_mhi_pm_state_str(mhi_cntrl->pm_state));
+ transition_state = (graceful) ? MHI_PM_SHUTDOWN_PROCESS :
+ MHI_PM_LD_ERR_FATAL_DETECT;
+
+ mutex_lock(&mhi_cntrl->pm_mutex);
+ write_lock_irq(&mhi_cntrl->pm_lock);
+ cur_state = mhi_tryset_pm_state(mhi_cntrl, transition_state);
+ if (cur_state != transition_state) {
+ dev_err(dev, "Failed to move to state: %s from: %s\n",
+ to_mhi_pm_state_str(transition_state),
+ to_mhi_pm_state_str(mhi_cntrl->pm_state));
+ /* Force link down or error fatal detected state */
+ mhi_cntrl->pm_state = MHI_PM_LD_ERR_FATAL_DETECT;
}
+ /* mark device inactive to avoid any further host processing */
+ mhi_cntrl->ee = MHI_EE_DISABLE_TRANSITION;
+ mhi_cntrl->dev_state = MHI_STATE_RESET;
+
+ wake_up_all(&mhi_cntrl->state_event);
+
+ write_unlock_irq(&mhi_cntrl->pm_lock);
+ mutex_unlock(&mhi_cntrl->pm_mutex);
+
mhi_queue_state_transition(mhi_cntrl, DEV_ST_TRANSITION_DISABLE);
/* Wait for shutdown to complete */
flush_work(&mhi_cntrl->st_worker);
- mhi_deinit_free_irq(mhi_cntrl);
+ free_irq(mhi_cntrl->irq[0], mhi_cntrl);
if (!mhi_cntrl->pre_init) {
/* Free all allocated resources */
diff --git a/drivers/bus/mhi/pci_generic.c b/drivers/bus/mhi/pci_generic.c
new file mode 100644
index 000000000000..f5bee76ea061
--- /dev/null
+++ b/drivers/bus/mhi/pci_generic.c
@@ -0,0 +1,345 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * MHI PCI driver - MHI over PCI controller driver
+ *
+ * This module is a generic driver for registering MHI-over-PCI devices,
+ * such as PCIe QCOM modems.
+ *
+ * Copyright (C) 2020 Linaro Ltd <loic.poulain@linaro.org>
+ */
+
+#include <linux/device.h>
+#include <linux/mhi.h>
+#include <linux/module.h>
+#include <linux/pci.h>
+
+#define MHI_PCI_DEFAULT_BAR_NUM 0
+
+/**
+ * struct mhi_pci_dev_info - MHI PCI device specific information
+ * @config: MHI controller configuration
+ * @name: name of the PCI module
+ * @fw: firmware path (if any)
+ * @edl: emergency download mode firmware path (if any)
+ * @bar_num: PCI base address register to use for MHI MMIO register space
+ * @dma_data_width: DMA transfer word size (32 or 64 bits)
+ */
+struct mhi_pci_dev_info {
+ const struct mhi_controller_config *config;
+ const char *name;
+ const char *fw;
+ const char *edl;
+ unsigned int bar_num;
+ unsigned int dma_data_width;
+};
+
+#define MHI_CHANNEL_CONFIG_UL(ch_num, ch_name, el_count, ev_ring) \
+ { \
+ .num = ch_num, \
+ .name = ch_name, \
+ .num_elements = el_count, \
+ .event_ring = ev_ring, \
+ .dir = DMA_TO_DEVICE, \
+ .ee_mask = BIT(MHI_EE_AMSS), \
+ .pollcfg = 0, \
+ .doorbell = MHI_DB_BRST_DISABLE, \
+ .lpm_notify = false, \
+ .offload_channel = false, \
+ .doorbell_mode_switch = false, \
+ } \
+
+#define MHI_CHANNEL_CONFIG_DL(ch_num, ch_name, el_count, ev_ring) \
+ { \
+ .num = ch_num, \
+ .name = ch_name, \
+ .num_elements = el_count, \
+ .event_ring = ev_ring, \
+ .dir = DMA_FROM_DEVICE, \
+ .ee_mask = BIT(MHI_EE_AMSS), \
+ .pollcfg = 0, \
+ .doorbell = MHI_DB_BRST_DISABLE, \
+ .lpm_notify = false, \
+ .offload_channel = false, \
+ .doorbell_mode_switch = false, \
+ }
+
+#define MHI_EVENT_CONFIG_CTRL(ev_ring) \
+ { \
+ .num_elements = 64, \
+ .irq_moderation_ms = 0, \
+ .irq = (ev_ring) + 1, \
+ .priority = 1, \
+ .mode = MHI_DB_BRST_DISABLE, \
+ .data_type = MHI_ER_CTRL, \
+ .hardware_event = false, \
+ .client_managed = false, \
+ .offload_channel = false, \
+ }
+
+#define MHI_EVENT_CONFIG_DATA(ev_ring) \
+ { \
+ .num_elements = 128, \
+ .irq_moderation_ms = 5, \
+ .irq = (ev_ring) + 1, \
+ .priority = 1, \
+ .mode = MHI_DB_BRST_DISABLE, \
+ .data_type = MHI_ER_DATA, \
+ .hardware_event = false, \
+ .client_managed = false, \
+ .offload_channel = false, \
+ }
+
+#define MHI_EVENT_CONFIG_HW_DATA(ev_ring, ch_num) \
+ { \
+ .num_elements = 128, \
+ .irq_moderation_ms = 5, \
+ .irq = (ev_ring) + 1, \
+ .priority = 1, \
+ .mode = MHI_DB_BRST_DISABLE, \
+ .data_type = MHI_ER_DATA, \
+ .hardware_event = true, \
+ .client_managed = false, \
+ .offload_channel = false, \
+ .channel = ch_num, \
+ }
+
+static const struct mhi_channel_config modem_qcom_v1_mhi_channels[] = {
+ MHI_CHANNEL_CONFIG_UL(12, "MBIM", 4, 0),
+ MHI_CHANNEL_CONFIG_DL(13, "MBIM", 4, 0),
+ MHI_CHANNEL_CONFIG_UL(14, "QMI", 4, 0),
+ MHI_CHANNEL_CONFIG_DL(15, "QMI", 4, 0),
+ MHI_CHANNEL_CONFIG_UL(20, "IPCR", 8, 0),
+ MHI_CHANNEL_CONFIG_DL(21, "IPCR", 8, 0),
+ MHI_CHANNEL_CONFIG_UL(100, "IP_HW0", 128, 1),
+ MHI_CHANNEL_CONFIG_DL(101, "IP_HW0", 128, 2),
+};
+
+static const struct mhi_event_config modem_qcom_v1_mhi_events[] = {
+ /* first ring is control+data ring */
+ MHI_EVENT_CONFIG_CTRL(0),
+ /* Hardware channels request dedicated hardware event rings */
+ MHI_EVENT_CONFIG_HW_DATA(1, 100),
+ MHI_EVENT_CONFIG_HW_DATA(2, 101)
+};
+
+static const struct mhi_controller_config modem_qcom_v1_mhiv_config = {
+ .max_channels = 128,
+ .timeout_ms = 5000,
+ .num_channels = ARRAY_SIZE(modem_qcom_v1_mhi_channels),
+ .ch_cfg = modem_qcom_v1_mhi_channels,
+ .num_events = ARRAY_SIZE(modem_qcom_v1_mhi_events),
+ .event_cfg = modem_qcom_v1_mhi_events,
+};
+
+static const struct mhi_pci_dev_info mhi_qcom_sdx55_info = {
+ .name = "qcom-sdx55m",
+ .fw = "qcom/sdx55m/sbl1.mbn",
+ .edl = "qcom/sdx55m/edl.mbn",
+ .config = &modem_qcom_v1_mhiv_config,
+ .bar_num = MHI_PCI_DEFAULT_BAR_NUM,
+ .dma_data_width = 32
+};
+
+static const struct pci_device_id mhi_pci_id_table[] = {
+ { PCI_DEVICE(PCI_VENDOR_ID_QCOM, 0x0306),
+ .driver_data = (kernel_ulong_t) &mhi_qcom_sdx55_info },
+ { }
+};
+MODULE_DEVICE_TABLE(pci, mhi_pci_id_table);
+
+static int mhi_pci_read_reg(struct mhi_controller *mhi_cntrl,
+ void __iomem *addr, u32 *out)
+{
+ *out = readl(addr);
+ return 0;
+}
+
+static void mhi_pci_write_reg(struct mhi_controller *mhi_cntrl,
+ void __iomem *addr, u32 val)
+{
+ writel(val, addr);
+}
+
+static void mhi_pci_status_cb(struct mhi_controller *mhi_cntrl,
+ enum mhi_callback cb)
+{
+ /* Nothing to do for now */
+}
+
+static int mhi_pci_claim(struct mhi_controller *mhi_cntrl,
+ unsigned int bar_num, u64 dma_mask)
+{
+ struct pci_dev *pdev = to_pci_dev(mhi_cntrl->cntrl_dev);
+ int err;
+
+ err = pci_assign_resource(pdev, bar_num);
+ if (err)
+ return err;
+
+ err = pcim_enable_device(pdev);
+ if (err) {
+ dev_err(&pdev->dev, "failed to enable pci device: %d\n", err);
+ return err;
+ }
+
+ err = pcim_iomap_regions(pdev, 1 << bar_num, pci_name(pdev));
+ if (err) {
+ dev_err(&pdev->dev, "failed to map pci region: %d\n", err);
+ return err;
+ }
+ mhi_cntrl->regs = pcim_iomap_table(pdev)[bar_num];
+
+ err = pci_set_dma_mask(pdev, dma_mask);
+ if (err) {
+ dev_err(&pdev->dev, "Cannot set proper DMA mask\n");
+ return err;
+ }
+
+ err = pci_set_consistent_dma_mask(pdev, dma_mask);
+ if (err) {
+ dev_err(&pdev->dev, "set consistent dma mask failed\n");
+ return err;
+ }
+
+ pci_set_master(pdev);
+
+ return 0;
+}
+
+static int mhi_pci_get_irqs(struct mhi_controller *mhi_cntrl,
+ const struct mhi_controller_config *mhi_cntrl_config)
+{
+ struct pci_dev *pdev = to_pci_dev(mhi_cntrl->cntrl_dev);
+ int nr_vectors, i;
+ int *irq;
+
+ /*
+ * Alloc one MSI vector for BHI + one vector per event ring, ideally...
+ * No explicit pci_free_irq_vectors required, done by pcim_release.
+ */
+ mhi_cntrl->nr_irqs = 1 + mhi_cntrl_config->num_events;
+
+ nr_vectors = pci_alloc_irq_vectors(pdev, 1, mhi_cntrl->nr_irqs, PCI_IRQ_MSI);
+ if (nr_vectors < 0) {
+ dev_err(&pdev->dev, "Error allocating MSI vectors %d\n",
+ nr_vectors);
+ return nr_vectors;
+ }
+
+ if (nr_vectors < mhi_cntrl->nr_irqs) {
+ dev_warn(&pdev->dev, "Not enough MSI vectors (%d/%d), use shared MSI\n",
+ nr_vectors, mhi_cntrl_config->num_events);
+ }
+
+ irq = devm_kcalloc(&pdev->dev, mhi_cntrl->nr_irqs, sizeof(int), GFP_KERNEL);
+ if (!irq)
+ return -ENOMEM;
+
+ for (i = 0; i < mhi_cntrl->nr_irqs; i++) {
+ int vector = i >= nr_vectors ? (nr_vectors - 1) : i;
+
+ irq[i] = pci_irq_vector(pdev, vector);
+ }
+
+ mhi_cntrl->irq = irq;
+
+ return 0;
+}
+
+static int mhi_pci_runtime_get(struct mhi_controller *mhi_cntrl)
+{
+ /* no PM for now */
+ return 0;
+}
+
+static void mhi_pci_runtime_put(struct mhi_controller *mhi_cntrl)
+{
+ /* no PM for now */
+}
+
+static int mhi_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id)
+{
+ const struct mhi_pci_dev_info *info = (struct mhi_pci_dev_info *) id->driver_data;
+ const struct mhi_controller_config *mhi_cntrl_config;
+ struct mhi_controller *mhi_cntrl;
+ int err;
+
+ dev_dbg(&pdev->dev, "MHI PCI device found: %s\n", info->name);
+
+ mhi_cntrl = mhi_alloc_controller();
+ if (!mhi_cntrl)
+ return -ENOMEM;
+
+ mhi_cntrl_config = info->config;
+ mhi_cntrl->cntrl_dev = &pdev->dev;
+ mhi_cntrl->iova_start = 0;
+ mhi_cntrl->iova_stop = (dma_addr_t)DMA_BIT_MASK(info->dma_data_width);
+ mhi_cntrl->fw_image = info->fw;
+ mhi_cntrl->edl_image = info->edl;
+
+ mhi_cntrl->read_reg = mhi_pci_read_reg;
+ mhi_cntrl->write_reg = mhi_pci_write_reg;
+ mhi_cntrl->status_cb = mhi_pci_status_cb;
+ mhi_cntrl->runtime_get = mhi_pci_runtime_get;
+ mhi_cntrl->runtime_put = mhi_pci_runtime_put;
+
+ err = mhi_pci_claim(mhi_cntrl, info->bar_num, DMA_BIT_MASK(info->dma_data_width));
+ if (err)
+ goto err_release;
+
+ err = mhi_pci_get_irqs(mhi_cntrl, mhi_cntrl_config);
+ if (err)
+ goto err_release;
+
+ pci_set_drvdata(pdev, mhi_cntrl);
+
+ err = mhi_register_controller(mhi_cntrl, mhi_cntrl_config);
+ if (err)
+ goto err_release;
+
+ /* MHI bus does not power up the controller by default */
+ err = mhi_prepare_for_power_up(mhi_cntrl);
+ if (err) {
+ dev_err(&pdev->dev, "failed to prepare MHI controller\n");
+ goto err_unregister;
+ }
+
+ err = mhi_sync_power_up(mhi_cntrl);
+ if (err) {
+ dev_err(&pdev->dev, "failed to power up MHI controller\n");
+ goto err_unprepare;
+ }
+
+ return 0;
+
+err_unprepare:
+ mhi_unprepare_after_power_down(mhi_cntrl);
+err_unregister:
+ mhi_unregister_controller(mhi_cntrl);
+err_release:
+ mhi_free_controller(mhi_cntrl);
+
+ return err;
+}
+
+static void mhi_pci_remove(struct pci_dev *pdev)
+{
+ struct mhi_controller *mhi_cntrl = pci_get_drvdata(pdev);
+
+ mhi_power_down(mhi_cntrl, true);
+ mhi_unprepare_after_power_down(mhi_cntrl);
+ mhi_unregister_controller(mhi_cntrl);
+ mhi_free_controller(mhi_cntrl);
+}
+
+static struct pci_driver mhi_pci_driver = {
+ .name = "mhi-pci-generic",
+ .id_table = mhi_pci_id_table,
+ .probe = mhi_pci_probe,
+ .remove = mhi_pci_remove
+};
+module_pci_driver(mhi_pci_driver);
+
+MODULE_AUTHOR("Loic Poulain <loic.poulain@linaro.org>");
+MODULE_DESCRIPTION("Modem Host Interface (MHI) PCI controller driver");
+MODULE_LICENSE("GPL");