xref: /openbmc/linux/drivers/irqchip/irq-mvebu-sei.c (revision c900529f3d9161bfde5cca0754f83b4d3c3e0220)
161ce8d8dSMiquel Raynal // SPDX-License-Identifier: GPL-2.0
261ce8d8dSMiquel Raynal 
361ce8d8dSMiquel Raynal #define pr_fmt(fmt) "mvebu-sei: " fmt
461ce8d8dSMiquel Raynal 
561ce8d8dSMiquel Raynal #include <linux/interrupt.h>
661ce8d8dSMiquel Raynal #include <linux/irq.h>
761ce8d8dSMiquel Raynal #include <linux/irqchip.h>
861ce8d8dSMiquel Raynal #include <linux/irqchip/chained_irq.h>
961ce8d8dSMiquel Raynal #include <linux/irqdomain.h>
1061ce8d8dSMiquel Raynal #include <linux/kernel.h>
1161ce8d8dSMiquel Raynal #include <linux/msi.h>
1261ce8d8dSMiquel Raynal #include <linux/platform_device.h>
1361ce8d8dSMiquel Raynal #include <linux/of_address.h>
1461ce8d8dSMiquel Raynal #include <linux/of_irq.h>
1561ce8d8dSMiquel Raynal #include <linux/of_platform.h>
1661ce8d8dSMiquel Raynal 
1761ce8d8dSMiquel Raynal /* Cause register */
1861ce8d8dSMiquel Raynal #define GICP_SECR(idx)		(0x0  + ((idx) * 0x4))
1961ce8d8dSMiquel Raynal /* Mask register */
2061ce8d8dSMiquel Raynal #define GICP_SEMR(idx)		(0x20 + ((idx) * 0x4))
2161ce8d8dSMiquel Raynal #define GICP_SET_SEI_OFFSET	0x30
2261ce8d8dSMiquel Raynal 
2361ce8d8dSMiquel Raynal #define SEI_IRQ_COUNT_PER_REG	32
2461ce8d8dSMiquel Raynal #define SEI_IRQ_REG_COUNT	2
2561ce8d8dSMiquel Raynal #define SEI_IRQ_COUNT		(SEI_IRQ_COUNT_PER_REG * SEI_IRQ_REG_COUNT)
2661ce8d8dSMiquel Raynal #define SEI_IRQ_REG_IDX(irq_id)	((irq_id) / SEI_IRQ_COUNT_PER_REG)
2761ce8d8dSMiquel Raynal #define SEI_IRQ_REG_BIT(irq_id)	((irq_id) % SEI_IRQ_COUNT_PER_REG)
2861ce8d8dSMiquel Raynal 
2961ce8d8dSMiquel Raynal struct mvebu_sei_interrupt_range {
3061ce8d8dSMiquel Raynal 	u32 first;
3161ce8d8dSMiquel Raynal 	u32 size;
3261ce8d8dSMiquel Raynal };
3361ce8d8dSMiquel Raynal 
3461ce8d8dSMiquel Raynal struct mvebu_sei_caps {
3561ce8d8dSMiquel Raynal 	struct mvebu_sei_interrupt_range ap_range;
3661ce8d8dSMiquel Raynal 	struct mvebu_sei_interrupt_range cp_range;
3761ce8d8dSMiquel Raynal };
3861ce8d8dSMiquel Raynal 
3961ce8d8dSMiquel Raynal struct mvebu_sei {
4061ce8d8dSMiquel Raynal 	struct device *dev;
4161ce8d8dSMiquel Raynal 	void __iomem *base;
4261ce8d8dSMiquel Raynal 	struct resource *res;
4361ce8d8dSMiquel Raynal 	struct irq_domain *sei_domain;
4461ce8d8dSMiquel Raynal 	struct irq_domain *ap_domain;
4561ce8d8dSMiquel Raynal 	struct irq_domain *cp_domain;
4661ce8d8dSMiquel Raynal 	const struct mvebu_sei_caps *caps;
4761ce8d8dSMiquel Raynal 
4861ce8d8dSMiquel Raynal 	/* Lock on MSI allocations/releases */
4961ce8d8dSMiquel Raynal 	struct mutex cp_msi_lock;
5061ce8d8dSMiquel Raynal 	DECLARE_BITMAP(cp_msi_bitmap, SEI_IRQ_COUNT);
5161ce8d8dSMiquel Raynal 
5261ce8d8dSMiquel Raynal 	/* Lock on IRQ masking register */
5361ce8d8dSMiquel Raynal 	raw_spinlock_t mask_lock;
5461ce8d8dSMiquel Raynal };
5561ce8d8dSMiquel Raynal 
mvebu_sei_ack_irq(struct irq_data * d)5661ce8d8dSMiquel Raynal static void mvebu_sei_ack_irq(struct irq_data *d)
5761ce8d8dSMiquel Raynal {
5861ce8d8dSMiquel Raynal 	struct mvebu_sei *sei = irq_data_get_irq_chip_data(d);
5961ce8d8dSMiquel Raynal 	u32 reg_idx = SEI_IRQ_REG_IDX(d->hwirq);
6061ce8d8dSMiquel Raynal 
6161ce8d8dSMiquel Raynal 	writel_relaxed(BIT(SEI_IRQ_REG_BIT(d->hwirq)),
6261ce8d8dSMiquel Raynal 		       sei->base + GICP_SECR(reg_idx));
6361ce8d8dSMiquel Raynal }
6461ce8d8dSMiquel Raynal 
mvebu_sei_mask_irq(struct irq_data * d)6561ce8d8dSMiquel Raynal static void mvebu_sei_mask_irq(struct irq_data *d)
6661ce8d8dSMiquel Raynal {
6761ce8d8dSMiquel Raynal 	struct mvebu_sei *sei = irq_data_get_irq_chip_data(d);
6861ce8d8dSMiquel Raynal 	u32 reg, reg_idx = SEI_IRQ_REG_IDX(d->hwirq);
6961ce8d8dSMiquel Raynal 	unsigned long flags;
7061ce8d8dSMiquel Raynal 
7161ce8d8dSMiquel Raynal 	/* 1 disables the interrupt */
7261ce8d8dSMiquel Raynal 	raw_spin_lock_irqsave(&sei->mask_lock, flags);
7361ce8d8dSMiquel Raynal 	reg = readl_relaxed(sei->base + GICP_SEMR(reg_idx));
7461ce8d8dSMiquel Raynal 	reg |= BIT(SEI_IRQ_REG_BIT(d->hwirq));
7561ce8d8dSMiquel Raynal 	writel_relaxed(reg, sei->base + GICP_SEMR(reg_idx));
7661ce8d8dSMiquel Raynal 	raw_spin_unlock_irqrestore(&sei->mask_lock, flags);
7761ce8d8dSMiquel Raynal }
7861ce8d8dSMiquel Raynal 
mvebu_sei_unmask_irq(struct irq_data * d)7961ce8d8dSMiquel Raynal static void mvebu_sei_unmask_irq(struct irq_data *d)
8061ce8d8dSMiquel Raynal {
8161ce8d8dSMiquel Raynal 	struct mvebu_sei *sei = irq_data_get_irq_chip_data(d);
8261ce8d8dSMiquel Raynal 	u32 reg, reg_idx = SEI_IRQ_REG_IDX(d->hwirq);
8361ce8d8dSMiquel Raynal 	unsigned long flags;
8461ce8d8dSMiquel Raynal 
8561ce8d8dSMiquel Raynal 	/* 0 enables the interrupt */
8661ce8d8dSMiquel Raynal 	raw_spin_lock_irqsave(&sei->mask_lock, flags);
8761ce8d8dSMiquel Raynal 	reg = readl_relaxed(sei->base + GICP_SEMR(reg_idx));
8861ce8d8dSMiquel Raynal 	reg &= ~BIT(SEI_IRQ_REG_BIT(d->hwirq));
8961ce8d8dSMiquel Raynal 	writel_relaxed(reg, sei->base + GICP_SEMR(reg_idx));
9061ce8d8dSMiquel Raynal 	raw_spin_unlock_irqrestore(&sei->mask_lock, flags);
9161ce8d8dSMiquel Raynal }
9261ce8d8dSMiquel Raynal 
mvebu_sei_set_affinity(struct irq_data * d,const struct cpumask * mask_val,bool force)9361ce8d8dSMiquel Raynal static int mvebu_sei_set_affinity(struct irq_data *d,
9461ce8d8dSMiquel Raynal 				  const struct cpumask *mask_val,
9561ce8d8dSMiquel Raynal 				  bool force)
9661ce8d8dSMiquel Raynal {
9761ce8d8dSMiquel Raynal 	return -EINVAL;
9861ce8d8dSMiquel Raynal }
9961ce8d8dSMiquel Raynal 
mvebu_sei_set_irqchip_state(struct irq_data * d,enum irqchip_irq_state which,bool state)10061ce8d8dSMiquel Raynal static int mvebu_sei_set_irqchip_state(struct irq_data *d,
10161ce8d8dSMiquel Raynal 				       enum irqchip_irq_state which,
10261ce8d8dSMiquel Raynal 				       bool state)
10361ce8d8dSMiquel Raynal {
10461ce8d8dSMiquel Raynal 	/* We can only clear the pending state by acking the interrupt */
10561ce8d8dSMiquel Raynal 	if (which != IRQCHIP_STATE_PENDING || state)
10661ce8d8dSMiquel Raynal 		return -EINVAL;
10761ce8d8dSMiquel Raynal 
10861ce8d8dSMiquel Raynal 	mvebu_sei_ack_irq(d);
10961ce8d8dSMiquel Raynal 	return 0;
11061ce8d8dSMiquel Raynal }
11161ce8d8dSMiquel Raynal 
11261ce8d8dSMiquel Raynal static struct irq_chip mvebu_sei_irq_chip = {
11361ce8d8dSMiquel Raynal 	.name			= "SEI",
11461ce8d8dSMiquel Raynal 	.irq_ack		= mvebu_sei_ack_irq,
11561ce8d8dSMiquel Raynal 	.irq_mask		= mvebu_sei_mask_irq,
11661ce8d8dSMiquel Raynal 	.irq_unmask		= mvebu_sei_unmask_irq,
11761ce8d8dSMiquel Raynal 	.irq_set_affinity       = mvebu_sei_set_affinity,
11861ce8d8dSMiquel Raynal 	.irq_set_irqchip_state	= mvebu_sei_set_irqchip_state,
11961ce8d8dSMiquel Raynal };
12061ce8d8dSMiquel Raynal 
mvebu_sei_ap_set_type(struct irq_data * data,unsigned int type)12161ce8d8dSMiquel Raynal static int mvebu_sei_ap_set_type(struct irq_data *data, unsigned int type)
12261ce8d8dSMiquel Raynal {
12361ce8d8dSMiquel Raynal 	if ((type & IRQ_TYPE_SENSE_MASK) != IRQ_TYPE_LEVEL_HIGH)
12461ce8d8dSMiquel Raynal 		return -EINVAL;
12561ce8d8dSMiquel Raynal 
12661ce8d8dSMiquel Raynal 	return 0;
12761ce8d8dSMiquel Raynal }
12861ce8d8dSMiquel Raynal 
12961ce8d8dSMiquel Raynal static struct irq_chip mvebu_sei_ap_irq_chip = {
13061ce8d8dSMiquel Raynal 	.name			= "AP SEI",
13161ce8d8dSMiquel Raynal 	.irq_ack		= irq_chip_ack_parent,
13261ce8d8dSMiquel Raynal 	.irq_mask		= irq_chip_mask_parent,
13361ce8d8dSMiquel Raynal 	.irq_unmask		= irq_chip_unmask_parent,
13461ce8d8dSMiquel Raynal 	.irq_set_affinity       = irq_chip_set_affinity_parent,
13561ce8d8dSMiquel Raynal 	.irq_set_type		= mvebu_sei_ap_set_type,
13661ce8d8dSMiquel Raynal };
13761ce8d8dSMiquel Raynal 
mvebu_sei_cp_compose_msi_msg(struct irq_data * data,struct msi_msg * msg)13861ce8d8dSMiquel Raynal static void mvebu_sei_cp_compose_msi_msg(struct irq_data *data,
13961ce8d8dSMiquel Raynal 					 struct msi_msg *msg)
14061ce8d8dSMiquel Raynal {
14161ce8d8dSMiquel Raynal 	struct mvebu_sei *sei = data->chip_data;
14261ce8d8dSMiquel Raynal 	phys_addr_t set = sei->res->start + GICP_SET_SEI_OFFSET;
14361ce8d8dSMiquel Raynal 
14461ce8d8dSMiquel Raynal 	msg->data = data->hwirq + sei->caps->cp_range.first;
14561ce8d8dSMiquel Raynal 	msg->address_lo = lower_32_bits(set);
14661ce8d8dSMiquel Raynal 	msg->address_hi = upper_32_bits(set);
14761ce8d8dSMiquel Raynal }
14861ce8d8dSMiquel Raynal 
mvebu_sei_cp_set_type(struct irq_data * data,unsigned int type)14961ce8d8dSMiquel Raynal static int mvebu_sei_cp_set_type(struct irq_data *data, unsigned int type)
15061ce8d8dSMiquel Raynal {
15161ce8d8dSMiquel Raynal 	if ((type & IRQ_TYPE_SENSE_MASK) != IRQ_TYPE_EDGE_RISING)
15261ce8d8dSMiquel Raynal 		return -EINVAL;
15361ce8d8dSMiquel Raynal 
15461ce8d8dSMiquel Raynal 	return 0;
15561ce8d8dSMiquel Raynal }
15661ce8d8dSMiquel Raynal 
15761ce8d8dSMiquel Raynal static struct irq_chip mvebu_sei_cp_irq_chip = {
15861ce8d8dSMiquel Raynal 	.name			= "CP SEI",
15961ce8d8dSMiquel Raynal 	.irq_ack		= irq_chip_ack_parent,
16061ce8d8dSMiquel Raynal 	.irq_mask		= irq_chip_mask_parent,
16161ce8d8dSMiquel Raynal 	.irq_unmask		= irq_chip_unmask_parent,
16261ce8d8dSMiquel Raynal 	.irq_set_affinity       = irq_chip_set_affinity_parent,
16361ce8d8dSMiquel Raynal 	.irq_set_type		= mvebu_sei_cp_set_type,
16461ce8d8dSMiquel Raynal 	.irq_compose_msi_msg	= mvebu_sei_cp_compose_msi_msg,
16561ce8d8dSMiquel Raynal };
16661ce8d8dSMiquel Raynal 
mvebu_sei_domain_alloc(struct irq_domain * domain,unsigned int virq,unsigned int nr_irqs,void * arg)16761ce8d8dSMiquel Raynal static int mvebu_sei_domain_alloc(struct irq_domain *domain, unsigned int virq,
16861ce8d8dSMiquel Raynal 				  unsigned int nr_irqs, void *arg)
16961ce8d8dSMiquel Raynal {
17061ce8d8dSMiquel Raynal 	struct mvebu_sei *sei = domain->host_data;
17161ce8d8dSMiquel Raynal 	struct irq_fwspec *fwspec = arg;
17261ce8d8dSMiquel Raynal 
17361ce8d8dSMiquel Raynal 	/* Not much to do, just setup the irqdata */
17461ce8d8dSMiquel Raynal 	irq_domain_set_hwirq_and_chip(domain, virq, fwspec->param[0],
17561ce8d8dSMiquel Raynal 				      &mvebu_sei_irq_chip, sei);
17661ce8d8dSMiquel Raynal 
17761ce8d8dSMiquel Raynal 	return 0;
17861ce8d8dSMiquel Raynal }
17961ce8d8dSMiquel Raynal 
mvebu_sei_domain_free(struct irq_domain * domain,unsigned int virq,unsigned int nr_irqs)18061ce8d8dSMiquel Raynal static void mvebu_sei_domain_free(struct irq_domain *domain, unsigned int virq,
18161ce8d8dSMiquel Raynal 				  unsigned int nr_irqs)
18261ce8d8dSMiquel Raynal {
18361ce8d8dSMiquel Raynal 	int i;
18461ce8d8dSMiquel Raynal 
18561ce8d8dSMiquel Raynal 	for (i = 0; i < nr_irqs; i++) {
18661ce8d8dSMiquel Raynal 		struct irq_data *d = irq_domain_get_irq_data(domain, virq + i);
18761ce8d8dSMiquel Raynal 		irq_set_handler(virq + i, NULL);
18861ce8d8dSMiquel Raynal 		irq_domain_reset_irq_data(d);
18961ce8d8dSMiquel Raynal 	}
19061ce8d8dSMiquel Raynal }
19161ce8d8dSMiquel Raynal 
19261ce8d8dSMiquel Raynal static const struct irq_domain_ops mvebu_sei_domain_ops = {
19361ce8d8dSMiquel Raynal 	.alloc	= mvebu_sei_domain_alloc,
19461ce8d8dSMiquel Raynal 	.free	= mvebu_sei_domain_free,
19561ce8d8dSMiquel Raynal };
19661ce8d8dSMiquel Raynal 
mvebu_sei_ap_translate(struct irq_domain * domain,struct irq_fwspec * fwspec,unsigned long * hwirq,unsigned int * type)19761ce8d8dSMiquel Raynal static int mvebu_sei_ap_translate(struct irq_domain *domain,
19861ce8d8dSMiquel Raynal 				  struct irq_fwspec *fwspec,
19961ce8d8dSMiquel Raynal 				  unsigned long *hwirq,
20061ce8d8dSMiquel Raynal 				  unsigned int *type)
20161ce8d8dSMiquel Raynal {
20261ce8d8dSMiquel Raynal 	*hwirq = fwspec->param[0];
20361ce8d8dSMiquel Raynal 	*type  = IRQ_TYPE_LEVEL_HIGH;
20461ce8d8dSMiquel Raynal 
20561ce8d8dSMiquel Raynal 	return 0;
20661ce8d8dSMiquel Raynal }
20761ce8d8dSMiquel Raynal 
mvebu_sei_ap_alloc(struct irq_domain * domain,unsigned int virq,unsigned int nr_irqs,void * arg)20861ce8d8dSMiquel Raynal static int mvebu_sei_ap_alloc(struct irq_domain *domain, unsigned int virq,
20961ce8d8dSMiquel Raynal 			      unsigned int nr_irqs, void *arg)
21061ce8d8dSMiquel Raynal {
21161ce8d8dSMiquel Raynal 	struct mvebu_sei *sei = domain->host_data;
21261ce8d8dSMiquel Raynal 	struct irq_fwspec fwspec;
21361ce8d8dSMiquel Raynal 	unsigned long hwirq;
21461ce8d8dSMiquel Raynal 	unsigned int type;
21561ce8d8dSMiquel Raynal 	int err;
21661ce8d8dSMiquel Raynal 
21761ce8d8dSMiquel Raynal 	mvebu_sei_ap_translate(domain, arg, &hwirq, &type);
21861ce8d8dSMiquel Raynal 
21961ce8d8dSMiquel Raynal 	fwspec.fwnode = domain->parent->fwnode;
22061ce8d8dSMiquel Raynal 	fwspec.param_count = 1;
22161ce8d8dSMiquel Raynal 	fwspec.param[0] = hwirq + sei->caps->ap_range.first;
22261ce8d8dSMiquel Raynal 
22361ce8d8dSMiquel Raynal 	err = irq_domain_alloc_irqs_parent(domain, virq, 1, &fwspec);
22461ce8d8dSMiquel Raynal 	if (err)
22561ce8d8dSMiquel Raynal 		return err;
22661ce8d8dSMiquel Raynal 
22761ce8d8dSMiquel Raynal 	irq_domain_set_info(domain, virq, hwirq,
22861ce8d8dSMiquel Raynal 			    &mvebu_sei_ap_irq_chip, sei,
22961ce8d8dSMiquel Raynal 			    handle_level_irq, NULL, NULL);
23061ce8d8dSMiquel Raynal 	irq_set_probe(virq);
23161ce8d8dSMiquel Raynal 
23261ce8d8dSMiquel Raynal 	return 0;
23361ce8d8dSMiquel Raynal }
23461ce8d8dSMiquel Raynal 
23561ce8d8dSMiquel Raynal static const struct irq_domain_ops mvebu_sei_ap_domain_ops = {
23661ce8d8dSMiquel Raynal 	.translate	= mvebu_sei_ap_translate,
23761ce8d8dSMiquel Raynal 	.alloc		= mvebu_sei_ap_alloc,
23861ce8d8dSMiquel Raynal 	.free		= irq_domain_free_irqs_parent,
23961ce8d8dSMiquel Raynal };
24061ce8d8dSMiquel Raynal 
mvebu_sei_cp_release_irq(struct mvebu_sei * sei,unsigned long hwirq)24161ce8d8dSMiquel Raynal static void mvebu_sei_cp_release_irq(struct mvebu_sei *sei, unsigned long hwirq)
24261ce8d8dSMiquel Raynal {
24361ce8d8dSMiquel Raynal 	mutex_lock(&sei->cp_msi_lock);
24461ce8d8dSMiquel Raynal 	clear_bit(hwirq, sei->cp_msi_bitmap);
24561ce8d8dSMiquel Raynal 	mutex_unlock(&sei->cp_msi_lock);
24661ce8d8dSMiquel Raynal }
24761ce8d8dSMiquel Raynal 
mvebu_sei_cp_domain_alloc(struct irq_domain * domain,unsigned int virq,unsigned int nr_irqs,void * args)24861ce8d8dSMiquel Raynal static int mvebu_sei_cp_domain_alloc(struct irq_domain *domain,
24961ce8d8dSMiquel Raynal 				     unsigned int virq, unsigned int nr_irqs,
25061ce8d8dSMiquel Raynal 				     void *args)
25161ce8d8dSMiquel Raynal {
25261ce8d8dSMiquel Raynal 	struct mvebu_sei *sei = domain->host_data;
25361ce8d8dSMiquel Raynal 	struct irq_fwspec fwspec;
25461ce8d8dSMiquel Raynal 	unsigned long hwirq;
25561ce8d8dSMiquel Raynal 	int ret;
25661ce8d8dSMiquel Raynal 
25761ce8d8dSMiquel Raynal 	/* The software only supports single allocations for now */
25861ce8d8dSMiquel Raynal 	if (nr_irqs != 1)
25961ce8d8dSMiquel Raynal 		return -ENOTSUPP;
26061ce8d8dSMiquel Raynal 
26161ce8d8dSMiquel Raynal 	mutex_lock(&sei->cp_msi_lock);
26261ce8d8dSMiquel Raynal 	hwirq = find_first_zero_bit(sei->cp_msi_bitmap,
26361ce8d8dSMiquel Raynal 				    sei->caps->cp_range.size);
26461ce8d8dSMiquel Raynal 	if (hwirq < sei->caps->cp_range.size)
26561ce8d8dSMiquel Raynal 		set_bit(hwirq, sei->cp_msi_bitmap);
26661ce8d8dSMiquel Raynal 	mutex_unlock(&sei->cp_msi_lock);
26761ce8d8dSMiquel Raynal 
26861ce8d8dSMiquel Raynal 	if (hwirq == sei->caps->cp_range.size)
26961ce8d8dSMiquel Raynal 		return -ENOSPC;
27061ce8d8dSMiquel Raynal 
27161ce8d8dSMiquel Raynal 	fwspec.fwnode = domain->parent->fwnode;
27261ce8d8dSMiquel Raynal 	fwspec.param_count = 1;
27361ce8d8dSMiquel Raynal 	fwspec.param[0] = hwirq + sei->caps->cp_range.first;
27461ce8d8dSMiquel Raynal 
27561ce8d8dSMiquel Raynal 	ret = irq_domain_alloc_irqs_parent(domain, virq, 1, &fwspec);
27661ce8d8dSMiquel Raynal 	if (ret)
27761ce8d8dSMiquel Raynal 		goto free_irq;
27861ce8d8dSMiquel Raynal 
27961ce8d8dSMiquel Raynal 	irq_domain_set_info(domain, virq, hwirq,
28061ce8d8dSMiquel Raynal 			    &mvebu_sei_cp_irq_chip, sei,
28161ce8d8dSMiquel Raynal 			    handle_edge_irq, NULL, NULL);
28261ce8d8dSMiquel Raynal 
28361ce8d8dSMiquel Raynal 	return 0;
28461ce8d8dSMiquel Raynal 
28561ce8d8dSMiquel Raynal free_irq:
28661ce8d8dSMiquel Raynal 	mvebu_sei_cp_release_irq(sei, hwirq);
28761ce8d8dSMiquel Raynal 	return ret;
28861ce8d8dSMiquel Raynal }
28961ce8d8dSMiquel Raynal 
mvebu_sei_cp_domain_free(struct irq_domain * domain,unsigned int virq,unsigned int nr_irqs)29061ce8d8dSMiquel Raynal static void mvebu_sei_cp_domain_free(struct irq_domain *domain,
29161ce8d8dSMiquel Raynal 				     unsigned int virq, unsigned int nr_irqs)
29261ce8d8dSMiquel Raynal {
29361ce8d8dSMiquel Raynal 	struct mvebu_sei *sei = domain->host_data;
29461ce8d8dSMiquel Raynal 	struct irq_data *d = irq_domain_get_irq_data(domain, virq);
29561ce8d8dSMiquel Raynal 
29661ce8d8dSMiquel Raynal 	if (nr_irqs != 1 || d->hwirq >= sei->caps->cp_range.size) {
29761ce8d8dSMiquel Raynal 		dev_err(sei->dev, "Invalid hwirq %lu\n", d->hwirq);
29861ce8d8dSMiquel Raynal 		return;
29961ce8d8dSMiquel Raynal 	}
30061ce8d8dSMiquel Raynal 
30161ce8d8dSMiquel Raynal 	mvebu_sei_cp_release_irq(sei, d->hwirq);
30261ce8d8dSMiquel Raynal 	irq_domain_free_irqs_parent(domain, virq, 1);
30361ce8d8dSMiquel Raynal }
30461ce8d8dSMiquel Raynal 
30561ce8d8dSMiquel Raynal static const struct irq_domain_ops mvebu_sei_cp_domain_ops = {
30661ce8d8dSMiquel Raynal 	.alloc	= mvebu_sei_cp_domain_alloc,
30761ce8d8dSMiquel Raynal 	.free	= mvebu_sei_cp_domain_free,
30861ce8d8dSMiquel Raynal };
30961ce8d8dSMiquel Raynal 
31061ce8d8dSMiquel Raynal static struct irq_chip mvebu_sei_msi_irq_chip = {
31161ce8d8dSMiquel Raynal 	.name		= "SEI pMSI",
31261ce8d8dSMiquel Raynal 	.irq_ack	= irq_chip_ack_parent,
31361ce8d8dSMiquel Raynal 	.irq_set_type	= irq_chip_set_type_parent,
31461ce8d8dSMiquel Raynal };
31561ce8d8dSMiquel Raynal 
31661ce8d8dSMiquel Raynal static struct msi_domain_ops mvebu_sei_msi_ops = {
31761ce8d8dSMiquel Raynal };
31861ce8d8dSMiquel Raynal 
31961ce8d8dSMiquel Raynal static struct msi_domain_info mvebu_sei_msi_domain_info = {
32061ce8d8dSMiquel Raynal 	.flags	= MSI_FLAG_USE_DEF_DOM_OPS | MSI_FLAG_USE_DEF_CHIP_OPS,
32161ce8d8dSMiquel Raynal 	.ops	= &mvebu_sei_msi_ops,
32261ce8d8dSMiquel Raynal 	.chip	= &mvebu_sei_msi_irq_chip,
32361ce8d8dSMiquel Raynal };
32461ce8d8dSMiquel Raynal 
mvebu_sei_handle_cascade_irq(struct irq_desc * desc)32561ce8d8dSMiquel Raynal static void mvebu_sei_handle_cascade_irq(struct irq_desc *desc)
32661ce8d8dSMiquel Raynal {
32761ce8d8dSMiquel Raynal 	struct mvebu_sei *sei = irq_desc_get_handler_data(desc);
32861ce8d8dSMiquel Raynal 	struct irq_chip *chip = irq_desc_get_chip(desc);
32961ce8d8dSMiquel Raynal 	u32 idx;
33061ce8d8dSMiquel Raynal 
33161ce8d8dSMiquel Raynal 	chained_irq_enter(chip, desc);
33261ce8d8dSMiquel Raynal 
33361ce8d8dSMiquel Raynal 	for (idx = 0; idx < SEI_IRQ_REG_COUNT; idx++) {
33461ce8d8dSMiquel Raynal 		unsigned long irqmap;
33561ce8d8dSMiquel Raynal 		int bit;
33661ce8d8dSMiquel Raynal 
33761ce8d8dSMiquel Raynal 		irqmap = readl_relaxed(sei->base + GICP_SECR(idx));
33861ce8d8dSMiquel Raynal 		for_each_set_bit(bit, &irqmap, SEI_IRQ_COUNT_PER_REG) {
33961ce8d8dSMiquel Raynal 			unsigned long hwirq;
340046a6ee2SMarc Zyngier 			int err;
34161ce8d8dSMiquel Raynal 
34261ce8d8dSMiquel Raynal 			hwirq = idx * SEI_IRQ_COUNT_PER_REG + bit;
343046a6ee2SMarc Zyngier 			err = generic_handle_domain_irq(sei->sei_domain, hwirq);
344046a6ee2SMarc Zyngier 			if (unlikely(err))
345046a6ee2SMarc Zyngier 				dev_warn(sei->dev, "Spurious IRQ detected (hwirq %lu)\n", hwirq);
34661ce8d8dSMiquel Raynal 		}
34761ce8d8dSMiquel Raynal 	}
34861ce8d8dSMiquel Raynal 
34961ce8d8dSMiquel Raynal 	chained_irq_exit(chip, desc);
35061ce8d8dSMiquel Raynal }
35161ce8d8dSMiquel Raynal 
mvebu_sei_reset(struct mvebu_sei * sei)35261ce8d8dSMiquel Raynal static void mvebu_sei_reset(struct mvebu_sei *sei)
35361ce8d8dSMiquel Raynal {
35461ce8d8dSMiquel Raynal 	u32 reg_idx;
35561ce8d8dSMiquel Raynal 
35661ce8d8dSMiquel Raynal 	/* Clear IRQ cause registers, mask all interrupts */
35761ce8d8dSMiquel Raynal 	for (reg_idx = 0; reg_idx < SEI_IRQ_REG_COUNT; reg_idx++) {
35861ce8d8dSMiquel Raynal 		writel_relaxed(0xFFFFFFFF, sei->base + GICP_SECR(reg_idx));
35961ce8d8dSMiquel Raynal 		writel_relaxed(0xFFFFFFFF, sei->base + GICP_SEMR(reg_idx));
36061ce8d8dSMiquel Raynal 	}
36161ce8d8dSMiquel Raynal }
36261ce8d8dSMiquel Raynal 
mvebu_sei_probe(struct platform_device * pdev)36361ce8d8dSMiquel Raynal static int mvebu_sei_probe(struct platform_device *pdev)
36461ce8d8dSMiquel Raynal {
36561ce8d8dSMiquel Raynal 	struct device_node *node = pdev->dev.of_node;
36661ce8d8dSMiquel Raynal 	struct irq_domain *plat_domain;
36761ce8d8dSMiquel Raynal 	struct mvebu_sei *sei;
36861ce8d8dSMiquel Raynal 	u32 parent_irq;
36961ce8d8dSMiquel Raynal 	int ret;
37061ce8d8dSMiquel Raynal 
37161ce8d8dSMiquel Raynal 	sei = devm_kzalloc(&pdev->dev, sizeof(*sei), GFP_KERNEL);
37261ce8d8dSMiquel Raynal 	if (!sei)
37361ce8d8dSMiquel Raynal 		return -ENOMEM;
37461ce8d8dSMiquel Raynal 
37561ce8d8dSMiquel Raynal 	sei->dev = &pdev->dev;
37661ce8d8dSMiquel Raynal 
37761ce8d8dSMiquel Raynal 	mutex_init(&sei->cp_msi_lock);
37861ce8d8dSMiquel Raynal 	raw_spin_lock_init(&sei->mask_lock);
37961ce8d8dSMiquel Raynal 
380*69da32b5SYangtao Li 	sei->base = devm_platform_get_and_ioremap_resource(pdev, 0, &sei->res);
381fbb80d5aSZhen Lei 	if (IS_ERR(sei->base))
3823424243eSDan Carpenter 		return PTR_ERR(sei->base);
38361ce8d8dSMiquel Raynal 
38461ce8d8dSMiquel Raynal 	/* Retrieve the SEI capabilities with the interrupt ranges */
38561ce8d8dSMiquel Raynal 	sei->caps = of_device_get_match_data(&pdev->dev);
38661ce8d8dSMiquel Raynal 	if (!sei->caps) {
38761ce8d8dSMiquel Raynal 		dev_err(sei->dev,
38861ce8d8dSMiquel Raynal 			"Could not retrieve controller capabilities\n");
38961ce8d8dSMiquel Raynal 		return -EINVAL;
39061ce8d8dSMiquel Raynal 	}
39161ce8d8dSMiquel Raynal 
39261ce8d8dSMiquel Raynal 	/*
39361ce8d8dSMiquel Raynal 	 * Reserve the single (top-level) parent SPI IRQ from which all the
39461ce8d8dSMiquel Raynal 	 * interrupts handled by this driver will be signaled.
39561ce8d8dSMiquel Raynal 	 */
39661ce8d8dSMiquel Raynal 	parent_irq = irq_of_parse_and_map(node, 0);
39761ce8d8dSMiquel Raynal 	if (parent_irq <= 0) {
39861ce8d8dSMiquel Raynal 		dev_err(sei->dev, "Failed to retrieve top-level SPI IRQ\n");
39961ce8d8dSMiquel Raynal 		return -ENODEV;
40061ce8d8dSMiquel Raynal 	}
40161ce8d8dSMiquel Raynal 
40261ce8d8dSMiquel Raynal 	/* Create the root SEI domain */
40361ce8d8dSMiquel Raynal 	sei->sei_domain = irq_domain_create_linear(of_node_to_fwnode(node),
40461ce8d8dSMiquel Raynal 						   (sei->caps->ap_range.size +
40561ce8d8dSMiquel Raynal 						    sei->caps->cp_range.size),
40661ce8d8dSMiquel Raynal 						   &mvebu_sei_domain_ops,
40761ce8d8dSMiquel Raynal 						   sei);
40861ce8d8dSMiquel Raynal 	if (!sei->sei_domain) {
40961ce8d8dSMiquel Raynal 		dev_err(sei->dev, "Failed to create SEI IRQ domain\n");
41061ce8d8dSMiquel Raynal 		ret = -ENOMEM;
41161ce8d8dSMiquel Raynal 		goto dispose_irq;
41261ce8d8dSMiquel Raynal 	}
41361ce8d8dSMiquel Raynal 
41461ce8d8dSMiquel Raynal 	irq_domain_update_bus_token(sei->sei_domain, DOMAIN_BUS_NEXUS);
41561ce8d8dSMiquel Raynal 
41661ce8d8dSMiquel Raynal 	/* Create the 'wired' domain */
41761ce8d8dSMiquel Raynal 	sei->ap_domain = irq_domain_create_hierarchy(sei->sei_domain, 0,
41861ce8d8dSMiquel Raynal 						     sei->caps->ap_range.size,
41961ce8d8dSMiquel Raynal 						     of_node_to_fwnode(node),
42061ce8d8dSMiquel Raynal 						     &mvebu_sei_ap_domain_ops,
42161ce8d8dSMiquel Raynal 						     sei);
42261ce8d8dSMiquel Raynal 	if (!sei->ap_domain) {
42361ce8d8dSMiquel Raynal 		dev_err(sei->dev, "Failed to create AP IRQ domain\n");
42461ce8d8dSMiquel Raynal 		ret = -ENOMEM;
42561ce8d8dSMiquel Raynal 		goto remove_sei_domain;
42661ce8d8dSMiquel Raynal 	}
42761ce8d8dSMiquel Raynal 
42861ce8d8dSMiquel Raynal 	irq_domain_update_bus_token(sei->ap_domain, DOMAIN_BUS_WIRED);
42961ce8d8dSMiquel Raynal 
43061ce8d8dSMiquel Raynal 	/* Create the 'MSI' domain */
43161ce8d8dSMiquel Raynal 	sei->cp_domain = irq_domain_create_hierarchy(sei->sei_domain, 0,
43261ce8d8dSMiquel Raynal 						     sei->caps->cp_range.size,
43361ce8d8dSMiquel Raynal 						     of_node_to_fwnode(node),
43461ce8d8dSMiquel Raynal 						     &mvebu_sei_cp_domain_ops,
43561ce8d8dSMiquel Raynal 						     sei);
43661ce8d8dSMiquel Raynal 	if (!sei->cp_domain) {
43761ce8d8dSMiquel Raynal 		pr_err("Failed to create CPs IRQ domain\n");
43861ce8d8dSMiquel Raynal 		ret = -ENOMEM;
43961ce8d8dSMiquel Raynal 		goto remove_ap_domain;
44061ce8d8dSMiquel Raynal 	}
44161ce8d8dSMiquel Raynal 
44261ce8d8dSMiquel Raynal 	irq_domain_update_bus_token(sei->cp_domain, DOMAIN_BUS_GENERIC_MSI);
44361ce8d8dSMiquel Raynal 
44461ce8d8dSMiquel Raynal 	plat_domain = platform_msi_create_irq_domain(of_node_to_fwnode(node),
44561ce8d8dSMiquel Raynal 						     &mvebu_sei_msi_domain_info,
44661ce8d8dSMiquel Raynal 						     sei->cp_domain);
44761ce8d8dSMiquel Raynal 	if (!plat_domain) {
44861ce8d8dSMiquel Raynal 		pr_err("Failed to create CPs MSI domain\n");
44961ce8d8dSMiquel Raynal 		ret = -ENOMEM;
45061ce8d8dSMiquel Raynal 		goto remove_cp_domain;
45161ce8d8dSMiquel Raynal 	}
45261ce8d8dSMiquel Raynal 
45361ce8d8dSMiquel Raynal 	mvebu_sei_reset(sei);
45461ce8d8dSMiquel Raynal 
45561ce8d8dSMiquel Raynal 	irq_set_chained_handler_and_data(parent_irq,
45661ce8d8dSMiquel Raynal 					 mvebu_sei_handle_cascade_irq,
45761ce8d8dSMiquel Raynal 					 sei);
45861ce8d8dSMiquel Raynal 
45961ce8d8dSMiquel Raynal 	return 0;
46061ce8d8dSMiquel Raynal 
46161ce8d8dSMiquel Raynal remove_cp_domain:
46261ce8d8dSMiquel Raynal 	irq_domain_remove(sei->cp_domain);
46361ce8d8dSMiquel Raynal remove_ap_domain:
46461ce8d8dSMiquel Raynal 	irq_domain_remove(sei->ap_domain);
46561ce8d8dSMiquel Raynal remove_sei_domain:
46661ce8d8dSMiquel Raynal 	irq_domain_remove(sei->sei_domain);
46761ce8d8dSMiquel Raynal dispose_irq:
46861ce8d8dSMiquel Raynal 	irq_dispose_mapping(parent_irq);
46961ce8d8dSMiquel Raynal 
47061ce8d8dSMiquel Raynal 	return ret;
47161ce8d8dSMiquel Raynal }
47261ce8d8dSMiquel Raynal 
473f27b744bSYueHaibing static struct mvebu_sei_caps mvebu_sei_ap806_caps = {
47461ce8d8dSMiquel Raynal 	.ap_range = {
47561ce8d8dSMiquel Raynal 		.first = 0,
47661ce8d8dSMiquel Raynal 		.size = 21,
47761ce8d8dSMiquel Raynal 	},
47861ce8d8dSMiquel Raynal 	.cp_range = {
47961ce8d8dSMiquel Raynal 		.first = 21,
48061ce8d8dSMiquel Raynal 		.size = 43,
48161ce8d8dSMiquel Raynal 	},
48261ce8d8dSMiquel Raynal };
48361ce8d8dSMiquel Raynal 
48461ce8d8dSMiquel Raynal static const struct of_device_id mvebu_sei_of_match[] = {
48561ce8d8dSMiquel Raynal 	{
48661ce8d8dSMiquel Raynal 		.compatible = "marvell,ap806-sei",
48761ce8d8dSMiquel Raynal 		.data = &mvebu_sei_ap806_caps,
48861ce8d8dSMiquel Raynal 	},
48961ce8d8dSMiquel Raynal 	{},
49061ce8d8dSMiquel Raynal };
49161ce8d8dSMiquel Raynal 
49261ce8d8dSMiquel Raynal static struct platform_driver mvebu_sei_driver = {
49361ce8d8dSMiquel Raynal 	.probe  = mvebu_sei_probe,
49461ce8d8dSMiquel Raynal 	.driver = {
49561ce8d8dSMiquel Raynal 		.name = "mvebu-sei",
49661ce8d8dSMiquel Raynal 		.of_match_table = mvebu_sei_of_match,
49761ce8d8dSMiquel Raynal 	},
49861ce8d8dSMiquel Raynal };
49961ce8d8dSMiquel Raynal builtin_platform_driver(mvebu_sei_driver);
500