11eb77c3bSTalel Shenhar // SPDX-License-Identifier: GPL-2.0
21eb77c3bSTalel Shenhar /*
31eb77c3bSTalel Shenhar * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
41eb77c3bSTalel Shenhar */
51eb77c3bSTalel Shenhar
61eb77c3bSTalel Shenhar #include <linux/bitfield.h>
71eb77c3bSTalel Shenhar #include <linux/irq.h>
81eb77c3bSTalel Shenhar #include <linux/irqchip.h>
91eb77c3bSTalel Shenhar #include <linux/irqchip/chained_irq.h>
101eb77c3bSTalel Shenhar #include <linux/irqdomain.h>
111eb77c3bSTalel Shenhar #include <linux/module.h>
121eb77c3bSTalel Shenhar #include <linux/of.h>
131eb77c3bSTalel Shenhar #include <linux/of_address.h>
141eb77c3bSTalel Shenhar #include <linux/of_irq.h>
151eb77c3bSTalel Shenhar
161eb77c3bSTalel Shenhar /* FIC Registers */
171eb77c3bSTalel Shenhar #define AL_FIC_CAUSE 0x00
189c426b77STalel Shenhar #define AL_FIC_SET_CAUSE 0x08
191eb77c3bSTalel Shenhar #define AL_FIC_MASK 0x10
201eb77c3bSTalel Shenhar #define AL_FIC_CONTROL 0x28
211eb77c3bSTalel Shenhar
221eb77c3bSTalel Shenhar #define CONTROL_TRIGGER_RISING BIT(3)
231eb77c3bSTalel Shenhar #define CONTROL_MASK_MSI_X BIT(5)
241eb77c3bSTalel Shenhar
251eb77c3bSTalel Shenhar #define NR_FIC_IRQS 32
261eb77c3bSTalel Shenhar
271eb77c3bSTalel Shenhar MODULE_AUTHOR("Talel Shenhar");
281eb77c3bSTalel Shenhar MODULE_DESCRIPTION("Amazon's Annapurna Labs Interrupt Controller Driver");
291eb77c3bSTalel Shenhar
301eb77c3bSTalel Shenhar enum al_fic_state {
311eb77c3bSTalel Shenhar AL_FIC_UNCONFIGURED = 0,
321eb77c3bSTalel Shenhar AL_FIC_CONFIGURED_LEVEL,
331eb77c3bSTalel Shenhar AL_FIC_CONFIGURED_RISING_EDGE,
341eb77c3bSTalel Shenhar };
351eb77c3bSTalel Shenhar
361eb77c3bSTalel Shenhar struct al_fic {
371eb77c3bSTalel Shenhar void __iomem *base;
381eb77c3bSTalel Shenhar struct irq_domain *domain;
391eb77c3bSTalel Shenhar const char *name;
401eb77c3bSTalel Shenhar unsigned int parent_irq;
411eb77c3bSTalel Shenhar enum al_fic_state state;
421eb77c3bSTalel Shenhar };
431eb77c3bSTalel Shenhar
al_fic_set_trigger(struct al_fic * fic,struct irq_chip_generic * gc,enum al_fic_state new_state)441eb77c3bSTalel Shenhar static void al_fic_set_trigger(struct al_fic *fic,
451eb77c3bSTalel Shenhar struct irq_chip_generic *gc,
461eb77c3bSTalel Shenhar enum al_fic_state new_state)
471eb77c3bSTalel Shenhar {
481eb77c3bSTalel Shenhar irq_flow_handler_t handler;
491eb77c3bSTalel Shenhar u32 control = readl_relaxed(fic->base + AL_FIC_CONTROL);
501eb77c3bSTalel Shenhar
511eb77c3bSTalel Shenhar if (new_state == AL_FIC_CONFIGURED_LEVEL) {
521eb77c3bSTalel Shenhar handler = handle_level_irq;
531eb77c3bSTalel Shenhar control &= ~CONTROL_TRIGGER_RISING;
541eb77c3bSTalel Shenhar } else {
551eb77c3bSTalel Shenhar handler = handle_edge_irq;
561eb77c3bSTalel Shenhar control |= CONTROL_TRIGGER_RISING;
571eb77c3bSTalel Shenhar }
581eb77c3bSTalel Shenhar gc->chip_types->handler = handler;
591eb77c3bSTalel Shenhar fic->state = new_state;
601eb77c3bSTalel Shenhar writel_relaxed(control, fic->base + AL_FIC_CONTROL);
611eb77c3bSTalel Shenhar }
621eb77c3bSTalel Shenhar
al_fic_irq_set_type(struct irq_data * data,unsigned int flow_type)631eb77c3bSTalel Shenhar static int al_fic_irq_set_type(struct irq_data *data, unsigned int flow_type)
641eb77c3bSTalel Shenhar {
651eb77c3bSTalel Shenhar struct irq_chip_generic *gc = irq_data_get_irq_chip_data(data);
661eb77c3bSTalel Shenhar struct al_fic *fic = gc->private;
671eb77c3bSTalel Shenhar enum al_fic_state new_state;
681eb77c3bSTalel Shenhar int ret = 0;
691eb77c3bSTalel Shenhar
701eb77c3bSTalel Shenhar irq_gc_lock(gc);
711eb77c3bSTalel Shenhar
721eb77c3bSTalel Shenhar if (((flow_type & IRQ_TYPE_SENSE_MASK) != IRQ_TYPE_LEVEL_HIGH) &&
731eb77c3bSTalel Shenhar ((flow_type & IRQ_TYPE_SENSE_MASK) != IRQ_TYPE_EDGE_RISING)) {
741eb77c3bSTalel Shenhar pr_debug("fic doesn't support flow type %d\n", flow_type);
751eb77c3bSTalel Shenhar ret = -EINVAL;
761eb77c3bSTalel Shenhar goto err;
771eb77c3bSTalel Shenhar }
781eb77c3bSTalel Shenhar
791eb77c3bSTalel Shenhar new_state = (flow_type & IRQ_TYPE_LEVEL_HIGH) ?
801eb77c3bSTalel Shenhar AL_FIC_CONFIGURED_LEVEL : AL_FIC_CONFIGURED_RISING_EDGE;
811eb77c3bSTalel Shenhar
821eb77c3bSTalel Shenhar /*
831eb77c3bSTalel Shenhar * A given FIC instance can be either all level or all edge triggered.
841eb77c3bSTalel Shenhar * This is generally fixed depending on what pieces of HW it's wired up
851eb77c3bSTalel Shenhar * to.
861eb77c3bSTalel Shenhar *
871eb77c3bSTalel Shenhar * We configure it based on the sensitivity of the first source
881eb77c3bSTalel Shenhar * being setup, and reject any subsequent attempt at configuring it in a
891eb77c3bSTalel Shenhar * different way.
901eb77c3bSTalel Shenhar */
911eb77c3bSTalel Shenhar if (fic->state == AL_FIC_UNCONFIGURED) {
921eb77c3bSTalel Shenhar al_fic_set_trigger(fic, gc, new_state);
931eb77c3bSTalel Shenhar } else if (fic->state != new_state) {
941eb77c3bSTalel Shenhar pr_debug("fic %s state already configured to %d\n",
951eb77c3bSTalel Shenhar fic->name, fic->state);
961eb77c3bSTalel Shenhar ret = -EINVAL;
971eb77c3bSTalel Shenhar goto err;
981eb77c3bSTalel Shenhar }
991eb77c3bSTalel Shenhar
1001eb77c3bSTalel Shenhar err:
1011eb77c3bSTalel Shenhar irq_gc_unlock(gc);
1021eb77c3bSTalel Shenhar
1031eb77c3bSTalel Shenhar return ret;
1041eb77c3bSTalel Shenhar }
1051eb77c3bSTalel Shenhar
al_fic_irq_handler(struct irq_desc * desc)1061eb77c3bSTalel Shenhar static void al_fic_irq_handler(struct irq_desc *desc)
1071eb77c3bSTalel Shenhar {
1081eb77c3bSTalel Shenhar struct al_fic *fic = irq_desc_get_handler_data(desc);
1091eb77c3bSTalel Shenhar struct irq_domain *domain = fic->domain;
1101eb77c3bSTalel Shenhar struct irq_chip *irqchip = irq_desc_get_chip(desc);
1111eb77c3bSTalel Shenhar struct irq_chip_generic *gc = irq_get_domain_generic_chip(domain, 0);
1121eb77c3bSTalel Shenhar unsigned long pending;
1131eb77c3bSTalel Shenhar u32 hwirq;
1141eb77c3bSTalel Shenhar
1151eb77c3bSTalel Shenhar chained_irq_enter(irqchip, desc);
1161eb77c3bSTalel Shenhar
1171eb77c3bSTalel Shenhar pending = readl_relaxed(fic->base + AL_FIC_CAUSE);
1181eb77c3bSTalel Shenhar pending &= ~gc->mask_cache;
1191eb77c3bSTalel Shenhar
120*046a6ee2SMarc Zyngier for_each_set_bit(hwirq, &pending, NR_FIC_IRQS)
121*046a6ee2SMarc Zyngier generic_handle_domain_irq(domain, hwirq);
1221eb77c3bSTalel Shenhar
1231eb77c3bSTalel Shenhar chained_irq_exit(irqchip, desc);
1241eb77c3bSTalel Shenhar }
1251eb77c3bSTalel Shenhar
al_fic_irq_retrigger(struct irq_data * data)1269c426b77STalel Shenhar static int al_fic_irq_retrigger(struct irq_data *data)
1279c426b77STalel Shenhar {
1289c426b77STalel Shenhar struct irq_chip_generic *gc = irq_data_get_irq_chip_data(data);
1299c426b77STalel Shenhar struct al_fic *fic = gc->private;
1309c426b77STalel Shenhar
1319c426b77STalel Shenhar writel_relaxed(BIT(data->hwirq), fic->base + AL_FIC_SET_CAUSE);
1329c426b77STalel Shenhar
1339c426b77STalel Shenhar return 1;
1349c426b77STalel Shenhar }
1359c426b77STalel Shenhar
al_fic_register(struct device_node * node,struct al_fic * fic)1361eb77c3bSTalel Shenhar static int al_fic_register(struct device_node *node,
1371eb77c3bSTalel Shenhar struct al_fic *fic)
1381eb77c3bSTalel Shenhar {
1391eb77c3bSTalel Shenhar struct irq_chip_generic *gc;
1401eb77c3bSTalel Shenhar int ret;
1411eb77c3bSTalel Shenhar
1421eb77c3bSTalel Shenhar fic->domain = irq_domain_add_linear(node,
1431eb77c3bSTalel Shenhar NR_FIC_IRQS,
1441eb77c3bSTalel Shenhar &irq_generic_chip_ops,
1451eb77c3bSTalel Shenhar fic);
1461eb77c3bSTalel Shenhar if (!fic->domain) {
1471eb77c3bSTalel Shenhar pr_err("fail to add irq domain\n");
1481eb77c3bSTalel Shenhar return -ENOMEM;
1491eb77c3bSTalel Shenhar }
1501eb77c3bSTalel Shenhar
1511eb77c3bSTalel Shenhar ret = irq_alloc_domain_generic_chips(fic->domain,
1521eb77c3bSTalel Shenhar NR_FIC_IRQS,
1531eb77c3bSTalel Shenhar 1, fic->name,
1541eb77c3bSTalel Shenhar handle_level_irq,
1551eb77c3bSTalel Shenhar 0, 0, IRQ_GC_INIT_MASK_CACHE);
1561eb77c3bSTalel Shenhar if (ret) {
1571eb77c3bSTalel Shenhar pr_err("fail to allocate generic chip (%d)\n", ret);
1581eb77c3bSTalel Shenhar goto err_domain_remove;
1591eb77c3bSTalel Shenhar }
1601eb77c3bSTalel Shenhar
1611eb77c3bSTalel Shenhar gc = irq_get_domain_generic_chip(fic->domain, 0);
1621eb77c3bSTalel Shenhar gc->reg_base = fic->base;
1631eb77c3bSTalel Shenhar gc->chip_types->regs.mask = AL_FIC_MASK;
1641eb77c3bSTalel Shenhar gc->chip_types->regs.ack = AL_FIC_CAUSE;
1651eb77c3bSTalel Shenhar gc->chip_types->chip.irq_mask = irq_gc_mask_set_bit;
1661eb77c3bSTalel Shenhar gc->chip_types->chip.irq_unmask = irq_gc_mask_clr_bit;
1671eb77c3bSTalel Shenhar gc->chip_types->chip.irq_ack = irq_gc_ack_clr_bit;
1681eb77c3bSTalel Shenhar gc->chip_types->chip.irq_set_type = al_fic_irq_set_type;
1699c426b77STalel Shenhar gc->chip_types->chip.irq_retrigger = al_fic_irq_retrigger;
1701eb77c3bSTalel Shenhar gc->chip_types->chip.flags = IRQCHIP_SKIP_SET_WAKE;
1711eb77c3bSTalel Shenhar gc->private = fic;
1721eb77c3bSTalel Shenhar
1731eb77c3bSTalel Shenhar irq_set_chained_handler_and_data(fic->parent_irq,
1741eb77c3bSTalel Shenhar al_fic_irq_handler,
1751eb77c3bSTalel Shenhar fic);
1761eb77c3bSTalel Shenhar return 0;
1771eb77c3bSTalel Shenhar
1781eb77c3bSTalel Shenhar err_domain_remove:
1791eb77c3bSTalel Shenhar irq_domain_remove(fic->domain);
1801eb77c3bSTalel Shenhar
1811eb77c3bSTalel Shenhar return ret;
1821eb77c3bSTalel Shenhar }
1831eb77c3bSTalel Shenhar
1841eb77c3bSTalel Shenhar /*
1851eb77c3bSTalel Shenhar * al_fic_wire_init() - initialize and configure fic in wire mode
1861eb77c3bSTalel Shenhar * @of_node: optional pointer to interrupt controller's device tree node.
1871eb77c3bSTalel Shenhar * @base: mmio to fic register
1881eb77c3bSTalel Shenhar * @name: name of the fic
1891eb77c3bSTalel Shenhar * @parent_irq: interrupt of parent
1901eb77c3bSTalel Shenhar *
1911eb77c3bSTalel Shenhar * This API will configure the fic hardware to to work in wire mode.
1921eb77c3bSTalel Shenhar * In wire mode, fic hardware is generating a wire ("wired") interrupt.
1931eb77c3bSTalel Shenhar * Interrupt can be generated based on positive edge or level - configuration is
1941eb77c3bSTalel Shenhar * to be determined based on connected hardware to this fic.
1951eb77c3bSTalel Shenhar */
al_fic_wire_init(struct device_node * node,void __iomem * base,const char * name,unsigned int parent_irq)1961eb77c3bSTalel Shenhar static struct al_fic *al_fic_wire_init(struct device_node *node,
1971eb77c3bSTalel Shenhar void __iomem *base,
1981eb77c3bSTalel Shenhar const char *name,
1991eb77c3bSTalel Shenhar unsigned int parent_irq)
2001eb77c3bSTalel Shenhar {
2011eb77c3bSTalel Shenhar struct al_fic *fic;
2021eb77c3bSTalel Shenhar int ret;
2031eb77c3bSTalel Shenhar u32 control = CONTROL_MASK_MSI_X;
2041eb77c3bSTalel Shenhar
2051eb77c3bSTalel Shenhar fic = kzalloc(sizeof(*fic), GFP_KERNEL);
2061eb77c3bSTalel Shenhar if (!fic)
2071eb77c3bSTalel Shenhar return ERR_PTR(-ENOMEM);
2081eb77c3bSTalel Shenhar
2091eb77c3bSTalel Shenhar fic->base = base;
2101eb77c3bSTalel Shenhar fic->parent_irq = parent_irq;
2111eb77c3bSTalel Shenhar fic->name = name;
2121eb77c3bSTalel Shenhar
2131eb77c3bSTalel Shenhar /* mask out all interrupts */
2141eb77c3bSTalel Shenhar writel_relaxed(0xFFFFFFFF, fic->base + AL_FIC_MASK);
2151eb77c3bSTalel Shenhar
2161eb77c3bSTalel Shenhar /* clear any pending interrupt */
2171eb77c3bSTalel Shenhar writel_relaxed(0, fic->base + AL_FIC_CAUSE);
2181eb77c3bSTalel Shenhar
2191eb77c3bSTalel Shenhar writel_relaxed(control, fic->base + AL_FIC_CONTROL);
2201eb77c3bSTalel Shenhar
2211eb77c3bSTalel Shenhar ret = al_fic_register(node, fic);
2221eb77c3bSTalel Shenhar if (ret) {
2231eb77c3bSTalel Shenhar pr_err("fail to register irqchip\n");
2241eb77c3bSTalel Shenhar goto err_free;
2251eb77c3bSTalel Shenhar }
2261eb77c3bSTalel Shenhar
2271eb77c3bSTalel Shenhar pr_debug("%s initialized successfully in Legacy mode (parent-irq=%u)\n",
2281eb77c3bSTalel Shenhar fic->name, parent_irq);
2291eb77c3bSTalel Shenhar
2301eb77c3bSTalel Shenhar return fic;
2311eb77c3bSTalel Shenhar
2321eb77c3bSTalel Shenhar err_free:
2331eb77c3bSTalel Shenhar kfree(fic);
2341eb77c3bSTalel Shenhar return ERR_PTR(ret);
2351eb77c3bSTalel Shenhar }
2361eb77c3bSTalel Shenhar
al_fic_init_dt(struct device_node * node,struct device_node * parent)2371eb77c3bSTalel Shenhar static int __init al_fic_init_dt(struct device_node *node,
2381eb77c3bSTalel Shenhar struct device_node *parent)
2391eb77c3bSTalel Shenhar {
2401eb77c3bSTalel Shenhar int ret;
2411eb77c3bSTalel Shenhar void __iomem *base;
2421eb77c3bSTalel Shenhar unsigned int parent_irq;
2431eb77c3bSTalel Shenhar struct al_fic *fic;
2441eb77c3bSTalel Shenhar
2451eb77c3bSTalel Shenhar if (!parent) {
2461eb77c3bSTalel Shenhar pr_err("%s: unsupported - device require a parent\n",
2471eb77c3bSTalel Shenhar node->name);
2481eb77c3bSTalel Shenhar return -EINVAL;
2491eb77c3bSTalel Shenhar }
2501eb77c3bSTalel Shenhar
2511eb77c3bSTalel Shenhar base = of_iomap(node, 0);
2521eb77c3bSTalel Shenhar if (!base) {
2531eb77c3bSTalel Shenhar pr_err("%s: fail to map memory\n", node->name);
2541eb77c3bSTalel Shenhar return -ENOMEM;
2551eb77c3bSTalel Shenhar }
2561eb77c3bSTalel Shenhar
2571eb77c3bSTalel Shenhar parent_irq = irq_of_parse_and_map(node, 0);
2581eb77c3bSTalel Shenhar if (!parent_irq) {
2591eb77c3bSTalel Shenhar pr_err("%s: fail to map irq\n", node->name);
2601eb77c3bSTalel Shenhar ret = -EINVAL;
2611eb77c3bSTalel Shenhar goto err_unmap;
2621eb77c3bSTalel Shenhar }
2631eb77c3bSTalel Shenhar
2641eb77c3bSTalel Shenhar fic = al_fic_wire_init(node,
2651eb77c3bSTalel Shenhar base,
2661eb77c3bSTalel Shenhar node->name,
2671eb77c3bSTalel Shenhar parent_irq);
2681eb77c3bSTalel Shenhar if (IS_ERR(fic)) {
2691eb77c3bSTalel Shenhar pr_err("%s: fail to initialize irqchip (%lu)\n",
2701eb77c3bSTalel Shenhar node->name,
2711eb77c3bSTalel Shenhar PTR_ERR(fic));
2721eb77c3bSTalel Shenhar ret = PTR_ERR(fic);
2731eb77c3bSTalel Shenhar goto err_irq_dispose;
2741eb77c3bSTalel Shenhar }
2751eb77c3bSTalel Shenhar
2761eb77c3bSTalel Shenhar return 0;
2771eb77c3bSTalel Shenhar
2781eb77c3bSTalel Shenhar err_irq_dispose:
2791eb77c3bSTalel Shenhar irq_dispose_mapping(parent_irq);
2801eb77c3bSTalel Shenhar err_unmap:
2811eb77c3bSTalel Shenhar iounmap(base);
2821eb77c3bSTalel Shenhar
2831eb77c3bSTalel Shenhar return ret;
2841eb77c3bSTalel Shenhar }
2851eb77c3bSTalel Shenhar
2861eb77c3bSTalel Shenhar IRQCHIP_DECLARE(al_fic, "amazon,al-fic", al_fic_init_dt);
287