| /* |
| * Generic IRQ handling, APB Peripheral IRQ demultiplexing, etc. |
| * |
| * Author: Jisheng Zhang <jszhang@marvell.com> |
| * Copyright: Marvell International Ltd. |
| * |
| * 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/init.h> |
| #include <linux/export.h> |
| #include <linux/irq.h> |
| #include <linux/irqdomain.h> |
| #include <linux/of_address.h> |
| #include <linux/of_irq.h> |
| #include <linux/io.h> |
| #include <linux/cpu_pm.h> |
| |
| #include <asm/hardware/gic.h> |
| #include <asm/mach/irq.h> |
| #include <asm/hardware/cache-l2x0.h> |
| |
| #define MAX_ICTL_NR 2 |
| |
| #define APB_ICTL_INTEN 0x00 |
| #define APB_ICTL_INTMASK 0x08 |
| #define APB_ICTL_FINALSTATUS 0x30 |
| |
| struct apb_ictl_data { |
| int nr_irqs; |
| unsigned int virq_base; |
| unsigned int cascade_irq; |
| void __iomem *ictl_base; |
| struct irq_domain *domain; |
| #ifdef CONFIG_CPU_PM |
| u32 ictl_intmask; |
| u32 ictl_inten; |
| #endif |
| }; |
| |
| static struct apb_ictl_data apb_data[MAX_ICTL_NR]; |
| static int max_ictl_nr; |
| |
| static inline u32 rdl(struct apb_ictl_data *ictl, int offset) |
| { |
| return readl_relaxed(ictl->ictl_base + offset); |
| } |
| |
| static inline void wrl(struct apb_ictl_data *ictl, int offset, u32 value) |
| { |
| writel_relaxed(value, ictl->ictl_base + offset); |
| } |
| |
| static void apb_enable_irq(struct irq_data *d) |
| { |
| u32 u; |
| struct irq_domain *domain = d->domain; |
| struct apb_ictl_data *data = (struct apb_ictl_data *)domain->host_data; |
| int pin = d->irq - data->virq_base; |
| |
| u = rdl(data, APB_ICTL_INTMASK); |
| u &= ~(1 << (pin & 31)); |
| wrl(data, APB_ICTL_INTMASK, u); |
| |
| u = rdl(data, APB_ICTL_INTEN); |
| u |= (1 << (pin & 31)); |
| wrl(data, APB_ICTL_INTEN, u); |
| } |
| |
| static void apb_disable_irq(struct irq_data *d) |
| { |
| u32 u; |
| struct irq_domain *domain = d->domain; |
| struct apb_ictl_data *data = (struct apb_ictl_data *)domain->host_data; |
| int pin = d->irq - data->virq_base; |
| |
| u = rdl(data, APB_ICTL_INTMASK); |
| u |= (1 << (pin & 31)); |
| wrl(data, APB_ICTL_INTMASK, u); |
| |
| u = rdl(data, APB_ICTL_INTEN); |
| u &= ~(1 << (pin & 31)); |
| wrl(data, APB_ICTL_INTEN, u); |
| } |
| |
| static void apb_mask_irq(struct irq_data *d) |
| { |
| u32 u; |
| struct irq_domain *domain = d->domain; |
| struct apb_ictl_data *data = (struct apb_ictl_data *)domain->host_data; |
| int pin = d->irq - data->virq_base; |
| |
| u = rdl(data, APB_ICTL_INTMASK); |
| u |= (1 << (pin & 31)); |
| wrl(data, APB_ICTL_INTMASK, u); |
| } |
| |
| static void apb_unmask_irq(struct irq_data *d) |
| { |
| u32 u; |
| struct irq_domain *domain = d->domain; |
| struct apb_ictl_data *data = (struct apb_ictl_data *)domain->host_data; |
| int pin = d->irq - data->virq_base; |
| |
| u = rdl(data, APB_ICTL_INTMASK); |
| u &= ~(1 << (pin & 31)); |
| wrl(data, APB_ICTL_INTMASK, u); |
| } |
| |
| static struct irq_chip apb_irq_chip = { |
| .name = "apb_ictl", |
| .irq_enable = apb_enable_irq, |
| .irq_disable = apb_disable_irq, |
| .irq_mask = apb_mask_irq, |
| .irq_unmask = apb_unmask_irq, |
| }; |
| |
| static void apb_irq_demux(unsigned int irq, struct irq_desc *desc) |
| { |
| int i; |
| struct irq_domain *domain; |
| struct apb_ictl_data *data; |
| unsigned long mask, status, n; |
| struct irq_chip *chip = irq_desc_get_chip(desc); |
| |
| chained_irq_enter(chip, desc); |
| for (i = 0; i < max_ictl_nr; i++) { |
| if (irq == apb_data[i].cascade_irq) { |
| domain = apb_data[i].domain; |
| data = (struct apb_ictl_data *)domain->host_data; |
| break; |
| } |
| } |
| if (i >= max_ictl_nr) { |
| chained_irq_exit(chip, desc); |
| pr_err("Spurious irq %d in APB ICTL\n", irq); |
| return; |
| } |
| |
| mask = rdl(data, APB_ICTL_INTMASK); |
| while (1) { |
| status = rdl(data, APB_ICTL_FINALSTATUS); |
| status &= ~mask; |
| if (status == 0) |
| break; |
| for_each_set_bit(n, &status, BITS_PER_LONG) { |
| generic_handle_irq(apb_data[i].virq_base + n); |
| } |
| } |
| chained_irq_exit(chip, desc); |
| } |
| |
| static int apb_irq_domain_map(struct irq_domain *d, unsigned int irq, |
| irq_hw_number_t hw) |
| { |
| irq_set_chip_and_handler(irq, &apb_irq_chip, handle_level_irq); |
| set_irq_flags(irq, IRQF_VALID); |
| return 0; |
| } |
| |
| static int apb_irq_domain_xlate(struct irq_domain *d, struct device_node *node, |
| const u32 *intspec, unsigned int intsize, |
| unsigned long *out_hwirq, |
| unsigned int *out_type) |
| { |
| *out_hwirq = intspec[0]; |
| return 0; |
| } |
| |
| const struct irq_domain_ops apb_irq_domain_ops = { |
| .map = apb_irq_domain_map, |
| .xlate = apb_irq_domain_xlate, |
| }; |
| |
| #ifdef CONFIG_CPU_PM |
| static inline void apb_ictl_save(unsigned int ictl_nr) |
| { |
| struct apb_ictl_data *ictl = &apb_data[ictl_nr]; |
| |
| ictl->ictl_intmask = rdl(ictl, APB_ICTL_INTMASK); |
| ictl->ictl_inten = rdl(ictl, APB_ICTL_INTEN); |
| } |
| |
| static inline void apb_ictl_restore(unsigned int ictl_nr) |
| { |
| struct apb_ictl_data *ictl = &apb_data[ictl_nr]; |
| |
| wrl(ictl, APB_ICTL_INTMASK, ictl->ictl_intmask); |
| wrl(ictl, APB_ICTL_INTEN, ictl->ictl_inten); |
| } |
| |
| static int apb_ictl_notifier(struct notifier_block *self, unsigned long cmd, void *v) |
| { |
| int i; |
| |
| for (i = 0; i < max_ictl_nr; i++) { |
| switch (cmd) { |
| case CPU_CLUSTER_PM_ENTER: |
| apb_ictl_save(i); |
| break; |
| case CPU_CLUSTER_PM_ENTER_FAILED: |
| case CPU_CLUSTER_PM_EXIT: |
| apb_ictl_restore(i); |
| break; |
| } |
| } |
| |
| return NOTIFY_OK; |
| } |
| |
| static struct notifier_block apb_ictl_notifier_block = { |
| .notifier_call = apb_ictl_notifier, |
| }; |
| |
| static void __init apb_ictl_pm_init(struct apb_ictl_data *ictl) |
| { |
| if (ictl == &apb_data[0]) |
| cpu_pm_register_notifier(&apb_ictl_notifier_block); |
| } |
| #else |
| static void __init apb_ictl_pm_init(struct apb_ictl_data *ictl) |
| { |
| } |
| #endif |
| static int __init apb_ictl_init(struct device_node *node, struct device_node *parent) |
| { |
| int i, irq_base, ret; |
| struct resource res; |
| u32 cnt; |
| |
| if ((max_ictl_nr + 1) > MAX_ICTL_NR) { |
| pr_err("Spurious APB ICTL DT node\n"); |
| ret = -EINVAL; |
| goto err; |
| } |
| |
| i = max_ictl_nr; |
| ret = of_property_read_u32(node, "ictl-nr-irqs", |
| &cnt); |
| if (ret) { |
| pr_err("Not found ictl-nr-irqs property\n"); |
| ret = -EINVAL; |
| goto err; |
| } |
| |
| apb_data[i].ictl_base = of_iomap(node, 0); |
| ret = of_address_to_resource(node, 0, &res); |
| if (!apb_data[i].ictl_base) { |
| pr_err("Not found reg property\n"); |
| ret = -EINVAL; |
| goto err; |
| } |
| |
| apb_data[i].cascade_irq = irq_of_parse_and_map(node, 0); |
| if (!apb_data[i].cascade_irq) { |
| pr_err("Not found irq property\n"); |
| ret = -EINVAL; |
| goto err; |
| } |
| |
| irq_base = irq_alloc_descs(-1, 0, cnt, 0); |
| if (irq_base < 0) { |
| pr_err("Failed to allocate IRQ numbers for apb ictl\n"); |
| ret = irq_base; |
| goto err; |
| } |
| irq_set_chained_handler(apb_data[i].cascade_irq, |
| apb_irq_demux); |
| apb_data[i].nr_irqs = cnt; |
| apb_data[i].virq_base = irq_base; |
| apb_data[i].domain = irq_domain_add_legacy(node, cnt, |
| irq_base, 0, |
| &apb_irq_domain_ops, |
| &apb_data[i]); |
| ++max_ictl_nr; |
| apb_ictl_pm_init(&apb_data[i]); |
| return 0; |
| err: |
| of_node_put(node); |
| return ret; |
| |
| } |
| |
| static const struct of_device_id berlin_dt_irq_match[] = { |
| { .compatible = "arm,cortex-a9-gic", .data = gic_of_init, }, |
| { .compatible = "arm,cortex-a15-gic", .data = gic_of_init, }, |
| { .compatible = "snps,dw-apb-ictl", .data = apb_ictl_init, }, |
| {}, |
| }; |
| |
| void __init berlin_init_irq(void) |
| { |
| #ifndef CONFIG_BERLIN2CDP |
| l2x0_of_init(0x70c00000, 0xfeffffff); |
| #endif |
| of_irq_init(berlin_dt_irq_match); |
| } |