// SPDX-License-Identifier: GPL-2.0-only /* Copyright (c) 2019-2021, The Linux Foundation. All rights reserved. */ /* Copyright (c) 2021-2023 Qualcomm Innovation Center, Inc. All rights reserved. */ #include #include #include #include #include #include #include #include "mhi_controller.h" #include "qaic.h" #define MAX_RESET_TIME_SEC 25 static unsigned int mhi_timeout_ms = 2000; /* 2 sec default */ module_param(mhi_timeout_ms, uint, 0600); MODULE_PARM_DESC(mhi_timeout_ms, "MHI controller timeout value"); static const struct mhi_channel_config aic100_channels[] = { { .name = "QAIC_LOOPBACK", .num = 0, .num_elements = 32, .local_elements = 0, .event_ring = 0, .dir = DMA_TO_DEVICE, .ee_mask = MHI_CH_EE_AMSS, .pollcfg = 0, .doorbell = MHI_DB_BRST_DISABLE, .lpm_notify = false, .offload_channel = false, .doorbell_mode_switch = false, .auto_queue = false, .wake_capable = false, }, { .name = "QAIC_LOOPBACK", .num = 1, .num_elements = 32, .local_elements = 0, .event_ring = 0, .dir = DMA_FROM_DEVICE, .ee_mask = MHI_CH_EE_AMSS, .pollcfg = 0, .doorbell = MHI_DB_BRST_DISABLE, .lpm_notify = false, .offload_channel = false, .doorbell_mode_switch = false, .auto_queue = false, .wake_capable = false, }, { .name = "QAIC_SAHARA", .num = 2, .num_elements = 32, .local_elements = 0, .event_ring = 0, .dir = DMA_TO_DEVICE, .ee_mask = MHI_CH_EE_SBL, .pollcfg = 0, .doorbell = MHI_DB_BRST_DISABLE, .lpm_notify = false, .offload_channel = false, .doorbell_mode_switch = false, .auto_queue = false, .wake_capable = false, }, { .name = "QAIC_SAHARA", .num = 3, .num_elements = 32, .local_elements = 0, .event_ring = 0, .dir = DMA_FROM_DEVICE, .ee_mask = MHI_CH_EE_SBL, .pollcfg = 0, .doorbell = MHI_DB_BRST_DISABLE, .lpm_notify = false, .offload_channel = false, .doorbell_mode_switch = false, .auto_queue = false, .wake_capable = false, }, { .name = "QAIC_DIAG", .num = 4, .num_elements = 32, .local_elements = 0, .event_ring = 0, .dir = DMA_TO_DEVICE, .ee_mask = MHI_CH_EE_AMSS, .pollcfg = 0, .doorbell = MHI_DB_BRST_DISABLE, .lpm_notify = false, .offload_channel = false, .doorbell_mode_switch = false, .auto_queue = false, .wake_capable = false, }, { .name = "QAIC_DIAG", .num = 5, .num_elements = 32, .local_elements = 0, .event_ring = 0, .dir = DMA_FROM_DEVICE, .ee_mask = MHI_CH_EE_AMSS, .pollcfg = 0, .doorbell = MHI_DB_BRST_DISABLE, .lpm_notify = false, .offload_channel = false, .doorbell_mode_switch = false, .auto_queue = false, .wake_capable = false, }, { .name = "QAIC_SSR", .num = 6, .num_elements = 32, .local_elements = 0, .event_ring = 0, .dir = DMA_TO_DEVICE, .ee_mask = MHI_CH_EE_AMSS, .pollcfg = 0, .doorbell = MHI_DB_BRST_DISABLE, .lpm_notify = false, .offload_channel = false, .doorbell_mode_switch = false, .auto_queue = false, .wake_capable = false, }, { .name = "QAIC_SSR", .num = 7, .num_elements = 32, .local_elements = 0, .event_ring = 0, .dir = DMA_FROM_DEVICE, .ee_mask = MHI_CH_EE_AMSS, .pollcfg = 0, .doorbell = MHI_DB_BRST_DISABLE, .lpm_notify = false, .offload_channel = false, .doorbell_mode_switch = false, .auto_queue = false, .wake_capable = false, }, { .name = "QAIC_QDSS", .num = 8, .num_elements = 32, .local_elements = 0, .event_ring = 0, .dir = DMA_TO_DEVICE, .ee_mask = MHI_CH_EE_AMSS, .pollcfg = 0, .doorbell = MHI_DB_BRST_DISABLE, .lpm_notify = false, .offload_channel = false, .doorbell_mode_switch = false, .auto_queue = false, .wake_capable = false, }, { .name = "QAIC_QDSS", .num = 9, .num_elements = 32, .local_elements = 0, .event_ring = 0, .dir = DMA_FROM_DEVICE, .ee_mask = MHI_CH_EE_AMSS, .pollcfg = 0, .doorbell = MHI_DB_BRST_DISABLE, .lpm_notify = false, .offload_channel = false, .doorbell_mode_switch = false, .auto_queue = false, .wake_capable = false, }, { .name = "QAIC_CONTROL", .num = 10, .num_elements = 128, .local_elements = 0, .event_ring = 0, .dir = DMA_TO_DEVICE, .ee_mask = MHI_CH_EE_AMSS, .pollcfg = 0, .doorbell = MHI_DB_BRST_DISABLE, .lpm_notify = false, .offload_channel = false, .doorbell_mode_switch = false, .auto_queue = false, .wake_capable = false, }, { .name = "QAIC_CONTROL", .num = 11, .num_elements = 128, .local_elements = 0, .event_ring = 0, .dir = DMA_FROM_DEVICE, .ee_mask = MHI_CH_EE_AMSS, .pollcfg = 0, .doorbell = MHI_DB_BRST_DISABLE, .lpm_notify = false, .offload_channel = false, .doorbell_mode_switch = false, .auto_queue = false, .wake_capable = false, }, { .name = "QAIC_LOGGING", .num = 12, .num_elements = 32, .local_elements = 0, .event_ring = 0, .dir = DMA_TO_DEVICE, .ee_mask = MHI_CH_EE_SBL, .pollcfg = 0, .doorbell = MHI_DB_BRST_DISABLE, .lpm_notify = false, .offload_channel = false, .doorbell_mode_switch = false, .auto_queue = false, .wake_capable = false, }, { .name = "QAIC_LOGGING", .num = 13, .num_elements = 32, .local_elements = 0, .event_ring = 0, .dir = DMA_FROM_DEVICE, .ee_mask = MHI_CH_EE_SBL, .pollcfg = 0, .doorbell = MHI_DB_BRST_DISABLE, .lpm_notify = false, .offload_channel = false, .doorbell_mode_switch = false, .auto_queue = false, .wake_capable = false, }, { .name = "QAIC_STATUS", .num = 14, .num_elements = 32, .local_elements = 0, .event_ring = 0, .dir = DMA_TO_DEVICE, .ee_mask = MHI_CH_EE_AMSS, .pollcfg = 0, .doorbell = MHI_DB_BRST_DISABLE, .lpm_notify = false, .offload_channel = false, .doorbell_mode_switch = false, .auto_queue = false, .wake_capable = false, }, { .name = "QAIC_STATUS", .num = 15, .num_elements = 32, .local_elements = 0, .event_ring = 0, .dir = DMA_FROM_DEVICE, .ee_mask = MHI_CH_EE_AMSS, .pollcfg = 0, .doorbell = MHI_DB_BRST_DISABLE, .lpm_notify = false, .offload_channel = false, .doorbell_mode_switch = false, .auto_queue = false, .wake_capable = false, }, { .name = "QAIC_TELEMETRY", .num = 16, .num_elements = 32, .local_elements = 0, .event_ring = 0, .dir = DMA_TO_DEVICE, .ee_mask = MHI_CH_EE_AMSS, .pollcfg = 0, .doorbell = MHI_DB_BRST_DISABLE, .lpm_notify = false, .offload_channel = false, .doorbell_mode_switch = false, .auto_queue = false, .wake_capable = false, }, { .name = "QAIC_TELEMETRY", .num = 17, .num_elements = 32, .local_elements = 0, .event_ring = 0, .dir = DMA_FROM_DEVICE, .ee_mask = MHI_CH_EE_AMSS, .pollcfg = 0, .doorbell = MHI_DB_BRST_DISABLE, .lpm_notify = false, .offload_channel = false, .doorbell_mode_switch = false, .auto_queue = false, .wake_capable = false, }, { .name = "QAIC_DEBUG", .num = 18, .num_elements = 32, .local_elements = 0, .event_ring = 0, .dir = DMA_TO_DEVICE, .ee_mask = MHI_CH_EE_AMSS, .pollcfg = 0, .doorbell = MHI_DB_BRST_DISABLE, .lpm_notify = false, .offload_channel = false, .doorbell_mode_switch = false, .auto_queue = false, .wake_capable = false, }, { .name = "QAIC_DEBUG", .num = 19, .num_elements = 32, .local_elements = 0, .event_ring = 0, .dir = DMA_FROM_DEVICE, .ee_mask = MHI_CH_EE_AMSS, .pollcfg = 0, .doorbell = MHI_DB_BRST_DISABLE, .lpm_notify = false, .offload_channel = false, .doorbell_mode_switch = false, .auto_queue = false, .wake_capable = false, }, { .name = "QAIC_TIMESYNC", .num = 20, .num_elements = 32, .local_elements = 0, .event_ring = 0, .dir = DMA_TO_DEVICE, .ee_mask = MHI_CH_EE_SBL, .pollcfg = 0, .doorbell = MHI_DB_BRST_DISABLE, .lpm_notify = false, .offload_channel = false, .doorbell_mode_switch = false, .auto_queue = false, .wake_capable = false, }, { .name = "QAIC_TIMESYNC", .num = 21, .num_elements = 32, .local_elements = 0, .event_ring = 0, .dir = DMA_FROM_DEVICE, .ee_mask = MHI_CH_EE_SBL, .pollcfg = 0, .doorbell = MHI_DB_BRST_DISABLE, .lpm_notify = false, .offload_channel = false, .doorbell_mode_switch = false, .auto_queue = false, .wake_capable = false, }, { .name = "QAIC_TIMESYNC_PERIODIC", .num = 22, .num_elements = 32, .local_elements = 0, .event_ring = 0, .dir = DMA_TO_DEVICE, .ee_mask = MHI_CH_EE_AMSS, .pollcfg = 0, .doorbell = MHI_DB_BRST_DISABLE, .lpm_notify = false, .offload_channel = false, .doorbell_mode_switch = false, .auto_queue = false, .wake_capable = false, }, { .name = "QAIC_TIMESYNC_PERIODIC", .num = 23, .num_elements = 32, .local_elements = 0, .event_ring = 0, .dir = DMA_FROM_DEVICE, .ee_mask = MHI_CH_EE_AMSS, .pollcfg = 0, .doorbell = MHI_DB_BRST_DISABLE, .lpm_notify = false, .offload_channel = false, .doorbell_mode_switch = false, .auto_queue = false, .wake_capable = false, }, }; static struct mhi_event_config aic100_events[] = { { .num_elements = 32, .irq_moderation_ms = 0, .irq = 0, .channel = U32_MAX, .priority = 1, .mode = MHI_DB_BRST_DISABLE, .data_type = MHI_ER_CTRL, .hardware_event = false, .client_managed = false, .offload_channel = false, }, }; static struct mhi_controller_config aic100_config = { .max_channels = 128, .timeout_ms = 0, /* controlled by mhi_timeout */ .buf_len = 0, .num_channels = ARRAY_SIZE(aic100_channels), .ch_cfg = aic100_channels, .num_events = ARRAY_SIZE(aic100_events), .event_cfg = aic100_events, .use_bounce_buf = false, .m2_no_db = false, }; static int mhi_read_reg(struct mhi_controller *mhi_cntrl, void __iomem *addr, u32 *out) { u32 tmp; /* * SOC_HW_VERSION quirk * The SOC_HW_VERSION register (offset 0x224) is not reliable and * may contain uninitialized values, including 0xFFFFFFFF. This could * cause a false positive link down error. Instead, intercept any * reads and provide the correct value of the register. */ if (addr - mhi_cntrl->regs == 0x224) { *out = 0x60110200; return 0; } tmp = readl_relaxed(addr); if (tmp == U32_MAX) return -EIO; *out = tmp; return 0; } static void mhi_write_reg(struct mhi_controller *mhi_cntrl, void __iomem *addr, u32 val) { writel_relaxed(val, addr); } static int mhi_runtime_get(struct mhi_controller *mhi_cntrl) { return 0; } static void mhi_runtime_put(struct mhi_controller *mhi_cntrl) { } static void mhi_status_cb(struct mhi_controller *mhi_cntrl, enum mhi_callback reason) { struct qaic_device *qdev = pci_get_drvdata(to_pci_dev(mhi_cntrl->cntrl_dev)); /* this event occurs in atomic context */ if (reason == MHI_CB_FATAL_ERROR) pci_err(qdev->pdev, "Fatal error received from device. Attempting to recover\n"); /* this event occurs in non-atomic context */ if (reason == MHI_CB_SYS_ERROR) qaic_dev_reset_clean_local_state(qdev); } static int mhi_reset_and_async_power_up(struct mhi_controller *mhi_cntrl) { u8 time_sec = 1; int current_ee; int ret; /* Reset the device to bring the device in PBL EE */ mhi_soc_reset(mhi_cntrl); /* * Keep checking the execution environment(EE) after every 1 second * interval. */ do { msleep(1000); current_ee = mhi_get_exec_env(mhi_cntrl); } while (current_ee != MHI_EE_PBL && time_sec++ <= MAX_RESET_TIME_SEC); /* If the device is in PBL EE retry power up */ if (current_ee == MHI_EE_PBL) ret = mhi_async_power_up(mhi_cntrl); else ret = -EIO; return ret; } struct mhi_controller *qaic_mhi_register_controller(struct pci_dev *pci_dev, void __iomem *mhi_bar, int mhi_irq, bool shared_msi) { struct mhi_controller *mhi_cntrl; int ret; mhi_cntrl = devm_kzalloc(&pci_dev->dev, sizeof(*mhi_cntrl), GFP_KERNEL); if (!mhi_cntrl) return ERR_PTR(-ENOMEM); mhi_cntrl->cntrl_dev = &pci_dev->dev; /* * Covers the entire possible physical ram region. Remote side is * going to calculate a size of this range, so subtract 1 to prevent * rollover. */ mhi_cntrl->iova_start = 0; mhi_cntrl->iova_stop = PHYS_ADDR_MAX - 1; mhi_cntrl->status_cb = mhi_status_cb; mhi_cntrl->runtime_get = mhi_runtime_get; mhi_cntrl->runtime_put = mhi_runtime_put; mhi_cntrl->read_reg = mhi_read_reg; mhi_cntrl->write_reg = mhi_write_reg; mhi_cntrl->regs = mhi_bar; mhi_cntrl->reg_len = SZ_4K; mhi_cntrl->nr_irqs = 1; mhi_cntrl->irq = devm_kmalloc(&pci_dev->dev, sizeof(*mhi_cntrl->irq), GFP_KERNEL); if (!mhi_cntrl->irq) return ERR_PTR(-ENOMEM); mhi_cntrl->irq[0] = mhi_irq; if (shared_msi) /* MSI shared with data path, no IRQF_NO_SUSPEND */ mhi_cntrl->irq_flags = IRQF_SHARED; mhi_cntrl->fw_image = "qcom/aic100/sbl.bin"; /* use latest configured timeout */ aic100_config.timeout_ms = mhi_timeout_ms; ret = mhi_register_controller(mhi_cntrl, &aic100_config); if (ret) { pci_err(pci_dev, "mhi_register_controller failed %d\n", ret); return ERR_PTR(ret); } ret = mhi_prepare_for_power_up(mhi_cntrl); if (ret) { pci_err(pci_dev, "mhi_prepare_for_power_up failed %d\n", ret); goto prepare_power_up_fail; } ret = mhi_async_power_up(mhi_cntrl); /* * If EIO is returned it is possible that device is in SBL EE, which is * undesired. SOC reset the device and try to power up again. */ if (ret == -EIO && MHI_EE_SBL == mhi_get_exec_env(mhi_cntrl)) { pci_err(pci_dev, "Found device in SBL at MHI init. Attempting a reset.\n"); ret = mhi_reset_and_async_power_up(mhi_cntrl); } if (ret) { pci_err(pci_dev, "mhi_async_power_up failed %d\n", ret); goto power_up_fail; } return mhi_cntrl; power_up_fail: mhi_unprepare_after_power_down(mhi_cntrl); prepare_power_up_fail: mhi_unregister_controller(mhi_cntrl); return ERR_PTR(ret); } void qaic_mhi_free_controller(struct mhi_controller *mhi_cntrl, bool link_up) { mhi_power_down(mhi_cntrl, link_up); mhi_unprepare_after_power_down(mhi_cntrl); mhi_unregister_controller(mhi_cntrl); } void qaic_mhi_start_reset(struct mhi_controller *mhi_cntrl) { mhi_power_down(mhi_cntrl, true); } void qaic_mhi_reset_done(struct mhi_controller *mhi_cntrl) { struct pci_dev *pci_dev = container_of(mhi_cntrl->cntrl_dev, struct pci_dev, dev); int ret; ret = mhi_async_power_up(mhi_cntrl); if (ret) pci_err(pci_dev, "mhi_async_power_up failed after reset %d\n", ret); }