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