]> git.donarmstrong.com Git - qmk_firmware.git/blob - tmk_core/common/chibios/eeprom.c
5ff8ee86f4fd941dda94e6c44ec0ac9b7b6e0599
[qmk_firmware.git] / tmk_core / common / chibios / eeprom.c
1 #include "ch.h"
2 #include "hal.h"
3
4 #include "eeconfig.h"
5
6 /*************************************/
7 /*          Hardware backend         */
8 /*                                   */
9 /*    Code from PJRC/Teensyduino     */
10 /*************************************/
11
12 /* Teensyduino Core Library
13  * http://www.pjrc.com/teensy/
14  * Copyright (c) 2013 PJRC.COM, LLC.
15  *
16  * Permission is hereby granted, free of charge, to any person obtaining
17  * a copy of this software and associated documentation files (the
18  * "Software"), to deal in the Software without restriction, including
19  * without limitation the rights to use, copy, modify, merge, publish,
20  * distribute, sublicense, and/or sell copies of the Software, and to
21  * permit persons to whom the Software is furnished to do so, subject to
22  * the following conditions:
23  *
24  * 1. The above copyright notice and this permission notice shall be 
25  * included in all copies or substantial portions of the Software.
26  *
27  * 2. If the Software is incorporated into a build system that allows 
28  * selection among a list of target devices, then similar target
29  * devices manufactured by PJRC.COM must be included in the list of
30  * target devices and selectable in the same manner.
31  *
32  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
33  * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
34  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
35  * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
36  * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
37  * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
38  * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
39  * SOFTWARE.
40  */
41
42
43 #if defined(K20x) /* chip selection */
44 /* Teensy 3.0, 3.1, 3.2; mchck; infinity keyboard */
45
46 // The EEPROM is really RAM with a hardware-based backup system to
47 // flash memory.  Selecting a smaller size EEPROM allows more wear
48 // leveling, for higher write endurance.  If you edit this file,
49 // set this to the smallest size your application can use.  Also,
50 // due to Freescale's implementation, writing 16 or 32 bit words
51 // (aligned to 2 or 4 byte boundaries) has twice the endurance
52 // compared to writing 8 bit bytes.
53 //
54 #define EEPROM_SIZE 32
55
56 // Writing unaligned 16 or 32 bit data is handled automatically when
57 // this is defined, but at a cost of extra code size.  Without this,
58 // any unaligned write will cause a hard fault exception!  If you're
59 // absolutely sure all 16 and 32 bit writes will be aligned, you can
60 // remove the extra unnecessary code.
61 //
62 #define HANDLE_UNALIGNED_WRITES
63
64 // Minimum EEPROM Endurance
65 // ------------------------
66 #if (EEPROM_SIZE == 2048)       // 35000 writes/byte or 70000 writes/word
67   #define EEESIZE 0x33
68 #elif (EEPROM_SIZE == 1024)     // 75000 writes/byte or 150000 writes/word
69   #define EEESIZE 0x34
70 #elif (EEPROM_SIZE == 512)      // 155000 writes/byte or 310000 writes/word
71   #define EEESIZE 0x35
72 #elif (EEPROM_SIZE == 256)      // 315000 writes/byte or 630000 writes/word
73   #define EEESIZE 0x36
74 #elif (EEPROM_SIZE == 128)      // 635000 writes/byte or 1270000 writes/word
75   #define EEESIZE 0x37
76 #elif (EEPROM_SIZE == 64)       // 1275000 writes/byte or 2550000 writes/word
77   #define EEESIZE 0x38
78 #elif (EEPROM_SIZE == 32)       // 2555000 writes/byte or 5110000 writes/word
79   #define EEESIZE 0x39
80 #endif
81
82 void eeprom_initialize(void)
83 {
84         uint32_t count=0;
85         uint16_t do_flash_cmd[] = {
86                 0xf06f, 0x037f, 0x7003, 0x7803,
87                 0xf013, 0x0f80, 0xd0fb, 0x4770};
88         uint8_t status;
89
90         if (FTFL->FCNFG & FTFL_FCNFG_RAMRDY) {
91                 // FlexRAM is configured as traditional RAM
92                 // We need to reconfigure for EEPROM usage
93                 FTFL->FCCOB0 = 0x80; // PGMPART = Program Partition Command
94                 FTFL->FCCOB4 = EEESIZE; // EEPROM Size
95                 FTFL->FCCOB5 = 0x03; // 0K for Dataflash, 32K for EEPROM backup
96                 __disable_irq();
97                 // do_flash_cmd() must execute from RAM.  Luckily the C syntax is simple...
98                 (*((void (*)(volatile uint8_t *))((uint32_t)do_flash_cmd | 1)))(&(FTFL->FSTAT));
99                 __enable_irq();
100                 status = FTFL->FSTAT;
101                 if (status & (FTFL_FSTAT_RDCOLERR|FTFL_FSTAT_ACCERR|FTFL_FSTAT_FPVIOL)) {
102                         FTFL->FSTAT = (status & (FTFL_FSTAT_RDCOLERR|FTFL_FSTAT_ACCERR|FTFL_FSTAT_FPVIOL));
103                         return; // error
104                 }
105         }
106         // wait for eeprom to become ready (is this really necessary?)
107         while (!(FTFL->FCNFG & FTFL_FCNFG_EEERDY)) {
108                 if (++count > 20000) break;
109         }
110 }
111
112 #define FlexRAM ((uint8_t *)0x14000000)
113
114 uint8_t eeprom_read_byte(const uint8_t *addr)
115 {
116         uint32_t offset = (uint32_t)addr;
117         if (offset >= EEPROM_SIZE) return 0;
118         if (!(FTFL->FCNFG & FTFL_FCNFG_EEERDY)) eeprom_initialize();
119         return FlexRAM[offset];
120 }
121
122 uint16_t eeprom_read_word(const uint16_t *addr)
123 {
124         uint32_t offset = (uint32_t)addr;
125         if (offset >= EEPROM_SIZE-1) return 0;
126         if (!(FTFL->FCNFG & FTFL_FCNFG_EEERDY)) eeprom_initialize();
127         return *(uint16_t *)(&FlexRAM[offset]);
128 }
129
130 uint32_t eeprom_read_dword(const uint32_t *addr)
131 {
132         uint32_t offset = (uint32_t)addr;
133         if (offset >= EEPROM_SIZE-3) return 0;
134         if (!(FTFL->FCNFG & FTFL_FCNFG_EEERDY)) eeprom_initialize();
135         return *(uint32_t *)(&FlexRAM[offset]);
136 }
137
138 void eeprom_read_block(void *buf, const void *addr, uint32_t len)
139 {
140         uint32_t offset = (uint32_t)addr;
141         uint8_t *dest = (uint8_t *)buf;
142         uint32_t end = offset + len;
143         
144         if (!(FTFL->FCNFG & FTFL_FCNFG_EEERDY)) eeprom_initialize();
145         if (end > EEPROM_SIZE) end = EEPROM_SIZE;
146         while (offset < end) {
147                 *dest++ = FlexRAM[offset++];
148         }
149 }
150
151 int eeprom_is_ready(void)
152 {
153         return (FTFL->FCNFG & FTFL_FCNFG_EEERDY) ? 1 : 0;
154 }
155
156 static void flexram_wait(void)
157 {
158         while (!(FTFL->FCNFG & FTFL_FCNFG_EEERDY)) {
159                 // TODO: timeout
160         }
161 }
162
163 void eeprom_write_byte(uint8_t *addr, uint8_t value)
164 {
165         uint32_t offset = (uint32_t)addr;
166
167         if (offset >= EEPROM_SIZE) return;
168         if (!(FTFL->FCNFG & FTFL_FCNFG_EEERDY)) eeprom_initialize();
169         if (FlexRAM[offset] != value) {
170                 FlexRAM[offset] = value;
171                 flexram_wait();
172         }
173 }
174
175 void eeprom_write_word(uint16_t *addr, uint16_t value)
176 {
177         uint32_t offset = (uint32_t)addr;
178
179         if (offset >= EEPROM_SIZE-1) return;
180         if (!(FTFL->FCNFG & FTFL_FCNFG_EEERDY)) eeprom_initialize();
181 #ifdef HANDLE_UNALIGNED_WRITES
182         if ((offset & 1) == 0) {
183 #endif
184                 if (*(uint16_t *)(&FlexRAM[offset]) != value) {
185                         *(uint16_t *)(&FlexRAM[offset]) = value;
186                         flexram_wait();
187                 }
188 #ifdef HANDLE_UNALIGNED_WRITES
189         } else {
190                 if (FlexRAM[offset] != value) {
191                         FlexRAM[offset] = value;
192                         flexram_wait();
193                 }
194                 if (FlexRAM[offset + 1] != (value >> 8)) {
195                         FlexRAM[offset + 1] = value >> 8;
196                         flexram_wait();
197                 }
198         }
199 #endif
200 }
201
202 void eeprom_write_dword(uint32_t *addr, uint32_t value)
203 {
204         uint32_t offset = (uint32_t)addr;
205
206         if (offset >= EEPROM_SIZE-3) return;
207         if (!(FTFL->FCNFG & FTFL_FCNFG_EEERDY)) eeprom_initialize();
208 #ifdef HANDLE_UNALIGNED_WRITES
209         switch (offset & 3) {
210         case 0:
211 #endif
212                 if (*(uint32_t *)(&FlexRAM[offset]) != value) {
213                         *(uint32_t *)(&FlexRAM[offset]) = value;
214                         flexram_wait();
215                 }
216                 return;
217 #ifdef HANDLE_UNALIGNED_WRITES
218         case 2:
219                 if (*(uint16_t *)(&FlexRAM[offset]) != value) {
220                         *(uint16_t *)(&FlexRAM[offset]) = value;
221                         flexram_wait();
222                 }
223                 if (*(uint16_t *)(&FlexRAM[offset + 2]) != (value >> 16)) {
224                         *(uint16_t *)(&FlexRAM[offset + 2]) = value >> 16;
225                         flexram_wait();
226                 }
227                 return;
228         default:
229                 if (FlexRAM[offset] != value) {
230                         FlexRAM[offset] = value;
231                         flexram_wait();
232                 }
233                 if (*(uint16_t *)(&FlexRAM[offset + 1]) != (value >> 8)) {
234                         *(uint16_t *)(&FlexRAM[offset + 1]) = value >> 8;
235                         flexram_wait();
236                 }
237                 if (FlexRAM[offset + 3] != (value >> 24)) {
238                         FlexRAM[offset + 3] = value >> 24;
239                         flexram_wait();
240                 }
241         }
242 #endif
243 }
244
245 void eeprom_write_block(const void *buf, void *addr, uint32_t len)
246 {
247         uint32_t offset = (uint32_t)addr;
248         const uint8_t *src = (const uint8_t *)buf;
249
250         if (offset >= EEPROM_SIZE) return;
251         if (!(FTFL->FCNFG & FTFL_FCNFG_EEERDY)) eeprom_initialize();
252         if (len >= EEPROM_SIZE) len = EEPROM_SIZE;
253         if (offset + len >= EEPROM_SIZE) len = EEPROM_SIZE - offset;
254         while (len > 0) {
255                 uint32_t lsb = offset & 3;
256                 if (lsb == 0 && len >= 4) {
257                         // write aligned 32 bits
258                         uint32_t val32;
259                         val32 = *src++;
260                         val32 |= (*src++ << 8);
261                         val32 |= (*src++ << 16);
262                         val32 |= (*src++ << 24);
263                         if (*(uint32_t *)(&FlexRAM[offset]) != val32) {
264                                 *(uint32_t *)(&FlexRAM[offset]) = val32;
265                                 flexram_wait();
266                         }
267                         offset += 4;
268                         len -= 4;
269                 } else if ((lsb == 0 || lsb == 2) && len >= 2) {
270                         // write aligned 16 bits
271                         uint16_t val16;
272                         val16 = *src++;
273                         val16 |= (*src++ << 8);
274                         if (*(uint16_t *)(&FlexRAM[offset]) != val16) {
275                                 *(uint16_t *)(&FlexRAM[offset]) = val16;
276                                 flexram_wait();
277                         }
278                         offset += 2;
279                         len -= 2;
280                 } else {
281                         // write 8 bits
282                         uint8_t val8 = *src++;
283                         if (FlexRAM[offset] != val8) {
284                                 FlexRAM[offset] = val8;
285                                 flexram_wait();
286                         }
287                         offset++;
288                         len--;
289                 }
290         }
291 }
292
293 /*
294 void do_flash_cmd(volatile uint8_t *fstat)
295 {
296         *fstat = 0x80;
297         while ((*fstat & 0x80) == 0) ; // wait
298 }
299 00000000 <do_flash_cmd>:
300    0:   f06f 037f       mvn.w   r3, #127        ; 0x7f
301    4:   7003            strb    r3, [r0, #0]
302    6:   7803            ldrb    r3, [r0, #0]
303    8:   f013 0f80       tst.w   r3, #128        ; 0x80
304    c:   d0fb            beq.n   6 <do_flash_cmd+0x6>
305    e:   4770            bx      lr
306 */
307
308 #elif defined(KL2x) /* chip selection */
309 /* Teensy LC (emulated) */
310
311 #define SYMVAL(sym) (uint32_t)(((uint8_t *)&(sym)) - ((uint8_t *)0))
312
313 extern uint32_t __eeprom_workarea_start__;
314 extern uint32_t __eeprom_workarea_end__;
315
316 #define EEPROM_SIZE 128
317
318 static uint32_t flashend = 0;
319
320 void eeprom_initialize(void)
321 {
322         const uint16_t *p = (uint16_t *)SYMVAL(__eeprom_workarea_start__);
323
324         do {
325                 if (*p++ == 0xFFFF) {
326                         flashend = (uint32_t)(p - 2);
327                         return;
328                 }
329         } while (p < (uint16_t *)SYMVAL(__eeprom_workarea_end__));
330         flashend = (uint32_t)((uint16_t *)SYMVAL(__eeprom_workarea_end__) - 1);
331 }
332
333 uint8_t eeprom_read_byte(const uint8_t *addr)
334 {
335         uint32_t offset = (uint32_t)addr;
336         const uint16_t *p = (uint16_t *)SYMVAL(__eeprom_workarea_start__);
337         const uint16_t *end = (const uint16_t *)((uint32_t)flashend);
338         uint16_t val;
339         uint8_t data=0xFF;
340
341         if (!end) {
342                 eeprom_initialize();
343                 end = (const uint16_t *)((uint32_t)flashend);
344         }
345         if (offset < EEPROM_SIZE) {
346                 while (p <= end) {
347                         val = *p++;
348                         if ((val & 255) == offset) data = val >> 8;
349                 }
350         }
351         return data;
352 }
353
354 static void flash_write(const uint16_t *code, uint32_t addr, uint32_t data)
355 {
356         // with great power comes great responsibility....
357         uint32_t stat;
358         *(uint32_t *)&(FTFA->FCCOB3) = 0x06000000 | (addr & 0x00FFFFFC);
359         *(uint32_t *)&(FTFA->FCCOB7) = data;
360         __disable_irq();
361         (*((void (*)(volatile uint8_t *))((uint32_t)code | 1)))(&(FTFA->FSTAT));
362         __enable_irq();
363         stat = FTFA->FSTAT & (FTFA_FSTAT_RDCOLERR|FTFA_FSTAT_ACCERR|FTFA_FSTAT_FPVIOL);
364         if (stat) {
365                 FTFA->FSTAT = stat;
366         }
367         MCM->PLACR |= MCM_PLACR_CFCC;
368 }
369
370 void eeprom_write_byte(uint8_t *addr, uint8_t data)
371 {
372         uint32_t offset = (uint32_t)addr;
373         const uint16_t *p, *end = (const uint16_t *)((uint32_t)flashend);
374         uint32_t i, val, flashaddr;
375         uint16_t do_flash_cmd[] = {
376                 0x2380, 0x7003, 0x7803, 0xb25b, 0x2b00, 0xdafb, 0x4770};
377         uint8_t buf[EEPROM_SIZE];
378
379         if (offset >= EEPROM_SIZE) return;
380         if (!end) {
381                 eeprom_initialize();
382                 end = (const uint16_t *)((uint32_t)flashend);
383         }
384         if (++end < (uint16_t *)SYMVAL(__eeprom_workarea_end__)) {
385                 val = (data << 8) | offset;
386                 flashaddr = (uint32_t)end;
387                 flashend = flashaddr;
388                 if ((flashaddr & 2) == 0) {
389                         val |= 0xFFFF0000;
390                 } else {
391                         val <<= 16;
392                         val |= 0x0000FFFF;
393                 }
394                 flash_write(do_flash_cmd, flashaddr, val);
395         } else {
396                 for (i=0; i < EEPROM_SIZE; i++) {
397                         buf[i] = 0xFF;
398                 }
399                 val = 0;
400                 for (p = (uint16_t *)SYMVAL(__eeprom_workarea_start__); p < (uint16_t *)SYMVAL(__eeprom_workarea_end__); p++) {
401                         val = *p;
402                         if ((val & 255) < EEPROM_SIZE) {
403                                 buf[val & 255] = val >> 8;
404                         }
405                 }
406                 buf[offset] = data;
407                 for (flashaddr=(uint32_t)(uint16_t *)SYMVAL(__eeprom_workarea_start__); flashaddr < (uint32_t)(uint16_t *)SYMVAL(__eeprom_workarea_end__); flashaddr += 1024) {
408                         *(uint32_t *)&(FTFA->FCCOB3) = 0x09000000 | flashaddr;
409                         __disable_irq();
410                         (*((void (*)(volatile uint8_t *))((uint32_t)do_flash_cmd | 1)))(&(FTFA->FSTAT));
411                         __enable_irq();
412                         val = FTFA->FSTAT & (FTFA_FSTAT_RDCOLERR|FTFA_FSTAT_ACCERR|FTFA_FSTAT_FPVIOL);;
413                         if (val) FTFA->FSTAT = val;
414                         MCM->PLACR |= MCM_PLACR_CFCC;
415                 }
416                 flashaddr=(uint32_t)(uint16_t *)SYMVAL(__eeprom_workarea_start__);
417                 for (i=0; i < EEPROM_SIZE; i++) {
418                         if (buf[i] == 0xFF) continue;
419                         if ((flashaddr & 2) == 0) {
420                                 val = (buf[i] << 8) | i;
421                         } else {
422                                 val = val | (buf[i] << 24) | (i << 16);
423                                 flash_write(do_flash_cmd, flashaddr, val);
424                         }
425                         flashaddr += 2;
426                 }
427                 flashend = flashaddr;
428                 if ((flashaddr & 2)) {
429                         val |= 0xFFFF0000;
430                         flash_write(do_flash_cmd, flashaddr, val);
431                 }
432         }
433 }
434
435 /*
436 void do_flash_cmd(volatile uint8_t *fstat)
437 {
438         *fstat = 0x80;
439         while ((*fstat & 0x80) == 0) ; // wait
440 }
441 00000000 <do_flash_cmd>:
442    0:   2380            movs    r3, #128        ; 0x80
443    2:   7003            strb    r3, [r0, #0]
444    4:   7803            ldrb    r3, [r0, #0]
445    6:   b25b            sxtb    r3, r3
446    8:   2b00            cmp     r3, #0
447    a:   dafb            bge.n   4 <do_flash_cmd+0x4>
448    c:   4770            bx      lr
449 */
450
451
452 uint16_t eeprom_read_word(const uint16_t *addr)
453 {
454         const uint8_t *p = (const uint8_t *)addr;
455         return eeprom_read_byte(p) | (eeprom_read_byte(p+1) << 8);
456 }
457
458 uint32_t eeprom_read_dword(const uint32_t *addr)
459 {
460         const uint8_t *p = (const uint8_t *)addr;
461         return eeprom_read_byte(p) | (eeprom_read_byte(p+1) << 8)
462                 | (eeprom_read_byte(p+2) << 16) | (eeprom_read_byte(p+3) << 24);
463 }
464
465 void eeprom_read_block(void *buf, const void *addr, uint32_t len)
466 {
467         const uint8_t *p = (const uint8_t *)addr;
468         uint8_t *dest = (uint8_t *)buf;
469         while (len--) {
470                 *dest++ = eeprom_read_byte(p++);
471         }
472 }
473
474 int eeprom_is_ready(void)
475 {
476         return 1;
477 }
478
479 void eeprom_write_word(uint16_t *addr, uint16_t value)
480 {
481         uint8_t *p = (uint8_t *)addr;
482         eeprom_write_byte(p++, value);
483         eeprom_write_byte(p, value >> 8);
484 }
485
486 void eeprom_write_dword(uint32_t *addr, uint32_t value)
487 {
488         uint8_t *p = (uint8_t *)addr;
489         eeprom_write_byte(p++, value);
490         eeprom_write_byte(p++, value >> 8);
491         eeprom_write_byte(p++, value >> 16);
492         eeprom_write_byte(p, value >> 24);
493 }
494
495 void eeprom_write_block(const void *buf, void *addr, uint32_t len)
496 {
497         uint8_t *p = (uint8_t *)addr;
498         const uint8_t *src = (const uint8_t *)buf;
499         while (len--) {
500                 eeprom_write_byte(p++, *src++);
501         }
502 }
503
504 #else
505 // No EEPROM supported, so emulate it
506
507 #define EEPROM_SIZE 32
508 static uint8_t buffer[EEPROM_SIZE];
509
510 uint8_t eeprom_read_byte(const uint8_t *addr) {
511         uint32_t offset = (uint32_t)addr;
512         return buffer[offset];
513 }
514
515 void eeprom_write_byte(uint8_t *addr, uint8_t value) {
516         uint32_t offset = (uint32_t)addr;
517         buffer[offset] = value;
518 }
519
520 uint16_t eeprom_read_word(const uint16_t *addr) {
521         const uint8_t *p = (const uint8_t *)addr;
522         return eeprom_read_byte(p) | (eeprom_read_byte(p+1) << 8);
523 }
524
525 uint32_t eeprom_read_dword(const uint32_t *addr) {
526         const uint8_t *p = (const uint8_t *)addr;
527         return eeprom_read_byte(p) | (eeprom_read_byte(p+1) << 8)
528                 | (eeprom_read_byte(p+2) << 16) | (eeprom_read_byte(p+3) << 24);
529 }
530
531 void eeprom_read_block(void *buf, const void *addr, uint32_t len) {
532         const uint8_t *p = (const uint8_t *)addr;
533         uint8_t *dest = (uint8_t *)buf;
534         while (len--) {
535                 *dest++ = eeprom_read_byte(p++);
536         }
537 }
538
539 void eeprom_write_word(uint16_t *addr, uint16_t value) {
540         uint8_t *p = (uint8_t *)addr;
541         eeprom_write_byte(p++, value);
542         eeprom_write_byte(p, value >> 8);
543 }
544
545 void eeprom_write_dword(uint32_t *addr, uint32_t value) {
546         uint8_t *p = (uint8_t *)addr;
547         eeprom_write_byte(p++, value);
548         eeprom_write_byte(p++, value >> 8);
549         eeprom_write_byte(p++, value >> 16);
550         eeprom_write_byte(p, value >> 24);
551 }
552
553 void eeprom_write_block(const void *buf, void *addr, uint32_t len) {
554         uint8_t *p = (uint8_t *)addr;
555         const uint8_t *src = (const uint8_t *)buf;
556         while (len--) {
557                 eeprom_write_byte(p++, *src++);
558         }
559 }
560
561 #endif /* chip selection */
562 // The update functions just calls write for now, but could probably be optimized
563
564 void eeprom_update_byte(uint8_t *addr, uint8_t value) {
565         eeprom_write_byte(addr, value);
566 }
567
568 void eeprom_update_word(uint16_t *addr, uint16_t value) {
569         uint8_t *p = (uint8_t *)addr;
570         eeprom_write_byte(p++, value);
571         eeprom_write_byte(p, value >> 8);
572 }
573
574 void eeprom_update_dword(uint32_t *addr, uint32_t value) {
575         uint8_t *p = (uint8_t *)addr;
576         eeprom_write_byte(p++, value);
577         eeprom_write_byte(p++, value >> 8);
578         eeprom_write_byte(p++, value >> 16);
579         eeprom_write_byte(p, value >> 24);
580 }
581
582 void eeprom_update_block(const void *buf, void *addr, uint32_t len) {
583         uint8_t *p = (uint8_t *)addr;
584         const uint8_t *src = (const uint8_t *)buf;
585         while (len--) {
586                 eeprom_write_byte(p++, *src++);
587         }
588 }