pci-imx6-ep-driver.c 4.24 KB
/*
 * PCIe endpoint skeleton driver for IMX6 SOCs
 *
 * Copyright (C) 2014 Freescale Semiconductor, Inc. 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 as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.

 * 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.

 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/pci.h>
#include <linux/pci-aspm.h>
#include <linux/slab.h>
#include <linux/dma-mapping.h>
#include <linux/delay.h>
#include <linux/sched.h>
#include <linux/interrupt.h>

#define DRV_DESCRIPTION	"i.MX PCIE endpoint device driver"
#define DRV_VERSION	"version 0.1"
#define DRV_NAME	"imx_pcie_ep"

struct imx_pcie_ep_priv {
	struct pci_dev *pci_dev;
	void __iomem *hw_base;
};

/**
 * imx_pcie_ep_probe - Device Initialization Routine
 * @pdev: PCI device information struct
 * @id: entry in id_tbl
 *
 * Returns 0 on success, negative on failure
 **/
static int imx_pcie_ep_probe(struct pci_dev *pdev,
		const struct pci_device_id *id)
{
	int ret = 0;
	unsigned int msi_addr = 0;
	struct device *dev = &pdev->dev;
	struct imx_pcie_ep_priv *priv;

	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
	if (!priv) {
		dev_err(dev, "can't alloc imx pcie priv\n");
		return -ENOMEM;
	}

	priv->pci_dev = pdev;

	if (pci_enable_device(pdev)) {
		ret = -ENODEV;
		goto out;
	}
	pci_set_master(pdev);

	pci_set_drvdata(pdev, priv);

	priv->hw_base = pci_iomap(pdev, 0, 0);
	if (!priv->hw_base) {
		ret = -ENODEV;
		goto out;
	}

	pr_info("pci_resource_len = 0x%08llx\n",
		(unsigned long long) pci_resource_len(pdev, 0));
	pr_info("pci_resource_base = %p\n", priv->hw_base);

	ret = pci_enable_msi(priv->pci_dev);
	if (ret < 0) {
		dev_err(dev, "can't enable msi\n");
		return ret;
	}

	/*
	 * Force to use 0x01FF8000 on iMX6q SD board and 0x08FF8000
	 * on iMX6sx SDB board as the MSI address, to do the MSI demo
	 */
	if (*(unsigned int *)priv->hw_base > 0x4FFFFFFF)
		/* iMX6sx SDB board, DDR layout [0x8000_0000 ~ 0xBFFF_FFFF] */
		msi_addr = 0x08FF8000;
	else
		/* iMX6q SD board, DDR layout [0x1000_0000 ~ 0x4FFF_FFFF] */
		msi_addr = 0x01FF8000;
	pr_info("pci_msi_addr = 0x%08x\n", msi_addr);
	pci_bus_write_config_dword(pdev->bus, 0, 0x54, msi_addr);
	pci_bus_write_config_dword(pdev->bus->parent, 0, 0x820, msi_addr);

	/* configure rc's msi cap */
	pci_bus_read_config_dword(pdev->bus->parent, 0, 0x50, &ret);
	ret |= (PCI_MSI_FLAGS_ENABLE << 16);
	pci_bus_write_config_dword(pdev->bus->parent, 0, 0x50, ret);
	pci_bus_write_config_dword(pdev->bus->parent, 0, 0x828, 0x1);
	pci_bus_write_config_dword(pdev->bus->parent, 0, 0x82C, 0xFFFFFFFE);

	return 0;

out:
	return ret;
}

static void imx_pcie_ep_remove(struct pci_dev *pdev)
{
	struct imx_pcie_ep_priv *priv = pci_get_drvdata(pdev);

	if (!priv)
		return;
	pr_info("***imx pcie ep driver unload***\n");
}

static struct pci_device_id imx_pcie_ep_ids[] = {
	{
	.class =	PCI_CLASS_MEMORY_RAM << 8,
	.class_mask =	~0,
	.vendor =	0xbeaf,
	.device =	0xdead,
	.subvendor =	PCI_ANY_ID,
	.subdevice =	PCI_ANY_ID,
	},
	{ }	/* terminate list */
};
MODULE_DEVICE_TABLE(pci, imx_pcie_ep_ids);

static struct pci_driver imx_pcie_ep_driver = {
	.name = DRV_NAME,
	.id_table = imx_pcie_ep_ids,
	.probe = imx_pcie_ep_probe,
	.remove = imx_pcie_ep_remove,
};

static int __init imx_pcie_ep_init(void)
{
	int ret;
	pr_info(DRV_DESCRIPTION ", " DRV_VERSION "\n");

	ret = pci_register_driver(&imx_pcie_ep_driver);
	if (ret)
		pr_err("Unable to initialize PCI module\n");

	return ret;
}

static void __exit imx_pcie_ep_exit(void)
{
	pci_unregister_driver(&imx_pcie_ep_driver);
}

module_exit(imx_pcie_ep_exit);
module_init(imx_pcie_ep_init);

MODULE_DESCRIPTION(DRV_DESCRIPTION);
MODULE_VERSION(DRV_VERSION);
MODULE_LICENSE("GPL");
MODULE_ALIAS("imx_pcie_ep");