1 /* 2 * Arm PrimeCell PL050 Keyboard / Mouse Interface 3 * 4 * Copyright (c) 2006-2007 CodeSourcery. 5 * Written by Paul Brook 6 * 7 * This code is licensed under the GPL. 8 */ 9 10 /* 11 * QEMU interface: 12 * + sysbus MMIO region 0: MemoryRegion defining the PL050 registers 13 * + Named GPIO input "ps2-input-irq": set to 1 if the downstream PS2 device 14 * has asserted its irq 15 * + sysbus IRQ 0: PL050 output irq 16 */ 17 18 #include "qemu/osdep.h" 19 #include "hw/sysbus.h" 20 #include "migration/vmstate.h" 21 #include "hw/input/ps2.h" 22 #include "hw/input/pl050.h" 23 #include "hw/irq.h" 24 #include "qemu/log.h" 25 #include "qemu/module.h" 26 #include "qom/object.h" 27 28 29 static const VMStateDescription vmstate_pl050 = { 30 .name = "pl050", 31 .version_id = 2, 32 .minimum_version_id = 2, 33 .fields = (VMStateField[]) { 34 VMSTATE_UINT32(cr, PL050State), 35 VMSTATE_UINT32(clk, PL050State), 36 VMSTATE_UINT32(last, PL050State), 37 VMSTATE_INT32(pending, PL050State), 38 VMSTATE_END_OF_LIST() 39 } 40 }; 41 42 #define PL050_TXEMPTY (1 << 6) 43 #define PL050_TXBUSY (1 << 5) 44 #define PL050_RXFULL (1 << 4) 45 #define PL050_RXBUSY (1 << 3) 46 #define PL050_RXPARITY (1 << 2) 47 #define PL050_KMIC (1 << 1) 48 #define PL050_KMID (1 << 0) 49 50 static const unsigned char pl050_id[] = { 51 0x50, 0x10, 0x04, 0x00, 0x0d, 0xf0, 0x05, 0xb1 52 }; 53 54 static void pl050_update_irq(PL050State *s) 55 { 56 int level = (s->pending && (s->cr & 0x10) != 0) 57 || (s->cr & 0x08) != 0; 58 59 qemu_set_irq(s->irq, level); 60 } 61 62 static void pl050_set_irq(void *opaque, int n, int level) 63 { 64 PL050State *s = (PL050State *)opaque; 65 66 s->pending = level; 67 pl050_update_irq(s); 68 } 69 70 static uint64_t pl050_read(void *opaque, hwaddr offset, 71 unsigned size) 72 { 73 PL050State *s = (PL050State *)opaque; 74 75 if (offset >= 0xfe0 && offset < 0x1000) { 76 return pl050_id[(offset - 0xfe0) >> 2]; 77 } 78 79 switch (offset >> 2) { 80 case 0: /* KMICR */ 81 return s->cr; 82 case 1: /* KMISTAT */ 83 { 84 uint8_t val; 85 uint32_t stat; 86 87 val = s->last; 88 val = val ^ (val >> 4); 89 val = val ^ (val >> 2); 90 val = (val ^ (val >> 1)) & 1; 91 92 stat = PL050_TXEMPTY; 93 if (val) { 94 stat |= PL050_RXPARITY; 95 } 96 if (s->pending) { 97 stat |= PL050_RXFULL; 98 } 99 100 return stat; 101 } 102 case 2: /* KMIDATA */ 103 if (s->pending) { 104 s->last = ps2_read_data(s->ps2dev); 105 } 106 return s->last; 107 case 3: /* KMICLKDIV */ 108 return s->clk; 109 case 4: /* KMIIR */ 110 return s->pending | 2; 111 default: 112 qemu_log_mask(LOG_GUEST_ERROR, 113 "pl050_read: Bad offset %x\n", (int)offset); 114 return 0; 115 } 116 } 117 118 static void pl050_write(void *opaque, hwaddr offset, 119 uint64_t value, unsigned size) 120 { 121 PL050State *s = (PL050State *)opaque; 122 123 switch (offset >> 2) { 124 case 0: /* KMICR */ 125 s->cr = value; 126 pl050_update_irq(s); 127 /* ??? Need to implement the enable/disable bit. */ 128 break; 129 case 2: /* KMIDATA */ 130 /* ??? This should toggle the TX interrupt line. */ 131 /* ??? This means kbd/mouse can block each other. */ 132 if (s->is_mouse) { 133 ps2_write_mouse(PS2_MOUSE_DEVICE(s->ps2dev), value); 134 } else { 135 ps2_write_keyboard(PS2_KBD_DEVICE(s->ps2dev), value); 136 } 137 break; 138 case 3: /* KMICLKDIV */ 139 s->clk = value; 140 return; 141 default: 142 qemu_log_mask(LOG_GUEST_ERROR, 143 "pl050_write: Bad offset %x\n", (int)offset); 144 } 145 } 146 static const MemoryRegionOps pl050_ops = { 147 .read = pl050_read, 148 .write = pl050_write, 149 .endianness = DEVICE_NATIVE_ENDIAN, 150 }; 151 152 static void pl050_realize(DeviceState *dev, Error **errp) 153 { 154 PL050State *s = PL050(dev); 155 SysBusDevice *sbd = SYS_BUS_DEVICE(dev); 156 157 memory_region_init_io(&s->iomem, OBJECT(s), &pl050_ops, s, "pl050", 0x1000); 158 sysbus_init_mmio(sbd, &s->iomem); 159 sysbus_init_irq(sbd, &s->irq); 160 if (s->is_mouse) { 161 s->ps2dev = ps2_mouse_init(); 162 } else { 163 s->ps2dev = ps2_kbd_init(); 164 } 165 166 qdev_connect_gpio_out(DEVICE(s->ps2dev), PS2_DEVICE_IRQ, 167 qdev_get_gpio_in_named(dev, "ps2-input-irq", 0)); 168 } 169 170 static void pl050_kbd_init(Object *obj) 171 { 172 PL050State *s = PL050(obj); 173 174 s->is_mouse = false; 175 } 176 177 static void pl050_mouse_init(Object *obj) 178 { 179 PL050State *s = PL050(obj); 180 181 s->is_mouse = true; 182 } 183 184 static const TypeInfo pl050_kbd_info = { 185 .name = TYPE_PL050_KBD_DEVICE, 186 .parent = TYPE_PL050, 187 .instance_init = pl050_kbd_init, 188 .instance_size = sizeof(PL050KbdState), 189 }; 190 191 static const TypeInfo pl050_mouse_info = { 192 .name = TYPE_PL050_MOUSE_DEVICE, 193 .parent = TYPE_PL050, 194 .instance_init = pl050_mouse_init, 195 .instance_size = sizeof(PL050MouseState), 196 }; 197 198 static void pl050_init(Object *obj) 199 { 200 qdev_init_gpio_in_named(DEVICE(obj), pl050_set_irq, "ps2-input-irq", 1); 201 } 202 203 static void pl050_class_init(ObjectClass *oc, void *data) 204 { 205 DeviceClass *dc = DEVICE_CLASS(oc); 206 207 dc->realize = pl050_realize; 208 dc->vmsd = &vmstate_pl050; 209 } 210 211 static const TypeInfo pl050_type_info = { 212 .name = TYPE_PL050, 213 .parent = TYPE_SYS_BUS_DEVICE, 214 .instance_init = pl050_init, 215 .instance_size = sizeof(PL050State), 216 .abstract = true, 217 .class_init = pl050_class_init, 218 }; 219 220 static void pl050_register_types(void) 221 { 222 type_register_static(&pl050_type_info); 223 type_register_static(&pl050_kbd_info); 224 type_register_static(&pl050_mouse_info); 225 } 226 227 type_init(pl050_register_types) 228