/*      $NetBSD: au_icu.c,v 1.31 2021/01/04 17:35:12 thorpej Exp $      */

/*-
* Copyright (c) 2006 Itronix Inc.
* All rights reserved.
*
* Written by Garrett D'Amore for Itronix Inc.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
*    notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
*    notice, this list of conditions and the following disclaimer in the
*    documentation and/or other materials provided with the distribution.
* 3. The name of Itronix Inc. may not be used to endorse
*    or promote products derived from this software without specific
*    prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY ITRONIX INC. ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL ITRONIX INC. BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/

/*-
* Copyright (c) 2001 The NetBSD Foundation, Inc.
* All rights reserved.
*
* This code is derived from software contributed to The NetBSD Foundation
* by Jason R. Thorpe.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
*    notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
*    notice, this list of conditions and the following disclaimer in the
*    documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/

/*
* Interrupt support for the Alchemy Semiconductor Au1x00 CPUs.
*
* The Alchemy Semiconductor Au1x00's interrupts are wired to two internal
* interrupt controllers.
*/

#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: au_icu.c,v 1.31 2021/01/04 17:35:12 thorpej Exp $");

#include "opt_ddb.h"
#define __INTR_PRIVATE

#include <sys/param.h>
#include <sys/bus.h>
#include <sys/device.h>
#include <sys/intr.h>
#include <sys/kernel.h>
#include <sys/kmem.h>
#include <sys/systm.h>

#include <mips/locore.h>
#include <mips/alchemy/include/aureg.h>
#include <mips/alchemy/include/auvar.h>

#define REGVAL(x)       *((volatile uint32_t *)(MIPS_PHYS_TO_KSEG1((x))))

/*
* This is a mask of bits to clear in the SR when we go to a
* given hardware interrupt priority level.
*/

static const struct ipl_sr_map alchemy_ipl_sr_map = {
   .sr_bits = {
       [IPL_NONE] =            0,
       [IPL_SOFTCLOCK] =       MIPS_SOFT_INT_MASK_0,
       [IPL_SOFTBIO] =         MIPS_SOFT_INT_MASK_0,
       [IPL_SOFTNET] =         MIPS_SOFT_INT_MASK,
       [IPL_SOFTSERIAL] =      MIPS_SOFT_INT_MASK,
       [IPL_VM] =              MIPS_SOFT_INT_MASK |
                               MIPS_INT_MASK_0 |
                               MIPS_INT_MASK_1 |
                               MIPS_INT_MASK_2 |
                               MIPS_INT_MASK_3,
       [IPL_SCHED] =           MIPS_INT_MASK,
       [IPL_DDB] =             MIPS_INT_MASK,
       [IPL_HIGH] =            MIPS_INT_MASK,
   },
};

#define NIRQS           64

struct au_icu_intrhead {
       struct evcnt intr_count;
       int intr_refcnt;
};
struct au_icu_intrhead au_icu_intrtab[NIRQS];

#define NINTRS                  4       /* MIPS INT0 - INT3 */

struct au_intrhand {
       LIST_ENTRY(au_intrhand) ih_q;
       int (*ih_func)(void *);
       void *ih_arg;
       int ih_irq;
       int ih_mask;
};

struct au_cpuintr {
       LIST_HEAD(, au_intrhand) cintr_list;
       struct evcnt cintr_count;
};

struct au_cpuintr au_cpuintrs[NINTRS];
const char * const au_cpuintrnames[NINTRS] = {
       "icu 0, req 0",
       "icu 0, req 1",
       "icu 1, req 0",
       "icu 1, req 1",
};

static bus_addr_t ic0_base, ic1_base;

void
au_intr_init(void)
{
       ipl_sr_map = alchemy_ipl_sr_map;

       for (size_t i = 0; i < NINTRS; i++) {
               LIST_INIT(&au_cpuintrs[i].cintr_list);
               evcnt_attach_dynamic(&au_cpuintrs[i].cintr_count,
                   EVCNT_TYPE_INTR, NULL, "mips", au_cpuintrnames[i]);
       }

       struct au_chipdep * const chip = au_chipdep();
       KASSERT(chip != NULL);

       ic0_base = chip->icus[0];
       ic1_base = chip->icus[1];

       for (size_t i = 0; i < NIRQS; i++) {
               au_icu_intrtab[i].intr_refcnt = 0;
               evcnt_attach_dynamic(&au_icu_intrtab[i].intr_count,
                   EVCNT_TYPE_INTR, NULL, chip->name, chip->irqnames[i]);
       }

       /* start with all interrupts masked */
       REGVAL(ic0_base + IC_MASK_CLEAR) = 0xffffffff;
       REGVAL(ic0_base + IC_WAKEUP_CLEAR) = 0xffffffff;
       REGVAL(ic0_base + IC_SOURCE_SET) = 0xffffffff;
       REGVAL(ic0_base + IC_RISING_EDGE) = 0xffffffff;
       REGVAL(ic0_base + IC_FALLING_EDGE) = 0xffffffff;
       REGVAL(ic0_base + IC_TEST_BIT) = 0;

       REGVAL(ic1_base + IC_MASK_CLEAR) = 0xffffffff;
       REGVAL(ic1_base + IC_WAKEUP_CLEAR) = 0xffffffff;
       REGVAL(ic1_base + IC_SOURCE_SET) = 0xffffffff;
       REGVAL(ic1_base + IC_RISING_EDGE) = 0xffffffff;
       REGVAL(ic1_base + IC_FALLING_EDGE) = 0xffffffff;
       REGVAL(ic1_base + IC_TEST_BIT) = 0;
}

void *
au_intr_establish(int irq, int req, int level, int type,
   int (*func)(void *), void *arg)
{
       struct au_intrhand      *ih;
       uint32_t                icu_base;
       int                     cpu_int, s;
       struct au_chipdep       *chip;

       chip = au_chipdep();
       KASSERT(chip != NULL);

       if (irq >= NIRQS)
               panic("au_intr_establish: bogus IRQ %d", irq);
       if (req > 1)
               panic("au_intr_establish: bogus request %d", req);

       ih = kmem_alloc(sizeof(*ih), KM_SLEEP);
       ih->ih_func = func;
       ih->ih_arg = arg;
       ih->ih_irq = irq;
       ih->ih_mask = (1 << (irq & 31));

       s = splhigh();

       /*
        * First, link it into the tables.
        * XXX do we want a separate list (really, should only be one item, not
        *     a list anyway) per irq, not per CPU interrupt?
        */
       cpu_int = (irq < 32 ? 0 : 2) + req;
       LIST_INSERT_HEAD(&au_cpuintrs[cpu_int].cintr_list, ih, ih_q);

       /*
        * Now enable it.
        */
       if (au_icu_intrtab[irq].intr_refcnt++ == 0) {
               icu_base = (irq < 32) ? ic0_base : ic1_base;

               irq &= 31;      /* throw away high bit if set */
               irq = 1 << irq; /* only used as a mask from here on */

               /* XXX Only level interrupts for now */
               switch (type) {
               case IST_NONE:
               case IST_PULSE:
               case IST_EDGE:
                       panic("unsupported irq type %d", type);
                       /* NOTREACHED */
               case IST_LEVEL:
               case IST_LEVEL_HIGH:
                       REGVAL(icu_base + IC_CONFIG2_SET) = irq;
                       REGVAL(icu_base + IC_CONFIG1_CLEAR) = irq;
                       REGVAL(icu_base + IC_CONFIG0_SET) = irq;
                       break;
               case IST_LEVEL_LOW:
                       REGVAL(icu_base + IC_CONFIG2_SET) = irq;
                       REGVAL(icu_base + IC_CONFIG1_SET) = irq;
                       REGVAL(icu_base + IC_CONFIG0_CLEAR) = irq;
                       break;
               }
               wbflush();

               /* XXX handle GPIO interrupts - not done at all yet */
               if (cpu_int & 0x1)
                       REGVAL(icu_base + IC_ASSIGN_REQUEST_CLEAR) = irq;
               else
                       REGVAL(icu_base + IC_ASSIGN_REQUEST_SET) = irq;

               /* Associate interrupt with peripheral */
               REGVAL(icu_base + IC_SOURCE_SET) = irq;

               /* Actually enable the interrupt */
               REGVAL(icu_base + IC_MASK_SET) = irq;

               /* And allow the interrupt to interrupt idle */
               REGVAL(icu_base + IC_WAKEUP_SET) = irq;

               wbflush();
       }
       splx(s);

       return (ih);
}

void
au_intr_disestablish(void *cookie)
{
       struct au_intrhand *ih = cookie;
       uint32_t icu_base;
       int irq, s;

       irq = ih->ih_irq;

       s = splhigh();

       /*
        * First, remove it from the table.
        */
       LIST_REMOVE(ih, ih_q);

       /*
        * Now, disable it, if there is nothing remaining on the
        * list.
        */
       if (au_icu_intrtab[irq].intr_refcnt-- == 1) {
               icu_base = (irq < 32) ? ic0_base : ic1_base;

               irq &= 31;      /* throw away high bit if set */
               irq = 1 << irq; /* only used as a mask from here on */

               REGVAL(icu_base + IC_CONFIG2_CLEAR) = irq;
               REGVAL(icu_base + IC_CONFIG1_CLEAR) = irq;
               REGVAL(icu_base + IC_CONFIG0_CLEAR) = irq;

               /* disable with MASK_CLEAR and WAKEUP_CLEAR */
               REGVAL(icu_base + IC_MASK_CLEAR) = irq;
               REGVAL(icu_base + IC_WAKEUP_CLEAR) = irq;
               wbflush();
       }

       splx(s);

       kmem_free(ih, sizeof(*ih));
}

void
au_iointr(int ipl, vaddr_t pc, uint32_t ipending)
{
       struct au_intrhand *ih;
       int level;
       uint32_t icu_base, irqstat, irqmask;

       icu_base = irqstat = 0;

       for (level = 3; level >= 0; level--) {
               if ((ipending & (MIPS_INT_MASK_0 << level)) == 0)
                       continue;

               /*
                * XXX  the following may well be slow to execute.
                *      investigate and possibly speed up.
                *
                * is something like:
                *
                *    irqstat = REGVAL(
                *       (level & 4 == 0) ? IC0_BASE ? IC1_BASE +
                *       (level & 2 == 0) ? IC_REQUEST0_INT : IC_REQUEST1_INT);
                *
                * be any better?
                *
                */
               switch (level) {
               case 0:
                       icu_base = ic0_base;
                       irqstat = REGVAL(icu_base + IC_REQUEST0_INT);
                       break;
               case 1:
                       icu_base = ic0_base;
                       irqstat = REGVAL(icu_base + IC_REQUEST1_INT);
                       break;
               case 2:
                       icu_base = ic1_base;
                       irqstat = REGVAL(icu_base + IC_REQUEST0_INT);
                       break;
               case 3:
                       icu_base = ic1_base;
                       irqstat = REGVAL(icu_base + IC_REQUEST1_INT);
                       break;
               }
               irqmask = REGVAL(icu_base + IC_MASK_READ);
               au_cpuintrs[level].cintr_count.ev_count++;
               LIST_FOREACH(ih, &au_cpuintrs[level].cintr_list, ih_q) {
                       int mask = ih->ih_mask;

                       if (mask & irqmask & irqstat) {
                               au_icu_intrtab[ih->ih_irq].intr_count.ev_count++;
                               (*ih->ih_func)(ih->ih_arg);

                               if (REGVAL(icu_base + IC_MASK_READ) & mask) {
                                       REGVAL(icu_base + IC_MASK_CLEAR) = mask;
                                       REGVAL(icu_base + IC_MASK_SET) = mask;
                                       wbflush();
                               }
                       }
               }
       }
}

/*
* Some devices (e.g. PCMCIA) want to be able to mask interrupts at
* the ICU, and leave them masked off until some later time
* (e.g. reenabled by a soft interrupt).
*/

void
au_intr_enable(int irq)
{
       int             s;
       uint32_t        icu_base, mask;

       if (irq >= NIRQS)
               panic("au_intr_enable: bogus IRQ %d", irq);

       icu_base = (irq < 32) ? ic0_base : ic1_base;
       mask = irq & 31;
       mask = 1 << mask;

       s = splhigh();
       /* only enable the interrupt if we have a handler */
       if (au_icu_intrtab[irq].intr_refcnt) {
               REGVAL(icu_base + IC_MASK_SET) = mask;
               REGVAL(icu_base + IC_WAKEUP_SET) = mask;
               wbflush();
       }
       splx(s);
}

void
au_intr_disable(int irq)
{
       int             s;
       uint32_t        icu_base, mask;

       if (irq >= NIRQS)
               panic("au_intr_disable: bogus IRQ %d", irq);

       icu_base = (irq < 32) ? ic0_base : ic1_base;
       mask = irq & 31;
       mask = 1 << mask;

       s = splhigh();
       REGVAL(icu_base + IC_MASK_CLEAR) = mask;
       REGVAL(icu_base + IC_WAKEUP_CLEAR) = mask;
       wbflush();
       splx(s);
}