exposed through MHI channels and accessed by the corresponding MHI
device drivers (no out-of-band communication).
Signed-off-by: Loic Poulain <loic.poulain@xxxxxxxxxx>
---
v2: - remove useless delay.h include
- remove over-logging on error
- remove controller subdir
- rename to mhi_pci_modem.c
- Fix mhi_pci_probe exit path on error
- expand module description
- drop module version
v3: - Rename to mhi_pci_generic
- Add hardware accelerated IP channel (IPA)
- Added fw/edl names for sdx55m
drivers/bus/mhi/Kconfig | 9 ++
drivers/bus/mhi/Makefile | 3 +
drivers/bus/mhi/mhi_pci_generic.c | 332
++++++++++++++++++++++++++++++++++++++
3 files changed, 344 insertions(+)
create mode 100644 drivers/bus/mhi/mhi_pci_generic.c
diff --git a/drivers/bus/mhi/Kconfig b/drivers/bus/mhi/Kconfig
index e841c10..daa8528 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 Modem Host Interface (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 19e6443..d1a4ef3 100644
--- a/drivers/bus/mhi/Makefile
+++ b/drivers/bus/mhi/Makefile
@@ -1,2 +1,5 @@
# core layer
obj-y += core/
+
+obj-$(CONFIG_MHI_BUS_PCI_GENERIC) := mhi_pci_generic.o
+
diff --git a/drivers/bus/mhi/mhi_pci_generic.c
b/drivers/bus/mhi/mhi_pci_generic.c
new file mode 100644
index 0000000..03fcf76
--- /dev/null
+++ b/drivers/bus/mhi/mhi_pci_generic.c
@@ -0,0 +1,332 @@
+// 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@xxxxxxxxxx>
+ */
+
+#include <linux/device.h>
+#include <linux/mhi.h>
+#include <linux/module.h>
+#include <linux/pci.h>
+
+#define MHI_PCI_BAR_NUM 0
+
+struct mhi_pci_dev_info {
+ const struct mhi_controller_config *config;
+ const char *name;
+ const char *fw;
+ const char *edl;
+};
+
+#define MHI_CHANNEL_CONFIG_TX(cnum, cname, elems, event) \
+ { \
+ .num = cnum, \
+ .name = cname, \
+ .num_elements = elems, \
+ .event_ring = event, \
+ .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_RX(cnum, cname, elems, event) \
+ { \
+ .num = cnum, \
+ .name = cname, \
+ .num_elements = elems, \
+ .event_ring = event, \
+ .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(enum) \
+ { \
+ .num_elements = 64, \
+ .irq_moderation_ms = 5, \
+ .irq = enum, \
+ .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(enum) \
+ { \
+ .num_elements = 64, \
+ .irq_moderation_ms = 5, \
+ .irq = enum, \
+ .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(enum, cnum) \
+ { \
+ .num_elements = 64, \
+ .irq_moderation_ms = 5, \
+ .irq = enum, \
+ .priority = 1, \
+ .mode = MHI_DB_BRST_ENABLE, \
+ .data_type = MHI_ER_DATA, \
+ .hardware_event = true, \
+ .client_managed = false, \
+ .offload_channel = false, \
+ .channel = cnum, \
+ }
+
+static const struct mhi_channel_config modem_qcom_v1_mhi_channels[] =
{
+ MHI_CHANNEL_CONFIG_TX(12, "MBIM", 4, 0),
+ MHI_CHANNEL_CONFIG_RX(13, "MBIM", 4, 0),
+ MHI_CHANNEL_CONFIG_TX(14, "QMI", 4, 0),
+ MHI_CHANNEL_CONFIG_RX(15, "QMI", 4, 0),
+ MHI_CHANNEL_CONFIG_TX(20, "IPCR", 4, 0),
+ MHI_CHANNEL_CONFIG_RX(21, "IPCR", 4, 0),
+ MHI_CHANNEL_CONFIG_TX(100, "IP_HW0", 64, 1),
+ MHI_CHANNEL_CONFIG_RX(101, "IP_HW0", 64, 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_mhi_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_mhi_config
+};
+
+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 *mhic, void __iomem
*addr,
+ u32 *out)
+{
+ *out = readl(addr);
+ return 0;
+}
+
+static void mhi_pci_write_reg(struct mhi_controller *mhic, void
__iomem *addr,
+ u32 val)
+{
+ writel(val, addr);
+}
+
+static void mhi_pci_status_cb(struct mhi_controller *mhi_cntrl,
+ enum mhi_callback cb)
+{
+ return;
+}
+
+static int mhi_pci_claim(struct mhi_controller *mhic)
+{
+ struct pci_dev *pdev = to_pci_dev(mhic->cntrl_dev);
+ int err;
+
+ err = pci_assign_resource(pdev, MHI_PCI_BAR_NUM);
+ if (err) {
+ dev_err(&pdev->dev, "Cannot set proper DMA mask\n");
+ return err;
+ }
+
+ err = pci_set_consistent_dma_mask(pdev, DMA_BIT_MASK(32));
+ 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 *mhic,
+ const struct mhi_controller_config *mhic_config)
+{
+ struct pci_dev *pdev = to_pci_dev(mhic->cntrl_dev);
+ int num_vectors, i;
+ int *irq;
+
+ /*
+ * Alloc one MSI vector per event ring, ideally...
+ * No explicit pci_free_irq_vectors required, done by pcim_release
+ */
+ num_vectors = pci_alloc_irq_vectors(pdev, 1, mhic_config->num_events,
+ PCI_IRQ_MSI);
+ if (num_vectors < 0) {
+ dev_err(&pdev->dev, "Error allocating MSI vectors %d\n",
+ num_vectors);
+ return num_vectors;
+ }
+
+ if (num_vectors < mhic_config->num_events) {
+ dev_warn(&pdev->dev, "Not enough MSI vectors (%d/%d)\n",
+ num_vectors, mhic_config->num_events);
+ /* use shared IRQ */
+ }
+
+ irq = devm_kcalloc(&pdev->dev, mhic_config->num_events, sizeof(int),
+ GFP_KERNEL);
+ if (!irq)
+ return -ENOMEM;
+
+ for (i = 0; i < mhic_config->num_events; i++) {
+ int vector = i >= num_vectors ? (num_vectors - 1) : i;
+
+ irq[i] = pci_irq_vector(pdev, vector);
+ }
+
+ mhic->irq = irq;
+ mhic->nr_irqs = mhic_config->num_events;
+
+ 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 */
+ return;
+}
+
+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 *mhic_config;
+ struct mhi_controller *mhic;
+ int err;
+
+ dev_info(&pdev->dev, "MHI PCI device found: %s\n", info->name);