|
1 /* |
|
2 * TI ADS7846 / TSC2046 chip emulation. |
|
3 * |
|
4 * Copyright (c) 2006 Openedhand Ltd. |
|
5 * Written by Andrzej Zaborowski <balrog@zabor.org> |
|
6 * |
|
7 * This code is licensed under the GNU GPL v2. |
|
8 */ |
|
9 |
|
10 #include "hw.h" |
|
11 #include "devices.h" |
|
12 #include "console.h" |
|
13 |
|
14 struct ads7846_state_s { |
|
15 qemu_irq interrupt; |
|
16 |
|
17 int input[8]; |
|
18 int pressure; |
|
19 int noise; |
|
20 |
|
21 int cycle; |
|
22 int output; |
|
23 }; |
|
24 |
|
25 /* Control-byte bitfields */ |
|
26 #define CB_PD0 (1 << 0) |
|
27 #define CB_PD1 (1 << 1) |
|
28 #define CB_SER (1 << 2) |
|
29 #define CB_MODE (1 << 3) |
|
30 #define CB_A0 (1 << 4) |
|
31 #define CB_A1 (1 << 5) |
|
32 #define CB_A2 (1 << 6) |
|
33 #define CB_START (1 << 7) |
|
34 |
|
35 #define X_AXIS_DMAX 3470 |
|
36 #define X_AXIS_MIN 290 |
|
37 #define Y_AXIS_DMAX 3450 |
|
38 #define Y_AXIS_MIN 200 |
|
39 |
|
40 #define ADS_VBAT 2000 |
|
41 #define ADS_VAUX 2000 |
|
42 #define ADS_TEMP0 2000 |
|
43 #define ADS_TEMP1 3000 |
|
44 #define ADS_XPOS(x, y) (X_AXIS_MIN + ((X_AXIS_DMAX * (x)) >> 15)) |
|
45 #define ADS_YPOS(x, y) (Y_AXIS_MIN + ((Y_AXIS_DMAX * (y)) >> 15)) |
|
46 #define ADS_Z1POS(x, y) 600 |
|
47 #define ADS_Z2POS(x, y) (600 + 6000 / ADS_XPOS(x, y)) |
|
48 |
|
49 static void ads7846_int_update(struct ads7846_state_s *s) |
|
50 { |
|
51 if (s->interrupt) |
|
52 qemu_set_irq(s->interrupt, s->pressure == 0); |
|
53 } |
|
54 |
|
55 uint32_t ads7846_read(void *opaque) |
|
56 { |
|
57 struct ads7846_state_s *s = (struct ads7846_state_s *) opaque; |
|
58 |
|
59 return s->output; |
|
60 } |
|
61 |
|
62 void ads7846_write(void *opaque, uint32_t value) |
|
63 { |
|
64 struct ads7846_state_s *s = (struct ads7846_state_s *) opaque; |
|
65 |
|
66 switch (s->cycle ++) { |
|
67 case 0: |
|
68 if (!(value & CB_START)) { |
|
69 s->cycle = 0; |
|
70 break; |
|
71 } |
|
72 |
|
73 s->output = s->input[(value >> 4) & 7]; |
|
74 |
|
75 /* Imitate the ADC noise, some drivers expect this. */ |
|
76 s->noise = (s->noise + 3) & 7; |
|
77 switch ((value >> 4) & 7) { |
|
78 case 1: s->output += s->noise ^ 2; break; |
|
79 case 3: s->output += s->noise ^ 0; break; |
|
80 case 4: s->output += s->noise ^ 7; break; |
|
81 case 5: s->output += s->noise ^ 5; break; |
|
82 } |
|
83 |
|
84 if (value & CB_MODE) |
|
85 s->output >>= 4; /* 8 bits instead of 12 */ |
|
86 |
|
87 break; |
|
88 case 1: |
|
89 s->cycle = 0; |
|
90 break; |
|
91 } |
|
92 } |
|
93 |
|
94 static void ads7846_ts_event(void *opaque, |
|
95 int x, int y, int z, int buttons_state) |
|
96 { |
|
97 struct ads7846_state_s *s = opaque; |
|
98 |
|
99 if (buttons_state) { |
|
100 x = 0x7fff - x; |
|
101 s->input[1] = ADS_XPOS(x, y); |
|
102 s->input[3] = ADS_Z1POS(x, y); |
|
103 s->input[4] = ADS_Z2POS(x, y); |
|
104 s->input[5] = ADS_YPOS(x, y); |
|
105 } |
|
106 |
|
107 if (s->pressure == !buttons_state) { |
|
108 s->pressure = !!buttons_state; |
|
109 |
|
110 ads7846_int_update(s); |
|
111 } |
|
112 } |
|
113 |
|
114 static void ads7846_save(QEMUFile *f, void *opaque) |
|
115 { |
|
116 struct ads7846_state_s *s = (struct ads7846_state_s *) opaque; |
|
117 int i; |
|
118 |
|
119 for (i = 0; i < 8; i ++) |
|
120 qemu_put_be32(f, s->input[i]); |
|
121 qemu_put_be32(f, s->noise); |
|
122 qemu_put_be32(f, s->cycle); |
|
123 qemu_put_be32(f, s->output); |
|
124 } |
|
125 |
|
126 static int ads7846_load(QEMUFile *f, void *opaque, int version_id) |
|
127 { |
|
128 struct ads7846_state_s *s = (struct ads7846_state_s *) opaque; |
|
129 int i; |
|
130 |
|
131 for (i = 0; i < 8; i ++) |
|
132 s->input[i] = qemu_get_be32(f); |
|
133 s->noise = qemu_get_be32(f); |
|
134 s->cycle = qemu_get_be32(f); |
|
135 s->output = qemu_get_be32(f); |
|
136 |
|
137 s->pressure = 0; |
|
138 ads7846_int_update(s); |
|
139 |
|
140 return 0; |
|
141 } |
|
142 |
|
143 struct ads7846_state_s *ads7846_init(qemu_irq penirq) |
|
144 { |
|
145 struct ads7846_state_s *s; |
|
146 s = (struct ads7846_state_s *) |
|
147 qemu_mallocz(sizeof(struct ads7846_state_s)); |
|
148 memset(s, 0, sizeof(struct ads7846_state_s)); |
|
149 |
|
150 s->interrupt = penirq; |
|
151 |
|
152 s->input[0] = ADS_TEMP0; /* TEMP0 */ |
|
153 s->input[2] = ADS_VBAT; /* VBAT */ |
|
154 s->input[6] = ADS_VAUX; /* VAUX */ |
|
155 s->input[7] = ADS_TEMP1; /* TEMP1 */ |
|
156 |
|
157 /* We want absolute coordinates */ |
|
158 qemu_add_mouse_event_handler(ads7846_ts_event, s, 1, |
|
159 "QEMU ADS7846-driven Touchscreen"); |
|
160 |
|
161 ads7846_int_update(s); |
|
162 |
|
163 register_savevm("ads7846", -1, 0, ads7846_save, ads7846_load, s); |
|
164 |
|
165 return s; |
|
166 } |