root/sys/dev/adb/adb_kbd.c
/*      $NetBSD: adb_kbd.c,v 1.36 2025/06/16 08:00:50 macallan Exp $    */

/*
 * Copyright (C) 1998   Colin Wood
 * Copyright (C) 2006, 2007 Michael Lorenz
 * 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. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *      This product includes software developed by Colin Wood.
 * 4. The name of the author may not be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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.
 */

#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: adb_kbd.c,v 1.36 2025/06/16 08:00:50 macallan Exp $");

#ifdef _KERNEL_OPT
#include "opt_ddb.h"
#endif

#include <sys/param.h>
#include <sys/device.h>
#include <sys/sysctl.h>
#include <sys/condvar.h>

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

#include <dev/sysmon/sysmonvar.h>
#include <dev/sysmon/sysmon_taskq.h>

#include <machine/autoconf.h>

#include <dev/adb/adbvar.h>
#include <dev/adb/adb_keymap.h>

#include "ioconf.h"

#include "opt_wsdisplay_compat.h"
#include "opt_adbkbd.h"
#include "adbdebug.h"
#include "wsmouse.h"

struct adbkbd_softc {
        device_t sc_dev;
        struct adb_device *sc_adbdev;
        struct adb_bus_accessops *sc_ops;
        device_t sc_wskbddev;
#if NWSMOUSE > 0
        device_t sc_wsmousedev;
#endif
        struct sysmon_pswitch sc_sm_pbutton;
        int sc_leds;
        int sc_have_led_control;
        int sc_power_button_delay;
        int sc_msg_len;
        kcondvar_t sc_event;
        kmutex_t sc_interlock;
        int sc_poll;
        int sc_polled_chars;
        int sc_trans[3];
        int sc_capslock;
        uint32_t sc_timestamp;
#ifdef WSDISPLAY_COMPAT_RAWKBD
        int sc_rawkbd;
#endif
        int sc_emul_usb;
        bool sc_power_dbg;

        uint32_t sc_power;
        uint8_t sc_buffer[16];
        uint8_t sc_pollbuf[16];
        uint8_t sc_us, sc_pe;
};      

/*
 * Function declarations.
 */
static int      adbkbd_match(device_t, cfdata_t, void *);
static void     adbkbd_attach(device_t, device_t, void *);

static void     adbkbd_initleds(struct adbkbd_softc *);
static void     adbkbd_keys(struct adbkbd_softc *, uint8_t, uint8_t);
static inline void adbkbd_key(struct adbkbd_softc *, uint8_t);
static int      adbkbd_wait(struct adbkbd_softc *, int);

/* Driver definition. */
CFATTACH_DECL_NEW(adbkbd, sizeof(struct adbkbd_softc),
    adbkbd_match, adbkbd_attach, NULL, NULL);

static int adbkbd_enable(void *, int);
static int adbkbd_ioctl(void *, u_long, void *, int, struct lwp *);
static void adbkbd_set_leds(void *, int);
static void adbkbd_handler(void *, int, uint8_t *);
static void adbkbd_powerbutton(void *);

struct wskbd_accessops adbkbd_accessops = {
        adbkbd_enable,
        adbkbd_set_leds,
        adbkbd_ioctl,
};

static void adbkbd_cngetc(void *, u_int *, int *);
static void adbkbd_cnpollc(void *, int);

struct wskbd_consops adbkbd_consops = {
        adbkbd_cngetc,
        adbkbd_cnpollc,
};

struct wskbd_mapdata adbkbd_keymapdata = {
        akbd_keydesctab,
#ifdef ADBKBD_LAYOUT
        ADBKBD_LAYOUT,
#else
        KB_US | KB_APPLE,
#endif
};

#if NWSMOUSE > 0
static int adbkms_enable(void *);
static int adbkms_ioctl(void *, u_long, void *, int, struct lwp *);
static void adbkms_disable(void *);

const struct wsmouse_accessops adbkms_accessops = {
        adbkms_enable,
        adbkms_ioctl,
        adbkms_disable,
};

static int  adbkbd_sysctl_mid(SYSCTLFN_ARGS);
static int  adbkbd_sysctl_right(SYSCTLFN_ARGS);
static int  adbkbd_sysctl_usb(SYSCTLFN_ARGS);

#endif /* NWSMOUSE > 0 */

static void adbkbd_setup_sysctl(struct adbkbd_softc *);

#ifdef ADBKBD_DEBUG
#define DPRINTF printf
#else
#define DPRINTF while (0) printf
#endif

static int adbkbd_is_console = 0;
static int adbkbd_console_attached = 0;

static int
adbkbd_match(device_t parent, cfdata_t cf, void *aux)
{
        struct adb_attach_args *aaa = aux;

        if (aaa->dev->original_addr == ADBADDR_KBD)
                return 1;
        else
                return 0;
}

static void
adbkbd_attach(device_t parent, device_t self, void *aux)
{
        struct adbkbd_softc *sc = device_private(self);
        struct adb_attach_args *aaa = aux;
        short cmd;
        struct wskbddev_attach_args a;
#if NWSMOUSE > 0
        struct wsmousedev_attach_args am;
#endif
        uint8_t buffer[2];

        sc->sc_dev = self;
        sc->sc_ops = aaa->ops;
        sc->sc_adbdev = aaa->dev;
        sc->sc_adbdev->cookie = sc;
        sc->sc_adbdev->handler = adbkbd_handler;
        mutex_init(&sc->sc_interlock, MUTEX_DEFAULT, IPL_NONE);
        cv_init(&sc->sc_event, "adbkbd");
        sc->sc_us = ADBTALK(sc->sc_adbdev->current_addr, 0);

        sc->sc_leds = 0;        /* initially off */
        sc->sc_have_led_control = 0;

        /*
         * If this is != 0 then pushing the power button will not immadiately
         * send a shutdown event to sysmon but instead require another key
         * press within 5 seconds with a gap of at least two seconds. The 
         * reason to do this is the fact that some PowerBook keyboards,
         * like the 2400, 3400 and original G3 have their power buttons
         * right next to the backspace key and it's extremely easy to hit
         * it by accident.
         * On most other keyboards the power button is sufficiently far out
         * of the way so we don't need this.
         */
        sc->sc_power_button_delay = 0;
        sc->sc_msg_len = 0;
        sc->sc_poll = 0;
        sc->sc_capslock = 0;
        sc->sc_trans[1] = 103;  /* F11 */
        sc->sc_trans[2] = 111;  /* F12 */
        
        /*
         * Most ADB keyboards send 0x7f 0x7f when the power button is pressed.
         * Some older PowerBooks, like the 3400c, will send a single scancode
         * 0x7e instead. Unfortunately Fn-Command on some more recent *Books
         * sends the same scancode, so by default sc_power is set to a value
         * that can't occur as a scancode and only set to 0x7e on hardware that
         * needs it
         */
        sc->sc_power = 0xffff;
        sc->sc_timestamp = 0;
        sc->sc_emul_usb = ADB_EMUL_USB_NONE;
#ifdef ADBKBD_POWER_DDB
        sc->sc_power_dbg = TRUE;
#else
        sc->sc_power_dbg = FALSE;
#endif

        aprint_normal(" addr %d: ", sc->sc_adbdev->current_addr);

        switch (sc->sc_adbdev->handler_id) {
        case ADB_STDKBD:
                aprint_normal("standard keyboard\n");
                break;
        case ADB_ISOKBD:
                aprint_normal("standard keyboard (ISO layout)\n");
                break;
        case ADB_EXTKBD:
                cmd = ADBTALK(sc->sc_adbdev->current_addr, 1);
                sc->sc_msg_len = 0;
                sc->sc_ops->send(sc->sc_ops->cookie, sc->sc_poll, cmd, 0, NULL);
                adbkbd_wait(sc, 10);

                /* Ignore Logitech MouseMan/Trackman pseudo keyboard */
                /* XXX needs testing */
                if (sc->sc_buffer[2] == 0x9a && sc->sc_buffer[3] == 0x20) {
                        aprint_normal("Mouseman (non-EMP) pseudo keyboard\n");
                        return;
                } else if (sc->sc_buffer[2] == 0x9a && 
                    sc->sc_buffer[3] == 0x21) {
                        aprint_normal("Trackman (non-EMP) pseudo keyboard\n");
                        return;
                } else {
                        aprint_normal("extended keyboard\n");
                        adbkbd_initleds(sc);
                }
                break;
        case ADB_EXTISOKBD:
                aprint_normal("extended keyboard (ISO layout)\n");
                adbkbd_initleds(sc);
                break;
        case ADB_KBDII:
                aprint_normal("keyboard II\n");
                break;
        case ADB_ISOKBDII:
                aprint_normal("keyboard II (ISO layout)\n");
                break;
        case ADB_PBKBD:
                aprint_normal("PowerBook keyboard\n");
                sc->sc_power = 0x7e;
                sc->sc_power_button_delay = 1;
                break;
        case ADB_PBISOKBD:
                aprint_normal("PowerBook keyboard (ISO layout)\n");
                sc->sc_power = 0x7e;
                sc->sc_power_button_delay = 1;
                break;
        case ADB_ADJKPD:
                aprint_normal("adjustable keypad\n");
                break;
        case ADB_ADJKBD:
                aprint_normal("adjustable keyboard\n");
                break;
        case ADB_ADJISOKBD:
                aprint_normal("adjustable keyboard (ISO layout)\n");
                break;
        case ADB_ADJJAPKBD:
                aprint_normal("adjustable keyboard (Japanese layout)\n");
                break;
        case ADB_PBEXTISOKBD:
                aprint_normal("PowerBook extended keyboard (ISO layout)\n");
                sc->sc_power_button_delay = 1;
                sc->sc_power = 0x7e;
                break;
        case ADB_PBEXTJAPKBD:
                aprint_normal("PowerBook extended keyboard (Japanese layout)\n");
                sc->sc_power_button_delay = 1;
                sc->sc_power = 0x7e;
                break;
        case ADB_JPKBDII:
                aprint_normal("keyboard II (Japanese layout)\n");
                break;
        case ADB_PBEXTKBD:
                aprint_normal("PowerBook extended keyboard\n");
                sc->sc_power_button_delay = 1;
                sc->sc_power = 0x7e;
                break;
        case ADB_DESIGNKBD:
                aprint_normal("extended keyboard\n");
                adbkbd_initleds(sc);
                break;
        case ADB_PBJPKBD:
                aprint_normal("PowerBook keyboard (Japanese layout)\n");
                sc->sc_power_button_delay = 1;
                sc->sc_power = 0x7e;
                break;
        case ADB_PBG3KBD:
                aprint_normal("PowerBook G3 keyboard\n");
                break;
        case ADB_PBG3JPKBD:
                aprint_normal("PowerBook G3 keyboard (Japanese layout)\n");
                break;
        case ADB_IBOOKKBD:
                aprint_normal("iBook keyboard\n");
                break;
        default:
                aprint_normal("mapped device (%d)\n", sc->sc_adbdev->handler_id);
                break;
        }

        /*
         * try to switch to extended protocol
         * as in, tell the keyboard to distinguish between left and right
         * Shift, Control and Alt keys
         */
        cmd = ADBLISTEN(sc->sc_adbdev->current_addr, 3);
        buffer[0] = sc->sc_adbdev->current_addr;
        buffer[1] = 3;
        sc->sc_msg_len = 0;
        sc->sc_ops->send(sc->sc_ops->cookie, sc->sc_poll, cmd, 2, buffer);
        adbkbd_wait(sc, 10);

        cmd = ADBTALK(sc->sc_adbdev->current_addr, 3);
        sc->sc_msg_len = 0;
        sc->sc_ops->send(sc->sc_ops->cookie, sc->sc_poll, cmd, 0, NULL);
        adbkbd_wait(sc, 10);
        if ((sc->sc_msg_len == 4) && (sc->sc_buffer[3] == 3)) {
                aprint_verbose_dev(sc->sc_dev, "extended protocol enabled\n");
        }

#ifdef ADBKBD_DEBUG
        cmd = ADBTALK(sc->sc_adbdev->current_addr, 1);
        sc->sc_msg_len = 0;
        sc->sc_ops->send(sc->sc_ops->cookie, sc->sc_poll, cmd, 0, NULL);
        adbkbd_wait(sc, 10);
        printf("buffer: %02x %02x\n", sc->sc_buffer[0], sc->sc_buffer[1]);
#endif

        if (adbkbd_is_console && (adbkbd_console_attached == 0)) {
                wskbd_cnattach(&adbkbd_consops, sc, &adbkbd_keymapdata);
                adbkbd_console_attached = 1;
                a.console = 1;
        } else {
                a.console = 0;
        }
        a.keymap = &adbkbd_keymapdata;
        a.accessops = &adbkbd_accessops;
        a.accesscookie = sc;

        sc->sc_wskbddev = config_found(self, &a, wskbddevprint,
            CFARGS(.iattr = "wskbddev"));
#ifdef ADBKBD_EMUL_USB
        /* Values from Linux's drivers/macintosh/adbhud.c */
        switch (sc->sc_adbdev->handler_id) {
        case ADB_ISOKBD:        /* FALLTHROUGH */
        case ADB_EXTISOKBD:     /* FALLTHROUGH */
        case 0x07:              /* FALLTHROUGH */
        case ADB_ISOKBDII:      /* FALLTHROUGH */
        case ADB_PBISOKBD:      /* FALLTHROUGH */
        case ADB_ADJISOKBD:     /* FALLTHROUGH */
        case ADB_PBEXTISOKBD:   /* FALLTHROUGH */
        case 0x19:              /* FALLTHROUGH */
        case 0x1d:              /* FALLTHROUGH */
        case 0xc1:              /* FALLTHROUGH */
        case ADB_IBOOKKBD:      /* FALLTHROUGH */
        case 0xc7:
                sc->sc_emul_usb = ADB_EMUL_USB_ISO;
                wskbd_set_evtrans(sc->sc_wskbddev, adb_to_usb_iso, 128);
                break;
#ifdef notyet
        case ADB_ADJJAPKBD:     /* FALLTHROUGH */
        case ADB_PBEXTJAPKBD:   /* FALLTHROUGH */
        case ADB_JPKBDII:       /* FALLTHROUGH */
        case 0x17:              /* FALLTHROUGH */
        case 0x1a:              /* FALLTHROUGH */
        case ADB_PBJPKBD:       /* FALLTHROUGH */
        case 0xc2:              /* FALLTHROUGH */
        case 0xc5:              /* FALLTHROUGH */
        case 0xc8:              /* FALLTHROUGH */
        case 0xc9:
                sc->sc_emul_usb = ADB_EMUL_USB_JIS;
                wskbd_set_evtrans(sc->sc_wskbddev, adb_to_usb_jis, 128);
                break;
#endif
        case ADB_STDKBD:        /* FALLTHROUGH */
        case ADB_EXTKBD:        /* FALLTHROUGH */
        case 0x03:              /* FALLTHROUGH */
        case 0x06:              /* FALLTHROUGH */
        case ADB_KBDII:         /* FALLTHROUGH */
        case ADB_PBKBD:         /* FALLTHROUGH */
        case ADB_ADJKBD:        /* FALLTHROUGH */
        case ADB_PBEXTKBD:      /* FALLTHROUGH */
        case ADB_DESIGNKBD:     /* FALLTHROUGH */
        case 0x1c:              /* FALLTHROUGH */
        case 0xc0:              /* FALLTHROUGH */
        case ADB_PBG3KBD:       /* FALLTHROUGH */
        case 0xc6:              /* FALLTHROUGH */
        default:        /* default to ANSI for unknown values */
                sc->sc_emul_usb = ADB_EMUL_USB_ANSI;
                wskbd_set_evtrans(sc->sc_wskbddev, adb_to_usb_ansi, 128);
                break;
        }
#endif /* ADBKBD_EMUL_USB */

#if NWSMOUSE > 0
        /* attach the mouse device */
        am.accessops = &adbkms_accessops;
        am.accesscookie = sc;
        sc->sc_wsmousedev = config_found(self, &am, wsmousedevprint,
            CFARGS(.iattr = "wsmousedev"));
#endif
        adbkbd_setup_sysctl(sc);

        /* finally register the power button */
        sysmon_task_queue_init();
        memset(&sc->sc_sm_pbutton, 0, sizeof(struct sysmon_pswitch));
        sc->sc_sm_pbutton.smpsw_name = device_xname(sc->sc_dev);
        sc->sc_sm_pbutton.smpsw_type = PSWITCH_TYPE_POWER;
        if (sysmon_pswitch_register(&sc->sc_sm_pbutton) != 0)
                aprint_error_dev(sc->sc_dev,
                    "unable to register power button with sysmon\n");
}

static void
adbkbd_handler(void *cookie, int len, uint8_t *data)
{
        struct adbkbd_softc *sc = cookie;

#ifdef ADBKBD_DEBUG
        int i;
        printf("%s: %02x - ", device_xname(sc->sc_dev), sc->sc_us);
        for (i = 0; i < len; i++) {
                printf(" %02x", data[i]);
        }
        printf("\n");
#endif
        if (len >= 2) {
                if (data[1] == sc->sc_us) {
                        adbkbd_keys(sc, data[2], data[3]);
                        return;
                } else {
                        memcpy(sc->sc_buffer, data, len);
                }
                sc->sc_msg_len = len;
                cv_signal(&sc->sc_event);
        } else {
                DPRINTF("bogus message\n");
        }
}

static int
adbkbd_wait(struct adbkbd_softc *sc, int timeout)
{
        int cnt = 0;
        
        if (sc->sc_poll) {
                while (sc->sc_msg_len == 0) {
                        sc->sc_ops->poll(sc->sc_ops->cookie);
                }
        } else {
                mutex_enter(&sc->sc_interlock);
                while ((sc->sc_msg_len == 0) && (cnt < timeout)) {
                        cv_timedwait(&sc->sc_event, &sc->sc_interlock, hz);
                        cnt++;
                }
                mutex_exit(&sc->sc_interlock);
        }
        return (sc->sc_msg_len > 0);
}

static void
adbkbd_keys(struct adbkbd_softc *sc, uint8_t k1, uint8_t k2)
{

        /* keyboard event processing */

        DPRINTF("[%02x %02x]", k1, k2);

        if (((k1 == k2) && (k1 == 0x7f)) || (k1 == sc->sc_power)) {
                uint32_t now = time_second;
                uint32_t diff = now - sc->sc_timestamp;

                sc->sc_timestamp = now;
                if (((diff > 1) && (diff < 5)) ||
                     (sc->sc_power_button_delay == 0)) {
#ifdef DDB
                        if (sc->sc_power_dbg) {
                                Debugger();
                                return;
                        }
#endif
                        /* power button, report to sysmon */
                        sc->sc_pe = k1;
                        sysmon_task_queue_sched(0, adbkbd_powerbutton, sc);
                }
        } else {

                adbkbd_key(sc, k1);
                if (k2 != 0xff)
                        adbkbd_key(sc, k2);
        }
}

static void
adbkbd_powerbutton(void *cookie)
{
        struct adbkbd_softc *sc = cookie;

        sysmon_pswitch_event(&sc->sc_sm_pbutton, 
            ADBK_PRESS(sc->sc_pe) ? PSWITCH_EVENT_PRESSED :
            PSWITCH_EVENT_RELEASED);

}

static inline void
adbkbd_key(struct adbkbd_softc *sc, uint8_t k)
{

        if (sc->sc_poll) {
                if (sc->sc_polled_chars >= 16) {
                        aprint_error_dev(sc->sc_dev,"polling buffer is full\n");
                }
                sc->sc_pollbuf[sc->sc_polled_chars] = k;
                sc->sc_polled_chars++;
                return;
        }

#if NWSMOUSE > 0
        /* translate some keys to mouse events */
        if (sc->sc_wsmousedev != NULL) {
                if (ADBK_KEYVAL(k) == sc->sc_trans[1]) {
                        wsmouse_input(sc->sc_wsmousedev, ADBK_PRESS(k) ? 2 : 0,
                              0, 0, 0, 0,
                              WSMOUSE_INPUT_DELTA);
                        return;
                }                       
                if (ADBK_KEYVAL(k) == sc->sc_trans[2]) {
                        wsmouse_input(sc->sc_wsmousedev, ADBK_PRESS(k) ? 4 : 0,
                              0, 0, 0, 0,
                              WSMOUSE_INPUT_DELTA);
                        return;
                }                       
        }
#endif

#ifdef WSDISPLAY_COMPAT_RAWKBD
        if (sc->sc_rawkbd) {
                char cbuf[2];
                int s;

                cbuf[0] = k;

                s = spltty();
                wskbd_rawinput(sc->sc_wskbddev, cbuf, 1);
                splx(s);
        } else {
#endif

        if (ADBK_KEYVAL(k) == 0x39) {
                /* caps lock - send up and down */
                if (ADBK_PRESS(k) != sc->sc_capslock) {
                        sc->sc_capslock = ADBK_PRESS(k);
                        wskbd_input(sc->sc_wskbddev,
                            WSCONS_EVENT_KEY_DOWN, 0x39);
                        wskbd_input(sc->sc_wskbddev,
                            WSCONS_EVENT_KEY_UP, 0x39);
                }
        } else {
                /* normal event */
                int type;

                type = ADBK_PRESS(k) ? 
                    WSCONS_EVENT_KEY_DOWN : WSCONS_EVENT_KEY_UP;
                wskbd_input(sc->sc_wskbddev, type, ADBK_KEYVAL(k));
        }
#ifdef WSDISPLAY_COMPAT_RAWKBD
        }
#endif
}

/*
 * Set the keyboard LED's.
 * 
 * Automatically translates from ioctl/softc format to the
 * actual keyboard register format
 */
static void
adbkbd_set_leds(void *cookie, int leds)
{
        struct adbkbd_softc *sc = cookie;
        int aleds;
        short cmd;
        uint8_t buffer[2];

        DPRINTF("adbkbd_set_leds: %02x\n", leds);
        if ((leds & 0x07) == (sc->sc_leds & 0x07))
                return;

        if (sc->sc_have_led_control) {

                aleds = (~leds & 0x04) | 3;
                if (leds & 1)
                        aleds &= ~2;
                if (leds & 2)
                        aleds &= ~1;

                buffer[0] = 0xff;
                buffer[1] = aleds | 0xf8;
        
                cmd = ADBLISTEN(sc->sc_adbdev->current_addr, 2);
                sc->sc_ops->send(sc->sc_ops->cookie, sc->sc_poll, cmd, 2, 
                    buffer);
        }

        sc->sc_leds = leds & 7;
}

static void 
adbkbd_initleds(struct adbkbd_softc *sc)
{
        short cmd;

        /* talk R2 */
        cmd = ADBTALK(sc->sc_adbdev->current_addr, 2);
        sc->sc_msg_len = 0;
        sc->sc_ops->send(sc->sc_ops->cookie, sc->sc_poll, cmd, 0, NULL);
        if (!adbkbd_wait(sc, 10)) {
                aprint_error_dev(sc->sc_dev, "unable to read LED state\n");
                return;
        }
        sc->sc_have_led_control = 1;
        DPRINTF("have LED control\n");  
        return;
}

static int
adbkbd_enable(void *v, int on)
{
        return 0;
}

static int
adbkbd_ioctl(void *v, u_long cmd, void *data, int flag, struct lwp *l)
{
        struct adbkbd_softc *sc = (struct adbkbd_softc *) v;

        switch (cmd) {

        case WSKBDIO_GTYPE:
                if (sc->sc_emul_usb != ADB_EMUL_USB_NONE) {
                        *(int *)data = WSKBD_TYPE_USB;
                } else {
                        *(int *)data = WSKBD_TYPE_ADB;
                }
                return 0;
        case WSKBDIO_SETLEDS:
                adbkbd_set_leds(sc, *(int *)data);
                return 0;
        case WSKBDIO_GETLEDS:
                *(int *)data = sc->sc_leds;
                return 0;
#ifdef WSDISPLAY_COMPAT_RAWKBD
        case WSKBDIO_SETMODE:
                sc->sc_rawkbd = *(int *)data == WSKBD_RAW;
                return 0;
#endif
        }

        return EPASSTHROUGH;
}

int
adbkbd_cnattach(void)
{

        adbkbd_is_console = 1;
        return 0;
}

static void
adbkbd_cngetc(void *v, u_int *type, int *data)
{
        struct adbkbd_softc *sc = v;
        int key, press, val;
        int s;

        s = splhigh();

        KASSERT(sc->sc_poll);

        DPRINTF("polling...");
        while (sc->sc_polled_chars == 0) {
                sc->sc_ops->poll(sc->sc_ops->cookie);
        }
        DPRINTF(" got one\n");
        splx(s);

        key = sc->sc_pollbuf[0];
        sc->sc_polled_chars--;
        memmove(sc->sc_pollbuf, sc->sc_pollbuf + 1,
                sc->sc_polled_chars);

        press = ADBK_PRESS(key);
        val = ADBK_KEYVAL(key);

        *data = val;
        *type = press ? WSCONS_EVENT_KEY_DOWN : WSCONS_EVENT_KEY_UP;
}

static void
adbkbd_cnpollc(void *v, int on)
{
        struct adbkbd_softc *sc = v;

        sc->sc_poll = on;
        if (!on) {
                int i;

                /* feed the poll buffer's content to wskbd */
                for (i = 0; i < sc->sc_polled_chars; i++) {
                        adbkbd_key(sc, sc->sc_pollbuf[i]);
                }
                sc->sc_polled_chars = 0;
        }
}

#if NWSMOUSE > 0
/* stuff for the pseudo mouse */
static int
adbkms_enable(void *v)
{
        return 0;
}

static int
adbkms_ioctl(void *v, u_long cmd, void *data, int flag, struct lwp *l)
{

        switch (cmd) {
        case WSMOUSEIO_GTYPE:
                *(u_int *)data = WSMOUSE_TYPE_PSEUDO;
                break;

        default:
                return (EPASSTHROUGH);
        }
        return (0);
}

static void
adbkms_disable(void *v)
{
}

static int
adbkbd_sysctl_mid(SYSCTLFN_ARGS)
{
        struct sysctlnode node = *rnode;
        struct adbkbd_softc *sc=(struct adbkbd_softc *)node.sysctl_data;
        const int *np = newp;
        int reg;

        DPRINTF("adbkbd_sysctl_mid\n");
        reg = sc->sc_trans[1];
        if (np) {
                /* we're asked to write */      
                node.sysctl_data = &reg;
                if (sysctl_lookup(SYSCTLFN_CALL(&node)) == 0) {
                        
                        sc->sc_trans[1] = *(int *)node.sysctl_data;
                        return 0;
                }
                return EINVAL;
        } else {
                node.sysctl_data = &reg;
                node.sysctl_size = 4;
                return (sysctl_lookup(SYSCTLFN_CALL(&node)));
        }
}

static int
adbkbd_sysctl_right(SYSCTLFN_ARGS)
{
        struct sysctlnode node = *rnode;
        struct adbkbd_softc *sc=(struct adbkbd_softc *)node.sysctl_data;
        const int *np = newp;
        int reg;

        DPRINTF("adbkbd_sysctl_right\n");
        reg = sc->sc_trans[2];
        if (np) {
                /* we're asked to write */      
                node.sysctl_data = &reg;
                if (sysctl_lookup(SYSCTLFN_CALL(&node)) == 0) {
                        
                        sc->sc_trans[2] = *(int *)node.sysctl_data;
                        return 0;
                }
                return EINVAL;
        } else {
                node.sysctl_data = &reg;
                node.sysctl_size = 4;
                return (sysctl_lookup(SYSCTLFN_CALL(&node)));
        }
}

#endif /* NWSMOUSE > 0 */

static int
adbkbd_sysctl_usb(SYSCTLFN_ARGS)
{
        struct sysctlnode node = *rnode;
        struct adbkbd_softc *sc=(struct adbkbd_softc *)node.sysctl_data;
        const int *np = newp;
        int reg;

        DPRINTF("%s\n", __func__);
        reg = sc->sc_emul_usb;
        if (np) {
                /* we're asked to write */      
                node.sysctl_data = &reg;
                if (sysctl_lookup(SYSCTLFN_CALL(&node)) == 0) {
                        
                        sc->sc_emul_usb = *(int *)node.sysctl_data;
                        switch (sc->sc_emul_usb) {
                        case ADB_EMUL_USB_NONE:
                                wskbd_set_evtrans(sc->sc_wskbddev, NULL, 0);
                                break;
                        case ADB_EMUL_USB_ANSI:
                                wskbd_set_evtrans(sc->sc_wskbddev,
                                    adb_to_usb_ansi, 128);
                                break;
                        case ADB_EMUL_USB_ISO:
                                wskbd_set_evtrans(sc->sc_wskbddev,
                                    adb_to_usb_iso, 128);
                                break;
                        case ADB_EMUL_USB_JIS:
                                wskbd_set_evtrans(sc->sc_wskbddev,
                                    adb_to_usb_jis, 128);
                                break;
                        default:
                                return EINVAL;
                                break;
                        }
                        return 0;
                }
                return EINVAL;
        } else {
                node.sysctl_data = &reg;
                node.sysctl_size = sizeof(reg);
                return (sysctl_lookup(SYSCTLFN_CALL(&node)));
        }
}

static int
adbkbd_sysctl_dbg(SYSCTLFN_ARGS)
{
        struct sysctlnode node = *rnode;
        struct adbkbd_softc *sc=(struct adbkbd_softc *)node.sysctl_data;
        const int *np = newp;
        bool reg;

        DPRINTF("%s\n", __func__);
        reg = sc->sc_power_dbg;
        if (np) {
                /* we're asked to write */      
                node.sysctl_data = &reg;
                if (sysctl_lookup(SYSCTLFN_CALL(&node)) == 0) {
                        
                        sc->sc_power_dbg = *(bool *)node.sysctl_data;
                        return 0;
                }
                return EINVAL;
        } else {
                node.sysctl_data = &reg;
                node.sysctl_size = sizeof(reg);
                return (sysctl_lookup(SYSCTLFN_CALL(&node)));
        }
}

static void
adbkbd_setup_sysctl(struct adbkbd_softc *sc)
{
        const struct sysctlnode *me, *node;
        int ret;

        DPRINTF("%s: sysctl setup\n", device_xname(sc->sc_dev));
        ret = sysctl_createv(NULL, 0, NULL, &me,
               CTLFLAG_READWRITE,
               CTLTYPE_NODE, device_xname(sc->sc_dev), NULL,
               NULL, 0, NULL, 0,
               CTL_MACHDEP, CTL_CREATE, CTL_EOL);
        ret = sysctl_createv(NULL, 0, NULL,
            (void *)&node, 
            CTLFLAG_READWRITE | CTLFLAG_OWNDESC,
            CTLTYPE_INT, "emulate_usb", "USB keyboard emulation", 
            adbkbd_sysctl_usb, 1, (void *)sc, 0, CTL_MACHDEP, 
            me->sysctl_num, CTL_CREATE, CTL_EOL);
        ret = sysctl_createv(NULL, 0, NULL,
            (void *)&node, 
            CTLFLAG_READWRITE | CTLFLAG_OWNDESC,
            CTLTYPE_BOOL, "power_ddb", "power button triggers ddb", 
            adbkbd_sysctl_dbg, 1, (void *)sc, 0, CTL_MACHDEP, 
            me->sysctl_num, CTL_CREATE, CTL_EOL);
#if NWSMOUSE > 0
        if (sc->sc_wsmousedev != NULL) {
                ret = sysctl_createv(NULL, 0, NULL,
                    (void *)&node, 
                    CTLFLAG_READWRITE | CTLFLAG_OWNDESC,
                    CTLTYPE_INT, "middle", "middle mouse button", 
                    adbkbd_sysctl_mid, 1, (void *)sc, 0, CTL_MACHDEP, 
                    me->sysctl_num, CTL_CREATE, CTL_EOL);

                ret = sysctl_createv(NULL, 0, NULL, 
                    (void *)&node, 
                    CTLFLAG_READWRITE | CTLFLAG_OWNDESC,
                    CTLTYPE_INT, "right", "right mouse button", 
                    adbkbd_sysctl_right, 2, (void *)sc, 0, CTL_MACHDEP, 
                    me->sysctl_num, CTL_CREATE, CTL_EOL);
        }
#endif /* NWSMOUSE > 0 */

        (void)ret;
}

SYSCTL_SETUP(sysctl_adbkbdtrans_setup, "adbkbd translator setup")
{

        sysctl_createv(NULL, 0, NULL, NULL,
                       CTLFLAG_PERMANENT,
                       CTLTYPE_NODE, "machdep", NULL,
                       NULL, 0, NULL, 0,
                       CTL_MACHDEP, CTL_EOL);
}