/* Copyright (c) 2018, The Linux Foundation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and * only version 2 as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "../core/mhi.h" #include "mhi_qcom.h" #if 1 #ifndef PCI_IRQ_MSI #define PCI_IRQ_MSI (1 << 1) /* Allow MSI interrupts */ #if (LINUX_VERSION_CODE < KERNEL_VERSION( 3,10,53 )) int pci_enable_msi_range(struct pci_dev *dev, int minvec, int maxvec) { int nvec = maxvec; int rc; if (maxvec < minvec) return -ERANGE; do { rc = pci_enable_msi_block(dev, nvec); if (rc < 0) { return rc; } else if (rc > 0) { if (rc < minvec) return -ENOSPC; nvec = rc; } } while (rc); return nvec; } #endif static int pci_alloc_irq_vectors(struct pci_dev *dev, unsigned int min_vecs, unsigned int max_vecs, unsigned int flags) { return pci_enable_msi_range(dev, min_vecs, max_vecs); } static void pci_free_irq_vectors(struct pci_dev *dev) { pci_disable_msi(dev); } static int pci_irq_vector(struct pci_dev *dev, unsigned int nr) { return dev->irq + nr; } #endif #endif static struct pci_device_id mhi_pcie_device_id[] = { {PCI_DEVICE(MHI_PCIE_VENDOR_ID, 0x0303)}, //SDX20 {PCI_DEVICE(MHI_PCIE_VENDOR_ID, 0x0304)}, //SDX24 {PCI_DEVICE(MHI_PCIE_VENDOR_ID, 0x0305)}, {PCI_DEVICE(MHI_PCIE_VENDOR_ID, 0x0306)}, //SDX55 {PCI_DEVICE(0x2C7C, 0x0512)}, {PCI_DEVICE(MHI_PCIE_VENDOR_ID, MHI_PCIE_DEBUG_ID)}, {0}, }; MODULE_DEVICE_TABLE(pci, mhi_pcie_device_id); static struct pci_driver mhi_pcie_driver; void mhi_deinit_pci_dev(struct mhi_controller *mhi_cntrl) { struct mhi_dev *mhi_dev = mhi_controller_get_devdata(mhi_cntrl); struct pci_dev *pci_dev = mhi_dev->pci_dev; pci_free_irq_vectors(pci_dev); iounmap(mhi_cntrl->regs); mhi_cntrl->regs = NULL; pci_clear_master(pci_dev); pci_release_region(pci_dev, mhi_dev->resn); pci_disable_device(pci_dev); } static int mhi_init_pci_dev(struct mhi_controller *mhi_cntrl) { struct mhi_dev *mhi_dev = mhi_controller_get_devdata(mhi_cntrl); struct pci_dev *pci_dev = mhi_dev->pci_dev; int ret; resource_size_t start, len; int i; mhi_dev->resn = MHI_PCI_BAR_NUM; ret = pci_assign_resource(pci_dev, mhi_dev->resn); if (ret) { MHI_ERR("Error assign pci resources, ret:%d\n", ret); return ret; } ret = pci_enable_device(pci_dev); if (ret) { MHI_ERR("Error enabling device, ret:%d\n", ret); goto error_enable_device; } ret = pci_request_region(pci_dev, mhi_dev->resn, "mhi"); if (ret) { MHI_ERR("Error pci_request_region, ret:%d\n", ret); goto error_request_region; } pci_set_master(pci_dev); start = pci_resource_start(pci_dev, mhi_dev->resn); len = pci_resource_len(pci_dev, mhi_dev->resn); mhi_cntrl->regs = ioremap_nocache(start, len); MHI_LOG("mhi_cntrl->regs = %p\n", mhi_cntrl->regs); if (!mhi_cntrl->regs) { MHI_ERR("Error ioremap region\n"); goto error_ioremap; } ret = pci_alloc_irq_vectors(pci_dev, 1, mhi_cntrl->msi_required, PCI_IRQ_MSI); if (IS_ERR_VALUE((ulong)ret) || ret < mhi_cntrl->msi_required) { if (ret == -ENOSPC) { /* imx_3.14.52_1.1.0_ga diff --git a/drivers/pci/host/pcie-designware.c b/drivers/pci/host/pcie-designware.c index f06e8f0..6a9614f 100644 --- a/drivers/pci/host/pcie-designware.c +++ b/drivers/pci/host/pcie-designware.c @@ -376,6 +376,13 @@ static int dw_msi_setup_irq(struct msi_chip *chip, struct pci_dev *pdev, if (msgvec > 5) msgvec = 0; +#if 1 //Add by Quectel 20190419 + if (msgvec > 0 && pdev->vendor == 0x17cb) { + dev_info(&pdev->dev, "%s quectel fixup pos=%d, msg_ctr=%04x, msgvec=%d\n", __func__, desc->msi_attrib.pos, msg_ctr, msgvec); + msgvec = 0; + } +#endif + irq = assign_irq((1 << msgvec), desc, &pos); if (irq < 0) return irq; */ } //imx_4.1.15_2.0.0_ga & DELL_OPTIPLEX_7010 only alloc one msi interrupt for one pcie device if (ret != 1) { MHI_ERR("Failed to enable MSI, ret=%d, msi_required=%d\n", ret, mhi_cntrl->msi_required); goto error_req_msi; } } mhi_cntrl->msi_allocated = ret; MHI_LOG("msi_required = %d, msi_allocated = %d, msi_irq = %u\n", mhi_cntrl->msi_required, mhi_cntrl->msi_allocated, pci_dev->irq); for (i = 0; i < mhi_cntrl->msi_allocated; 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; } } #if 0 /* configure runtime pm */ pm_runtime_set_autosuspend_delay(&pci_dev->dev, MHI_RPM_SUSPEND_TMR_MS); pm_runtime_dont_use_autosuspend(&pci_dev->dev); pm_suspend_ignore_children(&pci_dev->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(&pci_dev->dev); pm_runtime_put_noidle(&pci_dev->dev); #endif return 0; error_get_irq_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, mhi_dev->resn); return ret; } #ifdef CONFIG_PM static int mhi_runtime_idle(struct device *dev) { struct mhi_controller *mhi_cntrl = dev_get_drvdata(dev); MHI_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_suspend(struct device *dev) { int ret = 0; struct mhi_controller *mhi_cntrl = dev_get_drvdata(dev); MHI_LOG("Enter\n"); mutex_lock(&mhi_cntrl->pm_mutex); ret = mhi_pm_suspend(mhi_cntrl); if (ret) { MHI_LOG("Abort due to ret:%d\n", ret); goto exit_runtime_suspend; } ret = mhi_arch_link_off(mhi_cntrl, true); if (ret) MHI_ERR("Failed to Turn off link ret:%d\n", ret); exit_runtime_suspend: mutex_unlock(&mhi_cntrl->pm_mutex); MHI_LOG("Exited with ret:%d\n", ret); return ret; } static int mhi_runtime_resume(struct device *dev) { int ret = 0; struct mhi_controller *mhi_cntrl = dev_get_drvdata(dev); struct mhi_dev *mhi_dev = mhi_controller_get_devdata(mhi_cntrl); MHI_LOG("Enter\n"); mutex_lock(&mhi_cntrl->pm_mutex); if (!mhi_dev->powered_on) { MHI_LOG("Not fully powered, return success\n"); mutex_unlock(&mhi_cntrl->pm_mutex); return 0; } /* turn on link */ ret = mhi_arch_link_on(mhi_cntrl); if (ret) goto rpm_resume_exit; /* enter M0 state */ ret = mhi_pm_resume(mhi_cntrl); rpm_resume_exit: mutex_unlock(&mhi_cntrl->pm_mutex); MHI_LOG("Exited with :%d\n", ret); return ret; } static int mhi_system_resume(struct device *dev) { int ret = 0; struct mhi_controller *mhi_cntrl = dev_get_drvdata(dev); ret = mhi_runtime_resume(dev); if (ret) { MHI_ERR("Failed to resume link\n"); } else { pm_runtime_set_active(dev); pm_runtime_enable(dev); } return ret; } int mhi_system_suspend(struct device *dev) { struct mhi_controller *mhi_cntrl = dev_get_drvdata(dev); MHI_LOG("Entered\n"); /* if rpm status still active then force suspend */ if (!pm_runtime_status_suspended(dev)) return mhi_runtime_suspend(dev); pm_runtime_set_suspended(dev); pm_runtime_disable(dev); MHI_LOG("Exit\n"); return 0; } #endif /* checks if link is down */ static int mhi_link_status(struct mhi_controller *mhi_cntrl, void *priv) { struct mhi_dev *mhi_dev = priv; u16 dev_id; int ret; /* try reading device id, if dev id don't match, link is down */ ret = pci_read_config_word(mhi_dev->pci_dev, PCI_DEVICE_ID, &dev_id); return (ret || dev_id != mhi_cntrl->dev_id) ? -EIO : 0; } static int mhi_runtime_get(struct mhi_controller *mhi_cntrl, void *priv) { struct mhi_dev *mhi_dev = priv; struct device *dev = &mhi_dev->pci_dev->dev; return pm_runtime_get(dev); } static void mhi_runtime_put(struct mhi_controller *mhi_cntrl, void *priv) { struct mhi_dev *mhi_dev = priv; struct device *dev = &mhi_dev->pci_dev->dev; pm_runtime_put_noidle(dev); } static void mhi_status_cb(struct mhi_controller *mhi_cntrl, void *priv, enum MHI_CB reason) { struct mhi_dev *mhi_dev = priv; struct device *dev = &mhi_dev->pci_dev->dev; if (reason == MHI_CB_IDLE) { MHI_LOG("Schedule runtime suspend 1\n"); pm_runtime_mark_last_busy(dev); pm_request_autosuspend(dev); } } int mhi_debugfs_trigger_m0(void *data, u64 val) { struct mhi_controller *mhi_cntrl = data; struct mhi_dev *mhi_dev = mhi_controller_get_devdata(mhi_cntrl); MHI_LOG("Trigger M3 Exit\n"); pm_runtime_get(&mhi_dev->pci_dev->dev); pm_runtime_put(&mhi_dev->pci_dev->dev); return 0; } int mhi_debugfs_trigger_m3(void *data, u64 val) { struct mhi_controller *mhi_cntrl = data; struct mhi_dev *mhi_dev = mhi_controller_get_devdata(mhi_cntrl); MHI_LOG("Trigger M3 Entry\n"); pm_runtime_mark_last_busy(&mhi_dev->pci_dev->dev); pm_request_autosuspend(&mhi_dev->pci_dev->dev); return 0; } DEFINE_SIMPLE_ATTRIBUTE(debugfs_trigger_m0_fops, NULL, mhi_debugfs_trigger_m0, "%llu\n"); DEFINE_SIMPLE_ATTRIBUTE(debugfs_trigger_m3_fops, NULL, mhi_debugfs_trigger_m3, "%llu\n"); static int mhi_init_debugfs_trigger_go(void *data, u64 val) { struct mhi_controller *mhi_cntrl = data; MHI_LOG("Trigger power up sequence\n"); mhi_async_power_up(mhi_cntrl); return 0; } DEFINE_SIMPLE_ATTRIBUTE(mhi_init_debugfs_trigger_go_fops, NULL, mhi_init_debugfs_trigger_go, "%llu\n"); int mhi_init_debugfs_debug_show(struct seq_file *m, void *d) { seq_puts(m, "Enable debug mode to debug external soc\n"); seq_puts(m, "Usage: echo 'devid,timeout,domain,smmu_cfg' > debug_mode\n"); seq_puts(m, "No spaces between parameters\n"); seq_puts(m, "\t1. devid : 0 or pci device id to register\n"); seq_puts(m, "\t2. timeout: mhi cmd/state transition timeout\n"); seq_puts(m, "\t3. domain: Rootcomplex\n"); seq_puts(m, "\t4. smmu_cfg: smmu configuration mask:\n"); seq_puts(m, "\t\t- BIT0: ATTACH\n"); seq_puts(m, "\t\t- BIT1: S1 BYPASS\n"); seq_puts(m, "\t\t-BIT2: FAST_MAP\n"); seq_puts(m, "\t\t-BIT3: ATOMIC\n"); seq_puts(m, "\t\t-BIT4: FORCE_COHERENT\n"); seq_puts(m, "\t\t-BIT5: GEOMETRY\n"); seq_puts(m, "\tAll timeout are in ms, enter 0 to keep default\n"); seq_puts(m, "Examples inputs: '0x307,10000'\n"); seq_puts(m, "\techo '0,10000,1'\n"); seq_puts(m, "\techo '0x307,10000,0,0x3d'\n"); seq_puts(m, "firmware image name will be changed to debug.mbn\n"); return 0; } static int mhi_init_debugfs_debug_open(struct inode *node, struct file *file) { return single_open(file, mhi_init_debugfs_debug_show, NULL); } static ssize_t mhi_init_debugfs_debug_write(struct file *fp, const char __user *ubuf, size_t count, loff_t *pos) { char *buf = kmalloc(count + 1, GFP_KERNEL); /* #,devid,timeout,domain,smmu-cfg */ int args[5] = {0}; static char const *dbg_fw = "debug.mbn"; int ret; struct mhi_controller *mhi_cntrl = fp->f_inode->i_private; struct mhi_dev *mhi_dev = mhi_controller_get_devdata(mhi_cntrl); struct pci_device_id *id; if (!buf) return -ENOMEM; ret = copy_from_user(buf, ubuf, count); if (ret) goto error_read; buf[count] = 0; get_options(buf, ARRAY_SIZE(args), args); kfree(buf); /* override default parameters */ mhi_cntrl->fw_image = dbg_fw; mhi_cntrl->edl_image = dbg_fw; if (args[0] >= 2 && args[2]) mhi_cntrl->timeout_ms = args[2]; if (args[0] >= 3 && args[3]) mhi_cntrl->domain = args[3]; if (args[0] >= 4 && args[4]) mhi_dev->smmu_cfg = args[4]; /* If it's a new device id register it */ if (args[0] && args[1]) { /* find the debug_id and overwrite it */ for (id = mhi_pcie_device_id; id->vendor; id++) if (id->device == MHI_PCIE_DEBUG_ID) { id->device = args[1]; pci_unregister_driver(&mhi_pcie_driver); ret = pci_register_driver(&mhi_pcie_driver); } } mhi_dev->debug_mode = true; debugfs_create_file("go", 0444, mhi_cntrl->parent, mhi_cntrl, &mhi_init_debugfs_trigger_go_fops); pr_info( "%s: ret:%d pcidev:0x%x smm_cfg:%u timeout:%u\n", __func__, ret, args[1], mhi_dev->smmu_cfg, mhi_cntrl->timeout_ms); return count; error_read: kfree(buf); return ret; } static const struct file_operations debugfs_debug_ops = { .open = mhi_init_debugfs_debug_open, .release = single_release, .read = seq_read, .write = mhi_init_debugfs_debug_write, }; static struct mhi_controller * mhi_platform_probe(struct pci_dev *pci_dev) { struct mhi_controller *mhi_cntrl; struct mhi_dev *mhi_dev; u64 addr_win[2]; int ret; mhi_cntrl = mhi_alloc_controller(sizeof(*mhi_dev)); if (!mhi_cntrl) { pr_err("mhi_alloc_controller fail\n"); return NULL; } mhi_dev = mhi_controller_get_devdata(mhi_cntrl); mhi_cntrl->dev_id = pci_dev->device; mhi_cntrl->domain = pci_domain_nr(pci_dev->bus); mhi_cntrl->bus = pci_dev->bus->number; mhi_cntrl->slot = PCI_SLOT(pci_dev->devfn); mhi_dev->smmu_cfg = 0; #if 0 //def CONFIG_HAVE_MEMBLOCK addr_win[0] = memblock_start_of_DRAM(); addr_win[1] = memblock_end_of_DRAM(); #else #define MHI_MEM_BASE_DEFAULT 0x000000000 #define MHI_MEM_SIZE_DEFAULT 0x2000000000 addr_win[0] = MHI_MEM_BASE_DEFAULT; addr_win[1] = MHI_MEM_SIZE_DEFAULT; if (sizeof(dma_addr_t) == 4) { addr_win[1] = 0xFFFFFFFF; } #endif mhi_cntrl->iova_start = addr_win[0]; mhi_cntrl->iova_stop = addr_win[1]; mhi_dev->pci_dev = pci_dev; mhi_cntrl->pci_dev = pci_dev; /* 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->link_status = mhi_link_status; ret = mhi_arch_platform_init(mhi_dev); if (ret) goto error_probe; ret = mhi_register_mhi_controller(mhi_cntrl); if (ret) goto error_register; if (mhi_cntrl->parent) debugfs_create_file("debug_mode", 0444, mhi_cntrl->parent, mhi_cntrl, &debugfs_debug_ops); return mhi_cntrl; error_register: mhi_arch_platform_deinit(mhi_dev); error_probe: mhi_free_controller(mhi_cntrl); return NULL; } int mhi_pci_probe(struct pci_dev *pci_dev, const struct pci_device_id *device_id) { struct mhi_controller *mhi_cntrl = NULL; u32 domain = pci_domain_nr(pci_dev->bus); u32 bus = pci_dev->bus->number; u32 slot = PCI_SLOT(pci_dev->devfn); struct mhi_dev *mhi_dev; int ret; pr_info("%s pci_dev->name = %s, domain=%d, bus=%d, slot=%d, vendor=%04X, device=%04X\n", __func__, dev_name(&pci_dev->dev), domain, bus, slot, pci_dev->vendor, pci_dev->device); mhi_cntrl = mhi_platform_probe(pci_dev); if (!mhi_cntrl) { pr_err("mhi_platform_probe fail\n"); return -EPROBE_DEFER; } mhi_cntrl->dev_id = pci_dev->device; mhi_dev = mhi_controller_get_devdata(mhi_cntrl); mhi_dev->pci_dev = pci_dev; mhi_dev->powered_on = true; ret = mhi_arch_pcie_init(mhi_cntrl); if (ret) { MHI_ERR("Error mhi_arch_pcie_init, ret:%d\n", ret); return ret; } ret = mhi_arch_iommu_init(mhi_cntrl); if (ret) { MHI_ERR("Error mhi_arch_iommu_init, ret:%d\n", ret); goto error_iommu_init; } ret = mhi_init_pci_dev(mhi_cntrl); if (ret) { MHI_ERR("Error mhi_init_pci_dev, ret:%d\n", ret); goto error_init_pci; } /* start power up sequence if not in debug mode */ if (!mhi_dev->debug_mode) { ret = mhi_async_power_up(mhi_cntrl); if (ret) { MHI_ERR("Error mhi_async_power_up, ret:%d\n", ret); goto error_power_up; } } #if 0 pm_runtime_mark_last_busy(&pci_dev->dev); pm_runtime_allow(&pci_dev->dev); pm_runtime_disable(&pci_dev->dev); #endif if (mhi_cntrl->dentry) { debugfs_create_file("m0", 0444, mhi_cntrl->dentry, mhi_cntrl, &debugfs_trigger_m0_fops); debugfs_create_file("m3", 0444, mhi_cntrl->dentry, mhi_cntrl, &debugfs_trigger_m3_fops); } dev_set_drvdata(&pci_dev->dev, mhi_cntrl); MHI_LOG("Return successful\n"); return 0; error_power_up: mhi_deinit_pci_dev(mhi_cntrl); error_init_pci: mhi_arch_iommu_deinit(mhi_cntrl); error_iommu_init: mhi_arch_pcie_deinit(mhi_cntrl); return ret; } static void mhi_pci_remove(struct pci_dev *pci_dev) { struct mhi_controller *mhi_cntrl = (struct mhi_controller *)dev_get_drvdata(&pci_dev->dev); if (mhi_cntrl && mhi_cntrl->pci_dev == pci_dev) { struct mhi_dev *mhi_dev = mhi_controller_get_devdata(mhi_cntrl); MHI_LOG("%s\n", dev_name(&pci_dev->dev)); if (!mhi_dev->debug_mode) { mhi_power_down(mhi_cntrl, 1); } mhi_deinit_pci_dev(mhi_cntrl); mhi_arch_iommu_deinit(mhi_cntrl); mhi_arch_pcie_deinit(mhi_cntrl); mhi_unregister_mhi_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) }; 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 } }; int __init mhi_controller_qcom_init(void) { return pci_register_driver(&mhi_pcie_driver); }; void mhi_controller_qcom_exit(void) { pr_info("%s enter\n", __func__); pci_unregister_driver(&mhi_pcie_driver); pr_info("%s exit\n", __func__); }