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