Doxygen fix.
[bertos.git] / drv / eeprom.c
1 /*!
2  * \file
3  * <!--
4  * Copyright 2003, 2004 Develer S.r.l. (http://www.develer.com/)
5  * All Rights Reserved.
6  * -->
7  *
8  * \version $Id$
9  *
10  * \author Stefano Fedrigo <aleph@develer.com>
11  * \author Bernardo Innocenti <bernie@develer.com>
12  *
13  * \brief Driver for the 24xx16 and 24xx256 I2C EEPROMS (implementation)
14  *
15  * \note This implementation is AVR specific.
16  */
17
18 /*
19  * $Log$
20  * Revision 1.6  2004/08/24 14:27:20  bernie
21  * Doxygen fix.
22  *
23  * Revision 1.5  2004/08/24 13:46:48  bernie
24  * Include <macros.h>.
25  *
26  * Revision 1.4  2004/08/10 06:57:22  bernie
27  * eeprom_erase(): New function.
28  *
29  * Revision 1.3  2004/07/29 22:57:09  bernie
30  * Add 24LC16 support.
31  *
32  * Revision 1.2  2004/07/22 01:24:43  bernie
33  * Document AVR dependency.
34  *
35  * Revision 1.1  2004/07/20 17:11:18  bernie
36  * Import into DevLib.
37  *
38  */
39
40 #include "eeprom.h"
41
42 #include <mware/byteorder.h> /* cpu_to_be16() */
43 #include <drv/kdebug.h>
44 #include <hw.h>
45 #include <macros.h>
46
47 #include <string.h> // memset()
48
49 #include <avr/twi.h>
50
51
52 /* Wait for TWINT flag set: bus is ready */
53 #define WAIT_TWI_READY  do {} while (!(TWCR & BV(TWINT)))
54
55 /*! \name EEPROM control codes */
56 /*@{*/
57 #define SLA_W  0xA0
58 #define SLA_R  0xA1
59 /*@}*/
60
61
62 /*!
63  * Send START condition on the bus.
64  *
65  * \return true on success, false otherwise.
66  */
67 static bool twi_start(void)
68 {
69         TWCR = BV(TWINT) | BV(TWSTA) | BV(TWEN);
70         WAIT_TWI_READY;
71
72         if (TW_STATUS == TW_START || TW_STATUS == TW_REP_START)
73                 return true;
74
75         DB(kprintf("!TW_(REP)START: %x\n", TWSR);)
76         return false;
77 }
78
79
80 /*!
81  * Send START condition and select slave for write.
82  *
83  * \return true on success, false otherwise.
84  */
85 static bool twi_start_w(uint8_t slave_addr)
86 {
87         ASSERT(slave_addr < 8);
88
89         /*
90          * Loop on the select write sequence: when the eeprom is busy
91          * writing previously sent data it will reply to the SLA_W
92          * control byte with a NACK.  In this case, we must
93          * keep trying until the eeprom responds with an ACK.
94          */
95         while (twi_start())
96         {
97                 TWDR = SLA_W | (slave_addr << 1);
98                 TWCR = BV(TWINT) | BV(TWEN);
99                 WAIT_TWI_READY;
100
101                 if (TW_STATUS == TW_MT_SLA_ACK)
102                         return true;
103                 else if (TW_STATUS != TW_MT_SLA_NACK)
104                 {
105                         DB(kprintf("!TW_MT_SLA_(N)ACK: %x\n", TWSR);)
106                         break;
107                 }
108         }
109
110         return false;
111 }
112
113
114 /*!
115  * Send START condition and select slave for read.
116  *
117  * \return true on success, false otherwise.
118  */
119 static bool twi_start_r(uint8_t slave_addr)
120 {
121         ASSERT(slave_addr < 8);
122
123         if (twi_start())
124         {
125                 TWDR = SLA_R | (slave_addr << 1);
126                 TWCR = BV(TWINT) | BV(TWEN);
127                 WAIT_TWI_READY;
128
129                 if (TW_STATUS == TW_MR_SLA_ACK)
130                         return true;
131
132                 DB(kprintf("!TW_MR_SLA_ACK: %x\n", TWSR);)
133         }
134
135         return false;
136 }
137
138
139 /*!
140  * Send STOP condition.
141  */
142 static void twi_stop(void)
143 {
144         TWCR = BV(TWINT) | BV(TWEN) | BV(TWSTO);
145 }
146
147
148 /*!
149  * Send a sequence of bytes in master transmitter mode
150  * to the selected slave device through the TWI bus.
151  *
152  * \return true on success, false on error.
153  */
154 static bool twi_send(const uint8_t *buf, size_t count)
155 {
156         while (count--)
157         {
158                 TWDR = *buf++;
159                 TWCR = BV(TWINT) | BV(TWEN);
160                 WAIT_TWI_READY;
161                 if (TW_STATUS != TW_MT_DATA_ACK)
162                 {
163                         DB(kprintf("!TW_MT_DATA_ACK: %x\n", TWSR);)
164                         return false;
165                 }
166         }
167
168         return true;
169 }
170
171
172 /*!
173  * Receive a sequence of one or more bytes from the
174  * selected slave device in master receive mode through
175  * the TWI bus.
176  *
177  * Received data is placed in \c buf.
178  *
179  * \return true on success, false on error
180  */
181 static bool twi_recv(uint8_t *buf, size_t count)
182 {
183         /*
184          * When reading the last byte the TWEA bit is not
185          * set, and the eeprom should answer with NACK
186          */
187         while (count--)
188         {
189                 TWCR = BV(TWINT) | BV(TWEN) | (count ? BV(TWEA) : 0);
190                 WAIT_TWI_READY;
191
192                 if (count)
193                 {
194                         if (TW_STATUS != TW_MR_DATA_ACK)
195                         {
196                                 DB(kprintf("!TW_MR_DATA_ACK: %x\n", TWSR);)
197                                 return false;
198                         }
199                 }
200                 else
201                 {
202                         if (TW_STATUS != TW_MR_DATA_NACK)
203                         {
204                                 DB(kprintf("!TW_MR_DATA_NACK: %x\n", TWSR);)
205                                 return false;
206                         }
207                 }
208                 *buf++ = TWDR;
209         }
210
211         return true;
212 }
213
214 /*!
215  * Copy \c count bytes from buffer \c buf to
216  * eeprom at address \c addr.
217  */
218 bool eeprom_write(e2addr_t addr, const void *buf, size_t count)
219 {
220         bool result = true;
221         ASSERT(addr + count <= EEPROM_SIZE);
222
223         while (count && result)
224         {
225                 /*
226                  * Split write in multiple sequential mode operations that
227                  * don't cross page boundaries.
228                  */
229                 size_t size =
230                         MIN(count, (size_t)(EEPROM_BLKSIZE - (addr & (EEPROM_BLKSIZE - 1))));
231
232         #if CONFIG_EEPROM_TYPE == EEPROM_24XX16
233                 /*
234                  * The 24LC16 uses the slave address as a 3-bit
235                  * block address.
236                  */
237                 uint8_t blk_addr = (uint8_t)((addr >> 8) & 0x07);
238                 uint8_t blk_offs = (uint8_t)addr;
239
240                 result =
241                         twi_start_w(blk_addr)
242                         && twi_send(&blk_offs, sizeof blk_offs)
243                         && twi_send(buf, size);
244
245         #elif CONFIG_EEPROM_TYPE == EEPROM_24XX256
246
247                 // 24LC256 wants big-endian addresses
248                 uint16_t addr_be = cpu_to_be16(addr);
249
250                 result =
251                         twi_start_w(0)
252                         && twi_send((uint8_t *)&addr_be, sizeof addr_be)
253                         && twi_send(buf, size);
254
255         #else
256                 #error Unknown device type
257         #endif
258
259                 twi_stop();
260
261                 // DEBUG
262                 //kprintf("addr=%d, count=%d, size=%d, *#?=%d\n",
263                 //      addr, count, size,
264                 //      (EEPROM_BLKSIZE - (addr & (EEPROM_BLKSIZE - 1)))
265                 //);
266
267                 /* Update count and addr for next operation */
268                 count -= size;
269                 addr += size;
270                 buf = ((const char *)buf) + size;
271         }
272
273         return result;
274 }
275
276
277 /*!
278  * Copy \c count bytes at address \c addr
279  * from eeprom to RAM to buffer \c buf.
280  */
281 bool eeprom_read(e2addr_t addr, void *buf, size_t count)
282 {
283         ASSERT(addr + count <= EEPROM_SIZE);
284
285 #if CONFIG_EEPROM_TYPE == EEPROM_24XX16
286         /*
287          * The 24LC16 uses the slave address as a 3-bit
288          * block address.
289          */
290         uint8_t blk_addr = (uint8_t)((addr >> 8) & 0x07);
291         uint8_t blk_offs = (uint8_t)addr;
292
293         bool res =
294                 twi_start_w(blk_addr)
295                 && twi_send(&blk_offs, sizeof blk_offs)
296                 && twi_start_r(blk_addr)
297                 && twi_recv(buf, count);
298
299 #elif CONFIG_EEPROM_TYPE == EEPROM_24XX256
300
301         // 24LC256 wants big-endian addresses
302         addr = cpu_to_be16(addr);
303
304         bool res =
305                 twi_start_w(0)
306                 && twi_send((uint8_t *)&addr, sizeof(addr))
307                 && twi_start_r(0)
308                 && twi_recv(buf, count);
309 #else
310         #error Unknown device type
311 #endif
312
313         twi_stop();
314
315         return res;
316 }
317
318
319 /*!
320  * Write a single character \a c at address \a addr.
321  */
322 bool eeprom_write_char(e2addr_t addr, char c)
323 {
324         return eeprom_write(addr, &c, 1);
325 }
326
327
328 /*!
329  * Read a single character at address \a addr.
330  *
331  * \return the requested character or -1 in case of failure.
332  */
333 int eeprom_read_char(e2addr_t addr)
334 {
335         char c;
336
337         if (eeprom_read(addr, &c, 1))
338                 return c;
339         else
340                 return -1;
341 }
342
343
344 /*!
345  * Erase specified part of eeprom, writing 0xFF.
346  *
347  * \param addr   starting address
348  * \param count  length of block to erase
349  */
350 void eeprom_erase(e2addr_t addr, size_t count)
351 {
352         uint8_t buf[EEPROM_BLKSIZE];
353         memset(buf, 0xFF, sizeof buf);
354
355         // Clear all but struct hw_info at start of eeprom
356         while (count)
357         {
358                 size_t size = MIN(count, sizeof buf);
359                 eeprom_write(addr, buf, size);
360                 addr += size;
361                 count -= size;
362         }
363 }
364
365
366 /*!
367  * Initialize TWI module.
368  */
369 void eeprom_init(void)
370 {
371         cpuflags_t flags;
372         DISABLE_IRQSAVE(flags);
373
374 #if defined(__AVR_ATmega64__)
375         PORTD |= BV(PD0) | BV(PD1);
376         DDRD |= BV(PD0) | BV(PD1);
377 #elif defined(__AVR_ATmega8__)
378         PORTC |= BV(PC4) | BV(PC5);
379         DDRC |= BV(PC4) | BV(PC5);
380 #else
381         #error Unsupported architecture
382 #endif
383
384         /*
385          * Set speed:
386          * F = CLOCK_FREQ / (16 + 2*TWBR * 4^TWPS)
387          */
388         #define TWI_FREQ  300000  /* 300 kHz */
389         #define TWI_PRESC 1       /* 4 ^ TWPS */
390
391         TWBR = (CLOCK_FREQ / (2 * TWI_FREQ * TWI_PRESC)) - (8 / TWI_PRESC);
392         TWSR = 0;
393         TWCR = BV(TWEN);
394
395         ENABLE_IRQRESTORE(flags);
396 }
397
398
399 #ifdef _DEBUG
400
401 #include <string.h>
402
403 void eeprom_test(void)
404 {
405         static const char magic[13] = "Humpty Dumpty";
406         char buf[sizeof magic + 1];
407         size_t i;
408
409         // Write something to EEPROM using unaligned sequential writes
410         for (i = 0; i < 42; ++i)
411                 eeprom_write(i * sizeof magic, magic, sizeof magic);
412
413         // Read back with single-byte reads
414         for (i = 0; i < 42 * sizeof magic; ++i)
415         {
416                 eeprom_read(i, buf, 1);
417                 kprintf("EEPROM byte read: %c (%d)\n", buf[0], buf[0]);
418                 ASSERT(buf[0] == magic[i % sizeof magic]);
419         }
420
421         // Read back again using sequential reads
422         for (i = 0; i < 42; ++i)
423         {
424                 memset(buf, 0, sizeof buf);
425                 eeprom_read(i * sizeof magic, buf, sizeof magic);
426                 kprintf("EEPROM seq read @ 0x%x: '%s'\n", i * sizeof magic, buf);
427                 ASSERT(memcmp(buf, magic, sizeof magic) == 0);
428         }
429 }
430
431 #endif // _DEBUG