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