root/drivers/irqchip/irq-ast2700.c
// SPDX-License-Identifier: GPL-2.0-only
/*
 *  Aspeed AST2700 Interrupt Controller.
 *
 *  Copyright (C) 2026 ASPEED Technology Inc.
 */
#include "irq-ast2700.h"

#define ASPEED_INTC_RANGE_FIXED_CELLS   3U
#define ASPEED_INTC_RANGE_OFF_START     0U
#define ASPEED_INTC_RANGE_OFF_COUNT     1U
#define ASPEED_INTC_RANGE_OFF_PHANDLE   2U

/**
 * aspeed_intc_populate_ranges
 * @dev: Device owning the interrupt controller node.
 * @ranges: Destination for parsed range descriptors.
 *
 * Return: 0 on success, negative errno on error.
 */
int aspeed_intc_populate_ranges(struct device *dev,
                                struct aspeed_intc_interrupt_ranges *ranges)
{
        struct aspeed_intc_interrupt_range *arr;
        const __be32 *pvs, *pve;
        struct device_node *dn;
        int len;

        if (!dev || !ranges)
                return -EINVAL;

        dn = dev->of_node;

        pvs = of_get_property(dn, "aspeed,interrupt-ranges", &len);
        if (!pvs)
                return -EINVAL;

        if (len % sizeof(__be32))
                return -EINVAL;

        /* Over-estimate the range entry count for now */
        ranges->ranges = devm_kmalloc_array(dev,
                                            len / (ASPEED_INTC_RANGE_FIXED_CELLS * sizeof(__be32)),
                                            sizeof(*ranges->ranges),
                                            GFP_KERNEL);
        if (!ranges->ranges)
                return -ENOMEM;

        pve = pvs + (len / sizeof(__be32));
        for (unsigned int i = 0; pve - pvs >= ASPEED_INTC_RANGE_FIXED_CELLS; i++) {
                struct aspeed_intc_interrupt_range *r;
                struct device_node *target;
                u32 target_cells;

                target = of_find_node_by_phandle(be32_to_cpu(pvs[ASPEED_INTC_RANGE_OFF_PHANDLE]));
                if (!target)
                        return -EINVAL;

                if (of_property_read_u32(target, "#interrupt-cells",
                                         &target_cells)) {
                        of_node_put(target);
                        return -EINVAL;
                }

                if (!target_cells || target_cells > IRQ_DOMAIN_IRQ_SPEC_PARAMS) {
                        of_node_put(target);
                        return -EINVAL;
                }

                if (pve - pvs < ASPEED_INTC_RANGE_FIXED_CELLS + target_cells) {
                        of_node_put(target);
                        return -EINVAL;
                }

                r = &ranges->ranges[i];
                r->start = be32_to_cpu(pvs[ASPEED_INTC_RANGE_OFF_START]);
                r->count = be32_to_cpu(pvs[ASPEED_INTC_RANGE_OFF_COUNT]);

                {
                        struct of_phandle_args args = {
                                .np = target,
                                .args_count = target_cells,
                        };

                        for (u32 j = 0; j < target_cells; j++)
                                args.args[j] = be32_to_cpu(pvs[ASPEED_INTC_RANGE_FIXED_CELLS + j]);

                        of_phandle_args_to_fwspec(target, args.args,
                                                  args.args_count,
                                                  &r->upstream);
                }

                of_node_put(target);
                r->domain = irq_find_matching_fwspec(&r->upstream, DOMAIN_BUS_ANY);
                pvs += ASPEED_INTC_RANGE_FIXED_CELLS + target_cells;
                ranges->nranges++;
        }

        /* Re-fit the range array now we know the entry count */
        arr = devm_krealloc_array(dev, ranges->ranges, ranges->nranges,
                                  sizeof(*ranges->ranges), GFP_KERNEL);
        if (!arr)
                return -ENOMEM;
        ranges->ranges = arr;

        return 0;
}