18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
28c2ecf20Sopenharmony_ci
38c2ecf20Sopenharmony_ci#define pr_fmt(fmt) "mvebu-sei: " fmt
48c2ecf20Sopenharmony_ci
58c2ecf20Sopenharmony_ci#include <linux/interrupt.h>
68c2ecf20Sopenharmony_ci#include <linux/irq.h>
78c2ecf20Sopenharmony_ci#include <linux/irqchip.h>
88c2ecf20Sopenharmony_ci#include <linux/irqchip/chained_irq.h>
98c2ecf20Sopenharmony_ci#include <linux/irqdomain.h>
108c2ecf20Sopenharmony_ci#include <linux/kernel.h>
118c2ecf20Sopenharmony_ci#include <linux/msi.h>
128c2ecf20Sopenharmony_ci#include <linux/platform_device.h>
138c2ecf20Sopenharmony_ci#include <linux/of_address.h>
148c2ecf20Sopenharmony_ci#include <linux/of_irq.h>
158c2ecf20Sopenharmony_ci#include <linux/of_platform.h>
168c2ecf20Sopenharmony_ci
178c2ecf20Sopenharmony_ci/* Cause register */
188c2ecf20Sopenharmony_ci#define GICP_SECR(idx)		(0x0  + ((idx) * 0x4))
198c2ecf20Sopenharmony_ci/* Mask register */
208c2ecf20Sopenharmony_ci#define GICP_SEMR(idx)		(0x20 + ((idx) * 0x4))
218c2ecf20Sopenharmony_ci#define GICP_SET_SEI_OFFSET	0x30
228c2ecf20Sopenharmony_ci
238c2ecf20Sopenharmony_ci#define SEI_IRQ_COUNT_PER_REG	32
248c2ecf20Sopenharmony_ci#define SEI_IRQ_REG_COUNT	2
258c2ecf20Sopenharmony_ci#define SEI_IRQ_COUNT		(SEI_IRQ_COUNT_PER_REG * SEI_IRQ_REG_COUNT)
268c2ecf20Sopenharmony_ci#define SEI_IRQ_REG_IDX(irq_id)	((irq_id) / SEI_IRQ_COUNT_PER_REG)
278c2ecf20Sopenharmony_ci#define SEI_IRQ_REG_BIT(irq_id)	((irq_id) % SEI_IRQ_COUNT_PER_REG)
288c2ecf20Sopenharmony_ci
298c2ecf20Sopenharmony_cistruct mvebu_sei_interrupt_range {
308c2ecf20Sopenharmony_ci	u32 first;
318c2ecf20Sopenharmony_ci	u32 size;
328c2ecf20Sopenharmony_ci};
338c2ecf20Sopenharmony_ci
348c2ecf20Sopenharmony_cistruct mvebu_sei_caps {
358c2ecf20Sopenharmony_ci	struct mvebu_sei_interrupt_range ap_range;
368c2ecf20Sopenharmony_ci	struct mvebu_sei_interrupt_range cp_range;
378c2ecf20Sopenharmony_ci};
388c2ecf20Sopenharmony_ci
398c2ecf20Sopenharmony_cistruct mvebu_sei {
408c2ecf20Sopenharmony_ci	struct device *dev;
418c2ecf20Sopenharmony_ci	void __iomem *base;
428c2ecf20Sopenharmony_ci	struct resource *res;
438c2ecf20Sopenharmony_ci	struct irq_domain *sei_domain;
448c2ecf20Sopenharmony_ci	struct irq_domain *ap_domain;
458c2ecf20Sopenharmony_ci	struct irq_domain *cp_domain;
468c2ecf20Sopenharmony_ci	const struct mvebu_sei_caps *caps;
478c2ecf20Sopenharmony_ci
488c2ecf20Sopenharmony_ci	/* Lock on MSI allocations/releases */
498c2ecf20Sopenharmony_ci	struct mutex cp_msi_lock;
508c2ecf20Sopenharmony_ci	DECLARE_BITMAP(cp_msi_bitmap, SEI_IRQ_COUNT);
518c2ecf20Sopenharmony_ci
528c2ecf20Sopenharmony_ci	/* Lock on IRQ masking register */
538c2ecf20Sopenharmony_ci	raw_spinlock_t mask_lock;
548c2ecf20Sopenharmony_ci};
558c2ecf20Sopenharmony_ci
568c2ecf20Sopenharmony_cistatic void mvebu_sei_ack_irq(struct irq_data *d)
578c2ecf20Sopenharmony_ci{
588c2ecf20Sopenharmony_ci	struct mvebu_sei *sei = irq_data_get_irq_chip_data(d);
598c2ecf20Sopenharmony_ci	u32 reg_idx = SEI_IRQ_REG_IDX(d->hwirq);
608c2ecf20Sopenharmony_ci
618c2ecf20Sopenharmony_ci	writel_relaxed(BIT(SEI_IRQ_REG_BIT(d->hwirq)),
628c2ecf20Sopenharmony_ci		       sei->base + GICP_SECR(reg_idx));
638c2ecf20Sopenharmony_ci}
648c2ecf20Sopenharmony_ci
658c2ecf20Sopenharmony_cistatic void mvebu_sei_mask_irq(struct irq_data *d)
668c2ecf20Sopenharmony_ci{
678c2ecf20Sopenharmony_ci	struct mvebu_sei *sei = irq_data_get_irq_chip_data(d);
688c2ecf20Sopenharmony_ci	u32 reg, reg_idx = SEI_IRQ_REG_IDX(d->hwirq);
698c2ecf20Sopenharmony_ci	unsigned long flags;
708c2ecf20Sopenharmony_ci
718c2ecf20Sopenharmony_ci	/* 1 disables the interrupt */
728c2ecf20Sopenharmony_ci	raw_spin_lock_irqsave(&sei->mask_lock, flags);
738c2ecf20Sopenharmony_ci	reg = readl_relaxed(sei->base + GICP_SEMR(reg_idx));
748c2ecf20Sopenharmony_ci	reg |= BIT(SEI_IRQ_REG_BIT(d->hwirq));
758c2ecf20Sopenharmony_ci	writel_relaxed(reg, sei->base + GICP_SEMR(reg_idx));
768c2ecf20Sopenharmony_ci	raw_spin_unlock_irqrestore(&sei->mask_lock, flags);
778c2ecf20Sopenharmony_ci}
788c2ecf20Sopenharmony_ci
798c2ecf20Sopenharmony_cistatic void mvebu_sei_unmask_irq(struct irq_data *d)
808c2ecf20Sopenharmony_ci{
818c2ecf20Sopenharmony_ci	struct mvebu_sei *sei = irq_data_get_irq_chip_data(d);
828c2ecf20Sopenharmony_ci	u32 reg, reg_idx = SEI_IRQ_REG_IDX(d->hwirq);
838c2ecf20Sopenharmony_ci	unsigned long flags;
848c2ecf20Sopenharmony_ci
858c2ecf20Sopenharmony_ci	/* 0 enables the interrupt */
868c2ecf20Sopenharmony_ci	raw_spin_lock_irqsave(&sei->mask_lock, flags);
878c2ecf20Sopenharmony_ci	reg = readl_relaxed(sei->base + GICP_SEMR(reg_idx));
888c2ecf20Sopenharmony_ci	reg &= ~BIT(SEI_IRQ_REG_BIT(d->hwirq));
898c2ecf20Sopenharmony_ci	writel_relaxed(reg, sei->base + GICP_SEMR(reg_idx));
908c2ecf20Sopenharmony_ci	raw_spin_unlock_irqrestore(&sei->mask_lock, flags);
918c2ecf20Sopenharmony_ci}
928c2ecf20Sopenharmony_ci
938c2ecf20Sopenharmony_cistatic int mvebu_sei_set_affinity(struct irq_data *d,
948c2ecf20Sopenharmony_ci				  const struct cpumask *mask_val,
958c2ecf20Sopenharmony_ci				  bool force)
968c2ecf20Sopenharmony_ci{
978c2ecf20Sopenharmony_ci	return -EINVAL;
988c2ecf20Sopenharmony_ci}
998c2ecf20Sopenharmony_ci
1008c2ecf20Sopenharmony_cistatic int mvebu_sei_set_irqchip_state(struct irq_data *d,
1018c2ecf20Sopenharmony_ci				       enum irqchip_irq_state which,
1028c2ecf20Sopenharmony_ci				       bool state)
1038c2ecf20Sopenharmony_ci{
1048c2ecf20Sopenharmony_ci	/* We can only clear the pending state by acking the interrupt */
1058c2ecf20Sopenharmony_ci	if (which != IRQCHIP_STATE_PENDING || state)
1068c2ecf20Sopenharmony_ci		return -EINVAL;
1078c2ecf20Sopenharmony_ci
1088c2ecf20Sopenharmony_ci	mvebu_sei_ack_irq(d);
1098c2ecf20Sopenharmony_ci	return 0;
1108c2ecf20Sopenharmony_ci}
1118c2ecf20Sopenharmony_ci
1128c2ecf20Sopenharmony_cistatic struct irq_chip mvebu_sei_irq_chip = {
1138c2ecf20Sopenharmony_ci	.name			= "SEI",
1148c2ecf20Sopenharmony_ci	.irq_ack		= mvebu_sei_ack_irq,
1158c2ecf20Sopenharmony_ci	.irq_mask		= mvebu_sei_mask_irq,
1168c2ecf20Sopenharmony_ci	.irq_unmask		= mvebu_sei_unmask_irq,
1178c2ecf20Sopenharmony_ci	.irq_set_affinity       = mvebu_sei_set_affinity,
1188c2ecf20Sopenharmony_ci	.irq_set_irqchip_state	= mvebu_sei_set_irqchip_state,
1198c2ecf20Sopenharmony_ci};
1208c2ecf20Sopenharmony_ci
1218c2ecf20Sopenharmony_cistatic int mvebu_sei_ap_set_type(struct irq_data *data, unsigned int type)
1228c2ecf20Sopenharmony_ci{
1238c2ecf20Sopenharmony_ci	if ((type & IRQ_TYPE_SENSE_MASK) != IRQ_TYPE_LEVEL_HIGH)
1248c2ecf20Sopenharmony_ci		return -EINVAL;
1258c2ecf20Sopenharmony_ci
1268c2ecf20Sopenharmony_ci	return 0;
1278c2ecf20Sopenharmony_ci}
1288c2ecf20Sopenharmony_ci
1298c2ecf20Sopenharmony_cistatic struct irq_chip mvebu_sei_ap_irq_chip = {
1308c2ecf20Sopenharmony_ci	.name			= "AP SEI",
1318c2ecf20Sopenharmony_ci	.irq_ack		= irq_chip_ack_parent,
1328c2ecf20Sopenharmony_ci	.irq_mask		= irq_chip_mask_parent,
1338c2ecf20Sopenharmony_ci	.irq_unmask		= irq_chip_unmask_parent,
1348c2ecf20Sopenharmony_ci	.irq_set_affinity       = irq_chip_set_affinity_parent,
1358c2ecf20Sopenharmony_ci	.irq_set_type		= mvebu_sei_ap_set_type,
1368c2ecf20Sopenharmony_ci};
1378c2ecf20Sopenharmony_ci
1388c2ecf20Sopenharmony_cistatic void mvebu_sei_cp_compose_msi_msg(struct irq_data *data,
1398c2ecf20Sopenharmony_ci					 struct msi_msg *msg)
1408c2ecf20Sopenharmony_ci{
1418c2ecf20Sopenharmony_ci	struct mvebu_sei *sei = data->chip_data;
1428c2ecf20Sopenharmony_ci	phys_addr_t set = sei->res->start + GICP_SET_SEI_OFFSET;
1438c2ecf20Sopenharmony_ci
1448c2ecf20Sopenharmony_ci	msg->data = data->hwirq + sei->caps->cp_range.first;
1458c2ecf20Sopenharmony_ci	msg->address_lo = lower_32_bits(set);
1468c2ecf20Sopenharmony_ci	msg->address_hi = upper_32_bits(set);
1478c2ecf20Sopenharmony_ci}
1488c2ecf20Sopenharmony_ci
1498c2ecf20Sopenharmony_cistatic int mvebu_sei_cp_set_type(struct irq_data *data, unsigned int type)
1508c2ecf20Sopenharmony_ci{
1518c2ecf20Sopenharmony_ci	if ((type & IRQ_TYPE_SENSE_MASK) != IRQ_TYPE_EDGE_RISING)
1528c2ecf20Sopenharmony_ci		return -EINVAL;
1538c2ecf20Sopenharmony_ci
1548c2ecf20Sopenharmony_ci	return 0;
1558c2ecf20Sopenharmony_ci}
1568c2ecf20Sopenharmony_ci
1578c2ecf20Sopenharmony_cistatic struct irq_chip mvebu_sei_cp_irq_chip = {
1588c2ecf20Sopenharmony_ci	.name			= "CP SEI",
1598c2ecf20Sopenharmony_ci	.irq_ack		= irq_chip_ack_parent,
1608c2ecf20Sopenharmony_ci	.irq_mask		= irq_chip_mask_parent,
1618c2ecf20Sopenharmony_ci	.irq_unmask		= irq_chip_unmask_parent,
1628c2ecf20Sopenharmony_ci	.irq_set_affinity       = irq_chip_set_affinity_parent,
1638c2ecf20Sopenharmony_ci	.irq_set_type		= mvebu_sei_cp_set_type,
1648c2ecf20Sopenharmony_ci	.irq_compose_msi_msg	= mvebu_sei_cp_compose_msi_msg,
1658c2ecf20Sopenharmony_ci};
1668c2ecf20Sopenharmony_ci
1678c2ecf20Sopenharmony_cistatic int mvebu_sei_domain_alloc(struct irq_domain *domain, unsigned int virq,
1688c2ecf20Sopenharmony_ci				  unsigned int nr_irqs, void *arg)
1698c2ecf20Sopenharmony_ci{
1708c2ecf20Sopenharmony_ci	struct mvebu_sei *sei = domain->host_data;
1718c2ecf20Sopenharmony_ci	struct irq_fwspec *fwspec = arg;
1728c2ecf20Sopenharmony_ci
1738c2ecf20Sopenharmony_ci	/* Not much to do, just setup the irqdata */
1748c2ecf20Sopenharmony_ci	irq_domain_set_hwirq_and_chip(domain, virq, fwspec->param[0],
1758c2ecf20Sopenharmony_ci				      &mvebu_sei_irq_chip, sei);
1768c2ecf20Sopenharmony_ci
1778c2ecf20Sopenharmony_ci	return 0;
1788c2ecf20Sopenharmony_ci}
1798c2ecf20Sopenharmony_ci
1808c2ecf20Sopenharmony_cistatic void mvebu_sei_domain_free(struct irq_domain *domain, unsigned int virq,
1818c2ecf20Sopenharmony_ci				  unsigned int nr_irqs)
1828c2ecf20Sopenharmony_ci{
1838c2ecf20Sopenharmony_ci	int i;
1848c2ecf20Sopenharmony_ci
1858c2ecf20Sopenharmony_ci	for (i = 0; i < nr_irqs; i++) {
1868c2ecf20Sopenharmony_ci		struct irq_data *d = irq_domain_get_irq_data(domain, virq + i);
1878c2ecf20Sopenharmony_ci		irq_set_handler(virq + i, NULL);
1888c2ecf20Sopenharmony_ci		irq_domain_reset_irq_data(d);
1898c2ecf20Sopenharmony_ci	}
1908c2ecf20Sopenharmony_ci}
1918c2ecf20Sopenharmony_ci
1928c2ecf20Sopenharmony_cistatic const struct irq_domain_ops mvebu_sei_domain_ops = {
1938c2ecf20Sopenharmony_ci	.alloc	= mvebu_sei_domain_alloc,
1948c2ecf20Sopenharmony_ci	.free	= mvebu_sei_domain_free,
1958c2ecf20Sopenharmony_ci};
1968c2ecf20Sopenharmony_ci
1978c2ecf20Sopenharmony_cistatic int mvebu_sei_ap_translate(struct irq_domain *domain,
1988c2ecf20Sopenharmony_ci				  struct irq_fwspec *fwspec,
1998c2ecf20Sopenharmony_ci				  unsigned long *hwirq,
2008c2ecf20Sopenharmony_ci				  unsigned int *type)
2018c2ecf20Sopenharmony_ci{
2028c2ecf20Sopenharmony_ci	*hwirq = fwspec->param[0];
2038c2ecf20Sopenharmony_ci	*type  = IRQ_TYPE_LEVEL_HIGH;
2048c2ecf20Sopenharmony_ci
2058c2ecf20Sopenharmony_ci	return 0;
2068c2ecf20Sopenharmony_ci}
2078c2ecf20Sopenharmony_ci
2088c2ecf20Sopenharmony_cistatic int mvebu_sei_ap_alloc(struct irq_domain *domain, unsigned int virq,
2098c2ecf20Sopenharmony_ci			      unsigned int nr_irqs, void *arg)
2108c2ecf20Sopenharmony_ci{
2118c2ecf20Sopenharmony_ci	struct mvebu_sei *sei = domain->host_data;
2128c2ecf20Sopenharmony_ci	struct irq_fwspec fwspec;
2138c2ecf20Sopenharmony_ci	unsigned long hwirq;
2148c2ecf20Sopenharmony_ci	unsigned int type;
2158c2ecf20Sopenharmony_ci	int err;
2168c2ecf20Sopenharmony_ci
2178c2ecf20Sopenharmony_ci	mvebu_sei_ap_translate(domain, arg, &hwirq, &type);
2188c2ecf20Sopenharmony_ci
2198c2ecf20Sopenharmony_ci	fwspec.fwnode = domain->parent->fwnode;
2208c2ecf20Sopenharmony_ci	fwspec.param_count = 1;
2218c2ecf20Sopenharmony_ci	fwspec.param[0] = hwirq + sei->caps->ap_range.first;
2228c2ecf20Sopenharmony_ci
2238c2ecf20Sopenharmony_ci	err = irq_domain_alloc_irqs_parent(domain, virq, 1, &fwspec);
2248c2ecf20Sopenharmony_ci	if (err)
2258c2ecf20Sopenharmony_ci		return err;
2268c2ecf20Sopenharmony_ci
2278c2ecf20Sopenharmony_ci	irq_domain_set_info(domain, virq, hwirq,
2288c2ecf20Sopenharmony_ci			    &mvebu_sei_ap_irq_chip, sei,
2298c2ecf20Sopenharmony_ci			    handle_level_irq, NULL, NULL);
2308c2ecf20Sopenharmony_ci	irq_set_probe(virq);
2318c2ecf20Sopenharmony_ci
2328c2ecf20Sopenharmony_ci	return 0;
2338c2ecf20Sopenharmony_ci}
2348c2ecf20Sopenharmony_ci
2358c2ecf20Sopenharmony_cistatic const struct irq_domain_ops mvebu_sei_ap_domain_ops = {
2368c2ecf20Sopenharmony_ci	.translate	= mvebu_sei_ap_translate,
2378c2ecf20Sopenharmony_ci	.alloc		= mvebu_sei_ap_alloc,
2388c2ecf20Sopenharmony_ci	.free		= irq_domain_free_irqs_parent,
2398c2ecf20Sopenharmony_ci};
2408c2ecf20Sopenharmony_ci
2418c2ecf20Sopenharmony_cistatic void mvebu_sei_cp_release_irq(struct mvebu_sei *sei, unsigned long hwirq)
2428c2ecf20Sopenharmony_ci{
2438c2ecf20Sopenharmony_ci	mutex_lock(&sei->cp_msi_lock);
2448c2ecf20Sopenharmony_ci	clear_bit(hwirq, sei->cp_msi_bitmap);
2458c2ecf20Sopenharmony_ci	mutex_unlock(&sei->cp_msi_lock);
2468c2ecf20Sopenharmony_ci}
2478c2ecf20Sopenharmony_ci
2488c2ecf20Sopenharmony_cistatic int mvebu_sei_cp_domain_alloc(struct irq_domain *domain,
2498c2ecf20Sopenharmony_ci				     unsigned int virq, unsigned int nr_irqs,
2508c2ecf20Sopenharmony_ci				     void *args)
2518c2ecf20Sopenharmony_ci{
2528c2ecf20Sopenharmony_ci	struct mvebu_sei *sei = domain->host_data;
2538c2ecf20Sopenharmony_ci	struct irq_fwspec fwspec;
2548c2ecf20Sopenharmony_ci	unsigned long hwirq;
2558c2ecf20Sopenharmony_ci	int ret;
2568c2ecf20Sopenharmony_ci
2578c2ecf20Sopenharmony_ci	/* The software only supports single allocations for now */
2588c2ecf20Sopenharmony_ci	if (nr_irqs != 1)
2598c2ecf20Sopenharmony_ci		return -ENOTSUPP;
2608c2ecf20Sopenharmony_ci
2618c2ecf20Sopenharmony_ci	mutex_lock(&sei->cp_msi_lock);
2628c2ecf20Sopenharmony_ci	hwirq = find_first_zero_bit(sei->cp_msi_bitmap,
2638c2ecf20Sopenharmony_ci				    sei->caps->cp_range.size);
2648c2ecf20Sopenharmony_ci	if (hwirq < sei->caps->cp_range.size)
2658c2ecf20Sopenharmony_ci		set_bit(hwirq, sei->cp_msi_bitmap);
2668c2ecf20Sopenharmony_ci	mutex_unlock(&sei->cp_msi_lock);
2678c2ecf20Sopenharmony_ci
2688c2ecf20Sopenharmony_ci	if (hwirq == sei->caps->cp_range.size)
2698c2ecf20Sopenharmony_ci		return -ENOSPC;
2708c2ecf20Sopenharmony_ci
2718c2ecf20Sopenharmony_ci	fwspec.fwnode = domain->parent->fwnode;
2728c2ecf20Sopenharmony_ci	fwspec.param_count = 1;
2738c2ecf20Sopenharmony_ci	fwspec.param[0] = hwirq + sei->caps->cp_range.first;
2748c2ecf20Sopenharmony_ci
2758c2ecf20Sopenharmony_ci	ret = irq_domain_alloc_irqs_parent(domain, virq, 1, &fwspec);
2768c2ecf20Sopenharmony_ci	if (ret)
2778c2ecf20Sopenharmony_ci		goto free_irq;
2788c2ecf20Sopenharmony_ci
2798c2ecf20Sopenharmony_ci	irq_domain_set_info(domain, virq, hwirq,
2808c2ecf20Sopenharmony_ci			    &mvebu_sei_cp_irq_chip, sei,
2818c2ecf20Sopenharmony_ci			    handle_edge_irq, NULL, NULL);
2828c2ecf20Sopenharmony_ci
2838c2ecf20Sopenharmony_ci	return 0;
2848c2ecf20Sopenharmony_ci
2858c2ecf20Sopenharmony_cifree_irq:
2868c2ecf20Sopenharmony_ci	mvebu_sei_cp_release_irq(sei, hwirq);
2878c2ecf20Sopenharmony_ci	return ret;
2888c2ecf20Sopenharmony_ci}
2898c2ecf20Sopenharmony_ci
2908c2ecf20Sopenharmony_cistatic void mvebu_sei_cp_domain_free(struct irq_domain *domain,
2918c2ecf20Sopenharmony_ci				     unsigned int virq, unsigned int nr_irqs)
2928c2ecf20Sopenharmony_ci{
2938c2ecf20Sopenharmony_ci	struct mvebu_sei *sei = domain->host_data;
2948c2ecf20Sopenharmony_ci	struct irq_data *d = irq_domain_get_irq_data(domain, virq);
2958c2ecf20Sopenharmony_ci
2968c2ecf20Sopenharmony_ci	if (nr_irqs != 1 || d->hwirq >= sei->caps->cp_range.size) {
2978c2ecf20Sopenharmony_ci		dev_err(sei->dev, "Invalid hwirq %lu\n", d->hwirq);
2988c2ecf20Sopenharmony_ci		return;
2998c2ecf20Sopenharmony_ci	}
3008c2ecf20Sopenharmony_ci
3018c2ecf20Sopenharmony_ci	mvebu_sei_cp_release_irq(sei, d->hwirq);
3028c2ecf20Sopenharmony_ci	irq_domain_free_irqs_parent(domain, virq, 1);
3038c2ecf20Sopenharmony_ci}
3048c2ecf20Sopenharmony_ci
3058c2ecf20Sopenharmony_cistatic const struct irq_domain_ops mvebu_sei_cp_domain_ops = {
3068c2ecf20Sopenharmony_ci	.alloc	= mvebu_sei_cp_domain_alloc,
3078c2ecf20Sopenharmony_ci	.free	= mvebu_sei_cp_domain_free,
3088c2ecf20Sopenharmony_ci};
3098c2ecf20Sopenharmony_ci
3108c2ecf20Sopenharmony_cistatic struct irq_chip mvebu_sei_msi_irq_chip = {
3118c2ecf20Sopenharmony_ci	.name		= "SEI pMSI",
3128c2ecf20Sopenharmony_ci	.irq_ack	= irq_chip_ack_parent,
3138c2ecf20Sopenharmony_ci	.irq_set_type	= irq_chip_set_type_parent,
3148c2ecf20Sopenharmony_ci};
3158c2ecf20Sopenharmony_ci
3168c2ecf20Sopenharmony_cistatic struct msi_domain_ops mvebu_sei_msi_ops = {
3178c2ecf20Sopenharmony_ci};
3188c2ecf20Sopenharmony_ci
3198c2ecf20Sopenharmony_cistatic struct msi_domain_info mvebu_sei_msi_domain_info = {
3208c2ecf20Sopenharmony_ci	.flags	= MSI_FLAG_USE_DEF_DOM_OPS | MSI_FLAG_USE_DEF_CHIP_OPS,
3218c2ecf20Sopenharmony_ci	.ops	= &mvebu_sei_msi_ops,
3228c2ecf20Sopenharmony_ci	.chip	= &mvebu_sei_msi_irq_chip,
3238c2ecf20Sopenharmony_ci};
3248c2ecf20Sopenharmony_ci
3258c2ecf20Sopenharmony_cistatic void mvebu_sei_handle_cascade_irq(struct irq_desc *desc)
3268c2ecf20Sopenharmony_ci{
3278c2ecf20Sopenharmony_ci	struct mvebu_sei *sei = irq_desc_get_handler_data(desc);
3288c2ecf20Sopenharmony_ci	struct irq_chip *chip = irq_desc_get_chip(desc);
3298c2ecf20Sopenharmony_ci	u32 idx;
3308c2ecf20Sopenharmony_ci
3318c2ecf20Sopenharmony_ci	chained_irq_enter(chip, desc);
3328c2ecf20Sopenharmony_ci
3338c2ecf20Sopenharmony_ci	for (idx = 0; idx < SEI_IRQ_REG_COUNT; idx++) {
3348c2ecf20Sopenharmony_ci		unsigned long irqmap;
3358c2ecf20Sopenharmony_ci		int bit;
3368c2ecf20Sopenharmony_ci
3378c2ecf20Sopenharmony_ci		irqmap = readl_relaxed(sei->base + GICP_SECR(idx));
3388c2ecf20Sopenharmony_ci		for_each_set_bit(bit, &irqmap, SEI_IRQ_COUNT_PER_REG) {
3398c2ecf20Sopenharmony_ci			unsigned long hwirq;
3408c2ecf20Sopenharmony_ci			unsigned int virq;
3418c2ecf20Sopenharmony_ci
3428c2ecf20Sopenharmony_ci			hwirq = idx * SEI_IRQ_COUNT_PER_REG + bit;
3438c2ecf20Sopenharmony_ci			virq = irq_find_mapping(sei->sei_domain, hwirq);
3448c2ecf20Sopenharmony_ci			if (likely(virq)) {
3458c2ecf20Sopenharmony_ci				generic_handle_irq(virq);
3468c2ecf20Sopenharmony_ci				continue;
3478c2ecf20Sopenharmony_ci			}
3488c2ecf20Sopenharmony_ci
3498c2ecf20Sopenharmony_ci			dev_warn(sei->dev,
3508c2ecf20Sopenharmony_ci				 "Spurious IRQ detected (hwirq %lu)\n", hwirq);
3518c2ecf20Sopenharmony_ci		}
3528c2ecf20Sopenharmony_ci	}
3538c2ecf20Sopenharmony_ci
3548c2ecf20Sopenharmony_ci	chained_irq_exit(chip, desc);
3558c2ecf20Sopenharmony_ci}
3568c2ecf20Sopenharmony_ci
3578c2ecf20Sopenharmony_cistatic void mvebu_sei_reset(struct mvebu_sei *sei)
3588c2ecf20Sopenharmony_ci{
3598c2ecf20Sopenharmony_ci	u32 reg_idx;
3608c2ecf20Sopenharmony_ci
3618c2ecf20Sopenharmony_ci	/* Clear IRQ cause registers, mask all interrupts */
3628c2ecf20Sopenharmony_ci	for (reg_idx = 0; reg_idx < SEI_IRQ_REG_COUNT; reg_idx++) {
3638c2ecf20Sopenharmony_ci		writel_relaxed(0xFFFFFFFF, sei->base + GICP_SECR(reg_idx));
3648c2ecf20Sopenharmony_ci		writel_relaxed(0xFFFFFFFF, sei->base + GICP_SEMR(reg_idx));
3658c2ecf20Sopenharmony_ci	}
3668c2ecf20Sopenharmony_ci}
3678c2ecf20Sopenharmony_ci
3688c2ecf20Sopenharmony_cistatic int mvebu_sei_probe(struct platform_device *pdev)
3698c2ecf20Sopenharmony_ci{
3708c2ecf20Sopenharmony_ci	struct device_node *node = pdev->dev.of_node;
3718c2ecf20Sopenharmony_ci	struct irq_domain *plat_domain;
3728c2ecf20Sopenharmony_ci	struct mvebu_sei *sei;
3738c2ecf20Sopenharmony_ci	u32 parent_irq;
3748c2ecf20Sopenharmony_ci	int ret;
3758c2ecf20Sopenharmony_ci
3768c2ecf20Sopenharmony_ci	sei = devm_kzalloc(&pdev->dev, sizeof(*sei), GFP_KERNEL);
3778c2ecf20Sopenharmony_ci	if (!sei)
3788c2ecf20Sopenharmony_ci		return -ENOMEM;
3798c2ecf20Sopenharmony_ci
3808c2ecf20Sopenharmony_ci	sei->dev = &pdev->dev;
3818c2ecf20Sopenharmony_ci
3828c2ecf20Sopenharmony_ci	mutex_init(&sei->cp_msi_lock);
3838c2ecf20Sopenharmony_ci	raw_spin_lock_init(&sei->mask_lock);
3848c2ecf20Sopenharmony_ci
3858c2ecf20Sopenharmony_ci	sei->res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
3868c2ecf20Sopenharmony_ci	sei->base = devm_ioremap_resource(sei->dev, sei->res);
3878c2ecf20Sopenharmony_ci	if (IS_ERR(sei->base)) {
3888c2ecf20Sopenharmony_ci		dev_err(sei->dev, "Failed to remap SEI resource\n");
3898c2ecf20Sopenharmony_ci		return PTR_ERR(sei->base);
3908c2ecf20Sopenharmony_ci	}
3918c2ecf20Sopenharmony_ci
3928c2ecf20Sopenharmony_ci	/* Retrieve the SEI capabilities with the interrupt ranges */
3938c2ecf20Sopenharmony_ci	sei->caps = of_device_get_match_data(&pdev->dev);
3948c2ecf20Sopenharmony_ci	if (!sei->caps) {
3958c2ecf20Sopenharmony_ci		dev_err(sei->dev,
3968c2ecf20Sopenharmony_ci			"Could not retrieve controller capabilities\n");
3978c2ecf20Sopenharmony_ci		return -EINVAL;
3988c2ecf20Sopenharmony_ci	}
3998c2ecf20Sopenharmony_ci
4008c2ecf20Sopenharmony_ci	/*
4018c2ecf20Sopenharmony_ci	 * Reserve the single (top-level) parent SPI IRQ from which all the
4028c2ecf20Sopenharmony_ci	 * interrupts handled by this driver will be signaled.
4038c2ecf20Sopenharmony_ci	 */
4048c2ecf20Sopenharmony_ci	parent_irq = irq_of_parse_and_map(node, 0);
4058c2ecf20Sopenharmony_ci	if (parent_irq <= 0) {
4068c2ecf20Sopenharmony_ci		dev_err(sei->dev, "Failed to retrieve top-level SPI IRQ\n");
4078c2ecf20Sopenharmony_ci		return -ENODEV;
4088c2ecf20Sopenharmony_ci	}
4098c2ecf20Sopenharmony_ci
4108c2ecf20Sopenharmony_ci	/* Create the root SEI domain */
4118c2ecf20Sopenharmony_ci	sei->sei_domain = irq_domain_create_linear(of_node_to_fwnode(node),
4128c2ecf20Sopenharmony_ci						   (sei->caps->ap_range.size +
4138c2ecf20Sopenharmony_ci						    sei->caps->cp_range.size),
4148c2ecf20Sopenharmony_ci						   &mvebu_sei_domain_ops,
4158c2ecf20Sopenharmony_ci						   sei);
4168c2ecf20Sopenharmony_ci	if (!sei->sei_domain) {
4178c2ecf20Sopenharmony_ci		dev_err(sei->dev, "Failed to create SEI IRQ domain\n");
4188c2ecf20Sopenharmony_ci		ret = -ENOMEM;
4198c2ecf20Sopenharmony_ci		goto dispose_irq;
4208c2ecf20Sopenharmony_ci	}
4218c2ecf20Sopenharmony_ci
4228c2ecf20Sopenharmony_ci	irq_domain_update_bus_token(sei->sei_domain, DOMAIN_BUS_NEXUS);
4238c2ecf20Sopenharmony_ci
4248c2ecf20Sopenharmony_ci	/* Create the 'wired' domain */
4258c2ecf20Sopenharmony_ci	sei->ap_domain = irq_domain_create_hierarchy(sei->sei_domain, 0,
4268c2ecf20Sopenharmony_ci						     sei->caps->ap_range.size,
4278c2ecf20Sopenharmony_ci						     of_node_to_fwnode(node),
4288c2ecf20Sopenharmony_ci						     &mvebu_sei_ap_domain_ops,
4298c2ecf20Sopenharmony_ci						     sei);
4308c2ecf20Sopenharmony_ci	if (!sei->ap_domain) {
4318c2ecf20Sopenharmony_ci		dev_err(sei->dev, "Failed to create AP IRQ domain\n");
4328c2ecf20Sopenharmony_ci		ret = -ENOMEM;
4338c2ecf20Sopenharmony_ci		goto remove_sei_domain;
4348c2ecf20Sopenharmony_ci	}
4358c2ecf20Sopenharmony_ci
4368c2ecf20Sopenharmony_ci	irq_domain_update_bus_token(sei->ap_domain, DOMAIN_BUS_WIRED);
4378c2ecf20Sopenharmony_ci
4388c2ecf20Sopenharmony_ci	/* Create the 'MSI' domain */
4398c2ecf20Sopenharmony_ci	sei->cp_domain = irq_domain_create_hierarchy(sei->sei_domain, 0,
4408c2ecf20Sopenharmony_ci						     sei->caps->cp_range.size,
4418c2ecf20Sopenharmony_ci						     of_node_to_fwnode(node),
4428c2ecf20Sopenharmony_ci						     &mvebu_sei_cp_domain_ops,
4438c2ecf20Sopenharmony_ci						     sei);
4448c2ecf20Sopenharmony_ci	if (!sei->cp_domain) {
4458c2ecf20Sopenharmony_ci		pr_err("Failed to create CPs IRQ domain\n");
4468c2ecf20Sopenharmony_ci		ret = -ENOMEM;
4478c2ecf20Sopenharmony_ci		goto remove_ap_domain;
4488c2ecf20Sopenharmony_ci	}
4498c2ecf20Sopenharmony_ci
4508c2ecf20Sopenharmony_ci	irq_domain_update_bus_token(sei->cp_domain, DOMAIN_BUS_GENERIC_MSI);
4518c2ecf20Sopenharmony_ci
4528c2ecf20Sopenharmony_ci	plat_domain = platform_msi_create_irq_domain(of_node_to_fwnode(node),
4538c2ecf20Sopenharmony_ci						     &mvebu_sei_msi_domain_info,
4548c2ecf20Sopenharmony_ci						     sei->cp_domain);
4558c2ecf20Sopenharmony_ci	if (!plat_domain) {
4568c2ecf20Sopenharmony_ci		pr_err("Failed to create CPs MSI domain\n");
4578c2ecf20Sopenharmony_ci		ret = -ENOMEM;
4588c2ecf20Sopenharmony_ci		goto remove_cp_domain;
4598c2ecf20Sopenharmony_ci	}
4608c2ecf20Sopenharmony_ci
4618c2ecf20Sopenharmony_ci	mvebu_sei_reset(sei);
4628c2ecf20Sopenharmony_ci
4638c2ecf20Sopenharmony_ci	irq_set_chained_handler_and_data(parent_irq,
4648c2ecf20Sopenharmony_ci					 mvebu_sei_handle_cascade_irq,
4658c2ecf20Sopenharmony_ci					 sei);
4668c2ecf20Sopenharmony_ci
4678c2ecf20Sopenharmony_ci	return 0;
4688c2ecf20Sopenharmony_ci
4698c2ecf20Sopenharmony_ciremove_cp_domain:
4708c2ecf20Sopenharmony_ci	irq_domain_remove(sei->cp_domain);
4718c2ecf20Sopenharmony_ciremove_ap_domain:
4728c2ecf20Sopenharmony_ci	irq_domain_remove(sei->ap_domain);
4738c2ecf20Sopenharmony_ciremove_sei_domain:
4748c2ecf20Sopenharmony_ci	irq_domain_remove(sei->sei_domain);
4758c2ecf20Sopenharmony_cidispose_irq:
4768c2ecf20Sopenharmony_ci	irq_dispose_mapping(parent_irq);
4778c2ecf20Sopenharmony_ci
4788c2ecf20Sopenharmony_ci	return ret;
4798c2ecf20Sopenharmony_ci}
4808c2ecf20Sopenharmony_ci
4818c2ecf20Sopenharmony_cistatic struct mvebu_sei_caps mvebu_sei_ap806_caps = {
4828c2ecf20Sopenharmony_ci	.ap_range = {
4838c2ecf20Sopenharmony_ci		.first = 0,
4848c2ecf20Sopenharmony_ci		.size = 21,
4858c2ecf20Sopenharmony_ci	},
4868c2ecf20Sopenharmony_ci	.cp_range = {
4878c2ecf20Sopenharmony_ci		.first = 21,
4888c2ecf20Sopenharmony_ci		.size = 43,
4898c2ecf20Sopenharmony_ci	},
4908c2ecf20Sopenharmony_ci};
4918c2ecf20Sopenharmony_ci
4928c2ecf20Sopenharmony_cistatic const struct of_device_id mvebu_sei_of_match[] = {
4938c2ecf20Sopenharmony_ci	{
4948c2ecf20Sopenharmony_ci		.compatible = "marvell,ap806-sei",
4958c2ecf20Sopenharmony_ci		.data = &mvebu_sei_ap806_caps,
4968c2ecf20Sopenharmony_ci	},
4978c2ecf20Sopenharmony_ci	{},
4988c2ecf20Sopenharmony_ci};
4998c2ecf20Sopenharmony_ci
5008c2ecf20Sopenharmony_cistatic struct platform_driver mvebu_sei_driver = {
5018c2ecf20Sopenharmony_ci	.probe  = mvebu_sei_probe,
5028c2ecf20Sopenharmony_ci	.driver = {
5038c2ecf20Sopenharmony_ci		.name = "mvebu-sei",
5048c2ecf20Sopenharmony_ci		.of_match_table = mvebu_sei_of_match,
5058c2ecf20Sopenharmony_ci	},
5068c2ecf20Sopenharmony_ci};
5078c2ecf20Sopenharmony_cibuiltin_platform_driver(mvebu_sei_driver);
508