xref: /openbmc/qemu/hw/input/ads7846.c (revision efb91426)
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  * 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 "qemu/osdep.h"
14 #include "hw/irq.h"
15 #include "hw/ssi/ssi.h"
16 #include "migration/vmstate.h"
17 #include "qemu/module.h"
18 #include "ui/console.h"
19 #include "qom/object.h"
20 
21 struct ADS7846State {
22     SSIPeripheral ssidev;
23     qemu_irq interrupt;
24 
25     int input[8];
26     int pressure;
27     int noise;
28 
29     int cycle;
30     int output;
31 };
32 
33 #define TYPE_ADS7846 "ads7846"
34 OBJECT_DECLARE_SIMPLE_TYPE(ADS7846State, ADS7846)
35 
36 /* Control-byte bitfields */
37 #define CB_PD0          (1 << 0)
38 #define CB_PD1          (1 << 1)
39 #define CB_SER          (1 << 2)
40 #define CB_MODE         (1 << 3)
41 #define CB_A0           (1 << 4)
42 #define CB_A1           (1 << 5)
43 #define CB_A2           (1 << 6)
44 #define CB_START        (1 << 7)
45 
46 #define X_AXIS_DMAX     3470
47 #define X_AXIS_MIN      290
48 #define Y_AXIS_DMAX     3450
49 #define Y_AXIS_MIN      200
50 
51 #define ADS_VBAT        2000
52 #define ADS_VAUX        2000
53 #define ADS_TEMP0       2000
54 #define ADS_TEMP1       3000
55 #define ADS_XPOS(x, y)  (X_AXIS_MIN + ((X_AXIS_DMAX * (x)) >> 15))
56 #define ADS_YPOS(x, y)  (Y_AXIS_MIN + ((Y_AXIS_DMAX * (y)) >> 15))
57 #define ADS_Z1POS(x, y) 600
58 #define ADS_Z2POS(x, y) (600 + 6000 / ADS_XPOS(x, y))
59 
60 static void ads7846_int_update(ADS7846State *s)
61 {
62     if (s->interrupt)
63         qemu_set_irq(s->interrupt, s->pressure == 0);
64 }
65 
66 static uint32_t ads7846_transfer(SSIPeripheral *dev, uint32_t value)
67 {
68     ADS7846State *s = ADS7846(dev);
69 
70     switch (s->cycle ++) {
71     case 0:
72         if (!(value & CB_START)) {
73             s->cycle = 0;
74             break;
75         }
76 
77         s->output = s->input[(value >> 4) & 7];
78 
79         /* Imitate the ADC noise, some drivers expect this.  */
80         s->noise = (s->noise + 3) & 7;
81         switch ((value >> 4) & 7) {
82         case 1: s->output += s->noise ^ 2; break;
83         case 3: s->output += s->noise ^ 0; break;
84         case 4: s->output += s->noise ^ 7; break;
85         case 5: s->output += s->noise ^ 5; break;
86         }
87 
88         if (value & CB_MODE)
89             s->output >>= 4;    /* 8 bits instead of 12 */
90 
91         break;
92     case 1:
93         s->cycle = 0;
94         break;
95     }
96     return s->output;
97 }
98 
99 static void ads7846_ts_event(void *opaque,
100                 int x, int y, int z, int buttons_state)
101 {
102     ADS7846State *s = opaque;
103 
104     if (buttons_state) {
105         x = 0x7fff - x;
106         s->input[1] = ADS_XPOS(x, y);
107         s->input[3] = ADS_Z1POS(x, y);
108         s->input[4] = ADS_Z2POS(x, y);
109         s->input[5] = ADS_YPOS(x, y);
110     }
111 
112     if (s->pressure == !buttons_state) {
113         s->pressure = !!buttons_state;
114 
115         ads7846_int_update(s);
116     }
117 }
118 
119 static int ads7856_post_load(void *opaque, int version_id)
120 {
121     ADS7846State *s = opaque;
122 
123     s->pressure = 0;
124     ads7846_int_update(s);
125     return 0;
126 }
127 
128 static const VMStateDescription vmstate_ads7846 = {
129     .name = "ads7846",
130     .version_id = 1,
131     .minimum_version_id = 1,
132     .post_load = ads7856_post_load,
133     .fields = (const VMStateField[]) {
134         VMSTATE_SSI_PERIPHERAL(ssidev, ADS7846State),
135         VMSTATE_INT32_ARRAY(input, ADS7846State, 8),
136         VMSTATE_INT32(noise, ADS7846State),
137         VMSTATE_INT32(cycle, ADS7846State),
138         VMSTATE_INT32(output, ADS7846State),
139         VMSTATE_END_OF_LIST()
140     }
141 };
142 
143 static void ads7846_realize(SSIPeripheral *d, Error **errp)
144 {
145     DeviceState *dev = DEVICE(d);
146     ADS7846State *s = ADS7846(d);
147 
148     qdev_init_gpio_out(dev, &s->interrupt, 1);
149 
150     s->input[0] = ADS_TEMP0;    /* TEMP0 */
151     s->input[2] = ADS_VBAT;     /* VBAT */
152     s->input[6] = ADS_VAUX;     /* VAUX */
153     s->input[7] = ADS_TEMP1;    /* TEMP1 */
154 
155     /* We want absolute coordinates */
156     qemu_add_mouse_event_handler(ads7846_ts_event, s, 1,
157                     "QEMU ADS7846-driven Touchscreen");
158 
159     ads7846_int_update(s);
160 
161     vmstate_register_any(NULL, &vmstate_ads7846, s);
162 }
163 
164 static void ads7846_class_init(ObjectClass *klass, void *data)
165 {
166     DeviceClass *dc = DEVICE_CLASS(klass);
167     SSIPeripheralClass *k = SSI_PERIPHERAL_CLASS(klass);
168 
169     k->realize = ads7846_realize;
170     k->transfer = ads7846_transfer;
171     set_bit(DEVICE_CATEGORY_INPUT, dc->categories);
172 }
173 
174 static const TypeInfo ads7846_info = {
175     .name          = TYPE_ADS7846,
176     .parent        = TYPE_SSI_PERIPHERAL,
177     .instance_size = sizeof(ADS7846State),
178     .class_init    = ads7846_class_init,
179 };
180 
181 static void ads7846_register_types(void)
182 {
183     type_register_static(&ads7846_info);
184 }
185 
186 type_init(ads7846_register_types)
187