new file mode 100644
@@ -0,0 +1,14 @@
+/*
+ * FT5336 touch controller
+ *
+ * Copyright (c) 2022 Evgeny Ermakov <evgeny.v.ermakov@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
new file mode 100644
@@ -0,0 +1,357 @@
+/*
+ * FT5336 touch controller
+ *
+ * Copyright (c) 2022 Evgeny Ermakov <evgeny.v.ermakov@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)
@@ -46,3 +46,7 @@ config TSC210X
config LASIPS2
select PS2
+
+config FT5336
+ bool
+ depends on I2C
@@ -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'))
Signed-off-by: Evgeny Ermakov <evgeny.v.ermakov@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