blob: 406bf331ee194ee5d327f282be091d86d9062afe [file] [log] [blame]
/*
* GIC MSI Extensions Driver.
*
* This driver provides support for up to 1 MSI per pci_dev. The functionality
* is provided by an MSI widget which provides an MSI capture register which
* fires SPIs. This driver hooks into the standard MSI arch callbacks and maps
* MSI vectors to SPI interrupts.
*
* Copyright (C) 2012-2013 ARM Ltd.
* Author: Andrew Murray <andrew.murray@arm.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License 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.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include <linux/io.h>
#include <linux/irq.h>
#include <linux/irqchip/arm-gic-msi.h>
#include <linux/module.h>
#include <linux/msi.h>
#include <linux/of_address.h>
#include <linux/of_irq.h>
#include <linux/of_platform.h>
#include <linux/pci.h>
#include <linux/slab.h>
#define DEVICE_NAME "gic-msi"
#ifdef CONFIG_PCI_MSI
/* The kernel only supports a single MSI controller, thus our driver is limited
to a single instance */
static struct gic_msi_data *gd;
int arch_setup_msi_irq(struct pci_dev *pdev, struct msi_desc *desc)
{
int virt, vector;
struct msi_msg msg;
struct gic_msi_frame *frame;
if (WARN(!gd, DEVICE_NAME ": No MSI handler"))
return 1;
/* Look in all the frames to find an available MSI vector/SPI */
list_for_each_entry(frame, &gd->frames, list) {
again:
vector = find_first_zero_bit(frame->msi_irq_in_use,
frame->nr_msi);
if (vector >= frame->nr_msi)
continue;
if (test_and_set_bit(vector, frame->msi_irq_in_use))
goto again;
virt = vector + frame->start_spi;
irq_set_msi_desc(virt, desc);
irq_set_irq_type(virt, IRQ_TYPE_EDGE_RISING);
#ifdef CONFIG_64BIT
msg.address_hi = (phys_addr_t)frame->base_p >> 32;
#endif
msg.address_lo = (phys_addr_t)frame->base_p + GIC_MSI_SETSPI_NSR;
msg.data = virt;
write_msi_msg(virt, &msg);
return 0;
}
/* No free MSIs */
WARN_ON(DEVICE_NAME ": Unable to assign another MSI");
return 1;
}
void arch_teardown_msi_irq(unsigned int irq)
{
struct gic_msi_frame *frame;
int hwirq;
if (WARN(!gd, DEVICE_NAME ": No MSI handler"))
return;
list_for_each_entry(frame, &gd->frames, list) {
hwirq = irq - frame->start_spi;
if (hwirq < frame->nr_msi) {
clear_bit(hwirq, frame->msi_irq_in_use);
return;
}
}
}
static int gic_msi_init_frame(struct resource *res)
{
struct gic_msi_frame *frame;
int err, x;
frame = kzalloc(sizeof(struct gic_msi_frame), GFP_KERNEL);
if (WARN(!frame, DEVICE_NAME ": Unable to allocate memory"))
return -ENOMEM;
frame->base_p = res->start;
frame->base_sz = resource_size(res);
if (!request_mem_region(res->start, resource_size(res), DEVICE_NAME)) {
pr_err(DEVICE_NAME
": Failed to request config registers resource\n");
err = -EBUSY;
goto err_request_mem;
}
frame->base_p= res->start;
frame->base_sz= resource_size(res);
frame->base = ioremap(res->start, resource_size(res));
if (!frame->base) {
pr_err(DEVICE_NAME
": Failed to map configuration registers resource\n");
err = -ENOMEM;
goto err_ioremap;
}
x = readl(frame->base + GIC_MSI_TYPER);
frame->start_spi = (x & GIC_MSI_TYPER_START_MASK)
>> GIC_MSI_TYPER_START_SHIFT;
frame->nr_msi = x & GIC_MSI_TYPER_NR_MASK;
list_add_tail(&frame->list, &gd->frames);
return 0;
err_ioremap:
iounmap(frame->base);
err_request_mem:
kfree(frame);
return err;
}
static int gic_msi_remove_frames(void)
{
struct gic_msi_frame *frame, *tmp;
list_for_each_entry_safe(frame, tmp, &gd->frames, list) {
iounmap(frame->base);
release_mem_region(frame->base_p, frame->base_sz);
list_del(&frame->list);
kfree(frame);
}
return 0;
}
static int gic_msi_get_virt_offset(struct device_node *np, int *virt_offset)
{
struct device_node *p;
struct irq_domain *domain;
unsigned int type, spec[3] = { 0, 0, 0 };
irq_hw_number_t hwirq;
int err;
p = of_irq_find_parent(np);
if (WARN(!p, DEVICE_NAME ": Unable to find interrupt parent"))
return -EINVAL;
domain = irq_find_host(p);
if (WARN(!domain, DEVICE_NAME
": Unable to find interrupt parent domain"))
goto err;
if (WARN(!domain->ops->xlate, DEVICE_NAME
": Unable to translate interrupt parent domain"))
goto err;
err = domain->ops->xlate(domain, p, spec, sizeof(spec) / sizeof(int),
&hwirq, &type);
if (err) {
pr_err(DEVICE_NAME
": Unable to translate interrupt parent domain\n");
goto err;
}
*virt_offset = irq_create_mapping(domain, hwirq);
of_node_put(p);
return 0;
err:
of_node_put(p);
return -EINVAL;
}
static int gic_msi_probe(struct platform_device *pdev)
{
int err, x = 0;
struct device_node *np;
struct resource res;
np = pdev->dev.of_node;
if (WARN(gd, DEVICE_NAME ": MSI handler already installed"))
return -EEXIST;
if (WARN(!np, DEVICE_NAME ": No device tree node"))
return -EINVAL;
gd = kzalloc(sizeof(struct gic_msi_data), GFP_KERNEL);
if (WARN(!gd, DEVICE_NAME ": Unable to allocate memory"))
return -ENOMEM;
INIT_LIST_HEAD(&gd->frames);
err = gic_msi_get_virt_offset(np, &gd->virt_offset);
if (err) {
pr_err(DEVICE_NAME
": Unable to get virtual offset\n");
goto err_init_frame;
}
/* Iterate through each frame identified in the DT */
while (!(err = of_address_to_resource(np, x++, &res))) {
err = gic_msi_init_frame(&res);
if (err)
goto err_init_frame;
break;
}
return 0;
err_init_frame:
gic_msi_remove_frames();
kfree(gd);
gd = NULL;
return -EINVAL;
}
/**
* Remove is a bad idea. We cannot easily tell PCIe devices to stop
* sending MSI's they've been allocated. In any case remove is unlikely
* to be used.
*/
static int gic_msi_remove(struct platform_device *pdev)
{
gic_msi_remove_frames();
kfree(gd);
gd = NULL;
return 0;
}
static const struct of_device_id gic_msi_ids[] = {
{ .compatible = "arm,gic-msi", },
};
static struct platform_driver gic_msi_driver = {
.probe = gic_msi_probe,
.remove = gic_msi_remove,
.driver = {
.name = DEVICE_NAME,
.owner = THIS_MODULE,
.of_match_table = gic_msi_ids,
},
};
static int __init gic_msi_init(void)
{
return platform_driver_register(&gic_msi_driver);
}
subsys_initcall(gic_msi_init);
#endif