diff options
Diffstat (limited to 'drivers/irqchip/irq-mtk-sysirq.c')
-rw-r--r-- | drivers/irqchip/irq-mtk-sysirq.c | 235 |
1 files changed, 235 insertions, 0 deletions
diff --git a/drivers/irqchip/irq-mtk-sysirq.c b/drivers/irqchip/irq-mtk-sysirq.c new file mode 100644 index 000000000..586e52d54 --- /dev/null +++ b/drivers/irqchip/irq-mtk-sysirq.c @@ -0,0 +1,235 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2014 MediaTek Inc. + * Author: Joe.C <yingjoe.chen@mediatek.com> + */ + +#include <linux/irq.h> +#include <linux/irqchip.h> +#include <linux/irqdomain.h> +#include <linux/of.h> +#include <linux/of_irq.h> +#include <linux/of_address.h> +#include <linux/io.h> +#include <linux/slab.h> +#include <linux/spinlock.h> + +struct mtk_sysirq_chip_data { + raw_spinlock_t lock; + u32 nr_intpol_bases; + void __iomem **intpol_bases; + u32 *intpol_words; + u8 *intpol_idx; + u16 *which_word; +}; + +static int mtk_sysirq_set_type(struct irq_data *data, unsigned int type) +{ + irq_hw_number_t hwirq = data->hwirq; + struct mtk_sysirq_chip_data *chip_data = data->chip_data; + u8 intpol_idx = chip_data->intpol_idx[hwirq]; + void __iomem *base; + u32 offset, reg_index, value; + unsigned long flags; + int ret; + + base = chip_data->intpol_bases[intpol_idx]; + reg_index = chip_data->which_word[hwirq]; + offset = hwirq & 0x1f; + + raw_spin_lock_irqsave(&chip_data->lock, flags); + value = readl_relaxed(base + reg_index * 4); + if (type == IRQ_TYPE_LEVEL_LOW || type == IRQ_TYPE_EDGE_FALLING) { + if (type == IRQ_TYPE_LEVEL_LOW) + type = IRQ_TYPE_LEVEL_HIGH; + else + type = IRQ_TYPE_EDGE_RISING; + value |= (1 << offset); + } else { + value &= ~(1 << offset); + } + + writel_relaxed(value, base + reg_index * 4); + + data = data->parent_data; + ret = data->chip->irq_set_type(data, type); + raw_spin_unlock_irqrestore(&chip_data->lock, flags); + return ret; +} + +static struct irq_chip mtk_sysirq_chip = { + .name = "MT_SYSIRQ", + .irq_mask = irq_chip_mask_parent, + .irq_unmask = irq_chip_unmask_parent, + .irq_eoi = irq_chip_eoi_parent, + .irq_set_type = mtk_sysirq_set_type, + .irq_retrigger = irq_chip_retrigger_hierarchy, + .irq_set_affinity = irq_chip_set_affinity_parent, + .flags = IRQCHIP_SKIP_SET_WAKE, +}; + +static int mtk_sysirq_domain_translate(struct irq_domain *d, + struct irq_fwspec *fwspec, + unsigned long *hwirq, + unsigned int *type) +{ + if (is_of_node(fwspec->fwnode)) { + if (fwspec->param_count != 3) + return -EINVAL; + + /* No PPI should point to this domain */ + if (fwspec->param[0] != 0) + return -EINVAL; + + *hwirq = fwspec->param[1]; + *type = fwspec->param[2] & IRQ_TYPE_SENSE_MASK; + return 0; + } + + return -EINVAL; +} + +static int mtk_sysirq_domain_alloc(struct irq_domain *domain, unsigned int virq, + unsigned int nr_irqs, void *arg) +{ + int i; + irq_hw_number_t hwirq; + struct irq_fwspec *fwspec = arg; + struct irq_fwspec gic_fwspec = *fwspec; + + if (fwspec->param_count != 3) + return -EINVAL; + + /* sysirq doesn't support PPI */ + if (fwspec->param[0]) + return -EINVAL; + + hwirq = fwspec->param[1]; + for (i = 0; i < nr_irqs; i++) + irq_domain_set_hwirq_and_chip(domain, virq + i, hwirq + i, + &mtk_sysirq_chip, + domain->host_data); + + gic_fwspec.fwnode = domain->parent->fwnode; + return irq_domain_alloc_irqs_parent(domain, virq, nr_irqs, &gic_fwspec); +} + +static const struct irq_domain_ops sysirq_domain_ops = { + .translate = mtk_sysirq_domain_translate, + .alloc = mtk_sysirq_domain_alloc, + .free = irq_domain_free_irqs_common, +}; + +static int __init mtk_sysirq_of_init(struct device_node *node, + struct device_node *parent) +{ + struct irq_domain *domain, *domain_parent; + struct mtk_sysirq_chip_data *chip_data; + int ret, size, intpol_num = 0, nr_intpol_bases = 0, i = 0; + + domain_parent = irq_find_host(parent); + if (!domain_parent) { + pr_err("mtk_sysirq: interrupt-parent not found\n"); + return -EINVAL; + } + + chip_data = kzalloc(sizeof(*chip_data), GFP_KERNEL); + if (!chip_data) + return -ENOMEM; + + while (of_get_address(node, i++, NULL, NULL)) + nr_intpol_bases++; + + if (nr_intpol_bases == 0) { + pr_err("mtk_sysirq: base address not specified\n"); + ret = -EINVAL; + goto out_free_chip; + } + + chip_data->intpol_words = kcalloc(nr_intpol_bases, + sizeof(*chip_data->intpol_words), + GFP_KERNEL); + if (!chip_data->intpol_words) { + ret = -ENOMEM; + goto out_free_chip; + } + + chip_data->intpol_bases = kcalloc(nr_intpol_bases, + sizeof(*chip_data->intpol_bases), + GFP_KERNEL); + if (!chip_data->intpol_bases) { + ret = -ENOMEM; + goto out_free_intpol_words; + } + + for (i = 0; i < nr_intpol_bases; i++) { + struct resource res; + + ret = of_address_to_resource(node, i, &res); + size = resource_size(&res); + intpol_num += size * 8; + chip_data->intpol_words[i] = size / 4; + chip_data->intpol_bases[i] = of_iomap(node, i); + if (ret || !chip_data->intpol_bases[i]) { + pr_err("%pOF: couldn't map region %d\n", node, i); + ret = -ENODEV; + goto out_free_intpol; + } + } + + chip_data->intpol_idx = kcalloc(intpol_num, + sizeof(*chip_data->intpol_idx), + GFP_KERNEL); + if (!chip_data->intpol_idx) { + ret = -ENOMEM; + goto out_free_intpol; + } + + chip_data->which_word = kcalloc(intpol_num, + sizeof(*chip_data->which_word), + GFP_KERNEL); + if (!chip_data->which_word) { + ret = -ENOMEM; + goto out_free_intpol_idx; + } + + /* + * assign an index of the intpol_bases for each irq + * to set it fast later + */ + for (i = 0; i < intpol_num ; i++) { + u32 word = i / 32, j; + + for (j = 0; word >= chip_data->intpol_words[j] ; j++) + word -= chip_data->intpol_words[j]; + + chip_data->intpol_idx[i] = j; + chip_data->which_word[i] = word; + } + + domain = irq_domain_add_hierarchy(domain_parent, 0, intpol_num, node, + &sysirq_domain_ops, chip_data); + if (!domain) { + ret = -ENOMEM; + goto out_free_which_word; + } + raw_spin_lock_init(&chip_data->lock); + + return 0; + +out_free_which_word: + kfree(chip_data->which_word); +out_free_intpol_idx: + kfree(chip_data->intpol_idx); +out_free_intpol: + for (i = 0; i < nr_intpol_bases; i++) + if (chip_data->intpol_bases[i]) + iounmap(chip_data->intpol_bases[i]); + kfree(chip_data->intpol_bases); +out_free_intpol_words: + kfree(chip_data->intpol_words); +out_free_chip: + kfree(chip_data); + return ret; +} +IRQCHIP_DECLARE(mtk_sysirq, "mediatek,mt6577-sysirq", mtk_sysirq_of_init); |