root/sys/dev/i2c/ims.c
/* $NetBSD: ims.c,v 1.5 2023/05/10 00:10:02 riastradh Exp $ */
/* $OpenBSD ims.c,v 1.1 2016/01/12 01:11:15 jcs Exp $ */

/*
 * HID-over-i2c mouse/trackpad driver
 *
 * Copyright (c) 2015, 2016 joshua stein <jcs@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: ims.c,v 1.5 2023/05/10 00:10:02 riastradh Exp $");

#include <sys/param.h>
#include <sys/systm.h>
#include <sys/kernel.h>
#include <sys/device.h>
#include <sys/ioctl.h>

#include <dev/i2c/i2cvar.h>
#include <dev/i2c/ihidev.h>

#include <dev/hid/hid.h>
#include <dev/hid/hidms.h>

struct ims_softc {
        struct ihidev   sc_hdev;
        struct hidms    sc_ms;
        bool            sc_enabled;
};

static void     ims_intr(struct ihidev *addr, void *ibuf, u_int len);

static int      ims_enable(void *);
static void     ims_disable(void *);
static int      ims_ioctl(void *, u_long, void *, int, struct lwp *);

const struct wsmouse_accessops ims_accessops = {
        ims_enable,
        ims_ioctl,
        ims_disable,
};

static int      ims_match(device_t, cfdata_t, void *);
static void     ims_attach(device_t, device_t, void *);
static int      ims_detach(device_t, int);
static void     ims_childdet(device_t, device_t);

CFATTACH_DECL2_NEW(ims, sizeof(struct ims_softc), ims_match, ims_attach,
    ims_detach, NULL, NULL, ims_childdet);

static int
ims_match(device_t parent, cfdata_t match, void *aux)
{
        struct ihidev_attach_arg *iha = (struct ihidev_attach_arg *)aux;
        int size;
        void *desc;

        ihidev_get_report_desc(iha->parent, &desc, &size);

        if (hid_is_collection(desc, size, iha->reportid,
            HID_USAGE2(HUP_GENERIC_DESKTOP, HUG_POINTER)))
                return (IMATCH_IFACECLASS);

        if (hid_is_collection(desc, size, iha->reportid,
            HID_USAGE2(HUP_GENERIC_DESKTOP, HUG_MOUSE)))
                return (IMATCH_IFACECLASS);

        if (hid_is_collection(desc, size, iha->reportid,
            HID_USAGE2(HUP_DIGITIZERS, HUD_PEN)))
                return (IMATCH_IFACECLASS);

        if (hid_is_collection(desc, size, iha->reportid,
            HID_USAGE2(HUP_DIGITIZERS, HUD_TOUCH_SCREEN)))
                return (IMATCH_IFACECLASS);

        return (IMATCH_NONE);
}

static void
ims_attach(device_t parent, device_t self, void *aux)
{
        struct ims_softc *sc = device_private(self);
        struct hidms *ms = &sc->sc_ms;
        struct ihidev_attach_arg *iha = (struct ihidev_attach_arg *)aux;
        int size, repid;
        void *desc;
        struct hid_data * d __debugused;
        struct hid_item item __debugused;

        sc->sc_hdev.sc_idev = self;
        sc->sc_hdev.sc_intr = ims_intr;
        sc->sc_hdev.sc_parent = iha->parent;
        sc->sc_hdev.sc_report_id = iha->reportid;

        if (!pmf_device_register(self, NULL, NULL))
                aprint_error_dev(self, "couldn't establish power handler\n");

        ihidev_get_report_desc(iha->parent, &desc, &size);
        repid = iha->reportid;
        sc->sc_hdev.sc_isize = hid_report_size(desc, size, hid_input, repid);
        sc->sc_hdev.sc_osize = hid_report_size(desc, size, hid_output, repid);
        sc->sc_hdev.sc_fsize = hid_report_size(desc, size, hid_feature, repid);

        if (!hidms_setup(self, ms, iha->reportid, desc, size) != 0)
                return;

#if defined(DEBUG)
        /* calibrate the touchscreen */
        memset(&sc->sc_ms.sc_calibcoords, 0, sizeof(sc->sc_ms.sc_calibcoords));
        d = hid_start_parse(desc, size, hid_input);
        if (d != NULL) {
                while (hid_get_item(d, &item)) {
                        if (item.kind != hid_input
                            || HID_GET_USAGE_PAGE(item.usage) != HUP_GENERIC_DESKTOP
                            || item.report_ID != sc->sc_hdev.sc_report_id)
                                continue;
                        if (HID_GET_USAGE(item.usage) == HUG_X) {
                                aprint_normal("X range: %d - %d\n", item.logical_minimum, item.logical_maximum);
                        }
                        if (HID_GET_USAGE(item.usage) == HUG_Y) {
                                aprint_normal("Y range: %d - %d\n", item.logical_minimum, item.logical_maximum);
                        }
                }
                hid_end_parse(d);
        }
#endif
        tpcalib_init(&sc->sc_ms.sc_tpcalib);
        tpcalib_ioctl(&sc->sc_ms.sc_tpcalib, WSMOUSEIO_SCALIBCOORDS,
            (void *)&sc->sc_ms.sc_calibcoords, 0, 0);

        hidms_attach(self, ms, &ims_accessops);
}

static int
ims_detach(device_t self, int flags)
{
        int error;

        /* No need to do reference counting of ums, wsmouse has all the goo. */
        error = config_detach_children(self, flags);
        if (error)
                return error;

        pmf_device_deregister(self);
        return 0;
}

void
ims_childdet(device_t self, device_t child)
{
        struct ims_softc *sc = device_private(self);

        KASSERT(KERNEL_LOCKED_P());

        KASSERT(sc->sc_ms.hidms_wsmousedev == child);
        sc->sc_ms.hidms_wsmousedev = NULL;
}


static void
ims_intr(struct ihidev *addr, void *buf, u_int len)
{
        struct ims_softc *sc = (struct ims_softc *)addr;
        struct hidms *ms = &sc->sc_ms;

        if (sc->sc_enabled)
                hidms_intr(ms, buf, len);
}

static int
ims_enable(void *v)
{
        struct ims_softc *sc = v;
        int error;

        KASSERT(KERNEL_LOCKED_P());

        if (sc->sc_enabled)
                return EBUSY;

        sc->sc_enabled = 1;
        sc->sc_ms.hidms_buttons = 0;

        error = ihidev_open(&sc->sc_hdev);
        if (error)
                sc->sc_enabled = 0;
        return error;
}

static void
ims_disable(void *v)
{
        struct ims_softc *sc = v;

        KASSERT(KERNEL_LOCKED_P());

#ifdef DIAGNOSTIC
        if (!sc->sc_enabled) {
                printf("ums_disable: not enabled\n");
                return;
        }
#endif

        if (sc->sc_enabled) {
                sc->sc_enabled = 0;
                ihidev_close(&sc->sc_hdev);
        }

}

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

        switch (cmd) {
        case WSMOUSEIO_GTYPE:
                if (sc->sc_ms.flags & HIDMS_ABS) {
                        *(u_int *)data = WSMOUSE_TYPE_TPANEL;
                } else {
                        /* XXX: should we set something else? */
                        *(u_int *)data = WSMOUSE_TYPE_USB;
                }
                return 0;
        case WSMOUSEIO_SCALIBCOORDS:
        case WSMOUSEIO_GCALIBCOORDS:
                return tpcalib_ioctl(&sc->sc_ms.sc_tpcalib, cmd, data, flag, l);
        }
        return EPASSTHROUGH;
}