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