xref: /openbmc/linux/drivers/irqchip/irq-al-fic.c (revision 1ac731c529cd4d6adbce134754b51ff7d822b145)
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