summaryrefslogtreecommitdiffstats
path: root/src/hw/input
diff options
context:
space:
mode:
Diffstat (limited to 'src/hw/input')
-rw-r--r--src/hw/input/Makefile.objs19
-rw-r--r--src/hw/input/adb.c595
-rw-r--r--src/hw/input/hid.c611
-rw-r--r--src/hw/input/lm832x.c524
-rw-r--r--src/hw/input/milkymist-softusb.c317
-rw-r--r--src/hw/input/pckbd.c594
-rw-r--r--src/hw/input/pl050.c205
-rw-r--r--src/hw/input/ps2.c807
-rw-r--r--src/hw/input/pxa2xx_keypad.c334
-rw-r--r--src/hw/input/stellaris_input.c87
-rw-r--r--src/hw/input/tsc2005.c593
-rw-r--r--src/hw/input/tsc210x.c1271
-rw-r--r--src/hw/input/virtio-input-hid.c515
-rw-r--r--src/hw/input/virtio-input-host.c189
-rw-r--r--src/hw/input/virtio-input.c293
-rw-r--r--src/hw/input/vmmouse.c303
16 files changed, 7257 insertions, 0 deletions
diff --git a/src/hw/input/Makefile.objs b/src/hw/input/Makefile.objs
new file mode 100644
index 0000000..7715d72
--- /dev/null
+++ b/src/hw/input/Makefile.objs
@@ -0,0 +1,19 @@
+common-obj-$(CONFIG_ADB) += adb.o
+common-obj-y += hid.o
+common-obj-$(CONFIG_LM832X) += lm832x.o
+common-obj-$(CONFIG_PCKBD) += pckbd.o
+common-obj-$(CONFIG_PL050) += pl050.o
+common-obj-y += ps2.o
+common-obj-$(CONFIG_STELLARIS_INPUT) += stellaris_input.o
+common-obj-$(CONFIG_TSC2005) += tsc2005.o
+common-obj-$(CONFIG_VMMOUSE) += vmmouse.o
+
+common-obj-$(CONFIG_VIRTIO) += virtio-input.o
+common-obj-$(CONFIG_VIRTIO) += virtio-input-hid.o
+ifeq ($(CONFIG_LINUX),y)
+common-obj-$(CONFIG_VIRTIO) += virtio-input-host.o
+endif
+
+obj-$(CONFIG_MILKYMIST) += milkymist-softusb.o
+obj-$(CONFIG_PXA2XX) += pxa2xx_keypad.o
+obj-$(CONFIG_TSC210X) += tsc210x.o
diff --git a/src/hw/input/adb.c b/src/hw/input/adb.c
new file mode 100644
index 0000000..09eead9
--- /dev/null
+++ b/src/hw/input/adb.c
@@ -0,0 +1,595 @@
+/*
+ * QEMU ADB support
+ *
+ * Copyright (c) 2004 Fabrice Bellard
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+#include "hw/hw.h"
+#include "hw/input/adb.h"
+#include "ui/console.h"
+
+/* debug ADB */
+//#define DEBUG_ADB
+
+#ifdef DEBUG_ADB
+#define ADB_DPRINTF(fmt, ...) \
+do { printf("ADB: " fmt , ## __VA_ARGS__); } while (0)
+#else
+#define ADB_DPRINTF(fmt, ...)
+#endif
+
+/* ADB commands */
+#define ADB_BUSRESET 0x00
+#define ADB_FLUSH 0x01
+#define ADB_WRITEREG 0x08
+#define ADB_READREG 0x0c
+
+/* ADB device commands */
+#define ADB_CMD_SELF_TEST 0xff
+#define ADB_CMD_CHANGE_ID 0xfe
+#define ADB_CMD_CHANGE_ID_AND_ACT 0xfd
+#define ADB_CMD_CHANGE_ID_AND_ENABLE 0x00
+
+/* ADB default device IDs (upper 4 bits of ADB command byte) */
+#define ADB_DEVID_DONGLE 1
+#define ADB_DEVID_KEYBOARD 2
+#define ADB_DEVID_MOUSE 3
+#define ADB_DEVID_TABLET 4
+#define ADB_DEVID_MODEM 5
+#define ADB_DEVID_MISC 7
+
+/* error codes */
+#define ADB_RET_NOTPRESENT (-2)
+
+static void adb_device_reset(ADBDevice *d)
+{
+ qdev_reset_all(DEVICE(d));
+}
+
+int adb_request(ADBBusState *s, uint8_t *obuf, const uint8_t *buf, int len)
+{
+ ADBDevice *d;
+ int devaddr, cmd, i;
+
+ cmd = buf[0] & 0xf;
+ if (cmd == ADB_BUSRESET) {
+ for(i = 0; i < s->nb_devices; i++) {
+ d = s->devices[i];
+ adb_device_reset(d);
+ }
+ return 0;
+ }
+ devaddr = buf[0] >> 4;
+ for(i = 0; i < s->nb_devices; i++) {
+ d = s->devices[i];
+ if (d->devaddr == devaddr) {
+ ADBDeviceClass *adc = ADB_DEVICE_GET_CLASS(d);
+ return adc->devreq(d, obuf, buf, len);
+ }
+ }
+ return ADB_RET_NOTPRESENT;
+}
+
+/* XXX: move that to cuda ? */
+int adb_poll(ADBBusState *s, uint8_t *obuf)
+{
+ ADBDevice *d;
+ int olen, i;
+ uint8_t buf[1];
+
+ olen = 0;
+ for(i = 0; i < s->nb_devices; i++) {
+ if (s->poll_index >= s->nb_devices)
+ s->poll_index = 0;
+ d = s->devices[s->poll_index];
+ buf[0] = ADB_READREG | (d->devaddr << 4);
+ olen = adb_request(s, obuf + 1, buf, 1);
+ /* if there is data, we poll again the same device */
+ if (olen > 0) {
+ obuf[0] = buf[0];
+ olen++;
+ break;
+ }
+ s->poll_index++;
+ }
+ return olen;
+}
+
+static const TypeInfo adb_bus_type_info = {
+ .name = TYPE_ADB_BUS,
+ .parent = TYPE_BUS,
+ .instance_size = sizeof(ADBBusState),
+};
+
+static const VMStateDescription vmstate_adb_device = {
+ .name = "adb_device",
+ .version_id = 0,
+ .minimum_version_id = 0,
+ .fields = (VMStateField[]) {
+ VMSTATE_INT32(devaddr, ADBDevice),
+ VMSTATE_INT32(handler, ADBDevice),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void adb_device_realizefn(DeviceState *dev, Error **errp)
+{
+ ADBDevice *d = ADB_DEVICE(dev);
+ ADBBusState *bus = ADB_BUS(qdev_get_parent_bus(dev));
+
+ if (bus->nb_devices >= MAX_ADB_DEVICES) {
+ return;
+ }
+
+ bus->devices[bus->nb_devices++] = d;
+}
+
+static void adb_device_class_init(ObjectClass *oc, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(oc);
+
+ dc->realize = adb_device_realizefn;
+ dc->bus_type = TYPE_ADB_BUS;
+}
+
+static const TypeInfo adb_device_type_info = {
+ .name = TYPE_ADB_DEVICE,
+ .parent = TYPE_DEVICE,
+ .instance_size = sizeof(ADBDevice),
+ .abstract = true,
+ .class_init = adb_device_class_init,
+};
+
+/***************************************************************/
+/* Keyboard ADB device */
+
+#define ADB_KEYBOARD(obj) OBJECT_CHECK(KBDState, (obj), TYPE_ADB_KEYBOARD)
+
+typedef struct KBDState {
+ /*< private >*/
+ ADBDevice parent_obj;
+ /*< public >*/
+
+ uint8_t data[128];
+ int rptr, wptr, count;
+} KBDState;
+
+#define ADB_KEYBOARD_CLASS(class) \
+ OBJECT_CLASS_CHECK(ADBKeyboardClass, (class), TYPE_ADB_KEYBOARD)
+#define ADB_KEYBOARD_GET_CLASS(obj) \
+ OBJECT_GET_CLASS(ADBKeyboardClass, (obj), TYPE_ADB_KEYBOARD)
+
+typedef struct ADBKeyboardClass {
+ /*< private >*/
+ ADBDeviceClass parent_class;
+ /*< public >*/
+
+ DeviceRealize parent_realize;
+} ADBKeyboardClass;
+
+static const uint8_t pc_to_adb_keycode[256] = {
+ 0, 53, 18, 19, 20, 21, 23, 22, 26, 28, 25, 29, 27, 24, 51, 48,
+ 12, 13, 14, 15, 17, 16, 32, 34, 31, 35, 33, 30, 36, 54, 0, 1,
+ 2, 3, 5, 4, 38, 40, 37, 41, 39, 50, 56, 42, 6, 7, 8, 9,
+ 11, 45, 46, 43, 47, 44,123, 67, 58, 49, 57,122,120, 99,118, 96,
+ 97, 98,100,101,109, 71,107, 89, 91, 92, 78, 86, 87, 88, 69, 83,
+ 84, 85, 82, 65, 0, 0, 10,103,111, 0, 0,110, 81, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 94, 0, 93, 0, 0, 0, 0, 0, 0,104,102, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 76,125, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,105, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 75, 0, 0,124, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0,115, 62,116, 0, 59, 0, 60, 0,119,
+ 61,121,114,117, 0, 0, 0, 0, 0, 0, 0, 55,126, 0,127, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 95, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+};
+
+static void adb_kbd_put_keycode(void *opaque, int keycode)
+{
+ KBDState *s = opaque;
+
+ if (s->count < sizeof(s->data)) {
+ s->data[s->wptr] = keycode;
+ if (++s->wptr == sizeof(s->data))
+ s->wptr = 0;
+ s->count++;
+ }
+}
+
+static int adb_kbd_poll(ADBDevice *d, uint8_t *obuf)
+{
+ static int ext_keycode;
+ KBDState *s = ADB_KEYBOARD(d);
+ int adb_keycode, keycode;
+ int olen;
+
+ olen = 0;
+ for(;;) {
+ if (s->count == 0)
+ break;
+ keycode = s->data[s->rptr];
+ if (++s->rptr == sizeof(s->data))
+ s->rptr = 0;
+ s->count--;
+
+ if (keycode == 0xe0) {
+ ext_keycode = 1;
+ } else {
+ if (ext_keycode)
+ adb_keycode = pc_to_adb_keycode[keycode | 0x80];
+ else
+ adb_keycode = pc_to_adb_keycode[keycode & 0x7f];
+ obuf[0] = adb_keycode | (keycode & 0x80);
+ /* NOTE: could put a second keycode if needed */
+ obuf[1] = 0xff;
+ olen = 2;
+ ext_keycode = 0;
+ break;
+ }
+ }
+ return olen;
+}
+
+static int adb_kbd_request(ADBDevice *d, uint8_t *obuf,
+ const uint8_t *buf, int len)
+{
+ KBDState *s = ADB_KEYBOARD(d);
+ int cmd, reg, olen;
+
+ if ((buf[0] & 0x0f) == ADB_FLUSH) {
+ /* flush keyboard fifo */
+ s->wptr = s->rptr = s->count = 0;
+ return 0;
+ }
+
+ cmd = buf[0] & 0xc;
+ reg = buf[0] & 0x3;
+ olen = 0;
+ switch(cmd) {
+ case ADB_WRITEREG:
+ switch(reg) {
+ case 2:
+ /* LED status */
+ break;
+ case 3:
+ switch(buf[2]) {
+ case ADB_CMD_SELF_TEST:
+ break;
+ case ADB_CMD_CHANGE_ID:
+ case ADB_CMD_CHANGE_ID_AND_ACT:
+ case ADB_CMD_CHANGE_ID_AND_ENABLE:
+ d->devaddr = buf[1] & 0xf;
+ break;
+ default:
+ /* XXX: check this */
+ d->devaddr = buf[1] & 0xf;
+ d->handler = buf[2];
+ break;
+ }
+ }
+ break;
+ case ADB_READREG:
+ switch(reg) {
+ case 0:
+ olen = adb_kbd_poll(d, obuf);
+ break;
+ case 1:
+ break;
+ case 2:
+ obuf[0] = 0x00; /* XXX: check this */
+ obuf[1] = 0x07; /* led status */
+ olen = 2;
+ break;
+ case 3:
+ obuf[0] = d->handler;
+ obuf[1] = d->devaddr;
+ olen = 2;
+ break;
+ }
+ break;
+ }
+ return olen;
+}
+
+static const VMStateDescription vmstate_adb_kbd = {
+ .name = "adb_kbd",
+ .version_id = 2,
+ .minimum_version_id = 2,
+ .fields = (VMStateField[]) {
+ VMSTATE_STRUCT(parent_obj, KBDState, 0, vmstate_adb_device, ADBDevice),
+ VMSTATE_BUFFER(data, KBDState),
+ VMSTATE_INT32(rptr, KBDState),
+ VMSTATE_INT32(wptr, KBDState),
+ VMSTATE_INT32(count, KBDState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void adb_kbd_reset(DeviceState *dev)
+{
+ ADBDevice *d = ADB_DEVICE(dev);
+ KBDState *s = ADB_KEYBOARD(dev);
+
+ d->handler = 1;
+ d->devaddr = ADB_DEVID_KEYBOARD;
+ memset(s->data, 0, sizeof(s->data));
+ s->rptr = 0;
+ s->wptr = 0;
+ s->count = 0;
+}
+
+static void adb_kbd_realizefn(DeviceState *dev, Error **errp)
+{
+ ADBDevice *d = ADB_DEVICE(dev);
+ ADBKeyboardClass *akc = ADB_KEYBOARD_GET_CLASS(dev);
+
+ akc->parent_realize(dev, errp);
+
+ qemu_add_kbd_event_handler(adb_kbd_put_keycode, d);
+}
+
+static void adb_kbd_initfn(Object *obj)
+{
+ ADBDevice *d = ADB_DEVICE(obj);
+
+ d->devaddr = ADB_DEVID_KEYBOARD;
+}
+
+static void adb_kbd_class_init(ObjectClass *oc, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(oc);
+ ADBDeviceClass *adc = ADB_DEVICE_CLASS(oc);
+ ADBKeyboardClass *akc = ADB_KEYBOARD_CLASS(oc);
+
+ akc->parent_realize = dc->realize;
+ dc->realize = adb_kbd_realizefn;
+ set_bit(DEVICE_CATEGORY_INPUT, dc->categories);
+
+ adc->devreq = adb_kbd_request;
+ dc->reset = adb_kbd_reset;
+ dc->vmsd = &vmstate_adb_kbd;
+}
+
+static const TypeInfo adb_kbd_type_info = {
+ .name = TYPE_ADB_KEYBOARD,
+ .parent = TYPE_ADB_DEVICE,
+ .instance_size = sizeof(KBDState),
+ .instance_init = adb_kbd_initfn,
+ .class_init = adb_kbd_class_init,
+ .class_size = sizeof(ADBKeyboardClass),
+};
+
+/***************************************************************/
+/* Mouse ADB device */
+
+#define ADB_MOUSE(obj) OBJECT_CHECK(MouseState, (obj), TYPE_ADB_MOUSE)
+
+typedef struct MouseState {
+ /*< public >*/
+ ADBDevice parent_obj;
+ /*< private >*/
+
+ int buttons_state, last_buttons_state;
+ int dx, dy, dz;
+} MouseState;
+
+#define ADB_MOUSE_CLASS(class) \
+ OBJECT_CLASS_CHECK(ADBMouseClass, (class), TYPE_ADB_MOUSE)
+#define ADB_MOUSE_GET_CLASS(obj) \
+ OBJECT_GET_CLASS(ADBMouseClass, (obj), TYPE_ADB_MOUSE)
+
+typedef struct ADBMouseClass {
+ /*< public >*/
+ ADBDeviceClass parent_class;
+ /*< private >*/
+
+ DeviceRealize parent_realize;
+} ADBMouseClass;
+
+static void adb_mouse_event(void *opaque,
+ int dx1, int dy1, int dz1, int buttons_state)
+{
+ MouseState *s = opaque;
+
+ s->dx += dx1;
+ s->dy += dy1;
+ s->dz += dz1;
+ s->buttons_state = buttons_state;
+}
+
+
+static int adb_mouse_poll(ADBDevice *d, uint8_t *obuf)
+{
+ MouseState *s = ADB_MOUSE(d);
+ int dx, dy;
+
+ if (s->last_buttons_state == s->buttons_state &&
+ s->dx == 0 && s->dy == 0)
+ return 0;
+
+ dx = s->dx;
+ if (dx < -63)
+ dx = -63;
+ else if (dx > 63)
+ dx = 63;
+
+ dy = s->dy;
+ if (dy < -63)
+ dy = -63;
+ else if (dy > 63)
+ dy = 63;
+
+ s->dx -= dx;
+ s->dy -= dy;
+ s->last_buttons_state = s->buttons_state;
+
+ dx &= 0x7f;
+ dy &= 0x7f;
+
+ if (!(s->buttons_state & MOUSE_EVENT_LBUTTON))
+ dy |= 0x80;
+ if (!(s->buttons_state & MOUSE_EVENT_RBUTTON))
+ dx |= 0x80;
+
+ obuf[0] = dy;
+ obuf[1] = dx;
+ return 2;
+}
+
+static int adb_mouse_request(ADBDevice *d, uint8_t *obuf,
+ const uint8_t *buf, int len)
+{
+ MouseState *s = ADB_MOUSE(d);
+ int cmd, reg, olen;
+
+ if ((buf[0] & 0x0f) == ADB_FLUSH) {
+ /* flush mouse fifo */
+ s->buttons_state = s->last_buttons_state;
+ s->dx = 0;
+ s->dy = 0;
+ s->dz = 0;
+ return 0;
+ }
+
+ cmd = buf[0] & 0xc;
+ reg = buf[0] & 0x3;
+ olen = 0;
+ switch(cmd) {
+ case ADB_WRITEREG:
+ ADB_DPRINTF("write reg %d val 0x%2.2x\n", reg, buf[1]);
+ switch(reg) {
+ case 2:
+ break;
+ case 3:
+ switch(buf[2]) {
+ case ADB_CMD_SELF_TEST:
+ break;
+ case ADB_CMD_CHANGE_ID:
+ case ADB_CMD_CHANGE_ID_AND_ACT:
+ case ADB_CMD_CHANGE_ID_AND_ENABLE:
+ d->devaddr = buf[1] & 0xf;
+ break;
+ default:
+ /* XXX: check this */
+ d->devaddr = buf[1] & 0xf;
+ break;
+ }
+ }
+ break;
+ case ADB_READREG:
+ switch(reg) {
+ case 0:
+ olen = adb_mouse_poll(d, obuf);
+ break;
+ case 1:
+ break;
+ case 3:
+ obuf[0] = d->handler;
+ obuf[1] = d->devaddr;
+ olen = 2;
+ break;
+ }
+ ADB_DPRINTF("read reg %d obuf[0] 0x%2.2x obuf[1] 0x%2.2x\n", reg,
+ obuf[0], obuf[1]);
+ break;
+ }
+ return olen;
+}
+
+static void adb_mouse_reset(DeviceState *dev)
+{
+ ADBDevice *d = ADB_DEVICE(dev);
+ MouseState *s = ADB_MOUSE(dev);
+
+ d->handler = 2;
+ d->devaddr = ADB_DEVID_MOUSE;
+ s->last_buttons_state = s->buttons_state = 0;
+ s->dx = s->dy = s->dz = 0;
+}
+
+static const VMStateDescription vmstate_adb_mouse = {
+ .name = "adb_mouse",
+ .version_id = 2,
+ .minimum_version_id = 2,
+ .fields = (VMStateField[]) {
+ VMSTATE_STRUCT(parent_obj, MouseState, 0, vmstate_adb_device,
+ ADBDevice),
+ VMSTATE_INT32(buttons_state, MouseState),
+ VMSTATE_INT32(last_buttons_state, MouseState),
+ VMSTATE_INT32(dx, MouseState),
+ VMSTATE_INT32(dy, MouseState),
+ VMSTATE_INT32(dz, MouseState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void adb_mouse_realizefn(DeviceState *dev, Error **errp)
+{
+ MouseState *s = ADB_MOUSE(dev);
+ ADBMouseClass *amc = ADB_MOUSE_GET_CLASS(dev);
+
+ amc->parent_realize(dev, errp);
+
+ qemu_add_mouse_event_handler(adb_mouse_event, s, 0, "QEMU ADB Mouse");
+}
+
+static void adb_mouse_initfn(Object *obj)
+{
+ ADBDevice *d = ADB_DEVICE(obj);
+
+ d->devaddr = ADB_DEVID_MOUSE;
+}
+
+static void adb_mouse_class_init(ObjectClass *oc, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(oc);
+ ADBDeviceClass *adc = ADB_DEVICE_CLASS(oc);
+ ADBMouseClass *amc = ADB_MOUSE_CLASS(oc);
+
+ amc->parent_realize = dc->realize;
+ dc->realize = adb_mouse_realizefn;
+ set_bit(DEVICE_CATEGORY_INPUT, dc->categories);
+
+ adc->devreq = adb_mouse_request;
+ dc->reset = adb_mouse_reset;
+ dc->vmsd = &vmstate_adb_mouse;
+}
+
+static const TypeInfo adb_mouse_type_info = {
+ .name = TYPE_ADB_MOUSE,
+ .parent = TYPE_ADB_DEVICE,
+ .instance_size = sizeof(MouseState),
+ .instance_init = adb_mouse_initfn,
+ .class_init = adb_mouse_class_init,
+ .class_size = sizeof(ADBMouseClass),
+};
+
+
+static void adb_register_types(void)
+{
+ type_register_static(&adb_bus_type_info);
+ type_register_static(&adb_device_type_info);
+ type_register_static(&adb_kbd_type_info);
+ type_register_static(&adb_mouse_type_info);
+}
+
+type_init(adb_register_types)
diff --git a/src/hw/input/hid.c b/src/hw/input/hid.c
new file mode 100644
index 0000000..e39269f
--- /dev/null
+++ b/src/hw/input/hid.c
@@ -0,0 +1,611 @@
+/*
+ * QEMU HID devices
+ *
+ * Copyright (c) 2005 Fabrice Bellard
+ * Copyright (c) 2007 OpenMoko, Inc. (andrew@openedhand.com)
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+#include "hw/hw.h"
+#include "ui/console.h"
+#include "qemu/timer.h"
+#include "hw/input/hid.h"
+
+#define HID_USAGE_ERROR_ROLLOVER 0x01
+#define HID_USAGE_POSTFAIL 0x02
+#define HID_USAGE_ERROR_UNDEFINED 0x03
+
+/* Indices are QEMU keycodes, values are from HID Usage Table. Indices
+ * above 0x80 are for keys that come after 0xe0 or 0xe1+0x1d or 0xe1+0x9d. */
+static const uint8_t hid_usage_keys[0x100] = {
+ 0x00, 0x29, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23,
+ 0x24, 0x25, 0x26, 0x27, 0x2d, 0x2e, 0x2a, 0x2b,
+ 0x14, 0x1a, 0x08, 0x15, 0x17, 0x1c, 0x18, 0x0c,
+ 0x12, 0x13, 0x2f, 0x30, 0x28, 0xe0, 0x04, 0x16,
+ 0x07, 0x09, 0x0a, 0x0b, 0x0d, 0x0e, 0x0f, 0x33,
+ 0x34, 0x35, 0xe1, 0x31, 0x1d, 0x1b, 0x06, 0x19,
+ 0x05, 0x11, 0x10, 0x36, 0x37, 0x38, 0xe5, 0x55,
+ 0xe2, 0x2c, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e,
+ 0x3f, 0x40, 0x41, 0x42, 0x43, 0x53, 0x47, 0x5f,
+ 0x60, 0x61, 0x56, 0x5c, 0x5d, 0x5e, 0x57, 0x59,
+ 0x5a, 0x5b, 0x62, 0x63, 0x00, 0x00, 0x00, 0x44,
+ 0x45, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e,
+ 0xe8, 0xe9, 0x71, 0x72, 0x73, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x85, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0xe3, 0xe7, 0x65,
+
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x58, 0xe4, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x54, 0x00, 0x46,
+ 0xe6, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x48, 0x00, 0x4a,
+ 0x52, 0x4b, 0x00, 0x50, 0x00, 0x4f, 0x00, 0x4d,
+ 0x51, 0x4e, 0x49, 0x4c, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0xe3, 0xe7, 0x65, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+};
+
+bool hid_has_events(HIDState *hs)
+{
+ return hs->n > 0 || hs->idle_pending;
+}
+
+static void hid_idle_timer(void *opaque)
+{
+ HIDState *hs = opaque;
+
+ hs->idle_pending = true;
+ hs->event(hs);
+}
+
+static void hid_del_idle_timer(HIDState *hs)
+{
+ if (hs->idle_timer) {
+ timer_del(hs->idle_timer);
+ timer_free(hs->idle_timer);
+ hs->idle_timer = NULL;
+ }
+}
+
+void hid_set_next_idle(HIDState *hs)
+{
+ if (hs->idle) {
+ uint64_t expire_time = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) +
+ get_ticks_per_sec() * hs->idle * 4 / 1000;
+ if (!hs->idle_timer) {
+ hs->idle_timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, hid_idle_timer, hs);
+ }
+ timer_mod_ns(hs->idle_timer, expire_time);
+ } else {
+ hid_del_idle_timer(hs);
+ }
+}
+
+static void hid_pointer_event(DeviceState *dev, QemuConsole *src,
+ InputEvent *evt)
+{
+ static const int bmap[INPUT_BUTTON_MAX] = {
+ [INPUT_BUTTON_LEFT] = 0x01,
+ [INPUT_BUTTON_RIGHT] = 0x02,
+ [INPUT_BUTTON_MIDDLE] = 0x04,
+ };
+ HIDState *hs = (HIDState *)dev;
+ HIDPointerEvent *e;
+
+ assert(hs->n < QUEUE_LENGTH);
+ e = &hs->ptr.queue[(hs->head + hs->n) & QUEUE_MASK];
+
+ switch (evt->type) {
+ case INPUT_EVENT_KIND_REL:
+ if (evt->u.rel->axis == INPUT_AXIS_X) {
+ e->xdx += evt->u.rel->value;
+ } else if (evt->u.rel->axis == INPUT_AXIS_Y) {
+ e->ydy += evt->u.rel->value;
+ }
+ break;
+
+ case INPUT_EVENT_KIND_ABS:
+ if (evt->u.rel->axis == INPUT_AXIS_X) {
+ e->xdx = evt->u.rel->value;
+ } else if (evt->u.rel->axis == INPUT_AXIS_Y) {
+ e->ydy = evt->u.rel->value;
+ }
+ break;
+
+ case INPUT_EVENT_KIND_BTN:
+ if (evt->u.btn->down) {
+ e->buttons_state |= bmap[evt->u.btn->button];
+ if (evt->u.btn->button == INPUT_BUTTON_WHEEL_UP) {
+ e->dz--;
+ } else if (evt->u.btn->button == INPUT_BUTTON_WHEEL_DOWN) {
+ e->dz++;
+ }
+ } else {
+ e->buttons_state &= ~bmap[evt->u.btn->button];
+ }
+ break;
+
+ default:
+ /* keep gcc happy */
+ break;
+ }
+
+}
+
+static void hid_pointer_sync(DeviceState *dev)
+{
+ HIDState *hs = (HIDState *)dev;
+ HIDPointerEvent *prev, *curr, *next;
+ bool event_compression = false;
+
+ if (hs->n == QUEUE_LENGTH-1) {
+ /*
+ * Queue full. We are losing information, but we at least
+ * keep track of most recent button state.
+ */
+ return;
+ }
+
+ prev = &hs->ptr.queue[(hs->head + hs->n - 1) & QUEUE_MASK];
+ curr = &hs->ptr.queue[(hs->head + hs->n) & QUEUE_MASK];
+ next = &hs->ptr.queue[(hs->head + hs->n + 1) & QUEUE_MASK];
+
+ if (hs->n > 0) {
+ /*
+ * No button state change between previous and current event
+ * (and previous wasn't seen by the guest yet), so there is
+ * motion information only and we can combine the two event
+ * into one.
+ */
+ if (curr->buttons_state == prev->buttons_state) {
+ event_compression = true;
+ }
+ }
+
+ if (event_compression) {
+ /* add current motion to previous, clear current */
+ if (hs->kind == HID_MOUSE) {
+ prev->xdx += curr->xdx;
+ curr->xdx = 0;
+ prev->ydy += curr->ydy;
+ curr->ydy = 0;
+ } else {
+ prev->xdx = curr->xdx;
+ prev->ydy = curr->ydy;
+ }
+ prev->dz += curr->dz;
+ curr->dz = 0;
+ } else {
+ /* prepate next (clear rel, copy abs + btns) */
+ if (hs->kind == HID_MOUSE) {
+ next->xdx = 0;
+ next->ydy = 0;
+ } else {
+ next->xdx = curr->xdx;
+ next->ydy = curr->ydy;
+ }
+ next->dz = 0;
+ next->buttons_state = curr->buttons_state;
+ /* make current guest visible, notify guest */
+ hs->n++;
+ hs->event(hs);
+ }
+}
+
+static void hid_keyboard_event(DeviceState *dev, QemuConsole *src,
+ InputEvent *evt)
+{
+ HIDState *hs = (HIDState *)dev;
+ int scancodes[3], i, count;
+ int slot;
+
+ count = qemu_input_key_value_to_scancode(evt->u.key->key,
+ evt->u.key->down,
+ scancodes);
+ if (hs->n + count > QUEUE_LENGTH) {
+ fprintf(stderr, "usb-kbd: warning: key event queue full\n");
+ return;
+ }
+ for (i = 0; i < count; i++) {
+ slot = (hs->head + hs->n) & QUEUE_MASK; hs->n++;
+ hs->kbd.keycodes[slot] = scancodes[i];
+ }
+ hs->event(hs);
+}
+
+static void hid_keyboard_process_keycode(HIDState *hs)
+{
+ uint8_t hid_code, index, key;
+ int i, keycode, slot;
+
+ if (hs->n == 0) {
+ return;
+ }
+ slot = hs->head & QUEUE_MASK; QUEUE_INCR(hs->head); hs->n--;
+ keycode = hs->kbd.keycodes[slot];
+
+ key = keycode & 0x7f;
+ index = key | ((hs->kbd.modifiers & (1 << 8)) >> 1);
+ hid_code = hid_usage_keys[index];
+ hs->kbd.modifiers &= ~(1 << 8);
+
+ switch (hid_code) {
+ case 0x00:
+ return;
+
+ case 0xe0:
+ assert(key == 0x1d);
+ if (hs->kbd.modifiers & (1 << 9)) {
+ /* The hid_codes for the 0xe1/0x1d scancode sequence are 0xe9/0xe0.
+ * Here we're processing the second hid_code. By dropping bit 9
+ * and setting bit 8, the scancode after 0x1d will access the
+ * second half of the table.
+ */
+ hs->kbd.modifiers ^= (1 << 8) | (1 << 9);
+ return;
+ }
+ /* fall through to process Ctrl_L */
+ case 0xe1 ... 0xe7:
+ /* Ctrl_L/Ctrl_R, Shift_L/Shift_R, Alt_L/Alt_R, Win_L/Win_R.
+ * Handle releases here, or fall through to process presses.
+ */
+ if (keycode & (1 << 7)) {
+ hs->kbd.modifiers &= ~(1 << (hid_code & 0x0f));
+ return;
+ }
+ /* fall through */
+ case 0xe8 ... 0xe9:
+ /* USB modifiers are just 1 byte long. Bits 8 and 9 of
+ * hs->kbd.modifiers implement a state machine that detects the
+ * 0xe0 and 0xe1/0x1d sequences. These bits do not follow the
+ * usual rules where bit 7 marks released keys; they are cleared
+ * elsewhere in the function as the state machine dictates.
+ */
+ hs->kbd.modifiers |= 1 << (hid_code & 0x0f);
+ return;
+
+ case 0xea ... 0xef:
+ abort();
+
+ default:
+ break;
+ }
+
+ if (keycode & (1 << 7)) {
+ for (i = hs->kbd.keys - 1; i >= 0; i--) {
+ if (hs->kbd.key[i] == hid_code) {
+ hs->kbd.key[i] = hs->kbd.key[-- hs->kbd.keys];
+ hs->kbd.key[hs->kbd.keys] = 0x00;
+ break;
+ }
+ }
+ if (i < 0) {
+ return;
+ }
+ } else {
+ for (i = hs->kbd.keys - 1; i >= 0; i--) {
+ if (hs->kbd.key[i] == hid_code) {
+ break;
+ }
+ }
+ if (i < 0) {
+ if (hs->kbd.keys < sizeof(hs->kbd.key)) {
+ hs->kbd.key[hs->kbd.keys++] = hid_code;
+ }
+ } else {
+ return;
+ }
+ }
+}
+
+static inline int int_clamp(int val, int vmin, int vmax)
+{
+ if (val < vmin) {
+ return vmin;
+ } else if (val > vmax) {
+ return vmax;
+ } else {
+ return val;
+ }
+}
+
+void hid_pointer_activate(HIDState *hs)
+{
+ if (!hs->ptr.mouse_grabbed) {
+ qemu_input_handler_activate(hs->s);
+ hs->ptr.mouse_grabbed = 1;
+ }
+}
+
+int hid_pointer_poll(HIDState *hs, uint8_t *buf, int len)
+{
+ int dx, dy, dz, l;
+ int index;
+ HIDPointerEvent *e;
+
+ hs->idle_pending = false;
+
+ hid_pointer_activate(hs);
+
+ /* When the buffer is empty, return the last event. Relative
+ movements will all be zero. */
+ index = (hs->n ? hs->head : hs->head - 1);
+ e = &hs->ptr.queue[index & QUEUE_MASK];
+
+ if (hs->kind == HID_MOUSE) {
+ dx = int_clamp(e->xdx, -127, 127);
+ dy = int_clamp(e->ydy, -127, 127);
+ e->xdx -= dx;
+ e->ydy -= dy;
+ } else {
+ dx = e->xdx;
+ dy = e->ydy;
+ }
+ dz = int_clamp(e->dz, -127, 127);
+ e->dz -= dz;
+
+ if (hs->n &&
+ !e->dz &&
+ (hs->kind == HID_TABLET || (!e->xdx && !e->ydy))) {
+ /* that deals with this event */
+ QUEUE_INCR(hs->head);
+ hs->n--;
+ }
+
+ /* Appears we have to invert the wheel direction */
+ dz = 0 - dz;
+ l = 0;
+ switch (hs->kind) {
+ case HID_MOUSE:
+ if (len > l) {
+ buf[l++] = e->buttons_state;
+ }
+ if (len > l) {
+ buf[l++] = dx;
+ }
+ if (len > l) {
+ buf[l++] = dy;
+ }
+ if (len > l) {
+ buf[l++] = dz;
+ }
+ break;
+
+ case HID_TABLET:
+ if (len > l) {
+ buf[l++] = e->buttons_state;
+ }
+ if (len > l) {
+ buf[l++] = dx & 0xff;
+ }
+ if (len > l) {
+ buf[l++] = dx >> 8;
+ }
+ if (len > l) {
+ buf[l++] = dy & 0xff;
+ }
+ if (len > l) {
+ buf[l++] = dy >> 8;
+ }
+ if (len > l) {
+ buf[l++] = dz;
+ }
+ break;
+
+ default:
+ abort();
+ }
+
+ return l;
+}
+
+int hid_keyboard_poll(HIDState *hs, uint8_t *buf, int len)
+{
+ hs->idle_pending = false;
+
+ if (len < 2) {
+ return 0;
+ }
+
+ hid_keyboard_process_keycode(hs);
+
+ buf[0] = hs->kbd.modifiers & 0xff;
+ buf[1] = 0;
+ if (hs->kbd.keys > 6) {
+ memset(buf + 2, HID_USAGE_ERROR_ROLLOVER, MIN(8, len) - 2);
+ } else {
+ memcpy(buf + 2, hs->kbd.key, MIN(8, len) - 2);
+ }
+
+ return MIN(8, len);
+}
+
+int hid_keyboard_write(HIDState *hs, uint8_t *buf, int len)
+{
+ if (len > 0) {
+ int ledstate = 0;
+ /* 0x01: Num Lock LED
+ * 0x02: Caps Lock LED
+ * 0x04: Scroll Lock LED
+ * 0x08: Compose LED
+ * 0x10: Kana LED */
+ hs->kbd.leds = buf[0];
+ if (hs->kbd.leds & 0x04) {
+ ledstate |= QEMU_SCROLL_LOCK_LED;
+ }
+ if (hs->kbd.leds & 0x01) {
+ ledstate |= QEMU_NUM_LOCK_LED;
+ }
+ if (hs->kbd.leds & 0x02) {
+ ledstate |= QEMU_CAPS_LOCK_LED;
+ }
+ kbd_put_ledstate(ledstate);
+ }
+ return 0;
+}
+
+void hid_reset(HIDState *hs)
+{
+ switch (hs->kind) {
+ case HID_KEYBOARD:
+ memset(hs->kbd.keycodes, 0, sizeof(hs->kbd.keycodes));
+ memset(hs->kbd.key, 0, sizeof(hs->kbd.key));
+ hs->kbd.keys = 0;
+ break;
+ case HID_MOUSE:
+ case HID_TABLET:
+ memset(hs->ptr.queue, 0, sizeof(hs->ptr.queue));
+ break;
+ }
+ hs->head = 0;
+ hs->n = 0;
+ hs->protocol = 1;
+ hs->idle = 0;
+ hs->idle_pending = false;
+ hid_del_idle_timer(hs);
+}
+
+void hid_free(HIDState *hs)
+{
+ qemu_input_handler_unregister(hs->s);
+ hid_del_idle_timer(hs);
+}
+
+static QemuInputHandler hid_keyboard_handler = {
+ .name = "QEMU HID Keyboard",
+ .mask = INPUT_EVENT_MASK_KEY,
+ .event = hid_keyboard_event,
+};
+
+static QemuInputHandler hid_mouse_handler = {
+ .name = "QEMU HID Mouse",
+ .mask = INPUT_EVENT_MASK_BTN | INPUT_EVENT_MASK_REL,
+ .event = hid_pointer_event,
+ .sync = hid_pointer_sync,
+};
+
+static QemuInputHandler hid_tablet_handler = {
+ .name = "QEMU HID Tablet",
+ .mask = INPUT_EVENT_MASK_BTN | INPUT_EVENT_MASK_ABS,
+ .event = hid_pointer_event,
+ .sync = hid_pointer_sync,
+};
+
+void hid_init(HIDState *hs, int kind, HIDEventFunc event)
+{
+ hs->kind = kind;
+ hs->event = event;
+
+ if (hs->kind == HID_KEYBOARD) {
+ hs->s = qemu_input_handler_register((DeviceState *)hs,
+ &hid_keyboard_handler);
+ qemu_input_handler_activate(hs->s);
+ } else if (hs->kind == HID_MOUSE) {
+ hs->s = qemu_input_handler_register((DeviceState *)hs,
+ &hid_mouse_handler);
+ } else if (hs->kind == HID_TABLET) {
+ hs->s = qemu_input_handler_register((DeviceState *)hs,
+ &hid_tablet_handler);
+ }
+}
+
+static int hid_post_load(void *opaque, int version_id)
+{
+ HIDState *s = opaque;
+
+ hid_set_next_idle(s);
+
+ if (s->n == QUEUE_LENGTH && (s->kind == HID_TABLET ||
+ s->kind == HID_MOUSE)) {
+ /*
+ * Handle ptr device migration from old qemu with full queue.
+ *
+ * Throw away everything but the last event, so we propagate
+ * at least the current button state to the guest. Also keep
+ * current position for the tablet, signal "no motion" for the
+ * mouse.
+ */
+ HIDPointerEvent evt;
+ evt = s->ptr.queue[(s->head+s->n) & QUEUE_MASK];
+ if (s->kind == HID_MOUSE) {
+ evt.xdx = 0;
+ evt.ydy = 0;
+ }
+ s->ptr.queue[0] = evt;
+ s->head = 0;
+ s->n = 1;
+ }
+ return 0;
+}
+
+static const VMStateDescription vmstate_hid_ptr_queue = {
+ .name = "HIDPointerEventQueue",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_INT32(xdx, HIDPointerEvent),
+ VMSTATE_INT32(ydy, HIDPointerEvent),
+ VMSTATE_INT32(dz, HIDPointerEvent),
+ VMSTATE_INT32(buttons_state, HIDPointerEvent),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+const VMStateDescription vmstate_hid_ptr_device = {
+ .name = "HIDPointerDevice",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .post_load = hid_post_load,
+ .fields = (VMStateField[]) {
+ VMSTATE_STRUCT_ARRAY(ptr.queue, HIDState, QUEUE_LENGTH, 0,
+ vmstate_hid_ptr_queue, HIDPointerEvent),
+ VMSTATE_UINT32(head, HIDState),
+ VMSTATE_UINT32(n, HIDState),
+ VMSTATE_INT32(protocol, HIDState),
+ VMSTATE_UINT8(idle, HIDState),
+ VMSTATE_END_OF_LIST(),
+ }
+};
+
+const VMStateDescription vmstate_hid_keyboard_device = {
+ .name = "HIDKeyboardDevice",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .post_load = hid_post_load,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32_ARRAY(kbd.keycodes, HIDState, QUEUE_LENGTH),
+ VMSTATE_UINT32(head, HIDState),
+ VMSTATE_UINT32(n, HIDState),
+ VMSTATE_UINT16(kbd.modifiers, HIDState),
+ VMSTATE_UINT8(kbd.leds, HIDState),
+ VMSTATE_UINT8_ARRAY(kbd.key, HIDState, 16),
+ VMSTATE_INT32(kbd.keys, HIDState),
+ VMSTATE_INT32(protocol, HIDState),
+ VMSTATE_UINT8(idle, HIDState),
+ VMSTATE_END_OF_LIST(),
+ }
+};
diff --git a/src/hw/input/lm832x.c b/src/hw/input/lm832x.c
new file mode 100644
index 0000000..530a6e0
--- /dev/null
+++ b/src/hw/input/lm832x.c
@@ -0,0 +1,524 @@
+/*
+ * National Semiconductor LM8322/8323 GPIO keyboard & PWM chips.
+ *
+ * Copyright (C) 2008 Nokia Corporation
+ * Written by Andrzej Zaborowski <andrew@openedhand.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 or
+ * (at your option) version 3 of the License.
+ *
+ * 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 "hw/hw.h"
+#include "hw/i2c/i2c.h"
+#include "qemu/timer.h"
+#include "ui/console.h"
+
+#define TYPE_LM8323 "lm8323"
+#define LM8323(obj) OBJECT_CHECK(LM823KbdState, (obj), TYPE_LM8323)
+
+typedef struct {
+ I2CSlave parent_obj;
+
+ uint8_t i2c_dir;
+ uint8_t i2c_cycle;
+ uint8_t reg;
+
+ qemu_irq nirq;
+ uint16_t model;
+
+ struct {
+ qemu_irq out[2];
+ int in[2][2];
+ } mux;
+
+ uint8_t config;
+ uint8_t status;
+ uint8_t acttime;
+ uint8_t error;
+ uint8_t clock;
+
+ struct {
+ uint16_t pull;
+ uint16_t mask;
+ uint16_t dir;
+ uint16_t level;
+ qemu_irq out[16];
+ } gpio;
+
+ struct {
+ uint8_t dbnctime;
+ uint8_t size;
+ uint8_t start;
+ uint8_t len;
+ uint8_t fifo[16];
+ } kbd;
+
+ struct {
+ uint16_t file[256];
+ uint8_t faddr;
+ uint8_t addr[3];
+ QEMUTimer *tm[3];
+ } pwm;
+} LM823KbdState;
+
+#define INT_KEYPAD (1 << 0)
+#define INT_ERROR (1 << 3)
+#define INT_NOINIT (1 << 4)
+#define INT_PWMEND(n) (1 << (5 + n))
+
+#define ERR_BADPAR (1 << 0)
+#define ERR_CMDUNK (1 << 1)
+#define ERR_KEYOVR (1 << 2)
+#define ERR_FIFOOVR (1 << 6)
+
+static void lm_kbd_irq_update(LM823KbdState *s)
+{
+ qemu_set_irq(s->nirq, !s->status);
+}
+
+static void lm_kbd_gpio_update(LM823KbdState *s)
+{
+}
+
+static void lm_kbd_reset(LM823KbdState *s)
+{
+ s->config = 0x80;
+ s->status = INT_NOINIT;
+ s->acttime = 125;
+ s->kbd.dbnctime = 3;
+ s->kbd.size = 0x33;
+ s->clock = 0x08;
+
+ lm_kbd_irq_update(s);
+ lm_kbd_gpio_update(s);
+}
+
+static void lm_kbd_error(LM823KbdState *s, int err)
+{
+ s->error |= err;
+ s->status |= INT_ERROR;
+ lm_kbd_irq_update(s);
+}
+
+static void lm_kbd_pwm_tick(LM823KbdState *s, int line)
+{
+}
+
+static void lm_kbd_pwm_start(LM823KbdState *s, int line)
+{
+ lm_kbd_pwm_tick(s, line);
+}
+
+static void lm_kbd_pwm0_tick(void *opaque)
+{
+ lm_kbd_pwm_tick(opaque, 0);
+}
+static void lm_kbd_pwm1_tick(void *opaque)
+{
+ lm_kbd_pwm_tick(opaque, 1);
+}
+static void lm_kbd_pwm2_tick(void *opaque)
+{
+ lm_kbd_pwm_tick(opaque, 2);
+}
+
+enum {
+ LM832x_CMD_READ_ID = 0x80, /* Read chip ID. */
+ LM832x_CMD_WRITE_CFG = 0x81, /* Set configuration item. */
+ LM832x_CMD_READ_INT = 0x82, /* Get interrupt status. */
+ LM832x_CMD_RESET = 0x83, /* Reset, same as external one */
+ LM823x_CMD_WRITE_PULL_DOWN = 0x84, /* Select GPIO pull-up/down. */
+ LM832x_CMD_WRITE_PORT_SEL = 0x85, /* Select GPIO in/out. */
+ LM832x_CMD_WRITE_PORT_STATE = 0x86, /* Set GPIO pull-up/down. */
+ LM832x_CMD_READ_PORT_SEL = 0x87, /* Get GPIO in/out. */
+ LM832x_CMD_READ_PORT_STATE = 0x88, /* Get GPIO pull-up/down. */
+ LM832x_CMD_READ_FIFO = 0x89, /* Read byte from FIFO. */
+ LM832x_CMD_RPT_READ_FIFO = 0x8a, /* Read FIFO (no increment). */
+ LM832x_CMD_SET_ACTIVE = 0x8b, /* Set active time. */
+ LM832x_CMD_READ_ERROR = 0x8c, /* Get error status. */
+ LM832x_CMD_READ_ROTATOR = 0x8e, /* Read rotator status. */
+ LM832x_CMD_SET_DEBOUNCE = 0x8f, /* Set debouncing time. */
+ LM832x_CMD_SET_KEY_SIZE = 0x90, /* Set keypad size. */
+ LM832x_CMD_READ_KEY_SIZE = 0x91, /* Get keypad size. */
+ LM832x_CMD_READ_CFG = 0x92, /* Get configuration item. */
+ LM832x_CMD_WRITE_CLOCK = 0x93, /* Set clock config. */
+ LM832x_CMD_READ_CLOCK = 0x94, /* Get clock config. */
+ LM832x_CMD_PWM_WRITE = 0x95, /* Write PWM script. */
+ LM832x_CMD_PWM_START = 0x96, /* Start PWM engine. */
+ LM832x_CMD_PWM_STOP = 0x97, /* Stop PWM engine. */
+ LM832x_GENERAL_ERROR = 0xff, /* There was one error.
+ Previously was represented by -1
+ This is not a command */
+};
+
+#define LM832x_MAX_KPX 8
+#define LM832x_MAX_KPY 12
+
+static uint8_t lm_kbd_read(LM823KbdState *s, int reg, int byte)
+{
+ int ret;
+
+ switch (reg) {
+ case LM832x_CMD_READ_ID:
+ ret = 0x0400;
+ break;
+
+ case LM832x_CMD_READ_INT:
+ ret = s->status;
+ if (!(s->status & INT_NOINIT)) {
+ s->status = 0;
+ lm_kbd_irq_update(s);
+ }
+ break;
+
+ case LM832x_CMD_READ_PORT_SEL:
+ ret = s->gpio.dir;
+ break;
+ case LM832x_CMD_READ_PORT_STATE:
+ ret = s->gpio.mask;
+ break;
+
+ case LM832x_CMD_READ_FIFO:
+ if (s->kbd.len <= 1)
+ return 0x00;
+
+ /* Example response from the two commands after a INT_KEYPAD
+ * interrupt caused by the key 0x3c being pressed:
+ * RPT_READ_FIFO: 55 bc 00 4e ff 0a 50 08 00 29 d9 08 01 c9 01
+ * READ_FIFO: bc 00 00 4e ff 0a 50 08 00 29 d9 08 01 c9 01
+ * RPT_READ_FIFO: bc 00 00 4e ff 0a 50 08 00 29 d9 08 01 c9 01
+ *
+ * 55 is the code of the key release event serviced in the previous
+ * interrupt handling.
+ *
+ * TODO: find out whether the FIFO is advanced a single character
+ * before reading every byte or the whole size of the FIFO at the
+ * last LM832x_CMD_READ_FIFO. This affects LM832x_CMD_RPT_READ_FIFO
+ * output in cases where there are more than one event in the FIFO.
+ * Assume 0xbc and 0x3c events are in the FIFO:
+ * RPT_READ_FIFO: 55 bc 3c 00 4e ff 0a 50 08 00 29 d9 08 01 c9
+ * READ_FIFO: bc 3c 00 00 4e ff 0a 50 08 00 29 d9 08 01 c9
+ * Does RPT_READ_FIFO now return 0xbc and 0x3c or only 0x3c?
+ */
+ s->kbd.start ++;
+ s->kbd.start &= sizeof(s->kbd.fifo) - 1;
+ s->kbd.len --;
+
+ return s->kbd.fifo[s->kbd.start];
+ case LM832x_CMD_RPT_READ_FIFO:
+ if (byte >= s->kbd.len)
+ return 0x00;
+
+ return s->kbd.fifo[(s->kbd.start + byte) & (sizeof(s->kbd.fifo) - 1)];
+
+ case LM832x_CMD_READ_ERROR:
+ return s->error;
+
+ case LM832x_CMD_READ_ROTATOR:
+ return 0;
+
+ case LM832x_CMD_READ_KEY_SIZE:
+ return s->kbd.size;
+
+ case LM832x_CMD_READ_CFG:
+ return s->config & 0xf;
+
+ case LM832x_CMD_READ_CLOCK:
+ return (s->clock & 0xfc) | 2;
+
+ default:
+ lm_kbd_error(s, ERR_CMDUNK);
+ fprintf(stderr, "%s: unknown command %02x\n", __FUNCTION__, reg);
+ return 0x00;
+ }
+
+ return ret >> (byte << 3);
+}
+
+static void lm_kbd_write(LM823KbdState *s, int reg, int byte, uint8_t value)
+{
+ switch (reg) {
+ case LM832x_CMD_WRITE_CFG:
+ s->config = value;
+ /* This must be done whenever s->mux.in is updated (never). */
+ if ((s->config >> 1) & 1) /* MUX1EN */
+ qemu_set_irq(s->mux.out[0], s->mux.in[0][(s->config >> 0) & 1]);
+ if ((s->config >> 3) & 1) /* MUX2EN */
+ qemu_set_irq(s->mux.out[0], s->mux.in[0][(s->config >> 2) & 1]);
+ /* TODO: check that this is issued only following the chip reset
+ * and not in the middle of operation and that it is followed by
+ * the GPIO ports re-resablishing through WRITE_PORT_SEL and
+ * WRITE_PORT_STATE (using a timer perhaps) and otherwise output
+ * warnings. */
+ s->status = 0;
+ lm_kbd_irq_update(s);
+ s->kbd.len = 0;
+ s->kbd.start = 0;
+ s->reg = LM832x_GENERAL_ERROR;
+ break;
+
+ case LM832x_CMD_RESET:
+ if (value == 0xaa)
+ lm_kbd_reset(s);
+ else
+ lm_kbd_error(s, ERR_BADPAR);
+ s->reg = LM832x_GENERAL_ERROR;
+ break;
+
+ case LM823x_CMD_WRITE_PULL_DOWN:
+ if (!byte)
+ s->gpio.pull = value;
+ else {
+ s->gpio.pull |= value << 8;
+ lm_kbd_gpio_update(s);
+ s->reg = LM832x_GENERAL_ERROR;
+ }
+ break;
+ case LM832x_CMD_WRITE_PORT_SEL:
+ if (!byte)
+ s->gpio.dir = value;
+ else {
+ s->gpio.dir |= value << 8;
+ lm_kbd_gpio_update(s);
+ s->reg = LM832x_GENERAL_ERROR;
+ }
+ break;
+ case LM832x_CMD_WRITE_PORT_STATE:
+ if (!byte)
+ s->gpio.mask = value;
+ else {
+ s->gpio.mask |= value << 8;
+ lm_kbd_gpio_update(s);
+ s->reg = LM832x_GENERAL_ERROR;
+ }
+ break;
+
+ case LM832x_CMD_SET_ACTIVE:
+ s->acttime = value;
+ s->reg = LM832x_GENERAL_ERROR;
+ break;
+
+ case LM832x_CMD_SET_DEBOUNCE:
+ s->kbd.dbnctime = value;
+ s->reg = LM832x_GENERAL_ERROR;
+ if (!value)
+ lm_kbd_error(s, ERR_BADPAR);
+ break;
+
+ case LM832x_CMD_SET_KEY_SIZE:
+ s->kbd.size = value;
+ s->reg = LM832x_GENERAL_ERROR;
+ if (
+ (value & 0xf) < 3 || (value & 0xf) > LM832x_MAX_KPY ||
+ (value >> 4) < 3 || (value >> 4) > LM832x_MAX_KPX)
+ lm_kbd_error(s, ERR_BADPAR);
+ break;
+
+ case LM832x_CMD_WRITE_CLOCK:
+ s->clock = value;
+ s->reg = LM832x_GENERAL_ERROR;
+ if ((value & 3) && (value & 3) != 3) {
+ lm_kbd_error(s, ERR_BADPAR);
+ fprintf(stderr, "%s: invalid clock setting in RCPWM\n",
+ __FUNCTION__);
+ }
+ /* TODO: Validate that the command is only issued once */
+ break;
+
+ case LM832x_CMD_PWM_WRITE:
+ if (byte == 0) {
+ if (!(value & 3) || (value >> 2) > 59) {
+ lm_kbd_error(s, ERR_BADPAR);
+ s->reg = LM832x_GENERAL_ERROR;
+ break;
+ }
+
+ s->pwm.faddr = value;
+ s->pwm.file[s->pwm.faddr] = 0;
+ } else if (byte == 1) {
+ s->pwm.file[s->pwm.faddr] |= value << 8;
+ } else if (byte == 2) {
+ s->pwm.file[s->pwm.faddr] |= value << 0;
+ s->reg = LM832x_GENERAL_ERROR;
+ }
+ break;
+ case LM832x_CMD_PWM_START:
+ s->reg = LM832x_GENERAL_ERROR;
+ if (!(value & 3) || (value >> 2) > 59) {
+ lm_kbd_error(s, ERR_BADPAR);
+ break;
+ }
+
+ s->pwm.addr[(value & 3) - 1] = value >> 2;
+ lm_kbd_pwm_start(s, (value & 3) - 1);
+ break;
+ case LM832x_CMD_PWM_STOP:
+ s->reg = LM832x_GENERAL_ERROR;
+ if (!(value & 3)) {
+ lm_kbd_error(s, ERR_BADPAR);
+ break;
+ }
+
+ timer_del(s->pwm.tm[(value & 3) - 1]);
+ break;
+
+ case LM832x_GENERAL_ERROR:
+ lm_kbd_error(s, ERR_BADPAR);
+ break;
+ default:
+ lm_kbd_error(s, ERR_CMDUNK);
+ fprintf(stderr, "%s: unknown command %02x\n", __FUNCTION__, reg);
+ break;
+ }
+}
+
+static void lm_i2c_event(I2CSlave *i2c, enum i2c_event event)
+{
+ LM823KbdState *s = LM8323(i2c);
+
+ switch (event) {
+ case I2C_START_RECV:
+ case I2C_START_SEND:
+ s->i2c_cycle = 0;
+ s->i2c_dir = (event == I2C_START_SEND);
+ break;
+
+ default:
+ break;
+ }
+}
+
+static int lm_i2c_rx(I2CSlave *i2c)
+{
+ LM823KbdState *s = LM8323(i2c);
+
+ return lm_kbd_read(s, s->reg, s->i2c_cycle ++);
+}
+
+static int lm_i2c_tx(I2CSlave *i2c, uint8_t data)
+{
+ LM823KbdState *s = LM8323(i2c);
+
+ if (!s->i2c_cycle)
+ s->reg = data;
+ else
+ lm_kbd_write(s, s->reg, s->i2c_cycle - 1, data);
+ s->i2c_cycle ++;
+
+ return 0;
+}
+
+static int lm_kbd_post_load(void *opaque, int version_id)
+{
+ LM823KbdState *s = opaque;
+
+ lm_kbd_irq_update(s);
+ lm_kbd_gpio_update(s);
+
+ return 0;
+}
+
+static const VMStateDescription vmstate_lm_kbd = {
+ .name = "LM8323",
+ .version_id = 0,
+ .minimum_version_id = 0,
+ .post_load = lm_kbd_post_load,
+ .fields = (VMStateField[]) {
+ VMSTATE_I2C_SLAVE(parent_obj, LM823KbdState),
+ VMSTATE_UINT8(i2c_dir, LM823KbdState),
+ VMSTATE_UINT8(i2c_cycle, LM823KbdState),
+ VMSTATE_UINT8(reg, LM823KbdState),
+ VMSTATE_UINT8(config, LM823KbdState),
+ VMSTATE_UINT8(status, LM823KbdState),
+ VMSTATE_UINT8(acttime, LM823KbdState),
+ VMSTATE_UINT8(error, LM823KbdState),
+ VMSTATE_UINT8(clock, LM823KbdState),
+ VMSTATE_UINT16(gpio.pull, LM823KbdState),
+ VMSTATE_UINT16(gpio.mask, LM823KbdState),
+ VMSTATE_UINT16(gpio.dir, LM823KbdState),
+ VMSTATE_UINT16(gpio.level, LM823KbdState),
+ VMSTATE_UINT8(kbd.dbnctime, LM823KbdState),
+ VMSTATE_UINT8(kbd.size, LM823KbdState),
+ VMSTATE_UINT8(kbd.start, LM823KbdState),
+ VMSTATE_UINT8(kbd.len, LM823KbdState),
+ VMSTATE_BUFFER(kbd.fifo, LM823KbdState),
+ VMSTATE_UINT16_ARRAY(pwm.file, LM823KbdState, 256),
+ VMSTATE_UINT8(pwm.faddr, LM823KbdState),
+ VMSTATE_BUFFER(pwm.addr, LM823KbdState),
+ VMSTATE_TIMER_PTR_ARRAY(pwm.tm, LM823KbdState, 3),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+
+static int lm8323_init(I2CSlave *i2c)
+{
+ LM823KbdState *s = LM8323(i2c);
+
+ s->model = 0x8323;
+ s->pwm.tm[0] = timer_new_ns(QEMU_CLOCK_VIRTUAL, lm_kbd_pwm0_tick, s);
+ s->pwm.tm[1] = timer_new_ns(QEMU_CLOCK_VIRTUAL, lm_kbd_pwm1_tick, s);
+ s->pwm.tm[2] = timer_new_ns(QEMU_CLOCK_VIRTUAL, lm_kbd_pwm2_tick, s);
+ qdev_init_gpio_out(DEVICE(i2c), &s->nirq, 1);
+
+ lm_kbd_reset(s);
+
+ qemu_register_reset((void *) lm_kbd_reset, s);
+ return 0;
+}
+
+void lm832x_key_event(DeviceState *dev, int key, int state)
+{
+ LM823KbdState *s = LM8323(dev);
+
+ if ((s->status & INT_ERROR) && (s->error & ERR_FIFOOVR))
+ return;
+
+ if (s->kbd.len >= sizeof(s->kbd.fifo)) {
+ lm_kbd_error(s, ERR_FIFOOVR);
+ return;
+ }
+
+ s->kbd.fifo[(s->kbd.start + s->kbd.len ++) & (sizeof(s->kbd.fifo) - 1)] =
+ key | (state << 7);
+
+ /* We never set ERR_KEYOVR because we support multiple keys fine. */
+ s->status |= INT_KEYPAD;
+ lm_kbd_irq_update(s);
+}
+
+static void lm8323_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ I2CSlaveClass *k = I2C_SLAVE_CLASS(klass);
+
+ k->init = lm8323_init;
+ k->event = lm_i2c_event;
+ k->recv = lm_i2c_rx;
+ k->send = lm_i2c_tx;
+ dc->vmsd = &vmstate_lm_kbd;
+}
+
+static const TypeInfo lm8323_info = {
+ .name = TYPE_LM8323,
+ .parent = TYPE_I2C_SLAVE,
+ .instance_size = sizeof(LM823KbdState),
+ .class_init = lm8323_class_init,
+};
+
+static void lm832x_register_types(void)
+{
+ type_register_static(&lm8323_info);
+}
+
+type_init(lm832x_register_types)
diff --git a/src/hw/input/milkymist-softusb.c b/src/hw/input/milkymist-softusb.c
new file mode 100644
index 0000000..8a02d35
--- /dev/null
+++ b/src/hw/input/milkymist-softusb.c
@@ -0,0 +1,317 @@
+/*
+ * QEMU model of the Milkymist SoftUSB block.
+ *
+ * Copyright (c) 2010 Michael Walle <michael@walle.cc>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ *
+ *
+ * Specification available at:
+ * not available yet
+ */
+
+#include "hw/hw.h"
+#include "hw/sysbus.h"
+#include "trace.h"
+#include "ui/console.h"
+#include "hw/input/hid.h"
+#include "qemu/error-report.h"
+
+enum {
+ R_CTRL = 0,
+ R_MAX
+};
+
+enum {
+ CTRL_RESET = (1<<0),
+};
+
+#define COMLOC_DEBUG_PRODUCE 0x1000
+#define COMLOC_DEBUG_BASE 0x1001
+#define COMLOC_MEVT_PRODUCE 0x1101
+#define COMLOC_MEVT_BASE 0x1102
+#define COMLOC_KEVT_PRODUCE 0x1142
+#define COMLOC_KEVT_BASE 0x1143
+
+#define TYPE_MILKYMIST_SOFTUSB "milkymist-softusb"
+#define MILKYMIST_SOFTUSB(obj) \
+ OBJECT_CHECK(MilkymistSoftUsbState, (obj), TYPE_MILKYMIST_SOFTUSB)
+
+struct MilkymistSoftUsbState {
+ SysBusDevice parent_obj;
+
+ HIDState hid_kbd;
+ HIDState hid_mouse;
+
+ MemoryRegion regs_region;
+ MemoryRegion pmem;
+ MemoryRegion dmem;
+ qemu_irq irq;
+
+ void *pmem_ptr;
+ void *dmem_ptr;
+
+ /* device properties */
+ uint32_t pmem_size;
+ uint32_t dmem_size;
+
+ /* device registers */
+ uint32_t regs[R_MAX];
+
+ /* mouse state */
+ uint8_t mouse_hid_buffer[4];
+
+ /* keyboard state */
+ uint8_t kbd_hid_buffer[8];
+};
+typedef struct MilkymistSoftUsbState MilkymistSoftUsbState;
+
+static uint64_t softusb_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ MilkymistSoftUsbState *s = opaque;
+ uint32_t r = 0;
+
+ addr >>= 2;
+ switch (addr) {
+ case R_CTRL:
+ r = s->regs[addr];
+ break;
+
+ default:
+ error_report("milkymist_softusb: read access to unknown register 0x"
+ TARGET_FMT_plx, addr << 2);
+ break;
+ }
+
+ trace_milkymist_softusb_memory_read(addr << 2, r);
+
+ return r;
+}
+
+static void
+softusb_write(void *opaque, hwaddr addr, uint64_t value,
+ unsigned size)
+{
+ MilkymistSoftUsbState *s = opaque;
+
+ trace_milkymist_softusb_memory_write(addr, value);
+
+ addr >>= 2;
+ switch (addr) {
+ case R_CTRL:
+ s->regs[addr] = value;
+ break;
+
+ default:
+ error_report("milkymist_softusb: write access to unknown register 0x"
+ TARGET_FMT_plx, addr << 2);
+ break;
+ }
+}
+
+static const MemoryRegionOps softusb_mmio_ops = {
+ .read = softusb_read,
+ .write = softusb_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+ .valid = {
+ .min_access_size = 4,
+ .max_access_size = 4,
+ },
+};
+
+static inline void softusb_read_dmem(MilkymistSoftUsbState *s,
+ uint32_t offset, uint8_t *buf, uint32_t len)
+{
+ if (offset + len >= s->dmem_size) {
+ error_report("milkymist_softusb: read dmem out of bounds "
+ "at offset 0x%x, len %d", offset, len);
+ memset(buf, 0, len);
+ return;
+ }
+
+ memcpy(buf, s->dmem_ptr + offset, len);
+}
+
+static inline void softusb_write_dmem(MilkymistSoftUsbState *s,
+ uint32_t offset, uint8_t *buf, uint32_t len)
+{
+ if (offset + len >= s->dmem_size) {
+ error_report("milkymist_softusb: write dmem out of bounds "
+ "at offset 0x%x, len %d", offset, len);
+ return;
+ }
+
+ memcpy(s->dmem_ptr + offset, buf, len);
+}
+
+static void softusb_mouse_changed(MilkymistSoftUsbState *s)
+{
+ uint8_t m;
+
+ softusb_read_dmem(s, COMLOC_MEVT_PRODUCE, &m, 1);
+ trace_milkymist_softusb_mevt(m);
+ softusb_write_dmem(s, COMLOC_MEVT_BASE + 4 * m, s->mouse_hid_buffer, 4);
+ m = (m + 1) & 0xf;
+ softusb_write_dmem(s, COMLOC_MEVT_PRODUCE, &m, 1);
+
+ trace_milkymist_softusb_pulse_irq();
+ qemu_irq_pulse(s->irq);
+}
+
+static void softusb_kbd_changed(MilkymistSoftUsbState *s)
+{
+ uint8_t m;
+
+ softusb_read_dmem(s, COMLOC_KEVT_PRODUCE, &m, 1);
+ trace_milkymist_softusb_kevt(m);
+ softusb_write_dmem(s, COMLOC_KEVT_BASE + 8 * m, s->kbd_hid_buffer, 8);
+ m = (m + 1) & 0x7;
+ softusb_write_dmem(s, COMLOC_KEVT_PRODUCE, &m, 1);
+
+ trace_milkymist_softusb_pulse_irq();
+ qemu_irq_pulse(s->irq);
+}
+
+static void softusb_kbd_hid_datain(HIDState *hs)
+{
+ MilkymistSoftUsbState *s = container_of(hs, MilkymistSoftUsbState, hid_kbd);
+ int len;
+
+ /* if device is in reset, do nothing */
+ if (s->regs[R_CTRL] & CTRL_RESET) {
+ return;
+ }
+
+ while (hid_has_events(hs)) {
+ len = hid_keyboard_poll(hs, s->kbd_hid_buffer,
+ sizeof(s->kbd_hid_buffer));
+
+ if (len == 8) {
+ softusb_kbd_changed(s);
+ }
+ }
+}
+
+static void softusb_mouse_hid_datain(HIDState *hs)
+{
+ MilkymistSoftUsbState *s =
+ container_of(hs, MilkymistSoftUsbState, hid_mouse);
+ int len;
+
+ /* if device is in reset, do nothing */
+ if (s->regs[R_CTRL] & CTRL_RESET) {
+ return;
+ }
+
+ while (hid_has_events(hs)) {
+ len = hid_pointer_poll(hs, s->mouse_hid_buffer,
+ sizeof(s->mouse_hid_buffer));
+
+ if (len == 4) {
+ softusb_mouse_changed(s);
+ }
+ }
+}
+
+static void milkymist_softusb_reset(DeviceState *d)
+{
+ MilkymistSoftUsbState *s = MILKYMIST_SOFTUSB(d);
+ int i;
+
+ for (i = 0; i < R_MAX; i++) {
+ s->regs[i] = 0;
+ }
+ memset(s->kbd_hid_buffer, 0, sizeof(s->kbd_hid_buffer));
+ memset(s->mouse_hid_buffer, 0, sizeof(s->mouse_hid_buffer));
+
+ hid_reset(&s->hid_kbd);
+ hid_reset(&s->hid_mouse);
+
+ /* defaults */
+ s->regs[R_CTRL] = CTRL_RESET;
+}
+
+static int milkymist_softusb_init(SysBusDevice *dev)
+{
+ MilkymistSoftUsbState *s = MILKYMIST_SOFTUSB(dev);
+
+ sysbus_init_irq(dev, &s->irq);
+
+ memory_region_init_io(&s->regs_region, OBJECT(s), &softusb_mmio_ops, s,
+ "milkymist-softusb", R_MAX * 4);
+ sysbus_init_mmio(dev, &s->regs_region);
+
+ /* register pmem and dmem */
+ memory_region_init_ram(&s->pmem, OBJECT(s), "milkymist-softusb.pmem",
+ s->pmem_size, &error_fatal);
+ vmstate_register_ram_global(&s->pmem);
+ s->pmem_ptr = memory_region_get_ram_ptr(&s->pmem);
+ sysbus_init_mmio(dev, &s->pmem);
+ memory_region_init_ram(&s->dmem, OBJECT(s), "milkymist-softusb.dmem",
+ s->dmem_size, &error_fatal);
+ vmstate_register_ram_global(&s->dmem);
+ s->dmem_ptr = memory_region_get_ram_ptr(&s->dmem);
+ sysbus_init_mmio(dev, &s->dmem);
+
+ hid_init(&s->hid_kbd, HID_KEYBOARD, softusb_kbd_hid_datain);
+ hid_init(&s->hid_mouse, HID_MOUSE, softusb_mouse_hid_datain);
+
+ return 0;
+}
+
+static const VMStateDescription vmstate_milkymist_softusb = {
+ .name = "milkymist-softusb",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32_ARRAY(regs, MilkymistSoftUsbState, R_MAX),
+ VMSTATE_HID_KEYBOARD_DEVICE(hid_kbd, MilkymistSoftUsbState),
+ VMSTATE_HID_POINTER_DEVICE(hid_mouse, MilkymistSoftUsbState),
+ VMSTATE_BUFFER(kbd_hid_buffer, MilkymistSoftUsbState),
+ VMSTATE_BUFFER(mouse_hid_buffer, MilkymistSoftUsbState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static Property milkymist_softusb_properties[] = {
+ DEFINE_PROP_UINT32("pmem_size", MilkymistSoftUsbState, pmem_size, 0x00001000),
+ DEFINE_PROP_UINT32("dmem_size", MilkymistSoftUsbState, dmem_size, 0x00002000),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void milkymist_softusb_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
+
+ k->init = milkymist_softusb_init;
+ dc->reset = milkymist_softusb_reset;
+ dc->vmsd = &vmstate_milkymist_softusb;
+ dc->props = milkymist_softusb_properties;
+}
+
+static const TypeInfo milkymist_softusb_info = {
+ .name = TYPE_MILKYMIST_SOFTUSB,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(MilkymistSoftUsbState),
+ .class_init = milkymist_softusb_class_init,
+};
+
+static void milkymist_softusb_register_types(void)
+{
+ type_register_static(&milkymist_softusb_info);
+}
+
+type_init(milkymist_softusb_register_types)
diff --git a/src/hw/input/pckbd.c b/src/hw/input/pckbd.c
new file mode 100644
index 0000000..ddac69d
--- /dev/null
+++ b/src/hw/input/pckbd.c
@@ -0,0 +1,594 @@
+/*
+ * QEMU PC keyboard emulation
+ *
+ * Copyright (c) 2003 Fabrice Bellard
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+#include "hw/hw.h"
+#include "hw/isa/isa.h"
+#include "hw/i386/pc.h"
+#include "hw/input/ps2.h"
+#include "sysemu/sysemu.h"
+
+/* debug PC keyboard */
+//#define DEBUG_KBD
+#ifdef DEBUG_KBD
+#define DPRINTF(fmt, ...) \
+ do { printf("KBD: " fmt , ## __VA_ARGS__); } while (0)
+#else
+#define DPRINTF(fmt, ...)
+#endif
+
+/* Keyboard Controller Commands */
+#define KBD_CCMD_READ_MODE 0x20 /* Read mode bits */
+#define KBD_CCMD_WRITE_MODE 0x60 /* Write mode bits */
+#define KBD_CCMD_GET_VERSION 0xA1 /* Get controller version */
+#define KBD_CCMD_MOUSE_DISABLE 0xA7 /* Disable mouse interface */
+#define KBD_CCMD_MOUSE_ENABLE 0xA8 /* Enable mouse interface */
+#define KBD_CCMD_TEST_MOUSE 0xA9 /* Mouse interface test */
+#define KBD_CCMD_SELF_TEST 0xAA /* Controller self test */
+#define KBD_CCMD_KBD_TEST 0xAB /* Keyboard interface test */
+#define KBD_CCMD_KBD_DISABLE 0xAD /* Keyboard interface disable */
+#define KBD_CCMD_KBD_ENABLE 0xAE /* Keyboard interface enable */
+#define KBD_CCMD_READ_INPORT 0xC0 /* read input port */
+#define KBD_CCMD_READ_OUTPORT 0xD0 /* read output port */
+#define KBD_CCMD_WRITE_OUTPORT 0xD1 /* write output port */
+#define KBD_CCMD_WRITE_OBUF 0xD2
+#define KBD_CCMD_WRITE_AUX_OBUF 0xD3 /* Write to output buffer as if
+ initiated by the auxiliary device */
+#define KBD_CCMD_WRITE_MOUSE 0xD4 /* Write the following byte to the mouse */
+#define KBD_CCMD_DISABLE_A20 0xDD /* HP vectra only ? */
+#define KBD_CCMD_ENABLE_A20 0xDF /* HP vectra only ? */
+#define KBD_CCMD_PULSE_BITS_3_0 0xF0 /* Pulse bits 3-0 of the output port P2. */
+#define KBD_CCMD_RESET 0xFE /* Pulse bit 0 of the output port P2 = CPU reset. */
+#define KBD_CCMD_NO_OP 0xFF /* Pulse no bits of the output port P2. */
+
+/* Keyboard Commands */
+#define KBD_CMD_SET_LEDS 0xED /* Set keyboard leds */
+#define KBD_CMD_ECHO 0xEE
+#define KBD_CMD_GET_ID 0xF2 /* get keyboard ID */
+#define KBD_CMD_SET_RATE 0xF3 /* Set typematic rate */
+#define KBD_CMD_ENABLE 0xF4 /* Enable scanning */
+#define KBD_CMD_RESET_DISABLE 0xF5 /* reset and disable scanning */
+#define KBD_CMD_RESET_ENABLE 0xF6 /* reset and enable scanning */
+#define KBD_CMD_RESET 0xFF /* Reset */
+
+/* Keyboard Replies */
+#define KBD_REPLY_POR 0xAA /* Power on reset */
+#define KBD_REPLY_ACK 0xFA /* Command ACK */
+#define KBD_REPLY_RESEND 0xFE /* Command NACK, send the cmd again */
+
+/* Status Register Bits */
+#define KBD_STAT_OBF 0x01 /* Keyboard output buffer full */
+#define KBD_STAT_IBF 0x02 /* Keyboard input buffer full */
+#define KBD_STAT_SELFTEST 0x04 /* Self test successful */
+#define KBD_STAT_CMD 0x08 /* Last write was a command write (0=data) */
+#define KBD_STAT_UNLOCKED 0x10 /* Zero if keyboard locked */
+#define KBD_STAT_MOUSE_OBF 0x20 /* Mouse output buffer full */
+#define KBD_STAT_GTO 0x40 /* General receive/xmit timeout */
+#define KBD_STAT_PERR 0x80 /* Parity error */
+
+/* Controller Mode Register Bits */
+#define KBD_MODE_KBD_INT 0x01 /* Keyboard data generate IRQ1 */
+#define KBD_MODE_MOUSE_INT 0x02 /* Mouse data generate IRQ12 */
+#define KBD_MODE_SYS 0x04 /* The system flag (?) */
+#define KBD_MODE_NO_KEYLOCK 0x08 /* The keylock doesn't affect the keyboard if set */
+#define KBD_MODE_DISABLE_KBD 0x10 /* Disable keyboard interface */
+#define KBD_MODE_DISABLE_MOUSE 0x20 /* Disable mouse interface */
+#define KBD_MODE_KCC 0x40 /* Scan code conversion to PC format */
+#define KBD_MODE_RFU 0x80
+
+/* Output Port Bits */
+#define KBD_OUT_RESET 0x01 /* 1=normal mode, 0=reset */
+#define KBD_OUT_A20 0x02 /* x86 only */
+#define KBD_OUT_OBF 0x10 /* Keyboard output buffer full */
+#define KBD_OUT_MOUSE_OBF 0x20 /* Mouse output buffer full */
+
+/* OSes typically write 0xdd/0xdf to turn the A20 line off and on.
+ * We make the default value of the outport include these four bits,
+ * so that the subsection is rarely necessary.
+ */
+#define KBD_OUT_ONES 0xcc
+
+/* Mouse Commands */
+#define AUX_SET_SCALE11 0xE6 /* Set 1:1 scaling */
+#define AUX_SET_SCALE21 0xE7 /* Set 2:1 scaling */
+#define AUX_SET_RES 0xE8 /* Set resolution */
+#define AUX_GET_SCALE 0xE9 /* Get scaling factor */
+#define AUX_SET_STREAM 0xEA /* Set stream mode */
+#define AUX_POLL 0xEB /* Poll */
+#define AUX_RESET_WRAP 0xEC /* Reset wrap mode */
+#define AUX_SET_WRAP 0xEE /* Set wrap mode */
+#define AUX_SET_REMOTE 0xF0 /* Set remote mode */
+#define AUX_GET_TYPE 0xF2 /* Get type */
+#define AUX_SET_SAMPLE 0xF3 /* Set sample rate */
+#define AUX_ENABLE_DEV 0xF4 /* Enable aux device */
+#define AUX_DISABLE_DEV 0xF5 /* Disable aux device */
+#define AUX_SET_DEFAULT 0xF6
+#define AUX_RESET 0xFF /* Reset aux device */
+#define AUX_ACK 0xFA /* Command byte ACK. */
+
+#define MOUSE_STATUS_REMOTE 0x40
+#define MOUSE_STATUS_ENABLED 0x20
+#define MOUSE_STATUS_SCALE21 0x10
+
+#define KBD_PENDING_KBD 1
+#define KBD_PENDING_AUX 2
+
+typedef struct KBDState {
+ uint8_t write_cmd; /* if non zero, write data to port 60 is expected */
+ uint8_t status;
+ uint8_t mode;
+ uint8_t outport;
+ bool outport_present;
+ /* Bitmask of devices with data available. */
+ uint8_t pending;
+ void *kbd;
+ void *mouse;
+
+ qemu_irq irq_kbd;
+ qemu_irq irq_mouse;
+ qemu_irq *a20_out;
+ hwaddr mask;
+} KBDState;
+
+/* update irq and KBD_STAT_[MOUSE_]OBF */
+/* XXX: not generating the irqs if KBD_MODE_DISABLE_KBD is set may be
+ incorrect, but it avoids having to simulate exact delays */
+static void kbd_update_irq(KBDState *s)
+{
+ int irq_kbd_level, irq_mouse_level;
+
+ irq_kbd_level = 0;
+ irq_mouse_level = 0;
+ s->status &= ~(KBD_STAT_OBF | KBD_STAT_MOUSE_OBF);
+ s->outport &= ~(KBD_OUT_OBF | KBD_OUT_MOUSE_OBF);
+ if (s->pending) {
+ s->status |= KBD_STAT_OBF;
+ s->outport |= KBD_OUT_OBF;
+ /* kbd data takes priority over aux data. */
+ if (s->pending == KBD_PENDING_AUX) {
+ s->status |= KBD_STAT_MOUSE_OBF;
+ s->outport |= KBD_OUT_MOUSE_OBF;
+ if (s->mode & KBD_MODE_MOUSE_INT)
+ irq_mouse_level = 1;
+ } else {
+ if ((s->mode & KBD_MODE_KBD_INT) &&
+ !(s->mode & KBD_MODE_DISABLE_KBD))
+ irq_kbd_level = 1;
+ }
+ }
+ qemu_set_irq(s->irq_kbd, irq_kbd_level);
+ qemu_set_irq(s->irq_mouse, irq_mouse_level);
+}
+
+static void kbd_update_kbd_irq(void *opaque, int level)
+{
+ KBDState *s = (KBDState *)opaque;
+
+ if (level)
+ s->pending |= KBD_PENDING_KBD;
+ else
+ s->pending &= ~KBD_PENDING_KBD;
+ kbd_update_irq(s);
+}
+
+static void kbd_update_aux_irq(void *opaque, int level)
+{
+ KBDState *s = (KBDState *)opaque;
+
+ if (level)
+ s->pending |= KBD_PENDING_AUX;
+ else
+ s->pending &= ~KBD_PENDING_AUX;
+ kbd_update_irq(s);
+}
+
+static uint64_t kbd_read_status(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ KBDState *s = opaque;
+ int val;
+ val = s->status;
+ DPRINTF("kbd: read status=0x%02x\n", val);
+ return val;
+}
+
+static void kbd_queue(KBDState *s, int b, int aux)
+{
+ if (aux)
+ ps2_queue(s->mouse, b);
+ else
+ ps2_queue(s->kbd, b);
+}
+
+static void outport_write(KBDState *s, uint32_t val)
+{
+ DPRINTF("kbd: write outport=0x%02x\n", val);
+ s->outport = val;
+ if (s->a20_out) {
+ qemu_set_irq(*s->a20_out, (val >> 1) & 1);
+ }
+ if (!(val & 1)) {
+ qemu_system_reset_request();
+ }
+}
+
+static void kbd_write_command(void *opaque, hwaddr addr,
+ uint64_t val, unsigned size)
+{
+ KBDState *s = opaque;
+
+ DPRINTF("kbd: write cmd=0x%02" PRIx64 "\n", val);
+
+ /* Bits 3-0 of the output port P2 of the keyboard controller may be pulsed
+ * low for approximately 6 micro seconds. Bits 3-0 of the KBD_CCMD_PULSE
+ * command specify the output port bits to be pulsed.
+ * 0: Bit should be pulsed. 1: Bit should not be modified.
+ * The only useful version of this command is pulsing bit 0,
+ * which does a CPU reset.
+ */
+ if((val & KBD_CCMD_PULSE_BITS_3_0) == KBD_CCMD_PULSE_BITS_3_0) {
+ if(!(val & 1))
+ val = KBD_CCMD_RESET;
+ else
+ val = KBD_CCMD_NO_OP;
+ }
+
+ switch(val) {
+ case KBD_CCMD_READ_MODE:
+ kbd_queue(s, s->mode, 0);
+ break;
+ case KBD_CCMD_WRITE_MODE:
+ case KBD_CCMD_WRITE_OBUF:
+ case KBD_CCMD_WRITE_AUX_OBUF:
+ case KBD_CCMD_WRITE_MOUSE:
+ case KBD_CCMD_WRITE_OUTPORT:
+ s->write_cmd = val;
+ break;
+ case KBD_CCMD_MOUSE_DISABLE:
+ s->mode |= KBD_MODE_DISABLE_MOUSE;
+ break;
+ case KBD_CCMD_MOUSE_ENABLE:
+ s->mode &= ~KBD_MODE_DISABLE_MOUSE;
+ break;
+ case KBD_CCMD_TEST_MOUSE:
+ kbd_queue(s, 0x00, 0);
+ break;
+ case KBD_CCMD_SELF_TEST:
+ s->status |= KBD_STAT_SELFTEST;
+ kbd_queue(s, 0x55, 0);
+ break;
+ case KBD_CCMD_KBD_TEST:
+ kbd_queue(s, 0x00, 0);
+ break;
+ case KBD_CCMD_KBD_DISABLE:
+ s->mode |= KBD_MODE_DISABLE_KBD;
+ kbd_update_irq(s);
+ break;
+ case KBD_CCMD_KBD_ENABLE:
+ s->mode &= ~KBD_MODE_DISABLE_KBD;
+ kbd_update_irq(s);
+ break;
+ case KBD_CCMD_READ_INPORT:
+ kbd_queue(s, 0x80, 0);
+ break;
+ case KBD_CCMD_READ_OUTPORT:
+ kbd_queue(s, s->outport, 0);
+ break;
+ case KBD_CCMD_ENABLE_A20:
+ if (s->a20_out) {
+ qemu_irq_raise(*s->a20_out);
+ }
+ s->outport |= KBD_OUT_A20;
+ break;
+ case KBD_CCMD_DISABLE_A20:
+ if (s->a20_out) {
+ qemu_irq_lower(*s->a20_out);
+ }
+ s->outport &= ~KBD_OUT_A20;
+ break;
+ case KBD_CCMD_RESET:
+ qemu_system_reset_request();
+ break;
+ case KBD_CCMD_NO_OP:
+ /* ignore that */
+ break;
+ default:
+ fprintf(stderr, "qemu: unsupported keyboard cmd=0x%02x\n", (int)val);
+ break;
+ }
+}
+
+static uint64_t kbd_read_data(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ KBDState *s = opaque;
+ uint32_t val;
+
+ if (s->pending == KBD_PENDING_AUX)
+ val = ps2_read_data(s->mouse);
+ else
+ val = ps2_read_data(s->kbd);
+
+ DPRINTF("kbd: read data=0x%02x\n", val);
+ return val;
+}
+
+static void kbd_write_data(void *opaque, hwaddr addr,
+ uint64_t val, unsigned size)
+{
+ KBDState *s = opaque;
+
+ DPRINTF("kbd: write data=0x%02" PRIx64 "\n", val);
+
+ switch(s->write_cmd) {
+ case 0:
+ ps2_write_keyboard(s->kbd, val);
+ break;
+ case KBD_CCMD_WRITE_MODE:
+ s->mode = val;
+ ps2_keyboard_set_translation(s->kbd, (s->mode & KBD_MODE_KCC) != 0);
+ /* ??? */
+ kbd_update_irq(s);
+ break;
+ case KBD_CCMD_WRITE_OBUF:
+ kbd_queue(s, val, 0);
+ break;
+ case KBD_CCMD_WRITE_AUX_OBUF:
+ kbd_queue(s, val, 1);
+ break;
+ case KBD_CCMD_WRITE_OUTPORT:
+ outport_write(s, val);
+ break;
+ case KBD_CCMD_WRITE_MOUSE:
+ ps2_write_mouse(s->mouse, val);
+ break;
+ default:
+ break;
+ }
+ s->write_cmd = 0;
+}
+
+static void kbd_reset(void *opaque)
+{
+ KBDState *s = opaque;
+
+ s->mode = KBD_MODE_KBD_INT | KBD_MODE_MOUSE_INT;
+ s->status = KBD_STAT_CMD | KBD_STAT_UNLOCKED;
+ s->outport = KBD_OUT_RESET | KBD_OUT_A20 | KBD_OUT_ONES;
+ s->outport_present = false;
+}
+
+static uint8_t kbd_outport_default(KBDState *s)
+{
+ return KBD_OUT_RESET | KBD_OUT_A20 | KBD_OUT_ONES
+ | (s->status & KBD_STAT_OBF ? KBD_OUT_OBF : 0)
+ | (s->status & KBD_STAT_MOUSE_OBF ? KBD_OUT_MOUSE_OBF : 0);
+}
+
+static int kbd_outport_post_load(void *opaque, int version_id)
+{
+ KBDState *s = opaque;
+ s->outport_present = true;
+ return 0;
+}
+
+static bool kbd_outport_needed(void *opaque)
+{
+ KBDState *s = opaque;
+ return s->outport != kbd_outport_default(s);
+}
+
+static const VMStateDescription vmstate_kbd_outport = {
+ .name = "pckbd_outport",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .post_load = kbd_outport_post_load,
+ .needed = kbd_outport_needed,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT8(outport, KBDState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static int kbd_post_load(void *opaque, int version_id)
+{
+ KBDState *s = opaque;
+ if (!s->outport_present) {
+ s->outport = kbd_outport_default(s);
+ }
+ s->outport_present = false;
+ return 0;
+}
+
+static const VMStateDescription vmstate_kbd = {
+ .name = "pckbd",
+ .version_id = 3,
+ .minimum_version_id = 3,
+ .post_load = kbd_post_load,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT8(write_cmd, KBDState),
+ VMSTATE_UINT8(status, KBDState),
+ VMSTATE_UINT8(mode, KBDState),
+ VMSTATE_UINT8(pending, KBDState),
+ VMSTATE_END_OF_LIST()
+ },
+ .subsections = (const VMStateDescription*[]) {
+ &vmstate_kbd_outport,
+ NULL
+ }
+};
+
+/* Memory mapped interface */
+static uint32_t kbd_mm_readb (void *opaque, hwaddr addr)
+{
+ KBDState *s = opaque;
+
+ if (addr & s->mask)
+ return kbd_read_status(s, 0, 1) & 0xff;
+ else
+ return kbd_read_data(s, 0, 1) & 0xff;
+}
+
+static void kbd_mm_writeb (void *opaque, hwaddr addr, uint32_t value)
+{
+ KBDState *s = opaque;
+
+ if (addr & s->mask)
+ kbd_write_command(s, 0, value & 0xff, 1);
+ else
+ kbd_write_data(s, 0, value & 0xff, 1);
+}
+
+static const MemoryRegionOps i8042_mmio_ops = {
+ .endianness = DEVICE_NATIVE_ENDIAN,
+ .old_mmio = {
+ .read = { kbd_mm_readb, kbd_mm_readb, kbd_mm_readb },
+ .write = { kbd_mm_writeb, kbd_mm_writeb, kbd_mm_writeb },
+ },
+};
+
+void i8042_mm_init(qemu_irq kbd_irq, qemu_irq mouse_irq,
+ MemoryRegion *region, ram_addr_t size,
+ hwaddr mask)
+{
+ KBDState *s = g_malloc0(sizeof(KBDState));
+
+ s->irq_kbd = kbd_irq;
+ s->irq_mouse = mouse_irq;
+ s->mask = mask;
+
+ vmstate_register(NULL, 0, &vmstate_kbd, s);
+
+ memory_region_init_io(region, NULL, &i8042_mmio_ops, s, "i8042", size);
+
+ s->kbd = ps2_kbd_init(kbd_update_kbd_irq, s);
+ s->mouse = ps2_mouse_init(kbd_update_aux_irq, s);
+ qemu_register_reset(kbd_reset, s);
+}
+
+#define TYPE_I8042 "i8042"
+#define I8042(obj) OBJECT_CHECK(ISAKBDState, (obj), TYPE_I8042)
+
+typedef struct ISAKBDState {
+ ISADevice parent_obj;
+
+ KBDState kbd;
+ MemoryRegion io[2];
+} ISAKBDState;
+
+void i8042_isa_mouse_fake_event(void *opaque)
+{
+ ISADevice *dev = opaque;
+ ISAKBDState *isa = I8042(dev);
+ KBDState *s = &isa->kbd;
+
+ ps2_mouse_fake_event(s->mouse);
+}
+
+void i8042_setup_a20_line(ISADevice *dev, qemu_irq *a20_out)
+{
+ ISAKBDState *isa = I8042(dev);
+ KBDState *s = &isa->kbd;
+
+ s->a20_out = a20_out;
+}
+
+static const VMStateDescription vmstate_kbd_isa = {
+ .name = "pckbd",
+ .version_id = 3,
+ .minimum_version_id = 3,
+ .fields = (VMStateField[]) {
+ VMSTATE_STRUCT(kbd, ISAKBDState, 0, vmstate_kbd, KBDState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static const MemoryRegionOps i8042_data_ops = {
+ .read = kbd_read_data,
+ .write = kbd_write_data,
+ .impl = {
+ .min_access_size = 1,
+ .max_access_size = 1,
+ },
+ .endianness = DEVICE_LITTLE_ENDIAN,
+};
+
+static const MemoryRegionOps i8042_cmd_ops = {
+ .read = kbd_read_status,
+ .write = kbd_write_command,
+ .impl = {
+ .min_access_size = 1,
+ .max_access_size = 1,
+ },
+ .endianness = DEVICE_LITTLE_ENDIAN,
+};
+
+static void i8042_initfn(Object *obj)
+{
+ ISAKBDState *isa_s = I8042(obj);
+ KBDState *s = &isa_s->kbd;
+
+ memory_region_init_io(isa_s->io + 0, obj, &i8042_data_ops, s,
+ "i8042-data", 1);
+ memory_region_init_io(isa_s->io + 1, obj, &i8042_cmd_ops, s,
+ "i8042-cmd", 1);
+}
+
+static void i8042_realizefn(DeviceState *dev, Error **errp)
+{
+ ISADevice *isadev = ISA_DEVICE(dev);
+ ISAKBDState *isa_s = I8042(dev);
+ KBDState *s = &isa_s->kbd;
+
+ isa_init_irq(isadev, &s->irq_kbd, 1);
+ isa_init_irq(isadev, &s->irq_mouse, 12);
+
+ isa_register_ioport(isadev, isa_s->io + 0, 0x60);
+ isa_register_ioport(isadev, isa_s->io + 1, 0x64);
+
+ s->kbd = ps2_kbd_init(kbd_update_kbd_irq, s);
+ s->mouse = ps2_mouse_init(kbd_update_aux_irq, s);
+ qemu_register_reset(kbd_reset, s);
+}
+
+static void i8042_class_initfn(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->realize = i8042_realizefn;
+ dc->vmsd = &vmstate_kbd_isa;
+}
+
+static const TypeInfo i8042_info = {
+ .name = TYPE_I8042,
+ .parent = TYPE_ISA_DEVICE,
+ .instance_size = sizeof(ISAKBDState),
+ .instance_init = i8042_initfn,
+ .class_init = i8042_class_initfn,
+};
+
+static void i8042_register_types(void)
+{
+ type_register_static(&i8042_info);
+}
+
+type_init(i8042_register_types)
diff --git a/src/hw/input/pl050.c b/src/hw/input/pl050.c
new file mode 100644
index 0000000..c1b08d5
--- /dev/null
+++ b/src/hw/input/pl050.c
@@ -0,0 +1,205 @@
+/*
+ * Arm PrimeCell PL050 Keyboard / Mouse Interface
+ *
+ * Copyright (c) 2006-2007 CodeSourcery.
+ * Written by Paul Brook
+ *
+ * This code is licensed under the GPL.
+ */
+
+#include "hw/sysbus.h"
+#include "hw/input/ps2.h"
+
+#define TYPE_PL050 "pl050"
+#define PL050(obj) OBJECT_CHECK(PL050State, (obj), TYPE_PL050)
+
+typedef struct PL050State {
+ SysBusDevice parent_obj;
+
+ MemoryRegion iomem;
+ void *dev;
+ uint32_t cr;
+ uint32_t clk;
+ uint32_t last;
+ int pending;
+ qemu_irq irq;
+ bool is_mouse;
+} PL050State;
+
+static const VMStateDescription vmstate_pl050 = {
+ .name = "pl050",
+ .version_id = 2,
+ .minimum_version_id = 2,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32(cr, PL050State),
+ VMSTATE_UINT32(clk, PL050State),
+ VMSTATE_UINT32(last, PL050State),
+ VMSTATE_INT32(pending, PL050State),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+#define PL050_TXEMPTY (1 << 6)
+#define PL050_TXBUSY (1 << 5)
+#define PL050_RXFULL (1 << 4)
+#define PL050_RXBUSY (1 << 3)
+#define PL050_RXPARITY (1 << 2)
+#define PL050_KMIC (1 << 1)
+#define PL050_KMID (1 << 0)
+
+static const unsigned char pl050_id[] =
+{ 0x50, 0x10, 0x04, 0x00, 0x0d, 0xf0, 0x05, 0xb1 };
+
+static void pl050_update(void *opaque, int level)
+{
+ PL050State *s = (PL050State *)opaque;
+ int raise;
+
+ s->pending = level;
+ raise = (s->pending && (s->cr & 0x10) != 0)
+ || (s->cr & 0x08) != 0;
+ qemu_set_irq(s->irq, raise);
+}
+
+static uint64_t pl050_read(void *opaque, hwaddr offset,
+ unsigned size)
+{
+ PL050State *s = (PL050State *)opaque;
+ if (offset >= 0xfe0 && offset < 0x1000)
+ return pl050_id[(offset - 0xfe0) >> 2];
+
+ switch (offset >> 2) {
+ case 0: /* KMICR */
+ return s->cr;
+ case 1: /* KMISTAT */
+ {
+ uint8_t val;
+ uint32_t stat;
+
+ val = s->last;
+ val = val ^ (val >> 4);
+ val = val ^ (val >> 2);
+ val = (val ^ (val >> 1)) & 1;
+
+ stat = PL050_TXEMPTY;
+ if (val)
+ stat |= PL050_RXPARITY;
+ if (s->pending)
+ stat |= PL050_RXFULL;
+
+ return stat;
+ }
+ case 2: /* KMIDATA */
+ if (s->pending)
+ s->last = ps2_read_data(s->dev);
+ return s->last;
+ case 3: /* KMICLKDIV */
+ return s->clk;
+ case 4: /* KMIIR */
+ return s->pending | 2;
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "pl050_read: Bad offset %x\n", (int)offset);
+ return 0;
+ }
+}
+
+static void pl050_write(void *opaque, hwaddr offset,
+ uint64_t value, unsigned size)
+{
+ PL050State *s = (PL050State *)opaque;
+ switch (offset >> 2) {
+ case 0: /* KMICR */
+ s->cr = value;
+ pl050_update(s, s->pending);
+ /* ??? Need to implement the enable/disable bit. */
+ break;
+ case 2: /* KMIDATA */
+ /* ??? This should toggle the TX interrupt line. */
+ /* ??? This means kbd/mouse can block each other. */
+ if (s->is_mouse) {
+ ps2_write_mouse(s->dev, value);
+ } else {
+ ps2_write_keyboard(s->dev, value);
+ }
+ break;
+ case 3: /* KMICLKDIV */
+ s->clk = value;
+ return;
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "pl050_write: Bad offset %x\n", (int)offset);
+ }
+}
+static const MemoryRegionOps pl050_ops = {
+ .read = pl050_read,
+ .write = pl050_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static int pl050_initfn(SysBusDevice *dev)
+{
+ PL050State *s = PL050(dev);
+
+ memory_region_init_io(&s->iomem, OBJECT(s), &pl050_ops, s, "pl050", 0x1000);
+ sysbus_init_mmio(dev, &s->iomem);
+ sysbus_init_irq(dev, &s->irq);
+ if (s->is_mouse) {
+ s->dev = ps2_mouse_init(pl050_update, s);
+ } else {
+ s->dev = ps2_kbd_init(pl050_update, s);
+ }
+ return 0;
+}
+
+static void pl050_keyboard_init(Object *obj)
+{
+ PL050State *s = PL050(obj);
+
+ s->is_mouse = false;
+}
+
+static void pl050_mouse_init(Object *obj)
+{
+ PL050State *s = PL050(obj);
+
+ s->is_mouse = true;
+}
+
+static const TypeInfo pl050_kbd_info = {
+ .name = "pl050_keyboard",
+ .parent = TYPE_PL050,
+ .instance_init = pl050_keyboard_init,
+};
+
+static const TypeInfo pl050_mouse_info = {
+ .name = "pl050_mouse",
+ .parent = TYPE_PL050,
+ .instance_init = pl050_mouse_init,
+};
+
+static void pl050_class_init(ObjectClass *oc, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(oc);
+ SysBusDeviceClass *sdc = SYS_BUS_DEVICE_CLASS(oc);
+
+ sdc->init = pl050_initfn;
+ dc->vmsd = &vmstate_pl050;
+}
+
+static const TypeInfo pl050_type_info = {
+ .name = TYPE_PL050,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(PL050State),
+ .abstract = true,
+ .class_init = pl050_class_init,
+};
+
+static void pl050_register_types(void)
+{
+ type_register_static(&pl050_type_info);
+ type_register_static(&pl050_kbd_info);
+ type_register_static(&pl050_mouse_info);
+}
+
+type_init(pl050_register_types)
diff --git a/src/hw/input/ps2.c b/src/hw/input/ps2.c
new file mode 100644
index 0000000..3d6d496
--- /dev/null
+++ b/src/hw/input/ps2.c
@@ -0,0 +1,807 @@
+/*
+ * QEMU PS/2 keyboard/mouse emulation
+ *
+ * Copyright (c) 2003 Fabrice Bellard
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+#include "hw/hw.h"
+#include "hw/input/ps2.h"
+#include "ui/console.h"
+#include "ui/input.h"
+#include "sysemu/sysemu.h"
+
+#include "trace.h"
+
+/* debug PC keyboard */
+//#define DEBUG_KBD
+
+/* debug PC keyboard : only mouse */
+//#define DEBUG_MOUSE
+
+/* Keyboard Commands */
+#define KBD_CMD_SET_LEDS 0xED /* Set keyboard leds */
+#define KBD_CMD_ECHO 0xEE
+#define KBD_CMD_SCANCODE 0xF0 /* Get/set scancode set */
+#define KBD_CMD_GET_ID 0xF2 /* get keyboard ID */
+#define KBD_CMD_SET_RATE 0xF3 /* Set typematic rate */
+#define KBD_CMD_ENABLE 0xF4 /* Enable scanning */
+#define KBD_CMD_RESET_DISABLE 0xF5 /* reset and disable scanning */
+#define KBD_CMD_RESET_ENABLE 0xF6 /* reset and enable scanning */
+#define KBD_CMD_RESET 0xFF /* Reset */
+
+/* Keyboard Replies */
+#define KBD_REPLY_POR 0xAA /* Power on reset */
+#define KBD_REPLY_ID 0xAB /* Keyboard ID */
+#define KBD_REPLY_ACK 0xFA /* Command ACK */
+#define KBD_REPLY_RESEND 0xFE /* Command NACK, send the cmd again */
+
+/* Mouse Commands */
+#define AUX_SET_SCALE11 0xE6 /* Set 1:1 scaling */
+#define AUX_SET_SCALE21 0xE7 /* Set 2:1 scaling */
+#define AUX_SET_RES 0xE8 /* Set resolution */
+#define AUX_GET_SCALE 0xE9 /* Get scaling factor */
+#define AUX_SET_STREAM 0xEA /* Set stream mode */
+#define AUX_POLL 0xEB /* Poll */
+#define AUX_RESET_WRAP 0xEC /* Reset wrap mode */
+#define AUX_SET_WRAP 0xEE /* Set wrap mode */
+#define AUX_SET_REMOTE 0xF0 /* Set remote mode */
+#define AUX_GET_TYPE 0xF2 /* Get type */
+#define AUX_SET_SAMPLE 0xF3 /* Set sample rate */
+#define AUX_ENABLE_DEV 0xF4 /* Enable aux device */
+#define AUX_DISABLE_DEV 0xF5 /* Disable aux device */
+#define AUX_SET_DEFAULT 0xF6
+#define AUX_RESET 0xFF /* Reset aux device */
+#define AUX_ACK 0xFA /* Command byte ACK. */
+
+#define MOUSE_STATUS_REMOTE 0x40
+#define MOUSE_STATUS_ENABLED 0x20
+#define MOUSE_STATUS_SCALE21 0x10
+
+#define PS2_QUEUE_SIZE 16 /* Buffer size required by PS/2 protocol */
+
+typedef struct {
+ /* Keep the data array 256 bytes long, which compatibility
+ with older qemu versions. */
+ uint8_t data[256];
+ int rptr, wptr, count;
+} PS2Queue;
+
+typedef struct {
+ PS2Queue queue;
+ int32_t write_cmd;
+ void (*update_irq)(void *, int);
+ void *update_arg;
+} PS2State;
+
+typedef struct {
+ PS2State common;
+ int scan_enabled;
+ /* QEMU uses translated PC scancodes internally. To avoid multiple
+ conversions we do the translation (if any) in the PS/2 emulation
+ not the keyboard controller. */
+ int translate;
+ int scancode_set; /* 1=XT, 2=AT, 3=PS/2 */
+ int ledstate;
+} PS2KbdState;
+
+typedef struct {
+ PS2State common;
+ uint8_t mouse_status;
+ uint8_t mouse_resolution;
+ uint8_t mouse_sample_rate;
+ uint8_t mouse_wrap;
+ uint8_t mouse_type; /* 0 = PS2, 3 = IMPS/2, 4 = IMEX */
+ uint8_t mouse_detect_state;
+ int mouse_dx; /* current values, needed for 'poll' mode */
+ int mouse_dy;
+ int mouse_dz;
+ uint8_t mouse_buttons;
+} PS2MouseState;
+
+/* Table to convert from PC scancodes to raw scancodes. */
+static const unsigned char ps2_raw_keycode[128] = {
+ 0, 118, 22, 30, 38, 37, 46, 54, 61, 62, 70, 69, 78, 85, 102, 13,
+ 21, 29, 36, 45, 44, 53, 60, 67, 68, 77, 84, 91, 90, 20, 28, 27,
+ 35, 43, 52, 51, 59, 66, 75, 76, 82, 14, 18, 93, 26, 34, 33, 42,
+ 50, 49, 58, 65, 73, 74, 89, 124, 17, 41, 88, 5, 6, 4, 12, 3,
+ 11, 2, 10, 1, 9, 119, 126, 108, 117, 125, 123, 107, 115, 116, 121, 105,
+114, 122, 112, 113, 127, 96, 97, 120, 7, 15, 23, 31, 39, 47, 55, 63,
+ 71, 79, 86, 94, 8, 16, 24, 32, 40, 48, 56, 64, 72, 80, 87, 111,
+ 19, 25, 57, 81, 83, 92, 95, 98, 99, 100, 101, 103, 104, 106, 109, 110
+};
+static const unsigned char ps2_raw_keycode_set3[128] = {
+ 0, 8, 22, 30, 38, 37, 46, 54, 61, 62, 70, 69, 78, 85, 102, 13,
+ 21, 29, 36, 45, 44, 53, 60, 67, 68, 77, 84, 91, 90, 17, 28, 27,
+ 35, 43, 52, 51, 59, 66, 75, 76, 82, 14, 18, 92, 26, 34, 33, 42,
+ 50, 49, 58, 65, 73, 74, 89, 126, 25, 41, 20, 7, 15, 23, 31, 39,
+ 47, 2, 63, 71, 79, 118, 95, 108, 117, 125, 132, 107, 115, 116, 124, 105,
+114, 122, 112, 113, 127, 96, 97, 86, 94, 15, 23, 31, 39, 47, 55, 63,
+ 71, 79, 86, 94, 8, 16, 24, 32, 40, 48, 56, 64, 72, 80, 87, 111,
+ 19, 25, 57, 81, 83, 92, 95, 98, 99, 100, 101, 103, 104, 106, 109, 110
+};
+
+void ps2_queue(void *opaque, int b)
+{
+ PS2State *s = (PS2State *)opaque;
+ PS2Queue *q = &s->queue;
+
+ if (q->count >= PS2_QUEUE_SIZE - 1)
+ return;
+ q->data[q->wptr] = b;
+ if (++q->wptr == PS2_QUEUE_SIZE)
+ q->wptr = 0;
+ q->count++;
+ s->update_irq(s->update_arg, 1);
+}
+
+/*
+ keycode is expressed as follow:
+ bit 7 - 0 key pressed, 1 = key released
+ bits 6-0 - translated scancode set 2
+ */
+static void ps2_put_keycode(void *opaque, int keycode)
+{
+ PS2KbdState *s = opaque;
+
+ trace_ps2_put_keycode(opaque, keycode);
+ qemu_system_wakeup_request(QEMU_WAKEUP_REASON_OTHER);
+ /* XXX: add support for scancode set 1 */
+ if (!s->translate && keycode < 0xe0 && s->scancode_set > 1) {
+ if (keycode & 0x80) {
+ ps2_queue(&s->common, 0xf0);
+ }
+ if (s->scancode_set == 2) {
+ keycode = ps2_raw_keycode[keycode & 0x7f];
+ } else if (s->scancode_set == 3) {
+ keycode = ps2_raw_keycode_set3[keycode & 0x7f];
+ }
+ }
+ ps2_queue(&s->common, keycode);
+}
+
+static void ps2_keyboard_event(DeviceState *dev, QemuConsole *src,
+ InputEvent *evt)
+{
+ PS2KbdState *s = (PS2KbdState *)dev;
+ int scancodes[3], i, count;
+
+ qemu_system_wakeup_request(QEMU_WAKEUP_REASON_OTHER);
+ count = qemu_input_key_value_to_scancode(evt->u.key->key,
+ evt->u.key->down,
+ scancodes);
+ for (i = 0; i < count; i++) {
+ ps2_put_keycode(s, scancodes[i]);
+ }
+}
+
+uint32_t ps2_read_data(void *opaque)
+{
+ PS2State *s = (PS2State *)opaque;
+ PS2Queue *q;
+ int val, index;
+
+ trace_ps2_read_data(opaque);
+ q = &s->queue;
+ if (q->count == 0) {
+ /* NOTE: if no data left, we return the last keyboard one
+ (needed for EMM386) */
+ /* XXX: need a timer to do things correctly */
+ index = q->rptr - 1;
+ if (index < 0)
+ index = PS2_QUEUE_SIZE - 1;
+ val = q->data[index];
+ } else {
+ val = q->data[q->rptr];
+ if (++q->rptr == PS2_QUEUE_SIZE)
+ q->rptr = 0;
+ q->count--;
+ /* reading deasserts IRQ */
+ s->update_irq(s->update_arg, 0);
+ /* reassert IRQs if data left */
+ s->update_irq(s->update_arg, q->count != 0);
+ }
+ return val;
+}
+
+static void ps2_set_ledstate(PS2KbdState *s, int ledstate)
+{
+ trace_ps2_set_ledstate(s, ledstate);
+ s->ledstate = ledstate;
+ kbd_put_ledstate(ledstate);
+}
+
+static void ps2_reset_keyboard(PS2KbdState *s)
+{
+ trace_ps2_reset_keyboard(s);
+ s->scan_enabled = 1;
+ s->scancode_set = 2;
+ ps2_set_ledstate(s, 0);
+}
+
+void ps2_write_keyboard(void *opaque, int val)
+{
+ PS2KbdState *s = (PS2KbdState *)opaque;
+
+ trace_ps2_write_keyboard(opaque, val);
+ switch(s->common.write_cmd) {
+ default:
+ case -1:
+ switch(val) {
+ case 0x00:
+ ps2_queue(&s->common, KBD_REPLY_ACK);
+ break;
+ case 0x05:
+ ps2_queue(&s->common, KBD_REPLY_RESEND);
+ break;
+ case KBD_CMD_GET_ID:
+ ps2_queue(&s->common, KBD_REPLY_ACK);
+ /* We emulate a MF2 AT keyboard here */
+ ps2_queue(&s->common, KBD_REPLY_ID);
+ if (s->translate)
+ ps2_queue(&s->common, 0x41);
+ else
+ ps2_queue(&s->common, 0x83);
+ break;
+ case KBD_CMD_ECHO:
+ ps2_queue(&s->common, KBD_CMD_ECHO);
+ break;
+ case KBD_CMD_ENABLE:
+ s->scan_enabled = 1;
+ ps2_queue(&s->common, KBD_REPLY_ACK);
+ break;
+ case KBD_CMD_SCANCODE:
+ case KBD_CMD_SET_LEDS:
+ case KBD_CMD_SET_RATE:
+ s->common.write_cmd = val;
+ ps2_queue(&s->common, KBD_REPLY_ACK);
+ break;
+ case KBD_CMD_RESET_DISABLE:
+ ps2_reset_keyboard(s);
+ s->scan_enabled = 0;
+ ps2_queue(&s->common, KBD_REPLY_ACK);
+ break;
+ case KBD_CMD_RESET_ENABLE:
+ ps2_reset_keyboard(s);
+ s->scan_enabled = 1;
+ ps2_queue(&s->common, KBD_REPLY_ACK);
+ break;
+ case KBD_CMD_RESET:
+ ps2_reset_keyboard(s);
+ ps2_queue(&s->common, KBD_REPLY_ACK);
+ ps2_queue(&s->common, KBD_REPLY_POR);
+ break;
+ default:
+ ps2_queue(&s->common, KBD_REPLY_ACK);
+ break;
+ }
+ break;
+ case KBD_CMD_SCANCODE:
+ if (val == 0) {
+ if (s->scancode_set == 1)
+ ps2_put_keycode(s, 0x43);
+ else if (s->scancode_set == 2)
+ ps2_put_keycode(s, 0x41);
+ else if (s->scancode_set == 3)
+ ps2_put_keycode(s, 0x3f);
+ } else {
+ if (val >= 1 && val <= 3)
+ s->scancode_set = val;
+ ps2_queue(&s->common, KBD_REPLY_ACK);
+ }
+ s->common.write_cmd = -1;
+ break;
+ case KBD_CMD_SET_LEDS:
+ ps2_set_ledstate(s, val);
+ ps2_queue(&s->common, KBD_REPLY_ACK);
+ s->common.write_cmd = -1;
+ break;
+ case KBD_CMD_SET_RATE:
+ ps2_queue(&s->common, KBD_REPLY_ACK);
+ s->common.write_cmd = -1;
+ break;
+ }
+}
+
+/* Set the scancode translation mode.
+ 0 = raw scancodes.
+ 1 = translated scancodes (used by qemu internally). */
+
+void ps2_keyboard_set_translation(void *opaque, int mode)
+{
+ PS2KbdState *s = (PS2KbdState *)opaque;
+ trace_ps2_keyboard_set_translation(opaque, mode);
+ s->translate = mode;
+}
+
+static void ps2_mouse_send_packet(PS2MouseState *s)
+{
+ unsigned int b;
+ int dx1, dy1, dz1;
+
+ dx1 = s->mouse_dx;
+ dy1 = s->mouse_dy;
+ dz1 = s->mouse_dz;
+ /* XXX: increase range to 8 bits ? */
+ if (dx1 > 127)
+ dx1 = 127;
+ else if (dx1 < -127)
+ dx1 = -127;
+ if (dy1 > 127)
+ dy1 = 127;
+ else if (dy1 < -127)
+ dy1 = -127;
+ b = 0x08 | ((dx1 < 0) << 4) | ((dy1 < 0) << 5) | (s->mouse_buttons & 0x07);
+ ps2_queue(&s->common, b);
+ ps2_queue(&s->common, dx1 & 0xff);
+ ps2_queue(&s->common, dy1 & 0xff);
+ /* extra byte for IMPS/2 or IMEX */
+ switch(s->mouse_type) {
+ default:
+ break;
+ case 3:
+ if (dz1 > 127)
+ dz1 = 127;
+ else if (dz1 < -127)
+ dz1 = -127;
+ ps2_queue(&s->common, dz1 & 0xff);
+ break;
+ case 4:
+ if (dz1 > 7)
+ dz1 = 7;
+ else if (dz1 < -7)
+ dz1 = -7;
+ b = (dz1 & 0x0f) | ((s->mouse_buttons & 0x18) << 1);
+ ps2_queue(&s->common, b);
+ break;
+ }
+
+ trace_ps2_mouse_send_packet(s, dx1, dy1, dz1, b);
+ /* update deltas */
+ s->mouse_dx -= dx1;
+ s->mouse_dy -= dy1;
+ s->mouse_dz -= dz1;
+}
+
+static void ps2_mouse_event(DeviceState *dev, QemuConsole *src,
+ InputEvent *evt)
+{
+ static const int bmap[INPUT_BUTTON_MAX] = {
+ [INPUT_BUTTON_LEFT] = MOUSE_EVENT_LBUTTON,
+ [INPUT_BUTTON_MIDDLE] = MOUSE_EVENT_MBUTTON,
+ [INPUT_BUTTON_RIGHT] = MOUSE_EVENT_RBUTTON,
+ };
+ PS2MouseState *s = (PS2MouseState *)dev;
+
+ /* check if deltas are recorded when disabled */
+ if (!(s->mouse_status & MOUSE_STATUS_ENABLED))
+ return;
+
+ switch (evt->type) {
+ case INPUT_EVENT_KIND_REL:
+ if (evt->u.rel->axis == INPUT_AXIS_X) {
+ s->mouse_dx += evt->u.rel->value;
+ } else if (evt->u.rel->axis == INPUT_AXIS_Y) {
+ s->mouse_dy -= evt->u.rel->value;
+ }
+ break;
+
+ case INPUT_EVENT_KIND_BTN:
+ if (evt->u.btn->down) {
+ s->mouse_buttons |= bmap[evt->u.btn->button];
+ if (evt->u.btn->button == INPUT_BUTTON_WHEEL_UP) {
+ s->mouse_dz--;
+ } else if (evt->u.btn->button == INPUT_BUTTON_WHEEL_DOWN) {
+ s->mouse_dz++;
+ }
+ } else {
+ s->mouse_buttons &= ~bmap[evt->u.btn->button];
+ }
+ break;
+
+ default:
+ /* keep gcc happy */
+ break;
+ }
+}
+
+static void ps2_mouse_sync(DeviceState *dev)
+{
+ PS2MouseState *s = (PS2MouseState *)dev;
+
+ if (s->mouse_buttons) {
+ qemu_system_wakeup_request(QEMU_WAKEUP_REASON_OTHER);
+ }
+ if (!(s->mouse_status & MOUSE_STATUS_REMOTE)) {
+ while (s->common.queue.count < PS2_QUEUE_SIZE - 4) {
+ /* if not remote, send event. Multiple events are sent if
+ too big deltas */
+ ps2_mouse_send_packet(s);
+ if (s->mouse_dx == 0 && s->mouse_dy == 0 && s->mouse_dz == 0)
+ break;
+ }
+ }
+}
+
+void ps2_mouse_fake_event(void *opaque)
+{
+ PS2MouseState *s = opaque;
+ trace_ps2_mouse_fake_event(opaque);
+ s->mouse_dx++;
+ ps2_mouse_sync(opaque);
+}
+
+void ps2_write_mouse(void *opaque, int val)
+{
+ PS2MouseState *s = (PS2MouseState *)opaque;
+
+ trace_ps2_write_mouse(opaque, val);
+#ifdef DEBUG_MOUSE
+ printf("kbd: write mouse 0x%02x\n", val);
+#endif
+ switch(s->common.write_cmd) {
+ default:
+ case -1:
+ /* mouse command */
+ if (s->mouse_wrap) {
+ if (val == AUX_RESET_WRAP) {
+ s->mouse_wrap = 0;
+ ps2_queue(&s->common, AUX_ACK);
+ return;
+ } else if (val != AUX_RESET) {
+ ps2_queue(&s->common, val);
+ return;
+ }
+ }
+ switch(val) {
+ case AUX_SET_SCALE11:
+ s->mouse_status &= ~MOUSE_STATUS_SCALE21;
+ ps2_queue(&s->common, AUX_ACK);
+ break;
+ case AUX_SET_SCALE21:
+ s->mouse_status |= MOUSE_STATUS_SCALE21;
+ ps2_queue(&s->common, AUX_ACK);
+ break;
+ case AUX_SET_STREAM:
+ s->mouse_status &= ~MOUSE_STATUS_REMOTE;
+ ps2_queue(&s->common, AUX_ACK);
+ break;
+ case AUX_SET_WRAP:
+ s->mouse_wrap = 1;
+ ps2_queue(&s->common, AUX_ACK);
+ break;
+ case AUX_SET_REMOTE:
+ s->mouse_status |= MOUSE_STATUS_REMOTE;
+ ps2_queue(&s->common, AUX_ACK);
+ break;
+ case AUX_GET_TYPE:
+ ps2_queue(&s->common, AUX_ACK);
+ ps2_queue(&s->common, s->mouse_type);
+ break;
+ case AUX_SET_RES:
+ case AUX_SET_SAMPLE:
+ s->common.write_cmd = val;
+ ps2_queue(&s->common, AUX_ACK);
+ break;
+ case AUX_GET_SCALE:
+ ps2_queue(&s->common, AUX_ACK);
+ ps2_queue(&s->common, s->mouse_status);
+ ps2_queue(&s->common, s->mouse_resolution);
+ ps2_queue(&s->common, s->mouse_sample_rate);
+ break;
+ case AUX_POLL:
+ ps2_queue(&s->common, AUX_ACK);
+ ps2_mouse_send_packet(s);
+ break;
+ case AUX_ENABLE_DEV:
+ s->mouse_status |= MOUSE_STATUS_ENABLED;
+ ps2_queue(&s->common, AUX_ACK);
+ break;
+ case AUX_DISABLE_DEV:
+ s->mouse_status &= ~MOUSE_STATUS_ENABLED;
+ ps2_queue(&s->common, AUX_ACK);
+ break;
+ case AUX_SET_DEFAULT:
+ s->mouse_sample_rate = 100;
+ s->mouse_resolution = 2;
+ s->mouse_status = 0;
+ ps2_queue(&s->common, AUX_ACK);
+ break;
+ case AUX_RESET:
+ s->mouse_sample_rate = 100;
+ s->mouse_resolution = 2;
+ s->mouse_status = 0;
+ s->mouse_type = 0;
+ ps2_queue(&s->common, AUX_ACK);
+ ps2_queue(&s->common, 0xaa);
+ ps2_queue(&s->common, s->mouse_type);
+ break;
+ default:
+ break;
+ }
+ break;
+ case AUX_SET_SAMPLE:
+ s->mouse_sample_rate = val;
+ /* detect IMPS/2 or IMEX */
+ switch(s->mouse_detect_state) {
+ default:
+ case 0:
+ if (val == 200)
+ s->mouse_detect_state = 1;
+ break;
+ case 1:
+ if (val == 100)
+ s->mouse_detect_state = 2;
+ else if (val == 200)
+ s->mouse_detect_state = 3;
+ else
+ s->mouse_detect_state = 0;
+ break;
+ case 2:
+ if (val == 80)
+ s->mouse_type = 3; /* IMPS/2 */
+ s->mouse_detect_state = 0;
+ break;
+ case 3:
+ if (val == 80)
+ s->mouse_type = 4; /* IMEX */
+ s->mouse_detect_state = 0;
+ break;
+ }
+ ps2_queue(&s->common, AUX_ACK);
+ s->common.write_cmd = -1;
+ break;
+ case AUX_SET_RES:
+ s->mouse_resolution = val;
+ ps2_queue(&s->common, AUX_ACK);
+ s->common.write_cmd = -1;
+ break;
+ }
+}
+
+static void ps2_common_reset(PS2State *s)
+{
+ PS2Queue *q;
+ s->write_cmd = -1;
+ q = &s->queue;
+ q->rptr = 0;
+ q->wptr = 0;
+ q->count = 0;
+ s->update_irq(s->update_arg, 0);
+}
+
+static void ps2_common_post_load(PS2State *s)
+{
+ PS2Queue *q = &s->queue;
+ int size;
+ int i;
+ int tmp_data[PS2_QUEUE_SIZE];
+
+ /* set the useful data buffer queue size, < PS2_QUEUE_SIZE */
+ size = q->count > PS2_QUEUE_SIZE ? 0 : q->count;
+
+ /* move the queue elements to the start of data array */
+ if (size > 0) {
+ for (i = 0; i < size; i++) {
+ /* move the queue elements to the temporary buffer */
+ tmp_data[i] = q->data[q->rptr];
+ if (++q->rptr == 256) {
+ q->rptr = 0;
+ }
+ }
+ memcpy(q->data, tmp_data, size);
+ }
+ /* reset rptr/wptr/count */
+ q->rptr = 0;
+ q->wptr = size;
+ q->count = size;
+ s->update_irq(s->update_arg, q->count != 0);
+}
+
+static void ps2_kbd_reset(void *opaque)
+{
+ PS2KbdState *s = (PS2KbdState *) opaque;
+
+ trace_ps2_kbd_reset(opaque);
+ ps2_common_reset(&s->common);
+ s->scan_enabled = 0;
+ s->translate = 0;
+ s->scancode_set = 0;
+}
+
+static void ps2_mouse_reset(void *opaque)
+{
+ PS2MouseState *s = (PS2MouseState *) opaque;
+
+ trace_ps2_mouse_reset(opaque);
+ ps2_common_reset(&s->common);
+ s->mouse_status = 0;
+ s->mouse_resolution = 0;
+ s->mouse_sample_rate = 0;
+ s->mouse_wrap = 0;
+ s->mouse_type = 0;
+ s->mouse_detect_state = 0;
+ s->mouse_dx = 0;
+ s->mouse_dy = 0;
+ s->mouse_dz = 0;
+ s->mouse_buttons = 0;
+}
+
+static const VMStateDescription vmstate_ps2_common = {
+ .name = "PS2 Common State",
+ .version_id = 3,
+ .minimum_version_id = 2,
+ .fields = (VMStateField[]) {
+ VMSTATE_INT32(write_cmd, PS2State),
+ VMSTATE_INT32(queue.rptr, PS2State),
+ VMSTATE_INT32(queue.wptr, PS2State),
+ VMSTATE_INT32(queue.count, PS2State),
+ VMSTATE_BUFFER(queue.data, PS2State),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static bool ps2_keyboard_ledstate_needed(void *opaque)
+{
+ PS2KbdState *s = opaque;
+
+ return s->ledstate != 0; /* 0 is default state */
+}
+
+static int ps2_kbd_ledstate_post_load(void *opaque, int version_id)
+{
+ PS2KbdState *s = opaque;
+
+ kbd_put_ledstate(s->ledstate);
+ return 0;
+}
+
+static const VMStateDescription vmstate_ps2_keyboard_ledstate = {
+ .name = "ps2kbd/ledstate",
+ .version_id = 3,
+ .minimum_version_id = 2,
+ .post_load = ps2_kbd_ledstate_post_load,
+ .needed = ps2_keyboard_ledstate_needed,
+ .fields = (VMStateField[]) {
+ VMSTATE_INT32(ledstate, PS2KbdState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static int ps2_kbd_post_load(void* opaque, int version_id)
+{
+ PS2KbdState *s = (PS2KbdState*)opaque;
+ PS2State *ps2 = &s->common;
+
+ if (version_id == 2)
+ s->scancode_set=2;
+
+ ps2_common_post_load(ps2);
+
+ return 0;
+}
+
+static void ps2_kbd_pre_save(void *opaque)
+{
+ PS2KbdState *s = (PS2KbdState *)opaque;
+ PS2State *ps2 = &s->common;
+
+ ps2_common_post_load(ps2);
+}
+
+static const VMStateDescription vmstate_ps2_keyboard = {
+ .name = "ps2kbd",
+ .version_id = 3,
+ .minimum_version_id = 2,
+ .post_load = ps2_kbd_post_load,
+ .pre_save = ps2_kbd_pre_save,
+ .fields = (VMStateField[]) {
+ VMSTATE_STRUCT(common, PS2KbdState, 0, vmstate_ps2_common, PS2State),
+ VMSTATE_INT32(scan_enabled, PS2KbdState),
+ VMSTATE_INT32(translate, PS2KbdState),
+ VMSTATE_INT32_V(scancode_set, PS2KbdState,3),
+ VMSTATE_END_OF_LIST()
+ },
+ .subsections = (const VMStateDescription*[]) {
+ &vmstate_ps2_keyboard_ledstate,
+ NULL
+ }
+};
+
+static int ps2_mouse_post_load(void *opaque, int version_id)
+{
+ PS2MouseState *s = (PS2MouseState *)opaque;
+ PS2State *ps2 = &s->common;
+
+ ps2_common_post_load(ps2);
+
+ return 0;
+}
+
+static void ps2_mouse_pre_save(void *opaque)
+{
+ PS2MouseState *s = (PS2MouseState *)opaque;
+ PS2State *ps2 = &s->common;
+
+ ps2_common_post_load(ps2);
+}
+
+static const VMStateDescription vmstate_ps2_mouse = {
+ .name = "ps2mouse",
+ .version_id = 2,
+ .minimum_version_id = 2,
+ .post_load = ps2_mouse_post_load,
+ .pre_save = ps2_mouse_pre_save,
+ .fields = (VMStateField[]) {
+ VMSTATE_STRUCT(common, PS2MouseState, 0, vmstate_ps2_common, PS2State),
+ VMSTATE_UINT8(mouse_status, PS2MouseState),
+ VMSTATE_UINT8(mouse_resolution, PS2MouseState),
+ VMSTATE_UINT8(mouse_sample_rate, PS2MouseState),
+ VMSTATE_UINT8(mouse_wrap, PS2MouseState),
+ VMSTATE_UINT8(mouse_type, PS2MouseState),
+ VMSTATE_UINT8(mouse_detect_state, PS2MouseState),
+ VMSTATE_INT32(mouse_dx, PS2MouseState),
+ VMSTATE_INT32(mouse_dy, PS2MouseState),
+ VMSTATE_INT32(mouse_dz, PS2MouseState),
+ VMSTATE_UINT8(mouse_buttons, PS2MouseState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static QemuInputHandler ps2_keyboard_handler = {
+ .name = "QEMU PS/2 Keyboard",
+ .mask = INPUT_EVENT_MASK_KEY,
+ .event = ps2_keyboard_event,
+};
+
+void *ps2_kbd_init(void (*update_irq)(void *, int), void *update_arg)
+{
+ PS2KbdState *s = (PS2KbdState *)g_malloc0(sizeof(PS2KbdState));
+
+ trace_ps2_kbd_init(s);
+ s->common.update_irq = update_irq;
+ s->common.update_arg = update_arg;
+ s->scancode_set = 2;
+ vmstate_register(NULL, 0, &vmstate_ps2_keyboard, s);
+ qemu_input_handler_register((DeviceState *)s,
+ &ps2_keyboard_handler);
+ qemu_register_reset(ps2_kbd_reset, s);
+ return s;
+}
+
+static QemuInputHandler ps2_mouse_handler = {
+ .name = "QEMU PS/2 Mouse",
+ .mask = INPUT_EVENT_MASK_BTN | INPUT_EVENT_MASK_REL,
+ .event = ps2_mouse_event,
+ .sync = ps2_mouse_sync,
+};
+
+void *ps2_mouse_init(void (*update_irq)(void *, int), void *update_arg)
+{
+ PS2MouseState *s = (PS2MouseState *)g_malloc0(sizeof(PS2MouseState));
+
+ trace_ps2_mouse_init(s);
+ s->common.update_irq = update_irq;
+ s->common.update_arg = update_arg;
+ vmstate_register(NULL, 0, &vmstate_ps2_mouse, s);
+ qemu_input_handler_register((DeviceState *)s,
+ &ps2_mouse_handler);
+ qemu_register_reset(ps2_mouse_reset, s);
+ return s;
+}
diff --git a/src/hw/input/pxa2xx_keypad.c b/src/hw/input/pxa2xx_keypad.c
new file mode 100644
index 0000000..8501114
--- /dev/null
+++ b/src/hw/input/pxa2xx_keypad.c
@@ -0,0 +1,334 @@
+/*
+ * Intel PXA27X Keypad Controller emulation.
+ *
+ * Copyright (c) 2007 MontaVista Software, Inc
+ * Written by Armin Kuster <akuster@kama-aina.net>
+ * or <Akuster@mvista.com>
+ *
+ * This code is licensed under the GPLv2.
+ *
+ * Contributions after 2012-01-13 are licensed under the terms of the
+ * GNU GPL, version 2 or (at your option) any later version.
+ */
+
+#include "hw/hw.h"
+#include "hw/arm/pxa.h"
+#include "ui/console.h"
+
+/*
+ * Keypad
+ */
+#define KPC 0x00 /* Keypad Interface Control register */
+#define KPDK 0x08 /* Keypad Interface Direct Key register */
+#define KPREC 0x10 /* Keypad Interface Rotary Encoder register */
+#define KPMK 0x18 /* Keypad Interface Matrix Key register */
+#define KPAS 0x20 /* Keypad Interface Automatic Scan register */
+#define KPASMKP0 0x28 /* Keypad Interface Automatic Scan Multiple
+ Key Presser register 0 */
+#define KPASMKP1 0x30 /* Keypad Interface Automatic Scan Multiple
+ Key Presser register 1 */
+#define KPASMKP2 0x38 /* Keypad Interface Automatic Scan Multiple
+ Key Presser register 2 */
+#define KPASMKP3 0x40 /* Keypad Interface Automatic Scan Multiple
+ Key Presser register 3 */
+#define KPKDI 0x48 /* Keypad Interface Key Debounce Interval
+ register */
+
+/* Keypad defines */
+#define KPC_AS (0x1 << 30) /* Automatic Scan bit */
+#define KPC_ASACT (0x1 << 29) /* Automatic Scan on Activity */
+#define KPC_MI (0x1 << 22) /* Matrix interrupt bit */
+#define KPC_IMKP (0x1 << 21) /* Ignore Multiple Key Press */
+#define KPC_MS7 (0x1 << 20) /* Matrix scan line 7 */
+#define KPC_MS6 (0x1 << 19) /* Matrix scan line 6 */
+#define KPC_MS5 (0x1 << 18) /* Matrix scan line 5 */
+#define KPC_MS4 (0x1 << 17) /* Matrix scan line 4 */
+#define KPC_MS3 (0x1 << 16) /* Matrix scan line 3 */
+#define KPC_MS2 (0x1 << 15) /* Matrix scan line 2 */
+#define KPC_MS1 (0x1 << 14) /* Matrix scan line 1 */
+#define KPC_MS0 (0x1 << 13) /* Matrix scan line 0 */
+#define KPC_ME (0x1 << 12) /* Matrix Keypad Enable */
+#define KPC_MIE (0x1 << 11) /* Matrix Interrupt Enable */
+#define KPC_DK_DEB_SEL (0x1 << 9) /* Direct Keypad Debounce Select */
+#define KPC_DI (0x1 << 5) /* Direct key interrupt bit */
+#define KPC_RE_ZERO_DEB (0x1 << 4) /* Rotary Encoder Zero Debounce */
+#define KPC_REE1 (0x1 << 3) /* Rotary Encoder1 Enable */
+#define KPC_REE0 (0x1 << 2) /* Rotary Encoder0 Enable */
+#define KPC_DE (0x1 << 1) /* Direct Keypad Enable */
+#define KPC_DIE (0x1 << 0) /* Direct Keypad interrupt Enable */
+
+#define KPDK_DKP (0x1 << 31)
+#define KPDK_DK7 (0x1 << 7)
+#define KPDK_DK6 (0x1 << 6)
+#define KPDK_DK5 (0x1 << 5)
+#define KPDK_DK4 (0x1 << 4)
+#define KPDK_DK3 (0x1 << 3)
+#define KPDK_DK2 (0x1 << 2)
+#define KPDK_DK1 (0x1 << 1)
+#define KPDK_DK0 (0x1 << 0)
+
+#define KPREC_OF1 (0x1 << 31)
+#define KPREC_UF1 (0x1 << 30)
+#define KPREC_OF0 (0x1 << 15)
+#define KPREC_UF0 (0x1 << 14)
+
+#define KPMK_MKP (0x1 << 31)
+#define KPAS_SO (0x1 << 31)
+#define KPASMKPx_SO (0x1 << 31)
+
+
+#define KPASMKPx_MKC(row, col) (1 << (row + 16 * (col % 2)))
+
+#define PXAKBD_MAXROW 8
+#define PXAKBD_MAXCOL 8
+
+struct PXA2xxKeyPadState {
+ MemoryRegion iomem;
+ qemu_irq irq;
+ const struct keymap *map;
+ int pressed_cnt;
+ int alt_code;
+
+ uint32_t kpc;
+ uint32_t kpdk;
+ uint32_t kprec;
+ uint32_t kpmk;
+ uint32_t kpas;
+ uint32_t kpasmkp[4];
+ uint32_t kpkdi;
+};
+
+static void pxa27x_keypad_find_pressed_key(PXA2xxKeyPadState *kp, int *row, int *col)
+{
+ int i;
+ for (i = 0; i < 4; i++)
+ {
+ *col = i * 2;
+ for (*row = 0; *row < 8; (*row)++) {
+ if (kp->kpasmkp[i] & (1 << *row))
+ return;
+ }
+ *col = i * 2 + 1;
+ for (*row = 0; *row < 8; (*row)++) {
+ if (kp->kpasmkp[i] & (1 << (*row + 16)))
+ return;
+ }
+ }
+}
+
+static void pxa27x_keyboard_event (PXA2xxKeyPadState *kp, int keycode)
+{
+ int row, col, rel, assert_irq = 0;
+ uint32_t val;
+
+ if (keycode == 0xe0) {
+ kp->alt_code = 1;
+ return;
+ }
+
+ if(!(kp->kpc & KPC_ME)) /* skip if not enabled */
+ return;
+
+ rel = (keycode & 0x80) ? 1 : 0; /* key release from qemu */
+ keycode &= ~0x80; /* strip qemu key release bit */
+ if (kp->alt_code) {
+ keycode |= 0x80;
+ kp->alt_code = 0;
+ }
+
+ row = kp->map[keycode].row;
+ col = kp->map[keycode].column;
+ if (row == -1 || col == -1) {
+ return;
+ }
+
+ val = KPASMKPx_MKC(row, col);
+ if (rel) {
+ if (kp->kpasmkp[col / 2] & val) {
+ kp->kpasmkp[col / 2] &= ~val;
+ kp->pressed_cnt--;
+ assert_irq = 1;
+ }
+ } else {
+ if (!(kp->kpasmkp[col / 2] & val)) {
+ kp->kpasmkp[col / 2] |= val;
+ kp->pressed_cnt++;
+ assert_irq = 1;
+ }
+ }
+ kp->kpas = ((kp->pressed_cnt & 0x1f) << 26) | (0xf << 4) | 0xf;
+ if (kp->pressed_cnt == 1) {
+ kp->kpas &= ~((0xf << 4) | 0xf);
+ if (rel) {
+ pxa27x_keypad_find_pressed_key(kp, &row, &col);
+ }
+ kp->kpas |= ((row & 0xf) << 4) | (col & 0xf);
+ }
+
+ if (!(kp->kpc & (KPC_AS | KPC_ASACT)))
+ assert_irq = 0;
+
+ if (assert_irq && (kp->kpc & KPC_MIE)) {
+ kp->kpc |= KPC_MI;
+ qemu_irq_raise(kp->irq);
+ }
+}
+
+static uint64_t pxa2xx_keypad_read(void *opaque, hwaddr offset,
+ unsigned size)
+{
+ PXA2xxKeyPadState *s = (PXA2xxKeyPadState *) opaque;
+ uint32_t tmp;
+
+ switch (offset) {
+ case KPC:
+ tmp = s->kpc;
+ if(tmp & KPC_MI)
+ s->kpc &= ~(KPC_MI);
+ if(tmp & KPC_DI)
+ s->kpc &= ~(KPC_DI);
+ qemu_irq_lower(s->irq);
+ return tmp;
+ break;
+ case KPDK:
+ return s->kpdk;
+ break;
+ case KPREC:
+ tmp = s->kprec;
+ if(tmp & KPREC_OF1)
+ s->kprec &= ~(KPREC_OF1);
+ if(tmp & KPREC_UF1)
+ s->kprec &= ~(KPREC_UF1);
+ if(tmp & KPREC_OF0)
+ s->kprec &= ~(KPREC_OF0);
+ if(tmp & KPREC_UF0)
+ s->kprec &= ~(KPREC_UF0);
+ return tmp;
+ break;
+ case KPMK:
+ tmp = s->kpmk;
+ if(tmp & KPMK_MKP)
+ s->kpmk &= ~(KPMK_MKP);
+ return tmp;
+ break;
+ case KPAS:
+ return s->kpas;
+ break;
+ case KPASMKP0:
+ return s->kpasmkp[0];
+ break;
+ case KPASMKP1:
+ return s->kpasmkp[1];
+ break;
+ case KPASMKP2:
+ return s->kpasmkp[2];
+ break;
+ case KPASMKP3:
+ return s->kpasmkp[3];
+ break;
+ case KPKDI:
+ return s->kpkdi;
+ break;
+ default:
+ hw_error("%s: Bad offset " REG_FMT "\n", __FUNCTION__, offset);
+ }
+
+ return 0;
+}
+
+static void pxa2xx_keypad_write(void *opaque, hwaddr offset,
+ uint64_t value, unsigned size)
+{
+ PXA2xxKeyPadState *s = (PXA2xxKeyPadState *) opaque;
+
+ switch (offset) {
+ case KPC:
+ s->kpc = value;
+ if (s->kpc & KPC_AS) {
+ s->kpc &= ~(KPC_AS);
+ }
+ break;
+ case KPDK:
+ s->kpdk = value;
+ break;
+ case KPREC:
+ s->kprec = value;
+ break;
+ case KPMK:
+ s->kpmk = value;
+ break;
+ case KPAS:
+ s->kpas = value;
+ break;
+ case KPASMKP0:
+ s->kpasmkp[0] = value;
+ break;
+ case KPASMKP1:
+ s->kpasmkp[1] = value;
+ break;
+ case KPASMKP2:
+ s->kpasmkp[2] = value;
+ break;
+ case KPASMKP3:
+ s->kpasmkp[3] = value;
+ break;
+ case KPKDI:
+ s->kpkdi = value;
+ break;
+
+ default:
+ hw_error("%s: Bad offset " REG_FMT "\n", __FUNCTION__, offset);
+ }
+}
+
+static const MemoryRegionOps pxa2xx_keypad_ops = {
+ .read = pxa2xx_keypad_read,
+ .write = pxa2xx_keypad_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static const VMStateDescription vmstate_pxa2xx_keypad = {
+ .name = "pxa2xx_keypad",
+ .version_id = 0,
+ .minimum_version_id = 0,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32(kpc, PXA2xxKeyPadState),
+ VMSTATE_UINT32(kpdk, PXA2xxKeyPadState),
+ VMSTATE_UINT32(kprec, PXA2xxKeyPadState),
+ VMSTATE_UINT32(kpmk, PXA2xxKeyPadState),
+ VMSTATE_UINT32(kpas, PXA2xxKeyPadState),
+ VMSTATE_UINT32_ARRAY(kpasmkp, PXA2xxKeyPadState, 4),
+ VMSTATE_UINT32(kpkdi, PXA2xxKeyPadState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+PXA2xxKeyPadState *pxa27x_keypad_init(MemoryRegion *sysmem,
+ hwaddr base,
+ qemu_irq irq)
+{
+ PXA2xxKeyPadState *s;
+
+ s = (PXA2xxKeyPadState *) g_malloc0(sizeof(PXA2xxKeyPadState));
+ s->irq = irq;
+
+ memory_region_init_io(&s->iomem, NULL, &pxa2xx_keypad_ops, s,
+ "pxa2xx-keypad", 0x00100000);
+ memory_region_add_subregion(sysmem, base, &s->iomem);
+
+ vmstate_register(NULL, 0, &vmstate_pxa2xx_keypad, s);
+
+ return s;
+}
+
+void pxa27x_register_keypad(PXA2xxKeyPadState *kp,
+ const struct keymap *map, int size)
+{
+ if(!map || size < 0x80) {
+ fprintf(stderr, "%s - No PXA keypad map defined\n", __FUNCTION__);
+ exit(-1);
+ }
+
+ kp->map = map;
+ qemu_add_kbd_event_handler((QEMUPutKBDEvent *) pxa27x_keyboard_event, kp);
+}
diff --git a/src/hw/input/stellaris_input.c b/src/hw/input/stellaris_input.c
new file mode 100644
index 0000000..4d86c04
--- /dev/null
+++ b/src/hw/input/stellaris_input.c
@@ -0,0 +1,87 @@
+/*
+ * Gamepad style buttons connected to IRQ/GPIO lines
+ *
+ * Copyright (c) 2007 CodeSourcery.
+ * Written by Paul Brook
+ *
+ * This code is licensed under the GPL.
+ */
+#include "hw/hw.h"
+#include "hw/devices.h"
+#include "ui/console.h"
+
+typedef struct {
+ qemu_irq irq;
+ int keycode;
+ uint8_t pressed;
+} gamepad_button;
+
+typedef struct {
+ gamepad_button *buttons;
+ int num_buttons;
+ int extension;
+} gamepad_state;
+
+static void stellaris_gamepad_put_key(void * opaque, int keycode)
+{
+ gamepad_state *s = (gamepad_state *)opaque;
+ int i;
+ int down;
+
+ if (keycode == 0xe0 && !s->extension) {
+ s->extension = 0x80;
+ return;
+ }
+
+ down = (keycode & 0x80) == 0;
+ keycode = (keycode & 0x7f) | s->extension;
+
+ for (i = 0; i < s->num_buttons; i++) {
+ if (s->buttons[i].keycode == keycode
+ && s->buttons[i].pressed != down) {
+ s->buttons[i].pressed = down;
+ qemu_set_irq(s->buttons[i].irq, down);
+ }
+ }
+
+ s->extension = 0;
+}
+
+static const VMStateDescription vmstate_stellaris_button = {
+ .name = "stellaris_button",
+ .version_id = 0,
+ .minimum_version_id = 0,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT8(pressed, gamepad_button),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static const VMStateDescription vmstate_stellaris_gamepad = {
+ .name = "stellaris_gamepad",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_INT32(extension, gamepad_state),
+ VMSTATE_STRUCT_VARRAY_INT32(buttons, gamepad_state, num_buttons, 0,
+ vmstate_stellaris_button, gamepad_button),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+/* Returns an array of 5 output slots. */
+void stellaris_gamepad_init(int n, qemu_irq *irq, const int *keycode)
+{
+ gamepad_state *s;
+ int i;
+
+ s = g_new0(gamepad_state, 1);
+ s->buttons = g_new0(gamepad_button, n);
+ for (i = 0; i < n; i++) {
+ s->buttons[i].irq = irq[i];
+ s->buttons[i].keycode = keycode[i];
+ }
+ s->num_buttons = n;
+ qemu_add_kbd_event_handler(stellaris_gamepad_put_key, s);
+ vmstate_register(NULL, -1, &vmstate_stellaris_gamepad, s);
+}
diff --git a/src/hw/input/tsc2005.c b/src/hw/input/tsc2005.c
new file mode 100644
index 0000000..21d4f4d
--- /dev/null
+++ b/src/hw/input/tsc2005.c
@@ -0,0 +1,593 @@
+/*
+ * TI TSC2005 emulator.
+ *
+ * Copyright (c) 2006 Andrzej Zaborowski <balrog@zabor.org>
+ * Copyright (C) 2008 Nokia Corporation
+ *
+ * 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 or
+ * (at your option) version 3 of the License.
+ *
+ * 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 "hw/hw.h"
+#include "qemu/timer.h"
+#include "ui/console.h"
+#include "hw/devices.h"
+
+#define TSC_CUT_RESOLUTION(value, p) ((value) >> (16 - (p ? 12 : 10)))
+
+typedef struct {
+ qemu_irq pint; /* Combination of the nPENIRQ and DAV signals */
+ QEMUTimer *timer;
+ uint16_t model;
+
+ int x, y;
+ int pressure;
+
+ int state, reg, irq, command;
+ uint16_t data, dav;
+
+ int busy;
+ int enabled;
+ int host_mode;
+ int function;
+ int nextfunction;
+ int precision;
+ int nextprecision;
+ int filter;
+ int pin_func;
+ int timing[2];
+ int noise;
+ int reset;
+ int pdst;
+ int pnd0;
+ uint16_t temp_thr[2];
+ uint16_t aux_thr[2];
+
+ int tr[8];
+} TSC2005State;
+
+enum {
+ TSC_MODE_XYZ_SCAN = 0x0,
+ TSC_MODE_XY_SCAN,
+ TSC_MODE_X,
+ TSC_MODE_Y,
+ TSC_MODE_Z,
+ TSC_MODE_AUX,
+ TSC_MODE_TEMP1,
+ TSC_MODE_TEMP2,
+ TSC_MODE_AUX_SCAN,
+ TSC_MODE_X_TEST,
+ TSC_MODE_Y_TEST,
+ TSC_MODE_TS_TEST,
+ TSC_MODE_RESERVED,
+ TSC_MODE_XX_DRV,
+ TSC_MODE_YY_DRV,
+ TSC_MODE_YX_DRV,
+};
+
+static const uint16_t mode_regs[16] = {
+ 0xf000, /* X, Y, Z scan */
+ 0xc000, /* X, Y scan */
+ 0x8000, /* X */
+ 0x4000, /* Y */
+ 0x3000, /* Z */
+ 0x0800, /* AUX */
+ 0x0400, /* TEMP1 */
+ 0x0200, /* TEMP2 */
+ 0x0800, /* AUX scan */
+ 0x0040, /* X test */
+ 0x0020, /* Y test */
+ 0x0080, /* Short-circuit test */
+ 0x0000, /* Reserved */
+ 0x0000, /* X+, X- drivers */
+ 0x0000, /* Y+, Y- drivers */
+ 0x0000, /* Y+, X- drivers */
+};
+
+#define X_TRANSFORM(s) \
+ ((s->y * s->tr[0] - s->x * s->tr[1]) / s->tr[2] + s->tr[3])
+#define Y_TRANSFORM(s) \
+ ((s->y * s->tr[4] - s->x * s->tr[5]) / s->tr[6] + s->tr[7])
+#define Z1_TRANSFORM(s) \
+ ((400 - ((s)->x >> 7) + ((s)->pressure << 10)) << 4)
+#define Z2_TRANSFORM(s) \
+ ((4000 + ((s)->y >> 7) - ((s)->pressure << 10)) << 4)
+
+#define AUX_VAL (700 << 4) /* +/- 3 at 12-bit */
+#define TEMP1_VAL (1264 << 4) /* +/- 5 at 12-bit */
+#define TEMP2_VAL (1531 << 4) /* +/- 5 at 12-bit */
+
+static uint16_t tsc2005_read(TSC2005State *s, int reg)
+{
+ uint16_t ret;
+
+ switch (reg) {
+ case 0x0: /* X */
+ s->dav &= ~mode_regs[TSC_MODE_X];
+ return TSC_CUT_RESOLUTION(X_TRANSFORM(s), s->precision) +
+ (s->noise & 3);
+ case 0x1: /* Y */
+ s->dav &= ~mode_regs[TSC_MODE_Y];
+ s->noise ++;
+ return TSC_CUT_RESOLUTION(Y_TRANSFORM(s), s->precision) ^
+ (s->noise & 3);
+ case 0x2: /* Z1 */
+ s->dav &= 0xdfff;
+ return TSC_CUT_RESOLUTION(Z1_TRANSFORM(s), s->precision) -
+ (s->noise & 3);
+ case 0x3: /* Z2 */
+ s->dav &= 0xefff;
+ return TSC_CUT_RESOLUTION(Z2_TRANSFORM(s), s->precision) |
+ (s->noise & 3);
+
+ case 0x4: /* AUX */
+ s->dav &= ~mode_regs[TSC_MODE_AUX];
+ return TSC_CUT_RESOLUTION(AUX_VAL, s->precision);
+
+ case 0x5: /* TEMP1 */
+ s->dav &= ~mode_regs[TSC_MODE_TEMP1];
+ return TSC_CUT_RESOLUTION(TEMP1_VAL, s->precision) -
+ (s->noise & 5);
+ case 0x6: /* TEMP2 */
+ s->dav &= 0xdfff;
+ s->dav &= ~mode_regs[TSC_MODE_TEMP2];
+ return TSC_CUT_RESOLUTION(TEMP2_VAL, s->precision) ^
+ (s->noise & 3);
+
+ case 0x7: /* Status */
+ ret = s->dav | (s->reset << 7) | (s->pdst << 2) | 0x0;
+ s->dav &= ~(mode_regs[TSC_MODE_X_TEST] | mode_regs[TSC_MODE_Y_TEST] |
+ mode_regs[TSC_MODE_TS_TEST]);
+ s->reset = 1;
+ return ret;
+
+ case 0x8: /* AUX high treshold */
+ return s->aux_thr[1];
+ case 0x9: /* AUX low treshold */
+ return s->aux_thr[0];
+
+ case 0xa: /* TEMP high treshold */
+ return s->temp_thr[1];
+ case 0xb: /* TEMP low treshold */
+ return s->temp_thr[0];
+
+ case 0xc: /* CFR0 */
+ return (s->pressure << 15) | ((!s->busy) << 14) |
+ (s->nextprecision << 13) | s->timing[0];
+ case 0xd: /* CFR1 */
+ return s->timing[1];
+ case 0xe: /* CFR2 */
+ return (s->pin_func << 14) | s->filter;
+
+ case 0xf: /* Function select status */
+ return s->function >= 0 ? 1 << s->function : 0;
+ }
+
+ /* Never gets here */
+ return 0xffff;
+}
+
+static void tsc2005_write(TSC2005State *s, int reg, uint16_t data)
+{
+ switch (reg) {
+ case 0x8: /* AUX high treshold */
+ s->aux_thr[1] = data;
+ break;
+ case 0x9: /* AUX low treshold */
+ s->aux_thr[0] = data;
+ break;
+
+ case 0xa: /* TEMP high treshold */
+ s->temp_thr[1] = data;
+ break;
+ case 0xb: /* TEMP low treshold */
+ s->temp_thr[0] = data;
+ break;
+
+ case 0xc: /* CFR0 */
+ s->host_mode = data >> 15;
+ if (s->enabled != !(data & 0x4000)) {
+ s->enabled = !(data & 0x4000);
+ fprintf(stderr, "%s: touchscreen sense %sabled\n",
+ __FUNCTION__, s->enabled ? "en" : "dis");
+ if (s->busy && !s->enabled)
+ timer_del(s->timer);
+ s->busy &= s->enabled;
+ }
+ s->nextprecision = (data >> 13) & 1;
+ s->timing[0] = data & 0x1fff;
+ if ((s->timing[0] >> 11) == 3)
+ fprintf(stderr, "%s: illegal conversion clock setting\n",
+ __FUNCTION__);
+ break;
+ case 0xd: /* CFR1 */
+ s->timing[1] = data & 0xf07;
+ break;
+ case 0xe: /* CFR2 */
+ s->pin_func = (data >> 14) & 3;
+ s->filter = data & 0x3fff;
+ break;
+
+ default:
+ fprintf(stderr, "%s: write into read-only register %x\n",
+ __FUNCTION__, reg);
+ }
+}
+
+/* This handles most of the chip's logic. */
+static void tsc2005_pin_update(TSC2005State *s)
+{
+ int64_t expires;
+ int pin_state;
+
+ switch (s->pin_func) {
+ case 0:
+ pin_state = !s->pressure && !!s->dav;
+ break;
+ case 1:
+ case 3:
+ default:
+ pin_state = !s->dav;
+ break;
+ case 2:
+ pin_state = !s->pressure;
+ }
+
+ if (pin_state != s->irq) {
+ s->irq = pin_state;
+ qemu_set_irq(s->pint, s->irq);
+ }
+
+ switch (s->nextfunction) {
+ case TSC_MODE_XYZ_SCAN:
+ case TSC_MODE_XY_SCAN:
+ if (!s->host_mode && s->dav)
+ s->enabled = 0;
+ if (!s->pressure)
+ return;
+ /* Fall through */
+ case TSC_MODE_AUX_SCAN:
+ break;
+
+ case TSC_MODE_X:
+ case TSC_MODE_Y:
+ case TSC_MODE_Z:
+ if (!s->pressure)
+ return;
+ /* Fall through */
+ case TSC_MODE_AUX:
+ case TSC_MODE_TEMP1:
+ case TSC_MODE_TEMP2:
+ case TSC_MODE_X_TEST:
+ case TSC_MODE_Y_TEST:
+ case TSC_MODE_TS_TEST:
+ if (s->dav)
+ s->enabled = 0;
+ break;
+
+ case TSC_MODE_RESERVED:
+ case TSC_MODE_XX_DRV:
+ case TSC_MODE_YY_DRV:
+ case TSC_MODE_YX_DRV:
+ default:
+ return;
+ }
+
+ if (!s->enabled || s->busy)
+ return;
+
+ s->busy = 1;
+ s->precision = s->nextprecision;
+ s->function = s->nextfunction;
+ s->pdst = !s->pnd0; /* Synchronised on internal clock */
+ expires = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + (get_ticks_per_sec() >> 7);
+ timer_mod(s->timer, expires);
+}
+
+static void tsc2005_reset(TSC2005State *s)
+{
+ s->state = 0;
+ s->pin_func = 0;
+ s->enabled = 0;
+ s->busy = 0;
+ s->nextprecision = 0;
+ s->nextfunction = 0;
+ s->timing[0] = 0;
+ s->timing[1] = 0;
+ s->irq = 0;
+ s->dav = 0;
+ s->reset = 0;
+ s->pdst = 1;
+ s->pnd0 = 0;
+ s->function = -1;
+ s->temp_thr[0] = 0x000;
+ s->temp_thr[1] = 0xfff;
+ s->aux_thr[0] = 0x000;
+ s->aux_thr[1] = 0xfff;
+
+ tsc2005_pin_update(s);
+}
+
+static uint8_t tsc2005_txrx_word(void *opaque, uint8_t value)
+{
+ TSC2005State *s = opaque;
+ uint32_t ret = 0;
+
+ switch (s->state ++) {
+ case 0:
+ if (value & 0x80) {
+ /* Command */
+ if (value & (1 << 1))
+ tsc2005_reset(s);
+ else {
+ s->nextfunction = (value >> 3) & 0xf;
+ s->nextprecision = (value >> 2) & 1;
+ if (s->enabled != !(value & 1)) {
+ s->enabled = !(value & 1);
+ fprintf(stderr, "%s: touchscreen sense %sabled\n",
+ __FUNCTION__, s->enabled ? "en" : "dis");
+ if (s->busy && !s->enabled)
+ timer_del(s->timer);
+ s->busy &= s->enabled;
+ }
+ tsc2005_pin_update(s);
+ }
+
+ s->state = 0;
+ } else if (value) {
+ /* Data transfer */
+ s->reg = (value >> 3) & 0xf;
+ s->pnd0 = (value >> 1) & 1;
+ s->command = value & 1;
+
+ if (s->command) {
+ /* Read */
+ s->data = tsc2005_read(s, s->reg);
+ tsc2005_pin_update(s);
+ } else
+ s->data = 0;
+ } else
+ s->state = 0;
+ break;
+
+ case 1:
+ if (s->command)
+ ret = (s->data >> 8) & 0xff;
+ else
+ s->data |= value << 8;
+ break;
+
+ case 2:
+ if (s->command)
+ ret = s->data & 0xff;
+ else {
+ s->data |= value;
+ tsc2005_write(s, s->reg, s->data);
+ tsc2005_pin_update(s);
+ }
+
+ s->state = 0;
+ break;
+ }
+
+ return ret;
+}
+
+uint32_t tsc2005_txrx(void *opaque, uint32_t value, int len)
+{
+ uint32_t ret = 0;
+
+ len &= ~7;
+ while (len > 0) {
+ len -= 8;
+ ret |= tsc2005_txrx_word(opaque, (value >> len) & 0xff) << len;
+ }
+
+ return ret;
+}
+
+static void tsc2005_timer_tick(void *opaque)
+{
+ TSC2005State *s = opaque;
+
+ /* Timer ticked -- a set of conversions has been finished. */
+
+ if (!s->busy)
+ return;
+
+ s->busy = 0;
+ s->dav |= mode_regs[s->function];
+ s->function = -1;
+ tsc2005_pin_update(s);
+}
+
+static void tsc2005_touchscreen_event(void *opaque,
+ int x, int y, int z, int buttons_state)
+{
+ TSC2005State *s = opaque;
+ int p = s->pressure;
+
+ if (buttons_state) {
+ s->x = x;
+ s->y = y;
+ }
+ s->pressure = !!buttons_state;
+
+ /*
+ * Note: We would get better responsiveness in the guest by
+ * signaling TS events immediately, but for now we simulate
+ * the first conversion delay for sake of correctness.
+ */
+ if (p != s->pressure)
+ tsc2005_pin_update(s);
+}
+
+static void tsc2005_save(QEMUFile *f, void *opaque)
+{
+ TSC2005State *s = (TSC2005State *) opaque;
+ int i;
+
+ qemu_put_be16(f, s->x);
+ qemu_put_be16(f, s->y);
+ qemu_put_byte(f, s->pressure);
+
+ qemu_put_byte(f, s->state);
+ qemu_put_byte(f, s->reg);
+ qemu_put_byte(f, s->command);
+
+ qemu_put_byte(f, s->irq);
+ qemu_put_be16s(f, &s->dav);
+ qemu_put_be16s(f, &s->data);
+
+ timer_put(f, s->timer);
+ qemu_put_byte(f, s->enabled);
+ qemu_put_byte(f, s->host_mode);
+ qemu_put_byte(f, s->function);
+ qemu_put_byte(f, s->nextfunction);
+ qemu_put_byte(f, s->precision);
+ qemu_put_byte(f, s->nextprecision);
+ qemu_put_be16(f, s->filter);
+ qemu_put_byte(f, s->pin_func);
+ qemu_put_be16(f, s->timing[0]);
+ qemu_put_be16(f, s->timing[1]);
+ qemu_put_be16s(f, &s->temp_thr[0]);
+ qemu_put_be16s(f, &s->temp_thr[1]);
+ qemu_put_be16s(f, &s->aux_thr[0]);
+ qemu_put_be16s(f, &s->aux_thr[1]);
+ qemu_put_be32(f, s->noise);
+ qemu_put_byte(f, s->reset);
+ qemu_put_byte(f, s->pdst);
+ qemu_put_byte(f, s->pnd0);
+
+ for (i = 0; i < 8; i ++)
+ qemu_put_be32(f, s->tr[i]);
+}
+
+static int tsc2005_load(QEMUFile *f, void *opaque, int version_id)
+{
+ TSC2005State *s = (TSC2005State *) opaque;
+ int i;
+
+ s->x = qemu_get_be16(f);
+ s->y = qemu_get_be16(f);
+ s->pressure = qemu_get_byte(f);
+
+ s->state = qemu_get_byte(f);
+ s->reg = qemu_get_byte(f);
+ s->command = qemu_get_byte(f);
+
+ s->irq = qemu_get_byte(f);
+ qemu_get_be16s(f, &s->dav);
+ qemu_get_be16s(f, &s->data);
+
+ timer_get(f, s->timer);
+ s->enabled = qemu_get_byte(f);
+ s->host_mode = qemu_get_byte(f);
+ s->function = qemu_get_byte(f);
+ s->nextfunction = qemu_get_byte(f);
+ s->precision = qemu_get_byte(f);
+ s->nextprecision = qemu_get_byte(f);
+ s->filter = qemu_get_be16(f);
+ s->pin_func = qemu_get_byte(f);
+ s->timing[0] = qemu_get_be16(f);
+ s->timing[1] = qemu_get_be16(f);
+ qemu_get_be16s(f, &s->temp_thr[0]);
+ qemu_get_be16s(f, &s->temp_thr[1]);
+ qemu_get_be16s(f, &s->aux_thr[0]);
+ qemu_get_be16s(f, &s->aux_thr[1]);
+ s->noise = qemu_get_be32(f);
+ s->reset = qemu_get_byte(f);
+ s->pdst = qemu_get_byte(f);
+ s->pnd0 = qemu_get_byte(f);
+
+ for (i = 0; i < 8; i ++)
+ s->tr[i] = qemu_get_be32(f);
+
+ s->busy = timer_pending(s->timer);
+ tsc2005_pin_update(s);
+
+ return 0;
+}
+
+void *tsc2005_init(qemu_irq pintdav)
+{
+ TSC2005State *s;
+
+ s = (TSC2005State *)
+ g_malloc0(sizeof(TSC2005State));
+ s->x = 400;
+ s->y = 240;
+ s->pressure = 0;
+ s->precision = s->nextprecision = 0;
+ s->timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, tsc2005_timer_tick, s);
+ s->pint = pintdav;
+ s->model = 0x2005;
+
+ s->tr[0] = 0;
+ s->tr[1] = 1;
+ s->tr[2] = 1;
+ s->tr[3] = 0;
+ s->tr[4] = 1;
+ s->tr[5] = 0;
+ s->tr[6] = 1;
+ s->tr[7] = 0;
+
+ tsc2005_reset(s);
+
+ qemu_add_mouse_event_handler(tsc2005_touchscreen_event, s, 1,
+ "QEMU TSC2005-driven Touchscreen");
+
+ qemu_register_reset((void *) tsc2005_reset, s);
+ register_savevm(NULL, "tsc2005", -1, 0, tsc2005_save, tsc2005_load, s);
+
+ return s;
+}
+
+/*
+ * Use tslib generated calibration data to generate ADC input values
+ * from the touchscreen. Assuming 12-bit precision was used during
+ * tslib calibration.
+ */
+void tsc2005_set_transform(void *opaque, MouseTransformInfo *info)
+{
+ TSC2005State *s = (TSC2005State *) opaque;
+
+ /* This version assumes touchscreen X & Y axis are parallel or
+ * perpendicular to LCD's X & Y axis in some way. */
+ if (abs(info->a[0]) > abs(info->a[1])) {
+ s->tr[0] = 0;
+ s->tr[1] = -info->a[6] * info->x;
+ s->tr[2] = info->a[0];
+ s->tr[3] = -info->a[2] / info->a[0];
+ s->tr[4] = info->a[6] * info->y;
+ s->tr[5] = 0;
+ s->tr[6] = info->a[4];
+ s->tr[7] = -info->a[5] / info->a[4];
+ } else {
+ s->tr[0] = info->a[6] * info->y;
+ s->tr[1] = 0;
+ s->tr[2] = info->a[1];
+ s->tr[3] = -info->a[2] / info->a[1];
+ s->tr[4] = 0;
+ s->tr[5] = -info->a[6] * info->x;
+ s->tr[6] = info->a[3];
+ s->tr[7] = -info->a[5] / info->a[3];
+ }
+
+ s->tr[0] >>= 11;
+ s->tr[1] >>= 11;
+ s->tr[3] <<= 4;
+ s->tr[4] >>= 11;
+ s->tr[5] >>= 11;
+ s->tr[7] <<= 4;
+}
diff --git a/src/hw/input/tsc210x.c b/src/hw/input/tsc210x.c
new file mode 100644
index 0000000..1073bbf
--- /dev/null
+++ b/src/hw/input/tsc210x.c
@@ -0,0 +1,1271 @@
+/*
+ * TI TSC2102 (touchscreen/sensors/audio controller) emulator.
+ * TI TSC2301 (touchscreen/sensors/keypad).
+ *
+ * Copyright (c) 2006 Andrzej Zaborowski <balrog@zabor.org>
+ * Copyright (C) 2008 Nokia Corporation
+ *
+ * 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 or
+ * (at your option) version 3 of the License.
+ *
+ * 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 "hw/hw.h"
+#include "audio/audio.h"
+#include "qemu/timer.h"
+#include "ui/console.h"
+#include "hw/arm/omap.h" /* For I2SCodec and uWireSlave */
+#include "hw/devices.h"
+
+#define TSC_DATA_REGISTERS_PAGE 0x0
+#define TSC_CONTROL_REGISTERS_PAGE 0x1
+#define TSC_AUDIO_REGISTERS_PAGE 0x2
+
+#define TSC_VERBOSE
+
+#define TSC_CUT_RESOLUTION(value, p) ((value) >> (16 - resolution[p]))
+
+typedef struct {
+ qemu_irq pint;
+ qemu_irq kbint;
+ qemu_irq davint;
+ QEMUTimer *timer;
+ QEMUSoundCard card;
+ uWireSlave chip;
+ I2SCodec codec;
+ uint8_t in_fifo[16384];
+ uint8_t out_fifo[16384];
+ uint16_t model;
+
+ int x, y;
+ int pressure;
+
+ int state, page, offset, irq;
+ uint16_t command, dav;
+
+ int busy;
+ int enabled;
+ int host_mode;
+ int function;
+ int nextfunction;
+ int precision;
+ int nextprecision;
+ int filter;
+ int pin_func;
+ int ref;
+ int timing;
+ int noise;
+
+ uint16_t audio_ctrl1;
+ uint16_t audio_ctrl2;
+ uint16_t audio_ctrl3;
+ uint16_t pll[3];
+ uint16_t volume;
+ int64_t volume_change;
+ int softstep;
+ uint16_t dac_power;
+ int64_t powerdown;
+ uint16_t filter_data[0x14];
+
+ const char *name;
+ SWVoiceIn *adc_voice[1];
+ SWVoiceOut *dac_voice[1];
+ int i2s_rx_rate;
+ int i2s_tx_rate;
+
+ int tr[8];
+
+ struct {
+ uint16_t down;
+ uint16_t mask;
+ int scan;
+ int debounce;
+ int mode;
+ int intr;
+ } kb;
+} TSC210xState;
+
+static const int resolution[4] = { 12, 8, 10, 12 };
+
+#define TSC_MODE_NO_SCAN 0x0
+#define TSC_MODE_XY_SCAN 0x1
+#define TSC_MODE_XYZ_SCAN 0x2
+#define TSC_MODE_X 0x3
+#define TSC_MODE_Y 0x4
+#define TSC_MODE_Z 0x5
+#define TSC_MODE_BAT1 0x6
+#define TSC_MODE_BAT2 0x7
+#define TSC_MODE_AUX 0x8
+#define TSC_MODE_AUX_SCAN 0x9
+#define TSC_MODE_TEMP1 0xa
+#define TSC_MODE_PORT_SCAN 0xb
+#define TSC_MODE_TEMP2 0xc
+#define TSC_MODE_XX_DRV 0xd
+#define TSC_MODE_YY_DRV 0xe
+#define TSC_MODE_YX_DRV 0xf
+
+static const uint16_t mode_regs[16] = {
+ 0x0000, /* No scan */
+ 0x0600, /* X, Y scan */
+ 0x0780, /* X, Y, Z scan */
+ 0x0400, /* X */
+ 0x0200, /* Y */
+ 0x0180, /* Z */
+ 0x0040, /* BAT1 */
+ 0x0030, /* BAT2 */
+ 0x0010, /* AUX */
+ 0x0010, /* AUX scan */
+ 0x0004, /* TEMP1 */
+ 0x0070, /* Port scan */
+ 0x0002, /* TEMP2 */
+ 0x0000, /* X+, X- drivers */
+ 0x0000, /* Y+, Y- drivers */
+ 0x0000, /* Y+, X- drivers */
+};
+
+#define X_TRANSFORM(s) \
+ ((s->y * s->tr[0] - s->x * s->tr[1]) / s->tr[2] + s->tr[3])
+#define Y_TRANSFORM(s) \
+ ((s->y * s->tr[4] - s->x * s->tr[5]) / s->tr[6] + s->tr[7])
+#define Z1_TRANSFORM(s) \
+ ((400 - ((s)->x >> 7) + ((s)->pressure << 10)) << 4)
+#define Z2_TRANSFORM(s) \
+ ((4000 + ((s)->y >> 7) - ((s)->pressure << 10)) << 4)
+
+#define BAT1_VAL 0x8660
+#define BAT2_VAL 0x0000
+#define AUX1_VAL 0x35c0
+#define AUX2_VAL 0xffff
+#define TEMP1_VAL 0x8c70
+#define TEMP2_VAL 0xa5b0
+
+#define TSC_POWEROFF_DELAY 50
+#define TSC_SOFTSTEP_DELAY 50
+
+static void tsc210x_reset(TSC210xState *s)
+{
+ s->state = 0;
+ s->pin_func = 2;
+ s->enabled = 0;
+ s->busy = 0;
+ s->nextfunction = 0;
+ s->ref = 0;
+ s->timing = 0;
+ s->irq = 0;
+ s->dav = 0;
+
+ s->audio_ctrl1 = 0x0000;
+ s->audio_ctrl2 = 0x4410;
+ s->audio_ctrl3 = 0x0000;
+ s->pll[0] = 0x1004;
+ s->pll[1] = 0x0000;
+ s->pll[2] = 0x1fff;
+ s->volume = 0xffff;
+ s->dac_power = 0x8540;
+ s->softstep = 1;
+ s->volume_change = 0;
+ s->powerdown = 0;
+ s->filter_data[0x00] = 0x6be3;
+ s->filter_data[0x01] = 0x9666;
+ s->filter_data[0x02] = 0x675d;
+ s->filter_data[0x03] = 0x6be3;
+ s->filter_data[0x04] = 0x9666;
+ s->filter_data[0x05] = 0x675d;
+ s->filter_data[0x06] = 0x7d83;
+ s->filter_data[0x07] = 0x84ee;
+ s->filter_data[0x08] = 0x7d83;
+ s->filter_data[0x09] = 0x84ee;
+ s->filter_data[0x0a] = 0x6be3;
+ s->filter_data[0x0b] = 0x9666;
+ s->filter_data[0x0c] = 0x675d;
+ s->filter_data[0x0d] = 0x6be3;
+ s->filter_data[0x0e] = 0x9666;
+ s->filter_data[0x0f] = 0x675d;
+ s->filter_data[0x10] = 0x7d83;
+ s->filter_data[0x11] = 0x84ee;
+ s->filter_data[0x12] = 0x7d83;
+ s->filter_data[0x13] = 0x84ee;
+
+ s->i2s_tx_rate = 0;
+ s->i2s_rx_rate = 0;
+
+ s->kb.scan = 1;
+ s->kb.debounce = 0;
+ s->kb.mask = 0x0000;
+ s->kb.mode = 3;
+ s->kb.intr = 0;
+
+ qemu_set_irq(s->pint, !s->irq);
+ qemu_set_irq(s->davint, !s->dav);
+ qemu_irq_raise(s->kbint);
+}
+
+typedef struct {
+ int rate;
+ int dsor;
+ int fsref;
+} TSC210xRateInfo;
+
+/* { rate, dsor, fsref } */
+static const TSC210xRateInfo tsc2102_rates[] = {
+ /* Fsref / 6.0 */
+ { 7350, 63, 1 },
+ { 8000, 63, 0 },
+ /* Fsref / 6.0 */
+ { 7350, 54, 1 },
+ { 8000, 54, 0 },
+ /* Fsref / 5.0 */
+ { 8820, 45, 1 },
+ { 9600, 45, 0 },
+ /* Fsref / 4.0 */
+ { 11025, 36, 1 },
+ { 12000, 36, 0 },
+ /* Fsref / 3.0 */
+ { 14700, 27, 1 },
+ { 16000, 27, 0 },
+ /* Fsref / 2.0 */
+ { 22050, 18, 1 },
+ { 24000, 18, 0 },
+ /* Fsref / 1.5 */
+ { 29400, 9, 1 },
+ { 32000, 9, 0 },
+ /* Fsref */
+ { 44100, 0, 1 },
+ { 48000, 0, 0 },
+
+ { 0, 0, 0 },
+};
+
+static inline void tsc210x_out_flush(TSC210xState *s, int len)
+{
+ uint8_t *data = s->codec.out.fifo + s->codec.out.start;
+ uint8_t *end = data + len;
+
+ while (data < end)
+ data += AUD_write(s->dac_voice[0], data, end - data) ?: (end - data);
+
+ s->codec.out.len -= len;
+ if (s->codec.out.len)
+ memmove(s->codec.out.fifo, end, s->codec.out.len);
+ s->codec.out.start = 0;
+}
+
+static void tsc210x_audio_out_cb(TSC210xState *s, int free_b)
+{
+ if (s->codec.out.len >= free_b) {
+ tsc210x_out_flush(s, free_b);
+ return;
+ }
+
+ s->codec.out.size = MIN(free_b, 16384);
+ qemu_irq_raise(s->codec.tx_start);
+}
+
+static void tsc2102_audio_rate_update(TSC210xState *s)
+{
+ const TSC210xRateInfo *rate;
+
+ s->codec.tx_rate = 0;
+ s->codec.rx_rate = 0;
+ if (s->dac_power & (1 << 15)) /* PWDNC */
+ return;
+
+ for (rate = tsc2102_rates; rate->rate; rate ++)
+ if (rate->dsor == (s->audio_ctrl1 & 0x3f) && /* DACFS */
+ rate->fsref == ((s->audio_ctrl3 >> 13) & 1))/* REFFS */
+ break;
+ if (!rate->rate) {
+ printf("%s: unknown sampling rate configured\n", __FUNCTION__);
+ return;
+ }
+
+ s->codec.tx_rate = rate->rate;
+}
+
+static void tsc2102_audio_output_update(TSC210xState *s)
+{
+ int enable;
+ struct audsettings fmt;
+
+ if (s->dac_voice[0]) {
+ tsc210x_out_flush(s, s->codec.out.len);
+ s->codec.out.size = 0;
+ AUD_set_active_out(s->dac_voice[0], 0);
+ AUD_close_out(&s->card, s->dac_voice[0]);
+ s->dac_voice[0] = NULL;
+ }
+ s->codec.cts = 0;
+
+ enable =
+ (~s->dac_power & (1 << 15)) && /* PWDNC */
+ (~s->dac_power & (1 << 10)); /* DAPWDN */
+ if (!enable || !s->codec.tx_rate)
+ return;
+
+ /* Force our own sampling rate even in slave DAC mode */
+ fmt.endianness = 0;
+ fmt.nchannels = 2;
+ fmt.freq = s->codec.tx_rate;
+ fmt.fmt = AUD_FMT_S16;
+
+ s->dac_voice[0] = AUD_open_out(&s->card, s->dac_voice[0],
+ "tsc2102.sink", s, (void *) tsc210x_audio_out_cb, &fmt);
+ if (s->dac_voice[0]) {
+ s->codec.cts = 1;
+ AUD_set_active_out(s->dac_voice[0], 1);
+ }
+}
+
+static uint16_t tsc2102_data_register_read(TSC210xState *s, int reg)
+{
+ switch (reg) {
+ case 0x00: /* X */
+ s->dav &= 0xfbff;
+ return TSC_CUT_RESOLUTION(X_TRANSFORM(s), s->precision) +
+ (s->noise & 3);
+
+ case 0x01: /* Y */
+ s->noise ++;
+ s->dav &= 0xfdff;
+ return TSC_CUT_RESOLUTION(Y_TRANSFORM(s), s->precision) ^
+ (s->noise & 3);
+
+ case 0x02: /* Z1 */
+ s->dav &= 0xfeff;
+ return TSC_CUT_RESOLUTION(Z1_TRANSFORM(s), s->precision) -
+ (s->noise & 3);
+
+ case 0x03: /* Z2 */
+ s->dav &= 0xff7f;
+ return TSC_CUT_RESOLUTION(Z2_TRANSFORM(s), s->precision) |
+ (s->noise & 3);
+
+ case 0x04: /* KPData */
+ if ((s->model & 0xff00) == 0x2300) {
+ if (s->kb.intr && (s->kb.mode & 2)) {
+ s->kb.intr = 0;
+ qemu_irq_raise(s->kbint);
+ }
+ return s->kb.down;
+ }
+
+ return 0xffff;
+
+ case 0x05: /* BAT1 */
+ s->dav &= 0xffbf;
+ return TSC_CUT_RESOLUTION(BAT1_VAL, s->precision) +
+ (s->noise & 6);
+
+ case 0x06: /* BAT2 */
+ s->dav &= 0xffdf;
+ return TSC_CUT_RESOLUTION(BAT2_VAL, s->precision);
+
+ case 0x07: /* AUX1 */
+ s->dav &= 0xffef;
+ return TSC_CUT_RESOLUTION(AUX1_VAL, s->precision);
+
+ case 0x08: /* AUX2 */
+ s->dav &= 0xfff7;
+ return 0xffff;
+
+ case 0x09: /* TEMP1 */
+ s->dav &= 0xfffb;
+ return TSC_CUT_RESOLUTION(TEMP1_VAL, s->precision) -
+ (s->noise & 5);
+
+ case 0x0a: /* TEMP2 */
+ s->dav &= 0xfffd;
+ return TSC_CUT_RESOLUTION(TEMP2_VAL, s->precision) ^
+ (s->noise & 3);
+
+ case 0x0b: /* DAC */
+ s->dav &= 0xfffe;
+ return 0xffff;
+
+ default:
+#ifdef TSC_VERBOSE
+ fprintf(stderr, "tsc2102_data_register_read: "
+ "no such register: 0x%02x\n", reg);
+#endif
+ return 0xffff;
+ }
+}
+
+static uint16_t tsc2102_control_register_read(
+ TSC210xState *s, int reg)
+{
+ switch (reg) {
+ case 0x00: /* TSC ADC */
+ return (s->pressure << 15) | ((!s->busy) << 14) |
+ (s->nextfunction << 10) | (s->nextprecision << 8) | s->filter;
+
+ case 0x01: /* Status / Keypad Control */
+ if ((s->model & 0xff00) == 0x2100)
+ return (s->pin_func << 14) | ((!s->enabled) << 13) |
+ (s->host_mode << 12) | ((!!s->dav) << 11) | s->dav;
+ else
+ return (s->kb.intr << 15) | ((s->kb.scan || !s->kb.down) << 14) |
+ (s->kb.debounce << 11);
+
+ case 0x02: /* DAC Control */
+ if ((s->model & 0xff00) == 0x2300)
+ return s->dac_power & 0x8000;
+ else
+ goto bad_reg;
+
+ case 0x03: /* Reference */
+ return s->ref;
+
+ case 0x04: /* Reset */
+ return 0xffff;
+
+ case 0x05: /* Configuration */
+ return s->timing;
+
+ case 0x06: /* Secondary configuration */
+ if ((s->model & 0xff00) == 0x2100)
+ goto bad_reg;
+ return ((!s->dav) << 15) | ((s->kb.mode & 1) << 14) | s->pll[2];
+
+ case 0x10: /* Keypad Mask */
+ if ((s->model & 0xff00) == 0x2100)
+ goto bad_reg;
+ return s->kb.mask;
+
+ default:
+ bad_reg:
+#ifdef TSC_VERBOSE
+ fprintf(stderr, "tsc2102_control_register_read: "
+ "no such register: 0x%02x\n", reg);
+#endif
+ return 0xffff;
+ }
+}
+
+static uint16_t tsc2102_audio_register_read(TSC210xState *s, int reg)
+{
+ int l_ch, r_ch;
+ uint16_t val;
+
+ switch (reg) {
+ case 0x00: /* Audio Control 1 */
+ return s->audio_ctrl1;
+
+ case 0x01:
+ return 0xff00;
+
+ case 0x02: /* DAC Volume Control */
+ return s->volume;
+
+ case 0x03:
+ return 0x8b00;
+
+ case 0x04: /* Audio Control 2 */
+ l_ch = 1;
+ r_ch = 1;
+ if (s->softstep && !(s->dac_power & (1 << 10))) {
+ l_ch = (qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) >
+ s->volume_change + TSC_SOFTSTEP_DELAY);
+ r_ch = (qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) >
+ s->volume_change + TSC_SOFTSTEP_DELAY);
+ }
+
+ return s->audio_ctrl2 | (l_ch << 3) | (r_ch << 2);
+
+ case 0x05: /* Stereo DAC Power Control */
+ return 0x2aa0 | s->dac_power |
+ (((s->dac_power & (1 << 10)) &&
+ (qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) >
+ s->powerdown + TSC_POWEROFF_DELAY)) << 6);
+
+ case 0x06: /* Audio Control 3 */
+ val = s->audio_ctrl3 | 0x0001;
+ s->audio_ctrl3 &= 0xff3f;
+ return val;
+
+ case 0x07: /* LCH_BASS_BOOST_N0 */
+ case 0x08: /* LCH_BASS_BOOST_N1 */
+ case 0x09: /* LCH_BASS_BOOST_N2 */
+ case 0x0a: /* LCH_BASS_BOOST_N3 */
+ case 0x0b: /* LCH_BASS_BOOST_N4 */
+ case 0x0c: /* LCH_BASS_BOOST_N5 */
+ case 0x0d: /* LCH_BASS_BOOST_D1 */
+ case 0x0e: /* LCH_BASS_BOOST_D2 */
+ case 0x0f: /* LCH_BASS_BOOST_D4 */
+ case 0x10: /* LCH_BASS_BOOST_D5 */
+ case 0x11: /* RCH_BASS_BOOST_N0 */
+ case 0x12: /* RCH_BASS_BOOST_N1 */
+ case 0x13: /* RCH_BASS_BOOST_N2 */
+ case 0x14: /* RCH_BASS_BOOST_N3 */
+ case 0x15: /* RCH_BASS_BOOST_N4 */
+ case 0x16: /* RCH_BASS_BOOST_N5 */
+ case 0x17: /* RCH_BASS_BOOST_D1 */
+ case 0x18: /* RCH_BASS_BOOST_D2 */
+ case 0x19: /* RCH_BASS_BOOST_D4 */
+ case 0x1a: /* RCH_BASS_BOOST_D5 */
+ return s->filter_data[reg - 0x07];
+
+ case 0x1b: /* PLL Programmability 1 */
+ return s->pll[0];
+
+ case 0x1c: /* PLL Programmability 2 */
+ return s->pll[1];
+
+ case 0x1d: /* Audio Control 4 */
+ return (!s->softstep) << 14;
+
+ default:
+#ifdef TSC_VERBOSE
+ fprintf(stderr, "tsc2102_audio_register_read: "
+ "no such register: 0x%02x\n", reg);
+#endif
+ return 0xffff;
+ }
+}
+
+static void tsc2102_data_register_write(
+ TSC210xState *s, int reg, uint16_t value)
+{
+ switch (reg) {
+ case 0x00: /* X */
+ case 0x01: /* Y */
+ case 0x02: /* Z1 */
+ case 0x03: /* Z2 */
+ case 0x05: /* BAT1 */
+ case 0x06: /* BAT2 */
+ case 0x07: /* AUX1 */
+ case 0x08: /* AUX2 */
+ case 0x09: /* TEMP1 */
+ case 0x0a: /* TEMP2 */
+ return;
+
+ default:
+#ifdef TSC_VERBOSE
+ fprintf(stderr, "tsc2102_data_register_write: "
+ "no such register: 0x%02x\n", reg);
+#endif
+ }
+}
+
+static void tsc2102_control_register_write(
+ TSC210xState *s, int reg, uint16_t value)
+{
+ switch (reg) {
+ case 0x00: /* TSC ADC */
+ s->host_mode = value >> 15;
+ s->enabled = !(value & 0x4000);
+ if (s->busy && !s->enabled)
+ timer_del(s->timer);
+ s->busy &= s->enabled;
+ s->nextfunction = (value >> 10) & 0xf;
+ s->nextprecision = (value >> 8) & 3;
+ s->filter = value & 0xff;
+ return;
+
+ case 0x01: /* Status / Keypad Control */
+ if ((s->model & 0xff00) == 0x2100)
+ s->pin_func = value >> 14;
+ else {
+ s->kb.scan = (value >> 14) & 1;
+ s->kb.debounce = (value >> 11) & 7;
+ if (s->kb.intr && s->kb.scan) {
+ s->kb.intr = 0;
+ qemu_irq_raise(s->kbint);
+ }
+ }
+ return;
+
+ case 0x02: /* DAC Control */
+ if ((s->model & 0xff00) == 0x2300) {
+ s->dac_power &= 0x7fff;
+ s->dac_power |= 0x8000 & value;
+ } else
+ goto bad_reg;
+ break;
+
+ case 0x03: /* Reference */
+ s->ref = value & 0x1f;
+ return;
+
+ case 0x04: /* Reset */
+ if (value == 0xbb00) {
+ if (s->busy)
+ timer_del(s->timer);
+ tsc210x_reset(s);
+#ifdef TSC_VERBOSE
+ } else {
+ fprintf(stderr, "tsc2102_control_register_write: "
+ "wrong value written into RESET\n");
+#endif
+ }
+ return;
+
+ case 0x05: /* Configuration */
+ s->timing = value & 0x3f;
+#ifdef TSC_VERBOSE
+ if (value & ~0x3f)
+ fprintf(stderr, "tsc2102_control_register_write: "
+ "wrong value written into CONFIG\n");
+#endif
+ return;
+
+ case 0x06: /* Secondary configuration */
+ if ((s->model & 0xff00) == 0x2100)
+ goto bad_reg;
+ s->kb.mode = value >> 14;
+ s->pll[2] = value & 0x3ffff;
+ return;
+
+ case 0x10: /* Keypad Mask */
+ if ((s->model & 0xff00) == 0x2100)
+ goto bad_reg;
+ s->kb.mask = value;
+ return;
+
+ default:
+ bad_reg:
+#ifdef TSC_VERBOSE
+ fprintf(stderr, "tsc2102_control_register_write: "
+ "no such register: 0x%02x\n", reg);
+#endif
+ }
+}
+
+static void tsc2102_audio_register_write(
+ TSC210xState *s, int reg, uint16_t value)
+{
+ switch (reg) {
+ case 0x00: /* Audio Control 1 */
+ s->audio_ctrl1 = value & 0x0f3f;
+#ifdef TSC_VERBOSE
+ if ((value & ~0x0f3f) || ((value & 7) != ((value >> 3) & 7)))
+ fprintf(stderr, "tsc2102_audio_register_write: "
+ "wrong value written into Audio 1\n");
+#endif
+ tsc2102_audio_rate_update(s);
+ tsc2102_audio_output_update(s);
+ return;
+
+ case 0x01:
+#ifdef TSC_VERBOSE
+ if (value != 0xff00)
+ fprintf(stderr, "tsc2102_audio_register_write: "
+ "wrong value written into reg 0x01\n");
+#endif
+ return;
+
+ case 0x02: /* DAC Volume Control */
+ s->volume = value;
+ s->volume_change = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
+ return;
+
+ case 0x03:
+#ifdef TSC_VERBOSE
+ if (value != 0x8b00)
+ fprintf(stderr, "tsc2102_audio_register_write: "
+ "wrong value written into reg 0x03\n");
+#endif
+ return;
+
+ case 0x04: /* Audio Control 2 */
+ s->audio_ctrl2 = value & 0xf7f2;
+#ifdef TSC_VERBOSE
+ if (value & ~0xf7fd)
+ fprintf(stderr, "tsc2102_audio_register_write: "
+ "wrong value written into Audio 2\n");
+#endif
+ return;
+
+ case 0x05: /* Stereo DAC Power Control */
+ if ((value & ~s->dac_power) & (1 << 10))
+ s->powerdown = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
+
+ s->dac_power = value & 0x9543;
+#ifdef TSC_VERBOSE
+ if ((value & ~0x9543) != 0x2aa0)
+ fprintf(stderr, "tsc2102_audio_register_write: "
+ "wrong value written into Power\n");
+#endif
+ tsc2102_audio_rate_update(s);
+ tsc2102_audio_output_update(s);
+ return;
+
+ case 0x06: /* Audio Control 3 */
+ s->audio_ctrl3 &= 0x00c0;
+ s->audio_ctrl3 |= value & 0xf800;
+#ifdef TSC_VERBOSE
+ if (value & ~0xf8c7)
+ fprintf(stderr, "tsc2102_audio_register_write: "
+ "wrong value written into Audio 3\n");
+#endif
+ tsc2102_audio_output_update(s);
+ return;
+
+ case 0x07: /* LCH_BASS_BOOST_N0 */
+ case 0x08: /* LCH_BASS_BOOST_N1 */
+ case 0x09: /* LCH_BASS_BOOST_N2 */
+ case 0x0a: /* LCH_BASS_BOOST_N3 */
+ case 0x0b: /* LCH_BASS_BOOST_N4 */
+ case 0x0c: /* LCH_BASS_BOOST_N5 */
+ case 0x0d: /* LCH_BASS_BOOST_D1 */
+ case 0x0e: /* LCH_BASS_BOOST_D2 */
+ case 0x0f: /* LCH_BASS_BOOST_D4 */
+ case 0x10: /* LCH_BASS_BOOST_D5 */
+ case 0x11: /* RCH_BASS_BOOST_N0 */
+ case 0x12: /* RCH_BASS_BOOST_N1 */
+ case 0x13: /* RCH_BASS_BOOST_N2 */
+ case 0x14: /* RCH_BASS_BOOST_N3 */
+ case 0x15: /* RCH_BASS_BOOST_N4 */
+ case 0x16: /* RCH_BASS_BOOST_N5 */
+ case 0x17: /* RCH_BASS_BOOST_D1 */
+ case 0x18: /* RCH_BASS_BOOST_D2 */
+ case 0x19: /* RCH_BASS_BOOST_D4 */
+ case 0x1a: /* RCH_BASS_BOOST_D5 */
+ s->filter_data[reg - 0x07] = value;
+ return;
+
+ case 0x1b: /* PLL Programmability 1 */
+ s->pll[0] = value & 0xfffc;
+#ifdef TSC_VERBOSE
+ if (value & ~0xfffc)
+ fprintf(stderr, "tsc2102_audio_register_write: "
+ "wrong value written into PLL 1\n");
+#endif
+ return;
+
+ case 0x1c: /* PLL Programmability 2 */
+ s->pll[1] = value & 0xfffc;
+#ifdef TSC_VERBOSE
+ if (value & ~0xfffc)
+ fprintf(stderr, "tsc2102_audio_register_write: "
+ "wrong value written into PLL 2\n");
+#endif
+ return;
+
+ case 0x1d: /* Audio Control 4 */
+ s->softstep = !(value & 0x4000);
+#ifdef TSC_VERBOSE
+ if (value & ~0x4000)
+ fprintf(stderr, "tsc2102_audio_register_write: "
+ "wrong value written into Audio 4\n");
+#endif
+ return;
+
+ default:
+#ifdef TSC_VERBOSE
+ fprintf(stderr, "tsc2102_audio_register_write: "
+ "no such register: 0x%02x\n", reg);
+#endif
+ }
+}
+
+/* This handles most of the chip logic. */
+static void tsc210x_pin_update(TSC210xState *s)
+{
+ int64_t expires;
+ int pin_state;
+
+ switch (s->pin_func) {
+ case 0:
+ pin_state = s->pressure;
+ break;
+ case 1:
+ pin_state = !!s->dav;
+ break;
+ case 2:
+ default:
+ pin_state = s->pressure && !s->dav;
+ }
+
+ if (!s->enabled)
+ pin_state = 0;
+
+ if (pin_state != s->irq) {
+ s->irq = pin_state;
+ qemu_set_irq(s->pint, !s->irq);
+ }
+
+ switch (s->nextfunction) {
+ case TSC_MODE_XY_SCAN:
+ case TSC_MODE_XYZ_SCAN:
+ if (!s->pressure)
+ return;
+ break;
+
+ case TSC_MODE_X:
+ case TSC_MODE_Y:
+ case TSC_MODE_Z:
+ if (!s->pressure)
+ return;
+ /* Fall through */
+ case TSC_MODE_BAT1:
+ case TSC_MODE_BAT2:
+ case TSC_MODE_AUX:
+ case TSC_MODE_TEMP1:
+ case TSC_MODE_TEMP2:
+ if (s->dav)
+ s->enabled = 0;
+ break;
+
+ case TSC_MODE_AUX_SCAN:
+ case TSC_MODE_PORT_SCAN:
+ break;
+
+ case TSC_MODE_NO_SCAN:
+ case TSC_MODE_XX_DRV:
+ case TSC_MODE_YY_DRV:
+ case TSC_MODE_YX_DRV:
+ default:
+ return;
+ }
+
+ if (!s->enabled || s->busy || s->dav)
+ return;
+
+ s->busy = 1;
+ s->precision = s->nextprecision;
+ s->function = s->nextfunction;
+ expires = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + (get_ticks_per_sec() >> 10);
+ timer_mod(s->timer, expires);
+}
+
+static uint16_t tsc210x_read(TSC210xState *s)
+{
+ uint16_t ret = 0x0000;
+
+ if (!s->command)
+ fprintf(stderr, "tsc210x_read: SPI underrun!\n");
+
+ switch (s->page) {
+ case TSC_DATA_REGISTERS_PAGE:
+ ret = tsc2102_data_register_read(s, s->offset);
+ if (!s->dav)
+ qemu_irq_raise(s->davint);
+ break;
+ case TSC_CONTROL_REGISTERS_PAGE:
+ ret = tsc2102_control_register_read(s, s->offset);
+ break;
+ case TSC_AUDIO_REGISTERS_PAGE:
+ ret = tsc2102_audio_register_read(s, s->offset);
+ break;
+ default:
+ hw_error("tsc210x_read: wrong memory page\n");
+ }
+
+ tsc210x_pin_update(s);
+
+ /* Allow sequential reads. */
+ s->offset ++;
+ s->state = 0;
+ return ret;
+}
+
+static void tsc210x_write(TSC210xState *s, uint16_t value)
+{
+ /*
+ * This is a two-state state machine for reading
+ * command and data every second time.
+ */
+ if (!s->state) {
+ s->command = value >> 15;
+ s->page = (value >> 11) & 0x0f;
+ s->offset = (value >> 5) & 0x3f;
+ s->state = 1;
+ } else {
+ if (s->command)
+ fprintf(stderr, "tsc210x_write: SPI overrun!\n");
+ else
+ switch (s->page) {
+ case TSC_DATA_REGISTERS_PAGE:
+ tsc2102_data_register_write(s, s->offset, value);
+ break;
+ case TSC_CONTROL_REGISTERS_PAGE:
+ tsc2102_control_register_write(s, s->offset, value);
+ break;
+ case TSC_AUDIO_REGISTERS_PAGE:
+ tsc2102_audio_register_write(s, s->offset, value);
+ break;
+ default:
+ hw_error("tsc210x_write: wrong memory page\n");
+ }
+
+ tsc210x_pin_update(s);
+ s->state = 0;
+ }
+}
+
+uint32_t tsc210x_txrx(void *opaque, uint32_t value, int len)
+{
+ TSC210xState *s = opaque;
+ uint32_t ret = 0;
+
+ if (len != 16)
+ hw_error("%s: FIXME: bad SPI word width %i\n", __FUNCTION__, len);
+
+ /* TODO: sequential reads etc - how do we make sure the host doesn't
+ * unintentionally read out a conversion result from a register while
+ * transmitting the command word of the next command? */
+ if (!value || (s->state && s->command))
+ ret = tsc210x_read(s);
+ if (value || (s->state && !s->command))
+ tsc210x_write(s, value);
+
+ return ret;
+}
+
+static void tsc210x_timer_tick(void *opaque)
+{
+ TSC210xState *s = opaque;
+
+ /* Timer ticked -- a set of conversions has been finished. */
+
+ if (!s->busy)
+ return;
+
+ s->busy = 0;
+ s->dav |= mode_regs[s->function];
+ tsc210x_pin_update(s);
+ qemu_irq_lower(s->davint);
+}
+
+static void tsc210x_touchscreen_event(void *opaque,
+ int x, int y, int z, int buttons_state)
+{
+ TSC210xState *s = opaque;
+ int p = s->pressure;
+
+ if (buttons_state) {
+ s->x = x;
+ s->y = y;
+ }
+ s->pressure = !!buttons_state;
+
+ /*
+ * Note: We would get better responsiveness in the guest by
+ * signaling TS events immediately, but for now we simulate
+ * the first conversion delay for sake of correctness.
+ */
+ if (p != s->pressure)
+ tsc210x_pin_update(s);
+}
+
+static void tsc210x_i2s_swallow(TSC210xState *s)
+{
+ if (s->dac_voice[0])
+ tsc210x_out_flush(s, s->codec.out.len);
+ else
+ s->codec.out.len = 0;
+}
+
+static void tsc210x_i2s_set_rate(TSC210xState *s, int in, int out)
+{
+ s->i2s_tx_rate = out;
+ s->i2s_rx_rate = in;
+}
+
+static void tsc210x_save(QEMUFile *f, void *opaque)
+{
+ TSC210xState *s = (TSC210xState *) opaque;
+ int64_t now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
+ int i;
+
+ qemu_put_be16(f, s->x);
+ qemu_put_be16(f, s->y);
+ qemu_put_byte(f, s->pressure);
+
+ qemu_put_byte(f, s->state);
+ qemu_put_byte(f, s->page);
+ qemu_put_byte(f, s->offset);
+ qemu_put_byte(f, s->command);
+
+ qemu_put_byte(f, s->irq);
+ qemu_put_be16s(f, &s->dav);
+
+ timer_put(f, s->timer);
+ qemu_put_byte(f, s->enabled);
+ qemu_put_byte(f, s->host_mode);
+ qemu_put_byte(f, s->function);
+ qemu_put_byte(f, s->nextfunction);
+ qemu_put_byte(f, s->precision);
+ qemu_put_byte(f, s->nextprecision);
+ qemu_put_byte(f, s->filter);
+ qemu_put_byte(f, s->pin_func);
+ qemu_put_byte(f, s->ref);
+ qemu_put_byte(f, s->timing);
+ qemu_put_be32(f, s->noise);
+
+ qemu_put_be16s(f, &s->audio_ctrl1);
+ qemu_put_be16s(f, &s->audio_ctrl2);
+ qemu_put_be16s(f, &s->audio_ctrl3);
+ qemu_put_be16s(f, &s->pll[0]);
+ qemu_put_be16s(f, &s->pll[1]);
+ qemu_put_be16s(f, &s->volume);
+ qemu_put_sbe64(f, (s->volume_change - now));
+ qemu_put_sbe64(f, (s->powerdown - now));
+ qemu_put_byte(f, s->softstep);
+ qemu_put_be16s(f, &s->dac_power);
+
+ for (i = 0; i < 0x14; i ++)
+ qemu_put_be16s(f, &s->filter_data[i]);
+}
+
+static int tsc210x_load(QEMUFile *f, void *opaque, int version_id)
+{
+ TSC210xState *s = (TSC210xState *) opaque;
+ int64_t now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
+ int i;
+
+ s->x = qemu_get_be16(f);
+ s->y = qemu_get_be16(f);
+ s->pressure = qemu_get_byte(f);
+
+ s->state = qemu_get_byte(f);
+ s->page = qemu_get_byte(f);
+ s->offset = qemu_get_byte(f);
+ s->command = qemu_get_byte(f);
+
+ s->irq = qemu_get_byte(f);
+ qemu_get_be16s(f, &s->dav);
+
+ timer_get(f, s->timer);
+ s->enabled = qemu_get_byte(f);
+ s->host_mode = qemu_get_byte(f);
+ s->function = qemu_get_byte(f);
+ if (s->function < 0 || s->function >= ARRAY_SIZE(mode_regs)) {
+ return -EINVAL;
+ }
+ s->nextfunction = qemu_get_byte(f);
+ if (s->nextfunction < 0 || s->nextfunction >= ARRAY_SIZE(mode_regs)) {
+ return -EINVAL;
+ }
+ s->precision = qemu_get_byte(f);
+ if (s->precision < 0 || s->precision >= ARRAY_SIZE(resolution)) {
+ return -EINVAL;
+ }
+ s->nextprecision = qemu_get_byte(f);
+ if (s->nextprecision < 0 || s->nextprecision >= ARRAY_SIZE(resolution)) {
+ return -EINVAL;
+ }
+ s->filter = qemu_get_byte(f);
+ s->pin_func = qemu_get_byte(f);
+ s->ref = qemu_get_byte(f);
+ s->timing = qemu_get_byte(f);
+ s->noise = qemu_get_be32(f);
+
+ qemu_get_be16s(f, &s->audio_ctrl1);
+ qemu_get_be16s(f, &s->audio_ctrl2);
+ qemu_get_be16s(f, &s->audio_ctrl3);
+ qemu_get_be16s(f, &s->pll[0]);
+ qemu_get_be16s(f, &s->pll[1]);
+ qemu_get_be16s(f, &s->volume);
+ s->volume_change = qemu_get_sbe64(f) + now;
+ s->powerdown = qemu_get_sbe64(f) + now;
+ s->softstep = qemu_get_byte(f);
+ qemu_get_be16s(f, &s->dac_power);
+
+ for (i = 0; i < 0x14; i ++)
+ qemu_get_be16s(f, &s->filter_data[i]);
+
+ s->busy = timer_pending(s->timer);
+ qemu_set_irq(s->pint, !s->irq);
+ qemu_set_irq(s->davint, !s->dav);
+
+ return 0;
+}
+
+uWireSlave *tsc2102_init(qemu_irq pint)
+{
+ TSC210xState *s;
+
+ s = g_new0(TSC210xState, 1);
+ s->x = 160;
+ s->y = 160;
+ s->pressure = 0;
+ s->precision = s->nextprecision = 0;
+ s->timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, tsc210x_timer_tick, s);
+ s->pint = pint;
+ s->model = 0x2102;
+ s->name = "tsc2102";
+
+ s->tr[0] = 0;
+ s->tr[1] = 1;
+ s->tr[2] = 1;
+ s->tr[3] = 0;
+ s->tr[4] = 1;
+ s->tr[5] = 0;
+ s->tr[6] = 1;
+ s->tr[7] = 0;
+
+ s->chip.opaque = s;
+ s->chip.send = (void *) tsc210x_write;
+ s->chip.receive = (void *) tsc210x_read;
+
+ s->codec.opaque = s;
+ s->codec.tx_swallow = (void *) tsc210x_i2s_swallow;
+ s->codec.set_rate = (void *) tsc210x_i2s_set_rate;
+ s->codec.in.fifo = s->in_fifo;
+ s->codec.out.fifo = s->out_fifo;
+
+ tsc210x_reset(s);
+
+ qemu_add_mouse_event_handler(tsc210x_touchscreen_event, s, 1,
+ "QEMU TSC2102-driven Touchscreen");
+
+ AUD_register_card(s->name, &s->card);
+
+ qemu_register_reset((void *) tsc210x_reset, s);
+ register_savevm(NULL, s->name, -1, 0,
+ tsc210x_save, tsc210x_load, s);
+
+ return &s->chip;
+}
+
+uWireSlave *tsc2301_init(qemu_irq penirq, qemu_irq kbirq, qemu_irq dav)
+{
+ TSC210xState *s;
+
+ s = g_new0(TSC210xState, 1);
+ s->x = 400;
+ s->y = 240;
+ s->pressure = 0;
+ s->precision = s->nextprecision = 0;
+ s->timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, tsc210x_timer_tick, s);
+ s->pint = penirq;
+ s->kbint = kbirq;
+ s->davint = dav;
+ s->model = 0x2301;
+ s->name = "tsc2301";
+
+ s->tr[0] = 0;
+ s->tr[1] = 1;
+ s->tr[2] = 1;
+ s->tr[3] = 0;
+ s->tr[4] = 1;
+ s->tr[5] = 0;
+ s->tr[6] = 1;
+ s->tr[7] = 0;
+
+ s->chip.opaque = s;
+ s->chip.send = (void *) tsc210x_write;
+ s->chip.receive = (void *) tsc210x_read;
+
+ s->codec.opaque = s;
+ s->codec.tx_swallow = (void *) tsc210x_i2s_swallow;
+ s->codec.set_rate = (void *) tsc210x_i2s_set_rate;
+ s->codec.in.fifo = s->in_fifo;
+ s->codec.out.fifo = s->out_fifo;
+
+ tsc210x_reset(s);
+
+ qemu_add_mouse_event_handler(tsc210x_touchscreen_event, s, 1,
+ "QEMU TSC2301-driven Touchscreen");
+
+ AUD_register_card(s->name, &s->card);
+
+ qemu_register_reset((void *) tsc210x_reset, s);
+ register_savevm(NULL, s->name, -1, 0, tsc210x_save, tsc210x_load, s);
+
+ return &s->chip;
+}
+
+I2SCodec *tsc210x_codec(uWireSlave *chip)
+{
+ TSC210xState *s = (TSC210xState *) chip->opaque;
+
+ return &s->codec;
+}
+
+/*
+ * Use tslib generated calibration data to generate ADC input values
+ * from the touchscreen. Assuming 12-bit precision was used during
+ * tslib calibration.
+ */
+void tsc210x_set_transform(uWireSlave *chip,
+ MouseTransformInfo *info)
+{
+ TSC210xState *s = (TSC210xState *) chip->opaque;
+#if 0
+ int64_t ltr[8];
+
+ ltr[0] = (int64_t) info->a[1] * info->y;
+ ltr[1] = (int64_t) info->a[4] * info->x;
+ ltr[2] = (int64_t) info->a[1] * info->a[3] -
+ (int64_t) info->a[4] * info->a[0];
+ ltr[3] = (int64_t) info->a[2] * info->a[4] -
+ (int64_t) info->a[5] * info->a[1];
+ ltr[4] = (int64_t) info->a[0] * info->y;
+ ltr[5] = (int64_t) info->a[3] * info->x;
+ ltr[6] = (int64_t) info->a[4] * info->a[0] -
+ (int64_t) info->a[1] * info->a[3];
+ ltr[7] = (int64_t) info->a[2] * info->a[3] -
+ (int64_t) info->a[5] * info->a[0];
+
+ /* Avoid integer overflow */
+ s->tr[0] = ltr[0] >> 11;
+ s->tr[1] = ltr[1] >> 11;
+ s->tr[2] = muldiv64(ltr[2], 1, info->a[6]);
+ s->tr[3] = muldiv64(ltr[3], 1 << 4, ltr[2]);
+ s->tr[4] = ltr[4] >> 11;
+ s->tr[5] = ltr[5] >> 11;
+ s->tr[6] = muldiv64(ltr[6], 1, info->a[6]);
+ s->tr[7] = muldiv64(ltr[7], 1 << 4, ltr[6]);
+#else
+
+ /* This version assumes touchscreen X & Y axis are parallel or
+ * perpendicular to LCD's X & Y axis in some way. */
+ if (abs(info->a[0]) > abs(info->a[1])) {
+ s->tr[0] = 0;
+ s->tr[1] = -info->a[6] * info->x;
+ s->tr[2] = info->a[0];
+ s->tr[3] = -info->a[2] / info->a[0];
+ s->tr[4] = info->a[6] * info->y;
+ s->tr[5] = 0;
+ s->tr[6] = info->a[4];
+ s->tr[7] = -info->a[5] / info->a[4];
+ } else {
+ s->tr[0] = info->a[6] * info->y;
+ s->tr[1] = 0;
+ s->tr[2] = info->a[1];
+ s->tr[3] = -info->a[2] / info->a[1];
+ s->tr[4] = 0;
+ s->tr[5] = -info->a[6] * info->x;
+ s->tr[6] = info->a[3];
+ s->tr[7] = -info->a[5] / info->a[3];
+ }
+
+ s->tr[0] >>= 11;
+ s->tr[1] >>= 11;
+ s->tr[3] <<= 4;
+ s->tr[4] >>= 11;
+ s->tr[5] >>= 11;
+ s->tr[7] <<= 4;
+#endif
+}
+
+void tsc210x_key_event(uWireSlave *chip, int key, int down)
+{
+ TSC210xState *s = (TSC210xState *) chip->opaque;
+
+ if (down)
+ s->kb.down |= 1 << key;
+ else
+ s->kb.down &= ~(1 << key);
+
+ if (down && (s->kb.down & ~s->kb.mask) && !s->kb.intr) {
+ s->kb.intr = 1;
+ qemu_irq_lower(s->kbint);
+ } else if (s->kb.intr && !(s->kb.down & ~s->kb.mask) &&
+ !(s->kb.mode & 1)) {
+ s->kb.intr = 0;
+ qemu_irq_raise(s->kbint);
+ }
+}
diff --git a/src/hw/input/virtio-input-hid.c b/src/hw/input/virtio-input-hid.c
new file mode 100644
index 0000000..bdd479c
--- /dev/null
+++ b/src/hw/input/virtio-input-hid.c
@@ -0,0 +1,515 @@
+/*
+ * This work is licensed under the terms of the GNU GPL, version 2 or
+ * (at your option) any later version. See the COPYING file in the
+ * top-level directory.
+ */
+
+#include "qemu/iov.h"
+
+#include "hw/qdev.h"
+#include "hw/virtio/virtio.h"
+#include "hw/virtio/virtio-input.h"
+
+#undef CONFIG_CURSES
+#include "ui/console.h"
+
+#include "standard-headers/linux/input.h"
+
+#define VIRTIO_ID_NAME_KEYBOARD "QEMU Virtio Keyboard"
+#define VIRTIO_ID_NAME_MOUSE "QEMU Virtio Mouse"
+#define VIRTIO_ID_NAME_TABLET "QEMU Virtio Tablet"
+
+/* ----------------------------------------------------------------- */
+
+static const unsigned int keymap_qcode[Q_KEY_CODE_MAX] = {
+ [Q_KEY_CODE_ESC] = KEY_ESC,
+ [Q_KEY_CODE_1] = KEY_1,
+ [Q_KEY_CODE_2] = KEY_2,
+ [Q_KEY_CODE_3] = KEY_3,
+ [Q_KEY_CODE_4] = KEY_4,
+ [Q_KEY_CODE_5] = KEY_5,
+ [Q_KEY_CODE_6] = KEY_6,
+ [Q_KEY_CODE_7] = KEY_7,
+ [Q_KEY_CODE_8] = KEY_8,
+ [Q_KEY_CODE_9] = KEY_9,
+ [Q_KEY_CODE_0] = KEY_0,
+ [Q_KEY_CODE_MINUS] = KEY_MINUS,
+ [Q_KEY_CODE_EQUAL] = KEY_EQUAL,
+ [Q_KEY_CODE_BACKSPACE] = KEY_BACKSPACE,
+
+ [Q_KEY_CODE_TAB] = KEY_TAB,
+ [Q_KEY_CODE_Q] = KEY_Q,
+ [Q_KEY_CODE_W] = KEY_W,
+ [Q_KEY_CODE_E] = KEY_E,
+ [Q_KEY_CODE_R] = KEY_R,
+ [Q_KEY_CODE_T] = KEY_T,
+ [Q_KEY_CODE_Y] = KEY_Y,
+ [Q_KEY_CODE_U] = KEY_U,
+ [Q_KEY_CODE_I] = KEY_I,
+ [Q_KEY_CODE_O] = KEY_O,
+ [Q_KEY_CODE_P] = KEY_P,
+ [Q_KEY_CODE_BRACKET_LEFT] = KEY_LEFTBRACE,
+ [Q_KEY_CODE_BRACKET_RIGHT] = KEY_RIGHTBRACE,
+ [Q_KEY_CODE_RET] = KEY_ENTER,
+
+ [Q_KEY_CODE_CTRL] = KEY_LEFTCTRL,
+ [Q_KEY_CODE_A] = KEY_A,
+ [Q_KEY_CODE_S] = KEY_S,
+ [Q_KEY_CODE_D] = KEY_D,
+ [Q_KEY_CODE_F] = KEY_F,
+ [Q_KEY_CODE_G] = KEY_G,
+ [Q_KEY_CODE_H] = KEY_H,
+ [Q_KEY_CODE_J] = KEY_J,
+ [Q_KEY_CODE_K] = KEY_K,
+ [Q_KEY_CODE_L] = KEY_L,
+ [Q_KEY_CODE_SEMICOLON] = KEY_SEMICOLON,
+ [Q_KEY_CODE_APOSTROPHE] = KEY_APOSTROPHE,
+ [Q_KEY_CODE_GRAVE_ACCENT] = KEY_GRAVE,
+
+ [Q_KEY_CODE_SHIFT] = KEY_LEFTSHIFT,
+ [Q_KEY_CODE_BACKSLASH] = KEY_BACKSLASH,
+ [Q_KEY_CODE_LESS] = KEY_102ND,
+ [Q_KEY_CODE_Z] = KEY_Z,
+ [Q_KEY_CODE_X] = KEY_X,
+ [Q_KEY_CODE_C] = KEY_C,
+ [Q_KEY_CODE_V] = KEY_V,
+ [Q_KEY_CODE_B] = KEY_B,
+ [Q_KEY_CODE_N] = KEY_N,
+ [Q_KEY_CODE_M] = KEY_M,
+ [Q_KEY_CODE_COMMA] = KEY_COMMA,
+ [Q_KEY_CODE_DOT] = KEY_DOT,
+ [Q_KEY_CODE_SLASH] = KEY_SLASH,
+ [Q_KEY_CODE_SHIFT_R] = KEY_RIGHTSHIFT,
+
+ [Q_KEY_CODE_ALT] = KEY_LEFTALT,
+ [Q_KEY_CODE_SPC] = KEY_SPACE,
+ [Q_KEY_CODE_CAPS_LOCK] = KEY_CAPSLOCK,
+
+ [Q_KEY_CODE_F1] = KEY_F1,
+ [Q_KEY_CODE_F2] = KEY_F2,
+ [Q_KEY_CODE_F3] = KEY_F3,
+ [Q_KEY_CODE_F4] = KEY_F4,
+ [Q_KEY_CODE_F5] = KEY_F5,
+ [Q_KEY_CODE_F6] = KEY_F6,
+ [Q_KEY_CODE_F7] = KEY_F7,
+ [Q_KEY_CODE_F8] = KEY_F8,
+ [Q_KEY_CODE_F9] = KEY_F9,
+ [Q_KEY_CODE_F10] = KEY_F10,
+ [Q_KEY_CODE_NUM_LOCK] = KEY_NUMLOCK,
+ [Q_KEY_CODE_SCROLL_LOCK] = KEY_SCROLLLOCK,
+
+ [Q_KEY_CODE_KP_0] = KEY_KP0,
+ [Q_KEY_CODE_KP_1] = KEY_KP1,
+ [Q_KEY_CODE_KP_2] = KEY_KP2,
+ [Q_KEY_CODE_KP_3] = KEY_KP3,
+ [Q_KEY_CODE_KP_4] = KEY_KP4,
+ [Q_KEY_CODE_KP_5] = KEY_KP5,
+ [Q_KEY_CODE_KP_6] = KEY_KP6,
+ [Q_KEY_CODE_KP_7] = KEY_KP7,
+ [Q_KEY_CODE_KP_8] = KEY_KP8,
+ [Q_KEY_CODE_KP_9] = KEY_KP9,
+ [Q_KEY_CODE_KP_SUBTRACT] = KEY_KPMINUS,
+ [Q_KEY_CODE_KP_ADD] = KEY_KPPLUS,
+ [Q_KEY_CODE_KP_DECIMAL] = KEY_KPDOT,
+ [Q_KEY_CODE_KP_ENTER] = KEY_KPENTER,
+ [Q_KEY_CODE_KP_DIVIDE] = KEY_KPSLASH,
+ [Q_KEY_CODE_KP_MULTIPLY] = KEY_KPASTERISK,
+
+ [Q_KEY_CODE_F11] = KEY_F11,
+ [Q_KEY_CODE_F12] = KEY_F12,
+
+ [Q_KEY_CODE_CTRL_R] = KEY_RIGHTCTRL,
+ [Q_KEY_CODE_SYSRQ] = KEY_SYSRQ,
+ [Q_KEY_CODE_ALT_R] = KEY_RIGHTALT,
+
+ [Q_KEY_CODE_HOME] = KEY_HOME,
+ [Q_KEY_CODE_UP] = KEY_UP,
+ [Q_KEY_CODE_PGUP] = KEY_PAGEUP,
+ [Q_KEY_CODE_LEFT] = KEY_LEFT,
+ [Q_KEY_CODE_RIGHT] = KEY_RIGHT,
+ [Q_KEY_CODE_END] = KEY_END,
+ [Q_KEY_CODE_DOWN] = KEY_DOWN,
+ [Q_KEY_CODE_PGDN] = KEY_PAGEDOWN,
+ [Q_KEY_CODE_INSERT] = KEY_INSERT,
+ [Q_KEY_CODE_DELETE] = KEY_DELETE,
+
+ [Q_KEY_CODE_META_L] = KEY_LEFTMETA,
+ [Q_KEY_CODE_META_R] = KEY_RIGHTMETA,
+ [Q_KEY_CODE_MENU] = KEY_MENU,
+};
+
+static const unsigned int keymap_button[INPUT_BUTTON_MAX] = {
+ [INPUT_BUTTON_LEFT] = BTN_LEFT,
+ [INPUT_BUTTON_RIGHT] = BTN_RIGHT,
+ [INPUT_BUTTON_MIDDLE] = BTN_MIDDLE,
+ [INPUT_BUTTON_WHEEL_UP] = BTN_GEAR_UP,
+ [INPUT_BUTTON_WHEEL_DOWN] = BTN_GEAR_DOWN,
+};
+
+static const unsigned int axismap_rel[INPUT_AXIS_MAX] = {
+ [INPUT_AXIS_X] = REL_X,
+ [INPUT_AXIS_Y] = REL_Y,
+};
+
+static const unsigned int axismap_abs[INPUT_AXIS_MAX] = {
+ [INPUT_AXIS_X] = ABS_X,
+ [INPUT_AXIS_Y] = ABS_Y,
+};
+
+/* ----------------------------------------------------------------- */
+
+static void virtio_input_key_config(VirtIOInput *vinput,
+ const unsigned int *keymap,
+ size_t mapsize)
+{
+ virtio_input_config keys;
+ int i, bit, byte, bmax = 0;
+
+ memset(&keys, 0, sizeof(keys));
+ for (i = 0; i < mapsize; i++) {
+ bit = keymap[i];
+ if (!bit) {
+ continue;
+ }
+ byte = bit / 8;
+ bit = bit % 8;
+ keys.u.bitmap[byte] |= (1 << bit);
+ if (bmax < byte+1) {
+ bmax = byte+1;
+ }
+ }
+ keys.select = VIRTIO_INPUT_CFG_EV_BITS;
+ keys.subsel = EV_KEY;
+ keys.size = bmax;
+ virtio_input_add_config(vinput, &keys);
+}
+
+static void virtio_input_handle_event(DeviceState *dev, QemuConsole *src,
+ InputEvent *evt)
+{
+ VirtIOInput *vinput = VIRTIO_INPUT(dev);
+ virtio_input_event event;
+ int qcode;
+
+ switch (evt->type) {
+ case INPUT_EVENT_KIND_KEY:
+ qcode = qemu_input_key_value_to_qcode(evt->u.key->key);
+ if (qcode && keymap_qcode[qcode]) {
+ event.type = cpu_to_le16(EV_KEY);
+ event.code = cpu_to_le16(keymap_qcode[qcode]);
+ event.value = cpu_to_le32(evt->u.key->down ? 1 : 0);
+ virtio_input_send(vinput, &event);
+ } else {
+ if (evt->u.key->down) {
+ fprintf(stderr, "%s: unmapped key: %d [%s]\n", __func__,
+ qcode, QKeyCode_lookup[qcode]);
+ }
+ }
+ break;
+ case INPUT_EVENT_KIND_BTN:
+ if (keymap_button[evt->u.btn->button]) {
+ event.type = cpu_to_le16(EV_KEY);
+ event.code = cpu_to_le16(keymap_button[evt->u.btn->button]);
+ event.value = cpu_to_le32(evt->u.btn->down ? 1 : 0);
+ virtio_input_send(vinput, &event);
+ } else {
+ if (evt->u.btn->down) {
+ fprintf(stderr, "%s: unmapped button: %d [%s]\n", __func__,
+ evt->u.btn->button,
+ InputButton_lookup[evt->u.btn->button]);
+ }
+ }
+ break;
+ case INPUT_EVENT_KIND_REL:
+ event.type = cpu_to_le16(EV_REL);
+ event.code = cpu_to_le16(axismap_rel[evt->u.rel->axis]);
+ event.value = cpu_to_le32(evt->u.rel->value);
+ virtio_input_send(vinput, &event);
+ break;
+ case INPUT_EVENT_KIND_ABS:
+ event.type = cpu_to_le16(EV_ABS);
+ event.code = cpu_to_le16(axismap_abs[evt->u.abs->axis]);
+ event.value = cpu_to_le32(evt->u.abs->value);
+ virtio_input_send(vinput, &event);
+ break;
+ default:
+ /* keep gcc happy */
+ break;
+ }
+}
+
+static void virtio_input_handle_sync(DeviceState *dev)
+{
+ VirtIOInput *vinput = VIRTIO_INPUT(dev);
+ virtio_input_event event = {
+ .type = cpu_to_le16(EV_SYN),
+ .code = cpu_to_le16(SYN_REPORT),
+ .value = 0,
+ };
+
+ virtio_input_send(vinput, &event);
+}
+
+static void virtio_input_hid_realize(DeviceState *dev, Error **errp)
+{
+ VirtIOInputHID *vhid = VIRTIO_INPUT_HID(dev);
+
+ vhid->hs = qemu_input_handler_register(dev, vhid->handler);
+ if (vhid->display && vhid->hs) {
+ qemu_input_handler_bind(vhid->hs, vhid->display, vhid->head, NULL);
+ }
+}
+
+static void virtio_input_hid_unrealize(DeviceState *dev, Error **errp)
+{
+ VirtIOInputHID *vhid = VIRTIO_INPUT_HID(dev);
+ qemu_input_handler_unregister(vhid->hs);
+}
+
+static void virtio_input_hid_change_active(VirtIOInput *vinput)
+{
+ VirtIOInputHID *vhid = VIRTIO_INPUT_HID(vinput);
+
+ if (vinput->active) {
+ qemu_input_handler_activate(vhid->hs);
+ } else {
+ qemu_input_handler_deactivate(vhid->hs);
+ }
+}
+
+static void virtio_input_hid_handle_status(VirtIOInput *vinput,
+ virtio_input_event *event)
+{
+ VirtIOInputHID *vhid = VIRTIO_INPUT_HID(vinput);
+ int ledbit = 0;
+
+ switch (le16_to_cpu(event->type)) {
+ case EV_LED:
+ if (event->code == LED_NUML) {
+ ledbit = QEMU_NUM_LOCK_LED;
+ } else if (event->code == LED_CAPSL) {
+ ledbit = QEMU_CAPS_LOCK_LED;
+ } else if (event->code == LED_SCROLLL) {
+ ledbit = QEMU_SCROLL_LOCK_LED;
+ }
+ if (event->value) {
+ vhid->ledstate |= ledbit;
+ } else {
+ vhid->ledstate &= ~ledbit;
+ }
+ kbd_put_ledstate(vhid->ledstate);
+ break;
+ default:
+ fprintf(stderr, "%s: unknown type %d\n", __func__,
+ le16_to_cpu(event->type));
+ break;
+ }
+}
+
+static Property virtio_input_hid_properties[] = {
+ DEFINE_PROP_STRING("display", VirtIOInputHID, display),
+ DEFINE_PROP_UINT32("head", VirtIOInputHID, head, 0),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void virtio_input_hid_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ VirtIOInputClass *vic = VIRTIO_INPUT_CLASS(klass);
+
+ dc->props = virtio_input_hid_properties;
+ vic->realize = virtio_input_hid_realize;
+ vic->unrealize = virtio_input_hid_unrealize;
+ vic->change_active = virtio_input_hid_change_active;
+ vic->handle_status = virtio_input_hid_handle_status;
+}
+
+static const TypeInfo virtio_input_hid_info = {
+ .name = TYPE_VIRTIO_INPUT_HID,
+ .parent = TYPE_VIRTIO_INPUT,
+ .instance_size = sizeof(VirtIOInputHID),
+ .class_init = virtio_input_hid_class_init,
+ .abstract = true,
+};
+
+/* ----------------------------------------------------------------- */
+
+static QemuInputHandler virtio_keyboard_handler = {
+ .name = VIRTIO_ID_NAME_KEYBOARD,
+ .mask = INPUT_EVENT_MASK_KEY,
+ .event = virtio_input_handle_event,
+ .sync = virtio_input_handle_sync,
+};
+
+static struct virtio_input_config virtio_keyboard_config[] = {
+ {
+ .select = VIRTIO_INPUT_CFG_ID_NAME,
+ .size = sizeof(VIRTIO_ID_NAME_KEYBOARD),
+ .u.string = VIRTIO_ID_NAME_KEYBOARD,
+ },{
+ .select = VIRTIO_INPUT_CFG_ID_DEVIDS,
+ .size = sizeof(struct virtio_input_devids),
+ .u.ids = {
+ .bustype = const_le16(BUS_VIRTUAL),
+ .vendor = const_le16(0x0627), /* same we use for usb hid devices */
+ .product = const_le16(0x0001),
+ .version = const_le16(0x0001),
+ },
+ },{
+ .select = VIRTIO_INPUT_CFG_EV_BITS,
+ .subsel = EV_REP,
+ .size = 1,
+ },{
+ .select = VIRTIO_INPUT_CFG_EV_BITS,
+ .subsel = EV_LED,
+ .size = 1,
+ .u.bitmap = {
+ (1 << LED_NUML) | (1 << LED_CAPSL) | (1 << LED_SCROLLL),
+ },
+ },
+ { /* end of list */ },
+};
+
+static void virtio_keyboard_init(Object *obj)
+{
+ VirtIOInputHID *vhid = VIRTIO_INPUT_HID(obj);
+ VirtIOInput *vinput = VIRTIO_INPUT(obj);
+
+ vhid->handler = &virtio_keyboard_handler;
+ virtio_input_init_config(vinput, virtio_keyboard_config);
+ virtio_input_key_config(vinput, keymap_qcode,
+ ARRAY_SIZE(keymap_qcode));
+}
+
+static const TypeInfo virtio_keyboard_info = {
+ .name = TYPE_VIRTIO_KEYBOARD,
+ .parent = TYPE_VIRTIO_INPUT_HID,
+ .instance_size = sizeof(VirtIOInputHID),
+ .instance_init = virtio_keyboard_init,
+};
+
+/* ----------------------------------------------------------------- */
+
+static QemuInputHandler virtio_mouse_handler = {
+ .name = VIRTIO_ID_NAME_MOUSE,
+ .mask = INPUT_EVENT_MASK_BTN | INPUT_EVENT_MASK_REL,
+ .event = virtio_input_handle_event,
+ .sync = virtio_input_handle_sync,
+};
+
+static struct virtio_input_config virtio_mouse_config[] = {
+ {
+ .select = VIRTIO_INPUT_CFG_ID_NAME,
+ .size = sizeof(VIRTIO_ID_NAME_MOUSE),
+ .u.string = VIRTIO_ID_NAME_MOUSE,
+ },{
+ .select = VIRTIO_INPUT_CFG_ID_DEVIDS,
+ .size = sizeof(struct virtio_input_devids),
+ .u.ids = {
+ .bustype = const_le16(BUS_VIRTUAL),
+ .vendor = const_le16(0x0627), /* same we use for usb hid devices */
+ .product = const_le16(0x0002),
+ .version = const_le16(0x0001),
+ },
+ },{
+ .select = VIRTIO_INPUT_CFG_EV_BITS,
+ .subsel = EV_REL,
+ .size = 1,
+ .u.bitmap = {
+ (1 << REL_X) | (1 << REL_Y),
+ },
+ },
+ { /* end of list */ },
+};
+
+static void virtio_mouse_init(Object *obj)
+{
+ VirtIOInputHID *vhid = VIRTIO_INPUT_HID(obj);
+ VirtIOInput *vinput = VIRTIO_INPUT(obj);
+
+ vhid->handler = &virtio_mouse_handler;
+ virtio_input_init_config(vinput, virtio_mouse_config);
+ virtio_input_key_config(vinput, keymap_button,
+ ARRAY_SIZE(keymap_button));
+}
+
+static const TypeInfo virtio_mouse_info = {
+ .name = TYPE_VIRTIO_MOUSE,
+ .parent = TYPE_VIRTIO_INPUT_HID,
+ .instance_size = sizeof(VirtIOInputHID),
+ .instance_init = virtio_mouse_init,
+};
+
+/* ----------------------------------------------------------------- */
+
+static QemuInputHandler virtio_tablet_handler = {
+ .name = VIRTIO_ID_NAME_TABLET,
+ .mask = INPUT_EVENT_MASK_BTN | INPUT_EVENT_MASK_ABS,
+ .event = virtio_input_handle_event,
+ .sync = virtio_input_handle_sync,
+};
+
+static struct virtio_input_config virtio_tablet_config[] = {
+ {
+ .select = VIRTIO_INPUT_CFG_ID_NAME,
+ .size = sizeof(VIRTIO_ID_NAME_TABLET),
+ .u.string = VIRTIO_ID_NAME_TABLET,
+ },{
+ .select = VIRTIO_INPUT_CFG_ID_DEVIDS,
+ .size = sizeof(struct virtio_input_devids),
+ .u.ids = {
+ .bustype = const_le16(BUS_VIRTUAL),
+ .vendor = const_le16(0x0627), /* same we use for usb hid devices */
+ .product = const_le16(0x0003),
+ .version = const_le16(0x0001),
+ },
+ },{
+ .select = VIRTIO_INPUT_CFG_EV_BITS,
+ .subsel = EV_ABS,
+ .size = 1,
+ .u.bitmap = {
+ (1 << ABS_X) | (1 << ABS_Y),
+ },
+ },{
+ .select = VIRTIO_INPUT_CFG_ABS_INFO,
+ .subsel = ABS_X,
+ .size = sizeof(virtio_input_absinfo),
+ .u.abs.max = const_le32(INPUT_EVENT_ABS_SIZE),
+ },{
+ .select = VIRTIO_INPUT_CFG_ABS_INFO,
+ .subsel = ABS_Y,
+ .size = sizeof(virtio_input_absinfo),
+ .u.abs.max = const_le32(INPUT_EVENT_ABS_SIZE),
+ },
+ { /* end of list */ },
+};
+
+static void virtio_tablet_init(Object *obj)
+{
+ VirtIOInputHID *vhid = VIRTIO_INPUT_HID(obj);
+ VirtIOInput *vinput = VIRTIO_INPUT(obj);
+
+ vhid->handler = &virtio_tablet_handler;
+ virtio_input_init_config(vinput, virtio_tablet_config);
+ virtio_input_key_config(vinput, keymap_button,
+ ARRAY_SIZE(keymap_button));
+}
+
+static const TypeInfo virtio_tablet_info = {
+ .name = TYPE_VIRTIO_TABLET,
+ .parent = TYPE_VIRTIO_INPUT_HID,
+ .instance_size = sizeof(VirtIOInputHID),
+ .instance_init = virtio_tablet_init,
+};
+
+/* ----------------------------------------------------------------- */
+
+static void virtio_register_types(void)
+{
+ type_register_static(&virtio_input_hid_info);
+ type_register_static(&virtio_keyboard_info);
+ type_register_static(&virtio_mouse_info);
+ type_register_static(&virtio_tablet_info);
+}
+
+type_init(virtio_register_types)
diff --git a/src/hw/input/virtio-input-host.c b/src/hw/input/virtio-input-host.c
new file mode 100644
index 0000000..8978f16
--- /dev/null
+++ b/src/hw/input/virtio-input-host.c
@@ -0,0 +1,189 @@
+/*
+ * This work is licensed under the terms of the GNU GPL, version 2 or
+ * (at your option) any later version. See the COPYING file in the
+ * top-level directory.
+ */
+
+#include "qemu-common.h"
+#include "qemu/sockets.h"
+
+#include "hw/qdev.h"
+#include "hw/virtio/virtio.h"
+#include "hw/virtio/virtio-input.h"
+
+#include <sys/ioctl.h>
+#include "standard-headers/linux/input.h"
+
+/* ----------------------------------------------------------------- */
+
+static struct virtio_input_config virtio_input_host_config[] = {
+ { /* empty list */ },
+};
+
+static void virtio_input_host_event(void *opaque)
+{
+ VirtIOInputHost *vih = opaque;
+ VirtIOInput *vinput = VIRTIO_INPUT(vih);
+ struct virtio_input_event virtio;
+ struct input_event evdev;
+ int rc;
+
+ for (;;) {
+ rc = read(vih->fd, &evdev, sizeof(evdev));
+ if (rc != sizeof(evdev)) {
+ break;
+ }
+
+ virtio.type = cpu_to_le16(evdev.type);
+ virtio.code = cpu_to_le16(evdev.code);
+ virtio.value = cpu_to_le32(evdev.value);
+ virtio_input_send(vinput, &virtio);
+ }
+}
+
+static void virtio_input_bits_config(VirtIOInputHost *vih,
+ int type, int count)
+{
+ virtio_input_config bits;
+ int rc, i, size = 0;
+
+ memset(&bits, 0, sizeof(bits));
+ rc = ioctl(vih->fd, EVIOCGBIT(type, count/8), bits.u.bitmap);
+ if (rc < 0) {
+ return;
+ }
+
+ for (i = 0; i < count/8; i++) {
+ if (bits.u.bitmap[i]) {
+ size = i+1;
+ }
+ }
+ if (size == 0) {
+ return;
+ }
+
+ bits.select = VIRTIO_INPUT_CFG_EV_BITS;
+ bits.subsel = type;
+ bits.size = size;
+ virtio_input_add_config(VIRTIO_INPUT(vih), &bits);
+}
+
+static void virtio_input_host_realize(DeviceState *dev, Error **errp)
+{
+ VirtIOInputHost *vih = VIRTIO_INPUT_HOST(dev);
+ VirtIOInput *vinput = VIRTIO_INPUT(dev);
+ virtio_input_config id;
+ struct input_id ids;
+ int rc, ver;
+
+ if (!vih->evdev) {
+ error_setg(errp, "evdev property is required");
+ return;
+ }
+
+ vih->fd = open(vih->evdev, O_RDWR);
+ if (vih->fd < 0) {
+ error_setg_file_open(errp, errno, vih->evdev);
+ return;
+ }
+ qemu_set_nonblock(vih->fd);
+
+ rc = ioctl(vih->fd, EVIOCGVERSION, &ver);
+ if (rc < 0) {
+ error_setg(errp, "%s: is not an evdev device", vih->evdev);
+ goto err_close;
+ }
+
+ rc = ioctl(vih->fd, EVIOCGRAB, 1);
+ if (rc < 0) {
+ error_setg_errno(errp, errno, "%s: failed to get exclusive access",
+ vih->evdev);
+ goto err_close;
+ }
+
+ memset(&id, 0, sizeof(id));
+ ioctl(vih->fd, EVIOCGNAME(sizeof(id.u.string)-1), id.u.string);
+ id.select = VIRTIO_INPUT_CFG_ID_NAME;
+ id.size = strlen(id.u.string);
+ virtio_input_add_config(vinput, &id);
+
+ if (ioctl(vih->fd, EVIOCGID, &ids) == 0) {
+ memset(&id, 0, sizeof(id));
+ id.select = VIRTIO_INPUT_CFG_ID_DEVIDS;
+ id.size = sizeof(struct virtio_input_devids);
+ id.u.ids.bustype = cpu_to_le16(ids.bustype);
+ id.u.ids.vendor = cpu_to_le16(ids.vendor);
+ id.u.ids.product = cpu_to_le16(ids.product);
+ id.u.ids.version = cpu_to_le16(ids.version);
+ virtio_input_add_config(vinput, &id);
+ }
+
+ virtio_input_bits_config(vih, EV_KEY, KEY_CNT);
+ virtio_input_bits_config(vih, EV_REL, REL_CNT);
+ virtio_input_bits_config(vih, EV_ABS, ABS_CNT);
+ virtio_input_bits_config(vih, EV_MSC, MSC_CNT);
+ virtio_input_bits_config(vih, EV_SW, SW_CNT);
+
+ qemu_set_fd_handler(vih->fd, virtio_input_host_event, NULL, vih);
+ return;
+
+err_close:
+ close(vih->fd);
+ vih->fd = -1;
+ return;
+}
+
+static void virtio_input_host_unrealize(DeviceState *dev, Error **errp)
+{
+ VirtIOInputHost *vih = VIRTIO_INPUT_HOST(dev);
+
+ if (vih->fd > 0) {
+ qemu_set_fd_handler(vih->fd, NULL, NULL, NULL);
+ close(vih->fd);
+ }
+}
+
+static const VMStateDescription vmstate_virtio_input_host = {
+ .name = "virtio-input-host",
+ .unmigratable = 1,
+};
+
+static Property virtio_input_host_properties[] = {
+ DEFINE_PROP_STRING("evdev", VirtIOInputHost, evdev),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void virtio_input_host_class_init(ObjectClass *klass, void *data)
+{
+ VirtIOInputClass *vic = VIRTIO_INPUT_CLASS(klass);
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->vmsd = &vmstate_virtio_input_host;
+ dc->props = virtio_input_host_properties;
+ vic->realize = virtio_input_host_realize;
+ vic->unrealize = virtio_input_host_unrealize;
+}
+
+static void virtio_input_host_init(Object *obj)
+{
+ VirtIOInput *vinput = VIRTIO_INPUT(obj);
+
+ virtio_input_init_config(vinput, virtio_input_host_config);
+}
+
+static const TypeInfo virtio_input_host_info = {
+ .name = TYPE_VIRTIO_INPUT_HOST,
+ .parent = TYPE_VIRTIO_INPUT,
+ .instance_size = sizeof(VirtIOInputHost),
+ .instance_init = virtio_input_host_init,
+ .class_init = virtio_input_host_class_init,
+};
+
+/* ----------------------------------------------------------------- */
+
+static void virtio_register_types(void)
+{
+ type_register_static(&virtio_input_host_info);
+}
+
+type_init(virtio_register_types)
diff --git a/src/hw/input/virtio-input.c b/src/hw/input/virtio-input.c
new file mode 100644
index 0000000..1f5a40d
--- /dev/null
+++ b/src/hw/input/virtio-input.c
@@ -0,0 +1,293 @@
+/*
+ * This work is licensed under the terms of the GNU GPL, version 2 or
+ * (at your option) any later version. See the COPYING file in the
+ * top-level directory.
+ */
+
+#include "qemu/iov.h"
+
+#include "hw/qdev.h"
+#include "hw/virtio/virtio.h"
+#include "hw/virtio/virtio-input.h"
+
+#include "standard-headers/linux/input.h"
+
+/* ----------------------------------------------------------------- */
+
+void virtio_input_send(VirtIOInput *vinput, virtio_input_event *event)
+{
+ VirtQueueElement elem;
+ unsigned have, need;
+ int i, len;
+
+ if (!vinput->active) {
+ return;
+ }
+
+ /* queue up events ... */
+ if (vinput->qindex == vinput->qsize) {
+ vinput->qsize++;
+ vinput->queue = realloc(vinput->queue, vinput->qsize *
+ sizeof(virtio_input_event));
+ }
+ vinput->queue[vinput->qindex++] = *event;
+
+ /* ... until we see a report sync ... */
+ if (event->type != cpu_to_le16(EV_SYN) ||
+ event->code != cpu_to_le16(SYN_REPORT)) {
+ return;
+ }
+
+ /* ... then check available space ... */
+ need = sizeof(virtio_input_event) * vinput->qindex;
+ virtqueue_get_avail_bytes(vinput->evt, &have, NULL, need, 0);
+ if (have < need) {
+ vinput->qindex = 0;
+ fprintf(stderr, "%s: ENOSPC in vq, dropping events\n", __func__);
+ return;
+ }
+
+ /* ... and finally pass them to the guest */
+ for (i = 0; i < vinput->qindex; i++) {
+ if (!virtqueue_pop(vinput->evt, &elem)) {
+ /* should not happen, we've checked for space beforehand */
+ fprintf(stderr, "%s: Huh? No vq elem available ...\n", __func__);
+ return;
+ }
+ len = iov_from_buf(elem.in_sg, elem.in_num,
+ 0, vinput->queue+i, sizeof(virtio_input_event));
+ virtqueue_push(vinput->evt, &elem, len);
+ }
+ virtio_notify(VIRTIO_DEVICE(vinput), vinput->evt);
+ vinput->qindex = 0;
+}
+
+static void virtio_input_handle_evt(VirtIODevice *vdev, VirtQueue *vq)
+{
+ /* nothing */
+}
+
+static void virtio_input_handle_sts(VirtIODevice *vdev, VirtQueue *vq)
+{
+ VirtIOInputClass *vic = VIRTIO_INPUT_GET_CLASS(vdev);
+ VirtIOInput *vinput = VIRTIO_INPUT(vdev);
+ virtio_input_event event;
+ VirtQueueElement elem;
+ int len;
+
+ while (virtqueue_pop(vinput->sts, &elem)) {
+ memset(&event, 0, sizeof(event));
+ len = iov_to_buf(elem.out_sg, elem.out_num,
+ 0, &event, sizeof(event));
+ if (vic->handle_status) {
+ vic->handle_status(vinput, &event);
+ }
+ virtqueue_push(vinput->sts, &elem, len);
+ }
+ virtio_notify(vdev, vinput->sts);
+}
+
+static virtio_input_config *virtio_input_find_config(VirtIOInput *vinput,
+ uint8_t select,
+ uint8_t subsel)
+{
+ VirtIOInputConfig *cfg;
+
+ QTAILQ_FOREACH(cfg, &vinput->cfg_list, node) {
+ if (select == cfg->config.select &&
+ subsel == cfg->config.subsel) {
+ return &cfg->config;
+ }
+ }
+ return NULL;
+}
+
+void virtio_input_add_config(VirtIOInput *vinput,
+ virtio_input_config *config)
+{
+ VirtIOInputConfig *cfg;
+
+ if (virtio_input_find_config(vinput, config->select, config->subsel)) {
+ /* should not happen */
+ fprintf(stderr, "%s: duplicate config: %d/%d\n",
+ __func__, config->select, config->subsel);
+ abort();
+ }
+
+ cfg = g_new0(VirtIOInputConfig, 1);
+ cfg->config = *config;
+ QTAILQ_INSERT_TAIL(&vinput->cfg_list, cfg, node);
+}
+
+void virtio_input_init_config(VirtIOInput *vinput,
+ virtio_input_config *config)
+{
+ int i = 0;
+
+ QTAILQ_INIT(&vinput->cfg_list);
+ while (config[i].select) {
+ virtio_input_add_config(vinput, config + i);
+ i++;
+ }
+}
+
+void virtio_input_idstr_config(VirtIOInput *vinput,
+ uint8_t select, const char *string)
+{
+ virtio_input_config id;
+
+ if (!string) {
+ return;
+ }
+ memset(&id, 0, sizeof(id));
+ id.select = select;
+ id.size = snprintf(id.u.string, sizeof(id.u.string), "%s", string);
+ virtio_input_add_config(vinput, &id);
+}
+
+static void virtio_input_get_config(VirtIODevice *vdev, uint8_t *config_data)
+{
+ VirtIOInput *vinput = VIRTIO_INPUT(vdev);
+ virtio_input_config *config;
+
+ config = virtio_input_find_config(vinput, vinput->cfg_select,
+ vinput->cfg_subsel);
+ if (config) {
+ memcpy(config_data, config, vinput->cfg_size);
+ } else {
+ memset(config_data, 0, vinput->cfg_size);
+ }
+}
+
+static void virtio_input_set_config(VirtIODevice *vdev,
+ const uint8_t *config_data)
+{
+ VirtIOInput *vinput = VIRTIO_INPUT(vdev);
+ virtio_input_config *config = (virtio_input_config *)config_data;
+
+ vinput->cfg_select = config->select;
+ vinput->cfg_subsel = config->subsel;
+ virtio_notify_config(vdev);
+}
+
+static uint64_t virtio_input_get_features(VirtIODevice *vdev, uint64_t f,
+ Error **errp)
+{
+ return f;
+}
+
+static void virtio_input_set_status(VirtIODevice *vdev, uint8_t val)
+{
+ VirtIOInputClass *vic = VIRTIO_INPUT_GET_CLASS(vdev);
+ VirtIOInput *vinput = VIRTIO_INPUT(vdev);
+
+ if (val & VIRTIO_CONFIG_S_DRIVER_OK) {
+ if (!vinput->active) {
+ vinput->active = true;
+ if (vic->change_active) {
+ vic->change_active(vinput);
+ }
+ }
+ }
+}
+
+static void virtio_input_reset(VirtIODevice *vdev)
+{
+ VirtIOInputClass *vic = VIRTIO_INPUT_GET_CLASS(vdev);
+ VirtIOInput *vinput = VIRTIO_INPUT(vdev);
+
+ if (vinput->active) {
+ vinput->active = false;
+ if (vic->change_active) {
+ vic->change_active(vinput);
+ }
+ }
+}
+
+static void virtio_input_device_realize(DeviceState *dev, Error **errp)
+{
+ VirtIOInputClass *vic = VIRTIO_INPUT_GET_CLASS(dev);
+ VirtIODevice *vdev = VIRTIO_DEVICE(dev);
+ VirtIOInput *vinput = VIRTIO_INPUT(dev);
+ VirtIOInputConfig *cfg;
+ Error *local_err = NULL;
+
+ if (vic->realize) {
+ vic->realize(dev, &local_err);
+ if (local_err) {
+ error_propagate(errp, local_err);
+ return;
+ }
+ }
+
+ virtio_input_idstr_config(vinput, VIRTIO_INPUT_CFG_ID_SERIAL,
+ vinput->serial);
+
+ QTAILQ_FOREACH(cfg, &vinput->cfg_list, node) {
+ if (vinput->cfg_size < cfg->config.size) {
+ vinput->cfg_size = cfg->config.size;
+ }
+ }
+ vinput->cfg_size += 8;
+ assert(vinput->cfg_size <= sizeof(virtio_input_config));
+
+ virtio_init(vdev, "virtio-input", VIRTIO_ID_INPUT,
+ vinput->cfg_size);
+ vinput->evt = virtio_add_queue(vdev, 64, virtio_input_handle_evt);
+ vinput->sts = virtio_add_queue(vdev, 64, virtio_input_handle_sts);
+}
+
+static void virtio_input_device_unrealize(DeviceState *dev, Error **errp)
+{
+ VirtIOInputClass *vic = VIRTIO_INPUT_GET_CLASS(dev);
+ VirtIODevice *vdev = VIRTIO_DEVICE(dev);
+ Error *local_err = NULL;
+
+ if (vic->unrealize) {
+ vic->unrealize(dev, &local_err);
+ if (local_err) {
+ error_propagate(errp, local_err);
+ return;
+ }
+ }
+ virtio_cleanup(vdev);
+}
+
+static Property virtio_input_properties[] = {
+ DEFINE_PROP_STRING("serial", VirtIOInput, serial),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void virtio_input_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ VirtioDeviceClass *vdc = VIRTIO_DEVICE_CLASS(klass);
+
+ dc->props = virtio_input_properties;
+ set_bit(DEVICE_CATEGORY_INPUT, dc->categories);
+ vdc->realize = virtio_input_device_realize;
+ vdc->unrealize = virtio_input_device_unrealize;
+ vdc->get_config = virtio_input_get_config;
+ vdc->set_config = virtio_input_set_config;
+ vdc->get_features = virtio_input_get_features;
+ vdc->set_status = virtio_input_set_status;
+ vdc->reset = virtio_input_reset;
+}
+
+static const TypeInfo virtio_input_info = {
+ .name = TYPE_VIRTIO_INPUT,
+ .parent = TYPE_VIRTIO_DEVICE,
+ .instance_size = sizeof(VirtIOInput),
+ .class_size = sizeof(VirtIOInputClass),
+ .class_init = virtio_input_class_init,
+ .abstract = true,
+};
+
+/* ----------------------------------------------------------------- */
+
+static void virtio_register_types(void)
+{
+ type_register_static(&virtio_input_info);
+}
+
+type_init(virtio_register_types)
diff --git a/src/hw/input/vmmouse.c b/src/hw/input/vmmouse.c
new file mode 100644
index 0000000..d7b1c76
--- /dev/null
+++ b/src/hw/input/vmmouse.c
@@ -0,0 +1,303 @@
+/*
+ * QEMU VMMouse emulation
+ *
+ * Copyright (C) 2007 Anthony Liguori <anthony@codemonkey.ws>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+#include "hw/hw.h"
+#include "ui/console.h"
+#include "hw/input/ps2.h"
+#include "hw/i386/pc.h"
+#include "hw/qdev.h"
+
+/* debug only vmmouse */
+//#define DEBUG_VMMOUSE
+
+/* VMMouse Commands */
+#define VMMOUSE_GETVERSION 10
+#define VMMOUSE_DATA 39
+#define VMMOUSE_STATUS 40
+#define VMMOUSE_COMMAND 41
+
+#define VMMOUSE_READ_ID 0x45414552
+#define VMMOUSE_DISABLE 0x000000f5
+#define VMMOUSE_REQUEST_RELATIVE 0x4c455252
+#define VMMOUSE_REQUEST_ABSOLUTE 0x53424152
+
+#define VMMOUSE_QUEUE_SIZE 1024
+
+#define VMMOUSE_VERSION 0x3442554a
+
+#ifdef DEBUG_VMMOUSE
+#define DPRINTF(fmt, ...) printf(fmt, ## __VA_ARGS__)
+#else
+#define DPRINTF(fmt, ...) do { } while (0)
+#endif
+
+#define TYPE_VMMOUSE "vmmouse"
+#define VMMOUSE(obj) OBJECT_CHECK(VMMouseState, (obj), TYPE_VMMOUSE)
+
+typedef struct VMMouseState
+{
+ ISADevice parent_obj;
+
+ uint32_t queue[VMMOUSE_QUEUE_SIZE];
+ int32_t queue_size;
+ uint16_t nb_queue;
+ uint16_t status;
+ uint8_t absolute;
+ QEMUPutMouseEntry *entry;
+ void *ps2_mouse;
+} VMMouseState;
+
+static uint32_t vmmouse_get_status(VMMouseState *s)
+{
+ DPRINTF("vmmouse_get_status()\n");
+ return (s->status << 16) | s->nb_queue;
+}
+
+static void vmmouse_mouse_event(void *opaque, int x, int y, int dz, int buttons_state)
+{
+ VMMouseState *s = opaque;
+ int buttons = 0;
+
+ if (s->nb_queue > (VMMOUSE_QUEUE_SIZE - 4))
+ return;
+
+ DPRINTF("vmmouse_mouse_event(%d, %d, %d, %d)\n",
+ x, y, dz, buttons_state);
+
+ if ((buttons_state & MOUSE_EVENT_LBUTTON))
+ buttons |= 0x20;
+ if ((buttons_state & MOUSE_EVENT_RBUTTON))
+ buttons |= 0x10;
+ if ((buttons_state & MOUSE_EVENT_MBUTTON))
+ buttons |= 0x08;
+
+ if (s->absolute) {
+ x <<= 1;
+ y <<= 1;
+ }
+
+ s->queue[s->nb_queue++] = buttons;
+ s->queue[s->nb_queue++] = x;
+ s->queue[s->nb_queue++] = y;
+ s->queue[s->nb_queue++] = dz;
+
+ /* need to still generate PS2 events to notify driver to
+ read from queue */
+ i8042_isa_mouse_fake_event(s->ps2_mouse);
+}
+
+static void vmmouse_remove_handler(VMMouseState *s)
+{
+ if (s->entry) {
+ qemu_remove_mouse_event_handler(s->entry);
+ s->entry = NULL;
+ }
+}
+
+static void vmmouse_update_handler(VMMouseState *s, int absolute)
+{
+ if (s->status != 0) {
+ return;
+ }
+ if (s->absolute != absolute) {
+ s->absolute = absolute;
+ vmmouse_remove_handler(s);
+ }
+ if (s->entry == NULL) {
+ s->entry = qemu_add_mouse_event_handler(vmmouse_mouse_event,
+ s, s->absolute,
+ "vmmouse");
+ qemu_activate_mouse_event_handler(s->entry);
+ }
+}
+
+static void vmmouse_read_id(VMMouseState *s)
+{
+ DPRINTF("vmmouse_read_id()\n");
+
+ if (s->nb_queue == VMMOUSE_QUEUE_SIZE)
+ return;
+
+ s->queue[s->nb_queue++] = VMMOUSE_VERSION;
+ s->status = 0;
+}
+
+static void vmmouse_request_relative(VMMouseState *s)
+{
+ DPRINTF("vmmouse_request_relative()\n");
+ vmmouse_update_handler(s, 0);
+}
+
+static void vmmouse_request_absolute(VMMouseState *s)
+{
+ DPRINTF("vmmouse_request_absolute()\n");
+ vmmouse_update_handler(s, 1);
+}
+
+static void vmmouse_disable(VMMouseState *s)
+{
+ DPRINTF("vmmouse_disable()\n");
+ s->status = 0xffff;
+ vmmouse_remove_handler(s);
+}
+
+static void vmmouse_data(VMMouseState *s, uint32_t *data, uint32_t size)
+{
+ int i;
+
+ DPRINTF("vmmouse_data(%d)\n", size);
+
+ if (size == 0 || size > 6 || size > s->nb_queue) {
+ printf("vmmouse: driver requested too much data %d\n", size);
+ s->status = 0xffff;
+ vmmouse_remove_handler(s);
+ return;
+ }
+
+ for (i = 0; i < size; i++)
+ data[i] = s->queue[i];
+
+ s->nb_queue -= size;
+ if (s->nb_queue)
+ memmove(s->queue, &s->queue[size], sizeof(s->queue[0]) * s->nb_queue);
+}
+
+static uint32_t vmmouse_ioport_read(void *opaque, uint32_t addr)
+{
+ VMMouseState *s = opaque;
+ uint32_t data[6];
+ uint16_t command;
+
+ vmmouse_get_data(data);
+
+ command = data[2] & 0xFFFF;
+
+ switch (command) {
+ case VMMOUSE_STATUS:
+ data[0] = vmmouse_get_status(s);
+ break;
+ case VMMOUSE_COMMAND:
+ switch (data[1]) {
+ case VMMOUSE_DISABLE:
+ vmmouse_disable(s);
+ break;
+ case VMMOUSE_READ_ID:
+ vmmouse_read_id(s);
+ break;
+ case VMMOUSE_REQUEST_RELATIVE:
+ vmmouse_request_relative(s);
+ break;
+ case VMMOUSE_REQUEST_ABSOLUTE:
+ vmmouse_request_absolute(s);
+ break;
+ default:
+ printf("vmmouse: unknown command %x\n", data[1]);
+ break;
+ }
+ break;
+ case VMMOUSE_DATA:
+ vmmouse_data(s, data, data[1]);
+ break;
+ default:
+ printf("vmmouse: unknown command %x\n", command);
+ break;
+ }
+
+ vmmouse_set_data(data);
+ return data[0];
+}
+
+static int vmmouse_post_load(void *opaque, int version_id)
+{
+ VMMouseState *s = opaque;
+
+ vmmouse_remove_handler(s);
+ vmmouse_update_handler(s, s->absolute);
+ return 0;
+}
+
+static const VMStateDescription vmstate_vmmouse = {
+ .name = "vmmouse",
+ .version_id = 0,
+ .minimum_version_id = 0,
+ .post_load = vmmouse_post_load,
+ .fields = (VMStateField[]) {
+ VMSTATE_INT32_EQUAL(queue_size, VMMouseState),
+ VMSTATE_UINT32_ARRAY(queue, VMMouseState, VMMOUSE_QUEUE_SIZE),
+ VMSTATE_UINT16(nb_queue, VMMouseState),
+ VMSTATE_UINT16(status, VMMouseState),
+ VMSTATE_UINT8(absolute, VMMouseState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void vmmouse_reset(DeviceState *d)
+{
+ VMMouseState *s = VMMOUSE(d);
+
+ s->queue_size = VMMOUSE_QUEUE_SIZE;
+
+ vmmouse_disable(s);
+}
+
+static void vmmouse_realizefn(DeviceState *dev, Error **errp)
+{
+ VMMouseState *s = VMMOUSE(dev);
+
+ DPRINTF("vmmouse_init\n");
+
+ vmport_register(VMMOUSE_STATUS, vmmouse_ioport_read, s);
+ vmport_register(VMMOUSE_COMMAND, vmmouse_ioport_read, s);
+ vmport_register(VMMOUSE_DATA, vmmouse_ioport_read, s);
+}
+
+static Property vmmouse_properties[] = {
+ DEFINE_PROP_PTR("ps2_mouse", VMMouseState, ps2_mouse),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void vmmouse_class_initfn(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->realize = vmmouse_realizefn;
+ dc->reset = vmmouse_reset;
+ dc->vmsd = &vmstate_vmmouse;
+ dc->props = vmmouse_properties;
+ /* Reason: pointer property "ps2_mouse" */
+ dc->cannot_instantiate_with_device_add_yet = true;
+}
+
+static const TypeInfo vmmouse_info = {
+ .name = TYPE_VMMOUSE,
+ .parent = TYPE_ISA_DEVICE,
+ .instance_size = sizeof(VMMouseState),
+ .class_init = vmmouse_class_initfn,
+};
+
+static void vmmouse_register_types(void)
+{
+ type_register_static(&vmmouse_info);
+}
+
+type_init(vmmouse_register_types)
OpenPOWER on IntegriCloud