Message ID | 000401ce9739$e0a65410$a1f2fc30$@samsung.com (mailing list archive) |
---|---|
State | New, archived |
Delegated to: | Bjorn Helgaas |
Headers | show |
Hi Jingoo, On 12 August 2013 14:26, Jingoo Han <jg1.han@samsung.com> wrote: > This patch adds support for Message Signaled Interrupt in the > Exynops PCIe diver using Synopsys designware PCIe core IP. s/Exynops PCIe diver/Exynos PCIe driver > > 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> > Cc: Pratyush Anand <pratyush.anand@st.com> > Cc: Mohit KUMAR <Mohit.KUMAR@st.com> > --- > arch/arm/boot/dts/exynos5440.dtsi | 2 + > arch/arm/mach-exynos/Kconfig | 1 + > drivers/pci/host/pci-exynos.c | 60 ++++++++++ > drivers/pci/host/pcie-designware.c | 213 ++++++++++++++++++++++++++++++++++++ > drivers/pci/host/pcie-designware.h | 8 ++ > 5 files changed, 284 insertions(+) > > diff --git a/arch/arm/boot/dts/exynos5440.dtsi b/arch/arm/boot/dts/exynos5440.dtsi > index 586134e..3746835 100644 > --- a/arch/arm/boot/dts/exynos5440.dtsi > +++ b/arch/arm/boot/dts/exynos5440.dtsi > @@ -249,6 +249,7 @@ > interrupt-map-mask = <0 0 0 0>; > interrupt-map = <0x0 0 &gic 53>; > num-lanes = <4>; > + msi-base = <200>; Please update the bindings documentation too. > }; > > pcie@2a0000 { > @@ -269,5 +270,6 @@ [snip] > > +#ifdef CONFIG_PCI_MSI > +static void exynos_pcie_clear_irq_level(struct pcie_port *pp) > +{ > + u32 val; > + struct exynos_pcie *exynos_pcie = to_exynos_pcie(pp); > + void __iomem *elbi_base = exynos_pcie->elbi_base; > + > + val = readl(elbi_base + PCIE_IRQ_LEVEL); > + writel(val, elbi_base + PCIE_IRQ_LEVEL); Sorry, I did not get this. Writing the value read from the same register without any operation.
On Mon, Aug 12, 2013 at 05:56:47PM +0900, Jingoo Han wrote: [...] > diff --git a/arch/arm/mach-exynos/Kconfig b/arch/arm/mach-exynos/Kconfig > index 855d4a7..9ef1c95 100644 > --- a/arch/arm/mach-exynos/Kconfig > +++ b/arch/arm/mach-exynos/Kconfig > @@ -93,6 +93,7 @@ config SOC_EXYNOS5440 > default y > depends on ARCH_EXYNOS5 > select ARCH_HAS_OPP > + select ARCH_SUPPORTS_MSI This symbol goes away in Thomas Petazzoni's MSI patch series which is targetted at 3.12, so I don't think you should add that here. > +#ifdef CONFIG_PCI_MSI > +static void exynos_pcie_clear_irq_level(struct pcie_port *pp) > +{ > + u32 val; > + struct exynos_pcie *exynos_pcie = to_exynos_pcie(pp); > + void __iomem *elbi_base = exynos_pcie->elbi_base; > + > + val = readl(elbi_base + PCIE_IRQ_LEVEL); > + writel(val, elbi_base + PCIE_IRQ_LEVEL); > + return; > +} I'm a little confused by this: the above code seems to access the PCIe controller registers to clear an interrupt, but you pass in a PCIe port... > +static irqreturn_t exynos_pcie_msi_irq_handler(int irq, void *arg) > +{ > + struct pcie_port *pp = arg; > + > + /* handle msi irq */ > + dw_handle_msi_irq(pp); > + exynos_pcie_clear_irq_level(pp); ... so here dw_handle_msi_irq() seems to operate on a single port, while clearing the IRQ is done on a per-controller basis. I see that the Exynos PCIe driver hasn't made it into linux-next yet, so I don't have full context surrounding this, but it strikes me as odd that MSI's would be handled per-port instead of per-controller. And furthermore that the DesignWare part handles it per-port yet the Exynos specific part handles it per-controller. > + > + return IRQ_HANDLED; > +} > + > +static void exynos_pcie_msi_init(struct pcie_port *pp) > +{ > + u32 val; > + struct exynos_pcie *exynos_pcie = to_exynos_pcie(pp); > + void __iomem *elbi_base = exynos_pcie->elbi_base; > + > + dw_pcie_msi_init(pp); > + > + /* enable MSI interrupt */ > + val = readl(elbi_base + PCIE_IRQ_EN_LEVEL); > + val |= IRQ_MSI_ENABLE; > + writel(val, elbi_base + PCIE_IRQ_EN_LEVEL); > + return; > +} This function is called per-port, yet operates on per-controller registers. It's not terribly bad in this case because it only sets one bit, but it could eventually lead to problems in case you need to extend this function in the future to do more, which could then potentially be run multiple times and cause problems. > +#endif > + > static void exynos_pcie_enable_interrupts(struct pcie_port *pp) > { > exynos_pcie_enable_irq_pulse(pp); > +#ifdef CONFIG_PCI_MSI > + exynos_pcie_msi_init(pp); > +#endif > return; > } Instead of the whole #ifdef business above, can't you just use something like this in exynos_pcie_enable_interrupts(): if (IS_ENABLED(CONFIG_PCI_MSI)) exynos_pcie_msi_init(pp); Now you can drop the #ifdef guards and the compiler will throw away all the related code automatically if PCI_MSI is not selected because the functions are all static and unused. This has the advantage of compiling all the code whether or not PCI_MSI is selected or not, therefore increasing compile coverage of the driver. > diff --git a/drivers/pci/host/pcie-designware.c b/drivers/pci/host/pcie-designware.c [...] > @@ -62,6 +64,14 @@ > #define PCIE_ATU_FUNC(x) (((x) & 0x7) << 16) > #define PCIE_ATU_UPPER_TARGET 0x91C > > +#ifdef CONFIG_PCI_MSI > +#define MAX_MSI_IRQS 32 > +#define MAX_MSI_CTRLS 8 > + > +static unsigned int msi_data; > +static DECLARE_BITMAP(msi_irq_in_use, MAX_MSI_IRQS); > +#endif > + > static struct hw_pci dw_pci; > > unsigned long global_io_offset; > @@ -144,6 +154,202 @@ int dw_pcie_wr_own_conf(struct pcie_port *pp, int where, int size, > return ret; > } > > +#ifdef CONFIG_PCI_MSI > +static struct irq_chip dw_msi_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) > +{ > + /* program the msi_data */ > + dw_pcie_wr_own_conf(pp, PCIE_MSI_ADDR_LO, 4, > + __virt_to_phys((u32)(&msi_data))); That's slightly odd. You convert the virtual address of a local variable (local to the file) to a physical address and program that into a register. I assume that it works since you've probably tested this, but I wonder if it's safe to do this. Perhaps a better way would be to allocate a single free page (__get_free_pages(GFP_KERNEL, 0)) and write the physical address of that into the register instead. > +static int find_valid_pos0(int msgvec, int pos, int *pos0) > +{ > + int flag = 1; > + > + do { > + pos = find_next_zero_bit(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; > +} > + > +/* Dynamic irq allocate and deallocation */ > +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(msi_irq_in_use, > + MAX_MSI_IRQS); > + if (pos0 % no_irqs) { > + if (find_valid_pos0(no_irqs, pos0, &pos0)) > + goto no_valid_irq; > + } > + if (no_irqs > 1) { > + pos1 = find_next_bit(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(no_irqs, pos1, &pos0)) > + goto no_valid_irq; > + pos1 = find_next_bit(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, msi_irq_in_use); > + irq_alloc_descs((irq + i), (irq + i), 1, 0); > + irq_set_msi_desc(irq + i, desc); > + irq_set_chip_and_handler(irq + i, &dw_msi_chip, > + handle_simple_irq); > + set_irq_flags(irq + i, IRQF_VALID); > + /*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; > +} There was some discussion about this already, and I think eventually we really should refactor some of this code. Currently all three ARM PCIe drivers (Marvell, Tegra and Exynos/DesignWare) use a similar bitmap- based allocator for this. Benjamin Herrenschmidt pointed out that the same exists for PowerPC as well, so we should look at converging on one implementation eventually. But I think that can be done subsequently and shouldn't have to be done prior to this patch. > +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, 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); > +} Oh, and you should probably look into using an IRQ domain for the MSI support in this driver. > +int arch_setup_msi_irq(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_hi = 0x0; > + msg.address_lo = __virt_to_phys((u32)(&msi_data)); > + msg.data = pos; > + write_msi_msg(irq, &msg); > + > + return 0; > +} > + > +void arch_teardown_msi_irq(unsigned int irq) > +{ > + clear_irq(irq); > +} And we've reworked this largely so that drivers no longer provide arch_* functions because that prevents multi-platform support. So I think you need to port this to the new msi_chip infrastructure that's being introduced in 3.12. Thierry
On Mon, Aug 12, 2013 at 06:56:40PM +0800, Thierry Reding wrote: > On Mon, Aug 12, 2013 at 05:56:47PM +0900, Jingoo Han wrote: > [...] > > diff --git a/arch/arm/mach-exynos/Kconfig b/arch/arm/mach-exynos/Kconfig > > index 855d4a7..9ef1c95 100644 > > --- a/arch/arm/mach-exynos/Kconfig > > +++ b/arch/arm/mach-exynos/Kconfig > > @@ -93,6 +93,7 @@ config SOC_EXYNOS5440 > > default y > > depends on ARCH_EXYNOS5 > > select ARCH_HAS_OPP > > + select ARCH_SUPPORTS_MSI > > This symbol goes away in Thomas Petazzoni's MSI patch series which is > targetted at 3.12, so I don't think you should add that here. > > > +#ifdef CONFIG_PCI_MSI > > +static void exynos_pcie_clear_irq_level(struct pcie_port *pp) > > +{ > > + u32 val; > > + struct exynos_pcie *exynos_pcie = to_exynos_pcie(pp); > > + void __iomem *elbi_base = exynos_pcie->elbi_base; > > + > > + val = readl(elbi_base + PCIE_IRQ_LEVEL); > > + writel(val, elbi_base + PCIE_IRQ_LEVEL); > > + return; > > +} > > I'm a little confused by this: the above code seems to access the PCIe > controller registers to clear an interrupt, but you pass in a PCIe > port... > One struct pcie_port is associated with one controller and it has been assumed that there is only one root port per controller. [...] > > +void dw_pcie_msi_init(struct pcie_port *pp) > > +{ > > + /* program the msi_data */ > > + dw_pcie_wr_own_conf(pp, PCIE_MSI_ADDR_LO, 4, > > + __virt_to_phys((u32)(&msi_data))); > > That's slightly odd. You convert the virtual address of a local variable > (local to the file) to a physical address and program that into a > register. I assume that it works since you've probably tested this, but > I wonder if it's safe to do this. Perhaps a better way would be to > allocate a single free page (__get_free_pages(GFP_KERNEL, 0)) and write > the physical address of that into the register instead. > also msi_data must be different for different controller. Something like &msi_data[pp->port]. [...] > > +void arch_teardown_msi_irq(unsigned int irq) > > +{ > > + clear_irq(irq); > > +} > > And we've reworked this largely so that drivers no longer provide arch_* > functions because that prevents multi-platform support. So I think you > need to port this to the new msi_chip infrastructure that's being > introduced in 3.12. Yes, its needed. Regards Pratyush > > Thierry -- To unsubscribe from this list: send the line "unsubscribe linux-pci" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html
On Monday, August 12, 2013 6:13 PM, Sachin Kamat wrote: > On 12 August 2013 14:26, Jingoo Han <jg1.han@samsung.com> wrote: > > This patch adds support for Message Signaled Interrupt in the > > Exynops PCIe diver using Synopsys designware PCIe core IP. > > s/Exynops PCIe diver/Exynos PCIe driver OK, I will fix this typo. > > 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> > > Cc: Pratyush Anand <pratyush.anand@st.com> > > Cc: Mohit KUMAR <Mohit.KUMAR@st.com> > > --- > > arch/arm/boot/dts/exynos5440.dtsi | 2 + > > arch/arm/mach-exynos/Kconfig | 1 + > > drivers/pci/host/pci-exynos.c | 60 ++++++++++ > > drivers/pci/host/pcie-designware.c | 213 ++++++++++++++++++++++++++++++++++++ > > drivers/pci/host/pcie-designware.h | 8 ++ > > 5 files changed, 284 insertions(+) > > > > diff --git a/arch/arm/boot/dts/exynos5440.dtsi b/arch/arm/boot/dts/exynos5440.dtsi > > index 586134e..3746835 100644 > > --- a/arch/arm/boot/dts/exynos5440.dtsi > > +++ b/arch/arm/boot/dts/exynos5440.dtsi > > @@ -249,6 +249,7 @@ > > interrupt-map-mask = <0 0 0 0>; > > interrupt-map = <0x0 0 &gic 53>; > > num-lanes = <4>; > > + msi-base = <200>; > > Please update the bindings documentation too. OK, I will updated the bindings documentation. [.....] > > +#ifdef CONFIG_PCI_MSI > > +static void exynos_pcie_clear_irq_level(struct pcie_port *pp) > > +{ > > + u32 val; > > + struct exynos_pcie *exynos_pcie = to_exynos_pcie(pp); > > + void __iomem *elbi_base = exynos_pcie->elbi_base; > > + > > + val = readl(elbi_base + PCIE_IRQ_LEVEL); > > + writel(val, elbi_base + PCIE_IRQ_LEVEL); > > Sorry, I did not get this. Writing the value read from the same > register without any operation. It was intended to clear the bits by writing 1 of each bit. But I will remove this function. My coworker, Srikanth T Shivanand found that this function is unnecessary. This is because PCIE_IRQ_LEVEL register is read-only register. Also, PCIE_IRQ_LEVEL register is already cleared before this function is called. Thank you for your comment. Best regards, Jingoo Han -- To unsubscribe from this list: send the line "unsubscribe linux-pci" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html
On Monday, August 12, 2013 7:57 PM, Thierry Reding wrote: > On Mon, Aug 12, 2013 at 05:56:47PM +0900, Jingoo Han wrote: > [...] > > diff --git a/arch/arm/mach-exynos/Kconfig b/arch/arm/mach-exynos/Kconfig > > index 855d4a7..9ef1c95 100644 > > --- a/arch/arm/mach-exynos/Kconfig > > +++ b/arch/arm/mach-exynos/Kconfig > > @@ -93,6 +93,7 @@ config SOC_EXYNOS5440 > > default y > > depends on ARCH_EXYNOS5 > > select ARCH_HAS_OPP > > + select ARCH_SUPPORTS_MSI > > This symbol goes away in Thomas Petazzoni's MSI patch series which is > targetted at 3.12, so I don't think you should add that here. OK, I see. I will remove ARCH_SUPPORTS_MSI. [.....] > > +#endif > > + > > static void exynos_pcie_enable_interrupts(struct pcie_port *pp) > > { > > exynos_pcie_enable_irq_pulse(pp); > > +#ifdef CONFIG_PCI_MSI > > + exynos_pcie_msi_init(pp); > > +#endif > > return; > > } > > Instead of the whole #ifdef business above, can't you just use something > like this in exynos_pcie_enable_interrupts(): > > if (IS_ENABLED(CONFIG_PCI_MSI)) > exynos_pcie_msi_init(pp); > > Now you can drop the #ifdef guards and the compiler will throw away all > the related code automatically if PCI_MSI is not selected because the > functions are all static and unused. This has the advantage of compiling > all the code whether or not PCI_MSI is selected or not, therefore > increasing compile coverage of the driver. OK, I see. I will use 'if IS_ENABLED(CONFIG_PCI_MSI))', and remove #ifdef guards. [.....] > > + > > +void arch_teardown_msi_irq(unsigned int irq) > > +{ > > + clear_irq(irq); > > +} > > And we've reworked this largely so that drivers no longer provide arch_* > functions because that prevents multi-platform support. So I think you > need to port this to the new msi_chip infrastructure that's being > introduced in 3.12. OK, I have looked at the new msi_chip infrastructure made by Thomas Petazzoni. I will use this msi_chip. I really appreciate your comment. :) Thank you. Best regards, Jingoo Han -- To unsubscribe from this list: send the line "unsubscribe linux-pci" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html
diff --git a/arch/arm/boot/dts/exynos5440.dtsi b/arch/arm/boot/dts/exynos5440.dtsi index 586134e..3746835 100644 --- a/arch/arm/boot/dts/exynos5440.dtsi +++ b/arch/arm/boot/dts/exynos5440.dtsi @@ -249,6 +249,7 @@ interrupt-map-mask = <0 0 0 0>; interrupt-map = <0x0 0 &gic 53>; num-lanes = <4>; + msi-base = <200>; }; pcie@2a0000 { @@ -269,5 +270,6 @@ interrupt-map-mask = <0 0 0 0>; interrupt-map = <0x0 0 &gic 56>; num-lanes = <4>; + msi-base = <232>; }; }; diff --git a/arch/arm/mach-exynos/Kconfig b/arch/arm/mach-exynos/Kconfig index 855d4a7..9ef1c95 100644 --- a/arch/arm/mach-exynos/Kconfig +++ b/arch/arm/mach-exynos/Kconfig @@ -93,6 +93,7 @@ config SOC_EXYNOS5440 default y depends on ARCH_EXYNOS5 select ARCH_HAS_OPP + select ARCH_SUPPORTS_MSI select HAVE_ARM_ARCH_TIMER select AUTO_ZRELADDR select MIGHT_HAVE_PCI diff --git a/drivers/pci/host/pci-exynos.c b/drivers/pci/host/pci-exynos.c index 012ca8a..d0477d0 100644 --- a/drivers/pci/host/pci-exynos.c +++ b/drivers/pci/host/pci-exynos.c @@ -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 @@ -320,9 +321,51 @@ static irqreturn_t exynos_pcie_irq_handler(int irq, void *arg) return IRQ_HANDLED; } +#ifdef CONFIG_PCI_MSI +static void exynos_pcie_clear_irq_level(struct pcie_port *pp) +{ + u32 val; + struct exynos_pcie *exynos_pcie = to_exynos_pcie(pp); + void __iomem *elbi_base = exynos_pcie->elbi_base; + + val = readl(elbi_base + PCIE_IRQ_LEVEL); + writel(val, elbi_base + PCIE_IRQ_LEVEL); + return; +} + +static irqreturn_t exynos_pcie_msi_irq_handler(int irq, void *arg) +{ + struct pcie_port *pp = arg; + + /* handle msi irq */ + dw_handle_msi_irq(pp); + exynos_pcie_clear_irq_level(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); + void __iomem *elbi_base = exynos_pcie->elbi_base; + + dw_pcie_msi_init(pp); + + /* enable MSI interrupt */ + val = readl(elbi_base + PCIE_IRQ_EN_LEVEL); + val |= IRQ_MSI_ENABLE; + writel(val, elbi_base + PCIE_IRQ_EN_LEVEL); + return; +} +#endif + static void exynos_pcie_enable_interrupts(struct pcie_port *pp) { exynos_pcie_enable_irq_pulse(pp); +#ifdef CONFIG_PCI_MSI + exynos_pcie_msi_init(pp); +#endif return; } @@ -408,6 +451,23 @@ static int add_pcie_port(struct pcie_port *pp, struct platform_device *pdev) return ret; } +#ifdef 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; + } +#endif + pp->root_bus_nr = -1; pp->ops = &exynos_pcie_host_ops; diff --git a/drivers/pci/host/pcie-designware.c b/drivers/pci/host/pcie-designware.c index 77b0c25..5a47f11 100644 --- a/drivers/pci/host/pcie-designware.c +++ b/drivers/pci/host/pcie-designware.c @@ -11,8 +11,10 @@ * published by the Free Software Foundation. */ +#include <linux/irq.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> @@ -62,6 +64,14 @@ #define PCIE_ATU_FUNC(x) (((x) & 0x7) << 16) #define PCIE_ATU_UPPER_TARGET 0x91C +#ifdef CONFIG_PCI_MSI +#define MAX_MSI_IRQS 32 +#define MAX_MSI_CTRLS 8 + +static unsigned int msi_data; +static DECLARE_BITMAP(msi_irq_in_use, MAX_MSI_IRQS); +#endif + static struct hw_pci dw_pci; unsigned long global_io_offset; @@ -144,6 +154,202 @@ int dw_pcie_wr_own_conf(struct pcie_port *pp, int where, int size, return ret; } +#ifdef CONFIG_PCI_MSI +static struct irq_chip dw_msi_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) +{ + /* program the msi_data */ + dw_pcie_wr_own_conf(pp, PCIE_MSI_ADDR_LO, 4, + __virt_to_phys((u32)(&msi_data))); + dw_pcie_wr_own_conf(pp, PCIE_MSI_ADDR_HI, 4, 0); +} + +static int find_valid_pos0(int msgvec, int pos, int *pos0) +{ + int flag = 1; + + do { + pos = find_next_zero_bit(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; +} + +/* Dynamic irq allocate and deallocation */ +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(msi_irq_in_use, + MAX_MSI_IRQS); + if (pos0 % no_irqs) { + if (find_valid_pos0(no_irqs, pos0, &pos0)) + goto no_valid_irq; + } + if (no_irqs > 1) { + pos1 = find_next_bit(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(no_irqs, pos1, &pos0)) + goto no_valid_irq; + pos1 = find_next_bit(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, msi_irq_in_use); + irq_alloc_descs((irq + i), (irq + i), 1, 0); + irq_set_msi_desc(irq + i, desc); + irq_set_chip_and_handler(irq + i, &dw_msi_chip, + handle_simple_irq); + set_irq_flags(irq + i, IRQF_VALID); + /*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, 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); +} + +int arch_setup_msi_irq(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_hi = 0x0; + msg.address_lo = __virt_to_phys((u32)(&msi_data)); + msg.data = pos; + write_msi_msg(irq, &msg); + + return 0; +} + +void arch_teardown_msi_irq(unsigned int irq) +{ + clear_irq(irq); +} +#endif + int dw_pcie_link_up(struct pcie_port *pp) { if (pp->ops->link_up) @@ -225,6 +431,13 @@ int __init dw_pcie_host_init(struct pcie_port *pp) return -EINVAL; } +#ifdef CONFIG_PCI_MSI + if (of_property_read_u32(np, "msi-base", &pp->msi_irq_start)) { + dev_err(pp->dev, "Failed to parse the number of lanes\n"); + return -EINVAL; + } +#endif + if (pp->ops->host_init) pp->ops->host_init(pp); diff --git a/drivers/pci/host/pcie-designware.h b/drivers/pci/host/pcie-designware.h index 133820f..fcff10e 100644 --- a/drivers/pci/host/pcie-designware.h +++ b/drivers/pci/host/pcie-designware.h @@ -38,6 +38,10 @@ struct pcie_port { int irq; u32 lanes; struct pcie_host_ops *ops; +#ifdef CONFIG_PCI_MSI + int msi_irq; + int msi_irq_start; +#endif }; struct pcie_host_ops { @@ -57,6 +61,10 @@ 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); +#ifdef CONFIG_PCI_MSI +void dw_handle_msi_irq(struct pcie_port *pp); +void dw_pcie_msi_init(struct pcie_port *pp); +#endif 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);