| 1 | |
| 2 | |
| 3 | * Copyright (C) 2024 Inochi Amaoto <[email protected]>
|
| 4 |
|
| 5 |
|
| 6 | #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
|
| 7 |
|
| 8 | #include <linux/cpu.h>
|
| 9 | #include <linux/interrupt.h>
|
| 10 | #include <linux/irqchip.h>
|
| 11 | #include <linux/irqchip/chained_irq.h>
|
| 12 | #include <linux/of_address.h>
|
| 13 | #include <linux/spinlock.h>
|
| 14 | #include <linux/smp.h>
|
| 15 | #include <linux/string_choices.h>
|
| 16 | #include <asm/sbi.h>
|
| 17 | #include <asm/vendorid_list.h>
|
| 18 |
|
| 19 | static int sswi_ipi_virq __ro_after_init;
|
| 20 | static DEFINE_PER_CPU(void __iomem *, sswi_cpu_regs);
|
| 21 |
|
| 22 | static void aclint_sswi_ipi_send(unsigned int cpu)
|
| 23 | {
|
| 24 | writel(0x1, per_cpu(sswi_cpu_regs, cpu));
|
| 25 | }
|
| 26 |
|
| 27 | static void aclint_sswi_ipi_clear(void)
|
| 28 | {
|
| 29 | writel_relaxed(0x0, this_cpu_read(sswi_cpu_regs));
|
| 30 | }
|
| 31 |
|
| 32 | static void aclint_sswi_ipi_handle(struct irq_desc *desc)
|
| 33 | {
|
| 34 | struct irq_chip *chip = irq_desc_get_chip(desc);
|
| 35 |
|
| 36 | chained_irq_enter(chip, desc);
|
| 37 |
|
| 38 | csr_clear(CSR_IP, IE_SIE);
|
| 39 | aclint_sswi_ipi_clear();
|
| 40 |
|
| 41 | ipi_mux_process();
|
| 42 |
|
| 43 | chained_irq_exit(chip, desc);
|
| 44 | }
|
| 45 |
|
| 46 | static int aclint_sswi_starting_cpu(unsigned int cpu)
|
| 47 | {
|
| 48 | enable_percpu_irq(sswi_ipi_virq, irq_get_trigger_type(sswi_ipi_virq));
|
| 49 |
|
| 50 | return 0;
|
| 51 | }
|
| 52 |
|
| 53 | static int aclint_sswi_dying_cpu(unsigned int cpu)
|
| 54 | {
|
| 55 | aclint_sswi_ipi_clear();
|
| 56 |
|
| 57 | disable_percpu_irq(sswi_ipi_virq);
|
| 58 |
|
| 59 | return 0;
|
| 60 | }
|
| 61 |
|
| 62 | static int __init aclint_sswi_parse_irq(struct fwnode_handle *fwnode, void __iomem *reg)
|
| 63 | {
|
| 64 | u32 contexts = of_irq_count(to_of_node(fwnode));
|
| 65 |
|
| 66 | if (!(contexts)) {
|
| 67 | pr_err("%pfwP: no ACLINT SSWI context available\n", fwnode);
|
| 68 | return -EINVAL;
|
| 69 | }
|
| 70 |
|
| 71 | for (u32 i = 0; i < contexts; i++) {
|
| 72 | struct of_phandle_args parent;
|
| 73 | unsigned long hartid;
|
| 74 | u32 hart_index;
|
| 75 | int rc, cpu;
|
| 76 |
|
| 77 | rc = of_irq_parse_one(to_of_node(fwnode), i, &parent);
|
| 78 | if (rc)
|
| 79 | return rc;
|
| 80 |
|
| 81 | rc = riscv_of_parent_hartid(parent.np, &hartid);
|
| 82 | if (rc)
|
| 83 | return rc;
|
| 84 |
|
| 85 | if (parent.args[0] != RV_IRQ_SOFT)
|
| 86 | return -ENOTSUPP;
|
| 87 |
|
| 88 | cpu = riscv_hartid_to_cpuid(hartid);
|
| 89 |
|
| 90 | rc = riscv_get_hart_index(fwnode, i, &hart_index);
|
| 91 | if (rc) {
|
| 92 | pr_warn("%pfwP: hart index [%d] not found\n", fwnode, i);
|
| 93 | return -EINVAL;
|
| 94 | }
|
| 95 | per_cpu(sswi_cpu_regs, cpu) = reg + hart_index * 4;
|
| 96 | }
|
| 97 |
|
| 98 | pr_info("%pfwP: register %u CPU%s\n", fwnode, contexts, str_plural(contexts));
|
| 99 |
|
| 100 | return 0;
|
| 101 | }
|
| 102 |
|
| 103 | static int __init aclint_sswi_probe(struct fwnode_handle *fwnode)
|
| 104 | {
|
| 105 | struct irq_domain *domain;
|
| 106 | void __iomem *reg;
|
| 107 | int virq, rc;
|
| 108 |
|
| 109 | if (!is_of_node(fwnode))
|
| 110 | return -EINVAL;
|
| 111 |
|
| 112 | reg = of_io_request_and_map(to_of_node(fwnode), 0, NULL);
|
| 113 | if (IS_ERR(reg)) {
|
| 114 | pr_err("%pfwP: Failed to map MMIO region\n", fwnode);
|
| 115 | return PTR_ERR(reg);
|
| 116 | }
|
| 117 |
|
| 118 |
|
| 119 | rc = aclint_sswi_parse_irq(fwnode, reg);
|
| 120 | if (rc < 0)
|
| 121 | return rc;
|
| 122 |
|
| 123 |
|
| 124 | if (sswi_ipi_virq)
|
| 125 | return 0;
|
| 126 |
|
| 127 |
|
| 128 | domain = irq_find_matching_fwnode(riscv_get_intc_hwnode(), DOMAIN_BUS_ANY);
|
| 129 | if (!domain) {
|
| 130 | pr_err("%pfwP: Failed to find INTC domain\n", fwnode);
|
| 131 | return -ENOENT;
|
| 132 | }
|
| 133 |
|
| 134 | sswi_ipi_virq = irq_create_mapping(domain, RV_IRQ_SOFT);
|
| 135 | if (!sswi_ipi_virq) {
|
| 136 | pr_err("unable to create ACLINT SSWI IRQ mapping\n");
|
| 137 | return -ENOMEM;
|
| 138 | }
|
| 139 |
|
| 140 |
|
| 141 | virq = ipi_mux_create(BITS_PER_BYTE, aclint_sswi_ipi_send);
|
| 142 | if (virq <= 0) {
|
| 143 | pr_err("unable to create muxed IPIs\n");
|
| 144 | irq_dispose_mapping(sswi_ipi_virq);
|
| 145 | return virq < 0 ? virq : -ENOMEM;
|
| 146 | }
|
| 147 |
|
| 148 | irq_set_chained_handler(sswi_ipi_virq, aclint_sswi_ipi_handle);
|
| 149 |
|
| 150 | cpuhp_setup_state(CPUHP_AP_IRQ_ACLINT_SSWI_STARTING,
|
| 151 | "irqchip/aclint-sswi:starting",
|
| 152 | aclint_sswi_starting_cpu,
|
| 153 | aclint_sswi_dying_cpu);
|
| 154 |
|
| 155 | riscv_ipi_set_virq_range(virq, BITS_PER_BYTE);
|
| 156 |
|
| 157 | return 0;
|
| 158 | }
|
| 159 |
|
| 160 |
|
| 161 | static int __init generic_aclint_sswi_probe(struct fwnode_handle *fwnode)
|
| 162 | {
|
| 163 | int rc;
|
| 164 |
|
| 165 | rc = aclint_sswi_probe(fwnode);
|
| 166 | if (rc)
|
| 167 | return rc;
|
| 168 |
|
| 169 |
|
| 170 | pr_info("providing IPIs using ACLINT SSWI\n");
|
| 171 |
|
| 172 | return 0;
|
| 173 | }
|
| 174 |
|
| 175 | static int __init generic_aclint_sswi_early_probe(struct device_node *node,
|
| 176 | struct device_node *parent)
|
| 177 | {
|
| 178 | return generic_aclint_sswi_probe(&node->fwnode);
|
| 179 | }
|
| 180 | IRQCHIP_DECLARE(mips_p8700_sswi, "mips,p8700-aclint-sswi", generic_aclint_sswi_early_probe);
|
| 181 | IRQCHIP_DECLARE(nuclei_ux900_sswi, "nuclei,ux900-aclint-sswi", generic_aclint_sswi_early_probe);
|
| 182 |
|
| 183 |
|
| 184 | #define THEAD_C9XX_CSR_SXSTATUS 0x5c0
|
| 185 | #define THEAD_C9XX_SXSTATUS_CLINTEE BIT(17)
|
| 186 |
|
| 187 | static int __init thead_aclint_sswi_probe(struct fwnode_handle *fwnode)
|
| 188 | {
|
| 189 | int rc;
|
| 190 |
|
| 191 |
|
| 192 | if (riscv_cached_mvendorid(0) == THEAD_VENDOR_ID &&
|
| 193 | !(csr_read(THEAD_C9XX_CSR_SXSTATUS) & THEAD_C9XX_SXSTATUS_CLINTEE))
|
| 194 | return -ENOTSUPP;
|
| 195 |
|
| 196 | rc = aclint_sswi_probe(fwnode);
|
| 197 | if (rc)
|
| 198 | return rc;
|
| 199 |
|
| 200 |
|
| 201 | pr_info("providing IPIs using THEAD ACLINT SSWI\n");
|
| 202 |
|
| 203 | return 0;
|
| 204 | }
|
| 205 |
|
| 206 | static int __init thead_aclint_sswi_early_probe(struct device_node *node,
|
| 207 | struct device_node *parent)
|
| 208 | {
|
| 209 | return thead_aclint_sswi_probe(&node->fwnode);
|
| 210 | }
|
| 211 | IRQCHIP_DECLARE(thead_aclint_sswi, "thead,c900-aclint-sswi", thead_aclint_sswi_early_probe);
|