/* 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 #include #include #include #include #include #include #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; }