/* $NetBSD: tc_3000_300.c,v 1.39 2021/05/07 16:58:34 thorpej Exp $ */

/*
* Copyright (c) 1994, 1995, 1996 Carnegie-Mellon University.
* All rights reserved.
*
* Author: Chris G. Demetriou
*
* Permission to use, copy, modify and distribute this software and
* its documentation is hereby granted, provided that both the copyright
* notice and this permission notice appear in all copies of the
* software, derivative works or modified versions, and any portions
* thereof, and that both notices appear in supporting documentation.
*
* CARNEGIE MELLON ALLOWS FREE USE OF THIS SOFTWARE IN ITS "AS IS"
* CONDITION.  CARNEGIE MELLON DISCLAIMS ANY LIABILITY OF ANY KIND
* FOR ANY DAMAGES WHATSOEVER RESULTING FROM THE USE OF THIS SOFTWARE.
*
* Carnegie Mellon requests users of this software to return to
*
*  Software Distribution Coordinator  or  [email protected]
*  School of Computer Science
*  Carnegie Mellon University
*  Pittsburgh PA 15213-3890
*
* any improvements or extensions that they make and grant Carnegie the
* rights to redistribute these changes.
*/

#include <sys/cdefs.h>                  /* RCS ID & Copyright macro defns */

__KERNEL_RCSID(0, "$NetBSD: tc_3000_300.c,v 1.39 2021/05/07 16:58:34 thorpej Exp $");

#include <sys/param.h>
#include <sys/systm.h>
#include <sys/device.h>
#include <sys/kmem.h>
#include <sys/cpu.h>

#include <machine/autoconf.h>
#include <machine/pte.h>

#include <dev/tc/tcvar.h>
#include <dev/tc/ioasicreg.h>
#include <alpha/tc/tc_conf.h>
#include <alpha/tc/tc_3000_300.h>

#include "wsdisplay.h"
#include "sfb.h"

#if NSFB > 0
extern int      sfb_cnattach(tc_addr_t);
#endif

static int      tc_3000_300_intrnull(void *);

#define C(x)    ((void *)(u_long)x)
#define KV(x)   (ALPHA_PHYS_TO_K0SEG(x))

/*
* We have to read and modify the IOASIC registers directly, because
* the TC option slot interrupt request and mask bits are stored there,
* and the ioasic code isn't initted when we need to frob some interrupt
* bits.
*/
#define DEC_3000_300_IOASIC_ADDR        KV(0x1a0000000)

const struct tc_slotdesc tc_3000_300_slots[] = {
       { KV(0x100000000), C(TC_3000_300_DEV_OPT0), },  /* 0 - opt slot 0 */
       { KV(0x120000000), C(TC_3000_300_DEV_OPT1), },  /* 1 - opt slot 1 */
       { KV(0x140000000), C(TC_3000_300_DEV_BOGUS), }, /* 2 - unused */
       { KV(0x160000000), C(TC_3000_300_DEV_BOGUS), }, /* 3 - unused */
       { KV(0x180000000), C(TC_3000_300_DEV_BOGUS), }, /* 4 - TCDS ASIC */
       { KV(0x1a0000000), C(TC_3000_300_DEV_BOGUS), }, /* 5 - IOCTL ASIC */
       { KV(0x1c0000000), C(TC_3000_300_DEV_BOGUS), }, /* 6 - CXTurbo */
};
const int tc_3000_300_nslots = __arraycount(tc_3000_300_slots);

const struct tc_builtin tc_3000_300_builtins[] = {
       { "PMAGB-BA",   6, 0x02000000, C(TC_3000_300_DEV_CXTURBO),      },
       { "FLAMG-IO",   5, 0x00000000, C(TC_3000_300_DEV_IOASIC),       },
       { "PMAZ-DS ",   4, 0x00000000, C(TC_3000_300_DEV_TCDS),         },
};
const int tc_3000_300_nbuiltins = __arraycount(tc_3000_300_builtins);

static struct tcintr {
       int     (*tci_func)(void *);
       void    *tci_arg;
       struct evcnt tci_evcnt;
} tc_3000_300_intr[TC_3000_300_NCOOKIES];

void
tc_3000_300_intr_setup(void)
{
       volatile uint32_t *imskp;
       char *cp;
       u_long i;

       /*
        * Disable all interrupts that we can (can't disable builtins).
        */
       imskp = (volatile uint32_t *)(DEC_3000_300_IOASIC_ADDR + IOASIC_IMSK);
       *imskp &= ~(IOASIC_INTR_300_OPT0 | IOASIC_INTR_300_OPT1);

       /*
        * Set up interrupt handlers.
        */
       for (i = 0; i < TC_3000_300_NCOOKIES; i++) {
               tc_3000_300_intr[i].tci_func = tc_3000_300_intrnull;
               tc_3000_300_intr[i].tci_arg = (void *)i;

               cp = kmem_asprintf("slot %lu", i);
               evcnt_attach_dynamic(&tc_3000_300_intr[i].tci_evcnt,
                   EVCNT_TYPE_INTR, NULL, "tc", cp);
       }
}

const struct evcnt *
tc_3000_300_intr_evcnt(device_t tcadev, void *cookie)
{
       u_long dev = (u_long)cookie;

#ifdef DIAGNOSTIC
       /* XXX bounds-check cookie. */
#endif

       return (&tc_3000_300_intr[dev].tci_evcnt);
}

void
tc_3000_300_intr_establish(device_t tcadev, void *cookie, tc_intrlevel_t level, int (*func)(void *), void *arg)
{
       volatile uint32_t *imskp;
       u_long dev = (u_long)cookie;

#ifdef DIAGNOSTIC
       /* XXX bounds-check cookie. */
#endif

       if (tc_3000_300_intr[dev].tci_func != tc_3000_300_intrnull)
               panic("tc_3000_300_intr_establish: cookie %lu twice", dev);

       const int s = splhigh();

       /* All TC systems are uniprocessors. */
       KASSERT(CPU_IS_PRIMARY(curcpu()));
       KASSERT(ncpu == 1);
       curcpu()->ci_nintrhand++;

       tc_3000_300_intr[dev].tci_func = func;
       tc_3000_300_intr[dev].tci_arg = arg;

       splx(s);

       imskp = (volatile uint32_t *)(DEC_3000_300_IOASIC_ADDR + IOASIC_IMSK);
       switch (dev) {
       case TC_3000_300_DEV_OPT0:
               *imskp |= IOASIC_INTR_300_OPT0;
               break;
       case TC_3000_300_DEV_OPT1:
               *imskp |= IOASIC_INTR_300_OPT1;
               break;
       default:
               /* interrupts for builtins always enabled */
               break;
       }
}

void
tc_3000_300_intr_disestablish(device_t tcadev, void *cookie)
{
       volatile uint32_t *imskp;
       u_long dev = (u_long)cookie;

#ifdef DIAGNOSTIC
       /* XXX bounds-check cookie. */
#endif

       if (tc_3000_300_intr[dev].tci_func == tc_3000_300_intrnull)
               panic("tc_3000_300_intr_disestablish: cookie %lu bad intr",
                   dev);

       imskp = (volatile uint32_t *)(DEC_3000_300_IOASIC_ADDR + IOASIC_IMSK);
       switch (dev) {
       case TC_3000_300_DEV_OPT0:
               *imskp &= ~IOASIC_INTR_300_OPT0;
               break;
       case TC_3000_300_DEV_OPT1:
               *imskp &= ~IOASIC_INTR_300_OPT1;
               break;
       default:
               /* interrupts for builtins always enabled */
               break;
       }

       const int s = splhigh();

       curcpu()->ci_nintrhand--;

       tc_3000_300_intr[dev].tci_func = tc_3000_300_intrnull;
       tc_3000_300_intr[dev].tci_arg = (void *)dev;

       splx(s);
}

static int
tc_3000_300_intrnull(void *val)
{

       panic("tc_3000_300_intrnull: uncaught TC intr for cookie %ld",
           (u_long)val);
}

void
tc_3000_300_iointr(void *arg, unsigned long vec)
{
       uint32_t tcir, ioasicir, ioasicimr;
       int ifound;

       KERNEL_LOCK(1, NULL);

#ifdef DIAGNOSTIC
       int s;
       if (vec != 0x800)
               panic("INVALID ASSUMPTION: vec 0x%lx, not 0x800", vec);
       s = splhigh();
       if (s != ALPHA_PSL_IPL_IO_HI)
               panic("INVALID ASSUMPTION: IPL %d, not %d", s,
                   ALPHA_PSL_IPL_IO_HI);
       splx(s);
#endif

       do {
               tc_syncbus();

               /* find out what interrupts/errors occurred */
               tcir = *(volatile uint32_t *)TC_3000_300_IR;
               ioasicir = *(volatile uint32_t *)
                   (DEC_3000_300_IOASIC_ADDR + IOASIC_INTR);
               ioasicimr = *(volatile uint32_t *)
                   (DEC_3000_300_IOASIC_ADDR + IOASIC_IMSK);
               tc_mb();

               /* Ignore interrupts that aren't enabled out. */
               ioasicir &= ioasicimr;

               /* clear the interrupts/errors we found. */
               *(volatile uint32_t *)TC_3000_300_IR = tcir;
               /* XXX can't clear TC option slot interrupts here? */
               tc_wmb();

               ifound = 0;

#define INCRINTRCNT(slot)       tc_3000_300_intr[slot].tci_evcnt.ev_count++

#define CHECKINTR(slot, flag)                                           \
               if (flag) {                                             \
                       ifound = 1;                                     \
                       INCRINTRCNT(slot);                              \
                       (*tc_3000_300_intr[slot].tci_func)              \
                           (tc_3000_300_intr[slot].tci_arg);           \
               }
               /* Do them in order of priority; highest slot # first. */
               CHECKINTR(TC_3000_300_DEV_CXTURBO,
                   tcir & TC_3000_300_IR_CXTURBO);
               CHECKINTR(TC_3000_300_DEV_IOASIC,
                   (tcir & TC_3000_300_IR_IOASIC) &&
                   (ioasicir & ~(IOASIC_INTR_300_OPT1|IOASIC_INTR_300_OPT0)));
               CHECKINTR(TC_3000_300_DEV_TCDS, tcir & TC_3000_300_IR_TCDS);
               CHECKINTR(TC_3000_300_DEV_OPT1,
                   ioasicir & IOASIC_INTR_300_OPT1);
               CHECKINTR(TC_3000_300_DEV_OPT0,
                   ioasicir & IOASIC_INTR_300_OPT0);
#undef CHECKINTR

#ifdef DIAGNOSTIC
#define PRINTINTR(msg, bits)                                            \
       if (tcir & bits)                                                \
               printf(msg);
               PRINTINTR("BCache tag parity error\n",
                   TC_3000_300_IR_BCTAGPARITY);
               PRINTINTR("TC overrun error\n", TC_3000_300_IR_TCOVERRUN);
               PRINTINTR("TC I/O timeout\n", TC_3000_300_IR_TCTIMEOUT);
               PRINTINTR("Bcache parity error\n",
                   TC_3000_300_IR_BCACHEPARITY);
               PRINTINTR("Memory parity error\n", TC_3000_300_IR_MEMPARITY);
#undef PRINTINTR
#endif
       } while (ifound);

       KERNEL_UNLOCK_ONE(NULL);
}

#if NWSDISPLAY > 0
/*
* tc_3000_300_fb_cnattach --
*      Attempt to map the CTB output device to a slot and attach the
* framebuffer as the output side of the console.
*/
int
tc_3000_300_fb_cnattach(uint64_t turbo_slot)
{
       uint32_t output_slot;

       output_slot = turbo_slot & 0xffffffff;

       if (output_slot >= tc_3000_300_nslots) {
               return EINVAL;
       }

       if (output_slot == 0) {
#if NSFB > 0
               sfb_cnattach(KV(0x1c0000000) + 0x02000000);
               return 0;
#else
               return ENXIO;
#endif
       }

       return tc_fb_cnattach(tc_3000_300_slots[output_slot-1].tcs_addr);
}
#endif /* NWSDISPLAY */