root/sys/arch/amiga/dev/kbd.c
/*      $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 */