mirror of https://github.com/Qortal/Brooklyn
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
236 lines
5.7 KiB
236 lines
5.7 KiB
/* |
|
* Copyright (C) 2016 Marvell |
|
* |
|
* Thomas Petazzoni <[email protected]> |
|
* |
|
* This file is licensed under the terms of the GNU General Public |
|
* License version 2. This program is licensed "as is" without any |
|
* warranty of any kind, whether express or implied. |
|
*/ |
|
|
|
#define pr_fmt(fmt) "GIC-ODMI: " fmt |
|
|
|
#include <linux/irq.h> |
|
#include <linux/irqchip.h> |
|
#include <linux/irqdomain.h> |
|
#include <linux/kernel.h> |
|
#include <linux/msi.h> |
|
#include <linux/of_address.h> |
|
#include <linux/slab.h> |
|
#include <dt-bindings/interrupt-controller/arm-gic.h> |
|
|
|
#define GICP_ODMIN_SET 0x40 |
|
#define GICP_ODMI_INT_NUM_SHIFT 12 |
|
#define GICP_ODMIN_GM_EP_R0 0x110 |
|
#define GICP_ODMIN_GM_EP_R1 0x114 |
|
#define GICP_ODMIN_GM_EA_R0 0x108 |
|
#define GICP_ODMIN_GM_EA_R1 0x118 |
|
|
|
/* |
|
* We don't support the group events, so we simply have 8 interrupts |
|
* per frame. |
|
*/ |
|
#define NODMIS_SHIFT 3 |
|
#define NODMIS_PER_FRAME (1 << NODMIS_SHIFT) |
|
#define NODMIS_MASK (NODMIS_PER_FRAME - 1) |
|
|
|
struct odmi_data { |
|
struct resource res; |
|
void __iomem *base; |
|
unsigned int spi_base; |
|
}; |
|
|
|
static struct odmi_data *odmis; |
|
static unsigned long *odmis_bm; |
|
static unsigned int odmis_count; |
|
|
|
/* Protects odmis_bm */ |
|
static DEFINE_SPINLOCK(odmis_bm_lock); |
|
|
|
static void odmi_compose_msi_msg(struct irq_data *d, struct msi_msg *msg) |
|
{ |
|
struct odmi_data *odmi; |
|
phys_addr_t addr; |
|
unsigned int odmin; |
|
|
|
if (WARN_ON(d->hwirq >= odmis_count * NODMIS_PER_FRAME)) |
|
return; |
|
|
|
odmi = &odmis[d->hwirq >> NODMIS_SHIFT]; |
|
odmin = d->hwirq & NODMIS_MASK; |
|
|
|
addr = odmi->res.start + GICP_ODMIN_SET; |
|
|
|
msg->address_hi = upper_32_bits(addr); |
|
msg->address_lo = lower_32_bits(addr); |
|
msg->data = odmin << GICP_ODMI_INT_NUM_SHIFT; |
|
} |
|
|
|
static struct irq_chip odmi_irq_chip = { |
|
.name = "ODMI", |
|
.irq_mask = irq_chip_mask_parent, |
|
.irq_unmask = irq_chip_unmask_parent, |
|
.irq_eoi = irq_chip_eoi_parent, |
|
.irq_set_affinity = irq_chip_set_affinity_parent, |
|
.irq_compose_msi_msg = odmi_compose_msi_msg, |
|
}; |
|
|
|
static int odmi_irq_domain_alloc(struct irq_domain *domain, unsigned int virq, |
|
unsigned int nr_irqs, void *args) |
|
{ |
|
struct odmi_data *odmi = NULL; |
|
struct irq_fwspec fwspec; |
|
struct irq_data *d; |
|
unsigned int hwirq, odmin; |
|
int ret; |
|
|
|
spin_lock(&odmis_bm_lock); |
|
hwirq = find_first_zero_bit(odmis_bm, NODMIS_PER_FRAME * odmis_count); |
|
if (hwirq >= NODMIS_PER_FRAME * odmis_count) { |
|
spin_unlock(&odmis_bm_lock); |
|
return -ENOSPC; |
|
} |
|
|
|
__set_bit(hwirq, odmis_bm); |
|
spin_unlock(&odmis_bm_lock); |
|
|
|
odmi = &odmis[hwirq >> NODMIS_SHIFT]; |
|
odmin = hwirq & NODMIS_MASK; |
|
|
|
fwspec.fwnode = domain->parent->fwnode; |
|
fwspec.param_count = 3; |
|
fwspec.param[0] = GIC_SPI; |
|
fwspec.param[1] = odmi->spi_base - 32 + odmin; |
|
fwspec.param[2] = IRQ_TYPE_EDGE_RISING; |
|
|
|
ret = irq_domain_alloc_irqs_parent(domain, virq, 1, &fwspec); |
|
if (ret) { |
|
pr_err("Cannot allocate parent IRQ\n"); |
|
spin_lock(&odmis_bm_lock); |
|
__clear_bit(odmin, odmis_bm); |
|
spin_unlock(&odmis_bm_lock); |
|
return ret; |
|
} |
|
|
|
/* Configure the interrupt line to be edge */ |
|
d = irq_domain_get_irq_data(domain->parent, virq); |
|
d->chip->irq_set_type(d, IRQ_TYPE_EDGE_RISING); |
|
|
|
irq_domain_set_hwirq_and_chip(domain, virq, hwirq, |
|
&odmi_irq_chip, NULL); |
|
|
|
return 0; |
|
} |
|
|
|
static void odmi_irq_domain_free(struct irq_domain *domain, |
|
unsigned int virq, unsigned int nr_irqs) |
|
{ |
|
struct irq_data *d = irq_domain_get_irq_data(domain, virq); |
|
|
|
if (d->hwirq >= odmis_count * NODMIS_PER_FRAME) { |
|
pr_err("Failed to teardown msi. Invalid hwirq %lu\n", d->hwirq); |
|
return; |
|
} |
|
|
|
irq_domain_free_irqs_parent(domain, virq, nr_irqs); |
|
|
|
/* Actually free the MSI */ |
|
spin_lock(&odmis_bm_lock); |
|
__clear_bit(d->hwirq, odmis_bm); |
|
spin_unlock(&odmis_bm_lock); |
|
} |
|
|
|
static const struct irq_domain_ops odmi_domain_ops = { |
|
.alloc = odmi_irq_domain_alloc, |
|
.free = odmi_irq_domain_free, |
|
}; |
|
|
|
static struct irq_chip odmi_msi_irq_chip = { |
|
.name = "ODMI", |
|
}; |
|
|
|
static struct msi_domain_ops odmi_msi_ops = { |
|
}; |
|
|
|
static struct msi_domain_info odmi_msi_domain_info = { |
|
.flags = (MSI_FLAG_USE_DEF_DOM_OPS | MSI_FLAG_USE_DEF_CHIP_OPS), |
|
.ops = &odmi_msi_ops, |
|
.chip = &odmi_msi_irq_chip, |
|
}; |
|
|
|
static int __init mvebu_odmi_init(struct device_node *node, |
|
struct device_node *parent) |
|
{ |
|
struct irq_domain *inner_domain, *plat_domain; |
|
int ret, i; |
|
|
|
if (of_property_read_u32(node, "marvell,odmi-frames", &odmis_count)) |
|
return -EINVAL; |
|
|
|
odmis = kcalloc(odmis_count, sizeof(struct odmi_data), GFP_KERNEL); |
|
if (!odmis) |
|
return -ENOMEM; |
|
|
|
odmis_bm = kcalloc(BITS_TO_LONGS(odmis_count * NODMIS_PER_FRAME), |
|
sizeof(long), GFP_KERNEL); |
|
if (!odmis_bm) { |
|
ret = -ENOMEM; |
|
goto err_alloc; |
|
} |
|
|
|
for (i = 0; i < odmis_count; i++) { |
|
struct odmi_data *odmi = &odmis[i]; |
|
|
|
ret = of_address_to_resource(node, i, &odmi->res); |
|
if (ret) |
|
goto err_unmap; |
|
|
|
odmi->base = of_io_request_and_map(node, i, "odmi"); |
|
if (IS_ERR(odmi->base)) { |
|
ret = PTR_ERR(odmi->base); |
|
goto err_unmap; |
|
} |
|
|
|
if (of_property_read_u32_index(node, "marvell,spi-base", |
|
i, &odmi->spi_base)) { |
|
ret = -EINVAL; |
|
goto err_unmap; |
|
} |
|
} |
|
|
|
inner_domain = irq_domain_create_linear(of_node_to_fwnode(node), |
|
odmis_count * NODMIS_PER_FRAME, |
|
&odmi_domain_ops, NULL); |
|
if (!inner_domain) { |
|
ret = -ENOMEM; |
|
goto err_unmap; |
|
} |
|
|
|
inner_domain->parent = irq_find_host(parent); |
|
|
|
plat_domain = platform_msi_create_irq_domain(of_node_to_fwnode(node), |
|
&odmi_msi_domain_info, |
|
inner_domain); |
|
if (!plat_domain) { |
|
ret = -ENOMEM; |
|
goto err_remove_inner; |
|
} |
|
|
|
return 0; |
|
|
|
err_remove_inner: |
|
irq_domain_remove(inner_domain); |
|
err_unmap: |
|
for (i = 0; i < odmis_count; i++) { |
|
struct odmi_data *odmi = &odmis[i]; |
|
|
|
if (odmi->base && !IS_ERR(odmi->base)) |
|
iounmap(odmis[i].base); |
|
} |
|
kfree(odmis_bm); |
|
err_alloc: |
|
kfree(odmis); |
|
return ret; |
|
} |
|
|
|
IRQCHIP_DECLARE(mvebu_odmi, "marvell,odmi-controller", mvebu_odmi_init);
|
|
|