Signed-off-by: Evgeny Ermakov <evgeny.v.erma...@gmail.com> --- include/hw/input/ft5336.h | 14 ++ hw/input/ft5336.c | 357 ++++++++++++++++++++++++++++++++++++++ hw/input/Kconfig | 4 + hw/input/meson.build | 2 + 4 files changed, 377 insertions(+) create mode 100644 include/hw/input/ft5336.h create mode 100644 hw/input/ft5336.c
diff --git a/include/hw/input/ft5336.h b/include/hw/input/ft5336.h new file mode 100644 index 0000000000..7bef3f9efb --- /dev/null +++ b/include/hw/input/ft5336.h @@ -0,0 +1,14 @@ +/* + * FT5336 touch controller + * + * Copyright (c) 2022 Evgeny Ermakov <evgeny.v.erma...@gmail.com> + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef HW_INPUT_FT5336_H +#define HW_INPUT_FT5336_H + +#define TYPE_FT5336 "ft5336" + +#endif diff --git a/hw/input/ft5336.c b/hw/input/ft5336.c new file mode 100644 index 0000000000..bacf79201a --- /dev/null +++ b/hw/input/ft5336.c @@ -0,0 +1,357 @@ +/* + * FT5336 touch controller + * + * Copyright (c) 2022 Evgeny Ermakov <evgeny.v.erma...@gmail.com> + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "hw/input/ft5336.h" +#include "hw/i2c/i2c.h" +#include "hw/irq.h" +#include "migration/vmstate.h" +#include "qemu/module.h" +#include "qemu/log.h" +#include "qemu/timer.h" +#include "ui/input.h" +#include "qom/object.h" + +OBJECT_DECLARE_SIMPLE_TYPE(FT5336TouchState, FT5336) + +struct FT5336TouchState { + I2CSlave parent_obj; + + uint8_t i2c_cycle; + uint8_t reg; + + qemu_irq irq; + + int32_t abs_x; + int32_t abs_y; + uint16_t touch_x; + uint16_t touch_y; + bool touch_press; + + bool inte; +}; + +/* I2C Slave address of touchscreen FocalTech FT5336 */ +#define FT5336_I2C_SLAVE_ADDRESS 0x70 + +/* Maximum border values of the touchscreen pad */ +#define FT5336_MAX_WIDTH ((uint16_t)480) /* Touchscreen pad max width */ +#define FT5336_MAX_HEIGHT ((uint16_t)272) /* Touchscreen pad max height */ + +/* Max detectable simultaneous touches */ +#define FT5336_MAX_DETECTABLE_TOUCH 0x05 + + +enum { + FT5336_P_XH = 0x00, + FT5336_P_XL = 0x01, + FT5336_P_YH = 0x02, + FT5336_P_YL = 0x03, + /* Values Pn_XH and Pn_YH related */ +#define FT5336_TOUCH_EVT_FLAG_PRESS_DOWN 0x00 +#define FT5336_TOUCH_EVT_FLAG_LIFT_UP 0x01 +#define FT5336_TOUCH_EVT_FLAG_CONTACT 0x02 +#define FT5336_TOUCH_EVT_FLAG_NO_EVENT 0x03 + + FT5336_P_WEIGHT = 0x04, + /* Values Pn_WEIGHT related */ +#define FT5336_TOUCH_WEIGHT_MASK 0xFF +#define FT5336_TOUCH_WEIGHT_SHIFT 0x00 + + FT5336_P_MISC = 0x05 + /* Values related to FT5336_Pn_MISC_REG */ +#define FT5336_TOUCH_AREA_MASK (0x04 << 4)) +#define FT5336_TOUCH_AREA_SHIFT 0x04 +}; + +enum { + FT5336_R_MODE = 0x00, +#define FT5336_DEV_MODE_WORKING 0x00 +#define FT5336_DEV_MODE_FACTORY 0x04 + +#define FT5336_DEV_MODE_MASK 0x07 +#define FT5336_DEV_MODE_SHIFT 0x04 + + FT5336_R_GEST_ID = 0x01, +#define FT5336_GEST_ID_NO_GESTURE 0x00 +#define FT5336_GEST_ID_MOVE_UP 0x10 +#define FT5336_GEST_ID_MOVE_RIGHT 0x14 +#define FT5336_GEST_ID_MOVE_DOWN 0x18 +#define FT5336_GEST_ID_MOVE_LEFT 0x1C +#define FT5336_GEST_ID_SINGLE_CLICK 0x20 +#define FT5336_GEST_ID_DOUBLE_CLICK 0x22 +#define FT5336_GEST_ID_ROTATE_CLOCKWISE 0x28 +#define FT5336_GEST_ID_ROTATE_C_CLOCKWISE 0x29 +#define FT5336_GEST_ID_ZOOM_IN 0x40 +#define FT5336_GEST_ID_ZOOM_OUT 0x49 + + FT5336_R_STAT = 0x02, +#define FT5336_TD_STAT_MASK 0x0F +#define FT5336_TD_STAT_SHIFT 0x00 + + FT5336_R_P1_BASE = 0x03, + FT5336_R_P2_BASE = 0x09, + FT5336_R_P3_BASE = 0x0f, + FT5336_R_P4_BASE = 0x15, + FT5336_R_P5_BASE = 0x1b, + FT5336_R_P6_BASE = 0x21, + FT5336_R_P7_BASE = 0x27, + FT5336_R_P8_BASE = 0x2d, + FT5336_R_P9_BASE = 0x33, + FT5336_R_P10_BASE = 0x39, + +#define FT5336_TOUCH_EVT_FLAG_SHIFT 0x06 +#define FT5336_TOUCH_EVT_FLAG_MASK (3 << FT5336_TOUCH_EVT_FLAG_SHIFT)) + +#define FT5336_TOUCH_POS_MSB_MASK 0x0F +#define FT5336_TOUCH_POS_MSB_SHIFT 0x00 + + /* Values Pn_XL and Pn_YL related */ +#define FT5336_TOUCH_POS_LSB_MASK 0xFF +#define FT5336_TOUCH_POS_LSB_SHIFT 0x00 + + FT5336_R_TH_GROUP = 0x80, + /* Values FT5336_TH_GROUP_REG : threshold related */ +#define FT5336_THRESHOLD_MASK 0xFF +#define FT5336_THRESHOLD_SHIFT 0x00 + + FT5336_R_TH_DIFF = 0x85, + + FT5336_R_CTRL = 0x86, + /* Values related to FT5336_CTRL_REG */ + + /* Will keep the Active mode when there is no touching */ +#define FT5336_CTRL_KEEP_ACTIVE_MODE 0x00 + + /* Switching from Active mode to Monitor mode automatically when there is no touching */ +#define FT5336_CTRL_KEEP_AUTO_SWITCH_MONITOR_MODE 0x01 + FT5336_R_TIMEENTERMONITOR = 0x87, + FT5336_R_PERIODACTIVE = 0x88, + FT5336_R_PERIODMONITOR = 0x89, + FT5336_R_RADIAN_VALUE = 0x91, + FT5336_R_OFFSET_LEFT_RIGHT = 0x92, + FT5336_R_OFFSET_UP_DOWN = 0x93, + FT5336_R_DISTANCE_LEFT = 0x94, + FT5336_R_DISTANCE_UP_DOWN = 0x95, + FT5336_R_DISTANCE_ZOOM = 0x96, + + FT5336_R_LIB_VER_H = 0xa1, + FT5336_R_LIB_VER_L = 0xa2, + FT5336_R_CIPHER = 0xa3, + + FT5336_R_GMODE = 0xa4, +#define FT5336_G_MODE_INTERRUPT_MASK 0x03 +#define FT5336_G_MODE_INTERRUPT_SHIFT 0x00 + + /* Possible values of FT5336_GMODE_REG */ +#define FT5336_G_MODE_INTERRUPT_POLLING 0x00 +#define FT5336_G_MODE_INTERRUPT_TRIGGER 0x01 + + FT5336_R_PWR_MODE = 0xa5, + FT5336_R_FIRMID = 0xa6, + + FT5336_R_CHIP_ID = 0xa8, + /* Possible values of FT5336_CHIP_ID_REG */ +#define FT5336_ID_VALUE 0x51 + + FT5336_R_RELEASE_CODE_ID = 0xaf, + /* Release code version */ +#define FT5336_RELEASE_CODE_ID_REG 0xAF + + FT5336_R_STATE = 0xbc, +}; + + +static uint8_t ft5336_touch_read(FT5336TouchState *s, int reg, int byte) +{ + switch (reg) { + case FT5336_R_CHIP_ID: + return FT5336_ID_VALUE; + case FT5336_R_STAT: + return s->touch_press ? 1 : 0; + case FT5336_R_P1_BASE: + switch (byte) { + case FT5336_P_XH: return extract16(s->touch_x, 8, 8); + case FT5336_P_XL: return extract16(s->touch_x, 0, 8); + case FT5336_P_YH: return extract16(s->touch_y, 8, 8); + case FT5336_P_YL: return extract16(s->touch_y, 0, 8); + default: + return 0; + } + break; + default: + qemu_log_mask(LOG_GUEST_ERROR, + "%s: unknown register %02x\n", __func__, reg); + return 0; + } +} + +static void ft5336_touch_write(FT5336TouchState *s, int reg, int byte, uint8_t value) +{ + switch (reg) { + case FT5336_R_GMODE: + s->inte = value; + break; + default: + qemu_log_mask(LOG_GUEST_ERROR, + "%s: unknown register %02x\n", __func__, reg); + break; + } +} + +static int ft5336_i2c_event(I2CSlave *i2c, enum i2c_event event) +{ + FT5336TouchState *s = FT5336(i2c); + + switch (event) { + case I2C_START_RECV: + case I2C_START_SEND: + s->i2c_cycle = 0; + break; + default: + break; + } + + return 0; +} + +static uint8_t ft5336_i2c_recv(I2CSlave *i2c) +{ + FT5336TouchState *s = FT5336(i2c); + + return ft5336_touch_read(s, s->reg, s->i2c_cycle++); +} + +static int ft5336_i2c_send(I2CSlave *i2c, uint8_t data) +{ + FT5336TouchState *s = FT5336(i2c); + + if (!s->i2c_cycle) { + s->reg = data; + } else { + ft5336_touch_write(s, s->reg, s->i2c_cycle - 1, data); + } + s->i2c_cycle++; + + return 0; +} + +static void ft5336_input_event(DeviceState *dev, QemuConsole *src, + InputEvent *evt) +{ + FT5336TouchState *s = FT5336(dev); + InputBtnEvent *btn = NULL; + InputMoveEvent *move = NULL; + + switch (evt->type) { + case INPUT_EVENT_KIND_BTN: + btn = evt->u.btn.data; + s->touch_press = btn->down; + break; + case INPUT_EVENT_KIND_ABS: + move = evt->u.rel.data; + if (move->axis == INPUT_AXIS_X) { + s->abs_x = move->value; + } else if (move->axis == INPUT_AXIS_Y) { + s->abs_y = move->value; + } + break; + default: + break; + } +} + +static void ft5336_input_sync(DeviceState *dev) +{ + FT5336TouchState *s = FT5336(dev);; + + s->touch_x = qemu_input_scale_axis(s->abs_x, + INPUT_EVENT_ABS_MIN, + INPUT_EVENT_ABS_MAX, 0, 1777); + s->touch_y = qemu_input_scale_axis(s->abs_y, + INPUT_EVENT_ABS_MIN, + INPUT_EVENT_ABS_MAX, 0, 1023); + + if (s->touch_press) { + if (s->inte) { + qemu_irq_pulse(s->irq); + } + } +} + +static QemuInputHandler ft5336_input_handler = { + .name = "QEMU FT5336-driven Touchscreen", + .mask = INPUT_EVENT_MASK_BTN | INPUT_EVENT_MASK_ABS, + .event = ft5336_input_event, + .sync = ft5336_input_sync +}; + +static void ft5336_touch_reset_enter(Object *obj, ResetType type) +{ + FT5336TouchState *s = FT5336(obj); + + s->inte = false; +} + +static void ft5336_realize(DeviceState *dev, Error **errp) +{ + FT5336TouchState *s = FT5336(dev); + qdev_init_gpio_out(dev, &s->irq, 1); + + qemu_input_handler_register((DeviceState *) s, &ft5336_input_handler); +} + +static int ft5336_touch_post_load(void *opaque, int version_id) +{ + /* FT5336TouchState *s = opaque; */ + + return 0; +} + +static const VMStateDescription vmstate_ft5336_touch = { + .name = TYPE_FT5336, + .version_id = 0, + .minimum_version_id = 0, + .post_load = ft5336_touch_post_load, + .fields = (VMStateField[]) { + VMSTATE_I2C_SLAVE(parent_obj, FT5336TouchState), + VMSTATE_UINT8(i2c_cycle, FT5336TouchState), + VMSTATE_UINT8(reg, FT5336TouchState), + VMSTATE_END_OF_LIST() + } +}; + +static void ft5336_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + I2CSlaveClass *sc = I2C_SLAVE_CLASS(klass); + ResettableClass *rc = RESETTABLE_CLASS(klass); + + dc->realize = ft5336_realize; + dc->vmsd = &vmstate_ft5336_touch; + set_bit(DEVICE_CATEGORY_INPUT, dc->categories); + rc->phases.enter = ft5336_touch_reset_enter; + + sc->event = ft5336_i2c_event; + sc->recv = ft5336_i2c_recv; + sc->send = ft5336_i2c_send; +} + +static const TypeInfo ft5336_info = { + .name = TYPE_FT5336, + .parent = TYPE_I2C_SLAVE, + .instance_size = sizeof(FT5336TouchState), + .class_init = ft5336_class_init +}; + +static void ft5336_register_types(void) +{ + type_register_static(&ft5336_info); +} + +type_init(ft5336_register_types) diff --git a/hw/input/Kconfig b/hw/input/Kconfig index 55865bb386..73a6f67216 100644 --- a/hw/input/Kconfig +++ b/hw/input/Kconfig @@ -46,3 +46,7 @@ config TSC210X config LASIPS2 select PS2 + +config FT5336 + bool + depends on I2C diff --git a/hw/input/meson.build b/hw/input/meson.build index 8deb011d4a..c892ecbeb7 100644 --- a/hw/input/meson.build +++ b/hw/input/meson.build @@ -16,3 +16,5 @@ softmmu_ss.add(when: 'CONFIG_VHOST_USER_INPUT', if_true: files('vhost-user-input softmmu_ss.add(when: 'CONFIG_PXA2XX', if_true: files('pxa2xx_keypad.c')) softmmu_ss.add(when: 'CONFIG_TSC210X', if_true: files('tsc210x.c')) softmmu_ss.add(when: 'CONFIG_LASIPS2', if_true: files('lasips2.c')) + +softmmu_ss.add(when: 'CONFIG_FT5336', if_true: files('ft5336.c')) -- 2.38.1