blob: f59082746c45032df40f9b6e3a9bde7deb7e5462 [file] [log] [blame]
/* 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;
}