diff options
author | Paolo Bonzini <pbonzini@redhat.com> | 2013-03-01 13:59:19 +0100 |
---|---|---|
committer | Paolo Bonzini <pbonzini@redhat.com> | 2013-04-08 18:13:12 +0200 |
commit | 49ab747f668f421138d5b40d83fa279c4c5e278d (patch) | |
tree | 943225a04eac885aed038731adf058f2250a2f40 /hw/input | |
parent | ce3b494cb504f96992f2d37ebc8f56deed202b06 (diff) |
hw: move target-independent files to subdirectories
This patch tackles all files that are compiled once, moving
them to subdirectories of hw/.
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
Diffstat (limited to 'hw/input')
-rw-r--r-- | hw/input/Makefile.objs | 9 | ||||
-rw-r--r-- | hw/input/adb.c | 581 | ||||
-rw-r--r-- | hw/input/hid.c | 498 | ||||
-rw-r--r-- | hw/input/lm832x.c | 521 | ||||
-rw-r--r-- | hw/input/pckbd.c | 527 | ||||
-rw-r--r-- | hw/input/pl050.c | 199 | ||||
-rw-r--r-- | hw/input/ps2.c | 676 | ||||
-rw-r--r-- | hw/input/stellaris_input.c | 89 | ||||
-rw-r--r-- | hw/input/tsc2005.c | 593 | ||||
-rw-r--r-- | hw/input/vmmouse.c | 301 |
10 files changed, 3994 insertions, 0 deletions
diff --git a/hw/input/Makefile.objs b/hw/input/Makefile.objs index e69de29bb2..824997e367 100644 --- a/hw/input/Makefile.objs +++ b/hw/input/Makefile.objs @@ -0,0 +1,9 @@ +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 diff --git a/hw/input/adb.c b/hw/input/adb.c new file mode 100644 index 0000000000..a75d3fd7b9 --- /dev/null +++ b/hw/input/adb.c @@ -0,0 +1,581 @@ +/* + * 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 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 = 1, + .minimum_version_id = 1, + .minimum_version_id_old = 1, + .fields = (VMStateField[]) { + 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; + + 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 = 1, + .minimum_version_id = 1, + .minimum_version_id_old = 1, + .fields = (VMStateField[]) { + 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; + + 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/hw/input/hid.c b/hw/input/hid.c new file mode 100644 index 0000000000..5fbde98f65 --- /dev/null +++ b/hw/input/hid.c @@ -0,0 +1,498 @@ +/* + * 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, 0x32, 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) { + qemu_del_timer(hs->idle_timer); + qemu_free_timer(hs->idle_timer); + hs->idle_timer = NULL; + } +} + +void hid_set_next_idle(HIDState *hs) +{ + if (hs->idle) { + uint64_t expire_time = qemu_get_clock_ns(vm_clock) + + get_ticks_per_sec() * hs->idle * 4 / 1000; + if (!hs->idle_timer) { + hs->idle_timer = qemu_new_timer_ns(vm_clock, hid_idle_timer, hs); + } + qemu_mod_timer_ns(hs->idle_timer, expire_time); + } else { + hid_del_idle_timer(hs); + } +} + +static void hid_pointer_event_clear(HIDPointerEvent *e, int buttons) +{ + e->xdx = e->ydy = e->dz = 0; + e->buttons_state = buttons; +} + +static void hid_pointer_event_combine(HIDPointerEvent *e, int xyrel, + int x1, int y1, int z1) { + if (xyrel) { + e->xdx += x1; + e->ydy += y1; + } else { + e->xdx = x1; + e->ydy = y1; + /* Windows drivers do not like the 0/0 position and ignore such + * events. */ + if (!(x1 | y1)) { + e->xdx = 1; + } + } + e->dz += z1; +} + +static void hid_pointer_event(void *opaque, + int x1, int y1, int z1, int buttons_state) +{ + HIDState *hs = opaque; + unsigned use_slot = (hs->head + hs->n - 1) & QUEUE_MASK; + unsigned previous_slot = (use_slot - 1) & QUEUE_MASK; + + /* We combine events where feasible to keep the queue small. We shouldn't + * combine anything with the first event of a particular button state, as + * that would change the location of the button state change. When the + * queue is empty, a second event is needed because we don't know if + * the first event changed the button state. */ + if (hs->n == QUEUE_LENGTH) { + /* Queue full. Discard old button state, combine motion normally. */ + hs->ptr.queue[use_slot].buttons_state = buttons_state; + } else if (hs->n < 2 || + hs->ptr.queue[use_slot].buttons_state != buttons_state || + hs->ptr.queue[previous_slot].buttons_state != + hs->ptr.queue[use_slot].buttons_state) { + /* Cannot or should not combine, so add an empty item to the queue. */ + QUEUE_INCR(use_slot); + hs->n++; + hid_pointer_event_clear(&hs->ptr.queue[use_slot], buttons_state); + } + hid_pointer_event_combine(&hs->ptr.queue[use_slot], + hs->kind == HID_MOUSE, + x1, y1, z1); + hs->event(hs); +} + +static void hid_keyboard_event(void *opaque, int keycode) +{ + HIDState *hs = opaque; + int slot; + + if (hs->n == QUEUE_LENGTH) { + fprintf(stderr, "usb-kbd: warning: key event queue full\n"); + return; + } + slot = (hs->head + hs->n) & QUEUE_MASK; hs->n++; + hs->kbd.keycodes[slot] = keycode; + hs->event(hs); +} + +static void hid_keyboard_process_keycode(HIDState *hs) +{ + uint8_t hid_code, 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; + hid_code = hid_usage_keys[key | ((hs->kbd.modifiers >> 1) & (1 << 7))]; + hs->kbd.modifiers &= ~(1 << 8); + + switch (hid_code) { + case 0x00: + return; + + case 0xe0: + if (hs->kbd.modifiers & (1 << 9)) { + hs->kbd.modifiers ^= 3 << 8; + return; + } + case 0xe1 ... 0xe7: + if (keycode & (1 << 7)) { + hs->kbd.modifiers &= ~(1 << (hid_code & 0x0f)); + return; + } + case 0xe8 ... 0xef: + hs->kbd.modifiers |= 1 << (hid_code & 0x0f); + return; + } + + 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_activate_mouse_event_handler(hs->ptr.eh_entry); + hs->ptr.mouse_grabbed = 1; + } +} + +int hid_pointer_poll(HIDState *hs, uint8_t *buf, int len) +{ + int dx, dy, dz, b, 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; + + b = 0; + if (e->buttons_state & MOUSE_EVENT_LBUTTON) { + b |= 0x01; + } + if (e->buttons_state & MOUSE_EVENT_RBUTTON) { + b |= 0x02; + } + if (e->buttons_state & MOUSE_EVENT_MBUTTON) { + b |= 0x04; + } + + 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++] = b; + } + 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++] = b; + } + 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) +{ + switch (hs->kind) { + case HID_KEYBOARD: + qemu_remove_kbd_event_handler(); + break; + case HID_MOUSE: + case HID_TABLET: + qemu_remove_mouse_event_handler(hs->ptr.eh_entry); + break; + } + hid_del_idle_timer(hs); +} + +void hid_init(HIDState *hs, int kind, HIDEventFunc event) +{ + hs->kind = kind; + hs->event = event; + + if (hs->kind == HID_KEYBOARD) { + qemu_add_kbd_event_handler(hid_keyboard_event, hs); + } else if (hs->kind == HID_MOUSE) { + hs->ptr.eh_entry = qemu_add_mouse_event_handler(hid_pointer_event, hs, + 0, "QEMU HID Mouse"); + } else if (hs->kind == HID_TABLET) { + hs->ptr.eh_entry = qemu_add_mouse_event_handler(hid_pointer_event, hs, + 1, "QEMU HID Tablet"); + } +} + +static int hid_post_load(void *opaque, int version_id) +{ + HIDState *s = opaque; + + hid_set_next_idle(s); + 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/hw/input/lm832x.c b/hw/input/lm832x.c new file mode 100644 index 0000000000..bacbeb2343 --- /dev/null +++ b/hw/input/lm832x.c @@ -0,0 +1,521 @@ +/* + * 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" + +typedef struct { + I2CSlave i2c; + 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; + } + + qemu_del_timer(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 = FROM_I2C_SLAVE(LM823KbdState, 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 = FROM_I2C_SLAVE(LM823KbdState, i2c); + + return lm_kbd_read(s, s->reg, s->i2c_cycle ++); +} + +static int lm_i2c_tx(I2CSlave *i2c, uint8_t data) +{ + LM823KbdState *s = (LM823KbdState *) 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, + .minimum_version_id_old = 0, + .post_load = lm_kbd_post_load, + .fields = (VMStateField []) { + VMSTATE_I2C_SLAVE(i2c, 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_ARRAY(pwm.tm, LM823KbdState, 3), + VMSTATE_END_OF_LIST() + } +}; + + +static int lm8323_init(I2CSlave *i2c) +{ + LM823KbdState *s = FROM_I2C_SLAVE(LM823KbdState, i2c); + + s->model = 0x8323; + s->pwm.tm[0] = qemu_new_timer_ns(vm_clock, lm_kbd_pwm0_tick, s); + s->pwm.tm[1] = qemu_new_timer_ns(vm_clock, lm_kbd_pwm1_tick, s); + s->pwm.tm[2] = qemu_new_timer_ns(vm_clock, lm_kbd_pwm2_tick, s); + qdev_init_gpio_out(&i2c->qdev, &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 = FROM_I2C_SLAVE(LM823KbdState, I2C_SLAVE(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 = "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/hw/input/pckbd.c b/hw/input/pckbd.c new file mode 100644 index 0000000000..08ceb9fe8a --- /dev/null +++ b/hw/input/pckbd.c @@ -0,0 +1,527 @@ +/* + * 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 */ + +/* 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; + /* 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%02x\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, 0x00, 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%02x\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; +} + +static const VMStateDescription vmstate_kbd = { + .name = "pckbd", + .version_id = 3, + .minimum_version_id = 3, + .minimum_version_id_old = 3, + .fields = (VMStateField []) { + VMSTATE_UINT8(write_cmd, KBDState), + VMSTATE_UINT8(status, KBDState), + VMSTATE_UINT8(mode, KBDState), + VMSTATE_UINT8(pending, KBDState), + VMSTATE_END_OF_LIST() + } +}; + +/* 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, &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); +} + +typedef struct ISAKBDState { + ISADevice dev; + KBDState kbd; + MemoryRegion io[2]; +} ISAKBDState; + +void i8042_isa_mouse_fake_event(void *opaque) +{ + ISADevice *dev = opaque; + KBDState *s = &(DO_UPCAST(ISAKBDState, dev, dev)->kbd); + + ps2_mouse_fake_event(s->mouse); +} + +void i8042_setup_a20_line(ISADevice *dev, qemu_irq *a20_out) +{ + KBDState *s = &(DO_UPCAST(ISAKBDState, dev, dev)->kbd); + + s->a20_out = a20_out; +} + +static const VMStateDescription vmstate_kbd_isa = { + .name = "pckbd", + .version_id = 3, + .minimum_version_id = 3, + .minimum_version_id_old = 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 int i8042_initfn(ISADevice *dev) +{ + ISAKBDState *isa_s = DO_UPCAST(ISAKBDState, dev, dev); + KBDState *s = &isa_s->kbd; + + isa_init_irq(dev, &s->irq_kbd, 1); + isa_init_irq(dev, &s->irq_mouse, 12); + + memory_region_init_io(isa_s->io + 0, &i8042_data_ops, s, "i8042-data", 1); + isa_register_ioport(dev, isa_s->io + 0, 0x60); + + memory_region_init_io(isa_s->io + 1, &i8042_cmd_ops, s, "i8042-cmd", 1); + isa_register_ioport(dev, 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); + return 0; +} + +static void i8042_class_initfn(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + ISADeviceClass *ic = ISA_DEVICE_CLASS(klass); + ic->init = i8042_initfn; + dc->no_user = 1; + dc->vmsd = &vmstate_kbd_isa; +} + +static const TypeInfo i8042_info = { + .name = "i8042", + .parent = TYPE_ISA_DEVICE, + .instance_size = sizeof(ISAKBDState), + .class_init = i8042_class_initfn, +}; + +static void i8042_register_types(void) +{ + type_register_static(&i8042_info); +} + +type_init(i8042_register_types) diff --git a/hw/input/pl050.c b/hw/input/pl050.c new file mode 100644 index 0000000000..7dd8a59dd4 --- /dev/null +++ b/hw/input/pl050.c @@ -0,0 +1,199 @@ +/* + * 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" + +typedef struct { + SysBusDevice busdev; + MemoryRegion iomem; + void *dev; + uint32_t cr; + uint32_t clk; + uint32_t last; + int pending; + qemu_irq irq; + int is_mouse; +} pl050_state; + +static const VMStateDescription vmstate_pl050 = { + .name = "pl050", + .version_id = 2, + .minimum_version_id = 2, + .fields = (VMStateField[]) { + VMSTATE_UINT32(cr, pl050_state), + VMSTATE_UINT32(clk, pl050_state), + VMSTATE_UINT32(last, pl050_state), + VMSTATE_INT32(pending, pl050_state), + 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) +{ + pl050_state *s = (pl050_state *)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) +{ + pl050_state *s = (pl050_state *)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) +{ + pl050_state *s = (pl050_state *)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_init(SysBusDevice *dev, int is_mouse) +{ + pl050_state *s = FROM_SYSBUS(pl050_state, dev); + + memory_region_init_io(&s->iomem, &pl050_ops, s, "pl050", 0x1000); + sysbus_init_mmio(dev, &s->iomem); + sysbus_init_irq(dev, &s->irq); + s->is_mouse = is_mouse; + if (s->is_mouse) + s->dev = ps2_mouse_init(pl050_update, s); + else + s->dev = ps2_kbd_init(pl050_update, s); + return 0; +} + +static int pl050_init_keyboard(SysBusDevice *dev) +{ + return pl050_init(dev, 0); +} + +static int pl050_init_mouse(SysBusDevice *dev) +{ + return pl050_init(dev, 1); +} + +static void pl050_kbd_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass); + + k->init = pl050_init_keyboard; + dc->vmsd = &vmstate_pl050; +} + +static const TypeInfo pl050_kbd_info = { + .name = "pl050_keyboard", + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(pl050_state), + .class_init = pl050_kbd_class_init, +}; + +static void pl050_mouse_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass); + + k->init = pl050_init_mouse; + dc->vmsd = &vmstate_pl050; +} + +static const TypeInfo pl050_mouse_info = { + .name = "pl050_mouse", + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(pl050_state), + .class_init = pl050_mouse_class_init, +}; + +static void pl050_register_types(void) +{ + type_register_static(&pl050_kbd_info); + type_register_static(&pl050_mouse_info); +} + +type_init(pl050_register_types) diff --git a/hw/input/ps2.c b/hw/input/ps2.c new file mode 100644 index 0000000000..34120796b1 --- /dev/null +++ b/hw/input/ps2.c @@ -0,0 +1,676 @@ +/* + * 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 "sysemu/sysemu.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 256 + +typedef struct { + uint8_t data[PS2_QUEUE_SIZE]; + 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) + 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; + + 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); +} + +uint32_t ps2_read_data(void *opaque) +{ + PS2State *s = (PS2State *)opaque; + PS2Queue *q; + int val, index; + + 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) +{ + s->ledstate = ledstate; + kbd_put_ledstate(ledstate); +} + +static void ps2_reset_keyboard(PS2KbdState *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; + + 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; + 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; + } + + /* update deltas */ + s->mouse_dx -= dx1; + s->mouse_dy -= dy1; + s->mouse_dz -= dz1; +} + +static void ps2_mouse_event(void *opaque, + int dx, int dy, int dz, int buttons_state) +{ + PS2MouseState *s = opaque; + + /* check if deltas are recorded when disabled */ + if (!(s->mouse_status & MOUSE_STATUS_ENABLED)) + return; + + s->mouse_dx += dx; + s->mouse_dy -= dy; + s->mouse_dz += dz; + /* XXX: SDL sometimes generates nul events: we delete them */ + if (s->mouse_dx == 0 && s->mouse_dy == 0 && s->mouse_dz == 0 && + s->mouse_buttons == buttons_state) + return; + s->mouse_buttons = buttons_state; + + if (buttons_state) { + qemu_system_wakeup_request(QEMU_WAKEUP_REASON_OTHER); + } + + if (!(s->mouse_status & MOUSE_STATUS_REMOTE) && + (s->common.queue.count < (PS2_QUEUE_SIZE - 16))) { + for(;;) { + /* 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) +{ + ps2_mouse_event(opaque, 1, 0, 0, 0); +} + +void ps2_write_mouse(void *opaque, int val) +{ + PS2MouseState *s = (PS2MouseState *)opaque; +#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_kbd_reset(void *opaque) +{ + PS2KbdState *s = (PS2KbdState *) 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; + + 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, + .minimum_version_id_old = 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, + .minimum_version_id_old = 2, + .post_load = ps2_kbd_ledstate_post_load, + .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; + + if (version_id == 2) + s->scancode_set=2; + return 0; +} + +static const VMStateDescription vmstate_ps2_keyboard = { + .name = "ps2kbd", + .version_id = 3, + .minimum_version_id = 2, + .minimum_version_id_old = 2, + .post_load = ps2_kbd_post_load, + .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 = (VMStateSubsection []) { + { + .vmsd = &vmstate_ps2_keyboard_ledstate, + .needed = ps2_keyboard_ledstate_needed, + }, { + /* empty */ + } + } +}; + +static const VMStateDescription vmstate_ps2_mouse = { + .name = "ps2mouse", + .version_id = 2, + .minimum_version_id = 2, + .minimum_version_id_old = 2, + .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() + } +}; + +void *ps2_kbd_init(void (*update_irq)(void *, int), void *update_arg) +{ + PS2KbdState *s = (PS2KbdState *)g_malloc0(sizeof(PS2KbdState)); + + 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_add_kbd_event_handler(ps2_put_keycode, s); + qemu_register_reset(ps2_kbd_reset, s); + return s; +} + +void *ps2_mouse_init(void (*update_irq)(void *, int), void *update_arg) +{ + PS2MouseState *s = (PS2MouseState *)g_malloc0(sizeof(PS2MouseState)); + + s->common.update_irq = update_irq; + s->common.update_arg = update_arg; + vmstate_register(NULL, 0, &vmstate_ps2_mouse, s); + qemu_add_mouse_event_handler(ps2_mouse_event, s, 0, "QEMU PS/2 Mouse"); + qemu_register_reset(ps2_mouse_reset, s); + return s; +} diff --git a/hw/input/stellaris_input.c b/hw/input/stellaris_input.c new file mode 100644 index 0000000000..f83fc3f288 --- /dev/null +++ b/hw/input/stellaris_input.c @@ -0,0 +1,89 @@ +/* + * 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/arm/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, + .minimum_version_id_old = 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, + .minimum_version_id_old = 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 5 ouput slots. */ +void stellaris_gamepad_init(int n, qemu_irq *irq, const int *keycode) +{ + gamepad_state *s; + int i; + + s = (gamepad_state *)g_malloc0(sizeof (gamepad_state)); + s->buttons = (gamepad_button *)g_malloc0(n * sizeof (gamepad_button)); + 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/hw/input/tsc2005.c b/hw/input/tsc2005.c new file mode 100644 index 0000000000..34ee1fb3cf --- /dev/null +++ b/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/arm/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) + qemu_del_timer(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_get_clock_ns(vm_clock) + (get_ticks_per_sec() >> 7); + qemu_mod_timer(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) + qemu_del_timer(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); + + qemu_put_timer(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); + + qemu_get_timer(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 = qemu_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 = qemu_new_timer_ns(vm_clock, 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/hw/input/vmmouse.c b/hw/input/vmmouse.c new file mode 100644 index 0000000000..f4f9c9373d --- /dev/null +++ b/hw/input/vmmouse.c @@ -0,0 +1,301 @@ +/* + * 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 + +typedef struct _VMMouseState +{ + ISADevice dev; + 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, + .minimum_version_id_old = 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 = container_of(d, VMMouseState, dev.qdev); + + s->queue_size = VMMOUSE_QUEUE_SIZE; + + vmmouse_disable(s); +} + +static int vmmouse_initfn(ISADevice *dev) +{ + VMMouseState *s = DO_UPCAST(VMMouseState, dev, 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); + + return 0; +} + +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); + ISADeviceClass *ic = ISA_DEVICE_CLASS(klass); + ic->init = vmmouse_initfn; + dc->no_user = 1; + dc->reset = vmmouse_reset; + dc->vmsd = &vmstate_vmmouse; + dc->props = vmmouse_properties; +} + +static const TypeInfo vmmouse_info = { + .name = "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) |