d360892d37
On certain platforms such as DRA7, SPIs 0, 1, 2, 3, 5, 6, 10, 131, 132, 133 are direct wired to hardware blocks bypassing crossbar. This quirky implementation is *NOT* supposed to be the expectation of crossbar hardware usage. However, these are already marked in our description of the hardware with SKIP and RESERVED where appropriate. Unfortunately, we need to be able to refer to these hardwired IRQs. So, to request these, crossbar driver can use the existing information from it's table that these SKIP/RESERVED maps are direct wired sources and generic allocation/programming of crossbar should be avoided. Signed-off-by: Nishanth Menon <nm@ti.com> Signed-off-by: Sricharan R <r.sricharan@ti.com> Acked-by: Santosh Shilimkar <santosh.shilimkar@ti.com> Link: https://lkml.kernel.org/r/1403766634-18543-17-git-send-email-r.sricharan@ti.com Signed-off-by: Jason Cooper <jason@lakedaemon.net>
327 lines
7.2 KiB
C
327 lines
7.2 KiB
C
/*
|
|
* drivers/irqchip/irq-crossbar.c
|
|
*
|
|
* Copyright (C) 2013 Texas Instruments Incorporated - http://www.ti.com
|
|
* Author: Sricharan R <r.sricharan@ti.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.
|
|
*
|
|
*/
|
|
#include <linux/err.h>
|
|
#include <linux/io.h>
|
|
#include <linux/of_address.h>
|
|
#include <linux/of_irq.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/irqchip/arm-gic.h>
|
|
#include <linux/irqchip/irq-crossbar.h>
|
|
|
|
#define IRQ_FREE -1
|
|
#define IRQ_RESERVED -2
|
|
#define IRQ_SKIP -3
|
|
#define GIC_IRQ_START 32
|
|
|
|
/**
|
|
* struct crossbar_device - crossbar device description
|
|
* @int_max: maximum number of supported interrupts
|
|
* @safe_map: safe default value to initialize the crossbar
|
|
* @max_crossbar_sources: Maximum number of crossbar sources
|
|
* @irq_map: array of interrupts to crossbar number mapping
|
|
* @crossbar_base: crossbar base address
|
|
* @register_offsets: offsets for each irq number
|
|
* @write: register write function pointer
|
|
*/
|
|
struct crossbar_device {
|
|
uint int_max;
|
|
uint safe_map;
|
|
uint max_crossbar_sources;
|
|
uint *irq_map;
|
|
void __iomem *crossbar_base;
|
|
int *register_offsets;
|
|
void (*write)(int, int);
|
|
};
|
|
|
|
static struct crossbar_device *cb;
|
|
|
|
static inline void crossbar_writel(int irq_no, int cb_no)
|
|
{
|
|
writel(cb_no, cb->crossbar_base + cb->register_offsets[irq_no]);
|
|
}
|
|
|
|
static inline void crossbar_writew(int irq_no, int cb_no)
|
|
{
|
|
writew(cb_no, cb->crossbar_base + cb->register_offsets[irq_no]);
|
|
}
|
|
|
|
static inline void crossbar_writeb(int irq_no, int cb_no)
|
|
{
|
|
writeb(cb_no, cb->crossbar_base + cb->register_offsets[irq_no]);
|
|
}
|
|
|
|
static inline int get_prev_map_irq(int cb_no)
|
|
{
|
|
int i;
|
|
|
|
for (i = cb->int_max - 1; i >= 0; i--)
|
|
if (cb->irq_map[i] == cb_no)
|
|
return i;
|
|
|
|
return -ENODEV;
|
|
}
|
|
|
|
static inline int allocate_free_irq(int cb_no)
|
|
{
|
|
int i;
|
|
|
|
for (i = cb->int_max - 1; i >= 0; i--) {
|
|
if (cb->irq_map[i] == IRQ_FREE) {
|
|
cb->irq_map[i] = cb_no;
|
|
return i;
|
|
}
|
|
}
|
|
|
|
return -ENODEV;
|
|
}
|
|
|
|
static inline bool needs_crossbar_write(irq_hw_number_t hw)
|
|
{
|
|
int cb_no;
|
|
|
|
if (hw > GIC_IRQ_START) {
|
|
cb_no = cb->irq_map[hw - GIC_IRQ_START];
|
|
if (cb_no != IRQ_RESERVED && cb_no != IRQ_SKIP)
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
static int crossbar_domain_map(struct irq_domain *d, unsigned int irq,
|
|
irq_hw_number_t hw)
|
|
{
|
|
if (needs_crossbar_write(hw))
|
|
cb->write(hw - GIC_IRQ_START, cb->irq_map[hw - GIC_IRQ_START]);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* crossbar_domain_unmap - unmap a crossbar<->irq connection
|
|
* @d: domain of irq to unmap
|
|
* @irq: virq number
|
|
*
|
|
* We do not maintain a use count of total number of map/unmap
|
|
* calls for a particular irq to find out if a irq can be really
|
|
* unmapped. This is because unmap is called during irq_dispose_mapping(irq),
|
|
* after which irq is anyways unusable. So an explicit map has to be called
|
|
* after that.
|
|
*/
|
|
static void crossbar_domain_unmap(struct irq_domain *d, unsigned int irq)
|
|
{
|
|
irq_hw_number_t hw = irq_get_irq_data(irq)->hwirq;
|
|
|
|
if (needs_crossbar_write(hw)) {
|
|
cb->irq_map[hw - GIC_IRQ_START] = IRQ_FREE;
|
|
cb->write(hw - GIC_IRQ_START, cb->safe_map);
|
|
}
|
|
}
|
|
|
|
static int crossbar_domain_xlate(struct irq_domain *d,
|
|
struct device_node *controller,
|
|
const u32 *intspec, unsigned int intsize,
|
|
unsigned long *out_hwirq,
|
|
unsigned int *out_type)
|
|
{
|
|
int ret;
|
|
int req_num = intspec[1];
|
|
int direct_map_num;
|
|
|
|
if (req_num >= cb->max_crossbar_sources) {
|
|
direct_map_num = req_num - cb->max_crossbar_sources;
|
|
if (direct_map_num < cb->int_max) {
|
|
ret = cb->irq_map[direct_map_num];
|
|
if (ret == IRQ_RESERVED || ret == IRQ_SKIP) {
|
|
/* We use the interrupt num as h/w irq num */
|
|
ret = direct_map_num;
|
|
goto found;
|
|
}
|
|
}
|
|
|
|
pr_err("%s: requested crossbar number %d > max %d\n",
|
|
__func__, req_num, cb->max_crossbar_sources);
|
|
return -EINVAL;
|
|
}
|
|
|
|
ret = get_prev_map_irq(req_num);
|
|
if (ret >= 0)
|
|
goto found;
|
|
|
|
ret = allocate_free_irq(req_num);
|
|
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
found:
|
|
*out_hwirq = ret + GIC_IRQ_START;
|
|
return 0;
|
|
}
|
|
|
|
static const struct irq_domain_ops routable_irq_domain_ops = {
|
|
.map = crossbar_domain_map,
|
|
.unmap = crossbar_domain_unmap,
|
|
.xlate = crossbar_domain_xlate
|
|
};
|
|
|
|
static int __init crossbar_of_init(struct device_node *node)
|
|
{
|
|
int i, size, max = 0, reserved = 0, entry;
|
|
const __be32 *irqsr;
|
|
int ret = -ENOMEM;
|
|
|
|
cb = kzalloc(sizeof(*cb), GFP_KERNEL);
|
|
|
|
if (!cb)
|
|
return ret;
|
|
|
|
cb->crossbar_base = of_iomap(node, 0);
|
|
if (!cb->crossbar_base)
|
|
goto err_cb;
|
|
|
|
of_property_read_u32(node, "ti,max-crossbar-sources",
|
|
&cb->max_crossbar_sources);
|
|
if (!cb->max_crossbar_sources) {
|
|
pr_err("missing 'ti,max-crossbar-sources' property\n");
|
|
ret = -EINVAL;
|
|
goto err_base;
|
|
}
|
|
|
|
of_property_read_u32(node, "ti,max-irqs", &max);
|
|
if (!max) {
|
|
pr_err("missing 'ti,max-irqs' property\n");
|
|
ret = -EINVAL;
|
|
goto err_base;
|
|
}
|
|
cb->irq_map = kcalloc(max, sizeof(int), GFP_KERNEL);
|
|
if (!cb->irq_map)
|
|
goto err_base;
|
|
|
|
cb->int_max = max;
|
|
|
|
for (i = 0; i < max; i++)
|
|
cb->irq_map[i] = IRQ_FREE;
|
|
|
|
/* Get and mark reserved irqs */
|
|
irqsr = of_get_property(node, "ti,irqs-reserved", &size);
|
|
if (irqsr) {
|
|
size /= sizeof(__be32);
|
|
|
|
for (i = 0; i < size; i++) {
|
|
of_property_read_u32_index(node,
|
|
"ti,irqs-reserved",
|
|
i, &entry);
|
|
if (entry > max) {
|
|
pr_err("Invalid reserved entry\n");
|
|
ret = -EINVAL;
|
|
goto err_irq_map;
|
|
}
|
|
cb->irq_map[entry] = IRQ_RESERVED;
|
|
}
|
|
}
|
|
|
|
/* Skip irqs hardwired to bypass the crossbar */
|
|
irqsr = of_get_property(node, "ti,irqs-skip", &size);
|
|
if (irqsr) {
|
|
size /= sizeof(__be32);
|
|
|
|
for (i = 0; i < size; i++) {
|
|
of_property_read_u32_index(node,
|
|
"ti,irqs-skip",
|
|
i, &entry);
|
|
if (entry > max) {
|
|
pr_err("Invalid skip entry\n");
|
|
ret = -EINVAL;
|
|
goto err_irq_map;
|
|
}
|
|
cb->irq_map[entry] = IRQ_SKIP;
|
|
}
|
|
}
|
|
|
|
|
|
cb->register_offsets = kcalloc(max, sizeof(int), GFP_KERNEL);
|
|
if (!cb->register_offsets)
|
|
goto err_irq_map;
|
|
|
|
of_property_read_u32(node, "ti,reg-size", &size);
|
|
|
|
switch (size) {
|
|
case 1:
|
|
cb->write = crossbar_writeb;
|
|
break;
|
|
case 2:
|
|
cb->write = crossbar_writew;
|
|
break;
|
|
case 4:
|
|
cb->write = crossbar_writel;
|
|
break;
|
|
default:
|
|
pr_err("Invalid reg-size property\n");
|
|
ret = -EINVAL;
|
|
goto err_reg_offset;
|
|
break;
|
|
}
|
|
|
|
/*
|
|
* Register offsets are not linear because of the
|
|
* reserved irqs. so find and store the offsets once.
|
|
*/
|
|
for (i = 0; i < max; i++) {
|
|
if (cb->irq_map[i] == IRQ_RESERVED)
|
|
continue;
|
|
|
|
cb->register_offsets[i] = reserved;
|
|
reserved += size;
|
|
}
|
|
|
|
of_property_read_u32(node, "ti,irqs-safe-map", &cb->safe_map);
|
|
/* Initialize the crossbar with safe map to start with */
|
|
for (i = 0; i < max; i++) {
|
|
if (cb->irq_map[i] == IRQ_RESERVED ||
|
|
cb->irq_map[i] == IRQ_SKIP)
|
|
continue;
|
|
|
|
cb->write(i, cb->safe_map);
|
|
}
|
|
|
|
register_routable_domain_ops(&routable_irq_domain_ops);
|
|
return 0;
|
|
|
|
err_reg_offset:
|
|
kfree(cb->register_offsets);
|
|
err_irq_map:
|
|
kfree(cb->irq_map);
|
|
err_base:
|
|
iounmap(cb->crossbar_base);
|
|
err_cb:
|
|
kfree(cb);
|
|
|
|
cb = NULL;
|
|
return ret;
|
|
}
|
|
|
|
static const struct of_device_id crossbar_match[] __initconst = {
|
|
{ .compatible = "ti,irq-crossbar" },
|
|
{}
|
|
};
|
|
|
|
int __init irqcrossbar_init(void)
|
|
{
|
|
struct device_node *np;
|
|
np = of_find_matching_node(NULL, crossbar_match);
|
|
if (!np)
|
|
return -ENODEV;
|
|
|
|
crossbar_of_init(np);
|
|
return 0;
|
|
}
|