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