root/sys/dev/adb/adb_ktm.c
/*      $NetBSD: adb_ktm.c,v 1.9 2025/06/16 07:51:16 macallan Exp $     */

/*-
 * Copyright (c) 2019 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.
 *
 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. 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 FOUNDATION 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.
 */

#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: adb_ktm.c,v 1.9 2025/06/16 07:51:16 macallan Exp $");

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

#include <machine/autoconf.h>

#include <dev/wscons/wsconsio.h>
#include <dev/wscons/wsmousevar.h>

#include <dev/adb/adbvar.h>

#include "adbdebug.h"

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

/*
 * State info, per mouse instance.
 */
struct ktm_softc {
        device_t        sc_dev;
        struct adb_device *sc_adbdev;
        struct adb_bus_accessops *sc_ops;

        uint8_t         sc_us;          /* cmd to watch for */
        device_t        sc_wsmousedev;
        /* buffers */
        uint8_t         sc_config[8];
        int             sc_left;
        int             sc_right;
        int             sc_poll;
        int             sc_msg_len;
        kcondvar_t      sc_event;
        kmutex_t        sc_interlock;
        uint8_t         sc_buffer[16];
};

/*
 * Function declarations.
 */
static int      ktm_match(device_t, cfdata_t, void *);
static void     ktm_attach(device_t, device_t, void *);
static void     ktm_init(struct ktm_softc *);
static void     ktm_write_config(struct ktm_softc *);
static void     ktm_buttons(struct ktm_softc *);
static void     ktm_process_event(struct ktm_softc *, int, uint8_t *);
static int      ktm_send_sync(struct ktm_softc *, uint8_t, int, uint8_t *);

/* Driver definition. */
CFATTACH_DECL_NEW(ktm, sizeof(struct ktm_softc),
    ktm_match, ktm_attach, NULL, NULL);

static int ktm_enable(void *);
static int ktm_ioctl(void *, u_long, void *, int, struct lwp *);
static void ktm_disable(void *);

static void ktm_handler(void *, int, uint8_t *);
static int  ktm_wait(struct ktm_softc *, int);
static int  sysctl_ktm_left(SYSCTLFN_ARGS);
static int  sysctl_ktm_right(SYSCTLFN_ARGS);

const struct wsmouse_accessops ktm_accessops = {
        ktm_enable,
        ktm_ioctl,
        ktm_disable,
};

static int
ktm_match(device_t parent, cfdata_t cf, void *aux)
{
        struct adb_attach_args *aaa = aux;
        if ((aaa->dev->original_addr == ADBADDR_MS) &&
            (aaa->dev->handler_id == ADBMS_TURBO))
                return 50;      /* beat out adbms */
        else
                return 0;
}

static void
ktm_attach(device_t parent, device_t self, void *aux)
{
        struct ktm_softc *sc = device_private(self);
        struct adb_attach_args *aaa = aux;
        struct wsmousedev_attach_args a;

        sc->sc_dev = self;
        sc->sc_ops = aaa->ops;
        sc->sc_adbdev = aaa->dev;
        sc->sc_adbdev->cookie = sc;
        sc->sc_adbdev->handler = ktm_handler;
        mutex_init(&sc->sc_interlock, MUTEX_DEFAULT, IPL_NONE);
        cv_init(&sc->sc_event, "ktm");
        sc->sc_us = ADBTALK(sc->sc_adbdev->current_addr, 0);
        printf(" addr %d: Kensington Turbo Mouse\n",
            sc->sc_adbdev->current_addr);

        sc->sc_poll = 0;
        sc->sc_msg_len = 0;

        ktm_init(sc);

        a.accessops = &ktm_accessops;
        a.accesscookie = sc;
        sc->sc_wsmousedev = config_found(self, &a, wsmousedevprint, CFARGS_NONE);
}

static int
ktm_turbo_csum(uint8_t *d)
{
        int i = 0, sum = 0;

        for (i = 0; i < 7; i++)
                sum ^= d[i];
        return (sum ^ 0xff);
}

static void
ktm_write_config(struct ktm_softc *sc)
{
        uint8_t addr = sc->sc_adbdev->current_addr;

        ktm_send_sync(sc, ADBFLUSH(addr), 0, NULL);
        sc->sc_config[7] = ktm_turbo_csum(sc->sc_config);
        ktm_send_sync(sc, ADBLISTEN(addr, 2), 8, sc->sc_config);
}

static uint8_t button_to_reg[] = {0, 1, 4, 6};

static void ktm_buttons(struct ktm_softc *sc)
{
        uint8_t reg;

        reg = button_to_reg[sc->sc_right] |
              (button_to_reg[sc->sc_left] << 3);
        sc->sc_config[1] = reg;
}

static void
ktm_init(struct ktm_softc *sc)
{
        const struct sysctlnode *me = NULL, *node = NULL;
        int ret;

        /* Found Kensington Turbo Mouse */

/*
 * byte 0
 - 0x80 enables EMP output
 - 0x08 seems to map both buttons together
 - 0x04 enables the 2nd button
 - initialized to 0x20 on power up, no idea what that does
 
 * byte 1 assigns what which button does
 - 0x08 - button 1 - 1, button 2 - nothing
 - 0x09 - both buttons - 1
 - 0x0a - button 1 - 1, button 2 - toggle 1
 - 0x0b - button 1 - 1, button 2 - nothing
 - 0x0c - button 1 - 1, button 2 - 2
 - 0x0e - button 1 - 1, button 2 - 3
 - 0x0f - button 1 - 1, button 2 - toggle 3
 - 0x10 - button 1 toggle 1, button 2 nothing
 - 0x11 - button 1 - toggle 1, button 2 - 1
 - 0x12 - both toggle 1
 - 0x14 - button 1 toggle 1, button 2 - 2
 - 0x21 - button 1 - 2, button 2 - 1
 - 0x31 - button 1 - 3, button 2 - 1

 * byte 2 - 0x40 on powerup, seems to do nothing
 * byte 3 - 0x01 on powerup, seems to do nothing
 * byte 4 programs a delay for button presses, apparently in 1/100 seconds
 * byte 5 and 6 init to 0xff
 * byte 7 is a simple XOR checksum, writes will only stick if it's valid
          as in, b[7] = (b[0] ^ b[1] ^ ... ^ b[6]) ^ 0xff
 */
 
        /* this seems to be the most reasonable default */
        uint8_t data[8] = { 0xa5, 0x0e, 0, 0, 1, 0xff, 0xff, 0 };

        memcpy(sc->sc_config, data, sizeof(data));

        sc->sc_left = 1;
        sc->sc_right = 3;

        ktm_buttons(sc);

#ifdef KTM_DEBUG
        int addr = sc->sc_adbdev->current_addr;
        {
                int i;
                ktm_send_sync(sc, ADBTALK(addr, 2), 0, NULL);
                printf("reg *");
                for (i = 0; i < sc->sc_msg_len; i++)
                        printf(" %02x", sc->sc_buffer[i]);
                printf("\n");
        }
#endif

        ktm_write_config(sc);

#ifdef KTM_DEBUG
        int i, reg;
        for (reg = 1; reg < 4; reg++) {
                ktm_send_sync(sc, ADBTALK(addr, reg), 0, NULL);
                printf("reg %d", reg);
                for (i = 0; i < sc->sc_msg_len; i++)
                        printf(" %02x", sc->sc_buffer[i]);
                printf("\n");
        }
#endif
        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, &node,
            CTLFLAG_READWRITE | CTLFLAG_OWNDESC,
            CTLTYPE_INT, "left", "left button assignment",
            sysctl_ktm_left, 1, (void *)sc, 0,
            CTL_MACHDEP, me->sysctl_num, CTL_CREATE, CTL_EOL);

        ret = sysctl_createv(NULL, 0, NULL, &node,
            CTLFLAG_READWRITE | CTLFLAG_OWNDESC,
            CTLTYPE_INT, "right", "right button assignment",
            sysctl_ktm_right, 1, (void *)sc, 0,
            CTL_MACHDEP, me->sysctl_num, CTL_CREATE, CTL_EOL);
        __USE(ret);
}

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

#ifdef KTM_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) {
                memcpy(sc->sc_buffer, &data[2], len - 2);
                sc->sc_msg_len = len - 2;
                if (data[1] == sc->sc_us) {
                        /* make sense of the mouse message */
                        ktm_process_event(sc, sc->sc_msg_len, sc->sc_buffer);
                        return;
                }
                cv_signal(&sc->sc_event);
        } else {
                DPRINTF("bogus message\n");
        }
}

static void
ktm_process_event(struct ktm_softc *sc, int len, uint8_t *buffer)
{
        int buttons = 0, mask, dx, dy, i;
        int button_bit = 1;

        /* Classic Mouse Protocol (up to 2 buttons) */
        for (i = 0; i < 2; i++, button_bit <<= 1)
                /* 0 when button down */
                if (!(buffer[i] & 0x80))
                        buttons |= button_bit;
                else
                        buttons &= ~button_bit;
        /* Extended Protocol (up to 6 more buttons) */
        for (mask = 0x80; i < len;
             i += (mask == 0x80), button_bit <<= 1) {
                /* 0 when button down */
                if (!(buffer[i] & mask))
                        buttons |= button_bit;
                else
                        buttons &= ~button_bit;
                mask = ((mask >> 4) & 0xf) | ((mask & 0xf) << 4);
        }                               

        /* EMP crap, additional motion bits */
        int shift = 7, ddx, ddy, sign, smask;

#ifdef KTM_DEBUG
        printf("EMP packet:");
        for (i = 0; i < len; i++)
                printf(" %02x", buffer[i]);
        printf("\n");
#endif
        dx = (int)buffer[1] & 0x7f;
        dy = (int)buffer[0] & 0x7f;
        for (i = 2; i < len; i++) {
                ddx = (buffer[i] & 0x07);
                ddy = (buffer[i] & 0x70) >> 4;
                dx |= (ddx << shift);
                dy |= (ddy << shift);
                shift += 3;
        }
        sign = 1 << (shift - 1);
        smask = 0xffffffff << shift;
        if (dx & sign)
                dx |= smask;
        if (dy & sign)
                dy |= smask;
#ifdef KTM_DEBUG
        printf("%d %d %08x %d\n", dx, dy, smask, shift);
#endif

        if (sc->sc_wsmousedev)
                wsmouse_input(sc->sc_wsmousedev, buttons,
                              dx, -dy, 0, 0,
                              WSMOUSE_INPUT_DELTA);
}

static int
ktm_enable(void *v)
{
        return 0;
}

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

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

        default:
                return EPASSTHROUGH;
        }
        return 0;
}

static void
ktm_disable(void *v)
{
}

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

static int
ktm_send_sync(struct ktm_softc *sc, uint8_t cmd, int len, uint8_t *msg)
{
        int i;

        sc->sc_msg_len = -1;
        DPRINTF("send: %02x", cmd);
        for (i = 0; i < len; i++)
                DPRINTF(" %02x", msg[i]);
        DPRINTF("\n");
        sc->sc_ops->send(sc->sc_ops->cookie, sc->sc_poll, cmd, len, msg);
        ktm_wait(sc, 3);
        return (sc->sc_msg_len != -1);
}

static int
sysctl_ktm_left(SYSCTLFN_ARGS)
{
        struct sysctlnode node = *rnode;
        struct ktm_softc *sc = node.sysctl_data;
        int reg = sc->sc_left;

        if (newp) {

                /* we're asked to write */      
                node.sysctl_data = &reg;
                if (sysctl_lookup(SYSCTLFN_CALL(&node)) == 0) {

                        reg = *(int *)node.sysctl_data;
                        if ((reg != sc->sc_left) &&
                            (reg >= 0) &&
                            (reg < 4)) {
                                sc->sc_left = reg;
                                ktm_buttons(sc);
                                ktm_write_config(sc);
                        }
                        return 0;
                }
                return EINVAL;
        } else {

                node.sysctl_data = &reg;
                node.sysctl_size = 4;
                return (sysctl_lookup(SYSCTLFN_CALL(&node)));
        }

        return 0;
}

static int
sysctl_ktm_right(SYSCTLFN_ARGS)
{
        struct sysctlnode node = *rnode;
        struct ktm_softc *sc = node.sysctl_data;
        int reg = sc->sc_right;

        if (newp) {

                /* we're asked to write */      
                node.sysctl_data = &reg;
                if (sysctl_lookup(SYSCTLFN_CALL(&node)) == 0) {

                        reg = *(int *)node.sysctl_data;
                        if ((reg != sc->sc_right) &&
                            (reg >= 0) &&
                            (reg < 4)) {
                                sc->sc_right = reg;
                                ktm_buttons(sc);
                                ktm_write_config(sc);
                        }
                        return 0;
                }
                return EINVAL;
        } else {

                node.sysctl_data = &reg;
                node.sysctl_size = 4;
                return (sysctl_lookup(SYSCTLFN_CALL(&node)));
        }

        return 0;
}

SYSCTL_SETUP(sysctl_ktm_setup, "sysctl ktm subtree setup")
{

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