| /* |
| * Copyright (c) 2020, 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 <linux/memblock.h> |
| #include <linux/pm_runtime.h> |
| #include <linux/of.h> |
| #include <linux/spinlock.h> |
| #include <linux/of_device.h> |
| #include <soc/qcom/ramdump.h> |
| #include "commonmhitest.h" |
| |
| static struct mhi_channel_config mhitest_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 = 4, |
| .name = "DIAG", |
| .num_elements = 32, |
| .event_ring = 1, |
| .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 = 5, |
| .name = "DIAG", |
| .num_elements = 32, |
| .event_ring = 1, |
| .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 = 20, |
| .name = "IPCR", |
| .num_elements = 64, |
| .event_ring = 1, |
| .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 = true, |
| }, |
| { |
| .num = 21, |
| .name = "IPCR", |
| .num_elements = 64, |
| .event_ring = 1, |
| .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 = true, |
| .auto_start = true, |
| }, |
| }; |
| |
| static struct mhi_event_config mhitest_mhi_events[] = { |
| { |
| .num_elements = 32, |
| .irq_moderation_ms = 0, |
| .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, |
| .priority = 1, |
| .hardware_event = false, |
| .client_managed = false, |
| .offload_channel = false, |
| }, |
| }; |
| |
| static struct mhi_controller_config mhitest_mhi_config = { |
| .max_channels = 128, |
| .timeout_ms = 2000, |
| .use_bounce_buf = false, |
| .buf_len = 0, |
| .num_channels = ARRAY_SIZE(mhitest_mhi_channels), |
| .ch_cfg = mhitest_mhi_channels, |
| .num_events = ARRAY_SIZE(mhitest_mhi_events), |
| .event_cfg = mhitest_mhi_events, |
| }; |
| |
| static struct mhitest_msi_config msi_config = { |
| .total_vectors = 16, |
| .total_users = 1, |
| .users = (struct mhitest_msi_user[]) { |
| { .name = "MHI-TEST", .num_vectors = 16, .base_vector = 0 }, |
| }, |
| }; |
| |
| irqreturn_t mhitest_msi_handlr(int irq_number, void *dev) |
| { |
| printk("mhitest_msi_handlr irq_number==%d\n",irq_number); |
| return IRQ_HANDLED; |
| } |
| |
| int mhitest_dump_info(struct mhitest_platform *mplat, bool in_panic) |
| { |
| struct mhi_controller *mhi_ctrl; |
| struct image_info *rddm_img, *fw_img; |
| struct mhitest_dump_data *dump_data; |
| struct mhitest_dump_seg *dump_seg; |
| int ret, i; |
| u16 device_id; |
| |
| mhi_ctrl = mplat->mhi_ctrl; |
| pci_read_config_word(mplat->pci_dev, PCI_DEVICE_ID, &device_id); |
| MHITEST_EMERG("Read config space again, Device_id:0x%x\n", device_id); |
| if (device_id != mplat->pci_dev_id->device) { |
| MHITEST_ERR("Device Id does not match with Probe ID..\n"); |
| return -EIO; |
| } |
| |
| ret = mhi_download_rddm_image(mhi_ctrl, in_panic); |
| if (ret) { |
| MHITEST_ERR("Error .. not able to dload rddm img ret:%d\n", |
| ret); |
| return ret; |
| } |
| |
| MHITEST_LOG("Let's dump some more things...\n"); |
| mhi_debug_reg_dump(mhi_ctrl); |
| |
| rddm_img = mhi_ctrl->rddm_image; |
| fw_img = mhi_ctrl->fbc_image; |
| dump_data = &mplat->mhitest_rdinfo.dump_data; |
| dump_seg = mplat->mhitest_rdinfo.dump_data_vaddr; |
| |
| dump_data->nentries = 0; |
| MHITEST_EMERG("dump_dname:%s entries:%d\n", dump_data->name, |
| dump_data->nentries); |
| MHITEST_EMERG("----Collect FW image dump segment, nentries %d----\n", |
| fw_img->entries); |
| |
| for (i = 0; i < fw_img->entries; i++) { |
| dump_seg->address = fw_img->mhi_buf[i].dma_addr; |
| dump_seg->v_address = fw_img->mhi_buf[i].buf; |
| dump_seg->size = fw_img->mhi_buf[i].len; |
| dump_seg->type = FW_IMAGE; |
| MHITEST_EMERG("seg-%d:Address:0x%lx,v_Address %pK, size 0x%lx\n", |
| i, dump_seg->address, dump_seg->v_address, |
| dump_seg->size); |
| dump_seg++; |
| } |
| dump_data->nentries += fw_img->entries; |
| |
| MHITEST_EMERG("----Collect RDDM image dump segment, nentries %d----\n", |
| rddm_img->entries); |
| |
| for (i = 0; i < rddm_img->entries; i++) { |
| dump_seg->address = rddm_img->mhi_buf[i].dma_addr; |
| dump_seg->v_address = rddm_img->mhi_buf[i].buf; |
| dump_seg->size = rddm_img->mhi_buf[i].len; |
| dump_seg->type = FW_RDDM; |
| MHITEST_EMERG("seg-%d: address:0x%lx,v_address %pK,size 0x%lx\n", |
| i, dump_seg->address, dump_seg->v_address, |
| dump_seg->size); |
| dump_seg++; |
| } |
| dump_data->nentries += rddm_img->entries; |
| MHITEST_EMERG("----TODO/not need to Collect remote heap dump segment--\n"); |
| if (dump_data->nentries > 0) |
| mplat->mhitest_rdinfo.dump_data_valid = true; |
| |
| return 0; |
| } |
| |
| static int mhitest_get_msi_user(struct mhitest_platform *mplat, char *u_name, |
| int *num_vectors, u32 *user_base_data, u32 *base_vector) |
| { |
| int idx; |
| struct mhitest_msi_config *m_config = mplat->msi_config; |
| |
| if (!m_config) { |
| MHITEST_ERR("MSI config is NULL\n"); |
| return -ENODEV; |
| } |
| |
| for (idx = 0; idx < m_config->total_users; idx++) { |
| if (strcmp(u_name, m_config->users[idx].name) == 0) { |
| *num_vectors = m_config->users[idx].num_vectors; |
| *user_base_data = m_config->users[idx].base_vector |
| + mplat->msi_ep_base_data; |
| *base_vector = m_config->users[idx].base_vector; |
| MHITEST_VERB("Assign MSI to user:%s,num_vectors:%d,user_base_data:%u, base_vector: %u\n", |
| u_name, *num_vectors, *user_base_data, |
| *base_vector); |
| |
| return 0; |
| } |
| } |
| return -ENODEV; |
| } |
| |
| static int mhitest_get_msi_irq(struct device *device, unsigned int vector) |
| { |
| int irq_num; |
| struct pci_dev *pci_dev = to_pci_dev(device); |
| |
| irq_num = pci_irq_vector(pci_dev, vector); |
| MHITEST_VERB("Got irq_num :%d for vector : %d\n", irq_num, vector); |
| |
| return irq_num; |
| } |
| |
| int mhitest_suspend_pci_link(struct mhitest_platform *mplat) |
| { |
| /* |
| * no suspend resume as of now, return 0 |
| */ |
| MHITEST_LOG("No suspend resume now return 0\n"); |
| return 0; |
| } |
| |
| int mhitest_resume_pci_link(struct mhitest_platform *mplat) |
| { |
| /* |
| * no suspend resume as of now, return 0 |
| */ |
| MHITEST_LOG("No suspend resume now return 0\n"); |
| return 0; |
| } |
| |
| int mhitest_power_off_device(struct mhitest_platform *mplat) |
| { |
| /* |
| * add pinctrl code here if needed ! |
| */ |
| MHITEST_LOG("Powering OFF dummy!\n"); |
| return 0; |
| } |
| |
| int mhitest_power_on_device(struct mhitest_platform *mplat) |
| { |
| |
| /* |
| * add pinctrl code here if needed ! |
| */ |
| MHITEST_LOG("Powering ON dummy!\n"); |
| return 0; |
| } |
| |
| int mhitest_pci_get_link_status(struct mhitest_platform *mplat) |
| { |
| u16 link_stat; |
| int ret; |
| |
| ret = pcie_capability_read_word(mplat->pci_dev, PCI_EXP_LNKSTA, |
| &link_stat); |
| if (ret) { |
| MHITEST_ERR("PCIe link is not active !!ret:%d\n", ret); |
| return ret; |
| } |
| MHITEST_VERB("Get PCI link status register: %u\n", link_stat); |
| |
| mplat->def_link_speed = link_stat & PCI_EXP_LNKSTA_CLS; |
| mplat->def_link_width = |
| (link_stat & PCI_EXP_LNKSTA_NLW) >> PCI_EXP_LNKSTA_NLW_SHIFT; |
| |
| MHITEST_VERB("Default PCI link speed is 0x%x, link width is 0x%x\n", |
| mplat->def_link_speed, mplat->def_link_width); |
| |
| return ret; |
| } |
| |
| int mhitest_pci_get_mhi_msi(struct mhitest_platform *mplat) |
| { |
| int ret, *irq, num_vectors, i; |
| u32 user_base_data, base_vector; |
| |
| /* |
| * right now we have only one user in mhitest i.e MHI |
| */ |
| ret = mhitest_get_msi_user(mplat, "MHI-TEST", &num_vectors, |
| &user_base_data, &base_vector); |
| if (ret) { |
| MHITEST_ERR("Not able to get msi user ret:%d\n", ret); |
| return ret; |
| } |
| |
| MHITEST_LOG("MSI user:%s has num_vectors:%d and base_vector:%d\n", |
| "MHI-TEST", num_vectors, base_vector); |
| |
| irq = kcalloc(num_vectors, sizeof(int), GFP_KERNEL); |
| if (!irq) { |
| MHITEST_ERR("Error not able to allocate vectors\n"); |
| return -ENOMEM; |
| } |
| for (i = 0; i < num_vectors; i++) |
| irq[i] = mhitest_get_msi_irq(&mplat->pci_dev->dev, |
| base_vector + i); |
| |
| mplat->mhi_ctrl->irq = irq; |
| mplat->mhi_ctrl->nr_irqs = num_vectors; |
| |
| MHITEST_VERB("irq:[%p] msi_allocated :%d\n", mplat->mhi_ctrl->irq, |
| mplat->mhi_ctrl->nr_irqs); |
| |
| return 0; |
| } |
| |
| char *mhitest_get_reson_str(enum mhi_callback reason) |
| { |
| switch (reason) { |
| case MHI_CB_IDLE: |
| return "IDLE"; |
| case MHI_CB_EE_RDDM: |
| return "RDDM"; |
| case MHI_CB_EE_MISSION_MODE: |
| return "EE_MISSION_MODE"; |
| case MHI_CB_BW_REQ: |
| return "BW_REQ"; |
| case MHI_CB_SYS_ERROR: |
| return "SYS_ERROR"; |
| case MHI_CB_FATAL_ERROR: |
| return "FATAL_ERROR"; |
| default: |
| return "UNKNOWN"; |
| } |
| } |
| |
| char *mhitest_event_to_str(enum mhitest_event_type etype) |
| { |
| switch (etype) { |
| case MHITEST_RECOVERY_EVENT: |
| return "MHITEST_RECOVERY_EVENT"; |
| default: |
| return "UNKNOWN EVENT"; |
| } |
| } |
| |
| int mhitest_post_event(struct mhitest_platform *mplat, |
| struct mhitest_recovery_data *data, |
| enum mhitest_event_type etype, |
| u32 flags) |
| { |
| struct mhitest_driver_event *event; |
| int gfp = GFP_KERNEL; |
| unsigned long irq_flags; |
| |
| if (in_interrupt() || irqs_disabled()) |
| gfp = GFP_ATOMIC; |
| |
| event = kzalloc(sizeof(*event), gfp); |
| if (!event) |
| return -ENOMEM; |
| |
| event->type = etype; |
| event->data = data; |
| init_completion(&event->complete); |
| event->ret = -1; |
| event->sync = !!(flags); |
| |
| spin_lock_irqsave(&mplat->event_lock, irq_flags); |
| list_add_tail(&event->list, &mplat->event_list); |
| spin_unlock_irqrestore(&mplat->event_lock, irq_flags); |
| |
| queue_work(mplat->event_wq, &mplat->event_work); |
| if (flags) { |
| MHITEST_ERR("Waiting here to complete (%s) event ...\n", |
| mhitest_event_to_str(etype)); |
| wait_for_completion(&event->complete); |
| } |
| MHITEST_LOG("No waiting/Completed (%s) event ...ret:%d\n", |
| mhitest_event_to_str(etype), event->ret); |
| |
| return 0; |
| } |
| |
| void mhitest_sch_do_recovery(struct mhitest_platform *mplat, |
| enum mhitest_recovery_reason reason) |
| { |
| int gfp = GFP_KERNEL; |
| struct mhitest_recovery_data *data; |
| |
| if (in_interrupt() || irqs_disabled()) |
| gfp = GFP_ATOMIC; |
| |
| data = kzalloc(sizeof(*data), gfp); |
| if (!data) |
| return; |
| |
| data->reason = reason; |
| |
| mhitest_post_event(mplat, data, MHITEST_RECOVERY_EVENT, 0); |
| } |
| |
| int __must_check mhitest_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 (PCI_INVALID_READ(tmp)) |
| return -EIO; |
| |
| *out = tmp; |
| |
| return 0; |
| } |
| |
| void mhitest_write_reg(struct mhi_controller *mhi_cntrl, void __iomem *addr, |
| u32 val) |
| { |
| writel(val, addr); |
| } |
| |
| void mhitest_mhi_notify_status(struct mhi_controller *mhi_cntrl, |
| enum mhi_callback reason) |
| { |
| struct mhitest_platform *temp; |
| |
| temp = dev_get_drvdata(mhi_cntrl->cntrl_dev); |
| |
| MHITEST_VERB("Enter\n"); |
| if (reason > MHI_CB_FATAL_ERROR) { |
| MHITEST_ERR("Unsupported reason :%d\n", reason); |
| return; |
| } |
| MHITEST_EMERG(":[%s]- %d\n", mhitest_get_reson_str(reason), reason); |
| |
| switch (reason) { |
| case MHI_CB_IDLE: |
| case MHI_CB_SYS_ERROR: |
| return; |
| case MHI_CB_FATAL_ERROR: |
| reason = MHI_DEFAULT; |
| return; |
| case MHI_CB_EE_RDDM: |
| reason = MHI_RDDM; |
| break; |
| case MHI_CB_EE_MISSION_MODE: |
| MHITEST_VERB("MHI_CB_EE_MISSION_MODE\n"); |
| return; |
| case MHI_CB_BW_REQ: |
| MHITEST_VERB("MHI_CB_BW_REQ\n"); |
| return; |
| default: |
| MHITEST_ERR("Unsupported reason --reason:[%s]-(%d)\n", |
| mhitest_get_reson_str(reason), reason); |
| return; |
| } |
| mhitest_sch_do_recovery(temp, reason); |
| MHITEST_VERB("Exit\n"); |
| } |
| |
| int mhitest_mhi_pm_runtime_get(struct mhi_controller *mhi_cntrl) |
| { |
| return 0; |
| } |
| |
| void mhitest_mhi_pm_runtime_put_noidle(struct mhi_controller *mhi_cntrl) |
| { |
| } |
| |
| int mhitest_pci_register_mhi(struct mhitest_platform *mplat) |
| { |
| struct pci_dev *pci_dev = mplat->pci_dev; |
| struct mhi_controller *mhi_ctrl; |
| struct device_node *np; |
| int ret, len, sw, aw; |
| unsigned int *reg, *reg_end; |
| unsigned long start, size; |
| |
| mhi_ctrl = mhi_alloc_controller(); |
| if (!mhi_ctrl) { |
| MHITEST_ERR("Error: not able to allocate mhi_ctrl\n"); |
| return -ENOMEM; |
| } |
| MHITEST_LOG("MHI CTRL :%p\n", mhi_ctrl); |
| |
| mplat->mhi_ctrl = mhi_ctrl; |
| dev_set_drvdata(&pci_dev->dev, mplat); |
| mhi_ctrl->cntrl_dev = &pci_dev->dev; |
| |
| if (!mplat->fw_name) { |
| MHITEST_ERR("fw_name is NULLL\n"); |
| return -EINVAL; |
| } |
| mhi_ctrl->fw_image = mplat->fw_name; |
| |
| mhi_ctrl->regs = mplat->bar; |
| MHITEST_EMERG("BAR start at :%pa\n", &pci_resource_start(pci_dev, |
| PCI_BAR_NUM)); |
| |
| ret = mhitest_pci_get_mhi_msi(mplat); |
| if (ret) { |
| MHITEST_ERR("PCI get MHI MSI Failed ret:%d\n", ret); |
| goto out; |
| } |
| |
| np = of_find_node_by_type(NULL, "memory"); |
| if (!np) { |
| MHITEST_ERR("memory node not found !!\n"); |
| return 1; |
| } |
| |
| aw = of_n_addr_cells(np); |
| sw = of_n_size_cells(np); |
| |
| reg = (unsigned int *)of_get_property(np, "reg", &len); |
| if (!reg) { |
| pr_mhitest2("Couldn't get reg from mem node\n"); |
| return -ENOMEM; |
| } |
| reg_end = reg + len/4; |
| do { |
| start = of_read_number(reg, aw); |
| reg += aw; |
| size = of_read_number(reg, sw); |
| reg += sw; |
| } while (reg < reg_end); |
| |
| |
| mhi_ctrl->iova_start = (dma_addr_t)(start + 0x1000000); |
| mhi_ctrl->iova_stop = (dma_addr_t)(start + size); |
| |
| MHITEST_VERB("iova_start:%x iova_stop:%x\n", |
| (unsigned int)mhi_ctrl->iova_start, |
| (unsigned int)mhi_ctrl->iova_stop); |
| |
| mhi_ctrl->status_cb = mhitest_mhi_notify_status; |
| mhi_ctrl->runtime_get = mhitest_mhi_pm_runtime_get; |
| mhi_ctrl->runtime_put = mhitest_mhi_pm_runtime_put_noidle; |
| mhi_ctrl->read_reg = mhitest_read_reg; |
| mhi_ctrl->write_reg = mhitest_write_reg; |
| |
| mhi_ctrl->rddm_size = mplat->mhitest_rdinfo.ramdump_size; |
| mhi_ctrl->sbl_size = SZ_512K; |
| mhi_ctrl->seg_len = SZ_512K; |
| mhi_ctrl->fbc_download = true; |
| |
| ret = mhi_register_controller(mhi_ctrl, &mhitest_mhi_config); |
| if (ret) { |
| MHITEST_ERR("Failed to register mhi controller ret:%d\n", ret); |
| goto out; |
| } |
| MHITEST_VERB("GOOD!\n"); |
| return 0; |
| |
| out: |
| mhi_free_controller(mhi_ctrl); |
| return ret; |
| } |
| |
| int mhitest_pci_en_msi(struct mhitest_platform *temp) |
| { |
| struct pci_dev *pci_dev = temp->pci_dev; |
| int num_vectors, ret = 0; |
| struct msi_desc *msi_desc; |
| |
| temp->msi_config = &msi_config; |
| if (!temp->msi_config) { |
| MHITEST_ERR("MSI config is NULL\n"); |
| return -EINVAL; |
| } |
| |
| num_vectors = pci_alloc_irq_vectors(pci_dev, |
| temp->msi_config->total_vectors, |
| temp->msi_config->total_vectors, |
| PCI_IRQ_MSI | PCI_IRQ_LEGACY); |
| if (num_vectors != temp->msi_config->total_vectors) { |
| MHITEST_ERR("No Enough MSI vectors req:%d and allocated:%d\n", |
| temp->msi_config->total_vectors, num_vectors); |
| if (num_vectors >= 0) |
| ret = -EINVAL; |
| temp->msi_config = NULL; |
| goto out; |
| } |
| |
| msi_desc = irq_get_msi_desc(pci_dev->irq); |
| if (!msi_desc) { |
| MHITEST_ERR("MSI desc is NULL\n"); |
| goto free_irq_vectors; |
| } |
| |
| return 0; |
| |
| free_irq_vectors: |
| pci_free_irq_vectors(pci_dev); |
| out: |
| return ret; |
| } |
| |
| int mhitest_pci_enable_bus(struct mhitest_platform *temp) |
| { |
| struct pci_dev *pci_dev = temp->pci_dev; |
| u16 device_id; |
| int ret; |
| u32 pci_dma_mask = PCI_DMA_MASK_64_BIT; |
| |
| MHITEST_VERB("Going for PCI Enable bus\n"); |
| pci_read_config_word(pci_dev, PCI_DEVICE_ID, &device_id); |
| MHITEST_EMERG("Read config space, Device_id:0x%x\n", device_id); |
| |
| if (device_id != temp->pci_dev_id->device) { |
| MHITEST_ERR("Device Id does not match with Probe ID..\n"); |
| return -EIO; |
| } |
| |
| ret = pci_assign_resource(pci_dev, PCI_BAR_NUM); |
| if (ret) { |
| MHITEST_ERR("Failed to assign PCI resource Error:%d\n", ret); |
| goto out; |
| } |
| |
| ret = pci_enable_device(pci_dev); |
| if (ret) { |
| MHITEST_ERR("Failed to Enable PCI device Error:%d\n", ret); |
| goto out; |
| } |
| |
| ret = pci_request_region(pci_dev, PCI_BAR_NUM, "mhitest_region"); |
| if (ret) { |
| MHITEST_ERR("Failed to req. region Error:%d\n", ret); |
| goto out2; |
| } |
| |
| ret = pci_set_dma_mask(pci_dev, DMA_BIT_MASK(pci_dma_mask)); |
| if (ret) { |
| MHITEST_ERR("Failed to set dma mask:(%d) ret:%d\n", |
| pci_dma_mask, ret); |
| goto out3; |
| } |
| |
| pci_set_master(pci_dev); |
| |
| temp->bar = pci_iomap(pci_dev, PCI_BAR_NUM, 0); |
| if (!temp->bar) { |
| MHITEST_ERR("Failed to do PCI IO map ..\n"); |
| ret = -EIO; |
| goto out4; |
| } |
| |
| pci_save_state(pci_dev); |
| temp->pci_dev_default_state = pci_store_saved_state(pci_dev); |
| |
| return 0; |
| |
| out4: |
| pci_clear_master(pci_dev); |
| out3: |
| pci_release_region(pci_dev, PCI_BAR_NUM); |
| out2: |
| pci_disable_device(pci_dev); |
| out: |
| return ret; |
| } |
| |
| void mhitest_global_soc_reset(struct mhitest_platform *mplat) |
| { |
| u32 val; |
| u32 delay; |
| |
| val = readl(PCIE_SOC_GLOBAL_RESET_ADDRESS + mplat->bar); |
| |
| val |= PCIE_SOC_GLOBAL_RESET_V; |
| |
| writel(val, PCIE_SOC_GLOBAL_RESET_ADDRESS + mplat->bar); |
| |
| /* TODO: exact time to sleep is uncertain */ |
| delay = 10; |
| mdelay(delay); |
| |
| /* Need to toggle V bit back otherwise stuck in reset status */ |
| val &= ~PCIE_SOC_GLOBAL_RESET_V; |
| |
| writel(val, PCIE_SOC_GLOBAL_RESET_ADDRESS + mplat->bar); |
| |
| mdelay(delay); |
| |
| val = readl(PCIE_SOC_GLOBAL_RESET_ADDRESS + mplat->bar); |
| if (val == 0xffffffff) |
| MHITEST_EMERG("link down error during global reset\n"); |
| } |
| |
| void mhitest_clear_mhi_vector(struct mhitest_platform *mplat) |
| { |
| writel(0, PCIE_TXVECDB + mplat->bar); |
| writel(0, PCIE_TXVECSTATUS + mplat->bar); |
| writel(0, PCIE_RXVECDB + mplat->bar); |
| writel(0, PCIE_RXVECSTATUS + mplat->bar); |
| } |
| |
| void mhitest_set_mhi_mhictrl_reset(struct mhitest_platform *mplat) |
| { |
| u32 val; |
| |
| val = readl(MHISTATUS + mplat->bar); |
| |
| MHITEST_LOG("MHISTATUS 0x%x\n", val); |
| |
| /* Observed on QCN9000 that after SOC_GLOBAL_RESET, MHISTATUS |
| * has SYSERR bit set and thus need to set MHICTRL_RESET |
| * to clear SYSERR. |
| */ |
| writel(MHICTRL_RESET_MASK, MHICTRL + mplat->bar); |
| |
| mdelay(10); |
| } |
| |
| void mhitest_pci_sw_reset(struct mhitest_platform *mplat) |
| { |
| mhitest_global_soc_reset(mplat); |
| mhitest_clear_mhi_vector(mplat); |
| mhitest_global_soc_reset(mplat); |
| mhitest_set_mhi_mhictrl_reset(mplat); |
| /* TODO: Clear pci dbg registers */ |
| } |
| |
| void mhitest_pci_disable_bus(struct mhitest_platform *mplat) |
| { |
| struct pci_dev *pci_dev = mplat->pci_dev; |
| |
| if (mplat->bar) { |
| pci_iounmap(pci_dev, mplat->bar); |
| mplat->bar = NULL; |
| } |
| |
| pci_clear_master(pci_dev); |
| pci_release_region(pci_dev, PCI_BAR_NUM); |
| if (pci_is_enabled(pci_dev)) |
| pci_disable_device(pci_dev); |
| } |
| |
| int mhitest_unregister_ramdump(struct mhitest_platform *mplat) |
| { |
| struct mhitest_ramdump_info *mhitest_rdinfo = &mplat->mhitest_rdinfo; |
| |
| if (mhitest_rdinfo->ramdump_dev) |
| destroy_ramdump_device(mhitest_rdinfo->ramdump_dev); |
| kfree(mhitest_rdinfo->dump_data_vaddr); |
| mhitest_rdinfo->dump_data_vaddr = NULL; |
| mhitest_rdinfo->dump_data_valid = false; |
| |
| return 0; |
| } |
| |
| int mhitest_register_ramdump(struct mhitest_platform *mplat) |
| { |
| struct mhitest_ramdump_info *mhitest_rdinfo; |
| struct mhitest_dump_data *dump_data; |
| struct device *dev = &mplat->plat_dev->dev; |
| u32 ramdump_size = 0; |
| int ret; |
| |
| mhitest_rdinfo = &mplat->mhitest_rdinfo; |
| dump_data = &mhitest_rdinfo->dump_data; |
| if (!dev->of_node) { |
| MHITEST_ERR("of node is null\n"); |
| return -ENOMEM; |
| } |
| if (!dev->of_node->name) { |
| MHITEST_ERR("of node->name is NULL\n"); |
| return -ENOMEM; |
| } |
| |
| ret = of_property_read_u32(dev->of_node, "qcom,wlan-ramdump-dynamic", |
| &ramdump_size); |
| if (ret == 0) |
| mhitest_rdinfo->ramdump_size = ramdump_size; |
| |
| mhitest_rdinfo->dump_data_vaddr = kzalloc(0x1000, GFP_KERNEL); |
| if (!mhitest_rdinfo->dump_data_vaddr) |
| return -ENOMEM; |
| |
| dump_data->paddr = virt_to_phys(mhitest_rdinfo->dump_data_vaddr); |
| |
| /* |
| * TODO: used ramdom version and magic..etc check and correct it. |
| */ |
| dump_data->version = 0x00; |
| dump_data->magic = 0xAA55AA55; |
| dump_data->seg_version = 0x1; |
| strlcpy(dump_data->name, "mhitest_mod", |
| sizeof(dump_data->name)); |
| mhitest_rdinfo->ramdump_dev = |
| create_ramdump_device(mplat->mhitest_ss_desc_name, |
| mplat->subsys_handle->dev.parent); |
| if (!mhitest_rdinfo->ramdump_dev) { |
| MHITEST_ERR("Failed to create ramdump device!\n"); |
| ret = -ENOMEM; |
| goto free_ramdump; |
| } |
| |
| MHITEST_LOG("Ramdump registered ramdump_size:0x%x\n", ramdump_size); |
| |
| return 0; |
| |
| free_ramdump: |
| kfree(mhitest_rdinfo->dump_data_vaddr); |
| mhitest_rdinfo->dump_data_vaddr = NULL; |
| |
| return ret; |
| } |
| |
| int mhitest_prepare_pci_mhi_msi(struct mhitest_platform *temp) |
| { |
| int ret; |
| |
| MHITEST_VERB("Enter\n"); |
| if (!temp->pci_dev) { |
| MHITEST_ERR("pci_dev is NULLL\n"); |
| return -EINVAL; |
| } |
| |
| ret = mhitest_register_ramdump(temp); |
| if (ret) { |
| MHITEST_ERR("Error not able to reg ramdump. ret :%d\n", ret); |
| goto unreg_rdump; |
| } |
| |
| /* |
| * 1. pci enable bus |
| */ |
| ret = mhitest_pci_enable_bus(temp); |
| if (ret) { |
| MHITEST_ERR("Error mhitest_pci_enable. ret :%d\n", ret); |
| goto out; |
| } |
| |
| /* |
| * go with some condition for specific device for msi en |
| * 2. pci enable msi |
| */ |
| ret = mhitest_pci_en_msi(temp); |
| if (ret) { |
| MHITEST_ERR("Error mhitest_pci_enable_msi. ret :%d\n", ret); |
| goto disable_bus; |
| } |
| |
| /* |
| * 3. pci register mhi -of_controller |
| */ |
| ret = mhitest_pci_register_mhi(temp); |
| if (ret) { |
| MHITEST_ERR("Error pci register mhi. ret :%d\n", ret); |
| goto disable_bus; |
| } |
| |
| ret = mhitest_pci_get_link_status(temp); |
| if (ret) { |
| MHITEST_ERR("Error not able to get pci link status:%d\n", ret); |
| goto out; |
| } |
| |
| ret = mhitest_suspend_pci_link(temp); |
| if (ret) { |
| MHITEST_ERR("Error not able to suspend pci:%d\n", ret); |
| goto out; |
| } |
| |
| mhitest_power_off_device(temp); |
| MHITEST_VERB("Exit\n"); |
| |
| return 0; |
| |
| disable_bus: |
| mhitest_pci_disable_bus(temp); |
| unreg_rdump: |
| mhitest_unregister_ramdump(temp); |
| out: |
| return ret; |
| } |
| |
| char *mhitest_get_mhi_state_str(enum mhi_state state) |
| { |
| switch (state) { |
| case MHI_INIT: |
| return "MHI_INIT"; |
| case MHI_DEINIT: |
| return "MHI_DEINIT"; |
| case MHI_POWER_ON: |
| return "MHI_POWER_ON"; |
| case MHI_POWER_OFF: |
| return "MHI_POWER_OFF"; |
| case MHI_FORCE_POWER_OFF: |
| return "MHI_FORCE_POWER_OFF"; |
| default: |
| return "UNKNOWN"; |
| } |
| } |
| |
| int mhitest_pci_set_mhi_state(struct mhitest_platform *mplat, |
| enum MHI_STATE state) |
| { |
| int ret = 0; |
| int i = 0; |
| |
| if (state < 0) { |
| MHITEST_ERR("Invalid MHI state : %d\n", state); |
| return -EINVAL; |
| } |
| |
| MHITEST_EMERG("Set MHI_STATE- [%s]-(%d)\n", |
| mhitest_get_mhi_state_str(state), state); |
| |
| switch (state) { |
| case MHI_INIT: |
| ret = mhi_prepare_for_power_up(mplat->mhi_ctrl); |
| |
| /* Registering dummy interrupt handler for vectors |
| * 3 to 16 to demonstrate the usage of multiple |
| * GIC-MSI interrupts |
| */ |
| if (!ret && mplat->msi_config->total_vectors > 3) { |
| for (i = 3; i < mplat->msi_config->total_vectors; i++) { |
| ret = request_irq(mplat->mhi_ctrl->irq[i], |
| mhitest_msi_handlr, |
| IRQF_SHARED, |
| "mhi_rem_vec", |
| mplat->mhi_ctrl); |
| if (ret) { |
| MHITEST_ERR("Error requesting irq:%d for vector:%d----error_code-%d\n", |
| mplat->mhi_ctrl->irq[i], i, ret); |
| } |
| } |
| |
| /* Updating ret to 0. |
| * Since vectors 3 t0 16 are unused any failure |
| * in registering interrupt handler should not |
| * affect the flow of FBC. |
| */ |
| ret = 0; |
| } |
| break; |
| case MHI_POWER_ON: |
| ret = mhi_sync_power_up(mplat->mhi_ctrl); |
| break; |
| case MHI_DEINIT: |
| mhi_unprepare_after_power_down(mplat->mhi_ctrl); |
| if (mplat->msi_config->total_vectors > 3) { |
| for (i = 3; i < mplat->msi_config->total_vectors; i++) { |
| free_irq(mplat->mhi_ctrl->irq[i], mplat->mhi_ctrl); |
| } |
| } |
| ret = 0; |
| break; |
| case MHI_POWER_OFF: |
| mhi_power_down(mplat->mhi_ctrl, true); |
| ret = 0; |
| break; |
| case MHI_FORCE_POWER_OFF: |
| mhi_power_down(mplat->mhi_ctrl, false); |
| ret = 0; |
| break; |
| default: |
| MHITEST_ERR("I dont know the state:%d!!\n", state); |
| ret = -EINVAL; |
| } |
| return ret; |
| } |
| |
| extern int timeout_ms; |
| int mhitest_pci_start_mhi(struct mhitest_platform *mplat) |
| { |
| int ret; |
| |
| MHITEST_VERB("Enter\n"); |
| |
| if (!mplat->mhi_ctrl) { |
| MHITEST_ERR("mhi_ctrl is NULL .. returning..\n"); |
| return -EINVAL; |
| } |
| |
| mplat->mhi_ctrl->timeout_ms = timeout_ms; |
| |
| ret = mhitest_pci_set_mhi_state(mplat, MHI_INIT); |
| if (ret) { |
| MHITEST_ERR("Error not able to set mhi init. returning..\n"); |
| goto out1; |
| } |
| ret = mhitest_pci_set_mhi_state(mplat, MHI_POWER_ON); |
| if (ret) { |
| MHITEST_ERR("Error not able to POWER ON\n"); |
| if (ret == -ETIMEDOUT) { |
| /* |
| * Though it is ETIMEOUT we are returning 0 here so that |
| * we should be able to do rcremove and rmmod. |
| * rcremove api's are not exported and so mhitest driver |
| * cannot call them. |
| * |
| * Printing Error message here to inform the user. |
| */ |
| MHITEST_ERR("###### -ETIMEDOUT ERRRORR, do rcremove and rmmod to clean-up\n"); |
| ret = 0; |
| } |
| goto out1; |
| } |
| |
| MHITEST_VERB("Exit\n"); |
| return ret; |
| |
| out1: |
| MHITEST_VERB("Exit-Error\n"); |
| return ret; |
| } |
| |
| int mhitest_prepare_start_mhi(struct mhitest_platform *mplat) |
| { |
| int ret; |
| bool skip_pci_sw_reset; |
| struct device *dev = &mplat->plat_dev->dev; |
| |
| /* |
| * 1. power on, resume link if needed |
| */ |
| ret = mhitest_power_on_device(mplat); |
| if (ret) { |
| MHITEST_ERR("Error ret:%d\n", ret); |
| goto out; |
| } |
| ret = mhitest_resume_pci_link(mplat); |
| if (ret) { |
| MHITEST_ERR("Error ret: %d\n", ret); |
| goto out; |
| } |
| |
| skip_pci_sw_reset = of_property_read_bool(dev->of_node, |
| "skip-pci-sw-reset"); |
| if (!skip_pci_sw_reset) |
| mhitest_pci_sw_reset(mplat); |
| |
| /* |
| * 2. start mhi |
| */ |
| ret = mhitest_pci_start_mhi(mplat); |
| if (ret) { |
| MHITEST_ERR("Error ret: %d\n", ret); |
| goto out; |
| } |
| |
| out: |
| return ret; |
| } |
| |
| int mhitest_pci_probe(struct pci_dev *pci_dev, const struct pci_device_id *id) |
| { |
| struct mhitest_platform *temp = get_mhitest_mplat(id->device); |
| |
| MHITEST_VERB("Enter\n"); |
| |
| if (!temp) { |
| MHITEST_ERR("temp is null..\n"); |
| return -ENOMEM; |
| } |
| MHITEST_LOG("Vendor:0x%x Device:0x%x probed id:0x%x d_instance:%d\n", |
| pci_dev->vendor, pci_dev->device, id->device, |
| temp->d_instance); |
| /* store this tho main struct*/ |
| temp->pci_dev = pci_dev; |
| temp->device_id = pci_dev->device; |
| temp->pci_dev_id = id; |
| MHITEST_VERB("Exit\n"); |
| return 0; |
| } |
| |
| extern int debug_lvl; |
| |
| int mhitest_pci_probe2(struct pci_dev *pci_dev, const struct pci_device_id *id) |
| { |
| struct mhitest_platform *mplat; |
| struct platform_device *plat_dev = get_plat_device(); |
| int ret; |
| |
| MHITEST_EMERG("--->\n"); |
| mplat = devm_kzalloc(&plat_dev->dev, sizeof(*mplat), GFP_KERNEL); |
| if (!mplat) { |
| MHITEST_ERR("Error: not able to allocate memory\n"); |
| ret = -ENOMEM; |
| goto fail_probe; |
| } |
| |
| mplat->mhitest_klog_lvl = debug_lvl; |
| |
| if (plat_dev != NULL) |
| mplat->plat_dev = plat_dev; |
| else |
| MHITEST_ERR("Error: platform dev is broken\n"); |
| |
| platform_set_drvdata(plat_dev, mplat); |
| |
| mplat->pci_dev = pci_dev; |
| mplat->device_id = pci_dev->device; |
| mplat->pci_dev_id = id; |
| |
| MHITEST_LOG("Vendor ID:0x%x Device ID:0x%x Probed Device ID:0x%x\n", |
| pci_dev->vendor, pci_dev->device, id->device); |
| |
| ret = mhitest_event_work_init(mplat); |
| if (ret) |
| goto out1; |
| |
| ret = mhitest_store_mplat(mplat); |
| if (ret) { |
| MHITEST_ERR("Error ret:%d\n", ret); |
| goto out1; |
| } |
| |
| ret = mhitest_subsystem_register(mplat); |
| if (ret) { |
| MHITEST_ERR("Error subsystem register: ret:%d\n", ret); |
| goto out1; |
| } |
| |
| MHITEST_EMERG("<---done\n"); |
| return 0; |
| |
| out1: |
| kfree(mplat); |
| fail_probe: |
| return ret; |
| } |
| |
| void mhitest_pci_remove(struct pci_dev *pci_dev) |
| { |
| struct mhitest_platform *mplat; |
| |
| MHITEST_LOG("mhitest PCI removing\n"); |
| |
| mplat = get_mhitest_mplat_by_pcidev(pci_dev); |
| if (mplat) { |
| mhitest_subsystem_unregister(mplat); |
| mhitest_event_work_deinit(mplat); |
| pci_load_and_free_saved_state(pci_dev, &mplat->pci_dev_default_state); |
| mhitest_free_mplat(mplat); |
| } |
| } |
| |
| static const struct pci_device_id mhitest_pci_id_table[] = { |
| {QTI_PCI_VENDOR_ID, QCN90xx_DEVICE_ID, PCI_ANY_ID, PCI_ANY_ID}, |
| {QTI_PCI_VENDOR_ID, QCN92XX_DEVICE_ID, PCI_ANY_ID, PCI_ANY_ID}, |
| }; |
| |
| struct pci_driver mhitest_pci_driver = { |
| .name = "mhitest_pci", |
| .probe = mhitest_pci_probe2, |
| .remove = mhitest_pci_remove, |
| .id_table = mhitest_pci_id_table, |
| }; |
| |
| int mhitest_pci_register(void) |
| { |
| int ret; |
| |
| ret = pci_register_driver(&mhitest_pci_driver); |
| if (ret) { |
| MHITEST_ERR("Error ret:%d\n", ret); |
| goto out; |
| } |
| out: |
| return ret; |
| } |
| |
| void mhitest_pci_unregister(void) |
| { |
| MHITEST_VERB("\n"); |
| pci_unregister_driver(&mhitest_pci_driver); |
| } |