forked from mirrors/linux
		
	PCI: exynos: Add support for MSI
This patch adds support for Message Signaled Interrupt in the Exynos PCIe driver using Synopsys designware PCIe core IP. Signed-off-by: Siva Reddy Kallam <siva.kallam@samsung.com> Signed-off-by: Srikanth T Shivanand <ts.srikanth@samsung.com> Signed-off-by: Jingoo Han <jg1.han@samsung.com> Signed-off-by: Bjorn Helgaas <bhelgaas@google.com> Cc: Pratyush Anand <pratyush.anand@st.com> Cc: Mohit KUMAR <Mohit.KUMAR@st.com>
This commit is contained in:
		
							parent
							
								
									4af8225567
								
							
						
					
					
						commit
						f342d940ee
					
				
					 3 changed files with 298 additions and 0 deletions
				
			
		| 
						 | 
				
			
			@ -48,6 +48,7 @@ struct exynos_pcie {
 | 
			
		|||
#define PCIE_IRQ_SPECIAL		0x008
 | 
			
		||||
#define PCIE_IRQ_EN_PULSE		0x00c
 | 
			
		||||
#define PCIE_IRQ_EN_LEVEL		0x010
 | 
			
		||||
#define IRQ_MSI_ENABLE			(0x1 << 2)
 | 
			
		||||
#define PCIE_IRQ_EN_SPECIAL		0x014
 | 
			
		||||
#define PCIE_PWR_RESET			0x018
 | 
			
		||||
#define PCIE_CORE_RESET			0x01c
 | 
			
		||||
| 
						 | 
				
			
			@ -342,9 +343,36 @@ static irqreturn_t exynos_pcie_irq_handler(int irq, void *arg)
 | 
			
		|||
	return IRQ_HANDLED;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static irqreturn_t exynos_pcie_msi_irq_handler(int irq, void *arg)
 | 
			
		||||
{
 | 
			
		||||
	struct pcie_port *pp = arg;
 | 
			
		||||
 | 
			
		||||
	dw_handle_msi_irq(pp);
 | 
			
		||||
 | 
			
		||||
	return IRQ_HANDLED;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void exynos_pcie_msi_init(struct pcie_port *pp)
 | 
			
		||||
{
 | 
			
		||||
	u32 val;
 | 
			
		||||
	struct exynos_pcie *exynos_pcie = to_exynos_pcie(pp);
 | 
			
		||||
 | 
			
		||||
	dw_pcie_msi_init(pp);
 | 
			
		||||
 | 
			
		||||
	/* enable MSI interrupt */
 | 
			
		||||
	val = exynos_elb_readl(exynos_pcie, PCIE_IRQ_EN_LEVEL);
 | 
			
		||||
	val |= IRQ_MSI_ENABLE;
 | 
			
		||||
	exynos_elb_writel(exynos_pcie, val, PCIE_IRQ_EN_LEVEL);
 | 
			
		||||
	return;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void exynos_pcie_enable_interrupts(struct pcie_port *pp)
 | 
			
		||||
{
 | 
			
		||||
	exynos_pcie_enable_irq_pulse(pp);
 | 
			
		||||
 | 
			
		||||
	if (IS_ENABLED(CONFIG_PCI_MSI))
 | 
			
		||||
		exynos_pcie_msi_init(pp);
 | 
			
		||||
 | 
			
		||||
	return;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -430,6 +458,22 @@ static int add_pcie_port(struct pcie_port *pp, struct platform_device *pdev)
 | 
			
		|||
		return ret;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (IS_ENABLED(CONFIG_PCI_MSI)) {
 | 
			
		||||
		pp->msi_irq = platform_get_irq(pdev, 0);
 | 
			
		||||
		if (!pp->msi_irq) {
 | 
			
		||||
			dev_err(&pdev->dev, "failed to get msi irq\n");
 | 
			
		||||
			return -ENODEV;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		ret = devm_request_irq(&pdev->dev, pp->msi_irq,
 | 
			
		||||
					exynos_pcie_msi_irq_handler,
 | 
			
		||||
					IRQF_SHARED, "exynos-pcie", pp);
 | 
			
		||||
		if (ret) {
 | 
			
		||||
			dev_err(&pdev->dev, "failed to request msi irq\n");
 | 
			
		||||
			return ret;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	pp->root_bus_nr = -1;
 | 
			
		||||
	pp->ops = &exynos_pcie_host_ops;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -11,8 +11,11 @@
 | 
			
		|||
 * published by the Free Software Foundation.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#include <linux/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/pci.h>
 | 
			
		||||
#include <linux/pci_regs.h>
 | 
			
		||||
| 
						 | 
				
			
			@ -142,6 +145,204 @@ int dw_pcie_wr_own_conf(struct pcie_port *pp, int where, int size,
 | 
			
		|||
	return ret;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static struct irq_chip dw_msi_irq_chip = {
 | 
			
		||||
	.name = "PCI-MSI",
 | 
			
		||||
	.irq_enable = unmask_msi_irq,
 | 
			
		||||
	.irq_disable = mask_msi_irq,
 | 
			
		||||
	.irq_mask = mask_msi_irq,
 | 
			
		||||
	.irq_unmask = unmask_msi_irq,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/* MSI int handler */
 | 
			
		||||
void dw_handle_msi_irq(struct pcie_port *pp)
 | 
			
		||||
{
 | 
			
		||||
	unsigned long val;
 | 
			
		||||
	int i, pos;
 | 
			
		||||
 | 
			
		||||
	for (i = 0; i < MAX_MSI_CTRLS; i++) {
 | 
			
		||||
		dw_pcie_rd_own_conf(pp, PCIE_MSI_INTR0_STATUS + i * 12, 4,
 | 
			
		||||
				(u32 *)&val);
 | 
			
		||||
		if (val) {
 | 
			
		||||
			pos = 0;
 | 
			
		||||
			while ((pos = find_next_bit(&val, 32, pos)) != 32) {
 | 
			
		||||
				generic_handle_irq(pp->msi_irq_start
 | 
			
		||||
					+ (i * 32) + pos);
 | 
			
		||||
				pos++;
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		dw_pcie_wr_own_conf(pp, PCIE_MSI_INTR0_STATUS + i * 12, 4, val);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void dw_pcie_msi_init(struct pcie_port *pp)
 | 
			
		||||
{
 | 
			
		||||
	pp->msi_data = __get_free_pages(GFP_KERNEL, 0);
 | 
			
		||||
 | 
			
		||||
	/* program the msi_data */
 | 
			
		||||
	dw_pcie_wr_own_conf(pp, PCIE_MSI_ADDR_LO, 4,
 | 
			
		||||
			virt_to_phys((void *)pp->msi_data));
 | 
			
		||||
	dw_pcie_wr_own_conf(pp, PCIE_MSI_ADDR_HI, 4, 0);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static int find_valid_pos0(struct pcie_port *pp, int msgvec, int pos, int *pos0)
 | 
			
		||||
{
 | 
			
		||||
	int flag = 1;
 | 
			
		||||
 | 
			
		||||
	do {
 | 
			
		||||
		pos = find_next_zero_bit(pp->msi_irq_in_use,
 | 
			
		||||
				MAX_MSI_IRQS, pos);
 | 
			
		||||
		/*if you have reached to the end then get out from here.*/
 | 
			
		||||
		if (pos == MAX_MSI_IRQS)
 | 
			
		||||
			return -ENOSPC;
 | 
			
		||||
		/*
 | 
			
		||||
		 * Check if this position is at correct offset.nvec is always a
 | 
			
		||||
		 * power of two. pos0 must be nvec bit alligned.
 | 
			
		||||
		 */
 | 
			
		||||
		if (pos % msgvec)
 | 
			
		||||
			pos += msgvec - (pos % msgvec);
 | 
			
		||||
		else
 | 
			
		||||
			flag = 0;
 | 
			
		||||
	} while (flag);
 | 
			
		||||
 | 
			
		||||
	*pos0 = pos;
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static int assign_irq(int no_irqs, struct msi_desc *desc, int *pos)
 | 
			
		||||
{
 | 
			
		||||
	int res, bit, irq, pos0, pos1, i;
 | 
			
		||||
	u32 val;
 | 
			
		||||
	struct pcie_port *pp = sys_to_pcie(desc->dev->bus->sysdata);
 | 
			
		||||
 | 
			
		||||
	if (!pp) {
 | 
			
		||||
		BUG();
 | 
			
		||||
		return -EINVAL;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	pos0 = find_first_zero_bit(pp->msi_irq_in_use,
 | 
			
		||||
			MAX_MSI_IRQS);
 | 
			
		||||
	if (pos0 % no_irqs) {
 | 
			
		||||
		if (find_valid_pos0(pp, no_irqs, pos0, &pos0))
 | 
			
		||||
			goto no_valid_irq;
 | 
			
		||||
	}
 | 
			
		||||
	if (no_irqs > 1) {
 | 
			
		||||
		pos1 = find_next_bit(pp->msi_irq_in_use,
 | 
			
		||||
				MAX_MSI_IRQS, pos0);
 | 
			
		||||
		/* there must be nvec number of consecutive free bits */
 | 
			
		||||
		while ((pos1 - pos0) < no_irqs) {
 | 
			
		||||
			if (find_valid_pos0(pp, no_irqs, pos1, &pos0))
 | 
			
		||||
				goto no_valid_irq;
 | 
			
		||||
			pos1 = find_next_bit(pp->msi_irq_in_use,
 | 
			
		||||
					MAX_MSI_IRQS, pos0);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	irq = (pp->msi_irq_start + pos0);
 | 
			
		||||
 | 
			
		||||
	if ((irq + no_irqs) > (pp->msi_irq_start + MAX_MSI_IRQS-1))
 | 
			
		||||
		goto no_valid_irq;
 | 
			
		||||
 | 
			
		||||
	i = 0;
 | 
			
		||||
	while (i < no_irqs) {
 | 
			
		||||
		set_bit(pos0 + i, pp->msi_irq_in_use);
 | 
			
		||||
		irq_alloc_descs((irq + i), (irq + i), 1, 0);
 | 
			
		||||
		irq_set_msi_desc(irq + i, desc);
 | 
			
		||||
		/*Enable corresponding interrupt in MSI interrupt controller */
 | 
			
		||||
		res = ((pos0 + i) / 32) * 12;
 | 
			
		||||
		bit = (pos0 + i) % 32;
 | 
			
		||||
		dw_pcie_rd_own_conf(pp, PCIE_MSI_INTR0_ENABLE + res, 4, &val);
 | 
			
		||||
		val |= 1 << bit;
 | 
			
		||||
		dw_pcie_wr_own_conf(pp, PCIE_MSI_INTR0_ENABLE + res, 4, val);
 | 
			
		||||
		i++;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	*pos = pos0;
 | 
			
		||||
	return irq;
 | 
			
		||||
 | 
			
		||||
no_valid_irq:
 | 
			
		||||
	*pos = pos0;
 | 
			
		||||
	return -ENOSPC;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void clear_irq(unsigned int irq)
 | 
			
		||||
{
 | 
			
		||||
	int res, bit, val, pos;
 | 
			
		||||
	struct irq_desc *desc;
 | 
			
		||||
	struct msi_desc *msi;
 | 
			
		||||
	struct pcie_port *pp;
 | 
			
		||||
 | 
			
		||||
	/* get the port structure */
 | 
			
		||||
	desc = irq_to_desc(irq);
 | 
			
		||||
	msi = irq_desc_get_msi_desc(desc);
 | 
			
		||||
	pp = sys_to_pcie(msi->dev->bus->sysdata);
 | 
			
		||||
	if (!pp) {
 | 
			
		||||
		BUG();
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	pos = irq - pp->msi_irq_start;
 | 
			
		||||
 | 
			
		||||
	irq_free_desc(irq);
 | 
			
		||||
 | 
			
		||||
	clear_bit(pos, pp->msi_irq_in_use);
 | 
			
		||||
 | 
			
		||||
	/* Disable corresponding interrupt on MSI interrupt controller */
 | 
			
		||||
	res = (pos / 32) * 12;
 | 
			
		||||
	bit = pos % 32;
 | 
			
		||||
	dw_pcie_rd_own_conf(pp, PCIE_MSI_INTR0_ENABLE + res, 4, &val);
 | 
			
		||||
	val &= ~(1 << bit);
 | 
			
		||||
	dw_pcie_wr_own_conf(pp, PCIE_MSI_INTR0_ENABLE + res, 4, val);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static int dw_msi_setup_irq(struct msi_chip *chip, struct pci_dev *pdev,
 | 
			
		||||
			struct msi_desc *desc)
 | 
			
		||||
{
 | 
			
		||||
	int irq, pos, msgvec;
 | 
			
		||||
	u16 msg_ctr;
 | 
			
		||||
	struct msi_msg msg;
 | 
			
		||||
	struct pcie_port *pp = sys_to_pcie(pdev->bus->sysdata);
 | 
			
		||||
 | 
			
		||||
	if (!pp) {
 | 
			
		||||
		BUG();
 | 
			
		||||
		return -EINVAL;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	pci_read_config_word(pdev, desc->msi_attrib.pos+PCI_MSI_FLAGS,
 | 
			
		||||
				&msg_ctr);
 | 
			
		||||
	msgvec = (msg_ctr&PCI_MSI_FLAGS_QSIZE) >> 4;
 | 
			
		||||
	if (msgvec == 0)
 | 
			
		||||
		msgvec = (msg_ctr & PCI_MSI_FLAGS_QMASK) >> 1;
 | 
			
		||||
	if (msgvec > 5)
 | 
			
		||||
		msgvec = 0;
 | 
			
		||||
 | 
			
		||||
	irq = assign_irq((1 << msgvec), desc, &pos);
 | 
			
		||||
	if (irq < 0)
 | 
			
		||||
		return irq;
 | 
			
		||||
 | 
			
		||||
	msg_ctr &= ~PCI_MSI_FLAGS_QSIZE;
 | 
			
		||||
	msg_ctr |= msgvec << 4;
 | 
			
		||||
	pci_write_config_word(pdev, desc->msi_attrib.pos + PCI_MSI_FLAGS,
 | 
			
		||||
				msg_ctr);
 | 
			
		||||
	desc->msi_attrib.multiple = msgvec;
 | 
			
		||||
 | 
			
		||||
	msg.address_lo = virt_to_phys((void *)pp->msi_data);
 | 
			
		||||
	msg.address_hi = 0x0;
 | 
			
		||||
	msg.data = pos;
 | 
			
		||||
	write_msi_msg(irq, &msg);
 | 
			
		||||
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void dw_msi_teardown_irq(struct msi_chip *chip, unsigned int irq)
 | 
			
		||||
{
 | 
			
		||||
	clear_irq(irq);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static struct msi_chip dw_pcie_msi_chip = {
 | 
			
		||||
	.setup_irq = dw_msi_setup_irq,
 | 
			
		||||
	.teardown_irq = dw_msi_teardown_irq,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
int dw_pcie_link_up(struct pcie_port *pp)
 | 
			
		||||
{
 | 
			
		||||
	if (pp->ops->link_up)
 | 
			
		||||
| 
						 | 
				
			
			@ -150,6 +351,20 @@ int dw_pcie_link_up(struct pcie_port *pp)
 | 
			
		|||
		return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static int dw_pcie_msi_map(struct irq_domain *domain, unsigned int irq,
 | 
			
		||||
			irq_hw_number_t hwirq)
 | 
			
		||||
{
 | 
			
		||||
	irq_set_chip_and_handler(irq, &dw_msi_irq_chip, handle_simple_irq);
 | 
			
		||||
	irq_set_chip_data(irq, domain->host_data);
 | 
			
		||||
	set_irq_flags(irq, IRQF_VALID);
 | 
			
		||||
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static const struct irq_domain_ops msi_domain_ops = {
 | 
			
		||||
	.map = dw_pcie_msi_map,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
int __init dw_pcie_host_init(struct pcie_port *pp)
 | 
			
		||||
{
 | 
			
		||||
	struct device_node *np = pp->dev->of_node;
 | 
			
		||||
| 
						 | 
				
			
			@ -157,6 +372,8 @@ int __init dw_pcie_host_init(struct pcie_port *pp)
 | 
			
		|||
	struct of_pci_range_parser parser;
 | 
			
		||||
	u32 val;
 | 
			
		||||
 | 
			
		||||
	struct irq_domain *irq_domain;
 | 
			
		||||
 | 
			
		||||
	if (of_pci_range_parser_init(&parser, np)) {
 | 
			
		||||
		dev_err(pp->dev, "missing ranges property\n");
 | 
			
		||||
		return -EINVAL;
 | 
			
		||||
| 
						 | 
				
			
			@ -223,6 +440,18 @@ int __init dw_pcie_host_init(struct pcie_port *pp)
 | 
			
		|||
		return -EINVAL;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (IS_ENABLED(CONFIG_PCI_MSI)) {
 | 
			
		||||
		irq_domain = irq_domain_add_linear(pp->dev->of_node,
 | 
			
		||||
					MAX_MSI_IRQS, &msi_domain_ops,
 | 
			
		||||
					&dw_pcie_msi_chip);
 | 
			
		||||
		if (!irq_domain) {
 | 
			
		||||
			dev_err(pp->dev, "irq domain init failed\n");
 | 
			
		||||
			return -ENXIO;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		pp->msi_irq_start = irq_find_mapping(irq_domain, 0);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (pp->ops->host_init)
 | 
			
		||||
		pp->ops->host_init(pp);
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -485,10 +714,21 @@ int dw_pcie_map_irq(const struct pci_dev *dev, u8 slot, u8 pin)
 | 
			
		|||
	return pp->irq;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void dw_pcie_add_bus(struct pci_bus *bus)
 | 
			
		||||
{
 | 
			
		||||
	if (IS_ENABLED(CONFIG_PCI_MSI)) {
 | 
			
		||||
		struct pcie_port *pp = sys_to_pcie(bus->sysdata);
 | 
			
		||||
 | 
			
		||||
		dw_pcie_msi_chip.dev = pp->dev;
 | 
			
		||||
		bus->msi = &dw_pcie_msi_chip;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static struct hw_pci dw_pci = {
 | 
			
		||||
	.setup		= dw_pcie_setup,
 | 
			
		||||
	.scan		= dw_pcie_scan_bus,
 | 
			
		||||
	.map_irq	= dw_pcie_map_irq,
 | 
			
		||||
	.add_bus	= dw_pcie_add_bus,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
void dw_pcie_setup_rc(struct pcie_port *pp)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -20,6 +20,14 @@ struct pcie_port_info {
 | 
			
		|||
	phys_addr_t	mem_bus_addr;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 * Maximum number of MSI IRQs can be 256 per controller. But keep
 | 
			
		||||
 * it 32 as of now. Probably we will never need more than 32. If needed,
 | 
			
		||||
 * then increment it in multiple of 32.
 | 
			
		||||
 */
 | 
			
		||||
#define MAX_MSI_IRQS			32
 | 
			
		||||
#define MAX_MSI_CTRLS			(MAX_MSI_IRQS / 32)
 | 
			
		||||
 | 
			
		||||
struct pcie_port {
 | 
			
		||||
	struct device		*dev;
 | 
			
		||||
	u8			root_bus_nr;
 | 
			
		||||
| 
						 | 
				
			
			@ -38,6 +46,10 @@ struct pcie_port {
 | 
			
		|||
	int			irq;
 | 
			
		||||
	u32			lanes;
 | 
			
		||||
	struct pcie_host_ops	*ops;
 | 
			
		||||
	int			msi_irq;
 | 
			
		||||
	int			msi_irq_start;
 | 
			
		||||
	unsigned long		msi_data;
 | 
			
		||||
	DECLARE_BITMAP(msi_irq_in_use, MAX_MSI_IRQS);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
struct pcie_host_ops {
 | 
			
		||||
| 
						 | 
				
			
			@ -57,6 +69,8 @@ int cfg_read(void __iomem *addr, int where, int size, u32 *val);
 | 
			
		|||
int cfg_write(void __iomem *addr, int where, int size, u32 val);
 | 
			
		||||
int dw_pcie_wr_own_conf(struct pcie_port *pp, int where, int size, u32 val);
 | 
			
		||||
int dw_pcie_rd_own_conf(struct pcie_port *pp, int where, int size, u32 *val);
 | 
			
		||||
void dw_handle_msi_irq(struct pcie_port *pp);
 | 
			
		||||
void dw_pcie_msi_init(struct pcie_port *pp);
 | 
			
		||||
int dw_pcie_link_up(struct pcie_port *pp);
 | 
			
		||||
void dw_pcie_setup_rc(struct pcie_port *pp);
 | 
			
		||||
int dw_pcie_host_init(struct pcie_port *pp);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in a new issue