diff options
Diffstat (limited to 'drivers/pci/controller/pci-tegra.c')
| -rw-r--r-- | drivers/pci/controller/pci-tegra.c | 349 | 
1 files changed, 191 insertions, 158 deletions
| diff --git a/drivers/pci/controller/pci-tegra.c b/drivers/pci/controller/pci-tegra.c index 8fcabed7c6a6..8069bd9232d4 100644 --- a/drivers/pci/controller/pci-tegra.c +++ b/drivers/pci/controller/pci-tegra.c @@ -21,6 +21,7 @@  #include <linux/interrupt.h>  #include <linux/iopoll.h>  #include <linux/irq.h> +#include <linux/irqchip/chained_irq.h>  #include <linux/irqdomain.h>  #include <linux/kernel.h>  #include <linux/init.h> @@ -78,23 +79,8 @@  #define AFI_MSI_FPCI_BAR_ST	0x64  #define AFI_MSI_AXI_BAR_ST	0x68 -#define AFI_MSI_VEC0		0x6c -#define AFI_MSI_VEC1		0x70 -#define AFI_MSI_VEC2		0x74 -#define AFI_MSI_VEC3		0x78 -#define AFI_MSI_VEC4		0x7c -#define AFI_MSI_VEC5		0x80 -#define AFI_MSI_VEC6		0x84 -#define AFI_MSI_VEC7		0x88 - -#define AFI_MSI_EN_VEC0		0x8c -#define AFI_MSI_EN_VEC1		0x90 -#define AFI_MSI_EN_VEC2		0x94 -#define AFI_MSI_EN_VEC3		0x98 -#define AFI_MSI_EN_VEC4		0x9c -#define AFI_MSI_EN_VEC5		0xa0 -#define AFI_MSI_EN_VEC6		0xa4 -#define AFI_MSI_EN_VEC7		0xa8 +#define AFI_MSI_VEC(x)		(0x6c + ((x) * 4)) +#define AFI_MSI_EN_VEC(x)	(0x8c + ((x) * 4))  #define AFI_CONFIGURATION		0xac  #define  AFI_CONFIGURATION_EN_FPCI		(1 << 0) @@ -280,10 +266,10 @@  #define LINK_RETRAIN_TIMEOUT 100000 /* in usec */  struct tegra_msi { -	struct msi_controller chip;  	DECLARE_BITMAP(used, INT_PCI_MSI_NR);  	struct irq_domain *domain; -	struct mutex lock; +	struct mutex map_lock; +	spinlock_t mask_lock;  	void *virt;  	dma_addr_t phys;  	int irq; @@ -333,11 +319,6 @@ struct tegra_pcie_soc {  	} ectl;  }; -static inline struct tegra_msi *to_tegra_msi(struct msi_controller *chip) -{ -	return container_of(chip, struct tegra_msi, chip); -} -  struct tegra_pcie {  	struct device *dev; @@ -372,6 +353,11 @@ struct tegra_pcie {  	struct dentry *debugfs;  }; +static inline struct tegra_pcie *msi_to_pcie(struct tegra_msi *msi) +{ +	return container_of(msi, struct tegra_pcie, msi); +} +  struct tegra_pcie_port {  	struct tegra_pcie *pcie;  	struct device_node *np; @@ -1432,7 +1418,6 @@ static void tegra_pcie_phys_put(struct tegra_pcie *pcie)  	}  } -  static int tegra_pcie_get_resources(struct tegra_pcie *pcie)  {  	struct device *dev = pcie->dev; @@ -1509,6 +1494,7 @@ static int tegra_pcie_get_resources(struct tegra_pcie *pcie)  phys_put:  	if (soc->program_uphy)  		tegra_pcie_phys_put(pcie); +  	return err;  } @@ -1551,161 +1537,227 @@ static void tegra_pcie_pme_turnoff(struct tegra_pcie_port *port)  	afi_writel(pcie, val, AFI_PCIE_PME);  } -static int tegra_msi_alloc(struct tegra_msi *chip) -{ -	int msi; - -	mutex_lock(&chip->lock); - -	msi = find_first_zero_bit(chip->used, INT_PCI_MSI_NR); -	if (msi < INT_PCI_MSI_NR) -		set_bit(msi, chip->used); -	else -		msi = -ENOSPC; - -	mutex_unlock(&chip->lock); - -	return msi; -} - -static void tegra_msi_free(struct tegra_msi *chip, unsigned long irq) +static void tegra_pcie_msi_irq(struct irq_desc *desc)  { -	struct device *dev = chip->chip.dev; - -	mutex_lock(&chip->lock); - -	if (!test_bit(irq, chip->used)) -		dev_err(dev, "trying to free unused MSI#%lu\n", irq); -	else -		clear_bit(irq, chip->used); - -	mutex_unlock(&chip->lock); -} - -static irqreturn_t tegra_pcie_msi_irq(int irq, void *data) -{ -	struct tegra_pcie *pcie = data; -	struct device *dev = pcie->dev; +	struct tegra_pcie *pcie = irq_desc_get_handler_data(desc); +	struct irq_chip *chip = irq_desc_get_chip(desc);  	struct tegra_msi *msi = &pcie->msi; -	unsigned int i, processed = 0; +	struct device *dev = pcie->dev; +	unsigned int i; + +	chained_irq_enter(chip, desc);  	for (i = 0; i < 8; i++) { -		unsigned long reg = afi_readl(pcie, AFI_MSI_VEC0 + i * 4); +		unsigned long reg = afi_readl(pcie, AFI_MSI_VEC(i));  		while (reg) {  			unsigned int offset = find_first_bit(®, 32);  			unsigned int index = i * 32 + offset;  			unsigned int irq; -			/* clear the interrupt */ -			afi_writel(pcie, 1 << offset, AFI_MSI_VEC0 + i * 4); - -			irq = irq_find_mapping(msi->domain, index); +			irq = irq_find_mapping(msi->domain->parent, index);  			if (irq) { -				if (test_bit(index, msi->used)) -					generic_handle_irq(irq); -				else -					dev_info(dev, "unhandled MSI\n"); +				generic_handle_irq(irq);  			} else {  				/*  				 * that's weird who triggered this?  				 * just clear it  				 */  				dev_info(dev, "unexpected MSI\n"); +				afi_writel(pcie, BIT(index % 32), AFI_MSI_VEC(index));  			}  			/* see if there's any more pending in this vector */ -			reg = afi_readl(pcie, AFI_MSI_VEC0 + i * 4); - -			processed++; +			reg = afi_readl(pcie, AFI_MSI_VEC(i));  		}  	} -	return processed > 0 ? IRQ_HANDLED : IRQ_NONE; +	chained_irq_exit(chip, desc);  } -static int tegra_msi_setup_irq(struct msi_controller *chip, -			       struct pci_dev *pdev, struct msi_desc *desc) +static void tegra_msi_top_irq_ack(struct irq_data *d)  { -	struct tegra_msi *msi = to_tegra_msi(chip); -	struct msi_msg msg; -	unsigned int irq; -	int hwirq; +	irq_chip_ack_parent(d); +} -	hwirq = tegra_msi_alloc(msi); -	if (hwirq < 0) -		return hwirq; +static void tegra_msi_top_irq_mask(struct irq_data *d) +{ +	pci_msi_mask_irq(d); +	irq_chip_mask_parent(d); +} -	irq = irq_create_mapping(msi->domain, hwirq); -	if (!irq) { -		tegra_msi_free(msi, hwirq); -		return -EINVAL; -	} +static void tegra_msi_top_irq_unmask(struct irq_data *d) +{ +	pci_msi_unmask_irq(d); +	irq_chip_unmask_parent(d); +} + +static struct irq_chip tegra_msi_top_chip = { +	.name		= "Tegra PCIe MSI", +	.irq_ack	= tegra_msi_top_irq_ack, +	.irq_mask	= tegra_msi_top_irq_mask, +	.irq_unmask	= tegra_msi_top_irq_unmask, +}; -	irq_set_msi_desc(irq, desc); +static void tegra_msi_irq_ack(struct irq_data *d) +{ +	struct tegra_msi *msi = irq_data_get_irq_chip_data(d); +	struct tegra_pcie *pcie = msi_to_pcie(msi); +	unsigned int index = d->hwirq / 32; -	msg.address_lo = lower_32_bits(msi->phys); -	msg.address_hi = upper_32_bits(msi->phys); -	msg.data = hwirq; +	/* clear the interrupt */ +	afi_writel(pcie, BIT(d->hwirq % 32), AFI_MSI_VEC(index)); +} -	pci_write_msi_msg(irq, &msg); +static void tegra_msi_irq_mask(struct irq_data *d) +{ +	struct tegra_msi *msi = irq_data_get_irq_chip_data(d); +	struct tegra_pcie *pcie = msi_to_pcie(msi); +	unsigned int index = d->hwirq / 32; +	unsigned long flags; +	u32 value; -	return 0; +	spin_lock_irqsave(&msi->mask_lock, flags); +	value = afi_readl(pcie, AFI_MSI_EN_VEC(index)); +	value &= ~BIT(d->hwirq % 32); +	afi_writel(pcie, value, AFI_MSI_EN_VEC(index)); +	spin_unlock_irqrestore(&msi->mask_lock, flags);  } -static void tegra_msi_teardown_irq(struct msi_controller *chip, -				   unsigned int irq) +static void tegra_msi_irq_unmask(struct irq_data *d)  { -	struct tegra_msi *msi = to_tegra_msi(chip); -	struct irq_data *d = irq_get_irq_data(irq); -	irq_hw_number_t hwirq = irqd_to_hwirq(d); +	struct tegra_msi *msi = irq_data_get_irq_chip_data(d); +	struct tegra_pcie *pcie = msi_to_pcie(msi); +	unsigned int index = d->hwirq / 32; +	unsigned long flags; +	u32 value; -	irq_dispose_mapping(irq); -	tegra_msi_free(msi, hwirq); +	spin_lock_irqsave(&msi->mask_lock, flags); +	value = afi_readl(pcie, AFI_MSI_EN_VEC(index)); +	value |= BIT(d->hwirq % 32); +	afi_writel(pcie, value, AFI_MSI_EN_VEC(index)); +	spin_unlock_irqrestore(&msi->mask_lock, flags);  } -static struct irq_chip tegra_msi_irq_chip = { -	.name = "Tegra PCIe MSI", -	.irq_enable = pci_msi_unmask_irq, -	.irq_disable = pci_msi_mask_irq, -	.irq_mask = pci_msi_mask_irq, -	.irq_unmask = pci_msi_unmask_irq, +static int tegra_msi_set_affinity(struct irq_data *d, const struct cpumask *mask, bool force) +{ +	return -EINVAL; +} + +static void tegra_compose_msi_msg(struct irq_data *data, struct msi_msg *msg) +{ +	struct tegra_msi *msi = irq_data_get_irq_chip_data(data); + +	msg->address_lo = lower_32_bits(msi->phys); +	msg->address_hi = upper_32_bits(msi->phys); +	msg->data = data->hwirq; +} + +static struct irq_chip tegra_msi_bottom_chip = { +	.name			= "Tegra MSI", +	.irq_ack		= tegra_msi_irq_ack, +	.irq_mask		= tegra_msi_irq_mask, +	.irq_unmask		= tegra_msi_irq_unmask, +	.irq_set_affinity 	= tegra_msi_set_affinity, +	.irq_compose_msi_msg	= tegra_compose_msi_msg,  }; -static int tegra_msi_map(struct irq_domain *domain, unsigned int irq, -			 irq_hw_number_t hwirq) +static int tegra_msi_domain_alloc(struct irq_domain *domain, unsigned int virq, +				  unsigned int nr_irqs, void *args)  { -	irq_set_chip_and_handler(irq, &tegra_msi_irq_chip, handle_simple_irq); -	irq_set_chip_data(irq, domain->host_data); +	struct tegra_msi *msi = domain->host_data; +	unsigned int i; +	int hwirq; + +	mutex_lock(&msi->map_lock); + +	hwirq = bitmap_find_free_region(msi->used, INT_PCI_MSI_NR, order_base_2(nr_irqs)); + +	mutex_unlock(&msi->map_lock); + +	if (hwirq < 0) +		return -ENOSPC; + +	for (i = 0; i < nr_irqs; i++) +		irq_domain_set_info(domain, virq + i, hwirq + i, +				    &tegra_msi_bottom_chip, domain->host_data, +				    handle_edge_irq, NULL, NULL);  	tegra_cpuidle_pcie_irqs_in_use();  	return 0;  } -static const struct irq_domain_ops msi_domain_ops = { -	.map = tegra_msi_map, +static void tegra_msi_domain_free(struct irq_domain *domain, unsigned int virq, +				  unsigned int nr_irqs) +{ +	struct irq_data *d = irq_domain_get_irq_data(domain, virq); +	struct tegra_msi *msi = domain->host_data; + +	mutex_lock(&msi->map_lock); + +	bitmap_release_region(msi->used, d->hwirq, order_base_2(nr_irqs)); + +	mutex_unlock(&msi->map_lock); +} + +static const struct irq_domain_ops tegra_msi_domain_ops = { +	.alloc = tegra_msi_domain_alloc, +	.free = tegra_msi_domain_free, +}; + +static struct msi_domain_info tegra_msi_info = { +	.flags	= (MSI_FLAG_USE_DEF_DOM_OPS | MSI_FLAG_USE_DEF_CHIP_OPS | +		   MSI_FLAG_PCI_MSIX), +	.chip	= &tegra_msi_top_chip,  }; +static int tegra_allocate_domains(struct tegra_msi *msi) +{ +	struct tegra_pcie *pcie = msi_to_pcie(msi); +	struct fwnode_handle *fwnode = dev_fwnode(pcie->dev); +	struct irq_domain *parent; + +	parent = irq_domain_create_linear(fwnode, INT_PCI_MSI_NR, +					  &tegra_msi_domain_ops, msi); +	if (!parent) { +		dev_err(pcie->dev, "failed to create IRQ domain\n"); +		return -ENOMEM; +	} +	irq_domain_update_bus_token(parent, DOMAIN_BUS_NEXUS); + +	msi->domain = pci_msi_create_irq_domain(fwnode, &tegra_msi_info, parent); +	if (!msi->domain) { +		dev_err(pcie->dev, "failed to create MSI domain\n"); +		irq_domain_remove(parent); +		return -ENOMEM; +	} + +	return 0; +} + +static void tegra_free_domains(struct tegra_msi *msi) +{ +	struct irq_domain *parent = msi->domain->parent; + +	irq_domain_remove(msi->domain); +	irq_domain_remove(parent); +} +  static int tegra_pcie_msi_setup(struct tegra_pcie *pcie)  { -	struct pci_host_bridge *host = pci_host_bridge_from_priv(pcie);  	struct platform_device *pdev = to_platform_device(pcie->dev);  	struct tegra_msi *msi = &pcie->msi;  	struct device *dev = pcie->dev;  	int err; -	mutex_init(&msi->lock); - -	msi->chip.dev = dev; -	msi->chip.setup_irq = tegra_msi_setup_irq; -	msi->chip.teardown_irq = tegra_msi_teardown_irq; +	mutex_init(&msi->map_lock); +	spin_lock_init(&msi->mask_lock); -	msi->domain = irq_domain_add_linear(dev->of_node, INT_PCI_MSI_NR, -					    &msi_domain_ops, &msi->chip); -	if (!msi->domain) { -		dev_err(dev, "failed to create IRQ domain\n"); -		return -ENOMEM; +	if (IS_ENABLED(CONFIG_PCI_MSI)) { +		err = tegra_allocate_domains(msi); +		if (err) +			return err;  	}  	err = platform_get_irq_byname(pdev, "msi"); @@ -1714,12 +1766,7 @@ static int tegra_pcie_msi_setup(struct tegra_pcie *pcie)  	msi->irq = err; -	err = request_irq(msi->irq, tegra_pcie_msi_irq, IRQF_NO_THREAD, -			  tegra_msi_irq_chip.name, pcie); -	if (err < 0) { -		dev_err(dev, "failed to request IRQ: %d\n", err); -		goto free_irq_domain; -	} +	irq_set_chained_handler_and_data(msi->irq, tegra_pcie_msi_irq, pcie);  	/* Though the PCIe controller can address >32-bit address space, to  	 * facilitate endpoints that support only 32-bit MSI target address, @@ -1740,14 +1787,14 @@ static int tegra_pcie_msi_setup(struct tegra_pcie *pcie)  		goto free_irq;  	} -	host->msi = &msi->chip; -  	return 0;  free_irq: -	free_irq(msi->irq, pcie); +	irq_set_chained_handler_and_data(msi->irq, NULL, NULL);  free_irq_domain: -	irq_domain_remove(msi->domain); +	if (IS_ENABLED(CONFIG_PCI_MSI)) +		tegra_free_domains(msi); +  	return err;  } @@ -1755,22 +1802,18 @@ static void tegra_pcie_enable_msi(struct tegra_pcie *pcie)  {  	const struct tegra_pcie_soc *soc = pcie->soc;  	struct tegra_msi *msi = &pcie->msi; -	u32 reg; +	u32 reg, msi_state[INT_PCI_MSI_NR / 32]; +	int i;  	afi_writel(pcie, msi->phys >> soc->msi_base_shift, AFI_MSI_FPCI_BAR_ST);  	afi_writel(pcie, msi->phys, AFI_MSI_AXI_BAR_ST);  	/* this register is in 4K increments */  	afi_writel(pcie, 1, AFI_MSI_BAR_SZ); -	/* enable all MSI vectors */ -	afi_writel(pcie, 0xffffffff, AFI_MSI_EN_VEC0); -	afi_writel(pcie, 0xffffffff, AFI_MSI_EN_VEC1); -	afi_writel(pcie, 0xffffffff, AFI_MSI_EN_VEC2); -	afi_writel(pcie, 0xffffffff, AFI_MSI_EN_VEC3); -	afi_writel(pcie, 0xffffffff, AFI_MSI_EN_VEC4); -	afi_writel(pcie, 0xffffffff, AFI_MSI_EN_VEC5); -	afi_writel(pcie, 0xffffffff, AFI_MSI_EN_VEC6); -	afi_writel(pcie, 0xffffffff, AFI_MSI_EN_VEC7); +	/* Restore the MSI allocation state */ +	bitmap_to_arr32(msi_state, msi->used, INT_PCI_MSI_NR); +	for (i = 0; i < ARRAY_SIZE(msi_state); i++) +		afi_writel(pcie, msi_state[i], AFI_MSI_EN_VEC(i));  	/* and unmask the MSI interrupt */  	reg = afi_readl(pcie, AFI_INTR_MASK); @@ -1786,16 +1829,16 @@ static void tegra_pcie_msi_teardown(struct tegra_pcie *pcie)  	dma_free_attrs(pcie->dev, PAGE_SIZE, msi->virt, msi->phys,  		       DMA_ATTR_NO_KERNEL_MAPPING); -	if (msi->irq > 0) -		free_irq(msi->irq, pcie); -  	for (i = 0; i < INT_PCI_MSI_NR; i++) {  		irq = irq_find_mapping(msi->domain, i);  		if (irq > 0) -			irq_dispose_mapping(irq); +			irq_domain_free_irqs(irq, 1);  	} -	irq_domain_remove(msi->domain); +	irq_set_chained_handler_and_data(msi->irq, NULL, NULL); + +	if (IS_ENABLED(CONFIG_PCI_MSI)) +		tegra_free_domains(msi);  }  static int tegra_pcie_disable_msi(struct tegra_pcie *pcie) @@ -1807,16 +1850,6 @@ static int tegra_pcie_disable_msi(struct tegra_pcie *pcie)  	value &= ~AFI_INTR_MASK_MSI_MASK;  	afi_writel(pcie, value, AFI_INTR_MASK); -	/* disable all MSI vectors */ -	afi_writel(pcie, 0, AFI_MSI_EN_VEC0); -	afi_writel(pcie, 0, AFI_MSI_EN_VEC1); -	afi_writel(pcie, 0, AFI_MSI_EN_VEC2); -	afi_writel(pcie, 0, AFI_MSI_EN_VEC3); -	afi_writel(pcie, 0, AFI_MSI_EN_VEC4); -	afi_writel(pcie, 0, AFI_MSI_EN_VEC5); -	afi_writel(pcie, 0, AFI_MSI_EN_VEC6); -	afi_writel(pcie, 0, AFI_MSI_EN_VEC7); -  	return 0;  } | 
