| /* Copyright (c) 2012, The Linux Foundation. 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 version 2 and |
| * only version 2 as published by the Free Software Foundation. |
| * |
| * 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. |
| */ |
| |
| /* |
| * MSM PCIe controller IRQ driver. |
| */ |
| |
| #define pr_fmt(fmt) "%s: " fmt, __func__ |
| |
| #include <linux/bitops.h> |
| #include <linux/interrupt.h> |
| #include <linux/irq.h> |
| #include <linux/kernel.h> |
| #include <linux/msi.h> |
| #include <linux/pci.h> |
| #include <mach/irqs.h> |
| |
| #include "pcie.h" |
| |
| /* Any address will do here, as it won't be dereferenced */ |
| #define MSM_PCIE_MSI_PHY 0xa0000000 |
| |
| #define PCIE20_MSI_CTRL_ADDR (0x820) |
| #define PCIE20_MSI_CTRL_UPPER_ADDR (0x824) |
| #define PCIE20_MSI_CTRL_INTR_EN (0x828) |
| #define PCIE20_MSI_CTRL_INTR_MASK (0x82C) |
| #define PCIE20_MSI_CTRL_INTR_STATUS (0x830) |
| |
| #define PCIE20_MSI_CTRL_MAX 8 |
| |
| static DECLARE_BITMAP(msi_irq_in_use, NR_PCIE_MSI_IRQS); |
| |
| static irqreturn_t handle_wake_irq(int irq, void *data) |
| { |
| PCIE_DBG("\n"); |
| return IRQ_HANDLED; |
| } |
| |
| static irqreturn_t handle_msi_irq(int irq, void *data) |
| { |
| int i, j; |
| unsigned long val; |
| struct msm_pcie_dev_t *dev = data; |
| void __iomem *ctrl_status; |
| |
| /* check for set bits, clear it by setting that bit |
| and trigger corresponding irq */ |
| for (i = 0; i < PCIE20_MSI_CTRL_MAX; i++) { |
| ctrl_status = dev->pcie20 + |
| PCIE20_MSI_CTRL_INTR_STATUS + (i * 12); |
| |
| val = readl_relaxed(ctrl_status); |
| while (val) { |
| j = find_first_bit(&val, 32); |
| writel_relaxed(BIT(j), ctrl_status); |
| /* ensure that interrupt is cleared (acked) */ |
| wmb(); |
| |
| generic_handle_irq(MSM_PCIE_MSI_INT(j + (32 * i))); |
| val = readl_relaxed(ctrl_status); |
| } |
| } |
| |
| return IRQ_HANDLED; |
| } |
| |
| uint32_t __init msm_pcie_irq_init(struct msm_pcie_dev_t *dev) |
| { |
| int i, rc; |
| |
| PCIE_DBG("\n"); |
| |
| /* program MSI controller and enable all interrupts */ |
| writel_relaxed(MSM_PCIE_MSI_PHY, dev->pcie20 + PCIE20_MSI_CTRL_ADDR); |
| writel_relaxed(0, dev->pcie20 + PCIE20_MSI_CTRL_UPPER_ADDR); |
| |
| for (i = 0; i < PCIE20_MSI_CTRL_MAX; i++) |
| writel_relaxed(~0, dev->pcie20 + |
| PCIE20_MSI_CTRL_INTR_EN + (i * 12)); |
| |
| /* ensure that hardware is configured before proceeding */ |
| wmb(); |
| |
| /* register handler for physical MSI interrupt line */ |
| rc = request_irq(PCIE20_INT_MSI, handle_msi_irq, IRQF_TRIGGER_RISING, |
| "msm_pcie_msi", dev); |
| if (rc) { |
| pr_err("Unable to allocate msi interrupt\n"); |
| goto out; |
| } |
| |
| /* register handler for PCIE_WAKE_N interrupt line */ |
| rc = request_irq(dev->wake_n, handle_wake_irq, IRQF_TRIGGER_FALLING, |
| "msm_pcie_wake", dev); |
| if (rc) { |
| pr_err("Unable to allocate wake interrupt\n"); |
| free_irq(PCIE20_INT_MSI, dev); |
| goto out; |
| } |
| |
| enable_irq_wake(dev->wake_n); |
| |
| /* PCIE_WAKE_N should be enabled only during system suspend */ |
| disable_irq(dev->wake_n); |
| out: |
| return rc; |
| } |
| |
| void __exit msm_pcie_irq_deinit(struct msm_pcie_dev_t *dev) |
| { |
| free_irq(PCIE20_INT_MSI, dev); |
| free_irq(dev->wake_n, dev); |
| } |
| |
| void msm_pcie_destroy_irq(unsigned int irq) |
| { |
| int pos = irq - MSM_PCIE_MSI_INT(0); |
| |
| dynamic_irq_cleanup(irq); |
| clear_bit(pos, msi_irq_in_use); |
| } |
| |
| /* hookup to linux pci msi framework */ |
| void arch_teardown_msi_irq(unsigned int irq) |
| { |
| PCIE_DBG("irq %d deallocated\n", irq); |
| msm_pcie_destroy_irq(irq); |
| } |
| |
| static void msm_pcie_msi_nop(struct irq_data *d) |
| { |
| return; |
| } |
| |
| static struct irq_chip pcie_msi_chip = { |
| .name = "msm-pcie-msi", |
| .irq_ack = msm_pcie_msi_nop, |
| .irq_enable = unmask_msi_irq, |
| .irq_disable = mask_msi_irq, |
| .irq_mask = mask_msi_irq, |
| .irq_unmask = unmask_msi_irq, |
| }; |
| |
| static int msm_pcie_create_irq(void) |
| { |
| int irq, pos; |
| |
| again: |
| pos = find_first_zero_bit(msi_irq_in_use, NR_PCIE_MSI_IRQS); |
| irq = MSM_PCIE_MSI_INT(pos); |
| if (irq >= (MSM_PCIE_MSI_INT(0) + NR_PCIE_MSI_IRQS)) |
| return -ENOSPC; |
| |
| if (test_and_set_bit(pos, msi_irq_in_use)) |
| goto again; |
| |
| dynamic_irq_init(irq); |
| return irq; |
| } |
| |
| /* hookup to linux pci msi framework */ |
| int arch_setup_msi_irq(struct pci_dev *pdev, struct msi_desc *desc) |
| { |
| int irq; |
| struct msi_msg msg; |
| |
| irq = msm_pcie_create_irq(); |
| if (irq < 0) |
| return irq; |
| |
| PCIE_DBG("irq %d allocated\n", irq); |
| |
| irq_set_msi_desc(irq, desc); |
| |
| /* write msi vector and data */ |
| msg.address_hi = 0; |
| msg.address_lo = MSM_PCIE_MSI_PHY; |
| msg.data = irq - MSM_PCIE_MSI_INT(0); |
| write_msi_msg(irq, &msg); |
| |
| irq_set_chip_and_handler(irq, &pcie_msi_chip, handle_simple_irq); |
| set_irq_flags(irq, IRQF_VALID); |
| return 0; |
| } |