| Author | Tokens | Token Proportion | Commits | Commit Proportion |
|---|---|---|---|---|
| Ryan Chen | 1424 | 100.00% | 1 | 100.00% |
| Total | 1424 | 1 |
// SPDX-License-Identifier: GPL-2.0-only /* * Aspeed AST2700 Interrupt Controller. * * Copyright (C) 2026 ASPEED Technology Inc. */ #include <linux/bitops.h> #include <linux/device.h> #include <linux/io.h> #include <linux/irq.h> #include <linux/irqchip.h> #include <linux/irqchip/chained_irq.h> #include <linux/irqdomain.h> #include <linux/of.h> #include <linux/of_irq.h> #include <linux/spinlock.h> #include "irq-ast2700.h" #define INTC1_IER 0x100 #define INTC1_ISR 0x104 #define INTC1_BANK_SIZE 0x10 #define INTC1_SEL_BASE 0x80 #define INTC1_SEL_BANK_SIZE 0x4 #define INTC1_SEL_ROUTE_SIZE 0x20 #define INTC1_IRQS_PER_BANK 32 #define INTC1_BANK_NUM 6 #define INTC1_ROUTE_NUM 7 #define INTC1_IN_NUM 192 #define INTC1_BOOTMCU_ROUTE 6 #define INTC1_ROUTE_SELECTOR_BITS 3 #define INTC1_ROUTE_IRQS_PER_GROUP 32 #define INTC1_ROUTE_SHIFT 5 struct aspeed_intc1 { struct device *dev; void __iomem *base; raw_spinlock_t intc_lock; struct irq_domain *local; struct irq_domain *upstream; struct aspeed_intc_interrupt_ranges ranges; }; static void aspeed_intc1_disable_int(struct aspeed_intc1 *intc1) { for (int i = 0; i < INTC1_BANK_NUM; i++) writel(0, intc1->base + INTC1_IER + (INTC1_BANK_SIZE * i)); } static void aspeed_intc1_irq_handler(struct irq_desc *desc) { struct aspeed_intc1 *intc1 = irq_desc_get_handler_data(desc); struct irq_chip *chip = irq_desc_get_chip(desc); unsigned long bit, status; chained_irq_enter(chip, desc); for (int bank = 0; bank < INTC1_BANK_NUM; bank++) { status = readl(intc1->base + INTC1_ISR + (INTC1_BANK_SIZE * bank)); if (!status) continue; for_each_set_bit(bit, &status, INTC1_IRQS_PER_BANK) { generic_handle_domain_irq(intc1->local, (bank * INTC1_IRQS_PER_BANK) + bit); writel(BIT(bit), intc1->base + INTC1_ISR + (INTC1_BANK_SIZE * bank)); } } chained_irq_exit(chip, desc); } static void aspeed_intc1_irq_mask(struct irq_data *data) { struct aspeed_intc1 *intc1 = irq_data_get_irq_chip_data(data); int bank = data->hwirq / INTC1_IRQS_PER_BANK; int bit = data->hwirq % INTC1_IRQS_PER_BANK; u32 ier; guard(raw_spinlock)(&intc1->intc_lock); ier = readl(intc1->base + INTC1_IER + (INTC1_BANK_SIZE * bank)) & ~BIT(bit); writel(ier, intc1->base + INTC1_IER + (INTC1_BANK_SIZE * bank)); } static void aspeed_intc1_irq_unmask(struct irq_data *data) { struct aspeed_intc1 *intc1 = irq_data_get_irq_chip_data(data); int bank = data->hwirq / INTC1_IRQS_PER_BANK; int bit = data->hwirq % INTC1_IRQS_PER_BANK; u32 ier; guard(raw_spinlock)(&intc1->intc_lock); ier = readl(intc1->base + INTC1_IER + (INTC1_BANK_SIZE * bank)) | BIT(bit); writel(ier, intc1->base + INTC1_IER + (INTC1_BANK_SIZE * bank)); } static struct irq_chip aspeed_intc_chip = { .name = "ASPEED INTC1", .irq_mask = aspeed_intc1_irq_mask, .irq_unmask = aspeed_intc1_irq_unmask, }; static int aspeed_intc1_irq_domain_translate(struct irq_domain *domain, struct irq_fwspec *fwspec, unsigned long *hwirq, unsigned int *type) { if (fwspec->param_count != 1) return -EINVAL; *hwirq = fwspec->param[0]; *type = IRQ_TYPE_LEVEL_HIGH; return 0; } static int aspeed_intc1_map_irq_domain(struct irq_domain *domain, unsigned int irq, irq_hw_number_t hwirq) { irq_domain_set_info(domain, irq, hwirq, &aspeed_intc_chip, domain->host_data, handle_level_irq, NULL, NULL); return 0; } /* * In-bound interrupts are progressively merged into one out-bound interrupt in * groups of 32. Apply this fact to compress the route table in corresponding * groups of 32. */ static const u32 aspeed_intc1_routes[INTC1_IN_NUM / INTC1_ROUTE_IRQS_PER_GROUP][INTC1_ROUTE_NUM] = { { 0, AST2700_INTC_INVALID_ROUTE, 10, 20, 30, 40, 50 }, { 1, AST2700_INTC_INVALID_ROUTE, 11, 21, 31, 41, 50 }, { 2, AST2700_INTC_INVALID_ROUTE, 12, 22, 32, 42, 50 }, { 3, AST2700_INTC_INVALID_ROUTE, 13, 23, 33, 43, 50 }, { 4, AST2700_INTC_INVALID_ROUTE, 14, 24, 34, 44, 50 }, { 5, AST2700_INTC_INVALID_ROUTE, 15, 25, 35, 45, 50 }, }; static int aspeed_intc1_irq_domain_activate(struct irq_domain *domain, struct irq_data *data, bool reserve) { struct aspeed_intc1 *intc1 = irq_data_get_irq_chip_data(data); struct aspeed_intc_interrupt_range resolved; int rc, bank, bit; u32 mask; if (WARN_ON_ONCE((data->hwirq >> INTC1_ROUTE_SHIFT) >= ARRAY_SIZE(aspeed_intc1_routes))) return -EINVAL; /* * outpin may be an error if the upstream is the BootMCU APLIC node, or * anything except a valid intc0 driver instance */ rc = aspeed_intc0_resolve_route(intc1->upstream, INTC1_ROUTE_NUM, aspeed_intc1_routes[data->hwirq >> INTC1_ROUTE_SHIFT], intc1->ranges.nranges, intc1->ranges.ranges, &resolved); if (rc < 0) { if (!fwnode_device_is_compatible(intc1->upstream->fwnode, "riscv,aplic")) { dev_warn(intc1->dev, "Failed to resolve interrupt route for hwirq %lu in domain %s\n", data->hwirq, domain->name); return rc; } rc = INTC1_BOOTMCU_ROUTE; } bank = data->hwirq / INTC1_IRQS_PER_BANK; bit = data->hwirq % INTC1_IRQS_PER_BANK; mask = BIT(bit); guard(raw_spinlock)(&intc1->intc_lock); for (int i = 0; i < INTC1_ROUTE_SELECTOR_BITS; i++) { void __iomem *sel = intc1->base + INTC1_SEL_BASE + (bank * INTC1_SEL_BANK_SIZE) + (INTC1_SEL_ROUTE_SIZE * i); u32 reg = readl(sel); if (rc & BIT(i)) reg |= mask; else reg &= ~mask; writel(reg, sel); if (readl(sel) != reg) return -EACCES; } return 0; } static const struct irq_domain_ops aspeed_intc1_irq_domain_ops = { .map = aspeed_intc1_map_irq_domain, .translate = aspeed_intc1_irq_domain_translate, .activate = aspeed_intc1_irq_domain_activate, }; static void aspeed_intc1_request_interrupts(struct aspeed_intc1 *intc1) { for (unsigned int i = 0; i < intc1->ranges.nranges; i++) { struct aspeed_intc_interrupt_range *r = &intc1->ranges.ranges[i]; if (intc1->upstream != r->domain) continue; for (u32 k = 0; k < r->count; k++) { struct of_phandle_args parent_irq; int irq; parent_irq.np = to_of_node(r->upstream.fwnode); parent_irq.args_count = 1; parent_irq.args[0] = intc1->ranges.ranges[i].upstream.param[ASPEED_INTC_RANGES_BASE] + k; irq = irq_create_of_mapping(&parent_irq); if (!irq) continue; irq_set_chained_handler_and_data(irq, aspeed_intc1_irq_handler, intc1); } } } static int aspeed_intc1_probe(struct platform_device *pdev, struct device_node *parent) { struct device_node *node = pdev->dev.of_node; struct aspeed_intc1 *intc1; struct irq_domain *host; int ret; if (!parent) { dev_err(&pdev->dev, "missing parent interrupt node\n"); return -ENODEV; } if (!of_device_is_compatible(parent, "aspeed,ast2700-intc0")) return -ENODEV; host = irq_find_host(parent); if (!host) return -ENODEV; intc1 = devm_kzalloc(&pdev->dev, sizeof(*intc1), GFP_KERNEL); if (!intc1) return -ENOMEM; intc1->dev = &pdev->dev; intc1->upstream = host; intc1->base = devm_platform_ioremap_resource(pdev, 0); if (IS_ERR(intc1->base)) return PTR_ERR(intc1->base); aspeed_intc1_disable_int(intc1); raw_spin_lock_init(&intc1->intc_lock); intc1->local = irq_domain_create_linear(of_fwnode_handle(node), INTC1_BANK_NUM * INTC1_IRQS_PER_BANK, &aspeed_intc1_irq_domain_ops, intc1); if (!intc1->local) return -ENOMEM; ret = aspeed_intc_populate_ranges(&pdev->dev, &intc1->ranges); if (ret < 0) { irq_domain_remove(intc1->local); return ret; } aspeed_intc1_request_interrupts(intc1); return 0; } IRQCHIP_PLATFORM_DRIVER_BEGIN(ast2700_intc1) IRQCHIP_MATCH("aspeed,ast2700-intc1", aspeed_intc1_probe) IRQCHIP_PLATFORM_DRIVER_END(ast2700_intc1)
Information contained on this website is for historical information purposes only and does not indicate or represent copyright ownership.
Created with Cregit http://github.com/cregit/cregit
Version 2.0-RC1