| 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 |
|
| 27 | struct ts4800_irq_data {
|
| 28 | void __iomem *base;
|
| 29 | struct platform_device *pdev;
|
| 30 | struct irq_domain *domain;
|
| 31 | unsigned int parent_irq;
|
| 32 | };
|
| 33 |
|
| 34 | static 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 |
|
| 43 | static 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 |
|
| 52 | static 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 |
|
| 59 | static 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 |
|
| 65 | static 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 |
|
| 77 | static const struct irq_domain_ops ts4800_ic_ops = {
|
| 78 | .map = ts4800_irqdomain_map,
|
| 79 | .xlate = irq_domain_xlate_onecell,
|
| 80 | };
|
| 81 |
|
| 82 | static 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 |
|
| 102 | out:
|
| 103 | chained_irq_exit(chip, desc);
|
| 104 | }
|
| 105 |
|
| 106 | static 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 |
|
| 144 | static 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 |
|
| 159 | static const struct of_device_id ts4800_ic_of_match[] = {
|
| 160 | { .compatible = "technologic,ts4800-irqc", },
|
| 161 | {},
|
| 162 | };
|
| 163 | MODULE_DEVICE_TABLE(of, ts4800_ic_of_match);
|
| 164 |
|
| 165 | static 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 | };
|
| 173 | module_platform_driver(ts4800_ic_driver);
|
| 174 |
|
| 175 | MODULE_AUTHOR("Damien Riegel <[email protected]>");
|
| 176 | MODULE_DESCRIPTION("Multiplexed-IRQs driver for TS-4800's FPGA");
|
| 177 | MODULE_LICENSE("GPL v2");
|