1 | // SPDX-License-Identifier: GPL-2.0+ |
2 | /* |
3 | * RDA8810PL SoC irqchip driver |
4 | * |
5 | * Copyright RDA Microelectronics Company Limited |
6 | * Copyright (c) 2017 Andreas Färber |
7 | * Copyright (c) 2018 Manivannan Sadhasivam |
8 | */ |
9 | |
10 | #include <linux/init.h> |
11 | #include <linux/interrupt.h> |
12 | #include <linux/irq.h> |
13 | #include <linux/irqchip.h> |
14 | #include <linux/irqdomain.h> |
15 | #include <linux/of_address.h> |
16 | |
17 | #include <asm/exception.h> |
18 | |
19 | #define RDA_INTC_FINALSTATUS 0x00 |
20 | #define RDA_INTC_MASK_SET 0x08 |
21 | #define RDA_INTC_MASK_CLR 0x0c |
22 | |
23 | #define RDA_IRQ_MASK_ALL 0xFFFFFFFF |
24 | |
25 | #define RDA_NR_IRQS 32 |
26 | |
27 | static void __iomem *rda_intc_base; |
28 | static struct irq_domain *rda_irq_domain; |
29 | |
30 | static void rda_intc_mask_irq(struct irq_data *d) |
31 | { |
32 | writel_relaxed(BIT(d->hwirq), rda_intc_base + RDA_INTC_MASK_CLR); |
33 | } |
34 | |
35 | static void rda_intc_unmask_irq(struct irq_data *d) |
36 | { |
37 | writel_relaxed(BIT(d->hwirq), rda_intc_base + RDA_INTC_MASK_SET); |
38 | } |
39 | |
40 | static int rda_intc_set_type(struct irq_data *data, unsigned int flow_type) |
41 | { |
42 | /* Hardware supports only level triggered interrupts */ |
43 | if ((flow_type & (IRQF_TRIGGER_HIGH | IRQF_TRIGGER_LOW)) == flow_type) |
44 | return 0; |
45 | |
46 | return -EINVAL; |
47 | } |
48 | |
49 | static void __exception_irq_entry rda_handle_irq(struct pt_regs *regs) |
50 | { |
51 | u32 stat = readl_relaxed(rda_intc_base + RDA_INTC_FINALSTATUS); |
52 | u32 hwirq; |
53 | |
54 | while (stat) { |
55 | hwirq = __fls(stat); |
56 | generic_handle_domain_irq(rda_irq_domain, hwirq); |
57 | stat &= ~BIT(hwirq); |
58 | } |
59 | } |
60 | |
61 | static struct irq_chip rda_irq_chip = { |
62 | .name = "rda-intc" , |
63 | .irq_mask = rda_intc_mask_irq, |
64 | .irq_unmask = rda_intc_unmask_irq, |
65 | .irq_set_type = rda_intc_set_type, |
66 | }; |
67 | |
68 | static int rda_irq_map(struct irq_domain *d, |
69 | unsigned int virq, irq_hw_number_t hw) |
70 | { |
71 | irq_set_status_flags(irq: virq, set: IRQ_LEVEL); |
72 | irq_set_chip_and_handler(irq: virq, chip: &rda_irq_chip, handle: handle_level_irq); |
73 | irq_set_chip_data(irq: virq, data: d->host_data); |
74 | irq_set_probe(irq: virq); |
75 | |
76 | return 0; |
77 | } |
78 | |
79 | static const struct irq_domain_ops rda_irq_domain_ops = { |
80 | .map = rda_irq_map, |
81 | .xlate = irq_domain_xlate_onecell, |
82 | }; |
83 | |
84 | static int __init rda8810_intc_init(struct device_node *node, |
85 | struct device_node *parent) |
86 | { |
87 | rda_intc_base = of_io_request_and_map(device: node, index: 0, name: "rda-intc" ); |
88 | if (IS_ERR(ptr: rda_intc_base)) |
89 | return PTR_ERR(ptr: rda_intc_base); |
90 | |
91 | /* Mask all interrupt sources */ |
92 | writel_relaxed(RDA_IRQ_MASK_ALL, rda_intc_base + RDA_INTC_MASK_CLR); |
93 | |
94 | rda_irq_domain = irq_domain_create_linear(fwnode: &node->fwnode, RDA_NR_IRQS, |
95 | ops: &rda_irq_domain_ops, |
96 | host_data: rda_intc_base); |
97 | if (!rda_irq_domain) { |
98 | iounmap(addr: rda_intc_base); |
99 | return -ENOMEM; |
100 | } |
101 | |
102 | set_handle_irq(rda_handle_irq); |
103 | |
104 | return 0; |
105 | } |
106 | |
107 | IRQCHIP_DECLARE(rda_intc, "rda,8810pl-intc" , rda8810_intc_init); |
108 | |