18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause) 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * Copyright (c) 2020 MediaTek Inc. 48c2ecf20Sopenharmony_ci * Author Mark-PK Tsai <mark-pk.tsai@mediatek.com> 58c2ecf20Sopenharmony_ci */ 68c2ecf20Sopenharmony_ci#include <linux/interrupt.h> 78c2ecf20Sopenharmony_ci#include <linux/io.h> 88c2ecf20Sopenharmony_ci#include <linux/irq.h> 98c2ecf20Sopenharmony_ci#include <linux/irqchip.h> 108c2ecf20Sopenharmony_ci#include <linux/irqdomain.h> 118c2ecf20Sopenharmony_ci#include <linux/of.h> 128c2ecf20Sopenharmony_ci#include <linux/of_address.h> 138c2ecf20Sopenharmony_ci#include <linux/of_irq.h> 148c2ecf20Sopenharmony_ci#include <linux/slab.h> 158c2ecf20Sopenharmony_ci#include <linux/spinlock.h> 168c2ecf20Sopenharmony_ci 178c2ecf20Sopenharmony_ci#define INTC_MASK 0x0 188c2ecf20Sopenharmony_ci#define INTC_EOI 0x20 198c2ecf20Sopenharmony_ci 208c2ecf20Sopenharmony_cistruct mst_intc_chip_data { 218c2ecf20Sopenharmony_ci raw_spinlock_t lock; 228c2ecf20Sopenharmony_ci unsigned int irq_start, nr_irqs; 238c2ecf20Sopenharmony_ci void __iomem *base; 248c2ecf20Sopenharmony_ci bool no_eoi; 258c2ecf20Sopenharmony_ci}; 268c2ecf20Sopenharmony_ci 278c2ecf20Sopenharmony_cistatic void mst_set_irq(struct irq_data *d, u32 offset) 288c2ecf20Sopenharmony_ci{ 298c2ecf20Sopenharmony_ci irq_hw_number_t hwirq = irqd_to_hwirq(d); 308c2ecf20Sopenharmony_ci struct mst_intc_chip_data *cd = irq_data_get_irq_chip_data(d); 318c2ecf20Sopenharmony_ci u16 val, mask; 328c2ecf20Sopenharmony_ci unsigned long flags; 338c2ecf20Sopenharmony_ci 348c2ecf20Sopenharmony_ci mask = 1 << (hwirq % 16); 358c2ecf20Sopenharmony_ci offset += (hwirq / 16) * 4; 368c2ecf20Sopenharmony_ci 378c2ecf20Sopenharmony_ci raw_spin_lock_irqsave(&cd->lock, flags); 388c2ecf20Sopenharmony_ci val = readw_relaxed(cd->base + offset) | mask; 398c2ecf20Sopenharmony_ci writew_relaxed(val, cd->base + offset); 408c2ecf20Sopenharmony_ci raw_spin_unlock_irqrestore(&cd->lock, flags); 418c2ecf20Sopenharmony_ci} 428c2ecf20Sopenharmony_ci 438c2ecf20Sopenharmony_cistatic void mst_clear_irq(struct irq_data *d, u32 offset) 448c2ecf20Sopenharmony_ci{ 458c2ecf20Sopenharmony_ci irq_hw_number_t hwirq = irqd_to_hwirq(d); 468c2ecf20Sopenharmony_ci struct mst_intc_chip_data *cd = irq_data_get_irq_chip_data(d); 478c2ecf20Sopenharmony_ci u16 val, mask; 488c2ecf20Sopenharmony_ci unsigned long flags; 498c2ecf20Sopenharmony_ci 508c2ecf20Sopenharmony_ci mask = 1 << (hwirq % 16); 518c2ecf20Sopenharmony_ci offset += (hwirq / 16) * 4; 528c2ecf20Sopenharmony_ci 538c2ecf20Sopenharmony_ci raw_spin_lock_irqsave(&cd->lock, flags); 548c2ecf20Sopenharmony_ci val = readw_relaxed(cd->base + offset) & ~mask; 558c2ecf20Sopenharmony_ci writew_relaxed(val, cd->base + offset); 568c2ecf20Sopenharmony_ci raw_spin_unlock_irqrestore(&cd->lock, flags); 578c2ecf20Sopenharmony_ci} 588c2ecf20Sopenharmony_ci 598c2ecf20Sopenharmony_cistatic void mst_intc_mask_irq(struct irq_data *d) 608c2ecf20Sopenharmony_ci{ 618c2ecf20Sopenharmony_ci mst_set_irq(d, INTC_MASK); 628c2ecf20Sopenharmony_ci irq_chip_mask_parent(d); 638c2ecf20Sopenharmony_ci} 648c2ecf20Sopenharmony_ci 658c2ecf20Sopenharmony_cistatic void mst_intc_unmask_irq(struct irq_data *d) 668c2ecf20Sopenharmony_ci{ 678c2ecf20Sopenharmony_ci mst_clear_irq(d, INTC_MASK); 688c2ecf20Sopenharmony_ci irq_chip_unmask_parent(d); 698c2ecf20Sopenharmony_ci} 708c2ecf20Sopenharmony_ci 718c2ecf20Sopenharmony_cistatic void mst_intc_eoi_irq(struct irq_data *d) 728c2ecf20Sopenharmony_ci{ 738c2ecf20Sopenharmony_ci struct mst_intc_chip_data *cd = irq_data_get_irq_chip_data(d); 748c2ecf20Sopenharmony_ci 758c2ecf20Sopenharmony_ci if (!cd->no_eoi) 768c2ecf20Sopenharmony_ci mst_set_irq(d, INTC_EOI); 778c2ecf20Sopenharmony_ci 788c2ecf20Sopenharmony_ci irq_chip_eoi_parent(d); 798c2ecf20Sopenharmony_ci} 808c2ecf20Sopenharmony_ci 818c2ecf20Sopenharmony_cistatic struct irq_chip mst_intc_chip = { 828c2ecf20Sopenharmony_ci .name = "mst-intc", 838c2ecf20Sopenharmony_ci .irq_mask = mst_intc_mask_irq, 848c2ecf20Sopenharmony_ci .irq_unmask = mst_intc_unmask_irq, 858c2ecf20Sopenharmony_ci .irq_eoi = mst_intc_eoi_irq, 868c2ecf20Sopenharmony_ci .irq_get_irqchip_state = irq_chip_get_parent_state, 878c2ecf20Sopenharmony_ci .irq_set_irqchip_state = irq_chip_set_parent_state, 888c2ecf20Sopenharmony_ci .irq_set_affinity = irq_chip_set_affinity_parent, 898c2ecf20Sopenharmony_ci .irq_set_vcpu_affinity = irq_chip_set_vcpu_affinity_parent, 908c2ecf20Sopenharmony_ci .irq_set_type = irq_chip_set_type_parent, 918c2ecf20Sopenharmony_ci .irq_retrigger = irq_chip_retrigger_hierarchy, 928c2ecf20Sopenharmony_ci .flags = IRQCHIP_SET_TYPE_MASKED | 938c2ecf20Sopenharmony_ci IRQCHIP_SKIP_SET_WAKE | 948c2ecf20Sopenharmony_ci IRQCHIP_MASK_ON_SUSPEND, 958c2ecf20Sopenharmony_ci}; 968c2ecf20Sopenharmony_ci 978c2ecf20Sopenharmony_cistatic int mst_intc_domain_translate(struct irq_domain *d, 988c2ecf20Sopenharmony_ci struct irq_fwspec *fwspec, 998c2ecf20Sopenharmony_ci unsigned long *hwirq, 1008c2ecf20Sopenharmony_ci unsigned int *type) 1018c2ecf20Sopenharmony_ci{ 1028c2ecf20Sopenharmony_ci struct mst_intc_chip_data *cd = d->host_data; 1038c2ecf20Sopenharmony_ci 1048c2ecf20Sopenharmony_ci if (is_of_node(fwspec->fwnode)) { 1058c2ecf20Sopenharmony_ci if (fwspec->param_count != 3) 1068c2ecf20Sopenharmony_ci return -EINVAL; 1078c2ecf20Sopenharmony_ci 1088c2ecf20Sopenharmony_ci /* No PPI should point to this domain */ 1098c2ecf20Sopenharmony_ci if (fwspec->param[0] != 0) 1108c2ecf20Sopenharmony_ci return -EINVAL; 1118c2ecf20Sopenharmony_ci 1128c2ecf20Sopenharmony_ci if (fwspec->param[1] >= cd->nr_irqs) 1138c2ecf20Sopenharmony_ci return -EINVAL; 1148c2ecf20Sopenharmony_ci 1158c2ecf20Sopenharmony_ci *hwirq = fwspec->param[1]; 1168c2ecf20Sopenharmony_ci *type = fwspec->param[2] & IRQ_TYPE_SENSE_MASK; 1178c2ecf20Sopenharmony_ci return 0; 1188c2ecf20Sopenharmony_ci } 1198c2ecf20Sopenharmony_ci 1208c2ecf20Sopenharmony_ci return -EINVAL; 1218c2ecf20Sopenharmony_ci} 1228c2ecf20Sopenharmony_ci 1238c2ecf20Sopenharmony_cistatic int mst_intc_domain_alloc(struct irq_domain *domain, unsigned int virq, 1248c2ecf20Sopenharmony_ci unsigned int nr_irqs, void *data) 1258c2ecf20Sopenharmony_ci{ 1268c2ecf20Sopenharmony_ci int i; 1278c2ecf20Sopenharmony_ci irq_hw_number_t hwirq; 1288c2ecf20Sopenharmony_ci struct irq_fwspec parent_fwspec, *fwspec = data; 1298c2ecf20Sopenharmony_ci struct mst_intc_chip_data *cd = domain->host_data; 1308c2ecf20Sopenharmony_ci 1318c2ecf20Sopenharmony_ci /* Not GIC compliant */ 1328c2ecf20Sopenharmony_ci if (fwspec->param_count != 3) 1338c2ecf20Sopenharmony_ci return -EINVAL; 1348c2ecf20Sopenharmony_ci 1358c2ecf20Sopenharmony_ci /* No PPI should point to this domain */ 1368c2ecf20Sopenharmony_ci if (fwspec->param[0]) 1378c2ecf20Sopenharmony_ci return -EINVAL; 1388c2ecf20Sopenharmony_ci 1398c2ecf20Sopenharmony_ci hwirq = fwspec->param[1]; 1408c2ecf20Sopenharmony_ci for (i = 0; i < nr_irqs; i++) 1418c2ecf20Sopenharmony_ci irq_domain_set_hwirq_and_chip(domain, virq + i, hwirq + i, 1428c2ecf20Sopenharmony_ci &mst_intc_chip, 1438c2ecf20Sopenharmony_ci domain->host_data); 1448c2ecf20Sopenharmony_ci 1458c2ecf20Sopenharmony_ci parent_fwspec = *fwspec; 1468c2ecf20Sopenharmony_ci parent_fwspec.fwnode = domain->parent->fwnode; 1478c2ecf20Sopenharmony_ci parent_fwspec.param[1] = cd->irq_start + hwirq; 1488c2ecf20Sopenharmony_ci return irq_domain_alloc_irqs_parent(domain, virq, nr_irqs, &parent_fwspec); 1498c2ecf20Sopenharmony_ci} 1508c2ecf20Sopenharmony_ci 1518c2ecf20Sopenharmony_cistatic const struct irq_domain_ops mst_intc_domain_ops = { 1528c2ecf20Sopenharmony_ci .translate = mst_intc_domain_translate, 1538c2ecf20Sopenharmony_ci .alloc = mst_intc_domain_alloc, 1548c2ecf20Sopenharmony_ci .free = irq_domain_free_irqs_common, 1558c2ecf20Sopenharmony_ci}; 1568c2ecf20Sopenharmony_ci 1578c2ecf20Sopenharmony_cistatic int __init mst_intc_of_init(struct device_node *dn, 1588c2ecf20Sopenharmony_ci struct device_node *parent) 1598c2ecf20Sopenharmony_ci{ 1608c2ecf20Sopenharmony_ci struct irq_domain *domain, *domain_parent; 1618c2ecf20Sopenharmony_ci struct mst_intc_chip_data *cd; 1628c2ecf20Sopenharmony_ci u32 irq_start, irq_end; 1638c2ecf20Sopenharmony_ci 1648c2ecf20Sopenharmony_ci domain_parent = irq_find_host(parent); 1658c2ecf20Sopenharmony_ci if (!domain_parent) { 1668c2ecf20Sopenharmony_ci pr_err("mst-intc: interrupt-parent not found\n"); 1678c2ecf20Sopenharmony_ci return -EINVAL; 1688c2ecf20Sopenharmony_ci } 1698c2ecf20Sopenharmony_ci 1708c2ecf20Sopenharmony_ci if (of_property_read_u32_index(dn, "mstar,irqs-map-range", 0, &irq_start) || 1718c2ecf20Sopenharmony_ci of_property_read_u32_index(dn, "mstar,irqs-map-range", 1, &irq_end)) 1728c2ecf20Sopenharmony_ci return -EINVAL; 1738c2ecf20Sopenharmony_ci 1748c2ecf20Sopenharmony_ci cd = kzalloc(sizeof(*cd), GFP_KERNEL); 1758c2ecf20Sopenharmony_ci if (!cd) 1768c2ecf20Sopenharmony_ci return -ENOMEM; 1778c2ecf20Sopenharmony_ci 1788c2ecf20Sopenharmony_ci cd->base = of_iomap(dn, 0); 1798c2ecf20Sopenharmony_ci if (!cd->base) { 1808c2ecf20Sopenharmony_ci kfree(cd); 1818c2ecf20Sopenharmony_ci return -ENOMEM; 1828c2ecf20Sopenharmony_ci } 1838c2ecf20Sopenharmony_ci 1848c2ecf20Sopenharmony_ci cd->no_eoi = of_property_read_bool(dn, "mstar,intc-no-eoi"); 1858c2ecf20Sopenharmony_ci raw_spin_lock_init(&cd->lock); 1868c2ecf20Sopenharmony_ci cd->irq_start = irq_start; 1878c2ecf20Sopenharmony_ci cd->nr_irqs = irq_end - irq_start + 1; 1888c2ecf20Sopenharmony_ci domain = irq_domain_add_hierarchy(domain_parent, 0, cd->nr_irqs, dn, 1898c2ecf20Sopenharmony_ci &mst_intc_domain_ops, cd); 1908c2ecf20Sopenharmony_ci if (!domain) { 1918c2ecf20Sopenharmony_ci iounmap(cd->base); 1928c2ecf20Sopenharmony_ci kfree(cd); 1938c2ecf20Sopenharmony_ci return -ENOMEM; 1948c2ecf20Sopenharmony_ci } 1958c2ecf20Sopenharmony_ci 1968c2ecf20Sopenharmony_ci return 0; 1978c2ecf20Sopenharmony_ci} 1988c2ecf20Sopenharmony_ci 1998c2ecf20Sopenharmony_ciIRQCHIP_DECLARE(mst_intc, "mstar,mst-intc", mst_intc_of_init); 200