Commit 885d078b authored by Jason Cooper's avatar Jason Cooper

Merge branch 'irqchip/crossbar' into irqchip/core

parents 4db8e6d2 d360892d
...@@ -10,6 +10,7 @@ Required properties: ...@@ -10,6 +10,7 @@ Required properties:
- compatible : Should be "ti,irq-crossbar" - compatible : Should be "ti,irq-crossbar"
- reg: Base address and the size of the crossbar registers. - reg: Base address and the size of the crossbar registers.
- ti,max-irqs: Total number of irqs available at the interrupt controller. - ti,max-irqs: Total number of irqs available at the interrupt controller.
- ti,max-crossbar-sources: Maximum number of crossbar sources that can be routed.
- ti,reg-size: Size of a individual register in bytes. Every individual - ti,reg-size: Size of a individual register in bytes. Every individual
register is assumed to be of same size. Valid sizes are 1, 2, 4. register is assumed to be of same size. Valid sizes are 1, 2, 4.
- ti,irqs-reserved: List of the reserved irq lines that are not muxed using - ti,irqs-reserved: List of the reserved irq lines that are not muxed using
...@@ -17,11 +18,46 @@ Required properties: ...@@ -17,11 +18,46 @@ Required properties:
so crossbar bar driver should not consider them as free so crossbar bar driver should not consider them as free
lines. lines.
Optional properties:
- ti,irqs-skip: This is similar to "ti,irqs-reserved", but these are for
SOC-specific hard-wiring of those irqs which unexpectedly bypasses the
crossbar. These irqs have a crossbar register, but still cannot be used.
- ti,irqs-safe-map: integer which maps to a safe configuration to use
when the interrupt controller irq is unused (when not provided, default is 0)
Examples: Examples:
crossbar_mpu: @4a020000 { crossbar_mpu: @4a020000 {
compatible = "ti,irq-crossbar"; compatible = "ti,irq-crossbar";
reg = <0x4a002a48 0x130>; reg = <0x4a002a48 0x130>;
ti,max-irqs = <160>; ti,max-irqs = <160>;
ti,max-crossbar-sources = <400>;
ti,reg-size = <2>; ti,reg-size = <2>;
ti,irqs-reserved = <0 1 2 3 5 6 131 132 139 140>; ti,irqs-reserved = <0 1 2 3 5 6 131 132 139 140>;
ti,irqs-skip = <10 133 139 140>;
}; };
Consumer:
========
See Documentation/devicetree/bindings/interrupt-controller/interrupts.txt and
Documentation/devicetree/bindings/arm/gic.txt for further details.
An interrupt consumer on an SoC using crossbar will use:
interrupts = <GIC_SPI request_number interrupt_level>
When the request number is between 0 to that described by
"ti,max-crossbar-sources", it is assumed to be a crossbar mapping. If the
request_number is greater than "ti,max-crossbar-sources", then it is mapped as a
quirky hardware mapping direct to GIC.
Example:
device_x@0x4a023000 {
/* Crossbar 8 used */
interrupts = <GIC_SPI 8 IRQ_TYPE_LEVEL_HIGH>;
...
};
device_y@0x4a033000 {
/* Direct mapped GIC SPI 1 used */
interrupts = <GIC_SPI DIRECT_IRQ(1) IRQ_TYPE_LEVEL_HIGH>;
...
};
...@@ -15,22 +15,31 @@ ...@@ -15,22 +15,31 @@
#include <linux/of_irq.h> #include <linux/of_irq.h>
#include <linux/slab.h> #include <linux/slab.h>
#include <linux/irqchip/arm-gic.h> #include <linux/irqchip/arm-gic.h>
#include <linux/irqchip/irq-crossbar.h>
#define IRQ_FREE -1 #define IRQ_FREE -1
#define IRQ_RESERVED -2
#define IRQ_SKIP -3
#define GIC_IRQ_START 32 #define GIC_IRQ_START 32
/* /**
* struct crossbar_device - crossbar device description
* @int_max: maximum number of supported interrupts * @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 * @irq_map: array of interrupts to crossbar number mapping
* @crossbar_base: crossbar base address * @crossbar_base: crossbar base address
* @register_offsets: offsets for each irq number * @register_offsets: offsets for each irq number
* @write: register write function pointer
*/ */
struct crossbar_device { struct crossbar_device {
uint int_max; uint int_max;
uint safe_map;
uint max_crossbar_sources;
uint *irq_map; uint *irq_map;
void __iomem *crossbar_base; void __iomem *crossbar_base;
int *register_offsets; int *register_offsets;
void (*write) (int, int); void (*write)(int, int);
}; };
static struct crossbar_device *cb; static struct crossbar_device *cb;
...@@ -50,11 +59,22 @@ static inline void crossbar_writeb(int irq_no, int cb_no) ...@@ -50,11 +59,22 @@ static inline void crossbar_writeb(int irq_no, int cb_no)
writeb(cb_no, cb->crossbar_base + cb->register_offsets[irq_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) static inline int allocate_free_irq(int cb_no)
{ {
int i; int i;
for (i = 0; i < cb->int_max; i++) { for (i = cb->int_max - 1; i >= 0; i--) {
if (cb->irq_map[i] == IRQ_FREE) { if (cb->irq_map[i] == IRQ_FREE) {
cb->irq_map[i] = cb_no; cb->irq_map[i] = cb_no;
return i; return i;
...@@ -64,19 +84,47 @@ static inline int allocate_free_irq(int cb_no) ...@@ -64,19 +84,47 @@ static inline int allocate_free_irq(int cb_no)
return -ENODEV; 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, static int crossbar_domain_map(struct irq_domain *d, unsigned int irq,
irq_hw_number_t hw) irq_hw_number_t hw)
{ {
cb->write(hw - GIC_IRQ_START, cb->irq_map[hw - GIC_IRQ_START]); if (needs_crossbar_write(hw))
cb->write(hw - GIC_IRQ_START, cb->irq_map[hw - GIC_IRQ_START]);
return 0; 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) static void crossbar_domain_unmap(struct irq_domain *d, unsigned int irq)
{ {
irq_hw_number_t hw = irq_get_irq_data(irq)->hwirq; irq_hw_number_t hw = irq_get_irq_data(irq)->hwirq;
if (hw > GIC_IRQ_START) if (needs_crossbar_write(hw)) {
cb->irq_map[hw - GIC_IRQ_START] = IRQ_FREE; 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, static int crossbar_domain_xlate(struct irq_domain *d,
...@@ -85,18 +133,41 @@ static int crossbar_domain_xlate(struct irq_domain *d, ...@@ -85,18 +133,41 @@ static int crossbar_domain_xlate(struct irq_domain *d,
unsigned long *out_hwirq, unsigned long *out_hwirq,
unsigned int *out_type) unsigned int *out_type)
{ {
unsigned long ret; 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 = allocate_free_irq(intspec[1]); ret = get_prev_map_irq(req_num);
if (ret >= 0)
goto found;
if (IS_ERR_VALUE(ret)) ret = allocate_free_irq(req_num);
if (ret < 0)
return ret; return ret;
found:
*out_hwirq = ret + GIC_IRQ_START; *out_hwirq = ret + GIC_IRQ_START;
return 0; return 0;
} }
const struct irq_domain_ops routable_irq_domain_ops = { static const struct irq_domain_ops routable_irq_domain_ops = {
.map = crossbar_domain_map, .map = crossbar_domain_map,
.unmap = crossbar_domain_unmap, .unmap = crossbar_domain_unmap,
.xlate = crossbar_domain_xlate .xlate = crossbar_domain_xlate
...@@ -104,22 +175,36 @@ const struct irq_domain_ops routable_irq_domain_ops = { ...@@ -104,22 +175,36 @@ const struct irq_domain_ops routable_irq_domain_ops = {
static int __init crossbar_of_init(struct device_node *node) static int __init crossbar_of_init(struct device_node *node)
{ {
int i, size, max, reserved = 0, entry; int i, size, max = 0, reserved = 0, entry;
const __be32 *irqsr; const __be32 *irqsr;
int ret = -ENOMEM;
cb = kzalloc(sizeof(*cb), GFP_KERNEL); cb = kzalloc(sizeof(*cb), GFP_KERNEL);
if (!cb) if (!cb)
return -ENOMEM; return ret;
cb->crossbar_base = of_iomap(node, 0); cb->crossbar_base = of_iomap(node, 0);
if (!cb->crossbar_base) if (!cb->crossbar_base)
goto err1; 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); of_property_read_u32(node, "ti,max-irqs", &max);
cb->irq_map = kzalloc(max * sizeof(int), GFP_KERNEL); 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) if (!cb->irq_map)
goto err2; goto err_base;
cb->int_max = max; cb->int_max = max;
...@@ -137,15 +222,35 @@ static int __init crossbar_of_init(struct device_node *node) ...@@ -137,15 +222,35 @@ static int __init crossbar_of_init(struct device_node *node)
i, &entry); i, &entry);
if (entry > max) { if (entry > max) {
pr_err("Invalid reserved entry\n"); pr_err("Invalid reserved entry\n");
goto err3; 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] = 0; cb->irq_map[entry] = IRQ_SKIP;
} }
} }
cb->register_offsets = kzalloc(max * sizeof(int), GFP_KERNEL);
cb->register_offsets = kcalloc(max, sizeof(int), GFP_KERNEL);
if (!cb->register_offsets) if (!cb->register_offsets)
goto err3; goto err_irq_map;
of_property_read_u32(node, "ti,reg-size", &size); of_property_read_u32(node, "ti,reg-size", &size);
...@@ -161,7 +266,8 @@ static int __init crossbar_of_init(struct device_node *node) ...@@ -161,7 +266,8 @@ static int __init crossbar_of_init(struct device_node *node)
break; break;
default: default:
pr_err("Invalid reg-size property\n"); pr_err("Invalid reg-size property\n");
goto err4; ret = -EINVAL;
goto err_reg_offset;
break; break;
} }
...@@ -170,25 +276,37 @@ static int __init crossbar_of_init(struct device_node *node) ...@@ -170,25 +276,37 @@ static int __init crossbar_of_init(struct device_node *node)
* reserved irqs. so find and store the offsets once. * reserved irqs. so find and store the offsets once.
*/ */
for (i = 0; i < max; i++) { for (i = 0; i < max; i++) {
if (!cb->irq_map[i]) if (cb->irq_map[i] == IRQ_RESERVED)
continue; continue;
cb->register_offsets[i] = reserved; cb->register_offsets[i] = reserved;
reserved += size; 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); register_routable_domain_ops(&routable_irq_domain_ops);
return 0; return 0;
err4: err_reg_offset:
kfree(cb->register_offsets); kfree(cb->register_offsets);
err3: err_irq_map:
kfree(cb->irq_map); kfree(cb->irq_map);
err2: err_base:
iounmap(cb->crossbar_base); iounmap(cb->crossbar_base);
err1: err_cb:
kfree(cb); kfree(cb);
return -ENOMEM;
cb = NULL;
return ret;
} }
static const struct of_device_id crossbar_match[] __initconst = { static const struct of_device_id crossbar_match[] __initconst = {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment