18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * Copyright (C) 2020, Jiaxun Yang <jiaxun.yang@flygoat.com> 48c2ecf20Sopenharmony_ci * Jianmin Lv <lvjianmin@loongson.cn> 58c2ecf20Sopenharmony_ci * Huacai Chen <chenhuacai@loongson.cn> 68c2ecf20Sopenharmony_ci * Loongson HyperTransport Interrupt Vector support 78c2ecf20Sopenharmony_ci */ 88c2ecf20Sopenharmony_ci 98c2ecf20Sopenharmony_ci#define pr_fmt(fmt) "htvec: " fmt 108c2ecf20Sopenharmony_ci 118c2ecf20Sopenharmony_ci#include <linux/interrupt.h> 128c2ecf20Sopenharmony_ci#include <linux/irq.h> 138c2ecf20Sopenharmony_ci#include <linux/irqchip.h> 148c2ecf20Sopenharmony_ci#include <linux/irqdomain.h> 158c2ecf20Sopenharmony_ci#include <linux/irqchip/chained_irq.h> 168c2ecf20Sopenharmony_ci#include <linux/kernel.h> 178c2ecf20Sopenharmony_ci#include <linux/platform_device.h> 188c2ecf20Sopenharmony_ci#include <linux/of_address.h> 198c2ecf20Sopenharmony_ci#include <linux/of_irq.h> 208c2ecf20Sopenharmony_ci#include <linux/of_platform.h> 218c2ecf20Sopenharmony_ci#include <linux/syscore_ops.h> 228c2ecf20Sopenharmony_ci 238c2ecf20Sopenharmony_ci/* Registers */ 248c2ecf20Sopenharmony_ci#define HTVEC_EN_OFF 0x20 258c2ecf20Sopenharmony_ci#define HTVEC_MAX_PARENT_IRQ 8 268c2ecf20Sopenharmony_ci#define VEC_COUNT_PER_REG 32 278c2ecf20Sopenharmony_ci#define VEC_REG_IDX(irq_id) ((irq_id) / VEC_COUNT_PER_REG) 288c2ecf20Sopenharmony_ci#define VEC_REG_BIT(irq_id) ((irq_id) % VEC_COUNT_PER_REG) 298c2ecf20Sopenharmony_ci#define HTVEC_SIZE 0x400 308c2ecf20Sopenharmony_ci 318c2ecf20Sopenharmony_cistruct htvec { 328c2ecf20Sopenharmony_ci int num_parents; 338c2ecf20Sopenharmony_ci void __iomem *base; 348c2ecf20Sopenharmony_ci struct irq_domain *htvec_domain; 358c2ecf20Sopenharmony_ci raw_spinlock_t htvec_lock; 368c2ecf20Sopenharmony_ci struct fwnode_handle *domain_handle; 378c2ecf20Sopenharmony_ci u32 saved_vec_en[HTVEC_MAX_PARENT_IRQ]; 388c2ecf20Sopenharmony_ci}; 398c2ecf20Sopenharmony_ci 408c2ecf20Sopenharmony_cistatic struct htvec *htvec_priv; 418c2ecf20Sopenharmony_ci 428c2ecf20Sopenharmony_cistatic void htvec_irq_dispatch(struct irq_desc *desc) 438c2ecf20Sopenharmony_ci{ 448c2ecf20Sopenharmony_ci int i; 458c2ecf20Sopenharmony_ci u32 pending; 468c2ecf20Sopenharmony_ci bool handled = false; 478c2ecf20Sopenharmony_ci struct irq_chip *chip = irq_desc_get_chip(desc); 488c2ecf20Sopenharmony_ci struct htvec *priv = irq_desc_get_handler_data(desc); 498c2ecf20Sopenharmony_ci 508c2ecf20Sopenharmony_ci chained_irq_enter(chip, desc); 518c2ecf20Sopenharmony_ci 528c2ecf20Sopenharmony_ci for (i = 0; i < priv->num_parents; i++) { 538c2ecf20Sopenharmony_ci pending = readl(priv->base + 4 * i); 548c2ecf20Sopenharmony_ci while (pending) { 558c2ecf20Sopenharmony_ci int bit = __ffs(pending); 568c2ecf20Sopenharmony_ci 578c2ecf20Sopenharmony_ci generic_handle_irq(irq_linear_revmap(priv->htvec_domain, bit + 588c2ecf20Sopenharmony_ci VEC_COUNT_PER_REG * i)); 598c2ecf20Sopenharmony_ci pending &= ~BIT(bit); 608c2ecf20Sopenharmony_ci handled = true; 618c2ecf20Sopenharmony_ci } 628c2ecf20Sopenharmony_ci } 638c2ecf20Sopenharmony_ci 648c2ecf20Sopenharmony_ci if (!handled) 658c2ecf20Sopenharmony_ci spurious_interrupt(); 668c2ecf20Sopenharmony_ci 678c2ecf20Sopenharmony_ci chained_irq_exit(chip, desc); 688c2ecf20Sopenharmony_ci} 698c2ecf20Sopenharmony_ci 708c2ecf20Sopenharmony_cistatic void htvec_ack_irq(struct irq_data *d) 718c2ecf20Sopenharmony_ci{ 728c2ecf20Sopenharmony_ci struct htvec *priv = irq_data_get_irq_chip_data(d); 738c2ecf20Sopenharmony_ci 748c2ecf20Sopenharmony_ci writel(BIT(VEC_REG_BIT(d->hwirq)), 758c2ecf20Sopenharmony_ci priv->base + VEC_REG_IDX(d->hwirq) * 4); 768c2ecf20Sopenharmony_ci} 778c2ecf20Sopenharmony_ci 788c2ecf20Sopenharmony_cistatic void htvec_mask_irq(struct irq_data *d) 798c2ecf20Sopenharmony_ci{ 808c2ecf20Sopenharmony_ci u32 reg; 818c2ecf20Sopenharmony_ci void __iomem *addr; 828c2ecf20Sopenharmony_ci struct htvec *priv = irq_data_get_irq_chip_data(d); 838c2ecf20Sopenharmony_ci 848c2ecf20Sopenharmony_ci raw_spin_lock(&priv->htvec_lock); 858c2ecf20Sopenharmony_ci addr = priv->base + HTVEC_EN_OFF; 868c2ecf20Sopenharmony_ci addr += VEC_REG_IDX(d->hwirq) * 4; 878c2ecf20Sopenharmony_ci reg = readl(addr); 888c2ecf20Sopenharmony_ci reg &= ~BIT(VEC_REG_BIT(d->hwirq)); 898c2ecf20Sopenharmony_ci writel(reg, addr); 908c2ecf20Sopenharmony_ci raw_spin_unlock(&priv->htvec_lock); 918c2ecf20Sopenharmony_ci} 928c2ecf20Sopenharmony_ci 938c2ecf20Sopenharmony_cistatic void htvec_unmask_irq(struct irq_data *d) 948c2ecf20Sopenharmony_ci{ 958c2ecf20Sopenharmony_ci u32 reg; 968c2ecf20Sopenharmony_ci void __iomem *addr; 978c2ecf20Sopenharmony_ci struct htvec *priv = irq_data_get_irq_chip_data(d); 988c2ecf20Sopenharmony_ci 998c2ecf20Sopenharmony_ci raw_spin_lock(&priv->htvec_lock); 1008c2ecf20Sopenharmony_ci addr = priv->base + HTVEC_EN_OFF; 1018c2ecf20Sopenharmony_ci addr += VEC_REG_IDX(d->hwirq) * 4; 1028c2ecf20Sopenharmony_ci reg = readl(addr); 1038c2ecf20Sopenharmony_ci reg |= BIT(VEC_REG_BIT(d->hwirq)); 1048c2ecf20Sopenharmony_ci writel(reg, addr); 1058c2ecf20Sopenharmony_ci raw_spin_unlock(&priv->htvec_lock); 1068c2ecf20Sopenharmony_ci} 1078c2ecf20Sopenharmony_ci 1088c2ecf20Sopenharmony_cistatic struct irq_chip htvec_irq_chip = { 1098c2ecf20Sopenharmony_ci .name = "LOONGSON_HTVEC", 1108c2ecf20Sopenharmony_ci .irq_mask = htvec_mask_irq, 1118c2ecf20Sopenharmony_ci .irq_unmask = htvec_unmask_irq, 1128c2ecf20Sopenharmony_ci .irq_ack = htvec_ack_irq, 1138c2ecf20Sopenharmony_ci}; 1148c2ecf20Sopenharmony_ci 1158c2ecf20Sopenharmony_cistatic int htvec_domain_alloc(struct irq_domain *domain, unsigned int virq, 1168c2ecf20Sopenharmony_ci unsigned int nr_irqs, void *arg) 1178c2ecf20Sopenharmony_ci{ 1188c2ecf20Sopenharmony_ci int ret; 1198c2ecf20Sopenharmony_ci unsigned long hwirq; 1208c2ecf20Sopenharmony_ci unsigned int type, i; 1218c2ecf20Sopenharmony_ci struct htvec *priv = domain->host_data; 1228c2ecf20Sopenharmony_ci 1238c2ecf20Sopenharmony_ci ret = irq_domain_translate_onecell(domain, arg, &hwirq, &type); 1248c2ecf20Sopenharmony_ci if (ret) 1258c2ecf20Sopenharmony_ci return ret; 1268c2ecf20Sopenharmony_ci 1278c2ecf20Sopenharmony_ci for (i = 0; i < nr_irqs; i++) { 1288c2ecf20Sopenharmony_ci irq_domain_set_info(domain, virq + i, hwirq + i, &htvec_irq_chip, 1298c2ecf20Sopenharmony_ci priv, handle_edge_irq, NULL, NULL); 1308c2ecf20Sopenharmony_ci } 1318c2ecf20Sopenharmony_ci 1328c2ecf20Sopenharmony_ci return 0; 1338c2ecf20Sopenharmony_ci} 1348c2ecf20Sopenharmony_ci 1358c2ecf20Sopenharmony_cistatic void htvec_domain_free(struct irq_domain *domain, unsigned int virq, 1368c2ecf20Sopenharmony_ci unsigned int nr_irqs) 1378c2ecf20Sopenharmony_ci{ 1388c2ecf20Sopenharmony_ci int i; 1398c2ecf20Sopenharmony_ci 1408c2ecf20Sopenharmony_ci for (i = 0; i < nr_irqs; i++) { 1418c2ecf20Sopenharmony_ci struct irq_data *d = irq_domain_get_irq_data(domain, virq + i); 1428c2ecf20Sopenharmony_ci 1438c2ecf20Sopenharmony_ci irq_set_handler(virq + i, NULL); 1448c2ecf20Sopenharmony_ci irq_domain_reset_irq_data(d); 1458c2ecf20Sopenharmony_ci } 1468c2ecf20Sopenharmony_ci} 1478c2ecf20Sopenharmony_ci 1488c2ecf20Sopenharmony_cistatic const struct irq_domain_ops htvec_domain_ops = { 1498c2ecf20Sopenharmony_ci .translate = irq_domain_translate_onecell, 1508c2ecf20Sopenharmony_ci .alloc = htvec_domain_alloc, 1518c2ecf20Sopenharmony_ci .free = htvec_domain_free, 1528c2ecf20Sopenharmony_ci}; 1538c2ecf20Sopenharmony_ci 1548c2ecf20Sopenharmony_cistatic void htvec_reset(struct htvec *priv) 1558c2ecf20Sopenharmony_ci{ 1568c2ecf20Sopenharmony_ci u32 idx; 1578c2ecf20Sopenharmony_ci 1588c2ecf20Sopenharmony_ci /* Clear IRQ cause registers, mask all interrupts */ 1598c2ecf20Sopenharmony_ci for (idx = 0; idx < priv->num_parents; idx++) { 1608c2ecf20Sopenharmony_ci writel_relaxed(0x0, priv->base + HTVEC_EN_OFF + 4 * idx); 1618c2ecf20Sopenharmony_ci writel_relaxed(0xFFFFFFFF, priv->base + 4 * idx); 1628c2ecf20Sopenharmony_ci } 1638c2ecf20Sopenharmony_ci} 1648c2ecf20Sopenharmony_ci 1658c2ecf20Sopenharmony_cistatic int htvec_suspend(void) 1668c2ecf20Sopenharmony_ci{ 1678c2ecf20Sopenharmony_ci int i; 1688c2ecf20Sopenharmony_ci for (i = 0; i < htvec_priv->num_parents; i++) { 1698c2ecf20Sopenharmony_ci htvec_priv->saved_vec_en[i] = readl(htvec_priv->base + HTVEC_EN_OFF + 4 * i); 1708c2ecf20Sopenharmony_ci } 1718c2ecf20Sopenharmony_ci return 0; 1728c2ecf20Sopenharmony_ci} 1738c2ecf20Sopenharmony_ci 1748c2ecf20Sopenharmony_cistatic void htvec_resume(void) 1758c2ecf20Sopenharmony_ci{ 1768c2ecf20Sopenharmony_ci int i; 1778c2ecf20Sopenharmony_ci for (i = 0; i < htvec_priv->num_parents; i++) { 1788c2ecf20Sopenharmony_ci writel(htvec_priv->saved_vec_en[i], htvec_priv->base + HTVEC_EN_OFF + 4 * i); 1798c2ecf20Sopenharmony_ci } 1808c2ecf20Sopenharmony_ci} 1818c2ecf20Sopenharmony_ci 1828c2ecf20Sopenharmony_cistatic struct syscore_ops htvec_syscore_ops = { 1838c2ecf20Sopenharmony_ci .suspend = htvec_suspend, 1848c2ecf20Sopenharmony_ci .resume = htvec_resume, 1858c2ecf20Sopenharmony_ci}; 1868c2ecf20Sopenharmony_ci 1878c2ecf20Sopenharmony_cistatic int htvec_init(phys_addr_t addr, unsigned long size, 1888c2ecf20Sopenharmony_ci int num_parents, int parent_irq[], struct fwnode_handle *domain_handle) 1898c2ecf20Sopenharmony_ci{ 1908c2ecf20Sopenharmony_ci int i; 1918c2ecf20Sopenharmony_ci struct htvec *priv; 1928c2ecf20Sopenharmony_ci 1938c2ecf20Sopenharmony_ci priv = kzalloc(sizeof(*priv), GFP_KERNEL); 1948c2ecf20Sopenharmony_ci if (!priv) 1958c2ecf20Sopenharmony_ci return -ENOMEM; 1968c2ecf20Sopenharmony_ci 1978c2ecf20Sopenharmony_ci priv->num_parents = num_parents; 1988c2ecf20Sopenharmony_ci priv->base = ioremap(addr, size); 1998c2ecf20Sopenharmony_ci priv->domain_handle = domain_handle; 2008c2ecf20Sopenharmony_ci raw_spin_lock_init(&priv->htvec_lock); 2018c2ecf20Sopenharmony_ci 2028c2ecf20Sopenharmony_ci /* Setup IRQ domain */ 2038c2ecf20Sopenharmony_ci priv->htvec_domain = irq_domain_create_linear(priv->domain_handle, 2048c2ecf20Sopenharmony_ci (VEC_COUNT_PER_REG * priv->num_parents), 2058c2ecf20Sopenharmony_ci &htvec_domain_ops, priv); 2068c2ecf20Sopenharmony_ci if (!priv->htvec_domain) { 2078c2ecf20Sopenharmony_ci pr_err("loongson-htvec: cannot add IRQ domain\n"); 2088c2ecf20Sopenharmony_ci goto iounmap_base; 2098c2ecf20Sopenharmony_ci } 2108c2ecf20Sopenharmony_ci 2118c2ecf20Sopenharmony_ci htvec_reset(priv); 2128c2ecf20Sopenharmony_ci 2138c2ecf20Sopenharmony_ci for (i = 0; i < priv->num_parents; i++) { 2148c2ecf20Sopenharmony_ci irq_set_chained_handler_and_data(parent_irq[i], 2158c2ecf20Sopenharmony_ci htvec_irq_dispatch, priv); 2168c2ecf20Sopenharmony_ci } 2178c2ecf20Sopenharmony_ci 2188c2ecf20Sopenharmony_ci htvec_priv = priv; 2198c2ecf20Sopenharmony_ci 2208c2ecf20Sopenharmony_ci register_syscore_ops(&htvec_syscore_ops); 2218c2ecf20Sopenharmony_ci 2228c2ecf20Sopenharmony_ci return 0; 2238c2ecf20Sopenharmony_ci 2248c2ecf20Sopenharmony_ciiounmap_base: 2258c2ecf20Sopenharmony_ci iounmap(priv->base); 2268c2ecf20Sopenharmony_ci priv->domain_handle = NULL; 2278c2ecf20Sopenharmony_ci kfree(priv); 2288c2ecf20Sopenharmony_ci 2298c2ecf20Sopenharmony_ci return -EINVAL; 2308c2ecf20Sopenharmony_ci} 2318c2ecf20Sopenharmony_ci 2328c2ecf20Sopenharmony_ci#ifdef CONFIG_OF 2338c2ecf20Sopenharmony_ci 2348c2ecf20Sopenharmony_cistatic int htvec_of_init(struct device_node *node, 2358c2ecf20Sopenharmony_ci struct device_node *parent) 2368c2ecf20Sopenharmony_ci{ 2378c2ecf20Sopenharmony_ci int i, err; 2388c2ecf20Sopenharmony_ci int parent_irq[8]; 2398c2ecf20Sopenharmony_ci int num_parents = 0; 2408c2ecf20Sopenharmony_ci struct resource res; 2418c2ecf20Sopenharmony_ci 2428c2ecf20Sopenharmony_ci if (of_address_to_resource(node, 0, &res)) 2438c2ecf20Sopenharmony_ci return -EINVAL; 2448c2ecf20Sopenharmony_ci 2458c2ecf20Sopenharmony_ci /* Interrupt may come from any of the 8 interrupt lines */ 2468c2ecf20Sopenharmony_ci for (i = 0; i < HTVEC_MAX_PARENT_IRQ; i++) { 2478c2ecf20Sopenharmony_ci parent_irq[i] = irq_of_parse_and_map(node, i); 2488c2ecf20Sopenharmony_ci if (parent_irq[i] <= 0) 2498c2ecf20Sopenharmony_ci break; 2508c2ecf20Sopenharmony_ci 2518c2ecf20Sopenharmony_ci num_parents++; 2528c2ecf20Sopenharmony_ci } 2538c2ecf20Sopenharmony_ci 2548c2ecf20Sopenharmony_ci err = htvec_init(res.start, resource_size(&res), 2558c2ecf20Sopenharmony_ci num_parents, parent_irq, of_node_to_fwnode(node)); 2568c2ecf20Sopenharmony_ci if (err < 0) 2578c2ecf20Sopenharmony_ci return err; 2588c2ecf20Sopenharmony_ci 2598c2ecf20Sopenharmony_ci return 0; 2608c2ecf20Sopenharmony_ci} 2618c2ecf20Sopenharmony_ci 2628c2ecf20Sopenharmony_ciIRQCHIP_DECLARE(htvec, "loongson,htvec-1.0", htvec_of_init); 2638c2ecf20Sopenharmony_ci 2648c2ecf20Sopenharmony_ci#endif 2658c2ecf20Sopenharmony_ci 2668c2ecf20Sopenharmony_ci#ifdef CONFIG_ACPI 2678c2ecf20Sopenharmony_ci 2688c2ecf20Sopenharmony_cistruct irq_domain *htvec_acpi_init(struct irq_domain *parent, 2698c2ecf20Sopenharmony_ci struct acpi_madt_ht_pic *acpi_htvec) 2708c2ecf20Sopenharmony_ci{ 2718c2ecf20Sopenharmony_ci int i, ret; 2728c2ecf20Sopenharmony_ci int num_parents, parent_irq[8]; 2738c2ecf20Sopenharmony_ci struct fwnode_handle *domain_handle; 2748c2ecf20Sopenharmony_ci 2758c2ecf20Sopenharmony_ci if (!acpi_htvec) 2768c2ecf20Sopenharmony_ci return NULL; 2778c2ecf20Sopenharmony_ci 2788c2ecf20Sopenharmony_ci num_parents = HTVEC_MAX_PARENT_IRQ; 2798c2ecf20Sopenharmony_ci 2808c2ecf20Sopenharmony_ci domain_handle = irq_domain_alloc_fwnode((phys_addr_t *)acpi_htvec); 2818c2ecf20Sopenharmony_ci if (!domain_handle) { 2828c2ecf20Sopenharmony_ci pr_err("Unable to allocate domain handle\n"); 2838c2ecf20Sopenharmony_ci return NULL; 2848c2ecf20Sopenharmony_ci } 2858c2ecf20Sopenharmony_ci 2868c2ecf20Sopenharmony_ci /* Interrupt may come from any of the 8 interrupt lines */ 2878c2ecf20Sopenharmony_ci for (i = 0; i < HTVEC_MAX_PARENT_IRQ; i++) 2888c2ecf20Sopenharmony_ci parent_irq[i] = irq_create_mapping(parent, acpi_htvec->cascade[i]); 2898c2ecf20Sopenharmony_ci 2908c2ecf20Sopenharmony_ci ret = htvec_init(acpi_htvec->address, acpi_htvec->size, 2918c2ecf20Sopenharmony_ci num_parents, parent_irq, domain_handle); 2928c2ecf20Sopenharmony_ci if (ret < 0) 2938c2ecf20Sopenharmony_ci return NULL; 2948c2ecf20Sopenharmony_ci 2958c2ecf20Sopenharmony_ci return irq_find_matching_fwnode(domain_handle, DOMAIN_BUS_ANY); 2968c2ecf20Sopenharmony_ci} 2978c2ecf20Sopenharmony_ci 2988c2ecf20Sopenharmony_ci#endif 299