/*      $NetBSD: kbd.c,v 1.62 2024/09/08 09:36:48 rillig Exp $ */

/*
* Copyright (c) 1982, 1986, 1990 The Regents of the University of California.
* All rights reserved.
*
* 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. Neither the name of the University nor the names of its contributors
*    may be used to endorse or promote products derived from this software
*    without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE REGENTS 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 REGENTS 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.
*
*      kbd.c
*/

#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: kbd.c,v 1.62 2024/09/08 09:36:48 rillig Exp $");

#include <sys/param.h>
#include <sys/systm.h>
#include <sys/device.h>
#include <sys/ioctl.h>
#include <sys/tty.h>
#include <sys/proc.h>
#include <sys/file.h>
#include <sys/kernel.h>
#include <sys/syslog.h>
#include <sys/signalvar.h>
#include <sys/conf.h>
#include <dev/cons.h>
#include <machine/cpu.h>
#include <amiga/amiga/device.h>
#include <amiga/amiga/custom.h>
#ifdef DRACO
#include <m68k/asm_single.h>
#include <amiga/amiga/drcustom.h>
#endif
#include <amiga/amiga/cia.h>
#include <amiga/dev/itevar.h>
#include <amiga/dev/kbdreg.h>
#include <amiga/dev/kbdmap.h>
#include <amiga/dev/kbdvar.h>
#include <amiga/dev/event_var.h>
#include <amiga/dev/vuid_event.h>

#include "kbd.h"
#include "ite.h"

/* WSKBD */

/*
* If NWSKBD>0 we try to attach a wskbd device to us. What follows
* is definitions of callback functions and structures that are passed
* to wscons when initializing.
*/

/*
* Now with wscons this driver exhibits some weird behaviour.
* It may act both as a driver of its own and the md part of the
* wskbd driver. Therefore it can be accessed through /dev/kbd
* and /dev/wskbd0 both.
*
* The data from they keyboard may end up in at least four different
* places:
* - If this driver has been opened (/dev/kbd) and the
*   direct mode (TIOCDIRECT) has been set, data goes to
*   the process who opened the device. Data will transmit itself
*   as described by the firm_event structure.
* - If wskbd support is compiled in and a wskbd driver has been
*   attached then the data is sent to it. Wskbd in turn may
*   - Send the data in the wscons_event form to a process that
*     has opened /dev/wskbd0
*   - Feed the data to a virtual terminal.
* - If an ite is present the data may be fed to it.
*/

#include "wskbd.h"

#if NWSKBD>0
#include <dev/wscons/wsconsio.h>
#include <dev/wscons/wskbdvar.h>
#include <dev/wscons/wsksymdef.h>
#include <dev/wscons/wsksymvar.h>
#include <amiga/dev/wskbdmap_amiga.h>

/* accessops */
int     kbd_enable(void *, int);
void    kbd_set_leds(void *, int);
int     kbd_ioctl(void *, u_long, void *, int, struct lwp *);

/* console ops */
void    kbd_getc(void *, u_int *, int *);
void    kbd_pollc(void *, int);
void    kbd_bell(void *, u_int, u_int, u_int);

static struct wskbd_accessops kbd_accessops = {
       kbd_enable,
       kbd_set_leds,
       kbd_ioctl
};

static struct wskbd_consops kbd_consops = {
       kbd_getc,
       kbd_pollc,
       kbd_bell
};

/*
* Pointer to keymaps. They are defined in wskbdmap_amiga.c.
*/
static struct wskbd_mapdata kbd_mapdata = {
       amigakbd_keydesctab,
       KB_US
};

#endif /* WSKBD */

struct kbd_softc {
       int k_event_mode;       /* if true, collect events, else pass to ite */
       struct evvar k_events;  /* event queue state */
#ifdef DRACO
       u_char k_rlprfx;        /* MF-II rel. prefix has been seen */
       u_char k_mf2;
#endif

       int k_console;          /* true if used as console keyboard */
#if NWSKBD>0
       device_t k_wskbddev; /* pointer to wskbd for sending strokes */
       int k_pollingmode;         /* Polling mode on? Otherwise send events. */
#endif
};
struct kbd_softc kbd_softc;

int kbdmatch(device_t, cfdata_t, void *);
void kbdattach(device_t, device_t, void *);
void kbdintr(int);
void kbdstuffchar(u_char);

int drkbdgetc(void);
int drkbdrputc(u_int8_t);
int drkbdputc(u_int8_t);
int drkbdputc2(u_int8_t, u_int8_t);
int drkbdwaitfor(int);

CFATTACH_DECL_NEW(kbd, 0,
   kbdmatch, kbdattach, NULL, NULL);

dev_type_open(kbdopen);
dev_type_close(kbdclose);
dev_type_read(kbdread);
dev_type_ioctl(kbdioctl);
dev_type_poll(kbdpoll);
dev_type_kqfilter(kbdkqfilter);

const struct cdevsw kbd_cdevsw = {
       .d_open = kbdopen,
       .d_close = kbdclose,
       .d_read = kbdread,
       .d_write = nowrite,
       .d_ioctl = kbdioctl,
       .d_stop = nostop,
       .d_tty = notty,
       .d_poll = kbdpoll,
       .d_mmap = nommap,
       .d_kqfilter = kbdkqfilter,
       .d_discard = nodiscard,
       .d_flag = 0
};

/*ARGSUSED*/
int
kbdmatch(device_t parent, cfdata_t cf, void *aux)
{

       if (matchname((char *)aux, "kbd"))
               return(1);
       return(0);
}

/*ARGSUSED*/
void
kbdattach(device_t parent, device_t self, void *aux)
{
#ifdef DRACO
       kbdenable();
       if (kbd_softc.k_mf2)
               printf(": QuickLogic type MF-II\n");
       else
               printf(": CIA A type Amiga\n");
#else
       printf(": CIA A type Amiga\n");
#endif

#if NWSKBD>0
       if (self != NULL) {
               /*
                * Try to attach the wskbd.
                */
               struct wskbddev_attach_args waa;
               waa.console = kbd_softc.k_console;
               waa.keymap = &kbd_mapdata;
               waa.accessops = &kbd_accessops;
               waa.accesscookie = NULL;
               kbd_softc.k_wskbddev = config_found(self, &waa, wskbddevprint,
                   CFARGS(.iattr = "wskbddev"));

               kbd_softc.k_pollingmode = 0;
       }
       kbdenable();
#endif /* WSKBD */
}

/*
* This is called when somebody wants to use kbd as the console keyboard.
*/
void
kbd_cnattach(void)
{
#if NWSKBD>0
       wskbd_cnattach(&kbd_consops, NULL, &kbd_mapdata);
       kbd_softc.k_console = 1;
#endif
}

/* definitions for amiga keyboard encoding. */
#define KEY_CODE(c)  ((c) & 0x7f)
#define KEY_UP(c)    ((c) & 0x80)

#define DATLO single_inst_bclr_b(draco_ioct->io_control, DRCNTRL_KBDDATOUT)
#define DATHI single_inst_bset_b(draco_ioct->io_control, DRCNTRL_KBDDATOUT)

#define CLKLO single_inst_bclr_b(draco_ioct->io_control, DRCNTRL_KBDCLKOUT)
#define CLKHI single_inst_bset_b(draco_ioct->io_control, DRCNTRL_KBDCLKOUT)

void
kbdenable(void)
{
       static int kbd_inited = 0;

       int s;

#ifdef DRACO
       int id;
#endif
       /*
        * collides with external ints from SCSI, watch out for this when
        * enabling/disabling interrupts there !!
        */
       s = splhigh();  /* don't lower; might be called from early ddb */
       if (kbd_inited) {
               splx(s);
               return;
       }
       kbd_inited = 1;
#ifdef DRACO
       if (is_draco()) {

               CLKLO;
               delay(5000);
               draco_ioct->io_kbdrst = 0;

               if (drkbdputc(0xf2))
                       goto LnoMFII;

               id = drkbdgetc() << 8;
               id |= drkbdgetc();

               if (id != 0xab83)
                       goto LnoMFII;

               if (drkbdputc2(0xf0, 3))        /* mode 3 */
                       goto LnoMFII;

               if (drkbdputc(0xf8))            /* make/break, no typematic */
                       goto LnoMFII;

               if (drkbdputc(0xf4))            /* enable */
                       goto LnoMFII;
               kbd_softc.k_mf2 = 1;
               single_inst_bclr_b(draco_ioct->io_control, DRCNTRL_KBDINTENA);

               ciaa.icr = CIA_ICR_SP;  /* CIA SP interrupt disable */
               ciaa.cra &= ~(1<<6);    /* serial line == input */
               splx(s);
               return;

       LnoMFII:
               kbd_softc.k_mf2 = 0;
               single_inst_bset_b(*draco_intena, DRIRQ_INT2);
               ciaa.icr = CIA_ICR_IR_SC | CIA_ICR_SP;
                                       /* SP interrupt enable */
               ciaa.cra &= ~(1<<6);    /* serial line == input */
               splx(s);
               return;

       } else {
#endif
       custom.intena = INTF_SETCLR | INTF_PORTS;
       ciaa.icr = CIA_ICR_IR_SC | CIA_ICR_SP;  /* SP interrupt enable */
       ciaa.cra &= ~(1<<6);            /* serial line == input */
#ifdef DRACO
       }
#endif
       kbd_softc.k_event_mode = 0;
       kbd_softc.k_events.ev_io = 0;
       splx(s);
}

#ifdef DRACO
/*
* call this with kbd interrupt blocked
*/

int
drkbdgetc(void)
{
       u_int8_t in;

       while ((draco_ioct->io_status & DRSTAT_KBDRECV) == 0);
       in = draco_ioct->io_kbddata;
       draco_ioct->io_kbdrst = 0;

       return in;
}

#define WAIT0 if (drkbdwaitfor(0)) goto Ltimeout
#define WAIT1 if (drkbdwaitfor(DRSTAT_KBDCLKIN)) goto Ltimeout

int
drkbdwaitfor(int bit)
{
       int i;



       i = 60000;      /* about 50 ms max */

       do {
               if ((draco_ioct->io_status & DRSTAT_KBDCLKIN) == bit)
                       return 0;

       } while (--i >= 0);

       return 1;
}

/*
* Output a raw byte to the keyboard (+ parity and stop bit).
* return 0 on success, 1 on timeout.
*/
int
drkbdrputc(u_int8_t c)
{
       u_int8_t parity;
       int bitcnt;

       DATLO; CLKHI; WAIT1;
       parity = 0;

       for (bitcnt=7; bitcnt >= 0; bitcnt--) {
               WAIT0;
               if (c & 1) {
                       DATHI;
               } else {
                       ++parity;
                       DATLO;
               }
               c >>= 1;
               WAIT1;
       }
       WAIT0;
       /* parity bit */
       if (parity & 1) {
               DATLO;
       } else {
               DATHI;
       }
       WAIT1;
       /* stop bit */
       WAIT0; DATHI; WAIT1;

       WAIT0; /* XXX should check the ack bit here... */
       WAIT1;
       draco_ioct->io_kbdrst = 0;
       return 0;

Ltimeout:
       DATHI;
       draco_ioct->io_kbdrst = 0;
       return 1;
}

/*
* Output one cooked byte to the keyboard, with wait for ACK or RESEND,
* and retry if necessary. 0 == success, 1 == timeout
*/
int
drkbdputc(u_int8_t c)
{
       int rc;

       do {
               if (drkbdrputc(c))
                       return(-1);

               rc = drkbdgetc();
       } while (rc == 0xfe);
       return (!(rc == 0xfa));
}

/*
* same for twobyte sequence
*/

int
drkbdputc2(u_int8_t c1, u_int8_t c2)
{
       int rc;

       do {
               do {
                       if (drkbdrputc(c1))
                               return(-1);

                       rc = drkbdgetc();
               } while (rc == 0xfe);
               if (rc != 0xfa)
                       return (-1);

               if (drkbdrputc(c2))
                       return(-1);

               rc = drkbdgetc();
       } while (rc == 0xfe);
       return (!(rc == 0xfa));
}
#endif

int
kbdopen(dev_t dev, int flags, int mode, struct lwp *l)
{

       kbdenable();
       if (kbd_softc.k_events.ev_io)
               return EBUSY;

       kbd_softc.k_events.ev_io = l->l_proc;
       ev_init(&kbd_softc.k_events);
       return (0);
}

int
kbdclose(dev_t dev, int flags, int mode, struct lwp *l)
{

       /* Turn off event mode, dump the queue */
       kbd_softc.k_event_mode = 0;
       ev_fini(&kbd_softc.k_events);
       kbd_softc.k_events.ev_io = NULL;
       return (0);
}

int
kbdread(dev_t dev, struct uio *uio, int flags)
{
       return ev_read (&kbd_softc.k_events, uio, flags);
}

int
kbdioctl(dev_t dev, u_long cmd, register void *data, int flag,
        struct lwp *l)
{
       register struct kbd_softc *k = &kbd_softc;

       switch (cmd) {
               case KIOCTRANS:
                       if (*(int *)data == TR_UNTRANS_EVENT)
                               return 0;
                       break;

               case KIOCGTRANS:
                       /* Get translation mode */
                       *(int *)data = TR_UNTRANS_EVENT;
                       return 0;

               case KIOCSDIRECT:
                       k->k_event_mode = *(int *)data;
                       return 0;

               case FIONBIO:   /* we will remove this someday (soon???) */
                       return 0;

               case FIOASYNC:
                       k->k_events.ev_async = *(int *)data != 0;
                       return 0;

               case FIOSETOWN:
                       if (-*(int *)data != k->k_events.ev_io->p_pgid
                           && *(int *)data != k->k_events.ev_io->p_pid)
                               return EPERM;
                       return 0;

               case TIOCSPGRP:
                       if (*(int *)data != k->k_events.ev_io->p_pgid)
                               return EPERM;
                       return 0;

               default:
                       return ENOTTY;
       }

       /* We identified the ioctl, but we do not handle it. */
       return EOPNOTSUPP;      /* misuse, but what the heck */
}

int
kbdpoll(dev_t dev, int events, struct lwp *l)
{
       return ev_poll (&kbd_softc.k_events, events, l);
}

int
kbdkqfilter(dev_t dev, struct knote *kn)
{

       return (ev_kqfilter(&kbd_softc.k_events, kn));
}

void
kbdintr(int mask)
{
       u_char c;
#ifdef KBDRESET
       static int reset_warn;
#endif

       /*
        * now only invoked from generic CIA interrupt handler if there *is*
        * a keyboard interrupt pending
        */

       c = ~ciaa.sdr;  /* keyboard data is inverted */
       /* ack */
       ciaa.cra |= (1 << 6);   /* serial line output */
#ifdef KBDRESET
       if (reset_warn && c == 0xf0) {
#ifdef DEBUG
               printf ("kbdintr: !!!! Reset Warning !!!!\n");
#endif
               bootsync();
               reset_warn = 0;
               DELAY(30000000);
       }
#endif
       /* wait 200 microseconds (for bloody Cherry keyboards..) */
       DELAY(200);                     /* fudge delay a bit for some keyboards */
       ciaa.cra &= ~(1 << 6);

       /* process the character */
       c = (c >> 1) | (c << 7);        /* rotate right once */

#ifdef KBDRESET
       if (c == 0x78) {
#ifdef DEBUG
               printf ("kbdintr: Reset Warning started\n");
#endif
               ++reset_warn;
               return;
       }
#endif
       kbdstuffchar(c);
}

#ifdef DRACO
/* maps MF-II keycodes to Amiga keycodes */

const u_char drkbdtab[] = {
       0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x50,
       0x45, 0xff, 0xff, 0xff, 0xff, 0x42, 0x00, 0x51,

       0xff, 0x64, 0x60, 0x30, 0x63, 0x10, 0x01, 0x52,
       0xff, 0x66, 0x31, 0x21, 0x20, 0x11, 0x02, 0x53,

       0xff, 0x33, 0x32, 0x22, 0x12, 0x04, 0x03, 0x54,
       0xff, 0x40, 0x34, 0x23, 0x14, 0x13, 0x05, 0x55,

       0xff, 0x36, 0x35, 0x25, 0x24, 0x15, 0x06, 0x56,
       0xff, 0x67, 0x37, 0x26, 0x16, 0x07, 0x08, 0x57,
       /* --- */
       0xff, 0x38, 0x27, 0x17, 0x18, 0x0a, 0x09, 0x58,
       0xff, 0x39, 0x3a, 0x28, 0x29, 0x19, 0x0b, 0x59,

       0xff, 0xff, 0x2a, 0x2b, 0x1a, 0x0c, 0x4b, 0xff,
       0x65, 0x61, 0x44, 0x1b, 0xff, 0xff, 0x6f, 0xff,

       0x4d, 0x4f, 0xff, 0x4c, 0x0d, 0xff, 0x41, 0x46,
       0xff, 0x1d, 0x4e, 0x2d, 0x3d, 0x4a, 0x5f, 0x62,

       0x0f, 0x3c, 0x1e, 0x2e, 0x2f, 0x3e, 0x5a, 0x5b,
       0xff, 0x43, 0x1f, 0xff, 0x5e, 0x3f, 0x5c, 0xff,
       /* --- */
       0xff, 0xff, 0xff, 0xff, 0x5d
};
#endif


int
kbdgetcn(void)
{
       int s;
       u_char ints, mask, c, in;

#ifdef DRACO
       if (is_draco() && kbd_softc.k_mf2) {
               do {
                       c = 0;
                       s = spltty ();
                       while ((draco_ioct->io_status & DRSTAT_KBDRECV) == 0);
                       in = draco_ioct->io_kbddata;
                       draco_ioct->io_kbdrst = 0;
                       if (in == 0xF0) { /* release prefix */
                               c = 0x80;
                               while ((draco_ioct->io_status &
                                   DRSTAT_KBDRECV) == 0);
                               in = draco_ioct->io_kbddata;
                               draco_ioct->io_kbdrst = 0;
                       }
                       splx(s);
#ifdef DRACORAWKEYDEBUG
                       printf("<%02x>", in);
#endif
                       c |= in>=sizeof(drkbdtab) ? 0xff : drkbdtab[in];
               } while (c == 0xff);
               return (c);
       }
#endif
       s = spltty();
       for (ints = 0; ! ((mask = ciaa.icr) & CIA_ICR_SP);
           ints |= mask) ;

       in = ciaa.sdr;
       c = ~in;

       /* ack */
       ciaa.cra |= (1 << 6);   /* serial line output */
       ciaa.sdr = 0xff;        /* ack */
       /* wait 200 microseconds */
       DELAY(200);     /* XXXX only works as long as DELAY doesn't
                        * use a timer and waits.. */
       ciaa.cra &= ~(1 << 6);
       ciaa.sdr = in;

       splx (s);
       c = (c >> 1) | (c << 7);

       /* take care that no CIA-interrupts are lost */
       if (ints)
               dispatch_cia_ints (0, ints);

       return c;
}

void
kbdstuffchar(u_char c)
{
       struct firm_event *fe;
       struct kbd_softc *k = &kbd_softc;
       int put;

#if NWSKBD>0
       /*
        * If we have attached a wskbd and not in polling mode and
        * nobody has opened us directly, then send the keystroke
        * to the wskbd.
        */

       if (kbd_softc.k_pollingmode == 0
           && kbd_softc.k_wskbddev != NULL
           && k->k_event_mode == 0) {
               wskbd_input(kbd_softc.k_wskbddev,
                           KEY_UP(c) ?
                           WSCONS_EVENT_KEY_UP :
                           WSCONS_EVENT_KEY_DOWN,
                           KEY_CODE(c));
               return;
       }

#endif /* NWSKBD */

       /*
        * If not in event mode, deliver straight to ite to process
        * key stroke
        */

       if (! k->k_event_mode) {
#if NITE>0
               ite_filter (c, ITEFILT_TTY);
#endif
               return;
       }

       /*
        * Keyboard is generating events. Turn this keystroke into an
        * event and put it in the queue. If the queue is full, the
        * keystroke is lost (sorry!).
        */

       put = k->k_events.ev_put;
       fe = &k->k_events.ev_q[put];
       put = (put + 1) % EV_QSIZE;
       if (put == k->k_events.ev_get) {
               log(LOG_WARNING, "keyboard event queue overflow\n");
                       /* ??? */
               return;
       }
       fe->id = KEY_CODE(c);
       fe->value = KEY_UP(c) ? VKEY_UP : VKEY_DOWN;
       firm_gettime(fe);
       k->k_events.ev_put = put;
       EV_WAKEUP(&k->k_events);
}


#ifdef DRACO
void
drkbdintr(void)
{
       u_char in;
       struct kbd_softc *k = &kbd_softc;

       in = draco_ioct->io_kbddata;
       draco_ioct->io_kbdrst = 0;

       if (in == 0xF0)
               k->k_rlprfx = 0x80;
       else {
               kbdstuffchar(in>=sizeof(drkbdtab) ? 0xff :
                   drkbdtab[in] | k->k_rlprfx);
               k->k_rlprfx = 0;
       }
}

#endif


#if NWSKBD>0
/*
* These are the callback functions that are passed to wscons.
* They really don't do anything worth noting, just call the
* other functions above.
*/

int
kbd_enable(void *c, int on)
{
       /* Wonder what this is supposed to do... */
       return (0);
}

void
kbd_set_leds(void *c, int leds)
{
}

int
kbd_ioctl(void *c, u_long cmd, void *data, int flag, struct lwp *l)
{
       switch (cmd)
       {
       case WSKBDIO_COMPLEXBELL:
               return 0;
       case WSKBDIO_SETLEDS:
               return 0;
       case WSKBDIO_GETLEDS:
               *(int*)data = 0;
               return 0;
       case WSKBDIO_GTYPE:
               *(u_int*)data = WSKBD_TYPE_AMIGA;
               return 0;
       }

       /*
        * We are supposed to return EPASSTHROUGH to wscons if we didn't
        * understand.
        */
       return (EPASSTHROUGH);
}

void
kbd_getc(void *c, u_int *type, int *data)
{
       int key;

       key = kbdgetcn();

       *data = KEY_CODE(key);
       *type = KEY_UP(key) ? WSCONS_EVENT_KEY_UP : WSCONS_EVENT_KEY_DOWN;
}

void
kbd_pollc(void *c, int on)
{
       kbd_softc.k_pollingmode = on;
}

void
kbd_bell(void *c, u_int x, u_int y, u_int z)
{
}
#endif /* WSKBD */