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