]>
Commit | Line | Data |
---|---|---|
1 | /* | |
2 | * TI ADS7846 / TSC2046 chip emulation. | |
3 | * | |
4 | * Copyright (c) 2006 Openedhand Ltd. | |
5 | * Written by Andrzej Zaborowski <[email protected]> | |
6 | * | |
7 | * This code is licensed under the GNU GPL v2. | |
8 | * | |
9 | * Contributions after 2012-01-13 are licensed under the terms of the | |
10 | * GNU GPL, version 2 or (at your option) any later version. | |
11 | */ | |
12 | ||
13 | #include "ssi.h" | |
14 | #include "console.h" | |
15 | ||
16 | typedef struct { | |
17 | SSISlave ssidev; | |
18 | qemu_irq interrupt; | |
19 | ||
20 | int input[8]; | |
21 | int pressure; | |
22 | int noise; | |
23 | ||
24 | int cycle; | |
25 | int output; | |
26 | } ADS7846State; | |
27 | ||
28 | /* Control-byte bitfields */ | |
29 | #define CB_PD0 (1 << 0) | |
30 | #define CB_PD1 (1 << 1) | |
31 | #define CB_SER (1 << 2) | |
32 | #define CB_MODE (1 << 3) | |
33 | #define CB_A0 (1 << 4) | |
34 | #define CB_A1 (1 << 5) | |
35 | #define CB_A2 (1 << 6) | |
36 | #define CB_START (1 << 7) | |
37 | ||
38 | #define X_AXIS_DMAX 3470 | |
39 | #define X_AXIS_MIN 290 | |
40 | #define Y_AXIS_DMAX 3450 | |
41 | #define Y_AXIS_MIN 200 | |
42 | ||
43 | #define ADS_VBAT 2000 | |
44 | #define ADS_VAUX 2000 | |
45 | #define ADS_TEMP0 2000 | |
46 | #define ADS_TEMP1 3000 | |
47 | #define ADS_XPOS(x, y) (X_AXIS_MIN + ((X_AXIS_DMAX * (x)) >> 15)) | |
48 | #define ADS_YPOS(x, y) (Y_AXIS_MIN + ((Y_AXIS_DMAX * (y)) >> 15)) | |
49 | #define ADS_Z1POS(x, y) 600 | |
50 | #define ADS_Z2POS(x, y) (600 + 6000 / ADS_XPOS(x, y)) | |
51 | ||
52 | static void ads7846_int_update(ADS7846State *s) | |
53 | { | |
54 | if (s->interrupt) | |
55 | qemu_set_irq(s->interrupt, s->pressure == 0); | |
56 | } | |
57 | ||
58 | static uint32_t ads7846_transfer(SSISlave *dev, uint32_t value) | |
59 | { | |
60 | ADS7846State *s = FROM_SSI_SLAVE(ADS7846State, dev); | |
61 | ||
62 | switch (s->cycle ++) { | |
63 | case 0: | |
64 | if (!(value & CB_START)) { | |
65 | s->cycle = 0; | |
66 | break; | |
67 | } | |
68 | ||
69 | s->output = s->input[(value >> 4) & 7]; | |
70 | ||
71 | /* Imitate the ADC noise, some drivers expect this. */ | |
72 | s->noise = (s->noise + 3) & 7; | |
73 | switch ((value >> 4) & 7) { | |
74 | case 1: s->output += s->noise ^ 2; break; | |
75 | case 3: s->output += s->noise ^ 0; break; | |
76 | case 4: s->output += s->noise ^ 7; break; | |
77 | case 5: s->output += s->noise ^ 5; break; | |
78 | } | |
79 | ||
80 | if (value & CB_MODE) | |
81 | s->output >>= 4; /* 8 bits instead of 12 */ | |
82 | ||
83 | break; | |
84 | case 1: | |
85 | s->cycle = 0; | |
86 | break; | |
87 | } | |
88 | return s->output; | |
89 | } | |
90 | ||
91 | static void ads7846_ts_event(void *opaque, | |
92 | int x, int y, int z, int buttons_state) | |
93 | { | |
94 | ADS7846State *s = opaque; | |
95 | ||
96 | if (buttons_state) { | |
97 | x = 0x7fff - x; | |
98 | s->input[1] = ADS_XPOS(x, y); | |
99 | s->input[3] = ADS_Z1POS(x, y); | |
100 | s->input[4] = ADS_Z2POS(x, y); | |
101 | s->input[5] = ADS_YPOS(x, y); | |
102 | } | |
103 | ||
104 | if (s->pressure == !buttons_state) { | |
105 | s->pressure = !!buttons_state; | |
106 | ||
107 | ads7846_int_update(s); | |
108 | } | |
109 | } | |
110 | ||
111 | static int ads7856_post_load(void *opaque, int version_id) | |
112 | { | |
113 | ADS7846State *s = opaque; | |
114 | ||
115 | s->pressure = 0; | |
116 | ads7846_int_update(s); | |
117 | return 0; | |
118 | } | |
119 | ||
120 | static const VMStateDescription vmstate_ads7846 = { | |
121 | .name = "ads7846", | |
122 | .version_id = 0, | |
123 | .minimum_version_id = 0, | |
124 | .minimum_version_id_old = 0, | |
125 | .post_load = ads7856_post_load, | |
126 | .fields = (VMStateField[]) { | |
127 | VMSTATE_INT32_ARRAY(input, ADS7846State, 8), | |
128 | VMSTATE_INT32(noise, ADS7846State), | |
129 | VMSTATE_INT32(cycle, ADS7846State), | |
130 | VMSTATE_INT32(output, ADS7846State), | |
131 | VMSTATE_END_OF_LIST() | |
132 | } | |
133 | }; | |
134 | ||
135 | static int ads7846_init(SSISlave *dev) | |
136 | { | |
137 | ADS7846State *s = FROM_SSI_SLAVE(ADS7846State, dev); | |
138 | ||
139 | qdev_init_gpio_out(&dev->qdev, &s->interrupt, 1); | |
140 | ||
141 | s->input[0] = ADS_TEMP0; /* TEMP0 */ | |
142 | s->input[2] = ADS_VBAT; /* VBAT */ | |
143 | s->input[6] = ADS_VAUX; /* VAUX */ | |
144 | s->input[7] = ADS_TEMP1; /* TEMP1 */ | |
145 | ||
146 | /* We want absolute coordinates */ | |
147 | qemu_add_mouse_event_handler(ads7846_ts_event, s, 1, | |
148 | "QEMU ADS7846-driven Touchscreen"); | |
149 | ||
150 | ads7846_int_update(s); | |
151 | ||
152 | vmstate_register(NULL, -1, &vmstate_ads7846, s); | |
153 | return 0; | |
154 | } | |
155 | ||
156 | static void ads7846_class_init(ObjectClass *klass, void *data) | |
157 | { | |
158 | SSISlaveClass *k = SSI_SLAVE_CLASS(klass); | |
159 | ||
160 | k->init = ads7846_init; | |
161 | k->transfer = ads7846_transfer; | |
162 | } | |
163 | ||
164 | static TypeInfo ads7846_info = { | |
165 | .name = "ads7846", | |
166 | .parent = TYPE_SSI_SLAVE, | |
167 | .instance_size = sizeof(ADS7846State), | |
168 | .class_init = ads7846_class_init, | |
169 | }; | |
170 | ||
171 | static void ads7846_register_types(void) | |
172 | { | |
173 | type_register_static(&ads7846_info); | |
174 | } | |
175 | ||
176 | type_init(ads7846_register_types) |