941 lines
22 KiB
C
941 lines
22 KiB
C
/* $OpenBSD: iatp.c,v 1.9 2022/04/06 18:59:28 naddy Exp $ */
|
|
/*
|
|
* Atmel maXTouch i2c touchscreen/touchpad driver
|
|
* Copyright (c) 2016 joshua stein <jcs@openbsd.org>
|
|
*
|
|
* AT421085 datasheet:
|
|
* http://www.atmel.com/images/Atmel-9626-AT42-QTouch-BSW-AT421085-Object-Protocol-Guide_Datasheet.pdf
|
|
*
|
|
* Uses code from libmaxtouch <https://github.com/atmel-maxtouch/mxt-app>
|
|
* Copyright 2011 Atmel Corporation. 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 ATMEL ''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 ATMEL 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/param.h>
|
|
#include <sys/systm.h>
|
|
#include <sys/kernel.h>
|
|
#include <sys/device.h>
|
|
#include <sys/malloc.h>
|
|
#include <sys/stdint.h>
|
|
|
|
#include <dev/i2c/i2cvar.h>
|
|
|
|
#include <dev/wscons/wsconsio.h>
|
|
#include <dev/wscons/wsmousevar.h>
|
|
#include <dev/hid/hid.h>
|
|
#include <dev/hid/hidmsvar.h>
|
|
|
|
/* #define IATP_DEBUG */
|
|
|
|
#ifdef IATP_DEBUG
|
|
#define DPRINTF(x) printf x
|
|
#else
|
|
#define DPRINTF(x)
|
|
#endif
|
|
|
|
struct mxt_object {
|
|
uint8_t type;
|
|
uint16_t start_pos;
|
|
uint8_t size_minus_one;
|
|
#define MXT_SIZE(o) ((uint16_t)((o)->size_minus_one) + 1)
|
|
uint8_t instances_minus_one;
|
|
#define MXT_INSTANCES(o) ((uint16_t)((o)->instances_minus_one) + 1)
|
|
uint8_t num_report_ids;
|
|
} __packed;
|
|
|
|
struct mxt_id_info {
|
|
uint8_t family;
|
|
uint8_t variant;
|
|
uint8_t version;
|
|
uint8_t build;
|
|
uint8_t matrix_x_size;
|
|
uint8_t matrix_y_size;
|
|
uint8_t num_objects;
|
|
} __packed;
|
|
|
|
struct mxt_info {
|
|
struct mxt_id_info id;
|
|
struct mxt_object *objects;
|
|
uint32_t crc;
|
|
uint8_t *raw_info;
|
|
uint8_t max_report_id;
|
|
};
|
|
|
|
/* object types we care about (of 117 total!) */
|
|
|
|
#define MXT_GEN_MESSAGEPROCESSOR_T5 5
|
|
|
|
#define MXT_GEN_COMMANDPROCESSOR_T6 6
|
|
# define MXT_T6_STATUS_RESET (1 << 7)
|
|
# define MXT_T6_STATUS_OFL (1 << 6)
|
|
# define MXT_T6_STATUS_SIGERR (1 << 5)
|
|
# define MXT_T6_STATUS_CAL (1 << 4)
|
|
# define MXT_T6_STATUS_CFGERR (1 << 3)
|
|
# define MXT_T6_STATUS_COMSERR (1 << 2)
|
|
# define MXT_T6_CMD_RESET 0
|
|
# define MXT_T6_CMD_BACKUPNV 1
|
|
# define MXT_T6_CMD_CALIBRATE 2
|
|
# define MXT_T6_CMD_REPORTALL 3
|
|
# define MXT_T6_CMD_DIAGNOSTIC 5
|
|
|
|
#define MXT_GEN_POWERCONFIG_T7 7
|
|
# define MXT_T7_POWER_MODE_DEFAULT 1
|
|
# define MXT_T7_POWER_MODE_DEEP_SLEEP 2
|
|
struct mxt_t7_config {
|
|
uint8_t idle;
|
|
uint8_t active;
|
|
uint8_t atoi_timeout;
|
|
} __packed;
|
|
|
|
#define MXT_SPT_GPIOPWM_T19 19
|
|
static const struct mxt_t19_button_map {
|
|
const char *vendor;
|
|
const char *product;
|
|
const char *hid;
|
|
int bit;
|
|
} mxt_t19_button_map_devs[] = {
|
|
/* Chromebook Pixel 2015 */
|
|
{ "GOOGLE", "Samus", "ATML0000", 3 },
|
|
/* Other Google Chromebooks */
|
|
{ "GOOGLE", "", "ATML0000", 5 },
|
|
{ NULL }
|
|
};
|
|
|
|
#define MXT_SPT_MESSAGECOUNT_T44 44
|
|
|
|
#define MXT_TOUCH_MULTITOUCHSCREEN_T100 100
|
|
# define MXT_T100_CTRL 0
|
|
# define MXT_T100_CFG1 1
|
|
# define MXT_T100_TCHAUX 3
|
|
# define MXT_T100_XRANGE 13
|
|
# define MXT_T100_YRANGE 24
|
|
# define MXT_T100_CFG_SWITCHXY (1 << 5)
|
|
# define MXT_T100_TCHAUX_VECT (1 << 0)
|
|
# define MXT_T100_TCHAUX_AMPL (1 << 1)
|
|
# define MXT_T100_TCHAUX_AREA (1 << 2)
|
|
# define MXT_T100_DETECT (1 << 7)
|
|
# define MXT_T100_TYPE_MASK 0x70
|
|
|
|
enum t100_type {
|
|
MXT_T100_TYPE_FINGER = 1,
|
|
MXT_T100_TYPE_PASSIVE_STYLUS = 2,
|
|
MXT_T100_TYPE_HOVERING_FINGER = 4,
|
|
MXT_T100_TYPE_GLOVE = 5,
|
|
MXT_T100_TYPE_LARGE_TOUCH = 6,
|
|
};
|
|
|
|
#define MXT_DISTANCE_ACTIVE_TOUCH 0
|
|
#define MXT_DISTANCE_HOVERING 1
|
|
|
|
#define MXT_TOUCH_MAJOR_DEFAULT 1
|
|
|
|
struct iatp_softc {
|
|
struct device sc_dev;
|
|
i2c_tag_t sc_tag;
|
|
|
|
i2c_addr_t sc_addr;
|
|
void *sc_ih;
|
|
|
|
struct device *sc_wsmousedev;
|
|
char sc_hid[16];
|
|
int sc_busy;
|
|
int sc_enabled;
|
|
int sc_touchpad;
|
|
struct tsscale sc_tsscale;
|
|
|
|
uint8_t *table;
|
|
size_t table_size;
|
|
|
|
struct mxt_info info;
|
|
uint8_t *msg_buf;
|
|
uint8_t multitouch;
|
|
uint8_t num_touchids;
|
|
uint32_t max_x;
|
|
uint32_t max_y;
|
|
uint8_t button;
|
|
|
|
uint16_t t5_address;
|
|
uint8_t t5_msg_size;
|
|
uint16_t t6_address;
|
|
uint8_t t6_reportid;
|
|
uint16_t t7_address;
|
|
struct mxt_t7_config t7_config;
|
|
uint8_t t19_reportid;
|
|
int t19_button_bit;
|
|
uint16_t t44_address;
|
|
uint8_t t100_reportid_min;
|
|
uint8_t t100_reportid_max;
|
|
uint8_t t100_aux_ampl;
|
|
uint8_t t100_aux_area;
|
|
uint8_t t100_aux_vect;
|
|
};
|
|
|
|
int iatp_match(struct device *, void *, void *);
|
|
void iatp_attach(struct device *, struct device *, void *);
|
|
int iatp_detach(struct device *, int);
|
|
int iatp_activate(struct device *, int);
|
|
|
|
int iatp_ioctl(void *, u_long, caddr_t, int, struct proc *);
|
|
int iatp_enable(void *);
|
|
void iatp_disable(void *);
|
|
|
|
int iatp_read_reg(struct iatp_softc *, uint16_t, size_t, void *);
|
|
int iatp_write_reg(struct iatp_softc *, uint16_t, size_t, void *);
|
|
int iatp_init(struct iatp_softc *);
|
|
int iatp_intr(void *);
|
|
|
|
int iatp_proc_msg(struct iatp_softc *, uint8_t *);
|
|
int iatp_t5_read_msgs(struct iatp_softc *, int);
|
|
void iatp_t6_proc_msg(struct iatp_softc *, uint8_t *);
|
|
int iatp_t7_set_power_mode(struct iatp_softc *, int);
|
|
void iatp_t19_proc_msg(struct iatp_softc *, uint8_t *);
|
|
int iatp_t44_read_count(struct iatp_softc *);
|
|
void iatp_t100_proc_msg(struct iatp_softc *, uint8_t *);
|
|
|
|
const struct wsmouse_accessops iatp_accessops = {
|
|
iatp_enable,
|
|
iatp_ioctl,
|
|
iatp_disable,
|
|
};
|
|
|
|
const struct cfattach iatp_ca = {
|
|
sizeof(struct iatp_softc),
|
|
iatp_match,
|
|
iatp_attach,
|
|
iatp_detach,
|
|
iatp_activate
|
|
};
|
|
|
|
struct cfdriver iatp_cd = {
|
|
NULL, "iatp", DV_DULL
|
|
};
|
|
|
|
int
|
|
iatp_match(struct device *parent, void *match, void *aux)
|
|
{
|
|
struct i2c_attach_args *ia = aux;
|
|
|
|
if (strcmp(ia->ia_name, "iatp") == 0)
|
|
return 1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
void
|
|
iatp_attach(struct device *parent, struct device *self, void *aux)
|
|
{
|
|
struct iatp_softc *sc = (struct iatp_softc *)self;
|
|
struct i2c_attach_args *ia = aux;
|
|
struct wsmousedev_attach_args wsmaa;
|
|
|
|
sc->sc_tag = ia->ia_tag;
|
|
sc->sc_addr = ia->ia_addr;
|
|
|
|
if (ia->ia_cookie != NULL)
|
|
memcpy(&sc->sc_hid, ia->ia_cookie, sizeof(sc->sc_hid));
|
|
|
|
if (!iatp_init(sc))
|
|
return;
|
|
|
|
if (ia->ia_intr) {
|
|
printf(" %s", iic_intr_string(sc->sc_tag, ia->ia_intr));
|
|
|
|
sc->sc_ih = iic_intr_establish(sc->sc_tag, ia->ia_intr,
|
|
IPL_TTY, iatp_intr, sc, sc->sc_dev.dv_xname);
|
|
if (sc->sc_ih == NULL) {
|
|
printf(", can't establish interrupt\n");
|
|
return;
|
|
}
|
|
}
|
|
|
|
printf(": Atmel maXTouch Touch%s (%dx%d)\n",
|
|
sc->sc_touchpad ? "pad" : "screen", sc->max_x, sc->max_y);
|
|
|
|
wsmaa.accessops = &iatp_accessops;
|
|
wsmaa.accesscookie = sc;
|
|
sc->sc_wsmousedev = config_found(self, &wsmaa, wsmousedevprint);
|
|
}
|
|
|
|
int
|
|
iatp_detach(struct device *self, int flags)
|
|
{
|
|
struct iatp_softc *sc = (struct iatp_softc *)self;
|
|
|
|
if (sc->sc_ih != NULL) {
|
|
intr_disestablish(sc->sc_ih);
|
|
sc->sc_ih = NULL;
|
|
}
|
|
|
|
sc->sc_enabled = 0;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
iatp_activate(struct device *self, int act)
|
|
{
|
|
struct iatp_softc *sc = (struct iatp_softc *)self;
|
|
|
|
switch (act) {
|
|
case DVACT_QUIESCE:
|
|
#if 0
|
|
/* XXX: causes dwiic troubles */
|
|
iatp_t7_set_power_mode(sc, MXT_T7_POWER_MODE_DEEP_SLEEP);
|
|
#endif
|
|
break;
|
|
case DVACT_WAKEUP:
|
|
sc->sc_busy = 1;
|
|
iatp_init(sc);
|
|
sc->sc_busy = 0;
|
|
break;
|
|
}
|
|
|
|
config_activate_children(self, act);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
iatp_configure(struct iatp_softc *sc)
|
|
{
|
|
struct wsmousehw *hw;
|
|
|
|
hw = wsmouse_get_hw(sc->sc_wsmousedev);
|
|
if (sc->sc_touchpad) {
|
|
hw->type = WSMOUSE_TYPE_TOUCHPAD;
|
|
hw->hw_type = WSMOUSEHW_CLICKPAD;
|
|
} else {
|
|
hw->type = WSMOUSE_TYPE_TPANEL;
|
|
hw->hw_type = WSMOUSEHW_TPANEL;
|
|
}
|
|
hw->x_min = sc->sc_tsscale.minx;
|
|
hw->x_max = sc->sc_tsscale.maxx;
|
|
hw->y_min = sc->sc_tsscale.miny;
|
|
hw->y_max = sc->sc_tsscale.maxy;
|
|
hw->h_res = sc->sc_tsscale.resx;
|
|
hw->v_res = sc->sc_tsscale.resy;
|
|
hw->mt_slots = sc->num_touchids;
|
|
|
|
return (wsmouse_configure(sc->sc_wsmousedev, NULL, 0));
|
|
}
|
|
|
|
int
|
|
iatp_enable(void *v)
|
|
{
|
|
struct iatp_softc *sc = v;
|
|
|
|
if (sc->sc_busy &&
|
|
tsleep_nsec(&sc->sc_busy, PRIBIO, "iatp", SEC_TO_NSEC(1)) != 0) {
|
|
printf("%s: trying to enable but we're busy\n",
|
|
sc->sc_dev.dv_xname);
|
|
return 1;
|
|
}
|
|
|
|
sc->sc_busy = 1;
|
|
|
|
DPRINTF(("%s: enabling\n", sc->sc_dev.dv_xname));
|
|
|
|
if (iatp_configure(sc)) {
|
|
printf("%s: failed wsmouse_configure\n", sc->sc_dev.dv_xname);
|
|
return 1;
|
|
}
|
|
|
|
/* force a read of any pending messages so we start getting new
|
|
* interrupts */
|
|
iatp_t5_read_msgs(sc, sc->info.max_report_id);
|
|
|
|
sc->sc_enabled = 1;
|
|
sc->sc_busy = 0;
|
|
|
|
return 0;
|
|
}
|
|
|
|
void
|
|
iatp_disable(void *v)
|
|
{
|
|
struct iatp_softc *sc = v;
|
|
|
|
DPRINTF(("%s: disabling\n", sc->sc_dev.dv_xname));
|
|
|
|
if (sc->sc_touchpad)
|
|
wsmouse_set_mode(sc->sc_wsmousedev, WSMOUSE_COMPAT);
|
|
|
|
sc->sc_enabled = 0;
|
|
}
|
|
|
|
int
|
|
iatp_ioctl(void *v, u_long cmd, caddr_t data, int flag, struct proc *p)
|
|
{
|
|
struct iatp_softc *sc = v;
|
|
struct wsmouse_calibcoords *wsmc = (struct wsmouse_calibcoords *)data;
|
|
int wsmode;
|
|
|
|
DPRINTF(("%s: %s: cmd %ld\n", sc->sc_dev.dv_xname, __func__, cmd));
|
|
|
|
switch (cmd) {
|
|
case WSMOUSEIO_SCALIBCOORDS:
|
|
sc->sc_tsscale.minx = wsmc->minx;
|
|
sc->sc_tsscale.maxx = wsmc->maxx;
|
|
sc->sc_tsscale.miny = wsmc->miny;
|
|
sc->sc_tsscale.maxy = wsmc->maxy;
|
|
sc->sc_tsscale.swapxy = wsmc->swapxy;
|
|
sc->sc_tsscale.resx = wsmc->resx;
|
|
sc->sc_tsscale.resy = wsmc->resy;
|
|
break;
|
|
|
|
case WSMOUSEIO_GCALIBCOORDS:
|
|
wsmc->minx = sc->sc_tsscale.minx;
|
|
wsmc->maxx = sc->sc_tsscale.maxx;
|
|
wsmc->miny = sc->sc_tsscale.miny;
|
|
wsmc->maxy = sc->sc_tsscale.maxy;
|
|
wsmc->swapxy = sc->sc_tsscale.swapxy;
|
|
wsmc->resx = sc->sc_tsscale.resx;
|
|
wsmc->resy = sc->sc_tsscale.resy;
|
|
break;
|
|
|
|
case WSMOUSEIO_GTYPE: {
|
|
struct wsmousehw *hw = wsmouse_get_hw(sc->sc_wsmousedev);
|
|
*(u_int *)data = hw->type;
|
|
break;
|
|
}
|
|
|
|
case WSMOUSEIO_SETMODE:
|
|
if (!sc->sc_touchpad)
|
|
return -1;
|
|
|
|
wsmode = *(u_int *)data;
|
|
if (wsmode != WSMOUSE_COMPAT && wsmode != WSMOUSE_NATIVE) {
|
|
printf("%s: invalid mode %d\n", sc->sc_dev.dv_xname,
|
|
wsmode);
|
|
return EINVAL;
|
|
}
|
|
wsmouse_set_mode(sc->sc_wsmousedev, wsmode);
|
|
break;
|
|
|
|
default:
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
iatp_init(struct iatp_softc *sc)
|
|
{
|
|
uint8_t reportid;
|
|
int i;
|
|
|
|
sc->sc_enabled = 0;
|
|
|
|
/* some sane defaults */
|
|
sc->num_touchids = 10;
|
|
sc->max_x = 1023;
|
|
sc->max_y = 1023;
|
|
sc->sc_touchpad = 0;
|
|
|
|
/*
|
|
* AT42QT1085 Information block:
|
|
*
|
|
* ID information (struct mxt_id_info)
|
|
* 0 Family ID
|
|
* 1 Variant ID
|
|
* 2 Version
|
|
* 3 Build
|
|
* 4 Number of Keys
|
|
* 5 1
|
|
* 6 Number of Object Table Elements
|
|
* Object Table Element 1 (struct mxt_object)
|
|
* 7 Object Type
|
|
* 8-9 Object Start Address
|
|
* 10 Size - 1
|
|
* 11 Instances - 1
|
|
* 12 Number of report IDs per instance
|
|
* Object Table Element 2 (struct mxt_object)
|
|
* ...
|
|
* Information Block Checksum
|
|
* [ Object 1 ]
|
|
* ...
|
|
*/
|
|
|
|
/* read table header */
|
|
if (iatp_read_reg(sc, 0, sizeof(struct mxt_id_info), &sc->info.id) ||
|
|
!sc->info.id.num_objects) {
|
|
printf("%s: failed reading main memory map\n",
|
|
sc->sc_dev.dv_xname);
|
|
return 0;
|
|
}
|
|
|
|
sc->table_size = sc->info.id.num_objects * sizeof(struct mxt_object);
|
|
sc->table = malloc(sc->table_size, M_DEVBUF, M_NOWAIT | M_ZERO);
|
|
|
|
/* read all table objects */
|
|
if (iatp_read_reg(sc, sizeof(struct mxt_id_info), sc->table_size,
|
|
sc->table)) {
|
|
printf("%s: failed reading info table of size %zu\n",
|
|
sc->sc_dev.dv_xname, sc->table_size);
|
|
return 0;
|
|
}
|
|
|
|
reportid = 1;
|
|
for (i = 0; i < sc->info.id.num_objects; i++) {
|
|
struct mxt_object *object = (void *)(sc->table +
|
|
(sizeof(struct mxt_object) * i));
|
|
int min_id = 0, max_id = 0;
|
|
|
|
if (object->num_report_ids) {
|
|
min_id = reportid;
|
|
reportid += (object->num_report_ids *
|
|
(uint8_t)MXT_INSTANCES(object));
|
|
max_id = reportid - 1;
|
|
}
|
|
|
|
DPRINTF(("%s: object[%d] T%d at 0x%x, %d report ids (%d-%d)\n",
|
|
sc->sc_dev.dv_xname, i, object->type,
|
|
le16toh(object->start_pos), object->num_report_ids, min_id,
|
|
max_id));
|
|
|
|
switch (object->type) {
|
|
case MXT_GEN_MESSAGEPROCESSOR_T5:
|
|
/*
|
|
* 4.2 - message processor is what interrupts and
|
|
* relays new messages to us
|
|
*/
|
|
|
|
if (sc->info.id.family == 0x80 &&
|
|
sc->info.id.version < 0x20)
|
|
/*
|
|
* from linux: "On mXT224 firmware versions
|
|
* prior to V2.0 read and discard unused CRC
|
|
* byte otherwise DMA reads are misaligned."
|
|
*/
|
|
sc->t5_msg_size = MXT_SIZE(object);
|
|
else
|
|
sc->t5_msg_size = MXT_SIZE(object) - 1;
|
|
|
|
sc->t5_address = le16toh(object->start_pos);
|
|
break;
|
|
|
|
case MXT_GEN_COMMANDPROCESSOR_T6:
|
|
/*
|
|
* 4.3 - command processor receives commands from us
|
|
* and reports command status messages
|
|
*/
|
|
sc->t6_address = le16toh(object->start_pos);
|
|
sc->t6_reportid = min_id;
|
|
break;
|
|
|
|
case MXT_GEN_POWERCONFIG_T7:
|
|
/*
|
|
* 4.4 - power configuration, number of milliseconds
|
|
* between sampling in each mode
|
|
*/
|
|
sc->t7_address = le16toh(object->start_pos);
|
|
|
|
iatp_read_reg(sc, sc->t7_address,
|
|
sizeof(sc->t7_config), &sc->t7_config);
|
|
|
|
break;
|
|
|
|
case MXT_SPT_GPIOPWM_T19: {
|
|
/*
|
|
* generic gpio pin, mapped to touchpad button(s)
|
|
*/
|
|
const struct mxt_t19_button_map *m;
|
|
|
|
sc->t19_reportid = min_id;
|
|
|
|
/* find this machine's button config */
|
|
sc->t19_button_bit = -1;
|
|
if (hw_vendor == NULL || hw_prod == NULL)
|
|
break;
|
|
|
|
for (m = mxt_t19_button_map_devs; m->vendor != NULL;
|
|
m++) {
|
|
if (strncmp(hw_vendor, m->vendor,
|
|
strlen(m->vendor)) != 0 ||
|
|
strncmp(hw_prod, m->product,
|
|
strlen(m->product)) != 0 ||
|
|
strncmp(sc->sc_hid, m->hid,
|
|
strlen(m->hid)) != 0)
|
|
continue;
|
|
|
|
DPRINTF(("%s: found matching t19 "
|
|
"button map device \"%s\"/\"%s\" on %s: "
|
|
"bit %d\n", sc->sc_dev.dv_xname,
|
|
m->vendor, m->product, m->hid, m->bit));
|
|
sc->t19_button_bit = m->bit;
|
|
break;
|
|
}
|
|
|
|
if (sc->t19_button_bit > -1)
|
|
sc->sc_touchpad = 1;
|
|
|
|
break;
|
|
}
|
|
|
|
case MXT_SPT_MESSAGECOUNT_T44:
|
|
sc->t44_address = le16toh(object->start_pos);
|
|
break;
|
|
|
|
case MXT_TOUCH_MULTITOUCHSCREEN_T100: {
|
|
uint16_t range_x, range_y;
|
|
uint8_t orient, tchaux;
|
|
int aux;
|
|
|
|
sc->t100_reportid_min = min_id;
|
|
sc->t100_reportid_max = max_id;
|
|
sc->num_touchids = object->num_report_ids - 2;
|
|
sc->multitouch = MXT_TOUCH_MULTITOUCHSCREEN_T100;
|
|
|
|
if (iatp_read_reg(sc, object->start_pos +
|
|
MXT_T100_XRANGE, sizeof(range_x), &range_x) ||
|
|
iatp_read_reg(sc, object->start_pos +
|
|
MXT_T100_YRANGE, sizeof(range_y), &range_y) ||
|
|
iatp_read_reg(sc, object->start_pos +
|
|
MXT_T100_CFG1, 1, &orient) ||
|
|
iatp_read_reg(sc, object->start_pos +
|
|
MXT_T100_TCHAUX, 1, &tchaux)) {
|
|
printf("%s: failed reading t100 settings\n",
|
|
sc->sc_dev.dv_xname);
|
|
continue;
|
|
}
|
|
|
|
/*
|
|
* orient just affects the size we read, not the x/y
|
|
* values we read per-packet later.
|
|
*/
|
|
if (orient & MXT_T100_CFG_SWITCHXY) {
|
|
sc->max_x = le16toh(range_y);
|
|
sc->max_y = le16toh(range_x);
|
|
} else {
|
|
sc->max_x = le16toh(range_x);
|
|
sc->max_y = le16toh(range_y);
|
|
}
|
|
|
|
aux = 6;
|
|
if (tchaux & MXT_T100_TCHAUX_VECT)
|
|
sc->t100_aux_vect = aux++;
|
|
if (tchaux & MXT_T100_TCHAUX_AMPL)
|
|
sc->t100_aux_ampl = aux++;
|
|
if (tchaux & MXT_T100_TCHAUX_AREA)
|
|
sc->t100_aux_area = aux++;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
sc->info.max_report_id = reportid;
|
|
|
|
sc->sc_tsscale.minx = 0;
|
|
sc->sc_tsscale.maxx = sc->max_x;
|
|
sc->sc_tsscale.miny = 0;
|
|
sc->sc_tsscale.maxy = sc->max_y;
|
|
sc->sc_tsscale.swapxy = 0;
|
|
sc->sc_tsscale.resx = 0;
|
|
sc->sc_tsscale.resy = 0;
|
|
|
|
/*
|
|
* iatp_t44_read_count expects t5 message processor to immediately
|
|
* follow t44 message count byte
|
|
*/
|
|
if (sc->t44_address && (sc->t5_address != sc->t44_address + 1)) {
|
|
printf("%s: t5 address (0x%x) != t44 (0x%x + 1)\n",
|
|
sc->sc_dev.dv_xname, sc->t5_address, sc->t44_address);
|
|
return 0;
|
|
}
|
|
|
|
sc->msg_buf = mallocarray(sc->info.max_report_id, sc->t5_msg_size,
|
|
M_DEVBUF, M_NOWAIT | M_ZERO);
|
|
|
|
/* flush queue of any pending messages */
|
|
iatp_t5_read_msgs(sc, sc->info.max_report_id);
|
|
|
|
return 1;
|
|
}
|
|
|
|
int
|
|
iatp_read_reg(struct iatp_softc *sc, uint16_t reg, size_t len, void *val)
|
|
{
|
|
uint8_t cmd[2] = { reg & 0xff, (reg >> 8) & 0xff };
|
|
int ret;
|
|
|
|
iic_acquire_bus(sc->sc_tag, I2C_F_POLL);
|
|
|
|
ret = iic_exec(sc->sc_tag, I2C_OP_READ_WITH_STOP, sc->sc_addr, &cmd,
|
|
sizeof(cmd), val, len, I2C_F_POLL);
|
|
|
|
iic_release_bus(sc->sc_tag, I2C_F_POLL);
|
|
|
|
return ret;
|
|
}
|
|
|
|
int
|
|
iatp_write_reg(struct iatp_softc *sc, uint16_t reg, size_t len, void *val)
|
|
{
|
|
int ret;
|
|
uint8_t *cmd;
|
|
|
|
cmd = malloc(len + 2, M_DEVBUF, M_NOWAIT | M_ZERO);
|
|
cmd[0] = reg & 0xff;
|
|
cmd[1] = (reg >> 8) & 0xff;
|
|
memcpy(&cmd[2], val, len);
|
|
|
|
iic_acquire_bus(sc->sc_tag, 0);
|
|
|
|
ret = iic_exec(sc->sc_tag, I2C_OP_WRITE, sc->sc_addr, cmd, len + 2,
|
|
NULL, 0, I2C_F_POLL);
|
|
|
|
iic_release_bus(sc->sc_tag, 0);
|
|
|
|
free(cmd, M_DEVBUF, len + 2);
|
|
|
|
return ret;
|
|
}
|
|
|
|
int
|
|
iatp_intr(void *arg)
|
|
{
|
|
struct iatp_softc *sc = arg;
|
|
int count;
|
|
|
|
DPRINTF(("%s: %s (busy:%d enabled:%d)\n", sc->sc_dev.dv_xname,
|
|
__func__, sc->sc_busy, sc->sc_enabled));
|
|
|
|
if (sc->sc_busy)
|
|
return 1;
|
|
|
|
sc->sc_busy = 1;
|
|
|
|
if (sc->t44_address)
|
|
count = iatp_t44_read_count(sc);
|
|
else
|
|
count = 1;
|
|
|
|
if (count)
|
|
iatp_t5_read_msgs(sc, count);
|
|
|
|
sc->sc_busy = 0;
|
|
wakeup(&sc->sc_busy);
|
|
|
|
return 1;
|
|
}
|
|
|
|
int
|
|
iatp_proc_msg(struct iatp_softc *sc, uint8_t *msg)
|
|
{
|
|
uint8_t report_id = msg[0];
|
|
int i;
|
|
|
|
/* process a single message that has already been read off the wire */
|
|
|
|
if (report_id == 0xff)
|
|
/*
|
|
* this is usually when we've intentionally over-read just to
|
|
* clear any pending data to keep interrupts flowing
|
|
*/
|
|
return 0;
|
|
|
|
DPRINTF(("%s: %s: report id %d\n", sc->sc_dev.dv_xname, __func__,
|
|
report_id));
|
|
|
|
if (report_id == sc->t19_reportid)
|
|
iatp_t19_proc_msg(sc, msg);
|
|
else if (report_id >= sc->t100_reportid_min &&
|
|
report_id <= sc->t100_reportid_max)
|
|
iatp_t100_proc_msg(sc, msg);
|
|
else {
|
|
DPRINTF(("%s: unknown message (report id %d)",
|
|
sc->sc_dev.dv_xname, report_id));
|
|
for (i = 0; i < sc->t5_msg_size; i++)
|
|
DPRINTF((" %02x", msg[i]));
|
|
DPRINTF(("\n"));
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
int
|
|
iatp_t5_read_msgs(struct iatp_softc *sc, int count)
|
|
{
|
|
int i;
|
|
|
|
if (count > sc->info.max_report_id) {
|
|
DPRINTF(("%s: clamping count %d to max_report_id %d\n",
|
|
sc->sc_dev.dv_xname, count, sc->info.max_report_id));
|
|
count = sc->info.max_report_id;
|
|
}
|
|
|
|
DPRINTF(("%s: %s: %d message(s) to read\n", sc->sc_dev.dv_xname,
|
|
__func__, count));
|
|
|
|
if (iatp_read_reg(sc, sc->t5_address, sc->t5_msg_size * count,
|
|
sc->msg_buf)) {
|
|
printf("%s: failed reading %d\n", sc->sc_dev.dv_xname,
|
|
sc->t5_msg_size * count);
|
|
return 0;
|
|
}
|
|
|
|
for (i = 0; i < count; i++)
|
|
iatp_proc_msg(sc, sc->msg_buf + (sc->t5_msg_size * i));
|
|
|
|
return 1;
|
|
}
|
|
|
|
void
|
|
iatp_t6_proc_msg(struct iatp_softc *sc, uint8_t *msg)
|
|
{
|
|
uint8_t status = msg[1];
|
|
|
|
if (status & MXT_T6_STATUS_RESET)
|
|
DPRINTF(("%s: completed reset\n", sc->sc_dev.dv_xname));
|
|
else
|
|
DPRINTF(("%s: other status report 0x%x\n", sc->sc_dev.dv_xname,
|
|
status));
|
|
}
|
|
|
|
int
|
|
iatp_t7_set_power_mode(struct iatp_softc *sc, int mode)
|
|
{
|
|
struct mxt_t7_config new_config;
|
|
|
|
if (mode == MXT_T7_POWER_MODE_DEEP_SLEEP) {
|
|
new_config.idle = 0;
|
|
new_config.active = 0;
|
|
new_config.atoi_timeout = 0;
|
|
} else
|
|
new_config = sc->t7_config;
|
|
|
|
DPRINTF(("%s: setting power mode to %d\n", sc->sc_dev.dv_xname, mode));
|
|
|
|
if (iatp_write_reg(sc, sc->t7_address, sizeof(new_config),
|
|
&new_config)) {
|
|
printf("%s: failed setting power mode to %d\n",
|
|
sc->sc_dev.dv_xname, mode);
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void
|
|
iatp_t19_proc_msg(struct iatp_softc *sc, uint8_t *msg)
|
|
{
|
|
int s;
|
|
|
|
if (!sc->sc_enabled)
|
|
return;
|
|
|
|
/* active-low switch */
|
|
sc->button = !(msg[1] & (1 << sc->t19_button_bit));
|
|
|
|
DPRINTF(("%s: button is %d\n", sc->sc_dev.dv_xname, sc->button));
|
|
|
|
s = spltty();
|
|
wsmouse_buttons(sc->sc_wsmousedev, sc->button);
|
|
wsmouse_input_sync(sc->sc_wsmousedev);
|
|
splx(s);
|
|
}
|
|
|
|
int
|
|
iatp_t44_read_count(struct iatp_softc *sc)
|
|
{
|
|
int ret, count;
|
|
|
|
/* read t44 count byte and t5 message data in one shot */
|
|
ret = iatp_read_reg(sc, sc->t44_address, 1 + sc->t5_msg_size,
|
|
sc->msg_buf);
|
|
if (ret) {
|
|
printf("%s: failed reading t44 and t5\n", sc->sc_dev.dv_xname);
|
|
return 0;
|
|
}
|
|
|
|
count = sc->msg_buf[0];
|
|
if (count == 0) {
|
|
DPRINTF(("%s: %s: no messages\n", sc->sc_dev.dv_xname,
|
|
__func__));
|
|
/* flush so we keep getting interrupts */
|
|
iatp_t5_read_msgs(sc, sc->info.max_report_id);
|
|
return 0;
|
|
}
|
|
|
|
count--;
|
|
iatp_proc_msg(sc, sc->msg_buf + 1);
|
|
|
|
return count;
|
|
}
|
|
|
|
void
|
|
iatp_t100_proc_msg(struct iatp_softc *sc, uint8_t *msg)
|
|
{
|
|
int id = msg[0] - sc->t100_reportid_min - 2;
|
|
int s;
|
|
uint8_t status, type = 0, pressure = 0;
|
|
uint16_t x, y;
|
|
|
|
if (id < 0 || !sc->sc_enabled)
|
|
return;
|
|
|
|
status = msg[1];
|
|
x = (msg[3] << 8) | msg[2];
|
|
y = (msg[5] << 8) | msg[4];
|
|
|
|
if (status & MXT_T100_DETECT) {
|
|
type = (status & MXT_T100_TYPE_MASK) >> 4;
|
|
|
|
if (sc->t100_aux_ampl)
|
|
pressure = msg[sc->t100_aux_ampl];
|
|
|
|
if (!pressure && type != MXT_T100_TYPE_HOVERING_FINGER)
|
|
pressure = 50; /* large enough for synaptics driver */
|
|
|
|
DPRINTF(("%s: type=%d x=%d y=%d finger=%d pressure=%d "
|
|
"button=%d\n", sc->sc_dev.dv_xname, type, x, y, id,
|
|
pressure, sc->button));
|
|
} else {
|
|
DPRINTF(("%s: closing slot for finger=%d\n",
|
|
sc->sc_dev.dv_xname, id));
|
|
|
|
if (sc->sc_touchpad)
|
|
x = y = 0;
|
|
|
|
pressure = 0;
|
|
}
|
|
|
|
if (sc->sc_touchpad)
|
|
y = (sc->max_y - y);
|
|
|
|
/* TODO: adjust to sc_tsscale? */
|
|
|
|
s = spltty();
|
|
|
|
wsmouse_mtstate(sc->sc_wsmousedev, id, x, y, pressure);
|
|
|
|
/* on the touchscreen, assume any finger down is clicking */
|
|
if (!sc->sc_touchpad)
|
|
wsmouse_buttons(sc->sc_wsmousedev, pressure ? 1 : 0);
|
|
|
|
wsmouse_input_sync(sc->sc_wsmousedev);
|
|
|
|
splx(s);
|
|
}
|