And use it for exynos4210 NURI board emulation Signed-off-by: Igor Mitsyanko <i.mitsya...@samsung.com> --- Makefile.objs | 1 + default-configs/arm-softmmu.mak | 1 + hw/exynos4_boards.c | 11 +- hw/maxtouch.c | 1114 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 1124 insertions(+), 3 deletions(-) create mode 100644 hw/maxtouch.c
diff --git a/Makefile.objs b/Makefile.objs index 6d6f24d..d2a53b4 100644 --- a/Makefile.objs +++ b/Makefile.objs @@ -250,6 +250,7 @@ hw-obj-$(CONFIG_SMARTCARD) += usb/dev-smartcard-reader.o ccid-card-passthru.o hw-obj-$(CONFIG_SMARTCARD_NSS) += ccid-card-emulated.o hw-obj-$(CONFIG_USB_REDIR) += usb/redirect.o hw-obj-$(CONFIG_I8259) += i8259_common.o i8259.o +hw-obj-$(CONFIG_MAXTOUCH) += maxtouch.o # PPC devices hw-obj-$(CONFIG_PREP_PCI) += prep_pci.o diff --git a/default-configs/arm-softmmu.mak b/default-configs/arm-softmmu.mak index e542b4f..7666748 100644 --- a/default-configs/arm-softmmu.mak +++ b/default-configs/arm-softmmu.mak @@ -27,3 +27,4 @@ CONFIG_SMC91C111=y CONFIG_DS1338=y CONFIG_PFLASH_CFI01=y CONFIG_PFLASH_CFI02=y +CONFIG_MAXTOUCH=y diff --git a/hw/exynos4_boards.c b/hw/exynos4_boards.c index c02921b..4368aa1 100644 --- a/hw/exynos4_boards.c +++ b/hw/exynos4_boards.c @@ -28,6 +28,7 @@ #include "exec-memory.h" #include "exynos4210.h" #include "boards.h" +#include "i2c.h" #undef DEBUG @@ -44,6 +45,7 @@ #endif #define SMDK_LAN9118_BASE_ADDR 0x05000000 +#define MAXTOUCH_TS_I2C_ADDR 0x4a typedef enum Exynos4BoardType { EXYNOS4_BOARD_NURI, @@ -135,9 +137,12 @@ static void nuri_init(ram_addr_t ram_size, const char *kernel_filename, const char *kernel_cmdline, const char *initrd_filename, const char *cpu_model) { - exynos4_boards_init_common(kernel_filename, kernel_cmdline, - initrd_filename, EXYNOS4_BOARD_NURI); - + Exynos4210State *s = exynos4_boards_init_common(kernel_filename, + kernel_cmdline, initrd_filename, EXYNOS4_BOARD_NURI); + DeviceState *dev = + i2c_create_slave(s->i2c_if[3], "maxtouch.var0", MAXTOUCH_TS_I2C_ADDR); + qdev_connect_gpio_out(dev, 0, qdev_get_gpio_in(s->gpio2x, + EXYNOS4210_GPIO2X_LINE(GPX0, 4))); arm_load_kernel(first_cpu, &exynos4_board_binfo); } diff --git a/hw/maxtouch.c b/hw/maxtouch.c new file mode 100644 index 0000000..899c7f6 --- /dev/null +++ b/hw/maxtouch.c @@ -0,0 +1,1114 @@ +/* + * Atmel maXTouch touchscreen emulation + * + * Copyright (c) 2012 Samsung Electronics Co., Ltd. + * Igor Mitsyanko <i.mitsya...@samsung.com> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, see <http://www.gnu.org/licenses/>. + * + */ + +#include "i2c.h" +#include "console.h" + +#ifndef MXT_DEBUG +#define MXT_DEBUG 0 +#endif + +/* Fifo length must be a power of 2 */ +#define MXT_MESSAGE_FIFO_LEN 16 +#define MXT_MESSAGE_FIFO_MASK (MXT_MESSAGE_FIFO_LEN - 1) +/* Maxtouch supports up to 10 concurrent touches, but we emulate 3 since common + * PC mouse has only 3 buttons. Exact meaning of each touch (each mouse button + * press) is defined by target userspace application only */ +#define MXT_NUM_OF_TOUCHES 3 +#define MXT_CRC_POLY 0x80001B +#define MXT_CRC_SIZE 3 +/* Maximum value of x and y coordinate in QEMU mouse event callback */ +#define MXT_QEMU_MAX_COORD 0x7FFF + +/* Each maXTouch device consists of a certain number of subdevices (objects) + * with code names like T5, T6, T9, e.t.c. Each object implements only a portion + * of maXTouch functionality. For example, touch detection is performed + * by T9 object, but information about touch state changes is generated (and can + * be read) only in T5 object. + * Various variants of maXTouch can have different set of objects placed at + * different addresses within maXtouch address space. Composition of objects + * is described by mandatory Object Table which starts at address 0x7. + * Length of object table (i.e. number of objects) of this exact variant of + * maXTouch can be read from address 0x6 */ +#define MXT_OBJTBL_ENTRY_LEN 6 +/* Offsets within one object table entry */ +/* Object type code */ +#define MXT_OBJTBL_TYPE 0x0 +/* Start address of object registers within maxTouch address space */ +#define MXT_OBJTBL_START_LSB 0x1 +#define MXT_OBJTBL_START_MSB 0x2 +/* Number of object's registers (actually, this field contains size-1) */ +#define MXT_OBJTBL_SIZE 0x3 +/* Number of instances of this object (contains instances-1, so value 0 means + * one instance). All instances are placed continuously in memory */ +#define MXT_OBJTBL_INSTANCES 0x4 +/* Number of messages ID's object can generate in T5 object. For example, + * T9 will generate messages with different ID's for each separate touch */ +#define MXT_OBJTBL_REPORT_IDS 0x5 + +/* Object types */ +enum { + MXT_GEN_MESSAGE_T5 = 0, + MXT_GEN_COMMAND_T6, + MXT_GEN_POWER_T7, + MXT_GEN_ACQUIRE_T8, + MXT_TOUCH_MULTI_T9, + MXT_TOUCH_KEYARRAY_T15, + MXT_SPT_COMMSCONFIG_T18, + MXT_SPT_GPIOPWM_T19, + MXT_PROCI_GRIPFACE_T20, + MXT_PROCG_NOISE_T22, + MXT_TOUCH_PROXIMITY_T23, + MXT_PROCI_ONETOUCH_T24, + MXT_SPT_SELFTEST_T25, + MXT_PROCI_TWOTOUCH_T27, + MXT_SPT_CTECONFIG_T28, + MXT_DEBUG_DIAGNOSTIC_T37, + MXT_NUM_OF_OBJECT_TYPES +}; + +static const uint8_t mxt_obj_types_list[MXT_NUM_OF_OBJECT_TYPES] = { + [MXT_GEN_MESSAGE_T5] = 5, + [MXT_GEN_COMMAND_T6] = 6, + [MXT_GEN_POWER_T7] = 7, + [MXT_GEN_ACQUIRE_T8] = 8, + [MXT_TOUCH_MULTI_T9] = 9, + [MXT_TOUCH_KEYARRAY_T15] = 15, + [MXT_SPT_COMMSCONFIG_T18] = 18, + [MXT_SPT_GPIOPWM_T19] = 19, + [MXT_PROCI_GRIPFACE_T20] = 20, + [MXT_PROCG_NOISE_T22] = 22, + [MXT_TOUCH_PROXIMITY_T23] = 23, + [MXT_PROCI_ONETOUCH_T24] = 24, + [MXT_SPT_SELFTEST_T25] = 25, + [MXT_PROCI_TWOTOUCH_T27] = 27, + [MXT_SPT_CTECONFIG_T28] = 28, + [MXT_DEBUG_DIAGNOSTIC_T37] = 37 +}; + +static const uint8_t mxt_obj_sizes[MXT_NUM_OF_OBJECT_TYPES] = { + [MXT_GEN_MESSAGE_T5] = 10, + [MXT_GEN_COMMAND_T6] = 6, + [MXT_GEN_POWER_T7] = 3, + [MXT_GEN_ACQUIRE_T8] = 8, + [MXT_TOUCH_MULTI_T9] = 31, + [MXT_TOUCH_KEYARRAY_T15] = 11, + [MXT_SPT_COMMSCONFIG_T18] = 2, + [MXT_SPT_GPIOPWM_T19] = 16, + [MXT_PROCI_GRIPFACE_T20] = 12, + [MXT_PROCG_NOISE_T22] = 17, + [MXT_TOUCH_PROXIMITY_T23] = 15, + [MXT_PROCI_ONETOUCH_T24] = 19, + [MXT_SPT_SELFTEST_T25] = 14, + [MXT_PROCI_TWOTOUCH_T27] = 7, + [MXT_SPT_CTECONFIG_T28] = 6, + [MXT_DEBUG_DIAGNOSTIC_T37] = 128 +}; + +#define MXT_INFO_START 0x00 +#define MXT_INFO_SIZE 7 +#define MXT_INFO_END (MXT_INFO_START + MXT_INFO_SIZE - 1) +#define MXT_OBJTBL_START (MXT_INFO_START + MXT_INFO_SIZE) +#define MXT_OBJTBL_END(s) \ + (MXT_OBJTBL_START + (s->mxt_info[MXT_OBJ_NUM]) * MXT_OBJTBL_ENTRY_LEN - 1) +#define MXT_CRC_START(s) \ + (MXT_OBJTBL_START + (s->mxt_info[MXT_OBJ_NUM]) * MXT_OBJTBL_ENTRY_LEN) +#define MXT_CRC_END(s) \ + (MXT_OBJTBL_START + (s->mxt_info[MXT_OBJ_NUM]) * MXT_OBJTBL_ENTRY_LEN + \ + MXT_CRC_SIZE - 1) +#define MXT_OBJECTS_START(s) (s->obj_tbl[0].start_addr) + +/* MXT info Registers */ +#define MXT_FAMILY_ID 0x00 +#define MXT_VARIANT_ID 0x01 +#define MXT_VERSION 0x02 +#define MXT_BUILD 0x03 +#define MXT_MATRIX_X_SIZE 0x04 +#define MXT_MATRIX_Y_SIZE 0x05 +#define MXT_OBJ_NUM 0x06 + +/* Registers within Multitouch T9 object */ +#define MXT_OBJ_T9_CTRL 0 +#define MXT_OBJ_T9_XORIGIN 1 +#define MXT_OBJ_T9_YORIGIN 2 +#define MXT_OBJ_T9_XSIZE 3 +#define MXT_OBJ_T9_YSIZE 4 +#define MXT_OBJ_T9_BLEN 6 +#define MXT_OBJ_T9_TCHTHR 7 +#define MXT_OBJ_T9_TCHDI 8 +#define MXT_OBJ_T9_ORIENT 9 +#define MXT_OBJ_T9_MOVHYSTI 11 +#define MXT_OBJ_T9_MOVHYSTN 12 +#define MXT_OBJ_T9_NUMTOUCH 14 +#define MXT_OBJ_T9_MRGHYST 15 +#define MXT_OBJ_T9_MRGTHR 16 +#define MXT_OBJ_T9_AMPHYST 17 +#define MXT_OBJ_T9_XRANGE_LSB 18 +#define MXT_OBJ_T9_XRANGE_MSB 19 +#define MXT_OBJ_T9_YRANGE_LSB 20 +#define MXT_OBJ_T9_YRANGE_MSB 21 +#define MXT_OBJ_T9_XLOCLIP 22 +#define MXT_OBJ_T9_XHICLIP 23 +#define MXT_OBJ_T9_YLOCLIP 24 +#define MXT_OBJ_T9_YHICLIP 25 +#define MXT_OBJ_T9_XEDGECTRL 26 +#define MXT_OBJ_T9_XEDGEDIST 27 +#define MXT_OBJ_T9_YEDGECTRL 28 +#define MXT_OBJ_T9_YEDGEDIST 29 +#define MXT_OBJ_T9_JUMPLIMIT 30 + +/* Multitouch T9 messages status flags */ +#define MXT_T9_STAT_MOVE (1 << 4) +#define MXT_T9_STAT_RELEASE (1 << 5) +#define MXT_T9_STAT_PRESS (1 << 6) +#define MXT_T9_STAT_DETECT (1 << 7) + +/* Multitouch T9 orient bits */ +#define MXT_T9_XY_SWITCH (1 << 0) + +/* Fields of T5 message */ +#define MXT_OBJ_T5_REPORTID 0 +#define MXT_OBJ_T5_STATUS 1 +#define MXT_OBJ_T5_XPOSMSH 2 +#define MXT_OBJ_T5_YPOSMSH 3 +#define MXT_OBJ_T5_XYPOSLSH 4 +#define MXT_OBJ_T5_AREA 5 +#define MXT_OBJ_T5_PRESSURE 6 +#define MXT_OBJ_T5_UNKNOWN 7 +#define MXT_OBJ_T5_CHECKSUM 8 + +#if MXT_MESSAGE_FIFO_LEN & MXT_MESSAGE_FIFO_MASK +#error Message fifo length must be a power of 2 +#endif + +/* An entry of object description table */ +typedef struct MXTObjTblEntry { + uint8_t type; + uint16_t start_addr; + uint8_t size; + uint8_t instances; + uint8_t num_report_ids; +} MXTObjTblEntry; + +typedef struct ObjConfig { + uint8_t type; + uint8_t instances; +} ObjConfig; + +typedef struct MXTVariantInfo { + const char *name; + const uint8_t *mxt_variant_info; + const ObjConfig *mxt_variant_obj_list; +} MXTVariantInfo; + +#define TYPE_MAXTOUCH "maxtouch" +#define MXT(obj) \ + OBJECT_CHECK(MXTState, (obj), TYPE_MAXTOUCH) +#define MXT_CLASS(klass) \ + OBJECT_CLASS_CHECK(MXTClass, (klass), TYPE_MAXTOUCH) +#define MXT_GET_CLASS(obj) \ + OBJECT_GET_CLASS(MXTClass, (obj), TYPE_MAXTOUCH) + +/* Definitions for variant #0 of Atmel maXTouch */ +#define TYPE_MXT_VARIANT0 "maxtouch.var0" + +static const ObjConfig mxt_variant0_objlist[] = { + { .type = MXT_GEN_MESSAGE_T5, .instances = 0 }, + { .type = MXT_GEN_COMMAND_T6, .instances = 0 }, + { .type = MXT_GEN_POWER_T7, .instances = 0 }, + { .type = MXT_GEN_ACQUIRE_T8, .instances = 0 }, + { .type = MXT_TOUCH_MULTI_T9, .instances = 0 }, + { .type = MXT_TOUCH_KEYARRAY_T15, .instances = 0 }, + { .type = MXT_SPT_GPIOPWM_T19, .instances = 0 }, + { .type = MXT_PROCI_GRIPFACE_T20, .instances = 0 }, + { .type = MXT_PROCG_NOISE_T22, .instances = 0 }, + { .type = MXT_TOUCH_PROXIMITY_T23, .instances = 0 }, + { .type = MXT_PROCI_ONETOUCH_T24, .instances = 0 }, + { .type = MXT_SPT_SELFTEST_T25, .instances = 0 }, + { .type = MXT_PROCI_TWOTOUCH_T27, .instances = 0 }, + { .type = MXT_SPT_CTECONFIG_T28, .instances = 0 }, +}; + +static const uint8_t mxt_variant0_info[MXT_INFO_SIZE] = { + [MXT_FAMILY_ID] = 0x80, + [MXT_VARIANT_ID] = 0x0, + [MXT_VERSION] = 0x1, + [MXT_BUILD] = 0x1, + [MXT_MATRIX_X_SIZE] = 16, + [MXT_MATRIX_Y_SIZE] = 14, + [MXT_OBJ_NUM] = ARRAY_SIZE(mxt_variant0_objlist), +}; + +/* Definitions for variant #1 of Atmel maXTouch */ +#define TYPE_MXT_VARIANT1 "maxtouch.var1" + +static const ObjConfig mxt_variant1_objlist[] = { + { .type = MXT_GEN_MESSAGE_T5, .instances = 0 }, + { .type = MXT_GEN_COMMAND_T6, .instances = 0 }, + { .type = MXT_GEN_POWER_T7, .instances = 0 }, + { .type = MXT_GEN_ACQUIRE_T8, .instances = 0 }, + { .type = MXT_TOUCH_MULTI_T9, .instances = 0 }, + { .type = MXT_SPT_COMMSCONFIG_T18, .instances = 0 }, + { .type = MXT_PROCI_GRIPFACE_T20, .instances = 0 }, + { .type = MXT_PROCG_NOISE_T22, .instances = 0 }, + { .type = MXT_SPT_CTECONFIG_T28, .instances = 0 }, + { .type = MXT_DEBUG_DIAGNOSTIC_T37, .instances = 0 }, +}; + +static const uint8_t mxt_variant1_info[MXT_INFO_SIZE] = { + [MXT_FAMILY_ID] = 0x80, + [MXT_VARIANT_ID] = 0x1, + [MXT_VERSION] = 0x1, + [MXT_BUILD] = 0x1, + [MXT_MATRIX_X_SIZE] = 16, + [MXT_MATRIX_Y_SIZE] = 14, + [MXT_OBJ_NUM] = ARRAY_SIZE(mxt_variant1_objlist), +}; + +static const MXTVariantInfo mxt_variants_info_array[] = { + { + .name = TYPE_MXT_VARIANT0, + .mxt_variant_info = mxt_variant0_info, + .mxt_variant_obj_list = mxt_variant0_objlist + }, + { + .name = TYPE_MXT_VARIANT1, + .mxt_variant_info = mxt_variant1_info, + .mxt_variant_obj_list = mxt_variant1_objlist + } +}; + +#define MXT_NUM_OF_VARIANTS ARRAY_SIZE(mxt_variants_info_array) + +/* Generate Message T5 message format */ +typedef struct MXTMessage { + uint8_t reportid; + uint8_t status; + uint8_t xpos_msh; + uint8_t ypos_msh; + uint8_t xypos_lsh; + uint8_t area; + uint8_t pressure; + uint8_t checksum; +} MXTMessage; + +/* Possible MXT i2c-related states */ +enum { + IDLE = 0, + STARTING_WRITE, + SELECTING_REGISTER, + WRITING_DATA, + READING_DATA, +}; + +typedef struct MXTClass { + I2CSlaveClass parent_class; + + const uint8_t *mxt_info; + MXTObjTblEntry *obj_tbl; + uint8_t crc[MXT_CRC_SIZE]; + uint16_t end_addr; + /* This is used to speed things up a little */ + uint16_t t5_address; + uint16_t t9_address; +} MXTClass; + +typedef struct MXTState { + I2CSlave i2c; + QEMUPutMouseEntry *mouse; + qemu_irq nCHG; /* line state changes to low level to signal new event */ + uint8_t *objects; + uint32_t obj_len; + uint16_t selected_reg; + uint8_t i2c_state; + + uint8_t fifo_get; + uint8_t fifo_add; + bool fifo_lock; + MXTMessage msg_fifo[MXT_MESSAGE_FIFO_LEN]; + + double scale_x; + double scale_y; + uint16_t x_threshold; + uint16_t y_threshold; + uint16_t x_curr; + uint16_t y_curr; + uint8_t touches; +} MXTState; + +static const VMStateDescription mxt_message_vmstate = { + .name = "mxt-message", + .version_id = 1, + .minimum_version_id = 1, + .fields = (VMStateField[]) { + VMSTATE_UINT8(reportid, MXTMessage), + VMSTATE_UINT8(status, MXTMessage), + VMSTATE_UINT8(xpos_msh, MXTMessage), + VMSTATE_UINT8(ypos_msh, MXTMessage), + VMSTATE_UINT8(xypos_lsh, MXTMessage), + VMSTATE_UINT8(area, MXTMessage), + VMSTATE_UINT8(pressure, MXTMessage), + VMSTATE_UINT8(checksum, MXTMessage), + VMSTATE_END_OF_LIST() + } +}; + + +#if MXT_DEBUG +#define DPRINT(fmt, args...) \ + do { fprintf(stderr, "QEMU MXT: "fmt, ## args); } while (0) +#define DPRINT_SMPL(fmt, args...) \ + do { fprintf(stderr, fmt, ## args); } while (0) +#define ERRPRINT(fmt, args...) \ + do { fprintf(stderr, "QEMU MXT ERROR: "fmt, ## args); } while (0) + +static char dbg_reg_buf[50]; + +static const char *dbg_multitoucht9_regs[] = { + [MXT_OBJ_T9_CTRL] = "CTRL", + [MXT_OBJ_T9_XORIGIN] = "XORIGIN", + [MXT_OBJ_T9_YORIGIN] = "YORIGIN", + [MXT_OBJ_T9_XSIZE] = "XSIZE", + [MXT_OBJ_T9_YSIZE] = "YSIZE", + [5] = "REG_5", + [MXT_OBJ_T9_BLEN] = "BLEN", + [MXT_OBJ_T9_TCHTHR] = "TCHTHR", + [MXT_OBJ_T9_TCHDI] = "TCHDI", + [MXT_OBJ_T9_ORIENT] = "ORIENT", + [10] = "REG_10", + [MXT_OBJ_T9_MOVHYSTI] = "MOVHYSTI", + [MXT_OBJ_T9_MOVHYSTN] = "MOVHYSTN", + [13] = "REG_13", + [MXT_OBJ_T9_NUMTOUCH] = "NUMTOUCH", + [MXT_OBJ_T9_MRGHYST] = "MRGHYST", + [MXT_OBJ_T9_MRGTHR] = "MRGTHR", + [MXT_OBJ_T9_AMPHYST] = "AMPHYST", + [MXT_OBJ_T9_XRANGE_LSB] = "XRANGE_L", + [MXT_OBJ_T9_XRANGE_MSB] = "XRANGE_H", + [MXT_OBJ_T9_YRANGE_LSB] = "YRANGE_L", + [MXT_OBJ_T9_YRANGE_MSB] = "YRANGE_H", + [MXT_OBJ_T9_XLOCLIP] = "XLOCLIP", + [MXT_OBJ_T9_XHICLIP] = "XHICLIP", + [MXT_OBJ_T9_YLOCLIP] = "YLOCLIP", + [MXT_OBJ_T9_YHICLIP] = "YHICLIP", + [MXT_OBJ_T9_XEDGECTRL] = "XEDGECTRL", + [MXT_OBJ_T9_XEDGEDIST] = "XEDGEDIST", + [MXT_OBJ_T9_YEDGECTRL] = "YEDGECTRL", + [MXT_OBJ_T9_YEDGEDIST] = "YEDGEDIST", + [MXT_OBJ_T9_JUMPLIMIT] = "JUMPLIMIT", +}; + +static const char *dbg_gen_message_t5_regs[] = { + [MXT_OBJ_T5_REPORTID] = "REPORTID", + [MXT_OBJ_T5_STATUS] = "STATUS", + [MXT_OBJ_T5_XPOSMSH] = "XPOSMSH", + [MXT_OBJ_T5_YPOSMSH] = "YPOSMSH", + [MXT_OBJ_T5_XYPOSLSH] = "XYPOSLSH", + [MXT_OBJ_T5_AREA] = "AREA", + [MXT_OBJ_T5_PRESSURE] = "PRESSURE", + [MXT_OBJ_T5_UNKNOWN] = "REG_7", + [MXT_OBJ_T5_CHECKSUM] = "CHECKSUM", + [9] = "REG_9" +}; + +static const char *dbg_mxt_info_regs[] = { + [MXT_FAMILY_ID] = "FAMILY_ID", + [MXT_VARIANT_ID] = "VARIANT_ID", + [MXT_VERSION] = "VERSION", + [MXT_BUILD] = "BUILD", + [MXT_MATRIX_X_SIZE] = "MATRIX_X_SIZE", + [MXT_MATRIX_Y_SIZE] = "MATRIX_Y_SIZE", + [MXT_OBJ_NUM] = "OBJ_NUM", +}; + +static const char *dbg_mxt_obj_name(unsigned type) +{ + switch (type) { + case 5: + return "GEN_MESSAGE_T5"; + case 6: + return "GEN_COMMAND_T6"; + case 7: + return "GEN_POWER_T7"; + case 8: + return "GEN_ACQUIRE_T8"; + case 9: + return "TOUCH_MULTI_T9"; + case 15: + return "TOUCH_KEYARRAY_T15"; + case 18: + return "SPT_COMMSCONFIG_T18"; + case 19: + return "SPT_GPIOPWM_T19"; + case 20: + return "PROCI_GRIPFACE_T20"; + case 22: + return "PROCG_NOISE_T22"; + case 23: + return "TOUCH_PROXIMITY_T23"; + case 24: + return "PROCI_ONETOUCH_T24"; + case 25: + return "SPT_SELFTEST_T25"; + case 27: + return "PROCI_TWOTOUCH_T27"; + case 28: + return "SPT_CTECONFIG_T28"; + default: + return "UNKNOWN"; + } +} + +static char *mxt_get_reg_name(MXTState *s, unsigned int offset) +{ + MXTClass *k = MXT_GET_CLASS(s); + unsigned i; + + if ((offset >= k->t5_address) && + (offset < (k->t5_address + mxt_obj_sizes[MXT_GEN_MESSAGE_T5]))) { + i = (offset - k->t5_address); + snprintf(dbg_reg_buf, sizeof(dbg_reg_buf), "MESSAGE_T5[%s]", + dbg_gen_message_t5_regs[i]); + } else if ((offset >= k->t9_address) && (offset < k->t9_address + + mxt_obj_sizes[MXT_TOUCH_MULTI_T9])) { + i = (offset - k->t9_address); + snprintf(dbg_reg_buf, sizeof(dbg_reg_buf), "MULTITOUCH_T9[%s]", + dbg_multitoucht9_regs[i]); + } else if (offset <= MXT_INFO_END) { + i = (offset - MXT_INFO_START); + snprintf(dbg_reg_buf, sizeof(dbg_reg_buf), "INFO[%s]", + dbg_mxt_info_regs[i]); + } else if (offset <= MXT_OBJTBL_END(k)) { + i = (offset - MXT_OBJTBL_START) / MXT_OBJTBL_ENTRY_LEN; + switch ((offset - MXT_OBJTBL_START) % MXT_OBJTBL_ENTRY_LEN) { + case MXT_OBJTBL_TYPE: + snprintf(dbg_reg_buf, sizeof(dbg_reg_buf), "OBJTBL_%u TYPE", i); + break; + case MXT_OBJTBL_START_LSB: + snprintf(dbg_reg_buf, sizeof(dbg_reg_buf), "OBJTBL_%u ADDR_LSB", i); + break; + case MXT_OBJTBL_START_MSB: + snprintf(dbg_reg_buf, sizeof(dbg_reg_buf), "OBJTBL_%u ADDR_MSB", i); + break; + case MXT_OBJTBL_SIZE: + snprintf(dbg_reg_buf, sizeof(dbg_reg_buf), "OBJTBL_%u SIZE", i); + break; + case MXT_OBJTBL_INSTANCES: + snprintf(dbg_reg_buf, sizeof(dbg_reg_buf), + "OBJTBL_%u INSTANCES", i); + break; + case MXT_OBJTBL_REPORT_IDS: + snprintf(dbg_reg_buf, sizeof(dbg_reg_buf), + "OBJTBL_%u REPORT_IDS", i); + break; + } + } else if (offset <= MXT_CRC_END(k)) { + i = offset - MXT_CRC_START(k); + snprintf(dbg_reg_buf, sizeof(dbg_reg_buf), "CRC[%i]", i); + } else if (offset <= k->end_addr) { + for (i = 0; i < k->mxt_info[MXT_OBJ_NUM]; i++) { + if (offset >= (k->obj_tbl[i].start_addr + k->obj_tbl[i].size + 1)) { + continue; + } + snprintf(dbg_reg_buf, sizeof(dbg_reg_buf), "%s[%u]", + dbg_mxt_obj_name(k->obj_tbl[i].type), + offset - k->obj_tbl[i].start_addr); + break; + } + } else { + snprintf(dbg_reg_buf, sizeof(dbg_reg_buf), "UNKNOWN"); + } + return dbg_reg_buf; +} + +#else +#define DPRINT(fmt, args...) do { } while (0) +#define DPRINT_SMPL(fmt, args...) do { } while (0) +#define ERRPRINT(fmt, args...) \ + do { fprintf(stderr, "QEMU MXT ERROR: "fmt, ## args); } while (0) +#endif + +static void +mxt_mouse_event(void *opaque, int x, int y, int z, int buttons_state) +{ + MXTState *s = (MXTState *)opaque; + uint16_t x_new, y_new; + unsigned i; + bool state_changed = false; + + /* Check that message buffer is not full */ + if (s->fifo_lock || + ((s->fifo_add + 1) & MXT_MESSAGE_FIFO_MASK) == s->fifo_get) { + return; + } + + x_new = (uint16_t)((double)x * s->scale_x); + y_new = (uint16_t)((double)y * s->scale_y); + + for (i = 1; i <= MXT_NUM_OF_TOUCHES; i++) { + if ((s->touches & (1 << (i - 1))) != (buttons_state & (1 << (i - 1)))) { + if (buttons_state & (1 << (i - 1))) { + /* Generate press event message */ + s->msg_fifo[s->fifo_add].status = + MXT_T9_STAT_DETECT | MXT_T9_STAT_PRESS; + s->msg_fifo[s->fifo_add].area = 0x20; + s->msg_fifo[s->fifo_add].pressure = 0x10; + } else { + /* Generate release event message */ + s->msg_fifo[s->fifo_add].status = MXT_T9_STAT_RELEASE; + s->msg_fifo[s->fifo_add].area = 0x0; + s->msg_fifo[s->fifo_add].pressure = 0x0; + } + } else if ((s->touches & (1 << (i - 1))) && + (ABS(x_new - s->x_curr) >= s->x_threshold || + ABS(y_new - s->y_curr) >= s->y_threshold)) { + s->msg_fifo[s->fifo_add].status = + MXT_T9_STAT_DETECT | MXT_T9_STAT_MOVE; + s->msg_fifo[s->fifo_add].area = 0x20; + s->msg_fifo[s->fifo_add].pressure = 0x10; + } else { + continue; + } + + s->msg_fifo[s->fifo_add].xpos_msh = x_new >> 4; + s->msg_fifo[s->fifo_add].ypos_msh = y_new >> 4; + s->msg_fifo[s->fifo_add].xypos_lsh = + (y_new & 0xF) | ((x_new << 4) & 0xF0); + s->msg_fifo[s->fifo_add].reportid = i; + s->msg_fifo[s->fifo_add].checksum = 0; + s->fifo_add = (s->fifo_add + 1) & MXT_MESSAGE_FIFO_MASK; + state_changed = true; + } + + if (state_changed) { + s->touches = buttons_state; + s->x_curr = x_new; + s->y_curr = y_new; + /* CHG line changes to low and new message is generated in + * gen_message_t5 subsystem when touch event occurs. CHG line + * changes back to high only after all messages have been read from + * gen_message_t5 subsystem */ + if (s->fifo_add == ((s->fifo_get + 1) & MXT_MESSAGE_FIFO_MASK)) { + qemu_irq_lower(s->nCHG); + } + + } +} + +/* Read field of current message in message FIFO */ +static uint8_t mxt_read_message_field(MXTState *s, unsigned field) +{ + uint8_t ret; + + /* If there are no messages, return dummy message with REPORTID=0xFF */ + if (s->fifo_get == s->fifo_add) { + s->fifo_lock = true; + if (field == MXT_OBJ_T5_CHECKSUM) { + qemu_irq_raise(s->nCHG); + s->fifo_lock = false; + } + return 0xFF; + } + + switch (field) { + case MXT_OBJ_T5_REPORTID: + return s->msg_fifo[s->fifo_get].reportid; + case MXT_OBJ_T5_STATUS: + return s->msg_fifo[s->fifo_get].status; + case MXT_OBJ_T5_XPOSMSH: + return s->msg_fifo[s->fifo_get].xpos_msh; + case MXT_OBJ_T5_YPOSMSH: + return s->msg_fifo[s->fifo_get].ypos_msh; + case MXT_OBJ_T5_XYPOSLSH: + return s->msg_fifo[s->fifo_get].xypos_lsh; + case MXT_OBJ_T5_AREA: + return s->msg_fifo[s->fifo_get].area; + case MXT_OBJ_T5_PRESSURE: + return s->msg_fifo[s->fifo_get].pressure; + case MXT_OBJ_T5_UNKNOWN: + return 0; + case MXT_OBJ_T5_CHECKSUM: + ret = s->msg_fifo[s->fifo_get].checksum; + s->fifo_get = (s->fifo_get + 1) & MXT_MESSAGE_FIFO_MASK; + return ret; + } + return 0; +} + +static int mxt_read_info_reg(MXTClass *k, unsigned int offset) +{ + unsigned i; + + if (offset <= MXT_INFO_END) { + return k->mxt_info[offset - MXT_INFO_START]; + } else if (offset <= MXT_OBJTBL_END(k)) { + i = (offset - MXT_OBJTBL_START) / MXT_OBJTBL_ENTRY_LEN; + switch ((offset - MXT_OBJTBL_START) % MXT_OBJTBL_ENTRY_LEN) { + case MXT_OBJTBL_TYPE: + return k->obj_tbl[i].type; + case MXT_OBJTBL_START_LSB: + return (uint8_t)k->obj_tbl[i].start_addr; + case MXT_OBJTBL_START_MSB: + return (uint8_t)(k->obj_tbl[i].start_addr >> 8); + case MXT_OBJTBL_SIZE: + return k->obj_tbl[i].size; + case MXT_OBJTBL_INSTANCES: + return k->obj_tbl[i].instances; + case MXT_OBJTBL_REPORT_IDS: + return k->obj_tbl[i].num_report_ids; + } + } else if (offset <= MXT_CRC_END(k)) { + return k->crc[offset - MXT_CRC_START(k)]; + } + + return -1; +} + +static inline void mxt_calc_x_scalecoef(MXTState *s) +{ + MXTClass *k = MXT_GET_CLASS(s); + uint16_t div, tmp; + uint8_t *t9 = &s->objects[k->t9_address - MXT_OBJECTS_START(k)]; + uint16_t x_max = t9[MXT_OBJ_T9_XRANGE_LSB] | + (t9[MXT_OBJ_T9_XRANGE_MSB] << 8); + uint16_t threshold = t9[MXT_OBJ_T9_XSIZE]; + + if (x_max == 0) { + if (t9[MXT_OBJ_T9_ORIENT] & MXT_T9_XY_SWITCH) { + s->scale_y = 0; + } else { + s->scale_x = 0; + } + return; + } + + div = MXT_QEMU_MAX_COORD / x_max + 1; + tmp = x_max * div; + /* Divide by 4 if XRANGE less then 1024 */ + if ((t9[MXT_OBJ_T9_YRANGE_MSB] & 0xC) == 0) { + div >>= 2; + div++; + tmp = x_max * (div << 2); + threshold <<= 2; + } + + if (t9[MXT_OBJ_T9_ORIENT] & MXT_T9_XY_SWITCH) { + s->scale_y = div ? ((double)tmp / MXT_QEMU_MAX_COORD) / (double)div : 0; + s->y_threshold = threshold; + } else { + s->scale_x = div ? ((double)tmp / MXT_QEMU_MAX_COORD) / (double)div : 0; + s->x_threshold = threshold; + } +} + +static inline void mxt_calc_y_scalecoef(MXTState *s) +{ + MXTClass *k = MXT_GET_CLASS(s); + uint16_t div, tmp; + uint8_t *t9 = &s->objects[k->t9_address - MXT_OBJECTS_START(k)]; + uint16_t y_max = + t9[MXT_OBJ_T9_YRANGE_LSB] | (t9[MXT_OBJ_T9_YRANGE_MSB] << 8); + uint16_t threshold = t9[MXT_OBJ_T9_YSIZE]; + + if (y_max == 0) { + if (t9[MXT_OBJ_T9_ORIENT] & MXT_T9_XY_SWITCH) { + s->scale_x = 0; + } else { + s->scale_y = 0; + } + return; + } + + div = MXT_QEMU_MAX_COORD / y_max + 1; + tmp = y_max * div; + /* Divide by 4 if YRANGE less then 1024 */ + if ((t9[MXT_OBJ_T9_YRANGE_MSB] & 0xC) == 0) { + div >>= 2; + div++; + tmp = y_max * (div << 2); + threshold <<= 2; + } + + if (t9[MXT_OBJ_T9_ORIENT] & MXT_T9_XY_SWITCH) { + s->scale_x = div ? ((double)tmp / MXT_QEMU_MAX_COORD) / (double)div : 0; + s->x_threshold = threshold; + } else { + s->scale_y = div ? ((double)tmp / MXT_QEMU_MAX_COORD) / (double)div : 0; + s->y_threshold = threshold; + } +} + +static void mxt_write_to_t9(MXTState *s, unsigned int offset, uint8_t val) +{ + MXTClass *k = MXT_GET_CLASS(s); + uint16_t addr = k->t9_address - MXT_OBJECTS_START(k) + offset; + + s->objects[addr] = val; + + switch (offset) { + case MXT_OBJ_T9_CTRL: + if ((s->objects[addr] == 0x83) && !(s->mouse)) { + s->mouse = + qemu_add_mouse_event_handler(mxt_mouse_event, s, 1, "maxtouch"); + qemu_activate_mouse_event_handler(s->mouse); + } else if (s->objects[addr] == 0 && s->mouse) { + qemu_remove_mouse_event_handler(s->mouse); + s->mouse = NULL; + } + break; + case MXT_OBJ_T9_XSIZE: + case MXT_OBJ_T9_XRANGE_LSB: case MXT_OBJ_T9_XRANGE_MSB: + mxt_calc_x_scalecoef(s); + break; + case MXT_OBJ_T9_YSIZE: + case MXT_OBJ_T9_YRANGE_LSB: case MXT_OBJ_T9_YRANGE_MSB: + mxt_calc_y_scalecoef(s); + break; + case MXT_OBJ_T9_ORIENT: + mxt_calc_x_scalecoef(s); + mxt_calc_y_scalecoef(s); + break; + } +} + +/* Atmel maXTouch i2c registers read byte sequence: + * <Start bit> + * [MXT i2c address(0x4A) with last bit 0(write data)] + * [LSB of MXT register offset (starting from 0)] + * [MSB of MXT register offset] + * <Stop bit> + * <Start bit> + * [MXT address(0x4A) with last bit 1(read data)] + * [MXT sends 0x0] + * [MXT sends value of register offset] + * [MXT sends value of register offset+1] + * [MXT sends value of register offset+2] + * [...........] + * <Stop bit> + * + * Atmel maXTouch i2c registers write byte sequence: + * <Start bit> + * [MXT address(0x4A) with last bit 0(write data)] + * [LSB of MXT register offset (starting from 0)] + * [MSB of MXT register offset] + * [value to write into register with specified offset] + * [value to write into register with specified offset+1] + * [value to write into register with specified offset+2] + * [...........] + * <Stop bit> + */ + +static int mxt_i2c_read(I2CSlave *i2c) +{ + MXTState *s = MXT(i2c); + MXTClass *k = MXT_GET_CLASS(s); + int ret = -1; + + if (s->i2c_state != READING_DATA) { + ERRPRINT("data read request in wrong state!\n"); + return ret; + } + + if ((s->selected_reg >= k->t5_address) && (s->selected_reg < + (k->t5_address + mxt_obj_sizes[MXT_GEN_MESSAGE_T5]))) { + ret = mxt_read_message_field(s, s->selected_reg - k->t5_address); + } else if (s->selected_reg <= MXT_CRC_END(k)) { + ret = mxt_read_info_reg(k, s->selected_reg); + } else if (s->selected_reg <= k->end_addr) { + ret = s->objects[s->selected_reg - MXT_OBJECTS_START(k)]; + } else { + ERRPRINT("register with address 0x%04x doesn't exist\n", + s->selected_reg); + } + + DPRINT("Sending %s(0x%02x) -> 0x%02x\n", + mxt_get_reg_name(s, s->selected_reg), s->selected_reg, ret); + s->selected_reg++; + + return ret; +} + +static int mxt_i2c_write(I2CSlave *i2c, uint8_t data) +{ + MXTState *s = MXT(i2c); + MXTClass *k = MXT_GET_CLASS(s); + + switch (s->i2c_state) { + case STARTING_WRITE: + s->selected_reg = (s->selected_reg & 0xFF00) | data; + s->i2c_state = SELECTING_REGISTER; + break; + case SELECTING_REGISTER: + s->selected_reg = (s->selected_reg & 0x00FF) | (data << 8); + DPRINT("Selected register 0x%04x\n", s->selected_reg); + s->i2c_state = WRITING_DATA; + break; + case WRITING_DATA: + DPRINT("Writing %s <- 0x%02x\n", + mxt_get_reg_name(s, s->selected_reg), data); + if ((s->selected_reg >= k->t9_address) && (s->selected_reg < + k->t9_address + mxt_obj_sizes[MXT_TOUCH_MULTI_T9])) { + mxt_write_to_t9(s, s->selected_reg - k->t9_address, data); + } else if ((s->selected_reg >= MXT_OBJECTS_START(k)) && + (s->selected_reg <= k->end_addr)) { + s->objects[s->selected_reg - MXT_OBJECTS_START(k)] = data; + } else { + ERRPRINT("can't write to register with address 0x%04x\n", + s->selected_reg); + return -1; + } + s->selected_reg++; + break; + default: + ERRPRINT("data write request in wrong state!\n"); + return -1; + } + + return 0; +} + +static void mxt_i2c_event(I2CSlave *i2c, enum i2c_event event) +{ + MXTState *s = MXT(i2c); + + switch (event) { + case I2C_START_RECV: + DPRINT("I2C start bit appeared: reading data\n"); + s->i2c_state = READING_DATA; + break; + case I2C_START_SEND: + DPRINT("I2C start bit appeared: writing data\n"); + s->i2c_state = STARTING_WRITE; + break; + case I2C_FINISH: + DPRINT("I2C stop bit received\n"); + s->i2c_state = IDLE; + break; + default: + break; + } +} + +static void mxt_initfn(Object *obj) +{ + MXTState *s = MXT(obj); + MXTClass *k = MXT_GET_CLASS(obj); + unsigned i; + uint16_t objects_len = 0; + + for (i = 0; i < k->mxt_info[MXT_OBJ_NUM]; i++) { + objects_len += (k->obj_tbl[i].size + 1) * (k->obj_tbl[i].instances + 1); + } + s->objects = g_malloc0(objects_len); + s->obj_len = objects_len; + + s->i2c_state = IDLE; + s->selected_reg = 0; + s->fifo_add = s->fifo_get = 0; + s->fifo_lock = false; + s->scale_x = 0; + s->scale_y = 0; + s->x_threshold = 0; + s->y_threshold = 0; + s->x_curr = 0; + s->y_curr = 0; + s->touches = 0; + s->mouse = NULL; + + qdev_init_gpio_out(DEVICE(obj), &s->nCHG, 1); + qemu_irq_raise(s->nCHG); +} + +static void mxt_uninitfn(Object *obj) +{ + MXTState *s = MXT(obj); + + if (s->mouse) { + qemu_remove_mouse_event_handler(s->mouse); + s->mouse = NULL; + } + + g_free(s->objects); +} + +static int mxt_i2c_init(I2CSlave *i2c) +{ + return 0; +} + +static inline uint32_t mxt_crc24(uint32_t crc, uint8_t byte1, uint8_t byte2) +{ + uint32_t ret = (crc << 1) ^ ((byte2 << 8) | byte1); + + if (ret & 0x1000000) { + ret ^= MXT_CRC_POLY; + } + + return ret; +} + +static void mxt_calculate_crc(MXTClass *k) +{ + unsigned i; + uint32_t crc = 0; + + for (i = 0; i < MXT_OBJTBL_END(k); i += 2) { + crc = + mxt_crc24(crc, mxt_read_info_reg(k, i), mxt_read_info_reg(k, i + 1)); + } + + crc = mxt_crc24(crc, mxt_read_info_reg(k, i), 0) & 0x00FFFFFF; + k->crc[0] = crc & 0xFF; + k->crc[1] = (crc >> 8) & 0xFF; + k->crc[2] = (crc >> 16) & 0xFF; +} + +static void mxt_init_object_table(MXTClass *k, const ObjConfig *list) +{ + MXTObjTblEntry *tbl = k->obj_tbl; + unsigned i, tbl_len = k->mxt_info[MXT_OBJ_NUM]; + + for (i = 0; i < tbl_len; i++) { + tbl[i].type = mxt_obj_types_list[list[i].type]; + tbl[i].size = mxt_obj_sizes[list[i].type] - 1; + tbl[i].instances = list[i].instances; + tbl[i].num_report_ids = + (list[i].type == MXT_TOUCH_MULTI_T9) ? MXT_NUM_OF_TOUCHES : 0; + if (i == 0) { + tbl[i].start_addr = MXT_OBJTBL_START + tbl_len * + MXT_OBJTBL_ENTRY_LEN + MXT_CRC_SIZE; + } else { + tbl[i].start_addr = tbl[i-1].start_addr + + (tbl[i-1].size + 1) * (tbl[i-1].instances + 1); + } + if (list[i].type == MXT_GEN_MESSAGE_T5) { + k->t5_address = tbl[i].start_addr; + } else if (list[i].type == MXT_TOUCH_MULTI_T9) { + k->t9_address = tbl[i].start_addr; + } + } + + k->end_addr = tbl[i-1].start_addr + tbl[i-1].size; + /* T5 and T9 objects are mandatory */ + assert(k->t5_address); + assert(k->t9_address); +} + +static int mxt_post_load(void *opaque, int ver_id) +{ + MXTState *s = (MXTState *)opaque; + MXTClass *k = MXT_GET_CLASS(s); + uint16_t addr = k->t9_address - MXT_OBJECTS_START(k) + MXT_OBJ_T9_CTRL; + + if (s->fifo_get == s->fifo_add) { + s->fifo_lock = true; + } + mxt_calc_x_scalecoef(s); + mxt_calc_y_scalecoef(s); + + if ((s->objects[addr] == 0x83) && !(s->mouse)) { + s->mouse = + qemu_add_mouse_event_handler(mxt_mouse_event, s, 1, "maxtouch"); + qemu_activate_mouse_event_handler(s->mouse); + } + + return 0; +} + +static const VMStateDescription mxt_vmstate = { + .name = "mxt", + .version_id = 1, + .minimum_version_id = 1, + .post_load = mxt_post_load, + .fields = (VMStateField[]) { + VMSTATE_VBUFFER_UINT32(objects, MXTState, 1, NULL, 0, obj_len), + VMSTATE_UINT8(i2c_state, MXTState), + VMSTATE_STRUCT_ARRAY(msg_fifo, MXTState, MXT_MESSAGE_FIFO_LEN, 1, + mxt_message_vmstate, MXTMessage), + VMSTATE_UINT8(fifo_get, MXTState), + VMSTATE_UINT8(fifo_add, MXTState), + VMSTATE_UINT16(selected_reg, MXTState), + VMSTATE_UINT16(x_curr, MXTState), + VMSTATE_UINT16(y_curr, MXTState), + VMSTATE_UINT8(touches, MXTState), + VMSTATE_END_OF_LIST() + } +}; + +static void maxtouch_class_init(ObjectClass *klass, void *data) +{ + I2CSlaveClass *i2c = I2C_SLAVE_CLASS(klass); + MXTClass *k = MXT_CLASS(klass); + const MXTVariantInfo *info = (const MXTVariantInfo *)data; + + DEVICE_CLASS(klass)->vmsd = &mxt_vmstate; + i2c->init = mxt_i2c_init; + i2c->event = mxt_i2c_event; + i2c->recv = mxt_i2c_read; + i2c->send = mxt_i2c_write; + + k->mxt_info = info->mxt_variant_info; + k->obj_tbl = g_new0(MXTObjTblEntry, k->mxt_info[MXT_OBJ_NUM]); + mxt_init_object_table(k, info->mxt_variant_obj_list); + mxt_calculate_crc(k); +} + +static void maxtouch_class_finalize(ObjectClass *klass, void *data) +{ + MXTClass *k = MXT_CLASS(klass); + + g_free(k->obj_tbl); +} + +static void mxt_register_type(const MXTVariantInfo *info) +{ + TypeInfo type = { + .name = info->name, + .parent = TYPE_MAXTOUCH, + .class_init = maxtouch_class_init, + .class_finalize = maxtouch_class_finalize, + .class_data = (void *)info + }; + + type_register(&type); +} + +static const TypeInfo maxtouch_type_info = { + .name = TYPE_MAXTOUCH, + .parent = TYPE_I2C_SLAVE, + .instance_size = sizeof(MXTState), + .instance_init = mxt_initfn, + .instance_finalize = mxt_uninitfn, + .abstract = true, + .class_size = sizeof(MXTClass), +}; + +static void mxt_register_types(void) +{ + unsigned i; + + type_register_static(&maxtouch_type_info); + for (i = 0; i < MXT_NUM_OF_VARIANTS; i++) { + mxt_register_type(&mxt_variants_info_array[i]); + } +} + +type_init(mxt_register_types) -- 1.7.4.1