| // SPDX-License-Identifier: GPL-2.0-only |
| /* Copyright (c) 2018-2019, The Linux Foundation. All rights reserved.*/ |
| |
| #include <asm/arch_timer.h> |
| #include <linux/debugfs.h> |
| #include <linux/delay.h> |
| #include <linux/device.h> |
| #include <linux/dma-direction.h> |
| #include <linux/list.h> |
| #include <linux/of.h> |
| #include <linux/memblock.h> |
| #include <linux/module.h> |
| #include <linux/pci.h> |
| #include <linux/pm_runtime.h> |
| #include <linux/slab.h> |
| #include <linux/uaccess.h> |
| #include <linux/mhi.h> |
| #include <linux/msi.h> |
| #include <linux/interrupt.h> |
| #include <linux/notifier.h> |
| #include <linux/io.h> |
| #include <linux/gpio/consumer.h> |
| #include <linux/of_gpio.h> |
| #include "mhi_qti.h" |
| #include "../core/internal.h" |
| |
| #define WDOG_TIMEOUT 30 |
| #define MHI_PANIC_TIMER_STEP 1000 |
| |
| volatile int mhi_panic_timeout; |
| |
| int ap2mdm_gpio, mdm2ap_gpio; |
| bool mhi_ssr_negotiate; |
| |
| void __iomem *wdt; |
| |
| static struct kobject *mhi_kobj; |
| |
| struct notifier_block *global_mhi_panic_notifier; |
| |
| static ssize_t sysfs_show(struct kobject *kobj, struct kobj_attribute *attr, |
| char *buf); |
| static ssize_t sysfs_store(struct kobject *kobj, struct kobj_attribute *attr, |
| const char *buf, size_t count); |
| |
| static struct kobj_attribute mhi_attr = |
| __ATTR(mhi_panic_timeout, 0660, sysfs_show, sysfs_store); |
| |
| static ssize_t sysfs_show(struct kobject *kobj, struct kobj_attribute *attr, |
| char *buf) |
| { |
| return snprintf(buf, PAGE_SIZE, "%d\n", mhi_panic_timeout); |
| } |
| |
| static ssize_t sysfs_store(struct kobject *kobj, struct kobj_attribute *attr, |
| const char *buf, size_t count) |
| { |
| if (sscanf(buf, "%du", &mhi_panic_timeout) != 1) { |
| pr_err("failed to read timeout value from string\n"); |
| return -EINVAL; |
| } |
| return count; |
| } |
| |
| struct firmware_info { |
| unsigned int dev_id; |
| const char *fw_image; |
| const char *edl_image; |
| }; |
| |
| /* set ptr to control private data */ |
| static inline void mhi_controller_set_devdata(struct mhi_controller *mhi_cntrl, |
| void *priv) |
| { |
| mhi_cntrl->priv_data = priv; |
| } |
| |
| static struct mhi_channel_config mhi_sdx_mhi_channels[] = { |
| { |
| .num = 0, |
| .name = "LOOPBACK", |
| .num_elements = 32, |
| .event_ring = 0, |
| .dir = DMA_TO_DEVICE, |
| .ee_mask = 0x4, |
| .pollcfg = 0, |
| .doorbell = MHI_DB_BRST_DISABLE, |
| .lpm_notify = false, |
| .offload_channel = false, |
| .doorbell_mode_switch = false, |
| .auto_queue = false, |
| .auto_start = false, |
| }, |
| { |
| .num = 1, |
| .name = "LOOPBACK", |
| .num_elements = 32, |
| .event_ring = 0, |
| .dir = DMA_FROM_DEVICE, |
| .ee_mask = 0x4, |
| .pollcfg = 0, |
| .doorbell = MHI_DB_BRST_DISABLE, |
| .lpm_notify = false, |
| .offload_channel = false, |
| .doorbell_mode_switch = false, |
| .auto_queue = false, |
| .auto_start = false, |
| }, |
| { |
| .num = 14, |
| .name = "QMI0", |
| .num_elements = 64, |
| .event_ring = 1, |
| .dir = DMA_TO_DEVICE, |
| .doorbell = MHI_DB_BRST_DISABLE, |
| .ee_mask = 0x4, |
| .pollcfg = 0, |
| .lpm_notify = false, |
| .offload_channel = false, |
| .doorbell_mode_switch = false, |
| .auto_queue = false, |
| .auto_start = false, |
| }, |
| { |
| .num = 15, |
| .name = "QMI0", |
| .num_elements = 64, |
| .event_ring = 2, |
| .dir = DMA_FROM_DEVICE, |
| .doorbell = MHI_DB_BRST_DISABLE, |
| .ee_mask = 0x4, |
| .pollcfg = 0, |
| .lpm_notify = false, |
| .offload_channel = false, |
| .doorbell_mode_switch = false, |
| .auto_queue = false, |
| .auto_start = false, |
| }, |
| { |
| .num = 16, |
| .name = "QMI1", |
| .num_elements = 64, |
| .event_ring = 3, |
| .dir = DMA_TO_DEVICE, |
| .doorbell = MHI_DB_BRST_DISABLE, |
| .ee_mask = 0x4, |
| .pollcfg = 0, |
| .lpm_notify = false, |
| .offload_channel = false, |
| .doorbell_mode_switch = false, |
| .auto_queue = false, |
| .auto_start = false, |
| }, |
| { |
| .num = 17, |
| .name = "QMI1", |
| .num_elements = 64, |
| .event_ring = 3, |
| .dir = DMA_FROM_DEVICE, |
| .doorbell = MHI_DB_BRST_DISABLE, |
| .ee_mask = 0x4, |
| .pollcfg = 0, |
| .lpm_notify = false, |
| .offload_channel = false, |
| .doorbell_mode_switch = false, |
| .auto_queue = false, |
| .auto_start = false, |
| }, |
| { |
| .num = 18, |
| .name = "IP_CTRL", |
| .num_elements = 64, |
| .event_ring = 1, |
| .dir = DMA_TO_DEVICE, |
| .doorbell = MHI_DB_BRST_DISABLE, |
| .ee_mask = 0x4, |
| .pollcfg = 0, |
| .lpm_notify = false, |
| .offload_channel = false, |
| .doorbell_mode_switch = false, |
| .auto_queue = false, |
| .auto_start = false, |
| }, |
| { |
| .num = 19, |
| .name = "IP_CTRL", |
| .num_elements = 64, |
| .event_ring = 1, |
| .dir = DMA_FROM_DEVICE, |
| .doorbell = MHI_DB_BRST_DISABLE, |
| .ee_mask = 0x4, |
| .pollcfg = 0, |
| .lpm_notify = false, |
| .offload_channel = false, |
| .doorbell_mode_switch = false, |
| .auto_queue = true, |
| .auto_start = false, |
| }, |
| { |
| .num = 20, |
| .name = "IPCR", |
| .num_elements = 64, |
| .event_ring = 2, |
| .dir = DMA_TO_DEVICE, |
| .doorbell = MHI_DB_BRST_DISABLE, |
| .ee_mask = 0x4, |
| .pollcfg = 0, |
| .lpm_notify = false, |
| .offload_channel = false, |
| .doorbell_mode_switch = false, |
| .auto_queue = false, |
| .auto_start = true, |
| }, |
| { |
| .num = 21, |
| .name = "IPCR", |
| .num_elements = 64, |
| .event_ring = 2, |
| .dir = DMA_FROM_DEVICE, |
| .doorbell = MHI_DB_BRST_DISABLE, |
| .ee_mask = 0x4, |
| .pollcfg = 0, |
| .lpm_notify = false, |
| .offload_channel = false, |
| .doorbell_mode_switch = false, |
| .auto_queue = true, |
| .auto_start = true, |
| }, |
| { |
| .num = 46, |
| .name = "IP_SW0", |
| .num_elements = 512, |
| .event_ring = 4, |
| .dir = DMA_TO_DEVICE, |
| .doorbell = MHI_DB_BRST_DISABLE, |
| .ee_mask = 0x4, |
| .pollcfg = 0, |
| .lpm_notify = false, |
| .offload_channel = false, |
| .doorbell_mode_switch = false, |
| .auto_queue = false, |
| .auto_start = false, |
| }, |
| { |
| .num = 47, |
| .name = "IP_SW0", |
| .num_elements = 512, |
| .event_ring = 5, |
| .dir = DMA_FROM_DEVICE, |
| .doorbell = MHI_DB_BRST_DISABLE, |
| .ee_mask = 0x4, |
| .pollcfg = 0, |
| .lpm_notify = false, |
| .offload_channel = false, |
| .doorbell_mode_switch = false, |
| .auto_queue = false, |
| .auto_start = false, |
| }, |
| { |
| .num = 100, |
| .name = "IP_HW0", |
| .num_elements = 512, |
| .event_ring = 6, |
| .dir = DMA_TO_DEVICE, |
| .doorbell = MHI_DB_BRST_ENABLE, |
| .ee_mask = 0x4, |
| .pollcfg = 0, |
| .lpm_notify = false, |
| .offload_channel = false, |
| .doorbell_mode_switch = true, |
| .auto_queue = false, |
| .auto_start = false, |
| }, |
| { |
| .num = 101, |
| .name = "IP_HW0", |
| .num_elements = 512, |
| .event_ring = 7, |
| .dir = DMA_FROM_DEVICE, |
| .doorbell = MHI_DB_BRST_ENABLE, |
| .ee_mask = 0x4, |
| .pollcfg = 0, |
| .lpm_notify = false, |
| .offload_channel = false, |
| .doorbell_mode_switch = false, |
| .auto_queue = false, |
| .auto_start = false, |
| }, |
| { |
| .num = 105, |
| .name = "RMNET_CTL", |
| .num_elements = 128, |
| .event_ring = 8, |
| .dir = DMA_TO_DEVICE, |
| .doorbell = MHI_DB_BRST_DISABLE, |
| .ee_mask = 0x4, |
| .pollcfg = 0, |
| .lpm_notify = false, |
| .offload_channel = false, |
| .doorbell_mode_switch = false, |
| .auto_queue = false, |
| .auto_start = false, |
| }, |
| { |
| .num = 106, |
| .name = "RMNET_CTL", |
| .num_elements = 128, |
| .event_ring = 9, |
| .dir = DMA_FROM_DEVICE, |
| .doorbell = MHI_DB_BRST_DISABLE, |
| .ee_mask = 0x4, |
| .pollcfg = 0, |
| .lpm_notify = false, |
| .offload_channel = false, |
| .doorbell_mode_switch = false, |
| .auto_queue = false, |
| .auto_start = false, |
| }, |
| |
| }; |
| |
| static struct mhi_event_config mhi_sdx_mhi_events[] = { |
| { |
| .num_elements = 32, |
| .irq_moderation_ms = 1, |
| .irq = 1, |
| .mode = MHI_DB_BRST_DISABLE, |
| .data_type = MHI_ER_CTRL, |
| .hardware_event = false, |
| .client_managed = false, |
| .offload_channel = false, |
| }, |
| { |
| .num_elements = 256, |
| .irq_moderation_ms = 1, |
| .irq = 2, |
| .mode = MHI_DB_BRST_DISABLE, |
| .hardware_event = false, |
| .client_managed = false, |
| .offload_channel = false, |
| }, |
| { |
| .num_elements = 256, |
| .irq_moderation_ms = 1, |
| .irq = 3, |
| .mode = MHI_DB_BRST_DISABLE, |
| .hardware_event = false, |
| .client_managed = false, |
| .offload_channel = false, |
| }, |
| { |
| .num_elements = 256, |
| .irq_moderation_ms = 1, |
| .irq = 4, |
| .mode = MHI_DB_BRST_DISABLE, |
| .hardware_event = false, |
| .client_managed = false, |
| .offload_channel = false, |
| }, |
| { |
| .num_elements = 1024, |
| .irq_moderation_ms = 5, |
| .irq = 5, |
| .channel = 46, |
| .mode = MHI_DB_BRST_DISABLE, |
| .hardware_event = false, |
| .client_managed = false, |
| .offload_channel = false, |
| }, |
| { |
| .num_elements = 1024, |
| .irq_moderation_ms = 5, |
| .irq = 6, |
| .channel = 47, |
| .mode = MHI_DB_BRST_DISABLE, |
| .hardware_event = false, |
| .client_managed = true, |
| .offload_channel = false, |
| }, |
| { |
| .num_elements = 1024, |
| .irq_moderation_ms = 5, |
| .irq = 5, |
| .channel = 100, |
| .mode = MHI_DB_BRST_ENABLE, |
| .hardware_event = true, |
| .client_managed = false, |
| .offload_channel = false, |
| }, |
| { |
| .num_elements = 1024, |
| .irq_moderation_ms = 5, |
| .irq = 6, |
| .channel = 101, |
| .mode = MHI_DB_BRST_ENABLE, |
| .hardware_event = true, |
| .client_managed = true, |
| .offload_channel = false, |
| }, |
| { |
| .num_elements = 1024, |
| .irq_moderation_ms = 1, |
| .irq = 8, |
| .channel = 105, |
| .mode = MHI_DB_BRST_DISABLE, |
| .hardware_event = true, |
| .client_managed = false, |
| .offload_channel = false, |
| }, |
| { |
| .num_elements = 1024, |
| .irq_moderation_ms = 0, |
| .irq = 9, |
| .channel = 106, |
| .mode = MHI_DB_BRST_DISABLE, |
| .hardware_event = true, |
| .client_managed = false, |
| .offload_channel = false, |
| }, |
| }; |
| |
| static struct mhi_controller_config mhi_sdx_mhi_config = { |
| .max_channels = 128, |
| .timeout_ms = 6000, |
| .use_bounce_buf = false, |
| .buf_len = 0, |
| .num_channels = ARRAY_SIZE(mhi_sdx_mhi_channels), |
| .ch_cfg = mhi_sdx_mhi_channels, |
| .num_events = ARRAY_SIZE(mhi_sdx_mhi_events), |
| .event_cfg = mhi_sdx_mhi_events, |
| }; |
| |
| static const struct firmware_info firmware_table[] = { |
| {.dev_id = 0x306, .fw_image = "sdx55m/sbl1.mbn"}, |
| {.dev_id = 0x305, .fw_image = "sdx50m/sbl1.mbn"}, |
| {.dev_id = 0x304, .fw_image = "sbl.mbn", .edl_image = "edl.mbn"}, |
| /* default, set to debug.mbn */ |
| {.fw_image = "debug.mbn"}, |
| }; |
| |
| static int debug_mode; |
| module_param_named(debug_mode, debug_mode, int, 0644); |
| |
| int mhi_debugfs_trigger_m0(void *data, u64 val) |
| { |
| #ifdef CONFIG_PM |
| 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); |
| #endif |
| |
| return 0; |
| } |
| DEFINE_SIMPLE_ATTRIBUTE(debugfs_trigger_m0_fops, NULL, |
| mhi_debugfs_trigger_m0, "%llu\n"); |
| |
| int mhi_debugfs_trigger_m3(void *data, u64 val) |
| { |
| #ifdef CONFIG_PM |
| 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); |
| #endif |
| |
| return 0; |
| } |
| DEFINE_SIMPLE_ATTRIBUTE(debugfs_trigger_m3_fops, NULL, |
| mhi_debugfs_trigger_m3, "%llu\n"); |
| |
| 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; |
| |
| #ifdef CONFIG_PM |
| pm_runtime_mark_last_busy(&pci_dev->dev); |
| pm_runtime_dont_use_autosuspend(&pci_dev->dev); |
| pm_runtime_disable(&pci_dev->dev); |
| #endif |
| pci_free_irq_vectors(pci_dev); |
| kfree(mhi_cntrl->irq); |
| mhi_cntrl->irq = NULL; |
| iounmap(mhi_cntrl->regs); |
| iounmap(wdt); |
| 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 len; |
| int i, nr_vectors; |
| |
| 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); |
| |
| mhi_cntrl->base_addr = pci_resource_start(pci_dev, mhi_dev->resn); |
| len = pci_resource_len(pci_dev, mhi_dev->resn); |
| mhi_cntrl->regs = ioremap_nocache(mhi_cntrl->base_addr, len); |
| if (!mhi_cntrl->regs) { |
| MHI_ERR("Error ioremap region\n"); |
| goto error_ioremap; |
| } |
| |
| /* |
| * Alloc one MSI vector for BHI + one vector per event ring, ideally... |
| */ |
| mhi_cntrl->nr_irqs = mhi_sdx_mhi_config.num_events + 1; |
| mhi_cntrl->nr_irqs = 1U << get_count_order(mhi_cntrl->nr_irqs); |
| |
| nr_vectors = pci_alloc_irq_vectors(pci_dev, 1, mhi_cntrl->nr_irqs, |
| PCI_IRQ_MSI); |
| if (nr_vectors < 0) { |
| MHI_ERR("Error allocating MSI vectors, ret:%d\n", nr_vectors); |
| goto error_req_msi; |
| } |
| |
| if (nr_vectors < mhi_cntrl->nr_irqs) { |
| MHI_LOG("Not enough MSI vectors (%d/%d), use shared MHI\n", |
| nr_vectors, mhi_sdx_mhi_config.num_events); |
| } |
| |
| 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++) { |
| int vector = i >= nr_vectors ? (nr_vectors - 1) : i; |
| mhi_cntrl->irq[i] = pci_irq_vector(pci_dev, vector); |
| if (mhi_cntrl->irq[i] < 0) { |
| ret = mhi_cntrl->irq[i]; |
| goto error_get_irq_vec; |
| } |
| } |
| |
| dev_set_drvdata(&pci_dev->dev, mhi_cntrl); |
| |
| #ifdef CONFIG_PM |
| /* configure runtime pm */ |
| pm_runtime_set_autosuspend_delay(&pci_dev->dev, MHI_RPM_SUSPEND_TMR_MS); |
| pm_runtime_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: |
| 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, mhi_dev->resn); |
| |
| return ret; |
| } |
| |
| #ifdef CONFIG_PM |
| static int mhi_runtime_suspend(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; |
| } |
| |
| 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_idle(struct device *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_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; |
| |
| 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) |
| { |
| int ret; |
| |
| MHI_LOG("Entered\n"); |
| |
| /* if rpm status still active then force suspend */ |
| if (!pm_runtime_status_suspended(dev)) { |
| ret = mhi_runtime_suspend(dev); |
| if (ret) { |
| MHI_LOG("suspend failed ret:%d\n", ret); |
| return ret; |
| } |
| } |
| |
| pm_runtime_set_suspended(dev); |
| pm_runtime_disable(dev); |
| |
| MHI_LOG("Exit\n"); |
| return 0; |
| } |
| #endif |
| |
| static int mhi_power_up(struct mhi_controller *mhi_cntrl) |
| { |
| enum mhi_state dev_state = mhi_get_mhi_state(mhi_cntrl); |
| const u32 delayus = 10; |
| int itr = DIV_ROUND_UP(mhi_cntrl->timeout_ms * 1000, delayus); |
| int ret; |
| |
| /* |
| * It's possible device did not go thru a cold reset before |
| * power up and still in error state. If device in error state, |
| * we need to trigger a soft reset before continue with power |
| * up |
| */ |
| if (dev_state == MHI_STATE_SYS_ERR) { |
| mhi_set_mhi_state(mhi_cntrl, MHI_STATE_RESET); |
| while (itr--) { |
| dev_state = mhi_get_mhi_state(mhi_cntrl); |
| if (dev_state != MHI_STATE_SYS_ERR) |
| break; |
| usleep_range(delayus, delayus << 1); |
| } |
| /* device still in error state, abort power up */ |
| if (dev_state == MHI_STATE_SYS_ERR) |
| return -EIO; |
| } |
| |
| ret = mhi_async_power_up(mhi_cntrl); |
| |
| return ret; |
| } |
| |
| int __must_check mhi_sdx_read_reg(struct mhi_controller *mhi_cntrl, |
| void __iomem *addr, u32 *out) |
| { |
| u32 tmp = readl(addr); |
| |
| /* If the value is invalid, the link is down */ |
| if (tmp == U32_MAX) |
| return -EIO; |
| |
| *out = tmp; |
| |
| return 0; |
| } |
| |
| void mhi_sdx_write_reg(struct mhi_controller *mhi_cntrl, void __iomem *addr, |
| u32 val) |
| { |
| writel(val, addr); |
| } |
| |
| #ifdef CONFIG_PM |
| static int mhi_runtime_get(struct mhi_controller *mhi_cntrl) |
| { |
| struct mhi_dev *mhi_dev = mhi_cntrl->priv_data; |
| struct device *dev = &mhi_dev->pci_dev->dev; |
| |
| return pm_runtime_get(dev); |
| } |
| |
| static void mhi_runtime_put(struct mhi_controller *mhi_cntrl) |
| { |
| struct mhi_dev *mhi_dev = mhi_cntrl->priv_data; |
| struct device *dev = &mhi_dev->pci_dev->dev; |
| |
| pm_runtime_put_noidle(dev); |
| } |
| |
| static void mhi_status_cb(struct mhi_controller *mhi_cntrl, |
| enum mhi_callback reason) |
| { |
| struct mhi_dev *mhi_dev = mhi_cntrl->priv_data; |
| 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); |
| } |
| } |
| #endif |
| |
| static ssize_t timeout_ms_show(struct device *dev, |
| struct device_attribute *attr, |
| char *buf) |
| { |
| struct mhi_device *mhi_dev = to_mhi_device(dev); |
| struct mhi_controller *mhi_cntrl = mhi_dev->mhi_cntrl; |
| |
| /* buffer provided by sysfs has a minimum size of PAGE_SIZE */ |
| return snprintf(buf, PAGE_SIZE, "%u\n", mhi_cntrl->timeout_ms); |
| } |
| |
| static ssize_t timeout_ms_store(struct device *dev, |
| struct device_attribute *attr, |
| const char *buf, |
| size_t count) |
| { |
| struct mhi_device *mhi_dev = to_mhi_device(dev); |
| struct mhi_controller *mhi_cntrl = mhi_dev->mhi_cntrl; |
| u32 timeout_ms; |
| |
| if (kstrtou32(buf, 0, &timeout_ms) < 0) |
| return -EINVAL; |
| |
| mhi_cntrl->timeout_ms = timeout_ms; |
| |
| return count; |
| } |
| static DEVICE_ATTR_RW(timeout_ms); |
| |
| static ssize_t power_up_store(struct device *dev, |
| struct device_attribute *attr, |
| const char *buf, |
| size_t count) |
| { |
| int ret; |
| struct mhi_device *mhi_dev = to_mhi_device(dev); |
| struct mhi_controller *mhi_cntrl = mhi_dev->mhi_cntrl; |
| |
| ret = mhi_power_up(mhi_cntrl); |
| if (ret) |
| return ret; |
| |
| return count; |
| } |
| static DEVICE_ATTR_WO(power_up); |
| |
| static struct attribute *mhi_attrs[] = { |
| &dev_attr_timeout_ms.attr, |
| &dev_attr_power_up.attr, |
| NULL |
| }; |
| |
| static const struct attribute_group mhi_group = { |
| .attrs = mhi_attrs, |
| }; |
| |
| /* allocate mhi controller to register */ |
| struct mhi_controller *mhi_alloc_dev_and_controller(size_t size) |
| { |
| struct mhi_controller *mhi_cntrl; |
| |
| mhi_cntrl = kzalloc(size + sizeof(*mhi_cntrl), GFP_KERNEL); |
| |
| if (mhi_cntrl && size) |
| mhi_controller_set_devdata(mhi_cntrl, mhi_cntrl + 1); |
| |
| return mhi_cntrl; |
| } |
| |
| static struct mhi_controller *dt_register_mhi_controller(struct pci_dev *pci_dev) |
| { |
| struct mhi_controller *mhi_cntrl; |
| struct mhi_dev *mhi_dev; |
| struct device_node *of_node = pci_dev->dev.of_node; |
| bool use_bb; |
| u64 addr_win[2]; |
| int ret; |
| |
| if (!of_node) |
| return ERR_PTR(-ENODEV); |
| |
| mhi_cntrl = mhi_alloc_dev_and_controller(sizeof(*mhi_dev)); |
| if (!mhi_cntrl) |
| return ERR_PTR(-ENOMEM); |
| |
| mhi_dev = mhi_controller_get_devdata(mhi_cntrl); |
| mhi_cntrl->cntrl_dev = &pci_dev->dev; |
| |
| use_bb = of_property_read_bool(of_node, "mhi,use-bb"); |
| |
| /* |
| * if s1 translation enabled or using bounce buffer pull iova addr |
| * from dt |
| */ |
| if (use_bb || (mhi_dev->smmu_cfg & MHI_SMMU_ATTACH && |
| !(mhi_dev->smmu_cfg & MHI_SMMU_S1_BYPASS))) { |
| ret = of_property_count_elems_of_size(of_node, "qti,addr-win", |
| sizeof(addr_win)); |
| if (ret != 1) |
| return ERR_PTR(-EINVAL); |
| ret = of_property_read_u64_array(of_node, "qti,addr-win", |
| addr_win, 2); |
| if (ret) |
| return ERR_PTR(-EINVAL); |
| } else { |
| addr_win[0] = memblock_start_of_DRAM(); |
| addr_win[1] = memblock_end_of_DRAM(); |
| } |
| |
| mhi_dev->iova_start = addr_win[0]; |
| mhi_dev->iova_stop = addr_win[1]; |
| |
| /* |
| * 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 |
| */ |
| if (mhi_dev->smmu_cfg & MHI_SMMU_ATTACH && |
| !(mhi_dev->smmu_cfg & MHI_SMMU_S1_BYPASS)) |
| mhi_cntrl->iova_start = 0; |
| else |
| mhi_cntrl->iova_start = addr_win[0]; |
| |
| mhi_cntrl->iova_stop = mhi_dev->iova_stop; |
| mhi_cntrl->of_node = of_node; |
| |
| mhi_dev->pci_dev = pci_dev; |
| |
| #ifdef CONFIG_PM |
| /* 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; |
| #endif |
| mhi_cntrl->read_reg = mhi_sdx_read_reg; |
| mhi_cntrl->write_reg = mhi_sdx_write_reg; |
| |
| return mhi_cntrl; |
| } |
| |
| static int mhi_panic_handler(struct notifier_block *this, |
| unsigned long event, void *ptr) |
| { |
| int mdmreboot = 0, i; |
| struct gpio_desc *ap2mdm, *mdm2ap; |
| |
| ap2mdm = gpio_to_desc(ap2mdm_gpio); |
| if (IS_ERR(ap2mdm)) |
| return PTR_ERR(ap2mdm); |
| |
| mdm2ap = gpio_to_desc(mdm2ap_gpio); |
| if (IS_ERR(mdm2ap)) |
| return PTR_ERR(mdm2ap); |
| |
| |
| /* |
| * ap2mdm_status is set to 0 to indicate the SDX |
| * that IPQ has crashed. Now the SDX has to take |
| * dump. |
| */ |
| gpiod_set_value(ap2mdm, 0); |
| |
| if (mhi_panic_timeout) { |
| if (mhi_panic_timeout > WDOG_TIMEOUT) |
| writel(0, wdt); |
| |
| for (i = 0; i < mhi_panic_timeout; i++) { |
| |
| /* |
| * Waiting for the mdm2ap status to be 0 |
| * which indicates that SDX is rebooting and entering |
| * the crashdump path. |
| */ |
| if (!mdmreboot && !gpiod_get_value(mdm2ap)) { |
| MHI_LOG("MDM is rebooting and entering the crashdump path\n"); |
| mdmreboot = 1; |
| } |
| |
| |
| /* |
| * Waiting for the mdm2ap status to be 1 |
| * which indicates that SDX has completed crashdump |
| * collection and booted successfully. |
| */ |
| if (mdmreboot && gpiod_get_value(mdm2ap)) { |
| MHI_LOG("MDM has completed crashdump collection and booted successfully\n"); |
| break; |
| } |
| |
| mdelay(MHI_PANIC_TIMER_STEP); |
| } |
| |
| if (mhi_panic_timeout > WDOG_TIMEOUT) |
| writel(1, wdt); |
| } |
| |
| return NOTIFY_DONE; |
| } |
| |
| void mhi_wdt_panic_handler(void) |
| { |
| mhi_panic_handler(global_mhi_panic_notifier, |
| 0, NULL); |
| } |
| EXPORT_SYMBOL(mhi_wdt_panic_handler); |
| |
| int mhi_pci_probe(struct pci_dev *pci_dev, |
| const struct pci_device_id *device_id) |
| { |
| struct mhi_controller *mhi_cntrl; |
| const struct firmware_info *firmware_info; |
| struct mhi_dev *mhi_dev; |
| int i, ret; |
| |
| /* Fix me: Add check to see if already registered */ |
| mhi_cntrl = dt_register_mhi_controller(pci_dev); |
| if (IS_ERR(mhi_cntrl)) |
| return PTR_ERR(mhi_cntrl); |
| |
| mhi_dev = mhi_controller_get_devdata(mhi_cntrl); |
| mhi_dev->powered_on = true; |
| |
| ret = mhi_arch_pcie_init(mhi_cntrl); |
| if (ret) |
| return ret; |
| |
| mhi_cntrl->cntrl_dev = &pci_dev->dev; |
| ret = mhi_init_pci_dev(mhi_cntrl); |
| if (ret) |
| goto error_init_pci; |
| |
| ret = mhi_register_controller(mhi_cntrl, &mhi_sdx_mhi_config); |
| if (ret) |
| goto error_register; |
| |
| for (i = 0; i < ARRAY_SIZE(firmware_table); i++) { |
| firmware_info = firmware_table + i; |
| /* debug mode always use default */ |
| if (!debug_mode && pci_dev->device == firmware_info->dev_id) |
| break; |
| } |
| mhi_cntrl->fw_image = firmware_info->fw_image; |
| mhi_cntrl->edl_image = firmware_info->edl_image; |
| |
| if (sysfs_create_group(&mhi_cntrl->mhi_dev->dev.kobj, &mhi_group)) |
| MHI_ERR("Error while creating the sysfs group\n"); |
| |
| /* start power up sequence */ |
| if (!debug_mode) { |
| ret = mhi_power_up(mhi_cntrl); |
| if (ret) |
| goto error_power_up; |
| } |
| |
| #ifdef CONFIG_PM |
| pm_runtime_mark_last_busy(&pci_dev->dev); |
| pm_runtime_allow(&pci_dev->dev); |
| #endif |
| |
| mhi_ssr_negotiate = of_property_read_bool(mhi_cntrl->of_node, "mhi,ssr-negotiate"); |
| |
| if (mhi_ssr_negotiate) { |
| ap2mdm_gpio = of_get_named_gpio(mhi_cntrl->of_node, "ap2mdm-gpio", 0); |
| if (ap2mdm_gpio < 0) |
| pr_err("AP2MDM GPIO not configured\n"); |
| |
| mdm2ap_gpio = of_get_named_gpio(mhi_cntrl->of_node, "mdm2ap-gpio", 0); |
| if (mdm2ap_gpio < 0) |
| pr_err("MDM2AP GPIO not configured\n"); |
| |
| mhi_cntrl->mhi_panic_notifier.notifier_call = mhi_panic_handler; |
| |
| global_mhi_panic_notifier = &(mhi_cntrl->mhi_panic_notifier); |
| |
| ret = atomic_notifier_chain_register(&panic_notifier_list, |
| &mhi_cntrl->mhi_panic_notifier); |
| MHI_LOG("MHI panic notifier registered\n"); |
| |
| wdt = ioremap(0x0B017008, 4); |
| |
| /* Creating a directory in /sys/kernel/ */ |
| mhi_kobj = kobject_create_and_add("mhi", kernel_kobj); |
| |
| if (mhi_kobj) { |
| /* Creating sysfs file for mhi_panic_timeout */ |
| if (sysfs_create_file(mhi_kobj, &mhi_attr.attr)) { |
| MHI_ERR("Cannot create sysfs file for mhi_panic_timeout\n"); |
| kobject_put(mhi_kobj); |
| } |
| } else { |
| MHI_ERR("Unable to create mhi sysfs entry\n"); |
| } |
| } |
| MHI_LOG("Return successful\n"); |
| |
| return 0; |
| |
| error_power_up: |
| mhi_deinit_pci_dev(mhi_cntrl); |
| |
| error_register: |
| mhi_free_controller(mhi_cntrl); |
| |
| error_init_pci: |
| mhi_arch_pcie_deinit(mhi_cntrl); |
| |
| return ret; |
| } |
| |
| void mhi_pci_device_removed(struct pci_dev *pci_dev) |
| { |
| struct mhi_controller *mhi_cntrl; |
| struct gpio_desc *mdm2ap; |
| bool graceful = 0; |
| |
| mhi_cntrl = dev_get_drvdata(&pci_dev->dev); |
| |
| if (mhi_cntrl) { |
| |
| struct mhi_dev *mhi_dev = mhi_controller_get_devdata(mhi_cntrl); |
| |
| #ifdef CONFIG_PM |
| pm_stay_awake(&mhi_cntrl->mhi_dev->dev); |
| |
| /* if link is in drv suspend, wake it up */ |
| pm_runtime_get_sync(&pci_dev->dev); |
| #endif |
| |
| mutex_lock(&mhi_cntrl->pm_mutex); |
| if (!mhi_dev->powered_on) { |
| MHI_LOG("Not in active state\n"); |
| mutex_unlock(&mhi_cntrl->pm_mutex); |
| #ifdef CONFIG_PM |
| pm_runtime_put_noidle(&pci_dev->dev); |
| #endif |
| return; |
| } |
| mhi_dev->powered_on = false; |
| mutex_unlock(&mhi_cntrl->pm_mutex); |
| |
| #ifdef CONFIG_PM |
| pm_runtime_put_noidle(&pci_dev->dev); |
| #endif |
| |
| if (mhi_ssr_negotiate) { |
| mdm2ap = gpio_to_desc(mdm2ap_gpio); |
| if (IS_ERR(mdm2ap)) |
| MHI_ERR("Unable to acquire mdm2ap_gpio"); |
| |
| graceful = gpiod_get_value(mdm2ap); |
| } |
| |
| MHI_LOG("Triggering shutdown process\n"); |
| mhi_power_down(mhi_cntrl, graceful); |
| |
| /* turn the link off */ |
| mhi_deinit_pci_dev(mhi_cntrl); |
| mhi_arch_link_off(mhi_cntrl, false); |
| |
| mhi_arch_pcie_deinit(mhi_cntrl); |
| |
| #ifdef CONFIG_PM |
| pm_relax(&mhi_cntrl->mhi_dev->dev); |
| #endif |
| |
| kobject_put(mhi_kobj); |
| |
| mhi_unregister_controller(mhi_cntrl); |
| } |
| } |
| |
| #ifdef CONFIG_PM |
| 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) |
| }; |
| #endif |
| |
| static struct pci_device_id mhi_pcie_device_id[] = { |
| {PCI_DEVICE(MHI_PCIE_VENDOR_ID, 0x0300)}, |
| {PCI_DEVICE(MHI_PCIE_VENDOR_ID, 0x0301)}, |
| {PCI_DEVICE(MHI_PCIE_VENDOR_ID, 0x0303)}, |
| {PCI_DEVICE(MHI_PCIE_VENDOR_ID, 0x0304)}, |
| {PCI_DEVICE(MHI_PCIE_VENDOR_ID, 0x0305)}, |
| {PCI_DEVICE(MHI_PCIE_VENDOR_ID, 0x0306)}, |
| {PCI_DEVICE(MHI_PCIE_VENDOR_ID, MHI_PCIE_DEBUG_ID)}, |
| {0}, |
| }; |
| |
| static struct pci_driver mhi_pcie_driver = { |
| .name = "mhi", |
| .id_table = mhi_pcie_device_id, |
| .probe = mhi_pci_probe, |
| .remove = mhi_pci_device_removed, |
| #ifdef CONFIG_PM |
| .driver = { |
| .pm = &pm_ops |
| } |
| #endif |
| }; |
| |
| module_pci_driver(mhi_pcie_driver); |