333 lines
9.5 KiB
C
333 lines
9.5 KiB
C
|
/* Copyright (c) 2014-2015, 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/pci.h>
|
||
|
#include <linux/gpio.h>
|
||
|
#include <linux/module.h>
|
||
|
#include <linux/platform_device.h>
|
||
|
#include <linux/msm-bus.h>
|
||
|
#include <linux/delay.h>
|
||
|
#include <linux/debugfs.h>
|
||
|
#include <linux/pm_runtime.h>
|
||
|
#include <linux/interrupt.h>
|
||
|
#include <linux/slab.h>
|
||
|
#include <linux/err.h>
|
||
|
|
||
|
#define CREATE_TRACE_POINTS
|
||
|
#include "mhi_trace.h"
|
||
|
|
||
|
#include "mhi_sys.h"
|
||
|
#include "mhi.h"
|
||
|
#include "mhi_macros.h"
|
||
|
#include "mhi_hwio.h"
|
||
|
#include "mhi_bhi.h"
|
||
|
|
||
|
struct mhi_pcie_devices mhi_devices;
|
||
|
|
||
|
static int mhi_pci_probe(struct pci_dev *pcie_device,
|
||
|
const struct pci_device_id *mhi_device_id);
|
||
|
static int __exit mhi_plat_remove(struct platform_device *pdev);
|
||
|
void *mhi_ipc_log;
|
||
|
|
||
|
static DEFINE_PCI_DEVICE_TABLE(mhi_pcie_device_id) = {
|
||
|
{ MHI_PCIE_VENDOR_ID, MHI_PCIE_DEVICE_ID_9x35,
|
||
|
PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0},
|
||
|
{ MHI_PCIE_VENDOR_ID, MHI_PCIE_DEVICE_ID_ZIRC,
|
||
|
PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0},
|
||
|
{ 0, },
|
||
|
};
|
||
|
|
||
|
static const struct of_device_id mhi_plat_match[] = {
|
||
|
{
|
||
|
.compatible = "qcom,mhi",
|
||
|
},
|
||
|
{},
|
||
|
};
|
||
|
|
||
|
static void mhi_msm_fixup(struct pci_dev *pcie_device)
|
||
|
{
|
||
|
if (pcie_device->class == PCI_CLASS_NOT_DEFINED) {
|
||
|
mhi_log(MHI_MSG_INFO, "Setting msm pcie class\n");
|
||
|
pcie_device->class = PCI_CLASS_STORAGE_SCSI;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
int mhi_ctxt_init(struct mhi_pcie_dev_info *mhi_pcie_dev)
|
||
|
{
|
||
|
int ret_val = 0;
|
||
|
u32 i = 0, j = 0;
|
||
|
u32 requested_msi_number = 32, actual_msi_number = 0;
|
||
|
struct mhi_device_ctxt *mhi_dev_ctxt = NULL;
|
||
|
struct pci_dev *pcie_device = NULL;
|
||
|
|
||
|
if (NULL == mhi_pcie_dev)
|
||
|
return -EINVAL;
|
||
|
pcie_device = mhi_pcie_dev->pcie_device;
|
||
|
|
||
|
ret_val = mhi_init_pcie_device(mhi_pcie_dev);
|
||
|
if (ret_val) {
|
||
|
mhi_log(MHI_MSG_CRITICAL,
|
||
|
"Failed to initialize pcie device, ret %d\n",
|
||
|
ret_val);
|
||
|
return -ENODEV;
|
||
|
}
|
||
|
ret_val = mhi_init_device_ctxt(mhi_pcie_dev, &mhi_pcie_dev->mhi_ctxt);
|
||
|
if (ret_val) {
|
||
|
mhi_log(MHI_MSG_CRITICAL,
|
||
|
"Failed to initialize main MHI ctxt ret %d\n",
|
||
|
ret_val);
|
||
|
goto msi_config_err;
|
||
|
}
|
||
|
ret_val = mhi_esoc_register(&mhi_pcie_dev->mhi_ctxt);
|
||
|
if (ret_val) {
|
||
|
mhi_log(MHI_MSG_ERROR,
|
||
|
"Failed to register with esoc ret %d.\n",
|
||
|
ret_val);
|
||
|
}
|
||
|
mhi_pcie_dev->mhi_ctxt.bus_scale_table =
|
||
|
msm_bus_cl_get_pdata(mhi_pcie_dev->plat_dev);
|
||
|
mhi_pcie_dev->mhi_ctxt.bus_client =
|
||
|
msm_bus_scale_register_client(
|
||
|
mhi_pcie_dev->mhi_ctxt.bus_scale_table);
|
||
|
if (!mhi_pcie_dev->mhi_ctxt.bus_client) {
|
||
|
mhi_log(MHI_MSG_CRITICAL,
|
||
|
"Could not register for bus control ret: %d.\n",
|
||
|
mhi_pcie_dev->mhi_ctxt.bus_client);
|
||
|
} else {
|
||
|
ret_val = mhi_set_bus_request(&mhi_pcie_dev->mhi_ctxt, 1);
|
||
|
if (ret_val)
|
||
|
mhi_log(MHI_MSG_CRITICAL,
|
||
|
"Could not set bus frequency ret: %d\n",
|
||
|
ret_val);
|
||
|
}
|
||
|
|
||
|
device_disable_async_suspend(&pcie_device->dev);
|
||
|
ret_val = pci_enable_msi_range(pcie_device, 1, requested_msi_number);
|
||
|
if (IS_ERR_VALUE(ret_val)) {
|
||
|
mhi_log(MHI_MSG_ERROR,
|
||
|
"Failed to enable MSIs for pcie dev ret_val %d.\n",
|
||
|
ret_val);
|
||
|
goto msi_config_err;
|
||
|
} else if (ret_val) {
|
||
|
mhi_log(MHI_MSG_INFO,
|
||
|
"Hrmmm, got fewer MSIs than we requested. Requested %d, got %d.\n",
|
||
|
requested_msi_number, ret_val);
|
||
|
actual_msi_number = ret_val;
|
||
|
} else {
|
||
|
mhi_log(MHI_MSG_VERBOSE,
|
||
|
"Got all requested MSIs, moving on\n");
|
||
|
}
|
||
|
mhi_dev_ctxt = &mhi_pcie_dev->mhi_ctxt;
|
||
|
|
||
|
for (j = 0; j < mhi_dev_ctxt->mmio_info.nr_event_rings; j++) {
|
||
|
mhi_log(MHI_MSG_VERBOSE,
|
||
|
"MSI_number = %d, event ring number = %d\n",
|
||
|
mhi_dev_ctxt->ev_ring_props[j].msi_vec, j);
|
||
|
|
||
|
ret_val = request_irq(pcie_device->irq +
|
||
|
mhi_dev_ctxt->ev_ring_props[j].msi_vec,
|
||
|
mhi_dev_ctxt->ev_ring_props[j].mhi_handler_ptr,
|
||
|
IRQF_NO_SUSPEND,
|
||
|
"mhi_drv",
|
||
|
(void *)&pcie_device->dev);
|
||
|
if (ret_val) {
|
||
|
mhi_log(MHI_MSG_ERROR,
|
||
|
"Failed to register handler for MSI ret_val = %d\n",
|
||
|
ret_val);
|
||
|
goto msi_config_err;
|
||
|
}
|
||
|
}
|
||
|
mhi_pcie_dev->core.irq_base = pcie_device->irq;
|
||
|
mhi_log(MHI_MSG_VERBOSE,
|
||
|
"Setting IRQ Base to 0x%x\n", mhi_pcie_dev->core.irq_base);
|
||
|
mhi_pcie_dev->core.max_nr_msis = requested_msi_number;
|
||
|
ret_val = mhi_init_pm_sysfs(&pcie_device->dev);
|
||
|
if (ret_val != 0) {
|
||
|
mhi_log(MHI_MSG_ERROR, "Failed to setup sysfs.\n");
|
||
|
goto sysfs_config_err;
|
||
|
}
|
||
|
if (!mhi_init_debugfs(&mhi_pcie_dev->mhi_ctxt))
|
||
|
mhi_log(MHI_MSG_ERROR, "Failed to init debugfs.\n");
|
||
|
|
||
|
mhi_pcie_dev->mhi_ctxt.mmio_info.mmio_addr =
|
||
|
mhi_pcie_dev->core.bar0_base;
|
||
|
pcie_device->dev.platform_data = &mhi_pcie_dev->mhi_ctxt;
|
||
|
mhi_pcie_dev->mhi_ctxt.dev_info->plat_dev->dev.platform_data =
|
||
|
&mhi_pcie_dev->mhi_ctxt;
|
||
|
if (mhi_pcie_dev->mhi_ctxt.base_state == STATE_TRANSITION_BHI) {
|
||
|
ret_val = bhi_probe(mhi_pcie_dev);
|
||
|
if (ret_val) {
|
||
|
mhi_log(MHI_MSG_ERROR, "Failed to initialize BHI.\n");
|
||
|
goto mhi_state_transition_error;
|
||
|
}
|
||
|
}
|
||
|
if (MHI_STATUS_SUCCESS != mhi_reg_notifiers(&mhi_pcie_dev->mhi_ctxt)) {
|
||
|
mhi_log(MHI_MSG_ERROR, "Failed to register for notifiers\n");
|
||
|
return MHI_STATUS_ERROR;
|
||
|
}
|
||
|
mhi_log(MHI_MSG_INFO,
|
||
|
"Finished all driver probing returning ret_val %d.\n",
|
||
|
ret_val);
|
||
|
return ret_val;
|
||
|
|
||
|
mhi_state_transition_error:
|
||
|
kfree(mhi_dev_ctxt->state_change_work_item_list.q_lock);
|
||
|
kfree(mhi_dev_ctxt->mhi_ev_wq.mhi_event_wq);
|
||
|
kfree(mhi_dev_ctxt->mhi_ev_wq.state_change_event);
|
||
|
kfree(mhi_dev_ctxt->mhi_ev_wq.m0_event);
|
||
|
kfree(mhi_dev_ctxt->mhi_ev_wq.m3_event);
|
||
|
kfree(mhi_dev_ctxt->mhi_ev_wq.bhi_event);
|
||
|
dma_free_coherent(&mhi_dev_ctxt->dev_info->plat_dev->dev,
|
||
|
mhi_dev_ctxt->dev_space.dev_mem_len,
|
||
|
mhi_dev_ctxt->dev_space.dev_mem_start,
|
||
|
mhi_dev_ctxt->dev_space.dma_dev_mem_start);
|
||
|
kfree(mhi_dev_ctxt->mhi_cmd_mutex_list);
|
||
|
kfree(mhi_dev_ctxt->mhi_chan_mutex);
|
||
|
kfree(mhi_dev_ctxt->mhi_ev_spinlock_list);
|
||
|
kfree(mhi_dev_ctxt->ev_ring_props);
|
||
|
mhi_rem_pm_sysfs(&pcie_device->dev);
|
||
|
sysfs_config_err:
|
||
|
for (; i >= 0; --i)
|
||
|
free_irq(pcie_device->irq + i, &pcie_device->dev);
|
||
|
debugfs_remove_recursive(mhi_pcie_dev->mhi_ctxt.mhi_parent_folder);
|
||
|
msi_config_err:
|
||
|
pci_disable_device(pcie_device);
|
||
|
return ret_val;
|
||
|
}
|
||
|
|
||
|
static const struct dev_pm_ops pm_ops = {
|
||
|
.runtime_suspend = mhi_runtime_suspend,
|
||
|
.runtime_resume = mhi_runtime_resume,
|
||
|
.runtime_idle = NULL,
|
||
|
};
|
||
|
|
||
|
static struct pci_driver mhi_pcie_driver = {
|
||
|
.name = "mhi_pcie_drv",
|
||
|
.id_table = mhi_pcie_device_id,
|
||
|
.probe = mhi_pci_probe,
|
||
|
.suspend = mhi_pci_suspend,
|
||
|
.resume = mhi_pci_resume,
|
||
|
};
|
||
|
|
||
|
static int mhi_pci_probe(struct pci_dev *pcie_device,
|
||
|
const struct pci_device_id *mhi_device_id)
|
||
|
{
|
||
|
int ret_val = 0;
|
||
|
struct mhi_pcie_dev_info *mhi_pcie_dev = NULL;
|
||
|
struct platform_device *plat_dev;
|
||
|
u32 nr_dev = mhi_devices.nr_of_devices;
|
||
|
|
||
|
mhi_log(MHI_MSG_INFO, "Entering.\n");
|
||
|
mhi_pcie_dev = &mhi_devices.device_list[mhi_devices.nr_of_devices];
|
||
|
if (mhi_devices.nr_of_devices + 1 > MHI_MAX_SUPPORTED_DEVICES) {
|
||
|
mhi_log(MHI_MSG_ERROR, "Error: Too many devices\n");
|
||
|
return -ENOMEM;
|
||
|
}
|
||
|
|
||
|
mhi_devices.nr_of_devices++;
|
||
|
plat_dev = mhi_devices.device_list[nr_dev].plat_dev;
|
||
|
pcie_device->dev.of_node = plat_dev->dev.of_node;
|
||
|
mhi_pcie_dev->pcie_device = pcie_device;
|
||
|
mhi_pcie_dev->mhi_pcie_driver = &mhi_pcie_driver;
|
||
|
mhi_pcie_dev->mhi_pci_link_event.events =
|
||
|
(MSM_PCIE_EVENT_LINKDOWN | MSM_PCIE_EVENT_LINKUP |
|
||
|
MSM_PCIE_EVENT_WAKEUP);
|
||
|
mhi_pcie_dev->mhi_pci_link_event.user = pcie_device;
|
||
|
mhi_pcie_dev->mhi_pci_link_event.callback = mhi_link_state_cb;
|
||
|
mhi_pcie_dev->mhi_pci_link_event.notify.data = mhi_pcie_dev;
|
||
|
ret_val = msm_pcie_register_event(&mhi_pcie_dev->mhi_pci_link_event);
|
||
|
if (ret_val)
|
||
|
mhi_log(MHI_MSG_ERROR,
|
||
|
"Failed to register for link notifications %d.\n",
|
||
|
ret_val);
|
||
|
return ret_val;
|
||
|
}
|
||
|
|
||
|
static int mhi_plat_probe(struct platform_device *pdev)
|
||
|
{
|
||
|
u32 nr_dev = mhi_devices.nr_of_devices;
|
||
|
|
||
|
mhi_log(MHI_MSG_INFO, "Entered\n");
|
||
|
mhi_devices.device_list[nr_dev].plat_dev = pdev;
|
||
|
mhi_log(MHI_MSG_INFO, "Exited\n");
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static struct platform_driver mhi_plat_driver = {
|
||
|
.probe = mhi_plat_probe,
|
||
|
.remove = mhi_plat_remove,
|
||
|
.driver = {
|
||
|
.name = "mhi",
|
||
|
.owner = THIS_MODULE,
|
||
|
.of_match_table = mhi_plat_match,
|
||
|
.pm = &pm_ops,
|
||
|
},
|
||
|
};
|
||
|
|
||
|
static void __exit mhi_exit(void)
|
||
|
{
|
||
|
ipc_log_context_destroy(mhi_ipc_log);
|
||
|
pci_unregister_driver(&mhi_pcie_driver);
|
||
|
platform_driver_unregister(&mhi_plat_driver);
|
||
|
}
|
||
|
|
||
|
static int __exit mhi_plat_remove(struct platform_device *pdev)
|
||
|
{
|
||
|
platform_driver_unregister(&mhi_plat_driver);
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int __init mhi_init(void)
|
||
|
{
|
||
|
int r;
|
||
|
|
||
|
mhi_log(MHI_MSG_INFO, "Entered\n");
|
||
|
r = platform_driver_register(&mhi_plat_driver);
|
||
|
if (r) {
|
||
|
mhi_log(MHI_MSG_INFO, "Failed to probe platform ret %d\n", r);
|
||
|
return r;
|
||
|
}
|
||
|
r = pci_register_driver(&mhi_pcie_driver);
|
||
|
if (r) {
|
||
|
mhi_log(MHI_MSG_INFO,
|
||
|
"Failed to register pcie drv ret %d\n", r);
|
||
|
goto error;
|
||
|
}
|
||
|
mhi_ipc_log = ipc_log_context_create(MHI_IPC_LOG_PAGES, "mhi", 0);
|
||
|
if (!mhi_ipc_log) {
|
||
|
mhi_log(MHI_MSG_ERROR,
|
||
|
"Failed to create IPC logging context\n");
|
||
|
}
|
||
|
mhi_log(MHI_MSG_INFO, "Exited\n");
|
||
|
return 0;
|
||
|
error:
|
||
|
pci_unregister_driver(&mhi_pcie_driver);
|
||
|
return r;
|
||
|
}
|
||
|
|
||
|
DECLARE_PCI_FIXUP_HEADER(MHI_PCIE_VENDOR_ID,
|
||
|
MHI_PCIE_DEVICE_ID_9x35,
|
||
|
mhi_msm_fixup);
|
||
|
|
||
|
DECLARE_PCI_FIXUP_HEADER(MHI_PCIE_VENDOR_ID,
|
||
|
MHI_PCIE_DEVICE_ID_ZIRC,
|
||
|
mhi_msm_fixup);
|
||
|
|
||
|
|
||
|
module_exit(mhi_exit);
|
||
|
module_init(mhi_init);
|
||
|
|
||
|
MODULE_LICENSE("GPL v2");
|
||
|
MODULE_ALIAS("MHI_CORE");
|
||
|
MODULE_DESCRIPTION("MHI Host Driver");
|