|
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 licenced under the GPL. |
|
8 */ |
|
9 |
|
10 #include "hw.h" |
|
11 #include "primecell.h" |
|
12 #include "ps2.h" |
|
13 |
|
14 typedef struct { |
|
15 void *dev; |
|
16 uint32_t cr; |
|
17 uint32_t clk; |
|
18 uint32_t last; |
|
19 int pending; |
|
20 qemu_irq irq; |
|
21 int is_mouse; |
|
22 } pl050_state; |
|
23 |
|
24 #define PL050_TXEMPTY (1 << 6) |
|
25 #define PL050_TXBUSY (1 << 5) |
|
26 #define PL050_RXFULL (1 << 4) |
|
27 #define PL050_RXBUSY (1 << 3) |
|
28 #define PL050_RXPARITY (1 << 2) |
|
29 #define PL050_KMIC (1 << 1) |
|
30 #define PL050_KMID (1 << 0) |
|
31 |
|
32 static const unsigned char pl050_id[] = |
|
33 { 0x50, 0x10, 0x04, 0x00, 0x0d, 0xf0, 0x05, 0xb1 }; |
|
34 |
|
35 static void pl050_update(void *opaque, int level) |
|
36 { |
|
37 pl050_state *s = (pl050_state *)opaque; |
|
38 int raise; |
|
39 |
|
40 s->pending = level; |
|
41 raise = (s->pending && (s->cr & 0x10) != 0) |
|
42 || (s->cr & 0x08) != 0; |
|
43 qemu_set_irq(s->irq, raise); |
|
44 } |
|
45 |
|
46 static uint32_t pl050_read(void *opaque, target_phys_addr_t offset) |
|
47 { |
|
48 pl050_state *s = (pl050_state *)opaque; |
|
49 if (offset >= 0xfe0 && offset < 0x1000) |
|
50 return pl050_id[(offset - 0xfe0) >> 2]; |
|
51 |
|
52 switch (offset >> 2) { |
|
53 case 0: /* KMICR */ |
|
54 return s->cr; |
|
55 case 1: /* KMISTAT */ |
|
56 { |
|
57 uint8_t val; |
|
58 uint32_t stat; |
|
59 |
|
60 val = s->last; |
|
61 val = val ^ (val >> 4); |
|
62 val = val ^ (val >> 2); |
|
63 val = (val ^ (val >> 1)) & 1; |
|
64 |
|
65 stat = PL050_TXEMPTY; |
|
66 if (val) |
|
67 stat |= PL050_RXPARITY; |
|
68 if (s->pending) |
|
69 stat |= PL050_RXFULL; |
|
70 |
|
71 return stat; |
|
72 } |
|
73 case 2: /* KMIDATA */ |
|
74 if (s->pending) |
|
75 s->last = ps2_read_data(s->dev); |
|
76 return s->last; |
|
77 case 3: /* KMICLKDIV */ |
|
78 return s->clk; |
|
79 case 4: /* KMIIR */ |
|
80 return s->pending | 2; |
|
81 default: |
|
82 cpu_abort (cpu_single_env, "pl050_read: Bad offset %x\n", (int)offset); |
|
83 return 0; |
|
84 } |
|
85 } |
|
86 |
|
87 static void pl050_write(void *opaque, target_phys_addr_t offset, |
|
88 uint32_t value) |
|
89 { |
|
90 pl050_state *s = (pl050_state *)opaque; |
|
91 switch (offset >> 2) { |
|
92 case 0: /* KMICR */ |
|
93 s->cr = value; |
|
94 pl050_update(s, s->pending); |
|
95 /* ??? Need to implement the enable/disable bit. */ |
|
96 break; |
|
97 case 2: /* KMIDATA */ |
|
98 /* ??? This should toggle the TX interrupt line. */ |
|
99 /* ??? This means kbd/mouse can block each other. */ |
|
100 if (s->is_mouse) { |
|
101 ps2_write_mouse(s->dev, value); |
|
102 } else { |
|
103 ps2_write_keyboard(s->dev, value); |
|
104 } |
|
105 break; |
|
106 case 3: /* KMICLKDIV */ |
|
107 s->clk = value; |
|
108 return; |
|
109 default: |
|
110 cpu_abort (cpu_single_env, "pl050_write: Bad offset %x\n", (int)offset); |
|
111 } |
|
112 } |
|
113 static CPUReadMemoryFunc *pl050_readfn[] = { |
|
114 pl050_read, |
|
115 pl050_read, |
|
116 pl050_read |
|
117 }; |
|
118 |
|
119 static CPUWriteMemoryFunc *pl050_writefn[] = { |
|
120 pl050_write, |
|
121 pl050_write, |
|
122 pl050_write |
|
123 }; |
|
124 |
|
125 void pl050_init(uint32_t base, qemu_irq irq, int is_mouse) |
|
126 { |
|
127 int iomemtype; |
|
128 pl050_state *s; |
|
129 |
|
130 s = (pl050_state *)qemu_mallocz(sizeof(pl050_state)); |
|
131 iomemtype = cpu_register_io_memory(0, pl050_readfn, |
|
132 pl050_writefn, s); |
|
133 cpu_register_physical_memory(base, 0x00001000, iomemtype); |
|
134 s->irq = irq; |
|
135 s->is_mouse = is_mouse; |
|
136 if (is_mouse) |
|
137 s->dev = ps2_mouse_init(pl050_update, s); |
|
138 else |
|
139 s->dev = ps2_kbd_init(pl050_update, s); |
|
140 /* ??? Save/restore. */ |
|
141 } |