root/arch/powerpc/sysdev/fsl_mpic_err.c
// SPDX-License-Identifier: GPL-2.0-only
/*
 * Copyright (C) 2012 Freescale Semiconductor, Inc.
 *
 * Author: Varun Sethi <varun.sethi@freescale.com>
 */

#include <linux/irq.h>
#include <linux/smp.h>
#include <linux/interrupt.h>
#include <linux/irqdomain.h>

#include <asm/io.h>
#include <asm/irq.h>
#include <asm/mpic.h>

#include "mpic.h"

#define MPIC_ERR_INT_BASE       0x3900
#define MPIC_ERR_INT_EISR       0x0000
#define MPIC_ERR_INT_EIMR       0x0010

static inline u32 mpic_fsl_err_read(u32 __iomem *base, unsigned int err_reg)
{
        return in_be32(base + (err_reg >> 2));
}

static inline void mpic_fsl_err_write(u32 __iomem *base, u32 value)
{
        out_be32(base + (MPIC_ERR_INT_EIMR >> 2), value);
}

static void fsl_mpic_mask_err(struct irq_data *d)
{
        u32 eimr;
        struct mpic *mpic = irq_data_get_irq_chip_data(d);
        unsigned int src = virq_to_hw(d->irq) - mpic->err_int_vecs[0];

        eimr = mpic_fsl_err_read(mpic->err_regs, MPIC_ERR_INT_EIMR);
        eimr |= (1 << (31 - src));
        mpic_fsl_err_write(mpic->err_regs, eimr);
}

static void fsl_mpic_unmask_err(struct irq_data *d)
{
        u32 eimr;
        struct mpic *mpic = irq_data_get_irq_chip_data(d);
        unsigned int src = virq_to_hw(d->irq) - mpic->err_int_vecs[0];

        eimr = mpic_fsl_err_read(mpic->err_regs, MPIC_ERR_INT_EIMR);
        eimr &= ~(1 << (31 - src));
        mpic_fsl_err_write(mpic->err_regs, eimr);
}

static struct irq_chip fsl_mpic_err_chip = {
        .irq_disable    = fsl_mpic_mask_err,
        .irq_mask       = fsl_mpic_mask_err,
        .irq_unmask     = fsl_mpic_unmask_err,
};

int __init mpic_setup_error_int(struct mpic *mpic, int intvec)
{
        int i;

        mpic->err_regs = ioremap(mpic->paddr + MPIC_ERR_INT_BASE, 0x1000);
        if (!mpic->err_regs) {
                pr_err("could not map mpic error registers\n");
                return -ENOMEM;
        }
        mpic->hc_err = fsl_mpic_err_chip;
        mpic->hc_err.name = mpic->name;
        mpic->flags |= MPIC_FSL_HAS_EIMR;
        /* allocate interrupt vectors for error interrupts */
        for (i = MPIC_MAX_ERR - 1; i >= 0; i--)
                mpic->err_int_vecs[i] = intvec--;

        return 0;
}

int mpic_map_error_int(struct mpic *mpic, unsigned int virq, irq_hw_number_t  hw)
{
        if ((mpic->flags & MPIC_FSL_HAS_EIMR) &&
            (hw >= mpic->err_int_vecs[0] &&
             hw <= mpic->err_int_vecs[MPIC_MAX_ERR - 1])) {
                WARN_ON(mpic->flags & MPIC_SECONDARY);

                pr_debug("mpic: mapping as Error Interrupt\n");
                irq_set_chip_data(virq, mpic);
                irq_set_chip_and_handler(virq, &mpic->hc_err,
                                         handle_level_irq);
                return 1;
        }

        return 0;
}

static irqreturn_t fsl_error_int_handler(int irq, void *data)
{
        struct mpic *mpic = (struct mpic *) data;
        u32 eisr, eimr;
        int errint;

        eisr = mpic_fsl_err_read(mpic->err_regs, MPIC_ERR_INT_EISR);
        eimr = mpic_fsl_err_read(mpic->err_regs, MPIC_ERR_INT_EIMR);

        if (!(eisr & ~eimr))
                return IRQ_NONE;

        while (eisr) {
                int ret;
                errint = __builtin_clz(eisr);
                ret = generic_handle_domain_irq(mpic->irqhost,
                                                mpic->err_int_vecs[errint]);
                if (WARN_ON(ret)) {
                        eimr |=  1 << (31 - errint);
                        mpic_fsl_err_write(mpic->err_regs, eimr);
                }
                eisr &= ~(1 << (31 - errint));
        }

        return IRQ_HANDLED;
}

void __init mpic_err_int_init(struct mpic *mpic, irq_hw_number_t irqnum)
{
        unsigned int virq;
        int ret;

        virq = irq_create_mapping(mpic->irqhost, irqnum);
        if (!virq) {
                pr_err("Error interrupt setup failed\n");
                return;
        }

        /* Mask all error interrupts */
        mpic_fsl_err_write(mpic->err_regs, ~0);

        ret = request_irq(virq, fsl_error_int_handler, IRQF_NO_THREAD,
                    "mpic-error-int", mpic);
        if (ret)
                pr_err("Failed to register error interrupt handler\n");
}