Re: [RFC PATCH 2/2] PCI: Add Qualcomm PCIe ECAM root complex driver

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



On Thu, Apr 04, 2024 at 12:11:24PM -0700, Mayank Rana wrote:
> On some of Qualcomm platform, firmware configures PCIe controller into
> ECAM mode allowing static memory allocation for configuration space of
> supported bus range. Firmware also takes care of bringing up PCIe PHY
> and performing required operation to bring PCIe link into D0. Firmware
> also manages system resources (e.g. clocks/regulators/resets/ bus voting).
> Hence add Qualcomm PCIe ECAM root complex driver which enumerates PCIe
> root complex and connected PCIe devices. Firmware won't be enumerating
> or powering up PCIe root complex until this driver invokes power domain
> based notification to bring PCIe link into D0/D3cold mode.
> 

Is this an in-house PCIe IP of Qualcomm or the same DWC IP that is used in other
SoCs?

- Mani

> This driver also support MSI functionality using PCIe controller based
> MSI controller as GIC ITS based MSI functionality is not available on
> some of platform.
> 
> Signed-off-by: Mayank Rana <quic_mrana@xxxxxxxxxxx>
> ---
>  drivers/pci/controller/Kconfig          |  12 +
>  drivers/pci/controller/Makefile         |   1 +
>  drivers/pci/controller/pcie-qcom-ecam.c | 575 ++++++++++++++++++++++++++++++++
>  3 files changed, 588 insertions(+)
>  create mode 100644 drivers/pci/controller/pcie-qcom-ecam.c
> 
> diff --git a/drivers/pci/controller/Kconfig b/drivers/pci/controller/Kconfig
> index e534c02..abbd9f2 100644
> --- a/drivers/pci/controller/Kconfig
> +++ b/drivers/pci/controller/Kconfig
> @@ -353,6 +353,18 @@ config PCIE_XILINX_CPM
>  	  Say 'Y' here if you want kernel support for the
>  	  Xilinx Versal CPM host bridge.
>  
> +config PCIE_QCOM_ECAM
> +	tristate "QCOM PCIe ECAM host controller"
> +	depends on ARCH_QCOM && PCI
> +	depends on OF
> +	select PCI_MSI
> +	select PCI_HOST_COMMON
> +	select IRQ_DOMAIN
> +	help
> +	 Say 'Y' here if you want to use ECAM shift mode compatible Qualcomm
> +	 PCIe root host controller. The controller is programmed using firmware
> +	 to support ECAM compatible memory address space.
> +
>  source "drivers/pci/controller/cadence/Kconfig"
>  source "drivers/pci/controller/dwc/Kconfig"
>  source "drivers/pci/controller/mobiveil/Kconfig"
> diff --git a/drivers/pci/controller/Makefile b/drivers/pci/controller/Makefile
> index f2b19e6..2f1ee1e 100644
> --- a/drivers/pci/controller/Makefile
> +++ b/drivers/pci/controller/Makefile
> @@ -40,6 +40,7 @@ obj-$(CONFIG_PCI_LOONGSON) += pci-loongson.o
>  obj-$(CONFIG_PCIE_HISI_ERR) += pcie-hisi-error.o
>  obj-$(CONFIG_PCIE_APPLE) += pcie-apple.o
>  obj-$(CONFIG_PCIE_MT7621) += pcie-mt7621.o
> +obj-$(CONFIG_PCIE_QCOM_ECAM) += pcie-qcom-ecam.o
>  
>  # pcie-hisi.o quirks are needed even without CONFIG_PCIE_DW
>  obj-y				+= dwc/
> diff --git a/drivers/pci/controller/pcie-qcom-ecam.c b/drivers/pci/controller/pcie-qcom-ecam.c
> new file mode 100644
> index 00000000..5b4c68b
> --- /dev/null
> +++ b/drivers/pci/controller/pcie-qcom-ecam.c
> @@ -0,0 +1,575 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * Qualcomm PCIe ECAM root host controller driver
> + * Copyright (c) 2024 Qualcomm Innovation Center, Inc. All rights reserved.
> + */
> +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
> +
> +#include <linux/irq.h>
> +#include <linux/irqchip/chained_irq.h>
> +#include <linux/irqdomain.h>
> +#include <linux/kernel.h>
> +#include <linux/module.h>
> +#include <linux/msi.h>
> +#include <linux/of_address.h>
> +#include <linux/of_irq.h>
> +#include <linux/of_pci.h>
> +#include <linux/pci.h>
> +#include <linux/pci-ecam.h>
> +#include <linux/platform_device.h>
> +#include <linux/pm_domain.h>
> +#include <linux/pm_runtime.h>
> +#include <linux/slab.h>
> +#include <linux/types.h>
> +
> +#define PCIE_MSI_CTRL_BASE			(0x820)
> +#define PCIE_MSI_CTRL_SIZE			(0x68)
> +#define PCIE_MSI_CTRL_ADDR_OFFS			(0x0)
> +#define PCIE_MSI_CTRL_UPPER_ADDR_OFFS		(0x4)
> +#define PCIE_MSI_CTRL_INT_N_EN_OFFS(n)		(0x8 + 0xc * (n))
> +#define PCIE_MSI_CTRL_INT_N_MASK_OFFS(n)	(0xc + 0xc * (n))
> +#define PCIE_MSI_CTRL_INT_N_STATUS_OFFS(n)	(0x10 + 0xc * (n))
> +
> +#define	MSI_DB_ADDR	0xa0000000
> +#define MSI_IRQ_PER_GRP (32)
> +
> +/**
> + * struct qcom_msi_irq - MSI IRQ information
> + * @client:	pointer to MSI client struct
> + * @grp:	group the irq belongs to
> + * @grp_index:	index in group
> + * @hwirq:	hwirq number
> + * @virq:	virq number
> + * @pos:	position in MSI bitmap
> + */
> +struct qcom_msi_irq {
> +	struct qcom_msi_client *client;
> +	struct qcom_msi_grp *grp;
> +	unsigned int grp_index;
> +	unsigned int hwirq;
> +	unsigned int virq;
> +	u32 pos;
> +};
> +
> +/**
> + * struct qcom_msi_grp - MSI group information
> + * @int_en_reg:		memory-mapped interrupt enable register address
> + * @int_mask_reg:	memory-mapped interrupt mask register address
> + * @int_status_reg:	memory-mapped interrupt status register address
> + * @mask:		tracks masked/unmasked MSI
> + * @irqs:		structure to MSI IRQ information
> + */
> +struct qcom_msi_grp {
> +	void __iomem *int_en_reg;
> +	void __iomem *int_mask_reg;
> +	void __iomem *int_status_reg;
> +	u32 mask;
> +	struct qcom_msi_irq irqs[MSI_IRQ_PER_GRP];
> +};
> +
> +/**
> + * struct qcom_msi - PCIe controller based MSI controller information
> + * @clients:		list for tracking clients
> + * @dev:		platform device node
> + * @nr_hwirqs:		total number of hardware IRQs
> + * @nr_virqs:		total number of virqs
> + * @nr_grps:		total number of groups
> + * @grps:		pointer to all groups information
> + * @bitmap:		tracks used/unused MSI
> + * @mutex:		for modifying MSI client list and bitmap
> + * @inner_domain:	parent domain; gen irq related
> + * @msi_domain:		child domain; pcie related
> + * @msi_db_addr:	MSI doorbell address
> + * @cfg_lock:		lock for configuring MSI controller registers
> + * @pcie_msi_cfg:	memory-mapped MSI controller register space
> + */
> +struct qcom_msi {
> +	struct list_head clients;
> +	struct device *dev;
> +	int nr_hwirqs;
> +	int nr_virqs;
> +	int nr_grps;
> +	struct qcom_msi_grp *grps;
> +	unsigned long *bitmap;
> +	struct mutex mutex;
> +	struct irq_domain *inner_domain;
> +	struct irq_domain *msi_domain;
> +	phys_addr_t msi_db_addr;
> +	spinlock_t cfg_lock;
> +	void __iomem *pcie_msi_cfg;
> +};
> +
> +/**
> + * struct qcom_msi_client - structure for each client of MSI controller
> + * @node:		list to track number of MSI clients
> + * @msi:		client specific MSI controller based resource pointer
> + * @dev:		client's dev of pci_dev
> + * @nr_irqs:		number of irqs allocated for client
> + * @msi_addr:		MSI doorbell address
> + */
> +struct qcom_msi_client {
> +	struct list_head node;
> +	struct qcom_msi *msi;
> +	struct device *dev;
> +	unsigned int nr_irqs;
> +	phys_addr_t msi_addr;
> +};
> +
> +static void qcom_msi_handler(struct irq_desc *desc)
> +{
> +	struct irq_chip *chip = irq_desc_get_chip(desc);
> +	struct qcom_msi_grp *msi_grp;
> +	u32 status;
> +	int i;
> +
> +	chained_irq_enter(chip, desc);
> +
> +	msi_grp = irq_desc_get_handler_data(desc);
> +	status = readl_relaxed(msi_grp->int_status_reg);
> +	status ^= (msi_grp->mask & status);
> +	writel(status, msi_grp->int_status_reg);
> +
> +	for (i = 0; status; i++, status >>= 1)
> +		if (status & 0x1)
> +			generic_handle_irq(msi_grp->irqs[i].virq);
> +
> +	chained_irq_exit(chip, desc);
> +}
> +
> +static void qcom_msi_mask_irq(struct irq_data *data)
> +{
> +	struct irq_data *parent_data;
> +	struct qcom_msi_irq *msi_irq;
> +	struct qcom_msi_grp *msi_grp;
> +	struct qcom_msi *msi;
> +	unsigned long flags;
> +
> +	parent_data = data->parent_data;
> +	if (!parent_data)
> +		return;
> +
> +	msi_irq = irq_data_get_irq_chip_data(parent_data);
> +	msi = msi_irq->client->msi;
> +	msi_grp = msi_irq->grp;
> +
> +	spin_lock_irqsave(&msi->cfg_lock, flags);
> +	pci_msi_mask_irq(data);
> +	msi_grp->mask |= BIT(msi_irq->grp_index);
> +	writel(msi_grp->mask, msi_grp->int_mask_reg);
> +	spin_unlock_irqrestore(&msi->cfg_lock, flags);
> +}
> +
> +static void qcom_msi_unmask_irq(struct irq_data *data)
> +{
> +	struct irq_data *parent_data;
> +	struct qcom_msi_irq *msi_irq;
> +	struct qcom_msi_grp *msi_grp;
> +	struct qcom_msi *msi;
> +	unsigned long flags;
> +
> +	parent_data = data->parent_data;
> +	if (!parent_data)
> +		return;
> +
> +	msi_irq = irq_data_get_irq_chip_data(parent_data);
> +	msi = msi_irq->client->msi;
> +	msi_grp = msi_irq->grp;
> +
> +	spin_lock_irqsave(&msi->cfg_lock, flags);
> +	msi_grp->mask &= ~BIT(msi_irq->grp_index);
> +	writel(msi_grp->mask, msi_grp->int_mask_reg);
> +	pci_msi_unmask_irq(data);
> +	spin_unlock_irqrestore(&msi->cfg_lock, flags);
> +}
> +
> +static struct irq_chip qcom_msi_irq_chip = {
> +	.name		= "qcom_pci_msi",
> +	.irq_enable	= qcom_msi_unmask_irq,
> +	.irq_disable	= qcom_msi_mask_irq,
> +	.irq_mask	= qcom_msi_mask_irq,
> +	.irq_unmask	= qcom_msi_unmask_irq,
> +};
> +
> +static int qcom_msi_domain_prepare(struct irq_domain *domain, struct device *dev,
> +				int nvec, msi_alloc_info_t *arg)
> +{
> +	struct qcom_msi *msi = domain->parent->host_data;
> +	struct qcom_msi_client *client;
> +
> +	client = kzalloc(sizeof(*client), GFP_KERNEL);
> +	if (!client)
> +		return -ENOMEM;
> +
> +	client->msi = msi;
> +	client->dev = dev;
> +	client->msi_addr = msi->msi_db_addr;
> +	mutex_lock(&msi->mutex);
> +	list_add_tail(&client->node, &msi->clients);
> +	mutex_unlock(&msi->mutex);
> +
> +	/* zero out struct for pcie msi framework */
> +	memset(arg, 0, sizeof(*arg));
> +	return 0;
> +}
> +
> +static struct msi_domain_ops qcom_msi_domain_ops = {
> +	.msi_prepare	= qcom_msi_domain_prepare,
> +};
> +
> +static struct msi_domain_info qcom_msi_domain_info = {
> +	.flags	= (MSI_FLAG_USE_DEF_DOM_OPS | MSI_FLAG_USE_DEF_CHIP_OPS |
> +			MSI_FLAG_MULTI_PCI_MSI | MSI_FLAG_PCI_MSIX),
> +	.ops	= &qcom_msi_domain_ops,
> +	.chip	= &qcom_msi_irq_chip,
> +};
> +
> +static int qcom_msi_irq_set_affinity(struct irq_data *data,
> +				const struct cpumask *mask, bool force)
> +{
> +	struct irq_data *parent_data = irq_get_irq_data(irqd_to_hwirq(data));
> +	int ret = 0;
> +
> +	if (!parent_data)
> +		return -ENODEV;
> +
> +	/* set affinity for MSI HW IRQ */
> +	if (parent_data->chip->irq_set_affinity)
> +		ret = parent_data->chip->irq_set_affinity(parent_data, mask, force);
> +
> +	return ret;
> +}
> +
> +static void qcom_msi_irq_compose_msi_msg(struct irq_data *data, struct msi_msg *msg)
> +{
> +	struct irq_data *parent_data = irq_get_irq_data(irqd_to_hwirq(data));
> +	struct qcom_msi_irq *msi_irq = irq_data_get_irq_chip_data(data);
> +	struct qcom_msi_client *client = msi_irq->client;
> +
> +	if (!parent_data)
> +		return;
> +
> +	msg->address_lo = lower_32_bits(client->msi_addr);
> +	msg->address_hi = upper_32_bits(client->msi_addr);
> +	msg->data = msi_irq->pos;
> +}
> +
> +static struct irq_chip qcom_msi_bottom_irq_chip = {
> +	.name			= "qcom_msi",
> +	.irq_set_affinity	= qcom_msi_irq_set_affinity,
> +	.irq_compose_msi_msg	= qcom_msi_irq_compose_msi_msg,
> +};
> +
> +static int qcom_msi_irq_domain_alloc(struct irq_domain *domain, unsigned int virq,
> +				unsigned int nr_irqs, void *args)
> +{
> +	struct device *dev = ((msi_alloc_info_t *)args)->desc->dev;
> +	struct qcom_msi_client *tmp, *client = NULL;
> +	struct qcom_msi *msi = domain->host_data;
> +	int i, ret = 0;
> +	int pos;
> +
> +	mutex_lock(&msi->mutex);
> +	list_for_each_entry(tmp, &msi->clients, node) {
> +		if (tmp->dev == dev) {
> +			client = tmp;
> +			break;
> +		}
> +	}
> +
> +	if (!client) {
> +		dev_err(msi->dev, "failed to find MSI client dev\n");
> +		ret = -ENODEV;
> +		goto out;
> +	}
> +
> +	pos = bitmap_find_next_zero_area(msi->bitmap, msi->nr_virqs, 0,
> +					nr_irqs, nr_irqs - 1);
> +	if (pos > msi->nr_virqs) {
> +		ret = -ENOSPC;
> +		goto out;
> +	}
> +
> +	bitmap_set(msi->bitmap, pos, nr_irqs);
> +	for (i = 0; i < nr_irqs; i++) {
> +		u32 grp = pos / MSI_IRQ_PER_GRP;
> +		u32 index = pos % MSI_IRQ_PER_GRP;
> +		struct qcom_msi_irq *msi_irq = &msi->grps[grp].irqs[index];
> +
> +		msi_irq->virq = virq + i;
> +		msi_irq->client = client;
> +		irq_domain_set_info(domain, msi_irq->virq,
> +				msi_irq->hwirq,
> +				&qcom_msi_bottom_irq_chip, msi_irq,
> +				handle_simple_irq, NULL, NULL);
> +		client->nr_irqs++;
> +		pos++;
> +	}
> +out:
> +	mutex_unlock(&msi->mutex);
> +	return ret;
> +}
> +
> +static void qcom_msi_irq_domain_free(struct irq_domain *domain, unsigned int virq,
> +				unsigned int nr_irqs)
> +{
> +	struct irq_data *data = irq_domain_get_irq_data(domain, virq);
> +	struct qcom_msi_client *client;
> +	struct qcom_msi_irq *msi_irq;
> +	struct qcom_msi *msi;
> +
> +	if (!data)
> +		return;
> +
> +	msi_irq = irq_data_get_irq_chip_data(data);
> +	client  = msi_irq->client;
> +	msi = client->msi;
> +
> +	mutex_lock(&msi->mutex);
> +	bitmap_clear(msi->bitmap, msi_irq->pos, nr_irqs);
> +
> +	client->nr_irqs -= nr_irqs;
> +	if (!client->nr_irqs) {
> +		list_del(&client->node);
> +		kfree(client);
> +	}
> +	mutex_unlock(&msi->mutex);
> +
> +	irq_domain_free_irqs_parent(domain, virq, nr_irqs);
> +}
> +
> +static const struct irq_domain_ops msi_domain_ops = {
> +	.alloc	= qcom_msi_irq_domain_alloc,
> +	.free	= qcom_msi_irq_domain_free,
> +};
> +
> +static int qcom_msi_alloc_domains(struct qcom_msi *msi)
> +{
> +	msi->inner_domain = irq_domain_add_linear(NULL, msi->nr_virqs,
> +						&msi_domain_ops, msi);
> +	if (!msi->inner_domain) {
> +		dev_err(msi->dev, "failed to create IRQ inner domain\n");
> +		return -ENOMEM;
> +	}
> +
> +	msi->msi_domain = pci_msi_create_irq_domain(of_node_to_fwnode(msi->dev->of_node),
> +					&qcom_msi_domain_info, msi->inner_domain);
> +	if (!msi->msi_domain) {
> +		dev_err(msi->dev, "failed to create MSI domain\n");
> +		irq_domain_remove(msi->inner_domain);
> +		return -ENOMEM;
> +	}
> +
> +	return 0;
> +}
> +
> +static int qcom_msi_irq_setup(struct qcom_msi *msi)
> +{
> +	struct qcom_msi_grp *msi_grp;
> +	struct qcom_msi_irq *msi_irq;
> +	int i, index, ret;
> +	unsigned int irq;
> +
> +	/* setup each MSI group. nr_hwirqs == nr_grps */
> +	for (i = 0; i < msi->nr_hwirqs; i++) {
> +		irq = irq_of_parse_and_map(msi->dev->of_node, i);
> +		if (!irq) {
> +			dev_err(msi->dev,
> +				"MSI: failed to parse/map interrupt\n");
> +			ret = -ENODEV;
> +			goto free_irqs;
> +		}
> +
> +		msi_grp = &msi->grps[i];
> +		msi_grp->int_en_reg = msi->pcie_msi_cfg +
> +				PCIE_MSI_CTRL_INT_N_EN_OFFS(i);
> +		msi_grp->int_mask_reg = msi->pcie_msi_cfg +
> +				PCIE_MSI_CTRL_INT_N_MASK_OFFS(i);
> +		msi_grp->int_status_reg = msi->pcie_msi_cfg +
> +				PCIE_MSI_CTRL_INT_N_STATUS_OFFS(i);
> +
> +		for (index = 0; index < MSI_IRQ_PER_GRP; index++) {
> +			msi_irq = &msi_grp->irqs[index];
> +
> +			msi_irq->grp = msi_grp;
> +			msi_irq->grp_index = index;
> +			msi_irq->pos = (i * MSI_IRQ_PER_GRP) + index;
> +			msi_irq->hwirq = irq;
> +		}
> +
> +		irq_set_chained_handler_and_data(irq, qcom_msi_handler, msi_grp);
> +	}
> +
> +	return 0;
> +
> +free_irqs:
> +	for (--i; i >= 0; i--) {
> +		irq = msi->grps[i].irqs[0].hwirq;
> +
> +		irq_set_chained_handler_and_data(irq, NULL, NULL);
> +		irq_dispose_mapping(irq);
> +	}
> +
> +	return ret;
> +}
> +
> +static void qcom_msi_config(struct irq_domain *domain)
> +{
> +	struct qcom_msi *msi;
> +	int i;
> +
> +	msi = domain->parent->host_data;
> +
> +	/* program termination address */
> +	writel(msi->msi_db_addr, msi->pcie_msi_cfg + PCIE_MSI_CTRL_ADDR_OFFS);
> +	writel(0, msi->pcie_msi_cfg + PCIE_MSI_CTRL_UPPER_ADDR_OFFS);
> +
> +	/* restore mask and enable all interrupts for each group */
> +	for (i = 0; i < msi->nr_grps; i++) {
> +		struct qcom_msi_grp *msi_grp = &msi->grps[i];
> +
> +		writel(msi_grp->mask, msi_grp->int_mask_reg);
> +		writel(~0, msi_grp->int_en_reg);
> +	}
> +}
> +
> +static void qcom_msi_deinit(struct qcom_msi *msi)
> +{
> +	irq_domain_remove(msi->msi_domain);
> +	irq_domain_remove(msi->inner_domain);
> +}
> +
> +static struct qcom_msi *qcom_msi_init(struct device *dev)
> +{
> +	struct qcom_msi *msi;
> +	u64 addr;
> +	int ret;
> +
> +	msi = devm_kzalloc(dev, sizeof(*msi), GFP_KERNEL);
> +	if (!msi)
> +		return ERR_PTR(-ENOMEM);
> +
> +	msi->dev = dev;
> +	mutex_init(&msi->mutex);
> +	spin_lock_init(&msi->cfg_lock);
> +	INIT_LIST_HEAD(&msi->clients);
> +
> +	msi->msi_db_addr = MSI_DB_ADDR;
> +	msi->nr_hwirqs = of_irq_count(dev->of_node);
> +	if (!msi->nr_hwirqs) {
> +		dev_err(msi->dev, "no hwirqs found\n");
> +		return ERR_PTR(-ENODEV);
> +	}
> +
> +	if (of_property_read_reg(dev->of_node, 0, &addr, NULL) < 0) {
> +		dev_err(msi->dev, "failed to get reg address\n");
> +		return ERR_PTR(-ENODEV);
> +	}
> +
> +	dev_dbg(msi->dev, "hwirq:%d pcie_msi_cfg:%llx\n", msi->nr_hwirqs, addr);
> +	msi->pcie_msi_cfg = devm_ioremap(dev, addr + PCIE_MSI_CTRL_BASE, PCIE_MSI_CTRL_SIZE);
> +	if (!msi->pcie_msi_cfg)
> +		return ERR_PTR(-ENOMEM);
> +
> +	msi->nr_virqs = msi->nr_hwirqs * MSI_IRQ_PER_GRP;
> +	msi->nr_grps = msi->nr_hwirqs;
> +	msi->grps = devm_kcalloc(dev, msi->nr_grps, sizeof(*msi->grps), GFP_KERNEL);
> +	if (!msi->grps)
> +		return ERR_PTR(-ENOMEM);
> +
> +	msi->bitmap = devm_kcalloc(dev, BITS_TO_LONGS(msi->nr_virqs),
> +				sizeof(*msi->bitmap), GFP_KERNEL);
> +	if (!msi->bitmap)
> +		return ERR_PTR(-ENOMEM);
> +
> +	ret = qcom_msi_alloc_domains(msi);
> +	if (ret)
> +		return ERR_PTR(ret);
> +
> +	ret = qcom_msi_irq_setup(msi);
> +	if (ret) {
> +		qcom_msi_deinit(msi);
> +		return ERR_PTR(ret);
> +	}
> +
> +	qcom_msi_config(msi->msi_domain);
> +	return msi;
> +}
> +
> +static int qcom_pcie_ecam_suspend_noirq(struct device *dev)
> +{
> +	return pm_runtime_put_sync(dev);
> +}
> +
> +static int qcom_pcie_ecam_resume_noirq(struct device *dev)
> +{
> +	return pm_runtime_get_sync(dev);
> +}
> +
> +static int qcom_pcie_ecam_probe(struct platform_device *pdev)
> +{
> +	struct device *dev = &pdev->dev;
> +	struct qcom_msi *msi;
> +	int ret;
> +
> +	ret = devm_pm_runtime_enable(dev);
> +	if (ret)
> +		return ret;
> +
> +	ret = pm_runtime_resume_and_get(dev);
> +	if (ret < 0) {
> +		dev_err(dev, "fail to enable pcie controller: %d\n", ret);
> +		return ret;
> +	}
> +
> +	msi = qcom_msi_init(dev);
> +	if (IS_ERR(msi)) {
> +		pm_runtime_put_sync(dev);
> +		return PTR_ERR(msi);
> +	}
> +
> +	ret = pci_host_common_probe(pdev);
> +	if (ret) {
> +		dev_err(dev, "pci_host_common_probe() failed:%d\n", ret);
> +		qcom_msi_deinit(msi);
> +		pm_runtime_put_sync(dev);
> +	}
> +
> +	return ret;
> +}
> +
> +static const struct dev_pm_ops qcom_pcie_ecam_pm_ops = {
> +	NOIRQ_SYSTEM_SLEEP_PM_OPS(qcom_pcie_ecam_suspend_noirq,
> +				qcom_pcie_ecam_resume_noirq)
> +};
> +
> +static const struct pci_ecam_ops qcom_pcie_ecam_ops = {
> +	.pci_ops	= {
> +		.map_bus	= pci_ecam_map_bus,
> +		.read		= pci_generic_config_read,
> +		.write		= pci_generic_config_write,
> +	}
> +};
> +
> +static const struct of_device_id qcom_pcie_ecam_of_match[] = {
> +	{
> +		.compatible	= "qcom,pcie-ecam-rc",
> +		.data		= &qcom_pcie_ecam_ops,
> +	},
> +	{ },
> +};
> +MODULE_DEVICE_TABLE(of, qcom_pcie_ecam_of_match);
> +
> +static struct platform_driver qcom_pcie_ecam_driver = {
> +	.probe	= qcom_pcie_ecam_probe,
> +	.driver	= {
> +		.name			= "qcom-pcie-ecam-rc",
> +		.suppress_bind_attrs	= true,
> +		.of_match_table		= qcom_pcie_ecam_of_match,
> +		.probe_type		= PROBE_PREFER_ASYNCHRONOUS,
> +		.pm			= &qcom_pcie_ecam_pm_ops,
> +	},
> +};
> +module_platform_driver(qcom_pcie_ecam_driver);
> +
> +MODULE_DESCRIPTION("Qualcomm PCIe ECAM root complex driver");
> +MODULE_LICENSE("GPL");
> -- 
> 2.7.4
> 

-- 
மணிவண்ணன் சதாசிவம்




[Index of Archives]     [DMA Engine]     [Linux Coverity]     [Linux USB]     [Video for Linux]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]     [Greybus]

  Powered by Linux