162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 262306a36Sopenharmony_ci 362306a36Sopenharmony_ci#define pr_fmt(fmt) "mvebu-sei: " fmt 462306a36Sopenharmony_ci 562306a36Sopenharmony_ci#include <linux/interrupt.h> 662306a36Sopenharmony_ci#include <linux/irq.h> 762306a36Sopenharmony_ci#include <linux/irqchip.h> 862306a36Sopenharmony_ci#include <linux/irqchip/chained_irq.h> 962306a36Sopenharmony_ci#include <linux/irqdomain.h> 1062306a36Sopenharmony_ci#include <linux/kernel.h> 1162306a36Sopenharmony_ci#include <linux/msi.h> 1262306a36Sopenharmony_ci#include <linux/platform_device.h> 1362306a36Sopenharmony_ci#include <linux/of_address.h> 1462306a36Sopenharmony_ci#include <linux/of_irq.h> 1562306a36Sopenharmony_ci#include <linux/of_platform.h> 1662306a36Sopenharmony_ci 1762306a36Sopenharmony_ci/* Cause register */ 1862306a36Sopenharmony_ci#define GICP_SECR(idx) (0x0 + ((idx) * 0x4)) 1962306a36Sopenharmony_ci/* Mask register */ 2062306a36Sopenharmony_ci#define GICP_SEMR(idx) (0x20 + ((idx) * 0x4)) 2162306a36Sopenharmony_ci#define GICP_SET_SEI_OFFSET 0x30 2262306a36Sopenharmony_ci 2362306a36Sopenharmony_ci#define SEI_IRQ_COUNT_PER_REG 32 2462306a36Sopenharmony_ci#define SEI_IRQ_REG_COUNT 2 2562306a36Sopenharmony_ci#define SEI_IRQ_COUNT (SEI_IRQ_COUNT_PER_REG * SEI_IRQ_REG_COUNT) 2662306a36Sopenharmony_ci#define SEI_IRQ_REG_IDX(irq_id) ((irq_id) / SEI_IRQ_COUNT_PER_REG) 2762306a36Sopenharmony_ci#define SEI_IRQ_REG_BIT(irq_id) ((irq_id) % SEI_IRQ_COUNT_PER_REG) 2862306a36Sopenharmony_ci 2962306a36Sopenharmony_cistruct mvebu_sei_interrupt_range { 3062306a36Sopenharmony_ci u32 first; 3162306a36Sopenharmony_ci u32 size; 3262306a36Sopenharmony_ci}; 3362306a36Sopenharmony_ci 3462306a36Sopenharmony_cistruct mvebu_sei_caps { 3562306a36Sopenharmony_ci struct mvebu_sei_interrupt_range ap_range; 3662306a36Sopenharmony_ci struct mvebu_sei_interrupt_range cp_range; 3762306a36Sopenharmony_ci}; 3862306a36Sopenharmony_ci 3962306a36Sopenharmony_cistruct mvebu_sei { 4062306a36Sopenharmony_ci struct device *dev; 4162306a36Sopenharmony_ci void __iomem *base; 4262306a36Sopenharmony_ci struct resource *res; 4362306a36Sopenharmony_ci struct irq_domain *sei_domain; 4462306a36Sopenharmony_ci struct irq_domain *ap_domain; 4562306a36Sopenharmony_ci struct irq_domain *cp_domain; 4662306a36Sopenharmony_ci const struct mvebu_sei_caps *caps; 4762306a36Sopenharmony_ci 4862306a36Sopenharmony_ci /* Lock on MSI allocations/releases */ 4962306a36Sopenharmony_ci struct mutex cp_msi_lock; 5062306a36Sopenharmony_ci DECLARE_BITMAP(cp_msi_bitmap, SEI_IRQ_COUNT); 5162306a36Sopenharmony_ci 5262306a36Sopenharmony_ci /* Lock on IRQ masking register */ 5362306a36Sopenharmony_ci raw_spinlock_t mask_lock; 5462306a36Sopenharmony_ci}; 5562306a36Sopenharmony_ci 5662306a36Sopenharmony_cistatic void mvebu_sei_ack_irq(struct irq_data *d) 5762306a36Sopenharmony_ci{ 5862306a36Sopenharmony_ci struct mvebu_sei *sei = irq_data_get_irq_chip_data(d); 5962306a36Sopenharmony_ci u32 reg_idx = SEI_IRQ_REG_IDX(d->hwirq); 6062306a36Sopenharmony_ci 6162306a36Sopenharmony_ci writel_relaxed(BIT(SEI_IRQ_REG_BIT(d->hwirq)), 6262306a36Sopenharmony_ci sei->base + GICP_SECR(reg_idx)); 6362306a36Sopenharmony_ci} 6462306a36Sopenharmony_ci 6562306a36Sopenharmony_cistatic void mvebu_sei_mask_irq(struct irq_data *d) 6662306a36Sopenharmony_ci{ 6762306a36Sopenharmony_ci struct mvebu_sei *sei = irq_data_get_irq_chip_data(d); 6862306a36Sopenharmony_ci u32 reg, reg_idx = SEI_IRQ_REG_IDX(d->hwirq); 6962306a36Sopenharmony_ci unsigned long flags; 7062306a36Sopenharmony_ci 7162306a36Sopenharmony_ci /* 1 disables the interrupt */ 7262306a36Sopenharmony_ci raw_spin_lock_irqsave(&sei->mask_lock, flags); 7362306a36Sopenharmony_ci reg = readl_relaxed(sei->base + GICP_SEMR(reg_idx)); 7462306a36Sopenharmony_ci reg |= BIT(SEI_IRQ_REG_BIT(d->hwirq)); 7562306a36Sopenharmony_ci writel_relaxed(reg, sei->base + GICP_SEMR(reg_idx)); 7662306a36Sopenharmony_ci raw_spin_unlock_irqrestore(&sei->mask_lock, flags); 7762306a36Sopenharmony_ci} 7862306a36Sopenharmony_ci 7962306a36Sopenharmony_cistatic void mvebu_sei_unmask_irq(struct irq_data *d) 8062306a36Sopenharmony_ci{ 8162306a36Sopenharmony_ci struct mvebu_sei *sei = irq_data_get_irq_chip_data(d); 8262306a36Sopenharmony_ci u32 reg, reg_idx = SEI_IRQ_REG_IDX(d->hwirq); 8362306a36Sopenharmony_ci unsigned long flags; 8462306a36Sopenharmony_ci 8562306a36Sopenharmony_ci /* 0 enables the interrupt */ 8662306a36Sopenharmony_ci raw_spin_lock_irqsave(&sei->mask_lock, flags); 8762306a36Sopenharmony_ci reg = readl_relaxed(sei->base + GICP_SEMR(reg_idx)); 8862306a36Sopenharmony_ci reg &= ~BIT(SEI_IRQ_REG_BIT(d->hwirq)); 8962306a36Sopenharmony_ci writel_relaxed(reg, sei->base + GICP_SEMR(reg_idx)); 9062306a36Sopenharmony_ci raw_spin_unlock_irqrestore(&sei->mask_lock, flags); 9162306a36Sopenharmony_ci} 9262306a36Sopenharmony_ci 9362306a36Sopenharmony_cistatic int mvebu_sei_set_affinity(struct irq_data *d, 9462306a36Sopenharmony_ci const struct cpumask *mask_val, 9562306a36Sopenharmony_ci bool force) 9662306a36Sopenharmony_ci{ 9762306a36Sopenharmony_ci return -EINVAL; 9862306a36Sopenharmony_ci} 9962306a36Sopenharmony_ci 10062306a36Sopenharmony_cistatic int mvebu_sei_set_irqchip_state(struct irq_data *d, 10162306a36Sopenharmony_ci enum irqchip_irq_state which, 10262306a36Sopenharmony_ci bool state) 10362306a36Sopenharmony_ci{ 10462306a36Sopenharmony_ci /* We can only clear the pending state by acking the interrupt */ 10562306a36Sopenharmony_ci if (which != IRQCHIP_STATE_PENDING || state) 10662306a36Sopenharmony_ci return -EINVAL; 10762306a36Sopenharmony_ci 10862306a36Sopenharmony_ci mvebu_sei_ack_irq(d); 10962306a36Sopenharmony_ci return 0; 11062306a36Sopenharmony_ci} 11162306a36Sopenharmony_ci 11262306a36Sopenharmony_cistatic struct irq_chip mvebu_sei_irq_chip = { 11362306a36Sopenharmony_ci .name = "SEI", 11462306a36Sopenharmony_ci .irq_ack = mvebu_sei_ack_irq, 11562306a36Sopenharmony_ci .irq_mask = mvebu_sei_mask_irq, 11662306a36Sopenharmony_ci .irq_unmask = mvebu_sei_unmask_irq, 11762306a36Sopenharmony_ci .irq_set_affinity = mvebu_sei_set_affinity, 11862306a36Sopenharmony_ci .irq_set_irqchip_state = mvebu_sei_set_irqchip_state, 11962306a36Sopenharmony_ci}; 12062306a36Sopenharmony_ci 12162306a36Sopenharmony_cistatic int mvebu_sei_ap_set_type(struct irq_data *data, unsigned int type) 12262306a36Sopenharmony_ci{ 12362306a36Sopenharmony_ci if ((type & IRQ_TYPE_SENSE_MASK) != IRQ_TYPE_LEVEL_HIGH) 12462306a36Sopenharmony_ci return -EINVAL; 12562306a36Sopenharmony_ci 12662306a36Sopenharmony_ci return 0; 12762306a36Sopenharmony_ci} 12862306a36Sopenharmony_ci 12962306a36Sopenharmony_cistatic struct irq_chip mvebu_sei_ap_irq_chip = { 13062306a36Sopenharmony_ci .name = "AP SEI", 13162306a36Sopenharmony_ci .irq_ack = irq_chip_ack_parent, 13262306a36Sopenharmony_ci .irq_mask = irq_chip_mask_parent, 13362306a36Sopenharmony_ci .irq_unmask = irq_chip_unmask_parent, 13462306a36Sopenharmony_ci .irq_set_affinity = irq_chip_set_affinity_parent, 13562306a36Sopenharmony_ci .irq_set_type = mvebu_sei_ap_set_type, 13662306a36Sopenharmony_ci}; 13762306a36Sopenharmony_ci 13862306a36Sopenharmony_cistatic void mvebu_sei_cp_compose_msi_msg(struct irq_data *data, 13962306a36Sopenharmony_ci struct msi_msg *msg) 14062306a36Sopenharmony_ci{ 14162306a36Sopenharmony_ci struct mvebu_sei *sei = data->chip_data; 14262306a36Sopenharmony_ci phys_addr_t set = sei->res->start + GICP_SET_SEI_OFFSET; 14362306a36Sopenharmony_ci 14462306a36Sopenharmony_ci msg->data = data->hwirq + sei->caps->cp_range.first; 14562306a36Sopenharmony_ci msg->address_lo = lower_32_bits(set); 14662306a36Sopenharmony_ci msg->address_hi = upper_32_bits(set); 14762306a36Sopenharmony_ci} 14862306a36Sopenharmony_ci 14962306a36Sopenharmony_cistatic int mvebu_sei_cp_set_type(struct irq_data *data, unsigned int type) 15062306a36Sopenharmony_ci{ 15162306a36Sopenharmony_ci if ((type & IRQ_TYPE_SENSE_MASK) != IRQ_TYPE_EDGE_RISING) 15262306a36Sopenharmony_ci return -EINVAL; 15362306a36Sopenharmony_ci 15462306a36Sopenharmony_ci return 0; 15562306a36Sopenharmony_ci} 15662306a36Sopenharmony_ci 15762306a36Sopenharmony_cistatic struct irq_chip mvebu_sei_cp_irq_chip = { 15862306a36Sopenharmony_ci .name = "CP SEI", 15962306a36Sopenharmony_ci .irq_ack = irq_chip_ack_parent, 16062306a36Sopenharmony_ci .irq_mask = irq_chip_mask_parent, 16162306a36Sopenharmony_ci .irq_unmask = irq_chip_unmask_parent, 16262306a36Sopenharmony_ci .irq_set_affinity = irq_chip_set_affinity_parent, 16362306a36Sopenharmony_ci .irq_set_type = mvebu_sei_cp_set_type, 16462306a36Sopenharmony_ci .irq_compose_msi_msg = mvebu_sei_cp_compose_msi_msg, 16562306a36Sopenharmony_ci}; 16662306a36Sopenharmony_ci 16762306a36Sopenharmony_cistatic int mvebu_sei_domain_alloc(struct irq_domain *domain, unsigned int virq, 16862306a36Sopenharmony_ci unsigned int nr_irqs, void *arg) 16962306a36Sopenharmony_ci{ 17062306a36Sopenharmony_ci struct mvebu_sei *sei = domain->host_data; 17162306a36Sopenharmony_ci struct irq_fwspec *fwspec = arg; 17262306a36Sopenharmony_ci 17362306a36Sopenharmony_ci /* Not much to do, just setup the irqdata */ 17462306a36Sopenharmony_ci irq_domain_set_hwirq_and_chip(domain, virq, fwspec->param[0], 17562306a36Sopenharmony_ci &mvebu_sei_irq_chip, sei); 17662306a36Sopenharmony_ci 17762306a36Sopenharmony_ci return 0; 17862306a36Sopenharmony_ci} 17962306a36Sopenharmony_ci 18062306a36Sopenharmony_cistatic void mvebu_sei_domain_free(struct irq_domain *domain, unsigned int virq, 18162306a36Sopenharmony_ci unsigned int nr_irqs) 18262306a36Sopenharmony_ci{ 18362306a36Sopenharmony_ci int i; 18462306a36Sopenharmony_ci 18562306a36Sopenharmony_ci for (i = 0; i < nr_irqs; i++) { 18662306a36Sopenharmony_ci struct irq_data *d = irq_domain_get_irq_data(domain, virq + i); 18762306a36Sopenharmony_ci irq_set_handler(virq + i, NULL); 18862306a36Sopenharmony_ci irq_domain_reset_irq_data(d); 18962306a36Sopenharmony_ci } 19062306a36Sopenharmony_ci} 19162306a36Sopenharmony_ci 19262306a36Sopenharmony_cistatic const struct irq_domain_ops mvebu_sei_domain_ops = { 19362306a36Sopenharmony_ci .alloc = mvebu_sei_domain_alloc, 19462306a36Sopenharmony_ci .free = mvebu_sei_domain_free, 19562306a36Sopenharmony_ci}; 19662306a36Sopenharmony_ci 19762306a36Sopenharmony_cistatic int mvebu_sei_ap_translate(struct irq_domain *domain, 19862306a36Sopenharmony_ci struct irq_fwspec *fwspec, 19962306a36Sopenharmony_ci unsigned long *hwirq, 20062306a36Sopenharmony_ci unsigned int *type) 20162306a36Sopenharmony_ci{ 20262306a36Sopenharmony_ci *hwirq = fwspec->param[0]; 20362306a36Sopenharmony_ci *type = IRQ_TYPE_LEVEL_HIGH; 20462306a36Sopenharmony_ci 20562306a36Sopenharmony_ci return 0; 20662306a36Sopenharmony_ci} 20762306a36Sopenharmony_ci 20862306a36Sopenharmony_cistatic int mvebu_sei_ap_alloc(struct irq_domain *domain, unsigned int virq, 20962306a36Sopenharmony_ci unsigned int nr_irqs, void *arg) 21062306a36Sopenharmony_ci{ 21162306a36Sopenharmony_ci struct mvebu_sei *sei = domain->host_data; 21262306a36Sopenharmony_ci struct irq_fwspec fwspec; 21362306a36Sopenharmony_ci unsigned long hwirq; 21462306a36Sopenharmony_ci unsigned int type; 21562306a36Sopenharmony_ci int err; 21662306a36Sopenharmony_ci 21762306a36Sopenharmony_ci mvebu_sei_ap_translate(domain, arg, &hwirq, &type); 21862306a36Sopenharmony_ci 21962306a36Sopenharmony_ci fwspec.fwnode = domain->parent->fwnode; 22062306a36Sopenharmony_ci fwspec.param_count = 1; 22162306a36Sopenharmony_ci fwspec.param[0] = hwirq + sei->caps->ap_range.first; 22262306a36Sopenharmony_ci 22362306a36Sopenharmony_ci err = irq_domain_alloc_irqs_parent(domain, virq, 1, &fwspec); 22462306a36Sopenharmony_ci if (err) 22562306a36Sopenharmony_ci return err; 22662306a36Sopenharmony_ci 22762306a36Sopenharmony_ci irq_domain_set_info(domain, virq, hwirq, 22862306a36Sopenharmony_ci &mvebu_sei_ap_irq_chip, sei, 22962306a36Sopenharmony_ci handle_level_irq, NULL, NULL); 23062306a36Sopenharmony_ci irq_set_probe(virq); 23162306a36Sopenharmony_ci 23262306a36Sopenharmony_ci return 0; 23362306a36Sopenharmony_ci} 23462306a36Sopenharmony_ci 23562306a36Sopenharmony_cistatic const struct irq_domain_ops mvebu_sei_ap_domain_ops = { 23662306a36Sopenharmony_ci .translate = mvebu_sei_ap_translate, 23762306a36Sopenharmony_ci .alloc = mvebu_sei_ap_alloc, 23862306a36Sopenharmony_ci .free = irq_domain_free_irqs_parent, 23962306a36Sopenharmony_ci}; 24062306a36Sopenharmony_ci 24162306a36Sopenharmony_cistatic void mvebu_sei_cp_release_irq(struct mvebu_sei *sei, unsigned long hwirq) 24262306a36Sopenharmony_ci{ 24362306a36Sopenharmony_ci mutex_lock(&sei->cp_msi_lock); 24462306a36Sopenharmony_ci clear_bit(hwirq, sei->cp_msi_bitmap); 24562306a36Sopenharmony_ci mutex_unlock(&sei->cp_msi_lock); 24662306a36Sopenharmony_ci} 24762306a36Sopenharmony_ci 24862306a36Sopenharmony_cistatic int mvebu_sei_cp_domain_alloc(struct irq_domain *domain, 24962306a36Sopenharmony_ci unsigned int virq, unsigned int nr_irqs, 25062306a36Sopenharmony_ci void *args) 25162306a36Sopenharmony_ci{ 25262306a36Sopenharmony_ci struct mvebu_sei *sei = domain->host_data; 25362306a36Sopenharmony_ci struct irq_fwspec fwspec; 25462306a36Sopenharmony_ci unsigned long hwirq; 25562306a36Sopenharmony_ci int ret; 25662306a36Sopenharmony_ci 25762306a36Sopenharmony_ci /* The software only supports single allocations for now */ 25862306a36Sopenharmony_ci if (nr_irqs != 1) 25962306a36Sopenharmony_ci return -ENOTSUPP; 26062306a36Sopenharmony_ci 26162306a36Sopenharmony_ci mutex_lock(&sei->cp_msi_lock); 26262306a36Sopenharmony_ci hwirq = find_first_zero_bit(sei->cp_msi_bitmap, 26362306a36Sopenharmony_ci sei->caps->cp_range.size); 26462306a36Sopenharmony_ci if (hwirq < sei->caps->cp_range.size) 26562306a36Sopenharmony_ci set_bit(hwirq, sei->cp_msi_bitmap); 26662306a36Sopenharmony_ci mutex_unlock(&sei->cp_msi_lock); 26762306a36Sopenharmony_ci 26862306a36Sopenharmony_ci if (hwirq == sei->caps->cp_range.size) 26962306a36Sopenharmony_ci return -ENOSPC; 27062306a36Sopenharmony_ci 27162306a36Sopenharmony_ci fwspec.fwnode = domain->parent->fwnode; 27262306a36Sopenharmony_ci fwspec.param_count = 1; 27362306a36Sopenharmony_ci fwspec.param[0] = hwirq + sei->caps->cp_range.first; 27462306a36Sopenharmony_ci 27562306a36Sopenharmony_ci ret = irq_domain_alloc_irqs_parent(domain, virq, 1, &fwspec); 27662306a36Sopenharmony_ci if (ret) 27762306a36Sopenharmony_ci goto free_irq; 27862306a36Sopenharmony_ci 27962306a36Sopenharmony_ci irq_domain_set_info(domain, virq, hwirq, 28062306a36Sopenharmony_ci &mvebu_sei_cp_irq_chip, sei, 28162306a36Sopenharmony_ci handle_edge_irq, NULL, NULL); 28262306a36Sopenharmony_ci 28362306a36Sopenharmony_ci return 0; 28462306a36Sopenharmony_ci 28562306a36Sopenharmony_cifree_irq: 28662306a36Sopenharmony_ci mvebu_sei_cp_release_irq(sei, hwirq); 28762306a36Sopenharmony_ci return ret; 28862306a36Sopenharmony_ci} 28962306a36Sopenharmony_ci 29062306a36Sopenharmony_cistatic void mvebu_sei_cp_domain_free(struct irq_domain *domain, 29162306a36Sopenharmony_ci unsigned int virq, unsigned int nr_irqs) 29262306a36Sopenharmony_ci{ 29362306a36Sopenharmony_ci struct mvebu_sei *sei = domain->host_data; 29462306a36Sopenharmony_ci struct irq_data *d = irq_domain_get_irq_data(domain, virq); 29562306a36Sopenharmony_ci 29662306a36Sopenharmony_ci if (nr_irqs != 1 || d->hwirq >= sei->caps->cp_range.size) { 29762306a36Sopenharmony_ci dev_err(sei->dev, "Invalid hwirq %lu\n", d->hwirq); 29862306a36Sopenharmony_ci return; 29962306a36Sopenharmony_ci } 30062306a36Sopenharmony_ci 30162306a36Sopenharmony_ci mvebu_sei_cp_release_irq(sei, d->hwirq); 30262306a36Sopenharmony_ci irq_domain_free_irqs_parent(domain, virq, 1); 30362306a36Sopenharmony_ci} 30462306a36Sopenharmony_ci 30562306a36Sopenharmony_cistatic const struct irq_domain_ops mvebu_sei_cp_domain_ops = { 30662306a36Sopenharmony_ci .alloc = mvebu_sei_cp_domain_alloc, 30762306a36Sopenharmony_ci .free = mvebu_sei_cp_domain_free, 30862306a36Sopenharmony_ci}; 30962306a36Sopenharmony_ci 31062306a36Sopenharmony_cistatic struct irq_chip mvebu_sei_msi_irq_chip = { 31162306a36Sopenharmony_ci .name = "SEI pMSI", 31262306a36Sopenharmony_ci .irq_ack = irq_chip_ack_parent, 31362306a36Sopenharmony_ci .irq_set_type = irq_chip_set_type_parent, 31462306a36Sopenharmony_ci}; 31562306a36Sopenharmony_ci 31662306a36Sopenharmony_cistatic struct msi_domain_ops mvebu_sei_msi_ops = { 31762306a36Sopenharmony_ci}; 31862306a36Sopenharmony_ci 31962306a36Sopenharmony_cistatic struct msi_domain_info mvebu_sei_msi_domain_info = { 32062306a36Sopenharmony_ci .flags = MSI_FLAG_USE_DEF_DOM_OPS | MSI_FLAG_USE_DEF_CHIP_OPS, 32162306a36Sopenharmony_ci .ops = &mvebu_sei_msi_ops, 32262306a36Sopenharmony_ci .chip = &mvebu_sei_msi_irq_chip, 32362306a36Sopenharmony_ci}; 32462306a36Sopenharmony_ci 32562306a36Sopenharmony_cistatic void mvebu_sei_handle_cascade_irq(struct irq_desc *desc) 32662306a36Sopenharmony_ci{ 32762306a36Sopenharmony_ci struct mvebu_sei *sei = irq_desc_get_handler_data(desc); 32862306a36Sopenharmony_ci struct irq_chip *chip = irq_desc_get_chip(desc); 32962306a36Sopenharmony_ci u32 idx; 33062306a36Sopenharmony_ci 33162306a36Sopenharmony_ci chained_irq_enter(chip, desc); 33262306a36Sopenharmony_ci 33362306a36Sopenharmony_ci for (idx = 0; idx < SEI_IRQ_REG_COUNT; idx++) { 33462306a36Sopenharmony_ci unsigned long irqmap; 33562306a36Sopenharmony_ci int bit; 33662306a36Sopenharmony_ci 33762306a36Sopenharmony_ci irqmap = readl_relaxed(sei->base + GICP_SECR(idx)); 33862306a36Sopenharmony_ci for_each_set_bit(bit, &irqmap, SEI_IRQ_COUNT_PER_REG) { 33962306a36Sopenharmony_ci unsigned long hwirq; 34062306a36Sopenharmony_ci int err; 34162306a36Sopenharmony_ci 34262306a36Sopenharmony_ci hwirq = idx * SEI_IRQ_COUNT_PER_REG + bit; 34362306a36Sopenharmony_ci err = generic_handle_domain_irq(sei->sei_domain, hwirq); 34462306a36Sopenharmony_ci if (unlikely(err)) 34562306a36Sopenharmony_ci dev_warn(sei->dev, "Spurious IRQ detected (hwirq %lu)\n", hwirq); 34662306a36Sopenharmony_ci } 34762306a36Sopenharmony_ci } 34862306a36Sopenharmony_ci 34962306a36Sopenharmony_ci chained_irq_exit(chip, desc); 35062306a36Sopenharmony_ci} 35162306a36Sopenharmony_ci 35262306a36Sopenharmony_cistatic void mvebu_sei_reset(struct mvebu_sei *sei) 35362306a36Sopenharmony_ci{ 35462306a36Sopenharmony_ci u32 reg_idx; 35562306a36Sopenharmony_ci 35662306a36Sopenharmony_ci /* Clear IRQ cause registers, mask all interrupts */ 35762306a36Sopenharmony_ci for (reg_idx = 0; reg_idx < SEI_IRQ_REG_COUNT; reg_idx++) { 35862306a36Sopenharmony_ci writel_relaxed(0xFFFFFFFF, sei->base + GICP_SECR(reg_idx)); 35962306a36Sopenharmony_ci writel_relaxed(0xFFFFFFFF, sei->base + GICP_SEMR(reg_idx)); 36062306a36Sopenharmony_ci } 36162306a36Sopenharmony_ci} 36262306a36Sopenharmony_ci 36362306a36Sopenharmony_cistatic int mvebu_sei_probe(struct platform_device *pdev) 36462306a36Sopenharmony_ci{ 36562306a36Sopenharmony_ci struct device_node *node = pdev->dev.of_node; 36662306a36Sopenharmony_ci struct irq_domain *plat_domain; 36762306a36Sopenharmony_ci struct mvebu_sei *sei; 36862306a36Sopenharmony_ci u32 parent_irq; 36962306a36Sopenharmony_ci int ret; 37062306a36Sopenharmony_ci 37162306a36Sopenharmony_ci sei = devm_kzalloc(&pdev->dev, sizeof(*sei), GFP_KERNEL); 37262306a36Sopenharmony_ci if (!sei) 37362306a36Sopenharmony_ci return -ENOMEM; 37462306a36Sopenharmony_ci 37562306a36Sopenharmony_ci sei->dev = &pdev->dev; 37662306a36Sopenharmony_ci 37762306a36Sopenharmony_ci mutex_init(&sei->cp_msi_lock); 37862306a36Sopenharmony_ci raw_spin_lock_init(&sei->mask_lock); 37962306a36Sopenharmony_ci 38062306a36Sopenharmony_ci sei->base = devm_platform_get_and_ioremap_resource(pdev, 0, &sei->res); 38162306a36Sopenharmony_ci if (IS_ERR(sei->base)) 38262306a36Sopenharmony_ci return PTR_ERR(sei->base); 38362306a36Sopenharmony_ci 38462306a36Sopenharmony_ci /* Retrieve the SEI capabilities with the interrupt ranges */ 38562306a36Sopenharmony_ci sei->caps = of_device_get_match_data(&pdev->dev); 38662306a36Sopenharmony_ci if (!sei->caps) { 38762306a36Sopenharmony_ci dev_err(sei->dev, 38862306a36Sopenharmony_ci "Could not retrieve controller capabilities\n"); 38962306a36Sopenharmony_ci return -EINVAL; 39062306a36Sopenharmony_ci } 39162306a36Sopenharmony_ci 39262306a36Sopenharmony_ci /* 39362306a36Sopenharmony_ci * Reserve the single (top-level) parent SPI IRQ from which all the 39462306a36Sopenharmony_ci * interrupts handled by this driver will be signaled. 39562306a36Sopenharmony_ci */ 39662306a36Sopenharmony_ci parent_irq = irq_of_parse_and_map(node, 0); 39762306a36Sopenharmony_ci if (parent_irq <= 0) { 39862306a36Sopenharmony_ci dev_err(sei->dev, "Failed to retrieve top-level SPI IRQ\n"); 39962306a36Sopenharmony_ci return -ENODEV; 40062306a36Sopenharmony_ci } 40162306a36Sopenharmony_ci 40262306a36Sopenharmony_ci /* Create the root SEI domain */ 40362306a36Sopenharmony_ci sei->sei_domain = irq_domain_create_linear(of_node_to_fwnode(node), 40462306a36Sopenharmony_ci (sei->caps->ap_range.size + 40562306a36Sopenharmony_ci sei->caps->cp_range.size), 40662306a36Sopenharmony_ci &mvebu_sei_domain_ops, 40762306a36Sopenharmony_ci sei); 40862306a36Sopenharmony_ci if (!sei->sei_domain) { 40962306a36Sopenharmony_ci dev_err(sei->dev, "Failed to create SEI IRQ domain\n"); 41062306a36Sopenharmony_ci ret = -ENOMEM; 41162306a36Sopenharmony_ci goto dispose_irq; 41262306a36Sopenharmony_ci } 41362306a36Sopenharmony_ci 41462306a36Sopenharmony_ci irq_domain_update_bus_token(sei->sei_domain, DOMAIN_BUS_NEXUS); 41562306a36Sopenharmony_ci 41662306a36Sopenharmony_ci /* Create the 'wired' domain */ 41762306a36Sopenharmony_ci sei->ap_domain = irq_domain_create_hierarchy(sei->sei_domain, 0, 41862306a36Sopenharmony_ci sei->caps->ap_range.size, 41962306a36Sopenharmony_ci of_node_to_fwnode(node), 42062306a36Sopenharmony_ci &mvebu_sei_ap_domain_ops, 42162306a36Sopenharmony_ci sei); 42262306a36Sopenharmony_ci if (!sei->ap_domain) { 42362306a36Sopenharmony_ci dev_err(sei->dev, "Failed to create AP IRQ domain\n"); 42462306a36Sopenharmony_ci ret = -ENOMEM; 42562306a36Sopenharmony_ci goto remove_sei_domain; 42662306a36Sopenharmony_ci } 42762306a36Sopenharmony_ci 42862306a36Sopenharmony_ci irq_domain_update_bus_token(sei->ap_domain, DOMAIN_BUS_WIRED); 42962306a36Sopenharmony_ci 43062306a36Sopenharmony_ci /* Create the 'MSI' domain */ 43162306a36Sopenharmony_ci sei->cp_domain = irq_domain_create_hierarchy(sei->sei_domain, 0, 43262306a36Sopenharmony_ci sei->caps->cp_range.size, 43362306a36Sopenharmony_ci of_node_to_fwnode(node), 43462306a36Sopenharmony_ci &mvebu_sei_cp_domain_ops, 43562306a36Sopenharmony_ci sei); 43662306a36Sopenharmony_ci if (!sei->cp_domain) { 43762306a36Sopenharmony_ci pr_err("Failed to create CPs IRQ domain\n"); 43862306a36Sopenharmony_ci ret = -ENOMEM; 43962306a36Sopenharmony_ci goto remove_ap_domain; 44062306a36Sopenharmony_ci } 44162306a36Sopenharmony_ci 44262306a36Sopenharmony_ci irq_domain_update_bus_token(sei->cp_domain, DOMAIN_BUS_GENERIC_MSI); 44362306a36Sopenharmony_ci 44462306a36Sopenharmony_ci plat_domain = platform_msi_create_irq_domain(of_node_to_fwnode(node), 44562306a36Sopenharmony_ci &mvebu_sei_msi_domain_info, 44662306a36Sopenharmony_ci sei->cp_domain); 44762306a36Sopenharmony_ci if (!plat_domain) { 44862306a36Sopenharmony_ci pr_err("Failed to create CPs MSI domain\n"); 44962306a36Sopenharmony_ci ret = -ENOMEM; 45062306a36Sopenharmony_ci goto remove_cp_domain; 45162306a36Sopenharmony_ci } 45262306a36Sopenharmony_ci 45362306a36Sopenharmony_ci mvebu_sei_reset(sei); 45462306a36Sopenharmony_ci 45562306a36Sopenharmony_ci irq_set_chained_handler_and_data(parent_irq, 45662306a36Sopenharmony_ci mvebu_sei_handle_cascade_irq, 45762306a36Sopenharmony_ci sei); 45862306a36Sopenharmony_ci 45962306a36Sopenharmony_ci return 0; 46062306a36Sopenharmony_ci 46162306a36Sopenharmony_ciremove_cp_domain: 46262306a36Sopenharmony_ci irq_domain_remove(sei->cp_domain); 46362306a36Sopenharmony_ciremove_ap_domain: 46462306a36Sopenharmony_ci irq_domain_remove(sei->ap_domain); 46562306a36Sopenharmony_ciremove_sei_domain: 46662306a36Sopenharmony_ci irq_domain_remove(sei->sei_domain); 46762306a36Sopenharmony_cidispose_irq: 46862306a36Sopenharmony_ci irq_dispose_mapping(parent_irq); 46962306a36Sopenharmony_ci 47062306a36Sopenharmony_ci return ret; 47162306a36Sopenharmony_ci} 47262306a36Sopenharmony_ci 47362306a36Sopenharmony_cistatic struct mvebu_sei_caps mvebu_sei_ap806_caps = { 47462306a36Sopenharmony_ci .ap_range = { 47562306a36Sopenharmony_ci .first = 0, 47662306a36Sopenharmony_ci .size = 21, 47762306a36Sopenharmony_ci }, 47862306a36Sopenharmony_ci .cp_range = { 47962306a36Sopenharmony_ci .first = 21, 48062306a36Sopenharmony_ci .size = 43, 48162306a36Sopenharmony_ci }, 48262306a36Sopenharmony_ci}; 48362306a36Sopenharmony_ci 48462306a36Sopenharmony_cistatic const struct of_device_id mvebu_sei_of_match[] = { 48562306a36Sopenharmony_ci { 48662306a36Sopenharmony_ci .compatible = "marvell,ap806-sei", 48762306a36Sopenharmony_ci .data = &mvebu_sei_ap806_caps, 48862306a36Sopenharmony_ci }, 48962306a36Sopenharmony_ci {}, 49062306a36Sopenharmony_ci}; 49162306a36Sopenharmony_ci 49262306a36Sopenharmony_cistatic struct platform_driver mvebu_sei_driver = { 49362306a36Sopenharmony_ci .probe = mvebu_sei_probe, 49462306a36Sopenharmony_ci .driver = { 49562306a36Sopenharmony_ci .name = "mvebu-sei", 49662306a36Sopenharmony_ci .of_match_table = mvebu_sei_of_match, 49762306a36Sopenharmony_ci }, 49862306a36Sopenharmony_ci}; 49962306a36Sopenharmony_cibuiltin_platform_driver(mvebu_sei_driver); 500