1 // SPDX-License-Identifier: GPL-2.0-only 2 /* 3 * Microchip External Interrupt Controller driver 4 * 5 * Copyright (C) 2021 Microchip Technology Inc. and its subsidiaries 6 * 7 * Author: Claudiu Beznea <claudiu.beznea@microchip.com> 8 */ 9 #include <linux/clk.h> 10 #include <linux/delay.h> 11 #include <linux/interrupt.h> 12 #include <linux/irqchip.h> 13 #include <linux/of_address.h> 14 #include <linux/of_irq.h> 15 #include <linux/syscore_ops.h> 16 17 #include <dt-bindings/interrupt-controller/arm-gic.h> 18 19 #define MCHP_EIC_GFCS (0x0) 20 #define MCHP_EIC_SCFG(x) (0x4 + (x) * 0x4) 21 #define MCHP_EIC_SCFG_EN BIT(16) 22 #define MCHP_EIC_SCFG_LVL BIT(9) 23 #define MCHP_EIC_SCFG_POL BIT(8) 24 25 #define MCHP_EIC_NIRQ (2) 26 27 /* 28 * struct mchp_eic - EIC private data structure 29 * @base: base address 30 * @clk: peripheral clock 31 * @domain: irq domain 32 * @irqs: irqs b/w eic and gic 33 * @scfg: backup for scfg registers (necessary for backup and self-refresh mode) 34 * @wakeup_source: wakeup source mask 35 */ 36 struct mchp_eic { 37 void __iomem *base; 38 struct clk *clk; 39 struct irq_domain *domain; 40 u32 irqs[MCHP_EIC_NIRQ]; 41 u32 scfg[MCHP_EIC_NIRQ]; 42 u32 wakeup_source; 43 }; 44 45 static struct mchp_eic *eic; 46 47 static void mchp_eic_irq_mask(struct irq_data *d) 48 { 49 unsigned int tmp; 50 51 tmp = readl_relaxed(eic->base + MCHP_EIC_SCFG(d->hwirq)); 52 tmp &= ~MCHP_EIC_SCFG_EN; 53 writel_relaxed(tmp, eic->base + MCHP_EIC_SCFG(d->hwirq)); 54 55 irq_chip_mask_parent(d); 56 } 57 58 static void mchp_eic_irq_unmask(struct irq_data *d) 59 { 60 unsigned int tmp; 61 62 tmp = readl_relaxed(eic->base + MCHP_EIC_SCFG(d->hwirq)); 63 tmp |= MCHP_EIC_SCFG_EN; 64 writel_relaxed(tmp, eic->base + MCHP_EIC_SCFG(d->hwirq)); 65 66 irq_chip_unmask_parent(d); 67 } 68 69 static int mchp_eic_irq_set_type(struct irq_data *d, unsigned int type) 70 { 71 unsigned int parent_irq_type; 72 unsigned int tmp; 73 74 tmp = readl_relaxed(eic->base + MCHP_EIC_SCFG(d->hwirq)); 75 tmp &= ~(MCHP_EIC_SCFG_POL | MCHP_EIC_SCFG_LVL); 76 switch (type) { 77 case IRQ_TYPE_LEVEL_HIGH: 78 tmp |= MCHP_EIC_SCFG_POL | MCHP_EIC_SCFG_LVL; 79 parent_irq_type = IRQ_TYPE_LEVEL_HIGH; 80 break; 81 case IRQ_TYPE_LEVEL_LOW: 82 tmp |= MCHP_EIC_SCFG_LVL; 83 parent_irq_type = IRQ_TYPE_LEVEL_HIGH; 84 break; 85 case IRQ_TYPE_EDGE_RISING: 86 parent_irq_type = IRQ_TYPE_EDGE_RISING; 87 break; 88 case IRQ_TYPE_EDGE_FALLING: 89 tmp |= MCHP_EIC_SCFG_POL; 90 parent_irq_type = IRQ_TYPE_EDGE_RISING; 91 break; 92 default: 93 return -EINVAL; 94 } 95 96 writel_relaxed(tmp, eic->base + MCHP_EIC_SCFG(d->hwirq)); 97 98 return irq_chip_set_type_parent(d, parent_irq_type); 99 } 100 101 static int mchp_eic_irq_set_wake(struct irq_data *d, unsigned int on) 102 { 103 irq_set_irq_wake(eic->irqs[d->hwirq], on); 104 if (on) 105 eic->wakeup_source |= BIT(d->hwirq); 106 else 107 eic->wakeup_source &= ~BIT(d->hwirq); 108 109 return 0; 110 } 111 112 static int mchp_eic_irq_suspend(void *data) 113 { 114 unsigned int hwirq; 115 116 for (hwirq = 0; hwirq < MCHP_EIC_NIRQ; hwirq++) 117 eic->scfg[hwirq] = readl_relaxed(eic->base + 118 MCHP_EIC_SCFG(hwirq)); 119 120 if (!eic->wakeup_source) 121 clk_disable_unprepare(eic->clk); 122 123 return 0; 124 } 125 126 static void mchp_eic_irq_resume(void *data) 127 { 128 unsigned int hwirq; 129 130 if (!eic->wakeup_source) 131 clk_prepare_enable(eic->clk); 132 133 for (hwirq = 0; hwirq < MCHP_EIC_NIRQ; hwirq++) 134 writel_relaxed(eic->scfg[hwirq], eic->base + 135 MCHP_EIC_SCFG(hwirq)); 136 } 137 138 static const struct syscore_ops mchp_eic_syscore_ops = { 139 .suspend = mchp_eic_irq_suspend, 140 .resume = mchp_eic_irq_resume, 141 }; 142 143 static struct syscore mchp_eic_syscore = { 144 .ops = &mchp_eic_syscore_ops, 145 }; 146 147 static struct irq_chip mchp_eic_chip = { 148 .name = "eic", 149 .flags = IRQCHIP_MASK_ON_SUSPEND | IRQCHIP_SET_TYPE_MASKED, 150 .irq_mask = mchp_eic_irq_mask, 151 .irq_unmask = mchp_eic_irq_unmask, 152 .irq_set_type = mchp_eic_irq_set_type, 153 .irq_ack = irq_chip_ack_parent, 154 .irq_eoi = irq_chip_eoi_parent, 155 .irq_retrigger = irq_chip_retrigger_hierarchy, 156 .irq_set_wake = mchp_eic_irq_set_wake, 157 }; 158 159 static int mchp_eic_domain_alloc(struct irq_domain *domain, unsigned int virq, 160 unsigned int nr_irqs, void *data) 161 { 162 struct irq_fwspec *fwspec = data; 163 struct irq_fwspec parent_fwspec; 164 irq_hw_number_t hwirq; 165 unsigned int type; 166 int ret; 167 168 if (WARN_ON(nr_irqs != 1)) 169 return -EINVAL; 170 171 ret = irq_domain_translate_twocell(domain, fwspec, &hwirq, &type); 172 if (ret || hwirq >= MCHP_EIC_NIRQ) 173 return ret ?: -EINVAL; 174 175 switch (type) { 176 case IRQ_TYPE_EDGE_RISING: 177 case IRQ_TYPE_LEVEL_HIGH: 178 break; 179 case IRQ_TYPE_EDGE_FALLING: 180 type = IRQ_TYPE_EDGE_RISING; 181 break; 182 case IRQ_TYPE_LEVEL_LOW: 183 type = IRQ_TYPE_LEVEL_HIGH; 184 break; 185 default: 186 return -EINVAL; 187 } 188 189 irq_domain_set_hwirq_and_chip(domain, virq, hwirq, &mchp_eic_chip, eic); 190 191 parent_fwspec.fwnode = domain->parent->fwnode; 192 parent_fwspec.param_count = 3; 193 parent_fwspec.param[0] = GIC_SPI; 194 parent_fwspec.param[1] = eic->irqs[hwirq]; 195 parent_fwspec.param[2] = type; 196 197 return irq_domain_alloc_irqs_parent(domain, virq, 1, &parent_fwspec); 198 } 199 200 static const struct irq_domain_ops mchp_eic_domain_ops = { 201 .translate = irq_domain_translate_twocell, 202 .alloc = mchp_eic_domain_alloc, 203 .free = irq_domain_free_irqs_common, 204 }; 205 206 static int mchp_eic_probe(struct platform_device *pdev, struct device_node *parent) 207 { 208 struct device_node *node = pdev->dev.of_node; 209 struct irq_domain *parent_domain = NULL; 210 int ret, i; 211 212 eic = kzalloc(sizeof(*eic), GFP_KERNEL); 213 if (!eic) 214 return -ENOMEM; 215 216 eic->base = of_iomap(node, 0); 217 if (!eic->base) { 218 ret = -ENOMEM; 219 goto free; 220 } 221 222 parent_domain = irq_find_host(parent); 223 if (!parent_domain) { 224 ret = -ENODEV; 225 goto unmap; 226 } 227 228 eic->clk = of_clk_get_by_name(node, "pclk"); 229 if (IS_ERR(eic->clk)) { 230 ret = PTR_ERR(eic->clk); 231 goto unmap; 232 } 233 234 ret = clk_prepare_enable(eic->clk); 235 if (ret) 236 goto unmap; 237 238 for (i = 0; i < MCHP_EIC_NIRQ; i++) { 239 struct of_phandle_args irq; 240 241 /* Disable it, if any. */ 242 writel_relaxed(0UL, eic->base + MCHP_EIC_SCFG(i)); 243 244 ret = of_irq_parse_one(node, i, &irq); 245 if (ret) 246 goto clk_unprepare; 247 248 if (WARN_ON(irq.args_count != 3)) { 249 ret = -EINVAL; 250 goto clk_unprepare; 251 } 252 253 eic->irqs[i] = irq.args[1]; 254 } 255 256 eic->domain = irq_domain_create_hierarchy(parent_domain, 0, MCHP_EIC_NIRQ, 257 of_fwnode_handle(node), &mchp_eic_domain_ops, 258 eic); 259 if (!eic->domain) { 260 pr_err("%pOF: Failed to add domain\n", node); 261 ret = -ENODEV; 262 goto clk_unprepare; 263 } 264 265 register_syscore(&mchp_eic_syscore); 266 267 pr_info("%pOF: EIC registered, nr_irqs %u\n", node, MCHP_EIC_NIRQ); 268 269 return 0; 270 271 clk_unprepare: 272 clk_disable_unprepare(eic->clk); 273 unmap: 274 iounmap(eic->base); 275 free: 276 kfree(eic); 277 return ret; 278 } 279 280 IRQCHIP_PLATFORM_DRIVER_BEGIN(mchp_eic) 281 IRQCHIP_MATCH("microchip,sama7g5-eic", mchp_eic_probe) 282 IRQCHIP_PLATFORM_DRIVER_END(mchp_eic) 283 284 MODULE_DESCRIPTION("Microchip External Interrupt Controller"); 285 MODULE_AUTHOR("Claudiu Beznea <claudiu.beznea@microchip.com>"); 286