irq-mchp-eic.c (6566B)
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 */ 36struct 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 45static struct mchp_eic *eic; 46 47static 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 58static 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 69static 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 101static 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 112static int mchp_eic_irq_suspend(void) 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 126static void mchp_eic_irq_resume(void) 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 138static struct syscore_ops mchp_eic_syscore_ops = { 139 .suspend = mchp_eic_irq_suspend, 140 .resume = mchp_eic_irq_resume, 141}; 142 143static struct irq_chip mchp_eic_chip = { 144 .name = "eic", 145 .flags = IRQCHIP_MASK_ON_SUSPEND | IRQCHIP_SET_TYPE_MASKED, 146 .irq_mask = mchp_eic_irq_mask, 147 .irq_unmask = mchp_eic_irq_unmask, 148 .irq_set_type = mchp_eic_irq_set_type, 149 .irq_ack = irq_chip_ack_parent, 150 .irq_eoi = irq_chip_eoi_parent, 151 .irq_retrigger = irq_chip_retrigger_hierarchy, 152 .irq_set_wake = mchp_eic_irq_set_wake, 153}; 154 155static int mchp_eic_domain_alloc(struct irq_domain *domain, unsigned int virq, 156 unsigned int nr_irqs, void *data) 157{ 158 struct irq_fwspec *fwspec = data; 159 struct irq_fwspec parent_fwspec; 160 irq_hw_number_t hwirq; 161 unsigned int type; 162 int ret; 163 164 if (WARN_ON(nr_irqs != 1)) 165 return -EINVAL; 166 167 ret = irq_domain_translate_twocell(domain, fwspec, &hwirq, &type); 168 if (ret || hwirq >= MCHP_EIC_NIRQ) 169 return ret; 170 171 switch (type) { 172 case IRQ_TYPE_EDGE_RISING: 173 case IRQ_TYPE_LEVEL_HIGH: 174 break; 175 case IRQ_TYPE_EDGE_FALLING: 176 type = IRQ_TYPE_EDGE_RISING; 177 break; 178 case IRQ_TYPE_LEVEL_LOW: 179 type = IRQ_TYPE_LEVEL_HIGH; 180 break; 181 default: 182 return -EINVAL; 183 } 184 185 irq_domain_set_hwirq_and_chip(domain, virq, hwirq, &mchp_eic_chip, eic); 186 187 parent_fwspec.fwnode = domain->parent->fwnode; 188 parent_fwspec.param_count = 3; 189 parent_fwspec.param[0] = GIC_SPI; 190 parent_fwspec.param[1] = eic->irqs[hwirq]; 191 parent_fwspec.param[2] = type; 192 193 return irq_domain_alloc_irqs_parent(domain, virq, 1, &parent_fwspec); 194} 195 196static const struct irq_domain_ops mchp_eic_domain_ops = { 197 .translate = irq_domain_translate_twocell, 198 .alloc = mchp_eic_domain_alloc, 199 .free = irq_domain_free_irqs_common, 200}; 201 202static int mchp_eic_init(struct device_node *node, struct device_node *parent) 203{ 204 struct irq_domain *parent_domain = NULL; 205 int ret, i; 206 207 eic = kzalloc(sizeof(*eic), GFP_KERNEL); 208 if (!eic) 209 return -ENOMEM; 210 211 eic->base = of_iomap(node, 0); 212 if (!eic->base) { 213 ret = -ENOMEM; 214 goto free; 215 } 216 217 parent_domain = irq_find_host(parent); 218 if (!parent_domain) { 219 ret = -ENODEV; 220 goto unmap; 221 } 222 223 eic->clk = of_clk_get_by_name(node, "pclk"); 224 if (IS_ERR(eic->clk)) { 225 ret = PTR_ERR(eic->clk); 226 goto unmap; 227 } 228 229 ret = clk_prepare_enable(eic->clk); 230 if (ret) 231 goto unmap; 232 233 for (i = 0; i < MCHP_EIC_NIRQ; i++) { 234 struct of_phandle_args irq; 235 236 /* Disable it, if any. */ 237 writel_relaxed(0UL, eic->base + MCHP_EIC_SCFG(i)); 238 239 ret = of_irq_parse_one(node, i, &irq); 240 if (ret) 241 goto clk_unprepare; 242 243 if (WARN_ON(irq.args_count != 3)) { 244 ret = -EINVAL; 245 goto clk_unprepare; 246 } 247 248 eic->irqs[i] = irq.args[1]; 249 } 250 251 eic->domain = irq_domain_add_hierarchy(parent_domain, 0, MCHP_EIC_NIRQ, 252 node, &mchp_eic_domain_ops, eic); 253 if (!eic->domain) { 254 pr_err("%pOF: Failed to add domain\n", node); 255 ret = -ENODEV; 256 goto clk_unprepare; 257 } 258 259 register_syscore_ops(&mchp_eic_syscore_ops); 260 261 pr_info("%pOF: EIC registered, nr_irqs %u\n", node, MCHP_EIC_NIRQ); 262 263 return 0; 264 265clk_unprepare: 266 clk_disable_unprepare(eic->clk); 267unmap: 268 iounmap(eic->base); 269free: 270 kfree(eic); 271 return ret; 272} 273 274IRQCHIP_PLATFORM_DRIVER_BEGIN(mchp_eic) 275IRQCHIP_MATCH("microchip,sama7g5-eic", mchp_eic_init) 276IRQCHIP_PLATFORM_DRIVER_END(mchp_eic) 277 278MODULE_DESCRIPTION("Microchip External Interrupt Controller"); 279MODULE_LICENSE("GPL v2"); 280MODULE_AUTHOR("Claudiu Beznea <claudiu.beznea@microchip.com>");