| 1 | // SPDX-License-Identifier: GPL-2.0-or-later |
| 2 | /* |
| 3 | * Copyright (C) 2008 Freescale Semiconductor, Inc. All rights reserved. |
| 4 | * |
| 5 | * Author: John Rigby, <jrigby@freescale.com> |
| 6 | * |
| 7 | * Description: |
| 8 | * MPC5121ADS CPLD irq handling |
| 9 | */ |
| 10 | |
| 11 | #undef DEBUG |
| 12 | |
| 13 | #include <linux/kernel.h> |
| 14 | #include <linux/interrupt.h> |
| 15 | #include <linux/irq.h> |
| 16 | #include <linux/io.h> |
| 17 | #include <linux/of_address.h> |
| 18 | #include <linux/of_irq.h> |
| 19 | |
| 20 | #include "mpc5121_ads.h" |
| 21 | |
| 22 | static struct device_node *cpld_pic_node; |
| 23 | static struct irq_domain *cpld_pic_host; |
| 24 | |
| 25 | /* |
| 26 | * Bits to ignore in the misc_status register |
| 27 | * 0x10 touch screen pendown is hard routed to irq1 |
| 28 | * 0x02 pci status is read from pci status register |
| 29 | */ |
| 30 | #define MISC_IGNORE 0x12 |
| 31 | |
| 32 | /* |
| 33 | * Nothing to ignore in pci status register |
| 34 | */ |
| 35 | #define PCI_IGNORE 0x00 |
| 36 | |
| 37 | struct cpld_pic { |
| 38 | u8 pci_mask; |
| 39 | u8 pci_status; |
| 40 | u8 route; |
| 41 | u8 misc_mask; |
| 42 | u8 misc_status; |
| 43 | u8 misc_control; |
| 44 | }; |
| 45 | |
| 46 | static struct cpld_pic __iomem *cpld_regs; |
| 47 | |
| 48 | static void __iomem * |
| 49 | irq_to_pic_mask(unsigned int irq) |
| 50 | { |
| 51 | return irq <= 7 ? &cpld_regs->pci_mask : &cpld_regs->misc_mask; |
| 52 | } |
| 53 | |
| 54 | static unsigned int |
| 55 | irq_to_pic_bit(unsigned int irq) |
| 56 | { |
| 57 | return 1 << (irq & 0x7); |
| 58 | } |
| 59 | |
| 60 | static void |
| 61 | cpld_mask_irq(struct irq_data *d) |
| 62 | { |
| 63 | unsigned int cpld_irq = (unsigned int)irqd_to_hwirq(d); |
| 64 | void __iomem *pic_mask = irq_to_pic_mask(irq: cpld_irq); |
| 65 | |
| 66 | out_8(pic_mask, |
| 67 | in_8(pic_mask) | irq_to_pic_bit(irq: cpld_irq)); |
| 68 | } |
| 69 | |
| 70 | static void |
| 71 | cpld_unmask_irq(struct irq_data *d) |
| 72 | { |
| 73 | unsigned int cpld_irq = (unsigned int)irqd_to_hwirq(d); |
| 74 | void __iomem *pic_mask = irq_to_pic_mask(irq: cpld_irq); |
| 75 | |
| 76 | out_8(pic_mask, |
| 77 | in_8(pic_mask) & ~irq_to_pic_bit(irq: cpld_irq)); |
| 78 | } |
| 79 | |
| 80 | static struct irq_chip cpld_pic = { |
| 81 | .name = "CPLD PIC" , |
| 82 | .irq_mask = cpld_mask_irq, |
| 83 | .irq_ack = cpld_mask_irq, |
| 84 | .irq_unmask = cpld_unmask_irq, |
| 85 | }; |
| 86 | |
| 87 | static unsigned int |
| 88 | cpld_pic_get_irq(int offset, u8 ignore, u8 __iomem *statusp, |
| 89 | u8 __iomem *maskp) |
| 90 | { |
| 91 | u8 status = in_8(statusp); |
| 92 | u8 mask = in_8(maskp); |
| 93 | |
| 94 | /* ignore don't cares and masked irqs */ |
| 95 | status |= (ignore | mask); |
| 96 | |
| 97 | if (status == 0xff) |
| 98 | return ~0; |
| 99 | |
| 100 | return ffz(status) + offset; |
| 101 | } |
| 102 | |
| 103 | static void cpld_pic_cascade(struct irq_desc *desc) |
| 104 | { |
| 105 | unsigned int hwirq; |
| 106 | |
| 107 | hwirq = cpld_pic_get_irq(offset: 0, PCI_IGNORE, statusp: &cpld_regs->pci_status, |
| 108 | maskp: &cpld_regs->pci_mask); |
| 109 | if (hwirq != ~0) { |
| 110 | generic_handle_domain_irq(domain: cpld_pic_host, hwirq); |
| 111 | return; |
| 112 | } |
| 113 | |
| 114 | hwirq = cpld_pic_get_irq(offset: 8, MISC_IGNORE, statusp: &cpld_regs->misc_status, |
| 115 | maskp: &cpld_regs->misc_mask); |
| 116 | if (hwirq != ~0) { |
| 117 | generic_handle_domain_irq(domain: cpld_pic_host, hwirq); |
| 118 | return; |
| 119 | } |
| 120 | } |
| 121 | |
| 122 | static int |
| 123 | cpld_pic_host_match(struct irq_domain *h, struct device_node *node, |
| 124 | enum irq_domain_bus_token bus_token) |
| 125 | { |
| 126 | return cpld_pic_node == node; |
| 127 | } |
| 128 | |
| 129 | static int |
| 130 | cpld_pic_host_map(struct irq_domain *h, unsigned int virq, |
| 131 | irq_hw_number_t hw) |
| 132 | { |
| 133 | irq_set_status_flags(irq: virq, set: IRQ_LEVEL); |
| 134 | irq_set_chip_and_handler(irq: virq, chip: &cpld_pic, handle: handle_level_irq); |
| 135 | return 0; |
| 136 | } |
| 137 | |
| 138 | static const struct irq_domain_ops cpld_pic_host_ops = { |
| 139 | .match = cpld_pic_host_match, |
| 140 | .map = cpld_pic_host_map, |
| 141 | }; |
| 142 | |
| 143 | void __init |
| 144 | mpc5121_ads_cpld_map(void) |
| 145 | { |
| 146 | struct device_node *np = NULL; |
| 147 | |
| 148 | np = of_find_compatible_node(NULL, NULL, compat: "fsl,mpc5121ads-cpld-pic" ); |
| 149 | if (!np) { |
| 150 | printk(KERN_ERR "CPLD PIC init: can not find cpld-pic node\n" ); |
| 151 | return; |
| 152 | } |
| 153 | |
| 154 | cpld_regs = of_iomap(node: np, index: 0); |
| 155 | of_node_put(node: np); |
| 156 | } |
| 157 | |
| 158 | void __init |
| 159 | mpc5121_ads_cpld_pic_init(void) |
| 160 | { |
| 161 | unsigned int cascade_irq; |
| 162 | struct device_node *np = NULL; |
| 163 | |
| 164 | pr_debug("cpld_ic_init\n" ); |
| 165 | |
| 166 | np = of_find_compatible_node(NULL, NULL, compat: "fsl,mpc5121ads-cpld-pic" ); |
| 167 | if (!np) { |
| 168 | printk(KERN_ERR "CPLD PIC init: can not find cpld-pic node\n" ); |
| 169 | return; |
| 170 | } |
| 171 | |
| 172 | if (!cpld_regs) |
| 173 | goto end; |
| 174 | |
| 175 | cascade_irq = irq_of_parse_and_map(node: np, index: 0); |
| 176 | if (!cascade_irq) |
| 177 | goto end; |
| 178 | |
| 179 | /* |
| 180 | * statically route touch screen pendown through 1 |
| 181 | * and ignore it here |
| 182 | * route all others through our cascade irq |
| 183 | */ |
| 184 | out_8(&cpld_regs->route, 0xfd); |
| 185 | out_8(&cpld_regs->pci_mask, 0xff); |
| 186 | /* unmask pci ints in misc mask */ |
| 187 | out_8(&cpld_regs->misc_mask, ~(MISC_IGNORE)); |
| 188 | |
| 189 | cpld_pic_node = of_node_get(node: np); |
| 190 | |
| 191 | cpld_pic_host = irq_domain_create_linear(of_fwnode_handle(np), size: 16, |
| 192 | ops: &cpld_pic_host_ops, NULL); |
| 193 | if (!cpld_pic_host) { |
| 194 | printk(KERN_ERR "CPLD PIC: failed to allocate irq host!\n" ); |
| 195 | goto end; |
| 196 | } |
| 197 | |
| 198 | irq_set_chained_handler(irq: cascade_irq, handle: cpld_pic_cascade); |
| 199 | end: |
| 200 | of_node_put(node: np); |
| 201 | } |
| 202 | |