// SPDX-License-Identifier: GPL-2.0-only /* Copyright (c) 2018-2021, The Linux Foundation. All rights reserved.*/ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "mhi_qcom.h" #define MHI_CHANNEL_CONFIG_UL(ch_num, ch_name, elems, ev_ring, ee, \ dbmode, lpm, poll, offload, modeswitch, \ ch_type) \ { \ .dir = DMA_TO_DEVICE, \ .num = ch_num, \ .name = ch_name, \ .num_elements = elems, \ .event_ring = ev_ring, \ .ee_mask = BIT(ee), \ .pollcfg = poll, \ .doorbell = dbmode, \ .lpm_notify = lpm, \ .offload_channel = offload, \ .doorbell_mode_switch = modeswitch, \ .wake_capable = false, \ .auto_queue = false, \ .local_elements = 0, \ .type = ch_type, \ } #define MHI_CHANNEL_CONFIG_DL(ch_num, ch_name, elems, ev_ring, ee, \ dbmode, lpm, poll, offload, modeswitch, \ wake, autoq, local_el, ch_type) \ { \ .dir = DMA_FROM_DEVICE, \ .num = ch_num, \ .name = ch_name, \ .num_elements = elems, \ .event_ring = ev_ring, \ .ee_mask = BIT(ee), \ .pollcfg = poll, \ .doorbell = dbmode, \ .lpm_notify = lpm, \ .offload_channel = offload, \ .doorbell_mode_switch = modeswitch, \ .wake_capable = wake, \ .auto_queue = autoq, \ .local_elements = local_el, \ .type = ch_type, \ } #define MHI_EVENT_CONFIG(ev_ring, ev_irq, type, num_elems, int_mod, \ prio, dbmode, hw, cl_manage, offload, ch_num) \ { \ .num_elements = num_elems, \ .irq_moderation_ms = int_mod, \ .irq = ev_irq, \ .priority = prio, \ .mode = dbmode, \ .data_type = type, \ .hardware_event = hw, \ .client_managed = cl_manage, \ .offload_channel = offload, \ .channel = ch_num, \ } static const struct mhi_channel_config modem_qcom_sdx65_mhi_channels[] = { /* SBL channels */ MHI_CHANNEL_CONFIG_UL(2, "SAHARA", 128, 1, MHI_EE_SBL, MHI_DB_BRST_DISABLE, false, 0, false, false, 0), MHI_CHANNEL_CONFIG_DL(3, "SAHARA", 128, 1, MHI_EE_SBL, MHI_DB_BRST_DISABLE, false, 0, false, false, false, false, 0, 0), MHI_CHANNEL_CONFIG_DL(25, "BL", 32, 1, MHI_EE_SBL, MHI_DB_BRST_DISABLE, false, 0, false, false, false, false, 0, 0), /* AMSS channels */ MHI_CHANNEL_CONFIG_UL(0, "LOOPBACK", 64, 2, MHI_EE_AMSS, MHI_DB_BRST_DISABLE, false, 0, false, false, 0), MHI_CHANNEL_CONFIG_DL(1, "LOOPBACK", 64, 2, MHI_EE_AMSS, MHI_DB_BRST_DISABLE, false, 0, false, false, false, false, 0, 0), MHI_CHANNEL_CONFIG_UL(4, "DIAG", 64, 1, MHI_EE_AMSS, MHI_DB_BRST_DISABLE, false, 0, false, false, 0), MHI_CHANNEL_CONFIG_DL(5, "DIAG", 64, 3, MHI_EE_AMSS, MHI_DB_BRST_DISABLE, false, 0, false, false, false, false, 0, 0), MHI_CHANNEL_CONFIG_UL(8, "QDSS", 64, 1, MHI_EE_AMSS, MHI_DB_BRST_DISABLE, false, 0, false, false, 0), MHI_CHANNEL_CONFIG_DL(9, "QDSS", 64, 1, MHI_EE_AMSS, MHI_DB_BRST_DISABLE, false, 0, false, false, false, false, 0, 0), MHI_CHANNEL_CONFIG_UL(10, "EFS", 64, 1, MHI_EE_AMSS, MHI_DB_BRST_DISABLE, false, 0, false, false, 0), /* wake-capable */ MHI_CHANNEL_CONFIG_DL(11, "EFS", 64, 1, MHI_EE_AMSS, MHI_DB_BRST_DISABLE, false, 0, false, false, true, false, 0, 0), MHI_CHANNEL_CONFIG_UL(14, "QMI0", 64, 1, MHI_EE_AMSS, MHI_DB_BRST_DISABLE, false, 0, false, false, 0), MHI_CHANNEL_CONFIG_DL(15, "QMI0", 64, 2, MHI_EE_AMSS, MHI_DB_BRST_DISABLE, false, 0, false, false, false, false, 0, 0), MHI_CHANNEL_CONFIG_UL(16, "QMI1", 64, 3, MHI_EE_AMSS, MHI_DB_BRST_DISABLE, false, 0, false, false, 0), MHI_CHANNEL_CONFIG_DL(17, "QMI1", 64, 3, MHI_EE_AMSS, MHI_DB_BRST_DISABLE, false, 0, false, false, false, false, 0, 0), MHI_CHANNEL_CONFIG_UL(18, "IP_CTRL", 64, 1, MHI_EE_AMSS, MHI_DB_BRST_DISABLE, false, 0, false, false, 0), /* auto-queue */ MHI_CHANNEL_CONFIG_DL(19, "IP_CTRL", 64, 1, MHI_EE_AMSS, MHI_DB_BRST_DISABLE, false, 0, false, false, false, true, 0, 0), MHI_CHANNEL_CONFIG_UL(20, "IPCR", 32, 2, MHI_EE_AMSS, MHI_DB_BRST_DISABLE, false, 0, false, false, 0), /* auto-queue */ MHI_CHANNEL_CONFIG_DL(21, "IPCR", 32, 2, MHI_EE_AMSS, MHI_DB_BRST_DISABLE, false, 0, false, false, false, true, 0, 0), MHI_CHANNEL_CONFIG_UL(26, "DCI", 64, 3, MHI_EE_AMSS, MHI_DB_BRST_DISABLE, false, 0, false, false, 0), MHI_CHANNEL_CONFIG_DL(27, "DCI", 64, 3, MHI_EE_AMSS, MHI_DB_BRST_DISABLE, false, 0, false, false, false, false, 0, 0), MHI_CHANNEL_CONFIG_UL(32, "DUN", 64, 3, MHI_EE_AMSS, MHI_DB_BRST_DISABLE, false, 0, false, false, 0), MHI_CHANNEL_CONFIG_DL(33, "DUN", 64, 3, MHI_EE_AMSS, MHI_DB_BRST_DISABLE, false, 0, false, false, false, false, 0, 0), MHI_CHANNEL_CONFIG_UL(80, "AUDIO_VOICE_0", 32, 1, MHI_EE_AMSS, MHI_DB_BRST_DISABLE, false, 0, false, false, 0), MHI_CHANNEL_CONFIG_DL(81, "AUDIO_VOICE_0", 32, 1, MHI_EE_AMSS, MHI_DB_BRST_DISABLE, false, 0, false, false, false, false, 0, 0), MHI_CHANNEL_CONFIG_UL(100, "IP_HW0", 512, 6, MHI_EE_AMSS, MHI_DB_BRST_ENABLE, false, 0, false, true, 0), MHI_CHANNEL_CONFIG_DL(101, "IP_HW0", 512, 7, MHI_EE_AMSS, MHI_DB_BRST_ENABLE, false, 0, false, false, false, false, 0, 0), MHI_CHANNEL_CONFIG_DL(102, "IP_HW_ADPL", 1, 8, MHI_EE_AMSS, MHI_DB_BRST_DISABLE, true, 0, true, false, false, false, 0, 0), MHI_CHANNEL_CONFIG_DL(103, "IP_HW_QDSS", 1, 9, MHI_EE_AMSS, MHI_DB_BRST_DISABLE, false, 0, false, false, false, false, 0, 0), MHI_CHANNEL_CONFIG_DL(104, "IP_HW0_RSC", 512, 7, MHI_EE_AMSS, MHI_DB_BRST_ENABLE, false, 0, false, false, false, false, 3078, MHI_CH_TYPE_INBOUND_COALESCED), MHI_CHANNEL_CONFIG_UL(105, "RMNET_DATA_LL", 512, 10, MHI_EE_AMSS, MHI_DB_BRST_ENABLE, false, 0, false, true, 0), MHI_CHANNEL_CONFIG_DL(106, "RMNET_DATA_LL", 512, 10, MHI_EE_AMSS, MHI_DB_BRST_ENABLE, false, 0, false, false, false, false, 0, 0), MHI_CHANNEL_CONFIG_UL(107, "IP_HW_MHIP_1", 1, 11, MHI_EE_AMSS, MHI_DB_BRST_DISABLE, false, 0, true, true, 0), MHI_CHANNEL_CONFIG_DL(108, "IP_HW_MHIP_1", 1, 12, MHI_EE_AMSS, MHI_DB_BRST_DISABLE, true, 0, true, false, false, false, 0, 0), MHI_CHANNEL_CONFIG_UL(109, "RMNET_CTL", 128, 13, MHI_EE_AMSS, MHI_DB_BRST_ENABLE, false, 0, false, true, 0), MHI_CHANNEL_CONFIG_DL(110, "RMNET_CTL", 128, 14, MHI_EE_AMSS, MHI_DB_BRST_ENABLE, false, 0, false, false, false, false, 0, 0), }; static const struct mhi_event_config modem_qcom_sdx65_mhi_events[] = { MHI_EVENT_CONFIG(0, 1, MHI_ER_CTRL, 64, 0, MHI_ER_PRIORITY_HI_NOSLEEP, MHI_DB_BRST_DISABLE, false, false, false, 0), MHI_EVENT_CONFIG(1, 2, MHI_ER_DATA, 256, 0, MHI_ER_PRIORITY_DEFAULT_NOSLEEP, MHI_DB_BRST_DISABLE, false, false, false, 0), MHI_EVENT_CONFIG(2, 3, MHI_ER_DATA, 256, 0, MHI_ER_PRIORITY_DEFAULT_NOSLEEP, MHI_DB_BRST_DISABLE, false, false, false, 0), MHI_EVENT_CONFIG(3, 4, MHI_ER_DATA, 256, 0, MHI_ER_PRIORITY_DEFAULT_NOSLEEP, MHI_DB_BRST_DISABLE, false, false, false, 0), MHI_EVENT_CONFIG(4, 5, MHI_ER_BW_SCALE, 64, 0, MHI_ER_PRIORITY_HI_SLEEP, MHI_DB_BRST_DISABLE, false, false, false, 0), MHI_EVENT_CONFIG(5, 6, MHI_ER_TIMESYNC, 64, 0, MHI_ER_PRIORITY_HI_SLEEP, MHI_DB_BRST_DISABLE, false, false, false, 0), /* Hardware channels request dedicated hardware event rings */ MHI_EVENT_CONFIG(6, 7, MHI_ER_DATA, 1024, 5, MHI_ER_PRIORITY_DEFAULT_NOSLEEP, MHI_DB_BRST_ENABLE, true, false, false, 100), MHI_EVENT_CONFIG(7, 7, MHI_ER_DATA, 2048, 5, MHI_ER_PRIORITY_DEFAULT_NOSLEEP, MHI_DB_BRST_ENABLE, true, true, false, 101), MHI_EVENT_CONFIG(8, 8, MHI_ER_DATA, 0, 0, MHI_ER_PRIORITY_DEFAULT_NOSLEEP, MHI_DB_BRST_ENABLE, true, true, true, 102), MHI_EVENT_CONFIG(9, 9, MHI_ER_DATA, 1024, 5, MHI_ER_PRIORITY_DEFAULT_NOSLEEP, MHI_DB_BRST_DISABLE, true, false, false, 103), MHI_EVENT_CONFIG(10, 10, MHI_ER_DATA, 1024, 1, MHI_ER_PRIORITY_HI_NOSLEEP, MHI_DB_BRST_ENABLE, true, false, false, 0), MHI_EVENT_CONFIG(11, 11, MHI_ER_DATA, 0, 0, MHI_ER_PRIORITY_DEFAULT_NOSLEEP, MHI_DB_BRST_ENABLE, true, true, true, 107), MHI_EVENT_CONFIG(12, 12, MHI_ER_DATA, 0, 0, MHI_ER_PRIORITY_DEFAULT_NOSLEEP, MHI_DB_BRST_ENABLE, true, true, true, 108), MHI_EVENT_CONFIG(13, 13, MHI_ER_DATA, 1024, 1, MHI_ER_PRIORITY_HI_NOSLEEP, MHI_DB_BRST_DISABLE, true, false, false, 109), MHI_EVENT_CONFIG(14, 15, MHI_ER_DATA, 1024, 0, MHI_ER_PRIORITY_HI_NOSLEEP, MHI_DB_BRST_DISABLE, true, false, false, 110), }; static const struct mhi_controller_config modem_qcom_sdx65_mhi_config = { .max_channels = 128, .timeout_ms = 2000, .buf_len = 0x8000, .num_channels = ARRAY_SIZE(modem_qcom_sdx65_mhi_channels), .ch_cfg = modem_qcom_sdx65_mhi_channels, .num_events = ARRAY_SIZE(modem_qcom_sdx65_mhi_events), .event_cfg = modem_qcom_sdx65_mhi_events, }; static const struct mhi_pci_dev_info mhi_qcom_sdx65_info = { .device_id = 0x0308, .name = "esoc0", .fw_image = "sdx65m/xbl.elf", .edl_image = "sdx65m/edl.mbn", .config = &modem_qcom_sdx65_mhi_config, .bar_num = MHI_PCI_BAR_NUM, .dma_data_width = 64, .allow_m1 = false, .skip_forced_suspend = true, .sfr_support = true, .timesync = true, .drv_support = false, }; static const struct mhi_pci_dev_info mhi_qcom_debug_info = { .device_id = MHI_PCIE_DEBUG_ID, .name = "qcom-debug", .fw_image = "debug.mbn", .edl_image = "debug.mbn", .config = &modem_qcom_sdx65_mhi_config, .bar_num = MHI_PCI_BAR_NUM, .dma_data_width = 64, .allow_m1 = true, .skip_forced_suspend = true, .sfr_support = false, .timesync = false, .drv_support = false, }; static const struct pci_device_id mhi_pcie_device_id[] = { { PCI_DEVICE(MHI_PCIE_VENDOR_ID, 0x0308), .driver_data = (kernel_ulong_t) &mhi_qcom_sdx65_info }, { PCI_DEVICE(MHI_PCIE_VENDOR_ID, MHI_PCIE_DEBUG_ID), .driver_data = (kernel_ulong_t) &mhi_qcom_debug_info }, { } }; MODULE_DEVICE_TABLE(pci, mhi_pcie_device_id); static enum mhi_debug_mode debug_mode; const char * const mhi_debug_mode_str[MHI_DEBUG_MODE_MAX] = { [MHI_DEBUG_OFF] = "Debug mode OFF", [MHI_DEBUG_ON] = "Debug mode ON", [MHI_DEBUG_NO_LPM] = "Debug mode - no LPM", }; const char * const mhi_suspend_mode_str[MHI_SUSPEND_MODE_MAX] = { [MHI_ACTIVE_STATE] = "Active", [MHI_DEFAULT_SUSPEND] = "Default", [MHI_FAST_LINK_OFF] = "Fast Link Off", [MHI_FAST_LINK_ON] = "Fast Link On", }; static int mhi_qcom_power_up(struct mhi_controller *mhi_cntrl); static int mhi_link_status(struct mhi_controller *mhi_cntrl) { struct pci_dev *pci_dev = to_pci_dev(mhi_cntrl->cntrl_dev); u16 dev_id; int ret; /* try reading device IDs, a mismatch could indicate a link down */ ret = pci_read_config_word(pci_dev, PCI_DEVICE_ID, &dev_id); return (ret || dev_id != pci_dev->device) ? -EIO : 0; } static int mhi_qcom_read_reg(struct mhi_controller *mhi_cntrl, void __iomem *addr, u32 *out) { u32 tmp = readl_relaxed(addr); if (PCI_INVALID_READ(tmp) && mhi_link_status(mhi_cntrl)) return -EIO; *out = tmp; return 0; } static void mhi_qcom_write_reg(struct mhi_controller *mhi_cntrl, void __iomem *addr, u32 val) { writel_relaxed(val, addr); } static u64 mhi_qcom_time_get(struct mhi_controller *mhi_cntrl) { return mhi_arch_time_get(mhi_cntrl); } static int mhi_qcom_lpm_disable(struct mhi_controller *mhi_cntrl) { return mhi_arch_link_lpm_disable(mhi_cntrl); } static int mhi_qcom_lpm_enable(struct mhi_controller *mhi_cntrl) { return mhi_arch_link_lpm_enable(mhi_cntrl); } static int mhi_debugfs_power_up(void *data, u64 val) { struct mhi_controller *mhi_cntrl = data; struct mhi_qcom_priv *mhi_priv = mhi_controller_get_privdata(mhi_cntrl); int ret; if (!val || mhi_priv->powered_on) return -EINVAL; MHI_CNTRL_LOG("Trigger power up from %s\n", TO_MHI_DEBUG_MODE_STR(debug_mode)); ret = mhi_qcom_power_up(mhi_cntrl); if (ret) { MHI_CNTRL_ERR("Failed to power up MHI\n"); return ret; } mhi_priv->powered_on = true; return ret; } DEFINE_DEBUGFS_ATTRIBUTE(debugfs_power_up_fops, NULL, mhi_debugfs_power_up, "%llu\n"); static int mhi_debugfs_trigger_m0(void *data, u64 val) { struct mhi_controller *mhi_cntrl = data; MHI_CNTRL_LOG("Trigger M3 Exit\n"); pm_runtime_get(mhi_cntrl->cntrl_dev); pm_runtime_put(mhi_cntrl->cntrl_dev); return 0; } DEFINE_DEBUGFS_ATTRIBUTE(debugfs_trigger_m0_fops, NULL, mhi_debugfs_trigger_m0, "%llu\n"); static int mhi_debugfs_trigger_m3(void *data, u64 val) { struct mhi_controller *mhi_cntrl = data; MHI_CNTRL_LOG("Trigger M3 Entry\n"); pm_runtime_mark_last_busy(mhi_cntrl->cntrl_dev); pm_request_autosuspend(mhi_cntrl->cntrl_dev); return 0; } DEFINE_DEBUGFS_ATTRIBUTE(debugfs_trigger_m3_fops, NULL, mhi_debugfs_trigger_m3, "%llu\n"); static int mhi_debugfs_disable_pci_lpm_get(void *data, u64 *val) { struct mhi_controller *mhi_cntrl = data; struct mhi_qcom_priv *mhi_priv = mhi_controller_get_privdata(mhi_cntrl); *val = mhi_priv->disable_pci_lpm; MHI_CNTRL_LOG("PCIe low power modes (D3 hot/cold) are %s\n", mhi_priv->disable_pci_lpm ? "Disabled" : "Enabled"); return 0; } static int mhi_debugfs_disable_pci_lpm_set(void *data, u64 val) { struct mhi_controller *mhi_cntrl = data; struct mhi_qcom_priv *mhi_priv = mhi_controller_get_privdata(mhi_cntrl); mutex_lock(&mhi_cntrl->pm_mutex); mhi_priv->disable_pci_lpm = val ? true : false; mutex_unlock(&mhi_cntrl->pm_mutex); MHI_CNTRL_LOG("%s PCIe low power modes (D3 hot/cold)\n", val ? "Disabled" : "Enabled"); return 0; } DEFINE_DEBUGFS_ATTRIBUTE(debugfs_pci_lpm_fops, mhi_debugfs_disable_pci_lpm_get, mhi_debugfs_disable_pci_lpm_set, "%llu\n"); void mhi_deinit_pci_dev(struct pci_dev *pci_dev, const struct mhi_pci_dev_info *dev_info) { struct mhi_controller *mhi_cntrl = dev_get_drvdata(&pci_dev->dev); if (!mhi_cntrl) return; pm_runtime_mark_last_busy(mhi_cntrl->cntrl_dev); pm_runtime_dont_use_autosuspend(mhi_cntrl->cntrl_dev); pm_runtime_disable(mhi_cntrl->cntrl_dev); pci_free_irq_vectors(pci_dev); kfree(mhi_cntrl->irq); mhi_cntrl->irq = NULL; iounmap(mhi_cntrl->regs); mhi_cntrl->regs = NULL; mhi_cntrl->reg_len = 0; mhi_cntrl->nr_irqs = 0; pci_clear_master(pci_dev); pci_release_region(pci_dev, dev_info->bar_num); pci_disable_device(pci_dev); } static int mhi_init_pci_dev(struct pci_dev *pci_dev, const struct mhi_pci_dev_info *dev_info) { struct mhi_controller *mhi_cntrl = dev_get_drvdata(&pci_dev->dev); phys_addr_t base; int ret; int i; if (!mhi_cntrl) return -ENODEV; ret = pci_assign_resource(pci_dev, dev_info->bar_num); if (ret) { MHI_CNTRL_ERR("Error assign pci resources, ret: %d\n", ret); return ret; } ret = pci_enable_device(pci_dev); if (ret) { MHI_CNTRL_ERR("Error enabling device, ret: %d\n", ret); goto error_enable_device; } ret = pci_request_region(pci_dev, dev_info->bar_num, "mhi"); if (ret) { MHI_CNTRL_ERR("Error pci_request_region, ret: %d\n", ret); goto error_request_region; } pci_set_master(pci_dev); base = pci_resource_start(pci_dev, dev_info->bar_num); mhi_cntrl->reg_len = pci_resource_len(pci_dev, dev_info->bar_num); mhi_cntrl->regs = ioremap(base, mhi_cntrl->reg_len); if (!mhi_cntrl->regs) { MHI_CNTRL_ERR("Error ioremap region\n"); goto error_ioremap; } /* reserved MSI for BHI plus one for each event ring */ mhi_cntrl->nr_irqs = dev_info->config->num_events + 1; ret = pci_alloc_irq_vectors(pci_dev, mhi_cntrl->nr_irqs, mhi_cntrl->nr_irqs, PCI_IRQ_MSI); if (IS_ERR_VALUE((ulong)ret) || ret < mhi_cntrl->nr_irqs) { MHI_CNTRL_ERR("Failed to enable MSI, ret: %d\n", ret); goto error_req_msi; } mhi_cntrl->irq = kmalloc_array(mhi_cntrl->nr_irqs, sizeof(*mhi_cntrl->irq), GFP_KERNEL); if (!mhi_cntrl->irq) { ret = -ENOMEM; goto error_alloc_msi_vec; } for (i = 0; i < mhi_cntrl->nr_irqs; i++) { mhi_cntrl->irq[i] = pci_irq_vector(pci_dev, i); if (mhi_cntrl->irq[i] < 0) { ret = mhi_cntrl->irq[i]; goto error_get_irq_vec; } } /* configure runtime pm */ pm_runtime_set_autosuspend_delay(mhi_cntrl->cntrl_dev, MHI_RPM_SUSPEND_TMR_MS); pm_runtime_use_autosuspend(mhi_cntrl->cntrl_dev); pm_suspend_ignore_children(mhi_cntrl->cntrl_dev, true); /* * pci framework will increment usage count (twice) before * calling local device driver probe function. * 1st pci.c pci_pm_init() calls pm_runtime_forbid * 2nd pci-driver.c local_pci_probe calls pm_runtime_get_sync * Framework expect pci device driver to call * pm_runtime_put_noidle to decrement usage count after * successful probe and and call pm_runtime_allow to enable * runtime suspend. */ pm_runtime_mark_last_busy(mhi_cntrl->cntrl_dev); pm_runtime_put_noidle(mhi_cntrl->cntrl_dev); return 0; error_get_irq_vec: kfree(mhi_cntrl->irq); mhi_cntrl->irq = NULL; error_alloc_msi_vec: pci_free_irq_vectors(pci_dev); error_req_msi: iounmap(mhi_cntrl->regs); error_ioremap: pci_clear_master(pci_dev); error_request_region: pci_disable_device(pci_dev); error_enable_device: pci_release_region(pci_dev, dev_info->bar_num); return ret; } static int mhi_runtime_suspend(struct device *dev) { struct mhi_controller *mhi_cntrl = dev_get_drvdata(dev); struct mhi_qcom_priv *mhi_priv = mhi_controller_get_privdata(mhi_cntrl); int ret = 0; MHI_CNTRL_LOG("Entered\n"); mutex_lock(&mhi_cntrl->pm_mutex); if (!mhi_priv->powered_on) { MHI_CNTRL_LOG("Not fully powered, return success\n"); mutex_unlock(&mhi_cntrl->pm_mutex); return 0; } ret = mhi_pm_suspend(mhi_cntrl); if (ret) { MHI_CNTRL_LOG("Abort due to ret: %d\n", ret); mhi_priv->suspend_mode = MHI_ACTIVE_STATE; goto exit_runtime_suspend; } mhi_priv->suspend_mode = MHI_DEFAULT_SUSPEND; ret = mhi_arch_link_suspend(mhi_cntrl); /* failed suspending link abort mhi suspend */ if (ret) { MHI_CNTRL_LOG("Failed to suspend link, abort suspend\n"); mhi_pm_resume(mhi_cntrl); mhi_priv->suspend_mode = MHI_ACTIVE_STATE; } exit_runtime_suspend: mutex_unlock(&mhi_cntrl->pm_mutex); MHI_CNTRL_LOG("Exited with ret: %d\n", ret); return (ret < 0) ? -EBUSY : 0; } static int mhi_runtime_idle(struct device *dev) { struct mhi_controller *mhi_cntrl = dev_get_drvdata(dev); MHI_CNTRL_LOG("Entered returning -EBUSY\n"); /* * RPM framework during runtime resume always calls * rpm_idle to see if device ready to suspend. * If dev.power usage_count count is 0, rpm fw will call * rpm_idle cb to see if device is ready to suspend. * if cb return 0, or cb not defined the framework will * assume device driver is ready to suspend; * therefore, fw will schedule runtime suspend. * In MHI power management, MHI host shall go to * runtime suspend only after entering MHI State M2, even if * usage count is 0. Return -EBUSY to disable automatic suspend. */ return -EBUSY; } static int mhi_runtime_resume(struct device *dev) { int ret = 0; struct mhi_controller *mhi_cntrl = dev_get_drvdata(dev); struct mhi_qcom_priv *mhi_priv = mhi_controller_get_privdata(mhi_cntrl); MHI_CNTRL_LOG("Entered\n"); mutex_lock(&mhi_cntrl->pm_mutex); if (!mhi_priv->powered_on) { MHI_CNTRL_LOG("Not fully powered, return success\n"); mutex_unlock(&mhi_cntrl->pm_mutex); return 0; } /* turn on link */ ret = mhi_arch_link_resume(mhi_cntrl); if (ret) goto rpm_resume_exit; /* transition to M0 state */ if (mhi_priv->suspend_mode == MHI_DEFAULT_SUSPEND) ret = mhi_pm_resume(mhi_cntrl); else ret = mhi_pm_fast_resume(mhi_cntrl, MHI_FAST_LINK_ON); mhi_priv->suspend_mode = MHI_ACTIVE_STATE; rpm_resume_exit: mutex_unlock(&mhi_cntrl->pm_mutex); MHI_CNTRL_LOG("Exited with ret: %d\n", ret); return (ret < 0) ? -EBUSY : 0; } static int mhi_system_resume(struct device *dev) { return mhi_runtime_resume(dev); } int mhi_system_suspend(struct device *dev) { struct mhi_controller *mhi_cntrl = dev_get_drvdata(dev); struct mhi_qcom_priv *mhi_priv = mhi_controller_get_privdata(mhi_cntrl); const struct mhi_pci_dev_info *dev_info = mhi_priv->dev_info; int ret; MHI_CNTRL_LOG("Entered\n"); /* No DRV support - use regular suspends */ if (!dev_info->drv_support) return mhi_runtime_suspend(dev); mutex_lock(&mhi_cntrl->pm_mutex); if (!mhi_priv->powered_on) { MHI_CNTRL_LOG("Not fully powered, return success\n"); mutex_unlock(&mhi_cntrl->pm_mutex); return 0; } /* * pci framework always makes a dummy vote to rpm * framework to resume before calling system suspend * hence usage count is minimum one */ if (atomic_read(&dev->power.usage_count) > 1) { /* * clients have requested to keep link on, try * fast suspend. No need to notify clients since * we will not be turning off the pcie link */ ret = mhi_pm_fast_suspend(mhi_cntrl, false); mhi_priv->suspend_mode = MHI_FAST_LINK_ON; } else { /* try normal suspend */ mhi_priv->suspend_mode = MHI_DEFAULT_SUSPEND; ret = mhi_pm_suspend(mhi_cntrl); /* * normal suspend failed because we're busy, try * fast suspend before aborting system suspend. * this could happens if client has disabled * device lpm but no active vote for PCIe from * apps processor */ if (ret == -EBUSY) { ret = mhi_pm_fast_suspend(mhi_cntrl, true); mhi_priv->suspend_mode = MHI_FAST_LINK_ON; } } if (ret) { MHI_CNTRL_LOG("Abort due to ret: %d\n", ret); mhi_priv->suspend_mode = MHI_ACTIVE_STATE; goto exit_system_suspend; } ret = mhi_arch_link_suspend(mhi_cntrl); /* failed suspending link abort mhi suspend */ if (ret) { MHI_CNTRL_LOG("Failed to suspend link, abort suspend\n"); if (mhi_priv->suspend_mode == MHI_DEFAULT_SUSPEND) mhi_pm_resume(mhi_cntrl); else mhi_pm_fast_resume(mhi_cntrl, MHI_FAST_LINK_OFF); mhi_priv->suspend_mode = MHI_ACTIVE_STATE; } exit_system_suspend: mutex_unlock(&mhi_cntrl->pm_mutex); MHI_CNTRL_LOG("Exited with ret: %d\n", ret); return ret; } static int mhi_suspend_noirq(struct device *dev) { return 0; } static int mhi_resume_noirq(struct device *dev) { return 0; } static int mhi_force_suspend(struct mhi_controller *mhi_cntrl) { struct mhi_qcom_priv *mhi_priv = mhi_controller_get_privdata(mhi_cntrl); int itr = DIV_ROUND_UP(mhi_cntrl->timeout_ms, 100); int ret = -EIO; MHI_CNTRL_LOG("Entered\n"); mutex_lock(&mhi_cntrl->pm_mutex); for (; itr; itr--) { /* * This function get called soon as device entered mission mode * so most of the channels are still in disabled state. However, * sbl channels are active and clients could be trying to close * channels while we trying to suspend the link. So, we need to * re-try if MHI is busy */ ret = mhi_pm_suspend(mhi_cntrl); if (!ret || ret != -EBUSY) break; MHI_CNTRL_LOG("MHI busy, sleeping and retry\n"); msleep(100); } if (ret) { MHI_CNTRL_ERR("Force suspend ret:%d\n", ret); goto exit_force_suspend; } mhi_priv->suspend_mode = MHI_DEFAULT_SUSPEND; ret = mhi_arch_link_suspend(mhi_cntrl); exit_force_suspend: mutex_unlock(&mhi_cntrl->pm_mutex); return ret; } static int mhi_qcom_power_up(struct mhi_controller *mhi_cntrl) { int ret; /* when coming out of SSR, initial states are not valid */ mhi_cntrl->ee = MHI_EE_MAX; mhi_cntrl->dev_state = MHI_STATE_RESET; ret = mhi_prepare_for_power_up(mhi_cntrl); if (ret) return ret; ret = mhi_async_power_up(mhi_cntrl); if (ret) { mhi_unprepare_after_power_down(mhi_cntrl); return ret; } if (mhi_cntrl->debugfs_dentry) { debugfs_create_file("m0", 0444, mhi_cntrl->debugfs_dentry, mhi_cntrl, &debugfs_trigger_m0_fops); debugfs_create_file("m3", 0444, mhi_cntrl->debugfs_dentry, mhi_cntrl, &debugfs_trigger_m3_fops); debugfs_create_file("disable_pci_lpm", 0644, mhi_cntrl->debugfs_dentry, mhi_cntrl, &debugfs_pci_lpm_fops); } return ret; } static int mhi_runtime_get(struct mhi_controller *mhi_cntrl) { return pm_runtime_get(mhi_cntrl->cntrl_dev); } static void mhi_runtime_put(struct mhi_controller *mhi_cntrl) { pm_runtime_put_noidle(mhi_cntrl->cntrl_dev); } static void mhi_status_cb(struct mhi_controller *mhi_cntrl, enum mhi_callback reason) { struct mhi_qcom_priv *mhi_priv = mhi_controller_get_privdata(mhi_cntrl); const struct mhi_pci_dev_info *dev_info = mhi_priv->dev_info; struct device *dev = mhi_cntrl->cntrl_dev; int ret; switch (reason) { case MHI_CB_IDLE: MHI_CNTRL_LOG("Schedule runtime suspend\n"); pm_runtime_mark_last_busy(dev); pm_request_autosuspend(dev); break; case MHI_CB_EE_MISSION_MODE: MHI_CNTRL_LOG("Mission mode entry\n"); if (debug_mode == MHI_DEBUG_NO_LPM) { mhi_arch_mission_mode_enter(mhi_cntrl); MHI_CNTRL_LOG("Exit due to: %s\n", TO_MHI_DEBUG_MODE_STR(debug_mode)); break; } /* * we need to force a suspend so device can switch to * mission mode pcie phy settings. */ if (!dev_info->skip_forced_suspend) { pm_runtime_get(dev); ret = mhi_force_suspend(mhi_cntrl); if (!ret) { MHI_CNTRL_LOG("Resume after forced suspend\n"); mhi_runtime_resume(dev); } pm_runtime_put(dev); } mhi_arch_mission_mode_enter(mhi_cntrl); pm_runtime_allow(dev); break; default: MHI_CNTRL_LOG("Unhandled cb: 0x%x\n", reason); } } /* Setting to use this mhi_qcom_pm_domain ops will let PM framework override the * ops from dev->bus->pm which is pci_dev_pm_ops from pci-driver.c. This ops * has to take care everything device driver needed which is currently done * from pci_dev_pm_ops. */ static struct dev_pm_domain mhi_qcom_pm_domain = { .ops = { SET_SYSTEM_SLEEP_PM_OPS(mhi_system_suspend, mhi_system_resume) SET_NOIRQ_SYSTEM_SLEEP_PM_OPS(mhi_suspend_noirq, mhi_resume_noirq) SET_RUNTIME_PM_OPS(mhi_runtime_suspend, mhi_runtime_resume, mhi_runtime_idle) } }; static int mhi_qcom_register_controller(struct mhi_controller *mhi_cntrl, struct mhi_qcom_priv *mhi_priv) { const struct mhi_pci_dev_info *dev_info = mhi_priv->dev_info; const struct mhi_controller_config *mhi_cntrl_config = dev_info->config; struct pci_dev *pci_dev = to_pci_dev(mhi_cntrl->cntrl_dev); struct device_node *of_node = pci_dev->dev.of_node; struct mhi_device *mhi_dev; bool use_s1; u32 addr_win[2]; const char *iommu_dma_type; int ret; mhi_cntrl->iova_start = 0; mhi_cntrl->iova_stop = DMA_BIT_MASK(dev_info->dma_data_width); of_node = of_parse_phandle(of_node, "qcom,iommu-group", 0); if (of_node) { use_s1 = true; /* * s1 translation can be in bypass or fastmap mode * if "qcom,iommu-dma" property is missing, we assume s1 is * enabled and in default (no fastmap/atomic) mode */ ret = of_property_read_string(of_node, "qcom,iommu-dma", &iommu_dma_type); if (!ret && !strcmp("bypass", iommu_dma_type)) use_s1 = false; /* * if s1 translation enabled pull iova addr from dt using * iommu-dma-addr-pool property specified addresses */ if (use_s1) { ret = of_property_read_u32_array(of_node, "qcom,iommu-dma-addr-pool", addr_win, 2); if (ret) { of_node_put(of_node); return -EINVAL; } /* * If S1 is enabled, set MHI_CTRL start address to 0 * so we can use low level mapping api to map buffers * outside of smmu domain */ mhi_cntrl->iova_start = 0; mhi_cntrl->iova_stop = addr_win[0] + addr_win[1]; } of_node_put(of_node); } /* setup power management apis */ 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_qcom_read_reg; mhi_cntrl->write_reg = mhi_qcom_write_reg; ret = mhi_register_controller(mhi_cntrl, mhi_cntrl_config); if (ret) return ret; mhi_cntrl->fw_image = dev_info->fw_image; mhi_cntrl->edl_image = dev_info->edl_image; mhi_controller_set_privdata(mhi_cntrl, mhi_priv); mhi_controller_set_base(mhi_cntrl, pci_resource_start(pci_dev, dev_info->bar_num)); if (dev_info->sfr_support) { ret = mhi_controller_set_sfr_support(mhi_cntrl, MHI_MAX_SFR_LEN); if (ret) goto error_register; } if (dev_info->timesync) { ret = mhi_controller_setup_timesync(mhi_cntrl, &mhi_qcom_time_get, &mhi_qcom_lpm_disable, &mhi_qcom_lpm_enable); if (ret) goto error_register; } if (dev_info->drv_support) pci_dev->dev.pm_domain = &mhi_qcom_pm_domain; /* set name based on PCIe BDF format */ mhi_dev = mhi_cntrl->mhi_dev; dev_set_name(&mhi_dev->dev, "mhi_%04x_%02u.%02u.%02u", pci_dev->device, pci_domain_nr(pci_dev->bus), pci_dev->bus->number, PCI_SLOT(pci_dev->devfn)); mhi_dev->name = dev_name(&mhi_dev->dev); mhi_priv->cntrl_ipc_log = ipc_log_context_create(MHI_IPC_LOG_PAGES, dev_info->name, 0); return 0; error_register: mhi_unregister_controller(mhi_cntrl); return -EINVAL; } int mhi_qcom_pci_probe(struct pci_dev *pci_dev, struct mhi_controller *mhi_cntrl, struct mhi_qcom_priv *mhi_priv) { const struct mhi_pci_dev_info *dev_info = mhi_priv->dev_info; int ret; dev_set_drvdata(&pci_dev->dev, mhi_cntrl); mhi_cntrl->cntrl_dev = &pci_dev->dev; ret = mhi_init_pci_dev(pci_dev, dev_info); if (ret) return ret; /* driver removal boolen set to true indicates initial probe */ if (mhi_priv->driver_remove) { ret = mhi_qcom_register_controller(mhi_cntrl, mhi_priv); if (ret) goto error_init_pci; } mhi_priv->powered_on = true; ret = mhi_arch_pcie_init(mhi_cntrl); if (ret) goto error_init_pci; ret = dma_set_mask_and_coherent(mhi_cntrl->cntrl_dev, DMA_BIT_MASK(dev_info->dma_data_width)); if (ret) goto error_init_pci; if (debug_mode) { if (mhi_cntrl->debugfs_dentry) debugfs_create_file("power_up", 0644, mhi_cntrl->debugfs_dentry, mhi_cntrl, &debugfs_power_up_fops); mhi_priv->powered_on = false; return 0; } /* start power up sequence */ ret = mhi_qcom_power_up(mhi_cntrl); if (ret) { MHI_CNTRL_ERR("Failed to power up MHI\n"); mhi_priv->powered_on = false; goto error_power_up; } pm_runtime_mark_last_busy(mhi_cntrl->cntrl_dev); return 0; error_power_up: mhi_arch_pcie_deinit(mhi_cntrl); error_init_pci: mhi_deinit_pci_dev(pci_dev, dev_info); dev_set_drvdata(&pci_dev->dev, NULL); mhi_cntrl->cntrl_dev = NULL; return ret; } int mhi_pci_probe(struct pci_dev *pci_dev, const struct pci_device_id *id) { const struct mhi_pci_dev_info *dev_info = (struct mhi_pci_dev_info *) id->driver_data; struct mhi_controller *mhi_cntrl; struct mhi_qcom_priv *mhi_priv; u32 domain = pci_domain_nr(pci_dev->bus); u32 bus = pci_dev->bus->number; u32 dev_id = pci_dev->device; u32 slot = PCI_SLOT(pci_dev->devfn); int ret; /* see if we already registered */ mhi_cntrl = mhi_bdf_to_controller(domain, bus, slot, dev_id); if (!mhi_cntrl) { mhi_cntrl = mhi_alloc_controller(); if (!mhi_cntrl) return -ENOMEM; } mhi_priv = mhi_controller_get_privdata(mhi_cntrl); if (!mhi_priv) { mhi_priv = kzalloc(sizeof(*mhi_priv), GFP_KERNEL); if (!mhi_priv) return -ENOMEM; } /* set as true to initiate clean-up after first probe fails */ mhi_priv->driver_remove = true; mhi_priv->dev_info = dev_info; ret = mhi_qcom_pci_probe(pci_dev, mhi_cntrl, mhi_priv); if (ret) { kfree(mhi_priv); mhi_free_controller(mhi_cntrl); } return ret; } void mhi_pci_remove(struct pci_dev *pci_dev) { struct mhi_controller *mhi_cntrl; struct mhi_qcom_priv *mhi_priv; u32 domain = pci_domain_nr(pci_dev->bus); u32 bus = pci_dev->bus->number; u32 dev_id = pci_dev->device; u32 slot = PCI_SLOT(pci_dev->devfn); /* see if we already registered */ mhi_cntrl = mhi_bdf_to_controller(domain, bus, slot, dev_id); if (!mhi_cntrl) return; mhi_priv = mhi_controller_get_privdata(mhi_cntrl); if (!mhi_priv) return; /* if link is in suspend, wake it up */ pm_runtime_get_sync(mhi_cntrl->cntrl_dev); if (mhi_priv->powered_on) { MHI_CNTRL_LOG("Triggering shutdown process\n"); mhi_power_down(mhi_cntrl, false); mhi_unprepare_after_power_down(mhi_cntrl); } mhi_priv->powered_on = false; pm_runtime_put_noidle(mhi_cntrl->cntrl_dev); /* allow arch driver to free memory and unregister esoc if set */ mhi_priv->driver_remove = true; mhi_arch_pcie_deinit(mhi_cntrl); /* turn the link off */ mhi_deinit_pci_dev(pci_dev, mhi_priv->dev_info); mhi_unregister_controller(mhi_cntrl); kfree(mhi_priv); mhi_free_controller(mhi_cntrl); } static const struct dev_pm_ops pm_ops = { SET_RUNTIME_PM_OPS(mhi_runtime_suspend, mhi_runtime_resume, mhi_runtime_idle) SET_SYSTEM_SLEEP_PM_OPS(mhi_system_suspend, mhi_system_resume) }; #ifdef CONFIG_MHI_BUS_DEBUG static struct dentry *mhi_qcom_debugfs; static int mhi_qcom_debugfs_debug_mode_show(struct seq_file *m, void *d) { seq_printf(m, "%s\n", TO_MHI_DEBUG_MODE_STR(debug_mode)); return 0; } static ssize_t mhi_qcom_debugfs_debug_mode_write(struct file *file, const char __user *ubuf, size_t count, loff_t *ppos) { struct seq_file *m = file->private_data; u32 input; if (kstrtou32_from_user(ubuf, count, 0, &input)) return -EINVAL; if (input >= MHI_DEBUG_MODE_MAX) return -EINVAL; debug_mode = input; seq_printf(m, "Changed debug mode to: %s\n", TO_MHI_DEBUG_MODE_STR(debug_mode)); return count; } static int mhi_qcom_debugfs_debug_mode_open(struct inode *inode, struct file *p) { return single_open(p, mhi_qcom_debugfs_debug_mode_show, inode->i_private); } static const struct file_operations debugfs_debug_mode_fops = { .open = mhi_qcom_debugfs_debug_mode_open, .write = mhi_qcom_debugfs_debug_mode_write, .release = single_release, .read = seq_read, }; void mhi_qcom_debugfs_init(void) { mhi_qcom_debugfs = debugfs_create_dir("mhi_qcom", NULL); debugfs_create_file("debug_mode", 0644, mhi_qcom_debugfs, NULL, &debugfs_debug_mode_fops); } void mhi_qcom_debugfs_exit(void) { debugfs_remove_recursive(mhi_qcom_debugfs); mhi_qcom_debugfs = NULL; } #else static inline void mhi_qcom_debugfs_init(void) { } static inline void mhi_qcom_debugfs_exit(void) { } #endif static struct pci_driver mhi_pcie_driver = { .name = "mhi", .id_table = mhi_pcie_device_id, .probe = mhi_pci_probe, .remove = mhi_pci_remove, .driver = { .pm = &pm_ops } }; static int __init mhi_qcom_init(void) { int ret = 0; mhi_qcom_debugfs_init(); ret = pci_register_driver(&mhi_pcie_driver); if (ret) { mhi_qcom_debugfs_exit(); return ret; } return 0; } static void __exit mhi_qcom_exit(void) { pci_unregister_driver(&mhi_pcie_driver); mhi_qcom_debugfs_exit(); } module_init(mhi_qcom_init); module_exit(mhi_qcom_exit); MODULE_LICENSE("GPL v2"); MODULE_ALIAS("MHI_CORE"); MODULE_DESCRIPTION("MHI Host Driver");