1 | /* |
2 | * Copyright (C) 2016 Marvell |
3 | * |
4 | * Yehuda Yitschak <yehuday@marvell.com> |
5 | * Thomas Petazzoni <thomas.petazzoni@free-electrons.com> |
6 | * |
7 | * This file is licensed under the terms of the GNU General Public |
8 | * License version 2. This program is licensed "as is" without any |
9 | * warranty of any kind, whether express or implied. |
10 | */ |
11 | |
12 | #include <linux/interrupt.h> |
13 | #include <linux/io.h> |
14 | #include <linux/irq.h> |
15 | #include <linux/irqchip.h> |
16 | #include <linux/irqchip/chained_irq.h> |
17 | #include <linux/irqdomain.h> |
18 | #include <linux/module.h> |
19 | #include <linux/of_irq.h> |
20 | #include <linux/platform_device.h> |
21 | #include <linux/seq_file.h> |
22 | |
23 | #define PIC_CAUSE 0x0 |
24 | #define PIC_MASK 0x4 |
25 | |
26 | #define PIC_MAX_IRQS 32 |
27 | #define PIC_MAX_IRQ_MASK ((1UL << PIC_MAX_IRQS) - 1) |
28 | |
29 | struct mvebu_pic { |
30 | void __iomem *base; |
31 | u32 parent_irq; |
32 | struct irq_domain *domain; |
33 | struct platform_device *pdev; |
34 | }; |
35 | |
36 | static void mvebu_pic_reset(struct mvebu_pic *pic) |
37 | { |
38 | /* ACK and mask all interrupts */ |
39 | writel(val: 0, addr: pic->base + PIC_MASK); |
40 | writel(PIC_MAX_IRQ_MASK, addr: pic->base + PIC_CAUSE); |
41 | } |
42 | |
43 | static void mvebu_pic_eoi_irq(struct irq_data *d) |
44 | { |
45 | struct mvebu_pic *pic = irq_data_get_irq_chip_data(d); |
46 | |
47 | writel(val: 1 << d->hwirq, addr: pic->base + PIC_CAUSE); |
48 | } |
49 | |
50 | static void mvebu_pic_mask_irq(struct irq_data *d) |
51 | { |
52 | struct mvebu_pic *pic = irq_data_get_irq_chip_data(d); |
53 | u32 reg; |
54 | |
55 | reg = readl(addr: pic->base + PIC_MASK); |
56 | reg |= (1 << d->hwirq); |
57 | writel(val: reg, addr: pic->base + PIC_MASK); |
58 | } |
59 | |
60 | static void mvebu_pic_unmask_irq(struct irq_data *d) |
61 | { |
62 | struct mvebu_pic *pic = irq_data_get_irq_chip_data(d); |
63 | u32 reg; |
64 | |
65 | reg = readl(addr: pic->base + PIC_MASK); |
66 | reg &= ~(1 << d->hwirq); |
67 | writel(val: reg, addr: pic->base + PIC_MASK); |
68 | } |
69 | |
70 | static void mvebu_pic_print_chip(struct irq_data *d, struct seq_file *p) |
71 | { |
72 | struct mvebu_pic *pic = irq_data_get_irq_chip_data(d); |
73 | |
74 | seq_printf(m: p, fmt: dev_name(dev: &pic->pdev->dev)); |
75 | } |
76 | |
77 | static const struct irq_chip mvebu_pic_chip = { |
78 | .irq_mask = mvebu_pic_mask_irq, |
79 | .irq_unmask = mvebu_pic_unmask_irq, |
80 | .irq_eoi = mvebu_pic_eoi_irq, |
81 | .irq_print_chip = mvebu_pic_print_chip, |
82 | }; |
83 | |
84 | static int mvebu_pic_irq_map(struct irq_domain *domain, unsigned int virq, |
85 | irq_hw_number_t hwirq) |
86 | { |
87 | struct mvebu_pic *pic = domain->host_data; |
88 | |
89 | irq_set_percpu_devid(irq: virq); |
90 | irq_set_chip_data(irq: virq, data: pic); |
91 | irq_set_chip_and_handler(irq: virq, chip: &mvebu_pic_chip, handle: handle_percpu_devid_irq); |
92 | irq_set_status_flags(irq: virq, set: IRQ_LEVEL); |
93 | irq_set_probe(irq: virq); |
94 | |
95 | return 0; |
96 | } |
97 | |
98 | static const struct irq_domain_ops mvebu_pic_domain_ops = { |
99 | .map = mvebu_pic_irq_map, |
100 | .xlate = irq_domain_xlate_onecell, |
101 | }; |
102 | |
103 | static void mvebu_pic_handle_cascade_irq(struct irq_desc *desc) |
104 | { |
105 | struct mvebu_pic *pic = irq_desc_get_handler_data(desc); |
106 | struct irq_chip *chip = irq_desc_get_chip(desc); |
107 | unsigned long irqmap, irqn; |
108 | |
109 | irqmap = readl_relaxed(pic->base + PIC_CAUSE); |
110 | chained_irq_enter(chip, desc); |
111 | |
112 | for_each_set_bit(irqn, &irqmap, BITS_PER_LONG) |
113 | generic_handle_domain_irq(domain: pic->domain, hwirq: irqn); |
114 | |
115 | chained_irq_exit(chip, desc); |
116 | } |
117 | |
118 | static void mvebu_pic_enable_percpu_irq(void *data) |
119 | { |
120 | struct mvebu_pic *pic = data; |
121 | |
122 | mvebu_pic_reset(pic); |
123 | enable_percpu_irq(irq: pic->parent_irq, type: IRQ_TYPE_NONE); |
124 | } |
125 | |
126 | static void mvebu_pic_disable_percpu_irq(void *data) |
127 | { |
128 | struct mvebu_pic *pic = data; |
129 | |
130 | disable_percpu_irq(irq: pic->parent_irq); |
131 | } |
132 | |
133 | static int mvebu_pic_probe(struct platform_device *pdev) |
134 | { |
135 | struct device_node *node = pdev->dev.of_node; |
136 | struct mvebu_pic *pic; |
137 | |
138 | pic = devm_kzalloc(dev: &pdev->dev, size: sizeof(struct mvebu_pic), GFP_KERNEL); |
139 | if (!pic) |
140 | return -ENOMEM; |
141 | |
142 | pic->pdev = pdev; |
143 | pic->base = devm_platform_ioremap_resource(pdev, index: 0); |
144 | if (IS_ERR(ptr: pic->base)) |
145 | return PTR_ERR(ptr: pic->base); |
146 | |
147 | pic->parent_irq = irq_of_parse_and_map(node, index: 0); |
148 | if (pic->parent_irq <= 0) { |
149 | dev_err(&pdev->dev, "Failed to parse parent interrupt\n" ); |
150 | return -EINVAL; |
151 | } |
152 | |
153 | pic->domain = irq_domain_add_linear(of_node: node, PIC_MAX_IRQS, |
154 | ops: &mvebu_pic_domain_ops, host_data: pic); |
155 | if (!pic->domain) { |
156 | dev_err(&pdev->dev, "Failed to allocate irq domain\n" ); |
157 | return -ENOMEM; |
158 | } |
159 | |
160 | irq_set_chained_handler(irq: pic->parent_irq, handle: mvebu_pic_handle_cascade_irq); |
161 | irq_set_handler_data(irq: pic->parent_irq, data: pic); |
162 | |
163 | on_each_cpu(func: mvebu_pic_enable_percpu_irq, info: pic, wait: 1); |
164 | |
165 | platform_set_drvdata(pdev, data: pic); |
166 | |
167 | return 0; |
168 | } |
169 | |
170 | static int mvebu_pic_remove(struct platform_device *pdev) |
171 | { |
172 | struct mvebu_pic *pic = platform_get_drvdata(pdev); |
173 | |
174 | on_each_cpu(func: mvebu_pic_disable_percpu_irq, info: pic, wait: 1); |
175 | irq_domain_remove(host: pic->domain); |
176 | |
177 | return 0; |
178 | } |
179 | |
180 | static const struct of_device_id mvebu_pic_of_match[] = { |
181 | { .compatible = "marvell,armada-8k-pic" , }, |
182 | {}, |
183 | }; |
184 | MODULE_DEVICE_TABLE(of, mvebu_pic_of_match); |
185 | |
186 | static struct platform_driver mvebu_pic_driver = { |
187 | .probe = mvebu_pic_probe, |
188 | .remove = mvebu_pic_remove, |
189 | .driver = { |
190 | .name = "mvebu-pic" , |
191 | .of_match_table = mvebu_pic_of_match, |
192 | }, |
193 | }; |
194 | module_platform_driver(mvebu_pic_driver); |
195 | |
196 | MODULE_AUTHOR("Yehuda Yitschak <yehuday@marvell.com>" ); |
197 | MODULE_AUTHOR("Thomas Petazzoni <thomas.petazzoni@free-electrons.com>" ); |
198 | MODULE_LICENSE("GPL v2" ); |
199 | MODULE_ALIAS("platform:mvebu_pic" ); |
200 | |
201 | |