Texas Instruments ADS7846 ADC chip.
[qemu] / hw / ads7846.c
1 /*
2  * TI ADS7846 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 <vl.h>
11
12 struct ads7846_state_s {
13     qemu_irq interrupt;
14
15     int input[8];
16     int pressure;
17     int noise;
18
19     int cycle;
20     int output;
21 };
22
23 /* Control-byte bitfields */
24 #define CB_PD0          (1 << 0)
25 #define CB_PD1          (1 << 1)
26 #define CB_SER          (1 << 2)
27 #define CB_MODE         (1 << 3)
28 #define CB_A0           (1 << 4)
29 #define CB_A1           (1 << 5)
30 #define CB_A2           (1 << 6)
31 #define CB_START        (1 << 7)
32
33 #define X_AXIS_DMAX     3680
34 #define X_AXIS_MIN      150
35 #define Y_AXIS_DMAX     3640
36 #define Y_AXIS_MIN      190
37
38 #define ADS_VBAT        2000
39 #define ADS_VAUX        2000
40 #define ADS_TEMP0       2000
41 #define ADS_TEMP1       3000
42 #define ADS_XPOS(x, y)  (X_AXIS_MIN + ((X_AXIS_DMAX * (x)) >> 15))
43 #define ADS_YPOS(x, y)  (Y_AXIS_MIN + ((Y_AXIS_DMAX * (y)) >> 15))
44 #define ADS_Z1POS(x, y) 600
45 #define ADS_Z2POS(x, y) (600 + 6000 / ADS_XPOS(x, y))
46
47 static void ads7846_int_update(struct ads7846_state_s *s)
48 {
49     if (s->interrupt)
50         qemu_set_irq(s->interrupt, s->pressure == 0);
51 }
52
53 uint32_t ads7846_read(void *opaque)
54 {
55     struct ads7846_state_s *s = (struct ads7846_state_s *) opaque;
56
57     return s->output;
58 }
59
60 void ads7846_write(void *opaque, uint32_t value)
61 {
62     struct ads7846_state_s *s = (struct ads7846_state_s *) opaque;
63
64     switch (s->cycle ++) {
65     case 0:
66         if (!(value & CB_START)) {
67             s->cycle = 0;
68             break;
69         }
70
71         s->output = s->input[(value >> 4) & 7];
72
73         /* Imitate the ADC noise, some drivers expect this.  */
74         s->noise = (s->noise + 3) & 7;
75         switch ((value >> 4) & 7) {
76         case 1: s->output += s->noise ^ 2; break;
77         case 3: s->output += s->noise ^ 0; break;
78         case 4: s->output += s->noise ^ 7; break;
79         case 5: s->output += s->noise ^ 5; break;
80         }
81
82         if (value & CB_MODE)
83             s->output >>= 4;    /* 8 bits instead of 12 */
84
85         break;
86     case 1:
87         s->cycle = 0;
88         break;
89     }
90 }
91
92 static void ads7846_ts_event(void *opaque,
93                 int x, int y, int z, int buttons_state)
94 {
95     struct ads7846_state_s *s = opaque;
96
97     if (buttons_state) {
98         s->input[1] = ADS_YPOS(x, y);
99         s->input[3] = ADS_Z1POS(x, y);
100         s->input[4] = ADS_Z2POS(x, y);
101         s->input[5] = ADS_XPOS(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 struct ads7846_state_s *ads7846_init(qemu_irq penirq)
112 {
113     struct ads7846_state_s *s;
114     s = (struct ads7846_state_s *)
115             qemu_mallocz(sizeof(struct ads7846_state_s));
116     memset(s, 0, sizeof(struct ads7846_state_s));
117
118     s->interrupt = penirq;
119
120     s->input[0] = ADS_TEMP0;    /* TEMP0 */
121     s->input[2] = ADS_VBAT;     /* VBAT */
122     s->input[6] = ADS_VAUX;     /* VAUX */
123     s->input[7] = ADS_TEMP1;    /* TEMP1 */
124
125     /* We want absolute coordinates */
126     qemu_add_mouse_event_handler(ads7846_ts_event, s, 1,
127                     "QEMU ADS7846-driven Touchscreen");
128
129     ads7846_int_update(s);
130     return s;
131 }