altos: Execute self-test on MMA655X part
[fw/altos] / src / drivers / ao_mma655x.c
1 /*
2  * Copyright © 2012 Keith Packard <keithp@keithp.com>
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation; version 2 of the License.
7  *
8  * This program is distributed in the hope that it will be useful, but
9  * WITHOUT ANY WARRANTY; without even the implied warranty of
10  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
11  * General Public License for more details.
12  *
13  * You should have received a copy of the GNU General Public License along
14  * with this program; if not, write to the Free Software Foundation, Inc.,
15  * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
16  */
17
18 #include <ao.h>
19 #include <ao_mma655x.h>
20
21 #if HAS_MMA655X
22
23 #define DEBUG           0
24 #define DEBUG_LOW       1
25 #define DEBUG_HIGH      2
26 #if 1
27 #define PRINTD(l, ...) do { if (DEBUG & (l)) { printf ("\r%5u %s: ", ao_tick_count, __func__); printf(__VA_ARGS__); flush(); } } while(0)
28 #else
29 #define PRINTD(l,...) 
30 #endif
31
32 uint8_t ao_mma655x_spi_index = AO_MMA655X_SPI_INDEX;
33
34 static void
35 ao_mma655x_start(void) {
36         ao_spi_get_bit(AO_MMA655X_CS_PORT,
37                        AO_MMA655X_CS_PIN,
38                        AO_MMA655X_CS,
39                        AO_MMA655X_SPI_INDEX,
40                        AO_SPI_SPEED_FAST);
41 }
42
43 static void
44 ao_mma655x_stop(void) {
45         ao_spi_put_bit(AO_MMA655X_CS_PORT,
46                        AO_MMA655X_CS_PIN,
47                        AO_MMA655X_CS,
48                        AO_MMA655X_SPI_INDEX);
49 }
50
51 static void
52 ao_mma655x_restart(void) {
53         uint8_t i;
54         ao_gpio_set(AO_MMA655X_CS_PORT, AO_MMA655X_CS_PIN, AO_MMA655X_CS, 1);
55
56         /* Emperical testing on STM32L151 at 32MHz for this delay amount */
57         for (i = 0; i < 10; i++)
58                 ao_arch_nop();
59         ao_gpio_set(AO_MMA655X_CS_PORT, AO_MMA655X_CS_PIN, AO_MMA655X_CS, 0);
60 }
61
62 static uint8_t
63 ao_parity(uint8_t v)
64 {
65         uint8_t p;
66         /* down to four bits */
67         p = (v ^ (v >> 4)) & 0xf;
68
69         /* Cute lookup hack -- 0x6996 encodes the sixteen
70          * even parity values in order.
71          */
72         p = (~0x6996 >> p) & 1;
73         return p;
74 }
75
76 #if 0
77 static void
78 ao_mma655x_cmd(uint8_t d[2])
79 {
80         ao_mma655x_start();
81         PRINTD(DEBUG_LOW, "\tSEND %02x %02x\n", d[0], d[1]);
82         ao_spi_duplex(d, d, 2, AO_MMA655X_SPI_INDEX);
83         PRINTD(DEBUG_LOW, "\t\tRECV %02x %02x\n", d[0], d[1]);
84         ao_mma655x_stop();
85 }
86 #endif
87
88 static uint8_t
89 ao_mma655x_reg_read(uint8_t addr)
90 {
91         uint8_t d[2];
92         ao_mma655x_start();
93         d[0] = addr | (ao_parity(addr) << 7);
94         d[1] = 0;
95         ao_spi_send(&d, 2, AO_MMA655X_SPI_INDEX);
96         ao_mma655x_restart();
97
98         /* Send a dummy read of 00 to clock out the SPI data */
99         d[0] = 0x80;
100         d[1] = 0x00;
101         ao_spi_duplex(&d, &d, 2, AO_MMA655X_SPI_INDEX);
102         ao_mma655x_stop();
103         PRINTD(DEBUG_LOW, "read %x = %x %x\n", addr, d[0], d[1]);
104         return d[1];
105 }
106
107 static void
108 ao_mma655x_reg_write(uint8_t addr, uint8_t value)
109 {
110         uint8_t d[2];
111
112         PRINTD(DEBUG_LOW, "write %x %x\n", addr, value);
113         addr |= (1 << 6);       /* write mode */
114         d[0] = addr | (ao_parity(addr^value) << 7);
115         d[1] = value;
116         ao_mma655x_start();
117         ao_spi_send(d, 2, AO_MMA655X_SPI_INDEX);
118         ao_mma655x_stop();
119
120         addr &= ~(1 << 6);
121 }
122
123 static uint16_t
124 ao_mma655x_value(void)
125 {
126         uint8_t         d[2];
127         uint16_t        v;
128
129         d[0] = ((0 << 6) |      /* Axis selection (X) */
130                 (1 << 5) |      /* Acceleration operation */
131                 (1 << 4));      /* Raw data */
132         d[1] = ((1 << 3) |      /* must be one */
133                 (1 << 2) |      /* Unsigned data */
134                 (0 << 1) |      /* Arm disabled */
135                 (1 << 0));      /* Odd parity */
136         ao_mma655x_start();
137         PRINTD(DEBUG_LOW, "value SEND %02x %02x\n", d[0], d[1]);
138         ao_spi_send(d, 2, AO_MMA655X_SPI_INDEX);
139         ao_mma655x_restart();
140         d[0] = 0x80;
141         d[1] = 0x00;
142         ao_spi_duplex(d, d, 2, AO_MMA655X_SPI_INDEX);
143         ao_mma655x_stop();
144         PRINTD(DEBUG_LOW, "value RECV %02x %02x\n", d[0], d[1]);
145
146         v = (uint16_t) d[1] << 2;
147         v |= d[0] >> 6;
148         v |= (uint16_t) (d[0] & 3) << 10;
149         return v;
150 }
151
152 static void
153 ao_mma655x_reset(void) {
154         PRINTD(DEBUG_HIGH, "reset\n");
155         ao_mma655x_reg_write(AO_MMA655X_DEVCTL,
156                              (0 << AO_MMA655X_DEVCTL_RES_1) |
157                              (0 << AO_MMA655X_DEVCTL_RES_0));
158         ao_mma655x_reg_write(AO_MMA655X_DEVCTL,
159                              (1 << AO_MMA655X_DEVCTL_RES_1) |
160                              (1 << AO_MMA655X_DEVCTL_RES_0));
161         ao_mma655x_reg_write(AO_MMA655X_DEVCTL,
162                              (0 << AO_MMA655X_DEVCTL_RES_1) |
163                              (1 << AO_MMA655X_DEVCTL_RES_0));
164 }
165
166 #define DEVCFG_VALUE    (\
167         (1 << AO_MMA655X_DEVCFG_OC) |           /* Disable offset cancelation */ \
168         (1 << AO_MMA655X_DEVCFG_SD) |           /* Receive unsigned data */ \
169         (0 << AO_MMA655X_DEVCFG_OFMON) |        /* Disable offset monitor */ \
170         (AO_MMA655X_DEVCFG_A_CFG_DISABLE << AO_MMA655X_DEVCFG_A_CFG))
171
172 #define AXISCFG_VALUE   (\
173                 (0 << AO_MMA655X_AXISCFG_LPF))  /* 100Hz 4-pole filter */
174
175
176 #define AO_ST_TRIES     10
177 #define AO_ST_DELAY     AO_MS_TO_TICKS(100)
178
179 static void
180 ao_mma655x_setup(void)
181 {
182         uint16_t        a, a_st;
183         int16_t         st_change;
184         int             tries;
185         uint8_t         devstat;
186 #if 0
187         uint8_t s0, s1, s2, s3;
188         uint32_t        lot;
189 #endif
190
191         for (tries = 0; tries < AO_ST_TRIES; tries++) {
192                 ao_delay(AO_MS_TO_TICKS(10));
193                 ao_mma655x_reset();
194                 ao_delay(AO_MS_TO_TICKS(10));
195
196                 devstat = ao_mma655x_reg_read(AO_MMA655X_DEVSTAT);
197                 PRINTD(DEBUG_HIGH, "devstat %x\n", devstat);
198
199                 if (!(devstat & (1 << AO_MMA655X_DEVSTAT_DEVRES)))
200                         continue;
201
202                 /* Configure R/W register values.
203                  * Most of them relate to the arming feature, which
204                  * we don't use, so the only registers we need to
205                  * write are DEVCFG and AXISCFG
206                  */
207
208                 ao_mma655x_reg_write(AO_MMA655X_DEVCFG,
209                                      DEVCFG_VALUE | (0 << AO_MMA655X_DEVCFG_ENDINIT));
210
211                 /* Test X axis
212                  */
213         
214                 ao_mma655x_reg_write(AO_MMA655X_AXISCFG,
215                                      AXISCFG_VALUE |
216                                      (1 << AO_MMA655X_AXISCFG_ST));
217                 ao_delay(AO_MS_TO_TICKS(10));
218
219                 a_st = ao_mma655x_value();
220
221                 ao_mma655x_reg_write(AO_MMA655X_AXISCFG,
222                                      AXISCFG_VALUE |
223                                      (0 << AO_MMA655X_AXISCFG_ST));
224
225                 ao_delay(AO_MS_TO_TICKS(10));
226
227                 a = ao_mma655x_value();
228
229                 st_change = a_st - a;
230
231                 PRINTD(DEBUG_HIGH, "self test %d normal %d change %d\n", a_st, a, st_change);
232
233                 if (AO_ST_MIN <= st_change && st_change <= AO_ST_MAX)
234                         break;
235                 ao_delay(AO_ST_DELAY);
236         }
237         if (tries == AO_ST_TRIES)
238                 ao_sensor_errors = 1;
239
240         ao_mma655x_reg_write(AO_MMA655X_DEVCFG,
241                              DEVCFG_VALUE | (1 << AO_MMA655X_DEVCFG_ENDINIT));
242 #if 0
243         s0 = ao_mma655x_reg_read(AO_MMA655X_SN0);
244         s1 = ao_mma655x_reg_read(AO_MMA655X_SN1);
245         s2 = ao_mma655x_reg_read(AO_MMA655X_SN2);
246         s3 = ao_mma655x_reg_read(AO_MMA655X_SN3);
247         lot = ((uint32_t) s3 << 24) | ((uint32_t) s2 << 16) |
248                 ((uint32_t) s1 << 8) | ((uint32_t) s0);
249         serial = lot & 0x1fff;
250         lot >>= 12;
251         pn = ao_mma655x_reg_read(AO_MMA655X_PN);
252 #endif
253 }
254
255 uint16_t        ao_mma655x_current;
256
257 static void
258 ao_mma655x_dump(void)
259 {
260         printf ("MMA655X value %d\n", ao_mma655x_current);
261 }
262
263 __code struct ao_cmds ao_mma655x_cmds[] = {
264         { ao_mma655x_dump,      "A\0Display MMA655X data" },
265         { 0, NULL },
266 };
267
268 static void
269 ao_mma655x(void)
270 {
271         ao_mma655x_setup();
272         for (;;) {
273                 ao_mma655x_current = ao_mma655x_value();
274                 ao_arch_critical(
275                         AO_DATA_PRESENT(AO_DATA_MMA655X);
276                         AO_DATA_WAIT();
277                         );
278         }
279 }
280
281 static __xdata struct ao_task ao_mma655x_task;
282
283 void
284 ao_mma655x_init(void)
285 {
286         ao_cmd_register(&ao_mma655x_cmds[0]);
287         ao_spi_init_cs(AO_MMA655X_CS_PORT, (1 << AO_MMA655X_CS_PIN));
288
289         ao_add_task(&ao_mma655x_task, ao_mma655x, "mma655x");
290 }
291
292 #endif