altos: Add MMA655X driver
[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 static uint8_t  mma655x_configured;
22 uint8_t         ao_mma655x_valid;
23
24 static void
25 ao_mma655x_start(void) {
26         ao_spi_get_bit(AO_MMA655X_CS_GPIO,
27                        AO_MMA655X_CS,
28                        AO_MMA655X_CS_PIN,
29                        AO_MMA655X_SPI_INDEX,
30                        AO_SPI_SPEED_FAST);
31 }
32
33 static void
34 ao_mma655x_stop(void) {
35         ao_spi_put_bit(AO_MMA655X_CS_GPIO,
36                        AO_MMA655X_CS,
37                        AO_MMA655X_CS_PIN,
38                        AO_MMA655X_SPI_INDEX);
39 }
40
41 static uint8_t
42 ao_parity(uint8_t v)
43 {
44         /* down to four bits */
45         v = (v ^ (v >> 4)) & 0xf;
46
47         /* Cute lookup hack -- 0x6996 encodes the sixteen
48          * even parity values in order.
49          */
50         return (~0x6996 >> v) & 1;
51 }
52
53 static void
54 ao_mma655x_cmd(uint8_t d[2])
55 {
56         ao_mma655x_start();
57         ao_spi_send(d, 2, AO_MMA655X_SPI_INDEX);
58         ao_spi_recv(d, 2, AO_MMA655X_SPI_INDEX);
59         ao_mma655x_stop();
60 }
61
62 static uint8_t
63 ao_mma655x_reg_write(uint8_t addr, uint8_t value)
64 {
65         uint8_t d[2];
66
67         addr |= (1 << 6);       /* write mode */
68         d[0] = addr | (ao_parity(addr^value) << 7);
69         d[1] = value;
70         ao_mma655x_cmd(d);
71         return d[1];
72 }
73
74 static uint8_t
75 ao_mma655x_reg_read(uint8_t addr)
76 {
77         uint8_t d[2];
78
79         d[0] = addr | (ao_parity(addr) << 7);
80         d[1] = 0;
81         ao_mma655x_cmd(d);
82         return d[1];
83 }
84
85 static uint16_t
86 ao_mma655x_value(void)
87 {
88         uint8_t         d[2];
89         uint16_t        v;
90
91         d[0] = ((0 << 7) |      /* Axis selection (X) */
92                 (1 << 6) |      /* Acceleration operation */
93                 (1 << 5));      /* Raw data */
94         d[1] = ((1 << 3) |      /* must be one */
95                 (1 << 2) |      /* Unsigned data */
96                 (0 << 1) |      /* Arm disabled */
97                 (1 << 0));      /* Odd parity */
98         ao_mma655x_cmd(d);
99         v = (uint16_t) d[1] << 2;
100         v |= d[0] >> 6;
101         v |= (uint16_t) (d[0] & 3) << 10;
102         return v;
103 }
104
105 static void
106 ao_mma655x_reset(void) {
107         ao_mma655x_reg_write(AO_MMA655X_DEVCTL,
108                              (0 << AO_MMA655X_DEVCTL_RES_1) |
109                              (0 << AO_MMA655X_DEVCTL_RES_1));
110         ao_mma655x_reg_write(AO_MMA655X_DEVCTL,
111                              (1 << AO_MMA655X_DEVCTL_RES_1) |
112                              (1 << AO_MMA655X_DEVCTL_RES_1));
113         ao_mma655x_reg_write(AO_MMA655X_DEVCTL,
114                              (0 << AO_MMA655X_DEVCTL_RES_1) |
115                              (1 << AO_MMA655X_DEVCTL_RES_1));
116 }
117
118 #define DEVCFG_VALUE    (\
119         (1 << AO_MMA655X_DEVCFG_OC) |           /* Disable offset cancelation */ \
120         (1 << AO_MMA655X_DEVCFG_SD) |           /* Receive unsigned data */ \
121         (0 << AO_MMA655X_DEVCFG_OFMON) |        /* Disable offset monitor */ \
122         (AO_MMA655X_DEVCFG_A_CFG_DISABLE << AO_MMA655X_DEVCFG_A_CFG))
123
124 #define AXISCFG_VALUE   (\
125                 (0 << AO_MMA655X_AXISCFG_LPF))  /* 100Hz 4-pole filter */
126
127
128 static void
129 ao_mma655x_setup(void)
130 {
131         uint8_t         v;
132         uint16_t        a, a_st;
133         uint8_t         stdefl;
134
135         if (mma655x_configured)
136                 return;
137         mma655x_configured = 1;
138         ao_delay(AO_MS_TO_TICKS(10));   /* Top */
139         ao_mma655x_reset();
140         ao_delay(AO_MS_TO_TICKS(10));   /* Top */
141         (void) ao_mma655x_reg_read(AO_MMA655X_DEVSTAT);
142         v = ao_mma655x_reg_read(AO_MMA655X_DEVSTAT);
143
144         /* Configure R/W register values.
145          * Most of them relate to the arming feature, which
146          * we don't use, so the only registers we need to
147          * write are DEVCFG and AXISCFG
148          */
149
150         ao_mma655x_reg_write(AO_MMA655X_DEVCFG,
151                              DEVCFG_VALUE | (0 << AO_MMA655X_DEVCFG_ENDINIT));
152
153         /* Test X axis
154          */
155         
156         ao_mma655x_reg_write(AO_MMA655X_AXISCFG,
157                              AXISCFG_VALUE |
158                              (1 << AO_MMA655X_AXISCFG_ST));
159         a_st = ao_mma655x_value();
160
161         stdefl = ao_mma655x_reg_read(AO_MMA655X_STDEFL);
162
163         ao_mma655x_reg_write(AO_MMA655X_AXISCFG,
164                              AXISCFG_VALUE |
165                              (0 << AO_MMA655X_AXISCFG_ST));
166         a = ao_mma655x_value();
167         printf ("normal: %u self_test: %u stdefl: %u\n",
168                 a, a_st, stdefl);
169
170         ao_mma655x_reg_write(AO_MMA655X_DEVCFG,
171                              DEVCFG_VALUE | (1 << AO_MMA655X_DEVCFG_ENDINIT));
172 }
173
174 static void
175 ao_mma655x_dump(void)
176 {
177         uint8_t s0, s1, s2, s3;
178         uint32_t        lot;
179         uint16_t        serial;
180
181         ao_mma655x_setup();
182
183         s0 = ao_mma655x_reg_read(AO_MMA655X_SN0);
184         s1 = ao_mma655x_reg_read(AO_MMA655X_SN1);
185         s2 = ao_mma655x_reg_read(AO_MMA655X_SN2);
186         s3 = ao_mma655x_reg_read(AO_MMA655X_SN3);
187         lot = ((uint32_t) s3 << 24) | ((uint32_t) s2 << 16) |
188                 ((uint32_t) s1 << 8) | ((uint32_t) s0);
189         serial = lot & 0x1fff;
190         lot >>= 12;
191         printf ("MMA655X lot %d serial %d\n", lot, serial);
192         mma655x_configured = 0;
193 }
194
195 __code struct ao_cmds ao_mma655x_cmds[] = {
196         { ao_mma655x_dump,      "A\0Display MMA655X data" },
197         { 0, NULL },
198 };
199
200 void
201 ao_mma655x_init(void)
202 {
203         mma655x_configured = 0;
204         ao_mma655x_valid = 0;
205
206         ao_cmd_register(&ao_mma655x_cmds[0]);
207         ao_spi_init_cs(AO_MMA655X_CS_GPIO, (1 << AO_MMA655X_CS));
208
209 //      ao_add_task(&ao_mma655x_task, ao_mma655x, "mma655x");
210 }