projects/linux·blob master · refs

177 lines · 4.3 KiB

1/*
2 * Multiplexed-IRQs driver for TS-4800's FPGA
3 *
4 * Copyright (c) 2015 - Savoir-faire Linux
5 *
6 * This file is licensed under the terms of the GNU General Public
7 * License version 2. This program is licensed "as is" without any
8 * warranty of any kind, whether express or implied.
9 */
10
11#include <linux/interrupt.h>
12#include <linux/io.h>
13#include <linux/irq.h>
14#include <linux/irqchip.h>
15#include <linux/irqchip/chained_irq.h>
16#include <linux/irqdomain.h>
17#include <linux/module.h>
18#include <linux/of.h>
19#include <linux/of_address.h>
20#include <linux/of_irq.h>
21#include <linux/platform_device.h>
22#include <linux/seq_file.h>
23
24#define IRQ_MASK 0x4
25#define IRQ_STATUS 0x8
26
27struct ts4800_irq_data {
28 void __iomem *base;
29 struct platform_device *pdev;
30 struct irq_domain *domain;
31 unsigned int parent_irq;
32};
33
34static void ts4800_irq_mask(struct irq_data *d)
35{
36 struct ts4800_irq_data *data = irq_data_get_irq_chip_data(d);
37 u16 reg = readw(data->base + IRQ_MASK);
38 u16 mask = 1 << d->hwirq;
39
40 writew(reg | mask, data->base + IRQ_MASK);
41}
42
43static void ts4800_irq_unmask(struct irq_data *d)
44{
45 struct ts4800_irq_data *data = irq_data_get_irq_chip_data(d);
46 u16 reg = readw(data->base + IRQ_MASK);
47 u16 mask = 1 << d->hwirq;
48
49 writew(reg & ~mask, data->base + IRQ_MASK);
50}
51
52static void ts4800_irq_print_chip(struct irq_data *d, struct seq_file *p)
53{
54 struct ts4800_irq_data *data = irq_data_get_irq_chip_data(d);
55
56 seq_puts(p, dev_name(&data->pdev->dev));
57}
58
59static const struct irq_chip ts4800_chip = {
60 .irq_mask = ts4800_irq_mask,
61 .irq_unmask = ts4800_irq_unmask,
62 .irq_print_chip = ts4800_irq_print_chip,
63};
64
65static int ts4800_irqdomain_map(struct irq_domain *d, unsigned int irq,
66 irq_hw_number_t hwirq)
67{
68 struct ts4800_irq_data *data = d->host_data;
69
70 irq_set_chip_and_handler(irq, &ts4800_chip, handle_simple_irq);
71 irq_set_chip_data(irq, data);
72 irq_set_noprobe(irq);
73
74 return 0;
75}
76
77static const struct irq_domain_ops ts4800_ic_ops = {
78 .map = ts4800_irqdomain_map,
79 .xlate = irq_domain_xlate_onecell,
80};
81
82static void ts4800_ic_chained_handle_irq(struct irq_desc *desc)
83{
84 struct ts4800_irq_data *data = irq_desc_get_handler_data(desc);
85 struct irq_chip *chip = irq_desc_get_chip(desc);
86 u16 status = readw(data->base + IRQ_STATUS);
87
88 chained_irq_enter(chip, desc);
89
90 if (unlikely(status == 0)) {
91 handle_bad_irq(desc);
92 goto out;
93 }
94
95 do {
96 unsigned int bit = __ffs(status);
97
98 generic_handle_domain_irq(data->domain, bit);
99 status &= ~(1 << bit);
100 } while (status);
101
102out:
103 chained_irq_exit(chip, desc);
104}
105
106static int ts4800_ic_probe(struct platform_device *pdev)
107{
108 struct device_node *node = pdev->dev.of_node;
109 struct ts4800_irq_data *data;
110 int parent_irq;
111
112 data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
113 if (!data)
114 return -ENOMEM;
115
116 data->pdev = pdev;
117 data->base = devm_platform_ioremap_resource(pdev, 0);
118 if (IS_ERR(data->base))
119 return PTR_ERR(data->base);
120
121 writew(0xFFFF, data->base + IRQ_MASK);
122
123 parent_irq = irq_of_parse_and_map(node, 0);
124 if (!parent_irq) {
125 dev_err(&pdev->dev, "failed to get parent IRQ\n");
126 return -EINVAL;
127 }
128
129 data->domain = irq_domain_create_linear(dev_fwnode(&pdev->dev), 8, &ts4800_ic_ops, data);
130 if (!data->domain) {
131 dev_err(&pdev->dev, "cannot add IRQ domain\n");
132 return -ENOMEM;
133 }
134
135 irq_set_chained_handler_and_data(parent_irq,
136 ts4800_ic_chained_handle_irq, data);
137
138 data->parent_irq = parent_irq;
139 platform_set_drvdata(pdev, data);
140
141 return 0;
142}
143
144static void ts4800_ic_remove(struct platform_device *pdev)
145{
146 struct ts4800_irq_data *data = platform_get_drvdata(pdev);
147 unsigned int hwirq;
148
149 irq_set_chained_handler_and_data(data->parent_irq, NULL, NULL);
150
151 for (hwirq = 0; hwirq < 8; hwirq++)
152 irq_dispose_mapping(irq_find_mapping(data->domain, hwirq));
153
154 irq_dispose_mapping(data->parent_irq);
155
156 irq_domain_remove(data->domain);
157}
158
159static const struct of_device_id ts4800_ic_of_match[] = {
160 { .compatible = "technologic,ts4800-irqc", },
161 {},
162};
163MODULE_DEVICE_TABLE(of, ts4800_ic_of_match);
164
165static struct platform_driver ts4800_ic_driver = {
166 .probe = ts4800_ic_probe,
167 .remove = ts4800_ic_remove,
168 .driver = {
169 .name = "ts4800-irqc",
170 .of_match_table = ts4800_ic_of_match,
171 },
172};
173module_platform_driver(ts4800_ic_driver);
174
175MODULE_AUTHOR("Damien Riegel <[email protected]>");
176MODULE_DESCRIPTION("Multiplexed-IRQs driver for TS-4800's FPGA");
177MODULE_LICENSE("GPL v2");