root/sys/arch/zaurus/dev/zkbd.c
/*      $NetBSD: zkbd.c,v 1.22 2021/08/07 16:19:08 thorpej Exp $        */
/* $OpenBSD: zaurus_kbd.c,v 1.28 2005/12/21 20:36:03 deraadt Exp $ */

/*
 * Copyright (c) 2005 Dale Rahn <drahn@openbsd.org>
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: zkbd.c,v 1.22 2021/08/07 16:19:08 thorpej Exp $");

#include "opt_wsdisplay_compat.h"
#if 0   /* XXX */
#include "apm.h"
#endif
#include "lcdctl.h"

#include <sys/param.h>
#include <sys/systm.h>
#include <sys/device.h>
#include <sys/kmem.h>
#include <sys/kernel.h>
#include <sys/proc.h>
#include <sys/signalvar.h>
#include <sys/callout.h>

#include <arm/xscale/pxa2x0reg.h>
#include <arm/xscale/pxa2x0_gpio.h>

#include <dev/wscons/wsconsio.h>
#include <dev/wscons/wskbdvar.h>
#include <dev/wscons/wsksymdef.h>
#include <dev/wscons/wsksymvar.h>

#include <zaurus/zaurus/zaurus_var.h>
#include <zaurus/dev/zkbdmap.h>
#if NLCDCTL > 0
#include <zaurus/dev/lcdctlvar.h>
#endif

static const int gpio_sense_pins_c3000[] = {
        12,
        17,
        91,
        34,
        36,
        38,
        39,
        -1
};

static const int gpio_strobe_pins_c3000[] = {
        88,
        23,
        24,
        25,
        26,
        27,
        52,
        103,
        107,
        -1,
        108,
        114
};

static const int stuck_keys_c3000[] = {
        1,  7,  15, 22, 23, 31, 39, 47,
        53, 55, 60, 63, 66, 67, 69, 71,
        72, 73, 74, 75, 76, 77, 78, 79,
        82, 85, 86, 87, 90, 91, 92, 94,
        95
};

static const int gpio_sense_pins_c860[] = {
        58,
        59,
        60,
        61,
        62,
        63,
        64,
        65
};

static const int gpio_strobe_pins_c860[] = {
        66,
        67,
        68,
        69,
        70,
        71,
        72,
        73,
        74,
        75,
        76,
        77
};

static const int stuck_keys_c860[] = {
        0,  1,  47, 53, 55, 60, 63, 66,
        67, 69, 71, 72, 73, 74, 76, 77,
        78, 79, 80, 81, 82, 83, 85, 86,
        87, 88, 89, 90, 91, 92, 94, 95
};

#define REP_DELAY1 400
#define REP_DELAYN 100

struct zkbd_softc {
        device_t sc_dev;

        const int *sc_sense_array;
        const int *sc_strobe_array;
        const int *sc_stuck_keys;
        int sc_nsense;
        int sc_nstrobe;
        int sc_nstuck;

        short sc_onkey_pin;
        short sc_sync_pin;
        short sc_swa_pin;
        short sc_swb_pin;
        char *sc_okeystate;
        char *sc_keystate;
        char sc_hinge;          /* 0=open, 1=nonsense, 2=backwards, 3=closed */
        char sc_maxkbdcol;

        struct callout sc_roll_to;

        /* console stuff */
        int sc_polling;
        int sc_pollUD;
        int sc_pollkey;

        /* wskbd bits */
        device_t sc_wskbddev;
        struct wskbd_mapdata *sc_keymapdata;
        int sc_rawkbd;
#ifdef WSDISPLAY_COMPAT_RAWKBD
        const char *sc_xt_keymap;
        struct callout sc_rawrepeat_ch;
#define MAXKEYS 20
        char sc_rep[MAXKEYS];
        int sc_nrep;
#endif
};

static struct zkbd_softc *zkbd_sc;

static int      zkbd_match(device_t, cfdata_t, void *);
static void     zkbd_attach(device_t, device_t, void *);

CFATTACH_DECL_NEW(zkbd, sizeof(struct zkbd_softc),
        zkbd_match, zkbd_attach, NULL, NULL);

static int      zkbd_irq_c3000(void *v);
static int      zkbd_irq_c860(void *v);
static void     zkbd_poll(void *v);
static int      zkbd_on(void *v);
static int      zkbd_sync(void *v);
static int      zkbd_hinge(void *v);
static bool     zkbd_resume(device_t dv, const pmf_qual_t *);

int zkbd_modstate;

static int      zkbd_enable(void *, int);
static void     zkbd_set_leds(void *, int);
static int      zkbd_ioctl(void *, u_long, void *, int, struct lwp *);
#ifdef WSDISPLAY_COMPAT_RAWKBD
static void     zkbd_rawrepeat(void *v);
#endif

static struct wskbd_accessops zkbd_accessops = {
        zkbd_enable,
        zkbd_set_leds,
        zkbd_ioctl,
};

static void     zkbd_cngetc(void *, u_int *, int *);
static void     zkbd_cnpollc(void *, int);

static struct wskbd_consops zkbd_consops = {
        zkbd_cngetc,
        zkbd_cnpollc,
};

static struct wskbd_mapdata zkbd_keymapdata = {
        zkbd_keydesctab,
        KB_US,
};

static struct wskbd_mapdata zkbd_keymapdata_c860 = {
        zkbd_keydesctab_c860,
        KB_US,
};

static int
zkbd_match(device_t parent, cfdata_t cf, void *aux)
{

        if (zkbd_sc)
                return 0;

        return 1;
}

static void
zkbd_attach(device_t parent, device_t self, void *aux)
{
        struct zkbd_softc *sc = device_private(self);
        struct wskbddev_attach_args a;
        int pin, i;

        sc->sc_dev = self;
        zkbd_sc = sc;

        aprint_normal("\n");
        aprint_naive("\n");

        sc->sc_polling = 0;
#ifdef WSDISPLAY_COMPAT_RAWKBD
        sc->sc_rawkbd = 0;
#endif

        callout_init(&sc->sc_roll_to, 0);
        callout_setfunc(&sc->sc_roll_to, zkbd_poll, sc);
#ifdef WSDISPLAY_COMPAT_RAWKBD
        callout_init(&sc->sc_rawrepeat_ch, 0);
        callout_setfunc(&sc->sc_rawrepeat_ch, zkbd_rawrepeat, sc);
#endif

        if (ZAURUS_ISC1000 || ZAURUS_ISC3000) {
                sc->sc_sense_array = gpio_sense_pins_c3000;
                sc->sc_strobe_array = gpio_strobe_pins_c3000;
                sc->sc_nsense = __arraycount(gpio_sense_pins_c3000);
                sc->sc_nstrobe = __arraycount(gpio_strobe_pins_c3000);
                sc->sc_stuck_keys = stuck_keys_c3000;
                sc->sc_nstuck = __arraycount(stuck_keys_c3000);
                sc->sc_maxkbdcol = 10;
                sc->sc_onkey_pin = 95;
                sc->sc_sync_pin = 16;
                sc->sc_swa_pin = 97;
                sc->sc_swb_pin = 96;
                sc->sc_keymapdata = &zkbd_keymapdata;
#ifdef WSDISPLAY_COMPAT_RAWKBD
                sc->sc_xt_keymap = xt_keymap;
#endif
        } else if (ZAURUS_ISC860) {
                sc->sc_sense_array = gpio_sense_pins_c860;
                sc->sc_strobe_array = gpio_strobe_pins_c860;
                sc->sc_nsense = __arraycount(gpio_sense_pins_c860);
                sc->sc_nstrobe = __arraycount(gpio_strobe_pins_c860);
                sc->sc_stuck_keys = stuck_keys_c860;
                sc->sc_nstuck = __arraycount(stuck_keys_c860);
                sc->sc_maxkbdcol = 0;
                sc->sc_onkey_pin = -1;
                sc->sc_sync_pin = -1;
                sc->sc_swa_pin = -1;
                sc->sc_swb_pin = -1;
                sc->sc_keymapdata = &zkbd_keymapdata_c860;
#ifdef WSDISPLAY_COMPAT_RAWKBD
                sc->sc_xt_keymap = xt_keymap_c860;
#endif
        } else {
                /* XXX */
                return;
        }

        if (!pmf_device_register(sc->sc_dev, NULL, zkbd_resume))
                aprint_error_dev(sc->sc_dev,
                    "couldn't establish power handler\n");

        sc->sc_okeystate = kmem_zalloc(sc->sc_nsense * sc->sc_nstrobe,
            KM_SLEEP);
        sc->sc_keystate = kmem_zalloc(sc->sc_nsense * sc->sc_nstrobe,
            KM_SLEEP);

        /* set all the strobe bits */
        for (i = 0; i < sc->sc_nstrobe; i++) {
                pin = sc->sc_strobe_array[i];
                if (pin == -1)
                        continue;
                pxa2x0_gpio_set_function(pin, GPIO_SET|GPIO_OUT);
        }

        /* set all the sense bits */
        for (i = 0; i < sc->sc_nsense; i++) {
                pin = sc->sc_sense_array[i];
                if (pin == -1)
                        continue;
                pxa2x0_gpio_set_function(pin, GPIO_IN);
                if (ZAURUS_ISC1000 || ZAURUS_ISC3000) {
                        pxa2x0_gpio_intr_establish(pin, IST_EDGE_BOTH,
                            IPL_TTY, zkbd_irq_c3000, sc);
                } else if (ZAURUS_ISC860) {
                        pxa2x0_gpio_intr_establish(pin, IST_EDGE_RISING,
                            IPL_TTY, zkbd_irq_c860, sc);
                }
        }

        if (sc->sc_onkey_pin >= 0)
                pxa2x0_gpio_intr_establish(sc->sc_onkey_pin, IST_EDGE_BOTH,
                    IPL_TTY, zkbd_on, sc);
        if (sc->sc_sync_pin >= 0)
                pxa2x0_gpio_intr_establish(sc->sc_sync_pin, IST_EDGE_RISING,
                    IPL_TTY, zkbd_sync, sc);
        if (sc->sc_swa_pin >= 0)
                pxa2x0_gpio_intr_establish(sc->sc_swa_pin, IST_EDGE_BOTH,
                    IPL_TTY, zkbd_hinge, sc);
        if (sc->sc_swb_pin >= 0)
                pxa2x0_gpio_intr_establish(sc->sc_swb_pin, IST_EDGE_BOTH,
                    IPL_TTY, zkbd_hinge, sc);

        if (glass_console) {
                wskbd_cnattach(&zkbd_consops, sc, sc->sc_keymapdata);
                a.console = 1;
        } else {
                a.console = 0;
        }
        a.keymap = sc->sc_keymapdata;
        a.accessops = &zkbd_accessops;
        a.accesscookie = sc;

        zkbd_hinge(sc);         /* to initialize sc_hinge */

        sc->sc_wskbddev = config_found(self, &a, wskbddevprint, CFARGS_NONE);
}

#ifdef WSDISPLAY_COMPAT_RAWKBD
static void
zkbd_rawrepeat(void *v)
{
        struct zkbd_softc *sc = (struct zkbd_softc *)v;
        int s;

        s = spltty();
        wskbd_rawinput(sc->sc_wskbddev, sc->sc_rep, sc->sc_nrep);
        splx(s);
        callout_schedule(&sc->sc_rawrepeat_ch, hz * REP_DELAYN / 1000);
}
#endif

/* XXX only deal with keys that can be pressed when display is open? */
/* XXX are some not in the array? */
/* handle keypress interrupt */
static int
zkbd_irq_c3000(void *v)
{

        zkbd_poll(v);

        return 1;
}

/* Avoid chattering only for SL-C7x0/860 */
static int
zkbd_irq_c860(void *v)
{
        struct zkbd_softc *sc = (struct zkbd_softc *)v;

        if (!callout_pending(&sc->sc_roll_to)) {
                zkbd_poll(v);
        }

        return 1;
}

static void
zkbd_poll(void *v)
{
        struct zkbd_softc *sc = (struct zkbd_softc *)v;
        int i, j, col, pin, type, keysdown = 0;
        int stuck;
        int keystate;
        int s;
#ifdef WSDISPLAY_COMPAT_RAWKBD
        int npress = 0, ncbuf = 0, c;
        char cbuf[MAXKEYS * 2];
#endif

        s = spltty();

        /* discharge all */
        for (i = 0; i < sc->sc_nstrobe; i++) {
                pin = sc->sc_strobe_array[i];
                if (pin == -1)
                        continue;
                pxa2x0_gpio_clear_bit(pin);
                pxa2x0_gpio_set_dir(pin, GPIO_IN);
        }

        delay(10);
        for (col = 0; col < sc->sc_nstrobe; col++) {
                pin = sc->sc_strobe_array[col];
                if (pin == -1)
                        continue;

                /* activate_col */
                pxa2x0_gpio_set_bit(pin);
                pxa2x0_gpio_set_dir(pin, GPIO_OUT);

                /* wait activate delay */
                delay(10);

                /* read row */
                for (i = 0; i < sc->sc_nsense; i++) {
                        int bit;

                        if (sc->sc_sense_array[i] == -1)
                                continue;
                        bit = pxa2x0_gpio_get_bit(sc->sc_sense_array[i]);
                        if (bit && sc->sc_hinge && col < sc->sc_maxkbdcol)
                                continue;
                        sc->sc_keystate[i + (col * sc->sc_nsense)] = bit;
                }

                /* reset_col */
                pxa2x0_gpio_set_dir(pin, GPIO_IN);

                /* wait discharge delay */
                delay(10);
        }

        /* charge all */
        for (i = 0; i < sc->sc_nstrobe; i++) {
                pin = sc->sc_strobe_array[i];
                if (pin == -1)
                        continue;
                pxa2x0_gpio_set_bit(pin);
                pxa2x0_gpio_set_dir(pin, GPIO_OUT);
        }

        /* force the irqs to clear as we have just played with them. */
        for (i = 0; i < sc->sc_nsense; i++) {
                pin = sc->sc_sense_array[i];
                if (pin == -1)
                        continue;
                pxa2x0_gpio_clear_intr(pin);
        }

        /* process after resetting interrupt */
        zkbd_modstate = (
                (sc->sc_keystate[84] ? (1 << 0) : 0) | /* shift */
                (sc->sc_keystate[93] ? (1 << 1) : 0) | /* Fn */
                (sc->sc_keystate[14] ? (1 << 2) : 0)); /* 'alt' */

        for (i = 0; i < sc->sc_nsense * sc->sc_nstrobe; i++) {
                stuck = 0;
                /* extend  xt_keymap to do this faster. */
                /* ignore 'stuck' keys' */
                for (j = 0; j < sc->sc_nstuck; j++) {
                        if (sc->sc_stuck_keys[j] == i) {
                                stuck = 1;
                                break;
                        }
                }
                if (stuck)
                        continue;

                keystate = sc->sc_keystate[i];
                keysdown |= keystate; /* if any keys held */

#ifdef WSDISPLAY_COMPAT_RAWKBD
                if (sc->sc_polling == 0 && sc->sc_rawkbd) {
                        if ((keystate) || (sc->sc_okeystate[i] != keystate)) {
                                c = sc->sc_xt_keymap[i];
                                if (c & 0x80) {
                                        cbuf[ncbuf++] = 0xe0;
                                }
                                cbuf[ncbuf] = c & 0x7f;

                                if (keystate) {
                                        if (c & 0x80) {
                                                sc->sc_rep[npress++] = 0xe0;
                                        }
                                        sc->sc_rep[npress++] = c & 0x7f;
                                } else {
                                        cbuf[ncbuf] |= 0x80;
                                }
                                ncbuf++;
                                sc->sc_okeystate[i] = keystate;
                        }
                }
#endif

                if ((!sc->sc_rawkbd) && (sc->sc_okeystate[i] != keystate)) {
                        type = keystate ? WSCONS_EVENT_KEY_DOWN :
                            WSCONS_EVENT_KEY_UP;

                        if (sc->sc_polling) {
                                sc->sc_pollkey = i;
                                sc->sc_pollUD = type;
                        } else {
                                wskbd_input(sc->sc_wskbddev, type, i);
                        }

                        sc->sc_okeystate[i] = keystate;
                }
        }

#ifdef WSDISPLAY_COMPAT_RAWKBD
        if (sc->sc_polling == 0 && sc->sc_rawkbd) {
                wskbd_rawinput(sc->sc_wskbddev, cbuf, ncbuf);
                sc->sc_nrep = npress;
                if (npress != 0)
                        callout_schedule(&sc->sc_rawrepeat_ch,
                            hz * REP_DELAY1 / 1000);
                else 
                        callout_stop(&sc->sc_rawrepeat_ch);
        }
#endif
        if (keysdown)
                callout_schedule(&sc->sc_roll_to, hz * REP_DELAYN / 1000 / 2);
        else 
                callout_stop(&sc->sc_roll_to);  /* always cancel? */

        splx(s);
}

#if NAPM > 0
extern  int kbd_reset;
extern  int apm_suspends;
static  int zkbdondown;                         /* on key is pressed */
static  struct timeval zkbdontv = { 0, 0 };     /* last on key event */
const   struct timeval zkbdhalttv = { 3, 0 };   /*  3s for safe shutdown */
const   struct timeval zkbdsleeptv = { 0, 250000 };     /* .25s for suspend */
extern  int lid_suspend;
#endif

static int
zkbd_on(void *v)
{
#if NAPM > 0
        struct zkbd_softc *sc = (struct zkbd_softc *)v;
        int down;

        if (sc->sc_onkey_pin < 0)
                return 1;

        down = pxa2x0_gpio_get_bit(sc->sc_onkey_pin) ? 1 : 0;

        /*
         * Change run mode depending on how long the key is held down.
         * Ignore the key if it gets pressed while the lid is closed.
         *
         * Keys can bounce and we have to work around missed interrupts.
         * Only the second edge is detected upon exit from sleep mode.
         */
        if (down) {
                if (sc->sc_hinge == 3) {
                        zkbdondown = 0;
                } else {
                        microuptime(&zkbdontv);
                        zkbdondown = 1;
                }
        } else if (zkbdondown) {
                if (ratecheck(&zkbdontv, &zkbdhalttv)) {
                        if (kbd_reset == 1) {
                                kbd_reset = 0;
                                psignal(initproc, SIGUSR1);
                        }
                } else if (ratecheck(&zkbdontv, &zkbdsleeptv)) {
                        apm_suspends++;
                }
                zkbdondown = 0;
        }
#endif
        return 1;
}

static int
zkbd_sync(void *v)
{

        return 1;
}

static int
zkbd_hinge(void *v)
{
        struct zkbd_softc *sc = (struct zkbd_softc *)v;
        int a, b;

        if (sc->sc_swa_pin < 0 || sc->sc_swb_pin < 0)
                return 1;

        a = pxa2x0_gpio_get_bit(sc->sc_swa_pin) ? 1 : 0;
        b = pxa2x0_gpio_get_bit(sc->sc_swb_pin) ? 2 : 0;

        sc->sc_hinge = a | b;

        if (sc->sc_hinge == 3) {
#if NAPM > 0 
                if (lid_suspend)
                        apm_suspends++;
#endif
#if NLCDCTL > 0
                lcdctl_blank(true);
#endif
        } else {
#if NLCDCTL > 0
                lcdctl_blank(false);
#endif
        }

        return 1;
}

static int
zkbd_enable(void *v, int on)
{

        return 0;
}

void
zkbd_set_leds(void *v, int on)
{
}

static int
zkbd_ioctl(void *v, u_long cmd, void *data, int flag, struct lwp *l)
{
#ifdef WSDISPLAY_COMPAT_RAWKBD
        struct zkbd_softc *sc = (struct zkbd_softc *)v;
#endif

        switch (cmd) {
        case WSKBDIO_GTYPE:
                *(int *)data = WSKBD_TYPE_ZAURUS;
                return 0;

        case WSKBDIO_SETLEDS:
                return 0;

        case WSKBDIO_GETLEDS:
                *(int *)data = 0;
                return 0;

#ifdef WSDISPLAY_COMPAT_RAWKBD
        case WSKBDIO_SETMODE:
                sc->sc_rawkbd = *(int *)data == WSKBD_RAW;
                callout_stop(&sc->sc_rawrepeat_ch);
                return 0;
#endif
 
        }
        return EPASSTHROUGH;
}

/* implement polling for zaurus_kbd */
static void
zkbd_cngetc(void *v, u_int *type, int *data)
{
        struct zkbd_softc *sc = (struct zkbd_softc *)zkbd_sc;

        sc->sc_pollkey = -1;
        sc->sc_pollUD = -1;
        sc->sc_polling = 1;
        while (sc->sc_pollkey == -1) {
                zkbd_poll(sc);
                DELAY(10000);   /* XXX */
        }
        sc->sc_polling = 0;
        *data = sc->sc_pollkey;
        *type = sc->sc_pollUD;
}

static void
zkbd_cnpollc(void *v, int on)
{
}

static bool
zkbd_resume(device_t dv, const pmf_qual_t *qual)
{
        struct zkbd_softc *sc = device_private(dv);

        zkbd_hinge(sc);

        return true;
}