|  | /* | 
|  | *  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; | 
|  | } |