Make the TWI driver more generic to work with devices other than EEPROMS.
[bertos.git] / drv / eeprom.c
1 /*!
2  * \file
3  * <!--
4  * Copyright 2003, 2004, 2005 Develer S.r.l. (http://www.develer.com/)
5  * This file is part of DevLib - See README.devlib for information.
6  * -->
7  *
8  * \brief Driver for the 24xx16 and 24xx256 I2C EEPROMS (implementation)
9  *
10  * \note This implementation is AVR specific.
11  *
12  * \version $Id$
13  * \author Stefano Fedrigo <aleph@develer.com>
14  * \author Bernardo Innocenti <bernie@develer.com>
15  */
16
17 /*#*
18  *#* $Log$
19  *#* Revision 1.19  2006/03/20 17:49:50  bernie
20  *#* Make the TWI driver more generic to work with devices other than EEPROMS.
21  *#*
22  *#* Revision 1.18  2005/11/27 23:33:40  bernie
23  *#* Use appconfig.h instead of cfg/config.h.
24  *#*
25  *#* Revision 1.17  2005/04/11 19:10:27  bernie
26  *#* Include top-level headers from cfg/ subdir.
27  *#*
28  *#* Revision 1.16  2005/03/01 23:25:09  bernie
29  *#* Prune CVS log.
30  *#*
31  *#* Revision 1.11  2004/10/26 08:35:31  bernie
32  *#* Reset watchdog for long operations.
33  *#*
34  *#* Revision 1.10  2004/09/20 03:31:22  bernie
35  *#* Sanitize for C++.
36  *#*
37  *#* Revision 1.9  2004/09/14 21:03:46  bernie
38  *#* Use debug.h instead of kdebug.h.
39  *#*/
40
41 #include "eeprom.h"
42
43 #include <cfg/debug.h>
44 #include <appconfig.h>  // CONFIG_EEPROM_VERIFY
45 #include <cfg/macros.h>  // MIN()
46 #include <drv/twi.h>
47 #include <drv/wdt.h>
48 #include <mware/byteorder.h> // cpu_to_be16()
49
50 #include <string.h>  // memset()
51
52
53 // Configuration sanity checks
54 #if !defined(CONFIG_EEPROM_VERIFY) || (CONFIG_EEPROM_VERIFY != 0 && CONFIG_EEPROM_VERIFY != 1)
55         #error CONFIG_EEPROM_VERIFY must be defined to either 0 or 1
56 #endif
57
58 /**
59  * EEPROM ID code
60  */
61 #define EEPROM_ID  0xA0
62
63 /**
64  * This macros form the correct slave address for EEPROMs
65  */
66 #define EEPROM_ADDR(x) (EEPROM_ID | (((uint8_t)(x)) << 1))
67
68
69
70
71 /*!
72  * Copy \c count bytes from buffer \c buf to
73  * eeprom at address \c addr.
74  */
75 static bool eeprom_writeRaw(e2addr_t addr, const void *buf, size_t count)
76 {
77         bool result = true;
78         ASSERT(addr + count <= EEPROM_SIZE);
79
80         while (count && result)
81         {
82                 /*
83                  * Split write in multiple sequential mode operations that
84                  * don't cross page boundaries.
85                  */
86                 size_t size =
87                         MIN(count, (size_t)(EEPROM_BLKSIZE - (addr & (EEPROM_BLKSIZE - 1))));
88
89         #if CONFIG_EEPROM_TYPE == EEPROM_24XX16
90                 /*
91                  * The 24LC16 uses the slave address as a 3-bit
92                  * block address.
93                  */
94                 uint8_t blk_addr = (uint8_t)((addr >> 8) & 0x07);
95                 uint8_t blk_offs = (uint8_t)addr;
96
97                 result =
98                         twi_start_w(EEPROM_ADDR(blk_addr))
99                         && twi_send(&blk_offs, sizeof blk_offs)
100                         && twi_send(buf, size);
101
102         #elif CONFIG_EEPROM_TYPE == EEPROM_24XX256
103
104                 // 24LC256 wants big-endian addresses
105                 uint16_t addr_be = cpu_to_be16(addr);
106
107                 result =
108                         twi_start_w(EEPROM_ID)
109                         && twi_send((uint8_t *)&addr_be, sizeof addr_be)
110                         && twi_send(buf, size);
111
112         #else
113                 #error Unknown device type
114         #endif
115
116                 twi_stop();
117
118                 // DEBUG
119                 //kprintf("addr=%d, count=%d, size=%d, *#?=%d\n",
120                 //      addr, count, size,
121                 //      (EEPROM_BLKSIZE - (addr & (EEPROM_BLKSIZE - 1)))
122                 //);
123
124                 /* Update count and addr for next operation */
125                 count -= size;
126                 addr += size;
127                 buf = ((const char *)buf) + size;
128         }
129
130         if (!result)
131                 TRACEMSG("Write error!");
132         return result;
133 }
134
135
136 #if CONFIG_EEPROM_VERIFY
137 /*!
138  * Check that the contents of an EEPROM range
139  * match with a provided data buffer.
140  *
141  * \return true on success.
142  */
143 static bool eeprom_verify(e2addr_t addr, const void *buf, size_t count)
144 {
145         uint8_t verify_buf[16];
146         bool result = true;
147
148         while (count && result)
149         {
150                 /* Split read in smaller pieces */
151                 size_t size = MIN(count, sizeof verify_buf);
152
153                 /* Read back buffer */
154                 if (eeprom_read(addr, verify_buf, size))
155                 {
156                         if (memcmp(buf, verify_buf, size) != 0)
157                         {
158                                 TRACEMSG("Data mismatch!");
159                                 result = false;
160                         }
161                 }
162                 else
163                 {
164                         TRACEMSG("Read error!");
165                         result = false;
166                 }
167
168                 /* Update count and addr for next operation */
169                 count -= size;
170                 addr += size;
171                 buf = ((const char *)buf) + size;
172         }
173
174         return result;
175 }
176 #endif /* CONFIG_EEPROM_VERIFY */
177
178
179 bool eeprom_write(e2addr_t addr, const void *buf, size_t count)
180 {
181 #if CONFIG_EEPROM_VERIFY
182         int retries = 5;
183
184         while (retries--)
185                 if (eeprom_writeRaw(addr, buf, count)
186                                 && eeprom_verify(addr, buf, count))
187                         return true;
188
189         return false;
190
191 #else /* !CONFIG_EEPROM_VERIFY */
192         return eeprom_writeRaw(addr, buf, count);
193 #endif /* !CONFIG_EEPROM_VERIFY */
194 }
195
196
197 /*!
198  * Copy \c count bytes at address \c addr
199  * from eeprom to RAM to buffer \c buf.
200  *
201  * \return true on success.
202  */
203 bool eeprom_read(e2addr_t addr, void *buf, size_t count)
204 {
205         ASSERT(addr + count <= EEPROM_SIZE);
206
207 #if CONFIG_EEPROM_TYPE == EEPROM_24XX16
208         /*
209          * The 24LC16 uses the slave address as a 3-bit
210          * block address.
211          */
212         uint8_t blk_addr = (uint8_t)((addr >> 8) & 0x07);
213         uint8_t blk_offs = (uint8_t)addr;
214
215         bool res =
216                 twi_start_w(EEPROM_ADDR(blk_addr))
217                 && twi_send(&blk_offs, sizeof blk_offs)
218                 && twi_start_r(EEPROM_ADDR(blk_addr))
219                 && twi_recv(buf, count);
220
221 #elif CONFIG_EEPROM_TYPE == EEPROM_24XX256
222
223         // 24LC256 wants big-endian addresses
224         addr = cpu_to_be16(addr);
225
226         bool res =
227                 twi_start_w(EEPROM_ID)
228                 && twi_send((uint8_t *)&addr, sizeof(addr))
229                 && twi_start_r(EEPROM_ID)
230                 && twi_recv(buf, count);
231 #else
232         #error Unknown device type
233 #endif
234
235         twi_stop();
236
237         if (!res)
238                 TRACEMSG("Read error!");
239         return res;
240 }
241
242
243 /*!
244  * Write a single character \a c at address \a addr.
245  */
246 bool eeprom_write_char(e2addr_t addr, char c)
247 {
248         return eeprom_write(addr, &c, 1);
249 }
250
251
252 /*!
253  * Read a single character at address \a addr.
254  *
255  * \return the requested character or -1 in case of failure.
256  */
257 int eeprom_read_char(e2addr_t addr)
258 {
259         char c;
260
261         if (eeprom_read(addr, &c, 1))
262                 return c;
263         else
264                 return -1;
265 }
266
267
268 /*!
269  * Erase specified part of eeprom, writing 0xFF.
270  *
271  * \param addr   starting address
272  * \param count  length of block to erase
273  */
274 void eeprom_erase(e2addr_t addr, size_t count)
275 {
276         uint8_t buf[EEPROM_BLKSIZE];
277         memset(buf, 0xFF, sizeof buf);
278
279         // Clear all but struct hw_info at start of eeprom
280         while (count)
281         {
282                 // Long operation, reset watchdog
283                 wdt_reset();
284
285                 size_t size = MIN(count, sizeof buf);
286                 eeprom_write(addr, buf, size);
287                 addr += size;
288                 count -= size;
289         }
290 }
291
292
293 /*!
294  * Initialize TWI module.
295  */
296 void eeprom_init(void)
297 {
298         twi_init();
299 }
300
301
302 #ifdef _DEBUG
303
304 #include <string.h>
305
306 void eeprom_test(void)
307 {
308         static const char magic[14] = "Humpty Dumpty";
309         char buf[sizeof magic];
310         size_t i;
311
312         // Write something to EEPROM using unaligned sequential writes
313         for (i = 0; i < 42; ++i)
314         {
315                 wdt_reset();
316                 eeprom_write(i * sizeof magic, magic, sizeof magic);
317         }
318
319         // Read back with single-byte reads
320         for (i = 0; i < 42 * sizeof magic; ++i)
321         {
322                 wdt_reset();
323                 eeprom_read(i, buf, 1);
324                 kprintf("EEPROM byte read: %c (%d)\n", buf[0], buf[0]);
325                 ASSERT(buf[0] == magic[i % sizeof magic]);
326         }
327
328         // Read back again using sequential reads
329         for (i = 0; i < 42; ++i)
330         {
331                 wdt_reset();
332                 memset(buf, 0, sizeof buf);
333                 eeprom_read(i * sizeof magic, buf, sizeof magic);
334                 kprintf("EEPROM seq read @ 0x%x: '%s'\n", i * sizeof magic, buf);
335                 ASSERT(memcmp(buf, magic, sizeof magic) == 0);
336         }
337 }
338
339 #endif // _DEBUG