| 1 | |
| 2 | |
| 3 | * Aspeed AST2700 Interrupt Controller.
|
| 4 | *
|
| 5 | * Copyright (C) 2026 ASPEED Technology Inc.
|
| 6 |
|
| 7 |
|
| 8 | #include <linux/bitops.h>
|
| 9 | #include <linux/device.h>
|
| 10 | #include <linux/io.h>
|
| 11 | #include <linux/irq.h>
|
| 12 | #include <linux/irqchip.h>
|
| 13 | #include <linux/irqchip/chained_irq.h>
|
| 14 | #include <linux/irqdomain.h>
|
| 15 | #include <linux/of.h>
|
| 16 | #include <linux/of_irq.h>
|
| 17 | #include <linux/spinlock.h>
|
| 18 |
|
| 19 | #include "irq-ast2700.h"
|
| 20 |
|
| 21 | #define INTC1_IER 0x100
|
| 22 | #define INTC1_ISR 0x104
|
| 23 | #define INTC1_BANK_SIZE 0x10
|
| 24 | #define INTC1_SEL_BASE 0x80
|
| 25 | #define INTC1_SEL_BANK_SIZE 0x4
|
| 26 | #define INTC1_SEL_ROUTE_SIZE 0x20
|
| 27 | #define INTC1_IRQS_PER_BANK 32
|
| 28 | #define INTC1_BANK_NUM 6
|
| 29 | #define INTC1_ROUTE_NUM 7
|
| 30 | #define INTC1_IN_NUM 192
|
| 31 | #define INTC1_BOOTMCU_ROUTE 6
|
| 32 | #define INTC1_ROUTE_SELECTOR_BITS 3
|
| 33 | #define INTC1_ROUTE_IRQS_PER_GROUP 32
|
| 34 | #define INTC1_ROUTE_SHIFT 5
|
| 35 |
|
| 36 | struct aspeed_intc1 {
|
| 37 | struct device *dev;
|
| 38 | void __iomem *base;
|
| 39 | raw_spinlock_t intc_lock;
|
| 40 | struct irq_domain *local;
|
| 41 | struct irq_domain *upstream;
|
| 42 | struct aspeed_intc_interrupt_ranges ranges;
|
| 43 | };
|
| 44 |
|
| 45 | static void aspeed_intc1_disable_int(struct aspeed_intc1 *intc1)
|
| 46 | {
|
| 47 | for (int i = 0; i < INTC1_BANK_NUM; i++)
|
| 48 | writel(0, intc1->base + INTC1_IER + (INTC1_BANK_SIZE * i));
|
| 49 | }
|
| 50 |
|
| 51 | static void aspeed_intc1_irq_handler(struct irq_desc *desc)
|
| 52 | {
|
| 53 | struct aspeed_intc1 *intc1 = irq_desc_get_handler_data(desc);
|
| 54 | struct irq_chip *chip = irq_desc_get_chip(desc);
|
| 55 | unsigned long bit, status;
|
| 56 |
|
| 57 | chained_irq_enter(chip, desc);
|
| 58 |
|
| 59 | for (int bank = 0; bank < INTC1_BANK_NUM; bank++) {
|
| 60 | status = readl(intc1->base + INTC1_ISR + (INTC1_BANK_SIZE * bank));
|
| 61 | if (!status)
|
| 62 | continue;
|
| 63 |
|
| 64 | for_each_set_bit(bit, &status, INTC1_IRQS_PER_BANK) {
|
| 65 | generic_handle_domain_irq(intc1->local, (bank * INTC1_IRQS_PER_BANK) + bit);
|
| 66 | writel(BIT(bit), intc1->base + INTC1_ISR + (INTC1_BANK_SIZE * bank));
|
| 67 | }
|
| 68 | }
|
| 69 |
|
| 70 | chained_irq_exit(chip, desc);
|
| 71 | }
|
| 72 |
|
| 73 | static void aspeed_intc1_irq_mask(struct irq_data *data)
|
| 74 | {
|
| 75 | struct aspeed_intc1 *intc1 = irq_data_get_irq_chip_data(data);
|
| 76 | int bank = data->hwirq / INTC1_IRQS_PER_BANK;
|
| 77 | int bit = data->hwirq % INTC1_IRQS_PER_BANK;
|
| 78 | u32 ier;
|
| 79 |
|
| 80 | guard(raw_spinlock)(&intc1->intc_lock);
|
| 81 | ier = readl(intc1->base + INTC1_IER + (INTC1_BANK_SIZE * bank)) & ~BIT(bit);
|
| 82 | writel(ier, intc1->base + INTC1_IER + (INTC1_BANK_SIZE * bank));
|
| 83 | }
|
| 84 |
|
| 85 | static void aspeed_intc1_irq_unmask(struct irq_data *data)
|
| 86 | {
|
| 87 | struct aspeed_intc1 *intc1 = irq_data_get_irq_chip_data(data);
|
| 88 | int bank = data->hwirq / INTC1_IRQS_PER_BANK;
|
| 89 | int bit = data->hwirq % INTC1_IRQS_PER_BANK;
|
| 90 | u32 ier;
|
| 91 |
|
| 92 | guard(raw_spinlock)(&intc1->intc_lock);
|
| 93 | ier = readl(intc1->base + INTC1_IER + (INTC1_BANK_SIZE * bank)) | BIT(bit);
|
| 94 | writel(ier, intc1->base + INTC1_IER + (INTC1_BANK_SIZE * bank));
|
| 95 | }
|
| 96 |
|
| 97 | static struct irq_chip aspeed_intc_chip = {
|
| 98 | .name = "ASPEED INTC1",
|
| 99 | .irq_mask = aspeed_intc1_irq_mask,
|
| 100 | .irq_unmask = aspeed_intc1_irq_unmask,
|
| 101 | };
|
| 102 |
|
| 103 | static int aspeed_intc1_irq_domain_translate(struct irq_domain *domain,
|
| 104 | struct irq_fwspec *fwspec,
|
| 105 | unsigned long *hwirq,
|
| 106 | unsigned int *type)
|
| 107 | {
|
| 108 | if (fwspec->param_count != 1)
|
| 109 | return -EINVAL;
|
| 110 |
|
| 111 | *hwirq = fwspec->param[0];
|
| 112 | *type = IRQ_TYPE_LEVEL_HIGH;
|
| 113 | return 0;
|
| 114 | }
|
| 115 |
|
| 116 | static int aspeed_intc1_map_irq_domain(struct irq_domain *domain,
|
| 117 | unsigned int irq,
|
| 118 | irq_hw_number_t hwirq)
|
| 119 | {
|
| 120 | irq_domain_set_info(domain, irq, hwirq, &aspeed_intc_chip,
|
| 121 | domain->host_data, handle_level_irq, NULL, NULL);
|
| 122 | return 0;
|
| 123 | }
|
| 124 |
|
| 125 | |
| 126 | * In-bound interrupts are progressively merged into one out-bound interrupt in
|
| 127 | * groups of 32. Apply this fact to compress the route table in corresponding
|
| 128 | * groups of 32.
|
| 129 |
|
| 130 | static const u32
|
| 131 | aspeed_intc1_routes[INTC1_IN_NUM / INTC1_ROUTE_IRQS_PER_GROUP][INTC1_ROUTE_NUM] = {
|
| 132 | { 0, AST2700_INTC_INVALID_ROUTE, 10, 20, 30, 40, 50 },
|
| 133 | { 1, AST2700_INTC_INVALID_ROUTE, 11, 21, 31, 41, 50 },
|
| 134 | { 2, AST2700_INTC_INVALID_ROUTE, 12, 22, 32, 42, 50 },
|
| 135 | { 3, AST2700_INTC_INVALID_ROUTE, 13, 23, 33, 43, 50 },
|
| 136 | { 4, AST2700_INTC_INVALID_ROUTE, 14, 24, 34, 44, 50 },
|
| 137 | { 5, AST2700_INTC_INVALID_ROUTE, 15, 25, 35, 45, 50 },
|
| 138 | };
|
| 139 |
|
| 140 | static int aspeed_intc1_irq_domain_activate(struct irq_domain *domain,
|
| 141 | struct irq_data *data, bool reserve)
|
| 142 | {
|
| 143 | struct aspeed_intc1 *intc1 = irq_data_get_irq_chip_data(data);
|
| 144 | struct aspeed_intc_interrupt_range resolved;
|
| 145 | int rc, bank, bit;
|
| 146 | u32 mask;
|
| 147 |
|
| 148 | if (WARN_ON_ONCE((data->hwirq >> INTC1_ROUTE_SHIFT) >= ARRAY_SIZE(aspeed_intc1_routes)))
|
| 149 | return -EINVAL;
|
| 150 |
|
| 151 | |
| 152 | * outpin may be an error if the upstream is the BootMCU APLIC node, or
|
| 153 | * anything except a valid intc0 driver instance
|
| 154 |
|
| 155 | rc = aspeed_intc0_resolve_route(intc1->upstream, INTC1_ROUTE_NUM,
|
| 156 | aspeed_intc1_routes[data->hwirq >> INTC1_ROUTE_SHIFT],
|
| 157 | intc1->ranges.nranges,
|
| 158 | intc1->ranges.ranges, &resolved);
|
| 159 | if (rc < 0) {
|
| 160 | if (!of_device_is_compatible(to_of_node(intc1->upstream->fwnode),
|
| 161 | "riscv,aplic")) {
|
| 162 | dev_warn(intc1->dev,
|
| 163 | "Failed to resolve interrupt route for hwirq %lu in domain %s\n",
|
| 164 | data->hwirq, domain->name);
|
| 165 | return rc;
|
| 166 | }
|
| 167 | rc = INTC1_BOOTMCU_ROUTE;
|
| 168 | }
|
| 169 |
|
| 170 | bank = data->hwirq / INTC1_IRQS_PER_BANK;
|
| 171 | bit = data->hwirq % INTC1_IRQS_PER_BANK;
|
| 172 | mask = BIT(bit);
|
| 173 |
|
| 174 | guard(raw_spinlock)(&intc1->intc_lock);
|
| 175 | for (int i = 0; i < INTC1_ROUTE_SELECTOR_BITS; i++) {
|
| 176 | void __iomem *sel = intc1->base + INTC1_SEL_BASE +
|
| 177 | (bank * INTC1_SEL_BANK_SIZE) +
|
| 178 | (INTC1_SEL_ROUTE_SIZE * i);
|
| 179 | u32 reg = readl(sel);
|
| 180 |
|
| 181 | if (rc & BIT(i))
|
| 182 | reg |= mask;
|
| 183 | else
|
| 184 | reg &= ~mask;
|
| 185 |
|
| 186 | writel(reg, sel);
|
| 187 | if (readl(sel) != reg)
|
| 188 | return -EACCES;
|
| 189 | }
|
| 190 |
|
| 191 | return 0;
|
| 192 | }
|
| 193 |
|
| 194 | static const struct irq_domain_ops aspeed_intc1_irq_domain_ops = {
|
| 195 | .map = aspeed_intc1_map_irq_domain,
|
| 196 | .translate = aspeed_intc1_irq_domain_translate,
|
| 197 | .activate = aspeed_intc1_irq_domain_activate,
|
| 198 | };
|
| 199 |
|
| 200 | static void aspeed_intc1_request_interrupts(struct aspeed_intc1 *intc1)
|
| 201 | {
|
| 202 | for (unsigned int i = 0; i < intc1->ranges.nranges; i++) {
|
| 203 | struct aspeed_intc_interrupt_range *r =
|
| 204 | &intc1->ranges.ranges[i];
|
| 205 |
|
| 206 | if (intc1->upstream != r->domain)
|
| 207 | continue;
|
| 208 |
|
| 209 | for (u32 k = 0; k < r->count; k++) {
|
| 210 | struct of_phandle_args parent_irq;
|
| 211 | int irq;
|
| 212 |
|
| 213 | parent_irq.np = to_of_node(r->upstream.fwnode);
|
| 214 | parent_irq.args_count = 1;
|
| 215 | parent_irq.args[0] =
|
| 216 | intc1->ranges.ranges[i].upstream.param[ASPEED_INTC_RANGES_BASE] + k;
|
| 217 |
|
| 218 | irq = irq_create_of_mapping(&parent_irq);
|
| 219 | if (!irq)
|
| 220 | continue;
|
| 221 |
|
| 222 | irq_set_chained_handler_and_data(irq,
|
| 223 | aspeed_intc1_irq_handler, intc1);
|
| 224 | }
|
| 225 | }
|
| 226 | }
|
| 227 |
|
| 228 | static int aspeed_intc1_probe(struct platform_device *pdev,
|
| 229 | struct device_node *parent)
|
| 230 | {
|
| 231 | struct device_node *node = pdev->dev.of_node;
|
| 232 | struct aspeed_intc1 *intc1;
|
| 233 | struct irq_domain *host;
|
| 234 | int ret;
|
| 235 |
|
| 236 | if (!parent) {
|
| 237 | dev_err(&pdev->dev, "missing parent interrupt node\n");
|
| 238 | return -ENODEV;
|
| 239 | }
|
| 240 |
|
| 241 | if (!of_device_is_compatible(parent, "aspeed,ast2700-intc0"))
|
| 242 | return -ENODEV;
|
| 243 |
|
| 244 | host = irq_find_host(parent);
|
| 245 | if (!host)
|
| 246 | return -ENODEV;
|
| 247 |
|
| 248 | intc1 = devm_kzalloc(&pdev->dev, sizeof(*intc1), GFP_KERNEL);
|
| 249 | if (!intc1)
|
| 250 | return -ENOMEM;
|
| 251 |
|
| 252 | intc1->dev = &pdev->dev;
|
| 253 | intc1->upstream = host;
|
| 254 | intc1->base = devm_platform_ioremap_resource(pdev, 0);
|
| 255 | if (IS_ERR(intc1->base))
|
| 256 | return PTR_ERR(intc1->base);
|
| 257 |
|
| 258 | aspeed_intc1_disable_int(intc1);
|
| 259 |
|
| 260 | raw_spin_lock_init(&intc1->intc_lock);
|
| 261 |
|
| 262 | intc1->local = irq_domain_create_linear(of_fwnode_handle(node),
|
| 263 | INTC1_BANK_NUM * INTC1_IRQS_PER_BANK,
|
| 264 | &aspeed_intc1_irq_domain_ops, intc1);
|
| 265 | if (!intc1->local)
|
| 266 | return -ENOMEM;
|
| 267 |
|
| 268 | ret = aspeed_intc_populate_ranges(&pdev->dev, &intc1->ranges);
|
| 269 | if (ret < 0) {
|
| 270 | irq_domain_remove(intc1->local);
|
| 271 | return ret;
|
| 272 | }
|
| 273 |
|
| 274 | aspeed_intc1_request_interrupts(intc1);
|
| 275 |
|
| 276 | return 0;
|
| 277 | }
|
| 278 |
|
| 279 | IRQCHIP_PLATFORM_DRIVER_BEGIN(ast2700_intc1)
|
| 280 | IRQCHIP_MATCH("aspeed,ast2700-intc1", aspeed_intc1_probe)
|
| 281 | IRQCHIP_PLATFORM_DRIVER_END(ast2700_intc1)
|