diff options
Diffstat (limited to 'platforms/chibios/eeprom_teensy.c')
| -rw-r--r-- | platforms/chibios/eeprom_teensy.c | 795 |
1 files changed, 795 insertions, 0 deletions
diff --git a/platforms/chibios/eeprom_teensy.c b/platforms/chibios/eeprom_teensy.c new file mode 100644 index 000000000..97da6f9e1 --- /dev/null +++ b/platforms/chibios/eeprom_teensy.c | |||
| @@ -0,0 +1,795 @@ | |||
| 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 | #define SMC_PMSTAT_RUN ((uint8_t)0x01) | ||
| 43 | #define SMC_PMSTAT_HSRUN ((uint8_t)0x80) | ||
| 44 | |||
| 45 | #define F_CPU KINETIS_SYSCLK_FREQUENCY | ||
| 46 | |||
| 47 | static inline int kinetis_hsrun_disable(void) { | ||
| 48 | #if defined(MK66F18) | ||
| 49 | if (SMC->PMSTAT == SMC_PMSTAT_HSRUN) { | ||
| 50 | // First, reduce the CPU clock speed, but do not change | ||
| 51 | // the peripheral speed (F_BUS). Serial1 & Serial2 baud | ||
| 52 | // rates will be impacted, but most other peripherals | ||
| 53 | // will continue functioning at the same speed. | ||
| 54 | # if F_CPU == 256000000 && F_BUS == 64000000 | ||
| 55 | SIM_CLKDIV1 = SIM_CLKDIV1_OUTDIVS(1, 3, 1, 7); // TODO: TEST | ||
| 56 | # elif F_CPU == 256000000 && F_BUS == 128000000 | ||
| 57 | SIM_CLKDIV1 = SIM_CLKDIV1_OUTDIVS(1, 1, 1, 7); // TODO: TEST | ||
| 58 | # elif F_CPU == 240000000 && F_BUS == 60000000 | ||
| 59 | SIM_CLKDIV1 = SIM_CLKDIV1_OUTDIVS(1, 3, 1, 7); // ok | ||
| 60 | # elif F_CPU == 240000000 && F_BUS == 80000000 | ||
| 61 | SIM_CLKDIV1 = SIM_CLKDIV1_OUTDIVS(2, 2, 2, 8); // ok | ||
| 62 | # elif F_CPU == 240000000 && F_BUS == 120000000 | ||
| 63 | SIM_CLKDIV1 = SIM_CLKDIV1_OUTDIVS(1, 1, 1, 7); // ok | ||
| 64 | # elif F_CPU == 216000000 && F_BUS == 54000000 | ||
| 65 | SIM_CLKDIV1 = SIM_CLKDIV1_OUTDIVS(1, 3, 1, 7); // ok | ||
| 66 | # elif F_CPU == 216000000 && F_BUS == 72000000 | ||
| 67 | SIM_CLKDIV1 = SIM_CLKDIV1_OUTDIVS(2, 2, 2, 8); // ok | ||
| 68 | # elif F_CPU == 216000000 && F_BUS == 108000000 | ||
| 69 | SIM_CLKDIV1 = SIM_CLKDIV1_OUTDIVS(1, 1, 1, 7); // ok | ||
| 70 | # elif F_CPU == 192000000 && F_BUS == 48000000 | ||
| 71 | SIM_CLKDIV1 = SIM_CLKDIV1_OUTDIVS(1, 3, 1, 7); // ok | ||
| 72 | # elif F_CPU == 192000000 && F_BUS == 64000000 | ||
| 73 | SIM_CLKDIV1 = SIM_CLKDIV1_OUTDIVS(2, 2, 2, 8); // ok | ||
| 74 | # elif F_CPU == 192000000 && F_BUS == 96000000 | ||
| 75 | SIM_CLKDIV1 = SIM_CLKDIV1_OUTDIVS(1, 1, 1, 7); // ok | ||
| 76 | # elif F_CPU == 180000000 && F_BUS == 60000000 | ||
| 77 | SIM_CLKDIV1 = SIM_CLKDIV1_OUTDIVS(2, 2, 2, 8); // ok | ||
| 78 | # elif F_CPU == 180000000 && F_BUS == 90000000 | ||
| 79 | SIM_CLKDIV1 = SIM_CLKDIV1_OUTDIVS(1, 1, 1, 7); // ok | ||
| 80 | # elif F_CPU == 168000000 && F_BUS == 56000000 | ||
| 81 | SIM_CLKDIV1 = SIM_CLKDIV1_OUTDIVS(2, 2, 2, 5); // ok | ||
| 82 | # elif F_CPU == 144000000 && F_BUS == 48000000 | ||
| 83 | SIM_CLKDIV1 = SIM_CLKDIV1_OUTDIVS(2, 2, 2, 5); // ok | ||
| 84 | # elif F_CPU == 144000000 && F_BUS == 72000000 | ||
| 85 | SIM_CLKDIV1 = SIM_CLKDIV1_OUTDIVS(1, 1, 1, 5); // ok | ||
| 86 | # elif F_CPU == 120000000 && F_BUS == 60000000 | ||
| 87 | SIM->CLKDIV1 = SIM_CLKDIV1_OUTDIV1(KINETIS_CLKDIV1_OUTDIV1 - 1) | SIM_CLKDIV1_OUTDIV2(KINETIS_CLKDIV1_OUTDIV2 - 1) | | ||
| 88 | # if defined(MK66F18) | ||
| 89 | SIM_CLKDIV1_OUTDIV3(KINETIS_CLKDIV1_OUTDIV3 - 1) | | ||
| 90 | # endif | ||
| 91 | SIM_CLKDIV1_OUTDIV4(KINETIS_CLKDIV1_OUTDIV4 - 1); | ||
| 92 | # else | ||
| 93 | return 0; | ||
| 94 | # endif | ||
| 95 | // Then turn off HSRUN mode | ||
| 96 | SMC->PMCTRL = SMC_PMCTRL_RUNM_SET(0); | ||
| 97 | while (SMC->PMSTAT == SMC_PMSTAT_HSRUN) | ||
| 98 | ; // wait | ||
| 99 | return 1; | ||
| 100 | } | ||
| 101 | #endif | ||
| 102 | return 0; | ||
| 103 | } | ||
| 104 | |||
| 105 | static inline int kinetis_hsrun_enable(void) { | ||
| 106 | #if defined(MK66F18) | ||
| 107 | if (SMC->PMSTAT == SMC_PMSTAT_RUN) { | ||
| 108 | // Turn HSRUN mode on | ||
| 109 | SMC->PMCTRL = SMC_PMCTRL_RUNM_SET(3); | ||
| 110 | while (SMC->PMSTAT != SMC_PMSTAT_HSRUN) { | ||
| 111 | ; | ||
| 112 | } // wait | ||
| 113 | // Then configure clock for full speed | ||
| 114 | # if F_CPU == 256000000 && F_BUS == 64000000 | ||
| 115 | SIM_CLKDIV1 = SIM_CLKDIV1_OUTDIVS(0, 3, 0, 7); | ||
| 116 | # elif F_CPU == 256000000 && F_BUS == 128000000 | ||
| 117 | SIM_CLKDIV1 = SIM_CLKDIV1_OUTDIVS(0, 1, 0, 7); | ||
| 118 | # elif F_CPU == 240000000 && F_BUS == 60000000 | ||
| 119 | SIM_CLKDIV1 = SIM_CLKDIV1_OUTDIVS(0, 3, 0, 7); | ||
| 120 | # elif F_CPU == 240000000 && F_BUS == 80000000 | ||
| 121 | SIM_CLKDIV1 = SIM_CLKDIV1_OUTDIVS(0, 2, 0, 7); | ||
| 122 | # elif F_CPU == 240000000 && F_BUS == 120000000 | ||
| 123 | SIM_CLKDIV1 = SIM_CLKDIV1_OUTDIVS(0, 1, 0, 7); | ||
| 124 | # elif F_CPU == 216000000 && F_BUS == 54000000 | ||
| 125 | SIM_CLKDIV1 = SIM_CLKDIV1_OUTDIVS(0, 3, 0, 7); | ||
| 126 | # elif F_CPU == 216000000 && F_BUS == 72000000 | ||
| 127 | SIM_CLKDIV1 = SIM_CLKDIV1_OUTDIVS(0, 2, 0, 7); | ||
| 128 | # elif F_CPU == 216000000 && F_BUS == 108000000 | ||
| 129 | SIM_CLKDIV1 = SIM_CLKDIV1_OUTDIVS(0, 1, 0, 7); | ||
| 130 | # elif F_CPU == 192000000 && F_BUS == 48000000 | ||
| 131 | SIM_CLKDIV1 = SIM_CLKDIV1_OUTDIVS(0, 3, 0, 6); | ||
| 132 | # elif F_CPU == 192000000 && F_BUS == 64000000 | ||
| 133 | SIM_CLKDIV1 = SIM_CLKDIV1_OUTDIVS(0, 2, 0, 6); | ||
| 134 | # elif F_CPU == 192000000 && F_BUS == 96000000 | ||
| 135 | SIM_CLKDIV1 = SIM_CLKDIV1_OUTDIVS(0, 1, 0, 6); | ||
| 136 | # elif F_CPU == 180000000 && F_BUS == 60000000 | ||
| 137 | SIM_CLKDIV1 = SIM_CLKDIV1_OUTDIVS(0, 2, 0, 6); | ||
| 138 | # elif F_CPU == 180000000 && F_BUS == 90000000 | ||
| 139 | SIM_CLKDIV1 = SIM_CLKDIV1_OUTDIVS(0, 1, 0, 6); | ||
| 140 | # elif F_CPU == 168000000 && F_BUS == 56000000 | ||
| 141 | SIM_CLKDIV1 = SIM_CLKDIV1_OUTDIVS(0, 2, 0, 5); | ||
| 142 | # elif F_CPU == 144000000 && F_BUS == 48000000 | ||
| 143 | SIM_CLKDIV1 = SIM_CLKDIV1_OUTDIVS(0, 2, 0, 4); | ||
| 144 | # elif F_CPU == 144000000 && F_BUS == 72000000 | ||
| 145 | SIM_CLKDIV1 = SIM_CLKDIV1_OUTDIVS(0, 1, 0, 4); | ||
| 146 | # elif F_CPU == 120000000 && F_BUS == 60000000 | ||
| 147 | SIM->CLKDIV1 = SIM_CLKDIV1_OUTDIV1(KINETIS_CLKDIV1_OUTDIV1 - 1) | SIM_CLKDIV1_OUTDIV2(KINETIS_CLKDIV1_OUTDIV2 - 1) | | ||
| 148 | # if defined(MK66F18) | ||
| 149 | SIM_CLKDIV1_OUTDIV3(KINETIS_CLKDIV1_OUTDIV3 - 1) | | ||
| 150 | # endif | ||
| 151 | SIM_CLKDIV1_OUTDIV4(KINETIS_CLKDIV1_OUTDIV4 - 1); | ||
| 152 | # else | ||
| 153 | return 0; | ||
| 154 | # endif | ||
| 155 | return 1; | ||
| 156 | } | ||
| 157 | #endif | ||
| 158 | return 0; | ||
| 159 | } | ||
| 160 | |||
| 161 | #if defined(K20x) || defined(MK66F18) /* chip selection */ | ||
| 162 | /* Teensy 3.0, 3.1, 3.2; mchck; infinity keyboard */ | ||
| 163 | |||
| 164 | // The EEPROM is really RAM with a hardware-based backup system to | ||
| 165 | // flash memory. Selecting a smaller size EEPROM allows more wear | ||
| 166 | // leveling, for higher write endurance. If you edit this file, | ||
| 167 | // set this to the smallest size your application can use. Also, | ||
| 168 | // due to Freescale's implementation, writing 16 or 32 bit words | ||
| 169 | // (aligned to 2 or 4 byte boundaries) has twice the endurance | ||
| 170 | // compared to writing 8 bit bytes. | ||
| 171 | // | ||
| 172 | # ifndef EEPROM_SIZE | ||
| 173 | # define EEPROM_SIZE 32 | ||
| 174 | # endif | ||
| 175 | |||
| 176 | /* | ||
| 177 | ^^^ Here be dragons: | ||
| 178 | NXP AppNote AN4282 section 3.1 states that partitioning must only be done once. | ||
| 179 | Once EEPROM partitioning is done, the size is locked to this initial configuration. | ||
| 180 | Attempts to modify the EEPROM_SIZE setting may brick your board. | ||
| 181 | */ | ||
| 182 | |||
| 183 | // Writing unaligned 16 or 32 bit data is handled automatically when | ||
| 184 | // this is defined, but at a cost of extra code size. Without this, | ||
| 185 | // any unaligned write will cause a hard fault exception! If you're | ||
| 186 | // absolutely sure all 16 and 32 bit writes will be aligned, you can | ||
| 187 | // remove the extra unnecessary code. | ||
| 188 | // | ||
| 189 | # define HANDLE_UNALIGNED_WRITES | ||
| 190 | |||
| 191 | # if defined(K20x) | ||
| 192 | # define EEPROM_MAX 2048 | ||
| 193 | # define EEPARTITION 0x03 // all 32K dataflash for EEPROM, none for Data | ||
| 194 | # define EEESPLIT 0x30 // must be 0x30 on these chips | ||
| 195 | # elif defined(MK66F18) | ||
| 196 | # define EEPROM_MAX 4096 | ||
| 197 | # define EEPARTITION 0x05 // 128K dataflash for EEPROM, 128K for Data | ||
| 198 | # define EEESPLIT 0x10 // best endurance: 0x00 = first 12%, 0x10 = first 25%, 0x30 = all equal | ||
| 199 | # endif | ||
| 200 | |||
| 201 | // Minimum EEPROM Endurance | ||
| 202 | // ------------------------ | ||
| 203 | # if (EEPROM_SIZE == 4096) | ||
| 204 | # define EEESIZE 0x02 | ||
| 205 | # elif (EEPROM_SIZE == 2048) // 35000 writes/byte or 70000 writes/word | ||
| 206 | # define EEESIZE 0x03 | ||
| 207 | # elif (EEPROM_SIZE == 1024) // 75000 writes/byte or 150000 writes/word | ||
| 208 | # define EEESIZE 0x04 | ||
| 209 | # elif (EEPROM_SIZE == 512) // 155000 writes/byte or 310000 writes/word | ||
| 210 | # define EEESIZE 0x05 | ||
| 211 | # elif (EEPROM_SIZE == 256) // 315000 writes/byte or 630000 writes/word | ||
| 212 | # define EEESIZE 0x06 | ||
| 213 | # elif (EEPROM_SIZE == 128) // 635000 writes/byte or 1270000 writes/word | ||
| 214 | # define EEESIZE 0x07 | ||
| 215 | # elif (EEPROM_SIZE == 64) // 1275000 writes/byte or 2550000 writes/word | ||
| 216 | # define EEESIZE 0x08 | ||
| 217 | # elif (EEPROM_SIZE == 32) // 2555000 writes/byte or 5110000 writes/word | ||
| 218 | # define EEESIZE 0x09 | ||
| 219 | # endif | ||
| 220 | |||
| 221 | /** \brief eeprom initialization | ||
| 222 | * | ||
| 223 | * FIXME: needs doc | ||
| 224 | */ | ||
| 225 | void eeprom_initialize(void) { | ||
| 226 | uint32_t count = 0; | ||
| 227 | uint16_t do_flash_cmd[] = {0xf06f, 0x037f, 0x7003, 0x7803, 0xf013, 0x0f80, 0xd0fb, 0x4770}; | ||
| 228 | uint8_t status; | ||
| 229 | |||
| 230 | if (FTFL->FCNFG & FTFL_FCNFG_RAMRDY) { | ||
| 231 | uint8_t stat = FTFL->FSTAT & 0x70; | ||
| 232 | if (stat) FTFL->FSTAT = stat; | ||
| 233 | |||
| 234 | // FlexRAM is configured as traditional RAM | ||
| 235 | // We need to reconfigure for EEPROM usage | ||
| 236 | kinetis_hsrun_disable(); | ||
| 237 | FTFL->FCCOB0 = 0x80; // PGMPART = Program Partition Command | ||
| 238 | FTFL->FCCOB3 = 0; | ||
| 239 | FTFL->FCCOB4 = EEESPLIT | EEESIZE; | ||
| 240 | FTFL->FCCOB5 = EEPARTITION; | ||
| 241 | __disable_irq(); | ||
| 242 | // do_flash_cmd() must execute from RAM. Luckily the C syntax is simple... | ||
| 243 | (*((void (*)(volatile uint8_t *))((uint32_t)do_flash_cmd | 1)))(&(FTFL->FSTAT)); | ||
| 244 | __enable_irq(); | ||
| 245 | kinetis_hsrun_enable(); | ||
| 246 | status = FTFL->FSTAT; | ||
| 247 | if (status & (FTFL_FSTAT_RDCOLERR | FTFL_FSTAT_ACCERR | FTFL_FSTAT_FPVIOL)) { | ||
| 248 | FTFL->FSTAT = (status & (FTFL_FSTAT_RDCOLERR | FTFL_FSTAT_ACCERR | FTFL_FSTAT_FPVIOL)); | ||
| 249 | return; // error | ||
| 250 | } | ||
| 251 | } | ||
| 252 | // wait for eeprom to become ready (is this really necessary?) | ||
| 253 | while (!(FTFL->FCNFG & FTFL_FCNFG_EEERDY)) { | ||
| 254 | if (++count > 200000) break; | ||
| 255 | } | ||
| 256 | } | ||
| 257 | |||
| 258 | # define FlexRAM ((volatile uint8_t *)0x14000000) | ||
| 259 | |||
| 260 | /** \brief eeprom read byte | ||
| 261 | * | ||
| 262 | * FIXME: needs doc | ||
| 263 | */ | ||
| 264 | uint8_t eeprom_read_byte(const uint8_t *addr) { | ||
| 265 | uint32_t offset = (uint32_t)addr; | ||
| 266 | if (offset >= EEPROM_SIZE) return 0; | ||
| 267 | if (!(FTFL->FCNFG & FTFL_FCNFG_EEERDY)) eeprom_initialize(); | ||
| 268 | return FlexRAM[offset]; | ||
| 269 | } | ||
| 270 | |||
| 271 | /** \brief eeprom read word | ||
| 272 | * | ||
| 273 | * FIXME: needs doc | ||
| 274 | */ | ||
| 275 | uint16_t eeprom_read_word(const uint16_t *addr) { | ||
| 276 | uint32_t offset = (uint32_t)addr; | ||
| 277 | if (offset >= EEPROM_SIZE - 1) return 0; | ||
| 278 | if (!(FTFL->FCNFG & FTFL_FCNFG_EEERDY)) eeprom_initialize(); | ||
| 279 | return *(uint16_t *)(&FlexRAM[offset]); | ||
| 280 | } | ||
| 281 | |||
| 282 | /** \brief eeprom read dword | ||
| 283 | * | ||
| 284 | * FIXME: needs doc | ||
| 285 | */ | ||
| 286 | uint32_t eeprom_read_dword(const uint32_t *addr) { | ||
| 287 | uint32_t offset = (uint32_t)addr; | ||
| 288 | if (offset >= EEPROM_SIZE - 3) return 0; | ||
| 289 | if (!(FTFL->FCNFG & FTFL_FCNFG_EEERDY)) eeprom_initialize(); | ||
| 290 | return *(uint32_t *)(&FlexRAM[offset]); | ||
| 291 | } | ||
| 292 | |||
| 293 | /** \brief eeprom read block | ||
| 294 | * | ||
| 295 | * FIXME: needs doc | ||
| 296 | */ | ||
| 297 | void eeprom_read_block(void *buf, const void *addr, uint32_t len) { | ||
| 298 | uint32_t offset = (uint32_t)addr; | ||
| 299 | uint8_t *dest = (uint8_t *)buf; | ||
| 300 | uint32_t end = offset + len; | ||
| 301 | |||
| 302 | if (!(FTFL->FCNFG & FTFL_FCNFG_EEERDY)) eeprom_initialize(); | ||
| 303 | if (end > EEPROM_SIZE) end = EEPROM_SIZE; | ||
| 304 | while (offset < end) { | ||
| 305 | *dest++ = FlexRAM[offset++]; | ||
| 306 | } | ||
| 307 | } | ||
| 308 | |||
| 309 | /** \brief eeprom is ready | ||
| 310 | * | ||
| 311 | * FIXME: needs doc | ||
| 312 | */ | ||
| 313 | int eeprom_is_ready(void) { return (FTFL->FCNFG & FTFL_FCNFG_EEERDY) ? 1 : 0; } | ||
| 314 | |||
| 315 | /** \brief flexram wait | ||
| 316 | * | ||
| 317 | * FIXME: needs doc | ||
| 318 | */ | ||
| 319 | static void flexram_wait(void) { | ||
| 320 | while (!(FTFL->FCNFG & FTFL_FCNFG_EEERDY)) { | ||
| 321 | // TODO: timeout | ||
| 322 | } | ||
| 323 | } | ||
| 324 | |||
| 325 | /** \brief eeprom_write_byte | ||
| 326 | * | ||
| 327 | * FIXME: needs doc | ||
| 328 | */ | ||
| 329 | void eeprom_write_byte(uint8_t *addr, uint8_t value) { | ||
| 330 | uint32_t offset = (uint32_t)addr; | ||
| 331 | |||
| 332 | if (offset >= EEPROM_SIZE) return; | ||
| 333 | if (!(FTFL->FCNFG & FTFL_FCNFG_EEERDY)) eeprom_initialize(); | ||
| 334 | if (FlexRAM[offset] != value) { | ||
| 335 | kinetis_hsrun_disable(); | ||
| 336 | uint8_t stat = FTFL->FSTAT & 0x70; | ||
| 337 | if (stat) FTFL->FSTAT = stat; | ||
| 338 | FlexRAM[offset] = value; | ||
| 339 | flexram_wait(); | ||
| 340 | kinetis_hsrun_enable(); | ||
| 341 | } | ||
| 342 | } | ||
| 343 | |||
| 344 | /** \brief eeprom write word | ||
| 345 | * | ||
| 346 | * FIXME: needs doc | ||
| 347 | */ | ||
| 348 | void eeprom_write_word(uint16_t *addr, uint16_t value) { | ||
| 349 | uint32_t offset = (uint32_t)addr; | ||
| 350 | |||
| 351 | if (offset >= EEPROM_SIZE - 1) return; | ||
| 352 | if (!(FTFL->FCNFG & FTFL_FCNFG_EEERDY)) eeprom_initialize(); | ||
| 353 | # ifdef HANDLE_UNALIGNED_WRITES | ||
| 354 | if ((offset & 1) == 0) { | ||
| 355 | # endif | ||
| 356 | if (*(uint16_t *)(&FlexRAM[offset]) != value) { | ||
| 357 | kinetis_hsrun_disable(); | ||
| 358 | uint8_t stat = FTFL->FSTAT & 0x70; | ||
| 359 | if (stat) FTFL->FSTAT = stat; | ||
| 360 | *(uint16_t *)(&FlexRAM[offset]) = value; | ||
| 361 | flexram_wait(); | ||
| 362 | kinetis_hsrun_enable(); | ||
| 363 | } | ||
| 364 | # ifdef HANDLE_UNALIGNED_WRITES | ||
| 365 | } else { | ||
| 366 | if (FlexRAM[offset] != value) { | ||
| 367 | kinetis_hsrun_disable(); | ||
| 368 | uint8_t stat = FTFL->FSTAT & 0x70; | ||
| 369 | if (stat) FTFL->FSTAT = stat; | ||
| 370 | FlexRAM[offset] = value; | ||
| 371 | flexram_wait(); | ||
| 372 | kinetis_hsrun_enable(); | ||
| 373 | } | ||
| 374 | if (FlexRAM[offset + 1] != (value >> 8)) { | ||
| 375 | kinetis_hsrun_disable(); | ||
| 376 | uint8_t stat = FTFL->FSTAT & 0x70; | ||
| 377 | if (stat) FTFL->FSTAT = stat; | ||
| 378 | FlexRAM[offset + 1] = value >> 8; | ||
| 379 | flexram_wait(); | ||
| 380 | kinetis_hsrun_enable(); | ||
| 381 | } | ||
| 382 | } | ||
| 383 | # endif | ||
| 384 | } | ||
| 385 | |||
| 386 | /** \brief eeprom write dword | ||
| 387 | * | ||
| 388 | * FIXME: needs doc | ||
| 389 | */ | ||
| 390 | void eeprom_write_dword(uint32_t *addr, uint32_t value) { | ||
| 391 | uint32_t offset = (uint32_t)addr; | ||
| 392 | |||
| 393 | if (offset >= EEPROM_SIZE - 3) return; | ||
| 394 | if (!(FTFL->FCNFG & FTFL_FCNFG_EEERDY)) eeprom_initialize(); | ||
| 395 | # ifdef HANDLE_UNALIGNED_WRITES | ||
| 396 | switch (offset & 3) { | ||
| 397 | case 0: | ||
| 398 | # endif | ||
| 399 | if (*(uint32_t *)(&FlexRAM[offset]) != value) { | ||
| 400 | kinetis_hsrun_disable(); | ||
| 401 | uint8_t stat = FTFL->FSTAT & 0x70; | ||
| 402 | if (stat) FTFL->FSTAT = stat; | ||
| 403 | *(uint32_t *)(&FlexRAM[offset]) = value; | ||
| 404 | flexram_wait(); | ||
| 405 | kinetis_hsrun_enable(); | ||
| 406 | } | ||
| 407 | return; | ||
| 408 | # ifdef HANDLE_UNALIGNED_WRITES | ||
| 409 | case 2: | ||
| 410 | if (*(uint16_t *)(&FlexRAM[offset]) != value) { | ||
| 411 | kinetis_hsrun_disable(); | ||
| 412 | uint8_t stat = FTFL->FSTAT & 0x70; | ||
| 413 | if (stat) FTFL->FSTAT = stat; | ||
| 414 | *(uint16_t *)(&FlexRAM[offset]) = value; | ||
| 415 | flexram_wait(); | ||
| 416 | kinetis_hsrun_enable(); | ||
| 417 | } | ||
| 418 | if (*(uint16_t *)(&FlexRAM[offset + 2]) != (value >> 16)) { | ||
| 419 | kinetis_hsrun_disable(); | ||
| 420 | uint8_t stat = FTFL->FSTAT & 0x70; | ||
| 421 | if (stat) FTFL->FSTAT = stat; | ||
| 422 | *(uint16_t *)(&FlexRAM[offset + 2]) = value >> 16; | ||
| 423 | flexram_wait(); | ||
| 424 | kinetis_hsrun_enable(); | ||
| 425 | } | ||
| 426 | return; | ||
| 427 | default: | ||
| 428 | if (FlexRAM[offset] != value) { | ||
| 429 | kinetis_hsrun_disable(); | ||
| 430 | uint8_t stat = FTFL->FSTAT & 0x70; | ||
| 431 | if (stat) FTFL->FSTAT = stat; | ||
| 432 | FlexRAM[offset] = value; | ||
| 433 | flexram_wait(); | ||
| 434 | kinetis_hsrun_enable(); | ||
| 435 | } | ||
| 436 | if (*(uint16_t *)(&FlexRAM[offset + 1]) != (value >> 8)) { | ||
| 437 | kinetis_hsrun_disable(); | ||
| 438 | uint8_t stat = FTFL->FSTAT & 0x70; | ||
| 439 | if (stat) FTFL->FSTAT = stat; | ||
| 440 | *(uint16_t *)(&FlexRAM[offset + 1]) = value >> 8; | ||
| 441 | flexram_wait(); | ||
| 442 | kinetis_hsrun_enable(); | ||
| 443 | } | ||
| 444 | if (FlexRAM[offset + 3] != (value >> 24)) { | ||
| 445 | kinetis_hsrun_disable(); | ||
| 446 | uint8_t stat = FTFL->FSTAT & 0x70; | ||
| 447 | if (stat) FTFL->FSTAT = stat; | ||
| 448 | FlexRAM[offset + 3] = value >> 24; | ||
| 449 | flexram_wait(); | ||
| 450 | kinetis_hsrun_enable(); | ||
| 451 | } | ||
| 452 | } | ||
| 453 | # endif | ||
| 454 | } | ||
| 455 | |||
| 456 | /** \brief eeprom write block | ||
| 457 | * | ||
| 458 | * FIXME: needs doc | ||
| 459 | */ | ||
| 460 | void eeprom_write_block(const void *buf, void *addr, uint32_t len) { | ||
| 461 | uint32_t offset = (uint32_t)addr; | ||
| 462 | const uint8_t *src = (const uint8_t *)buf; | ||
| 463 | |||
| 464 | if (offset >= EEPROM_SIZE) return; | ||
| 465 | if (!(FTFL->FCNFG & FTFL_FCNFG_EEERDY)) eeprom_initialize(); | ||
| 466 | if (len >= EEPROM_SIZE) len = EEPROM_SIZE; | ||
| 467 | if (offset + len >= EEPROM_SIZE) len = EEPROM_SIZE - offset; | ||
| 468 | kinetis_hsrun_disable(); | ||
| 469 | while (len > 0) { | ||
| 470 | uint32_t lsb = offset & 3; | ||
| 471 | if (lsb == 0 && len >= 4) { | ||
| 472 | // write aligned 32 bits | ||
| 473 | uint32_t val32; | ||
| 474 | val32 = *src++; | ||
| 475 | val32 |= (*src++ << 8); | ||
| 476 | val32 |= (*src++ << 16); | ||
| 477 | val32 |= (*src++ << 24); | ||
| 478 | if (*(uint32_t *)(&FlexRAM[offset]) != val32) { | ||
| 479 | uint8_t stat = FTFL->FSTAT & 0x70; | ||
| 480 | if (stat) FTFL->FSTAT = stat; | ||
| 481 | *(uint32_t *)(&FlexRAM[offset]) = val32; | ||
| 482 | flexram_wait(); | ||
| 483 | } | ||
| 484 | offset += 4; | ||
| 485 | len -= 4; | ||
| 486 | } else if ((lsb == 0 || lsb == 2) && len >= 2) { | ||
| 487 | // write aligned 16 bits | ||
| 488 | uint16_t val16; | ||
| 489 | val16 = *src++; | ||
| 490 | val16 |= (*src++ << 8); | ||
| 491 | if (*(uint16_t *)(&FlexRAM[offset]) != val16) { | ||
| 492 | uint8_t stat = FTFL->FSTAT & 0x70; | ||
| 493 | if (stat) FTFL->FSTAT = stat; | ||
| 494 | *(uint16_t *)(&FlexRAM[offset]) = val16; | ||
| 495 | flexram_wait(); | ||
| 496 | } | ||
| 497 | offset += 2; | ||
| 498 | len -= 2; | ||
| 499 | } else { | ||
| 500 | // write 8 bits | ||
| 501 | uint8_t val8 = *src++; | ||
| 502 | if (FlexRAM[offset] != val8) { | ||
| 503 | uint8_t stat = FTFL->FSTAT & 0x70; | ||
| 504 | if (stat) FTFL->FSTAT = stat; | ||
| 505 | FlexRAM[offset] = val8; | ||
| 506 | flexram_wait(); | ||
| 507 | } | ||
| 508 | offset++; | ||
| 509 | len--; | ||
| 510 | } | ||
| 511 | } | ||
| 512 | kinetis_hsrun_enable(); | ||
| 513 | } | ||
| 514 | |||
| 515 | /* | ||
| 516 | void do_flash_cmd(volatile uint8_t *fstat) | ||
| 517 | { | ||
| 518 | *fstat = 0x80; | ||
| 519 | while ((*fstat & 0x80) == 0) ; // wait | ||
| 520 | } | ||
| 521 | 00000000 <do_flash_cmd>: | ||
| 522 | 0: f06f 037f mvn.w r3, #127 ; 0x7f | ||
| 523 | 4: 7003 strb r3, [r0, #0] | ||
| 524 | 6: 7803 ldrb r3, [r0, #0] | ||
| 525 | 8: f013 0f80 tst.w r3, #128 ; 0x80 | ||
| 526 | c: d0fb beq.n 6 <do_flash_cmd+0x6> | ||
| 527 | e: 4770 bx lr | ||
| 528 | */ | ||
| 529 | |||
| 530 | #elif defined(KL2x) /* chip selection */ | ||
| 531 | /* Teensy LC (emulated) */ | ||
| 532 | |||
| 533 | # define SYMVAL(sym) (uint32_t)(((uint8_t *)&(sym)) - ((uint8_t *)0)) | ||
| 534 | |||
| 535 | extern uint32_t __eeprom_workarea_start__; | ||
| 536 | extern uint32_t __eeprom_workarea_end__; | ||
| 537 | |||
| 538 | # define EEPROM_SIZE 128 | ||
| 539 | |||
| 540 | static uint32_t flashend = 0; | ||
| 541 | |||
| 542 | void eeprom_initialize(void) { | ||
| 543 | const uint16_t *p = (uint16_t *)SYMVAL(__eeprom_workarea_start__); | ||
| 544 | |||
| 545 | do { | ||
| 546 | if (*p++ == 0xFFFF) { | ||
| 547 | flashend = (uint32_t)(p - 2); | ||
| 548 | return; | ||
| 549 | } | ||
| 550 | } while (p < (uint16_t *)SYMVAL(__eeprom_workarea_end__)); | ||
| 551 | flashend = (uint32_t)(p - 1); | ||
| 552 | } | ||
| 553 | |||
| 554 | uint8_t eeprom_read_byte(const uint8_t *addr) { | ||
| 555 | uint32_t offset = (uint32_t)addr; | ||
| 556 | const uint16_t *p = (uint16_t *)SYMVAL(__eeprom_workarea_start__); | ||
| 557 | const uint16_t *end = (const uint16_t *)((uint32_t)flashend); | ||
| 558 | uint16_t val; | ||
| 559 | uint8_t data = 0xFF; | ||
| 560 | |||
| 561 | if (!end) { | ||
| 562 | eeprom_initialize(); | ||
| 563 | end = (const uint16_t *)((uint32_t)flashend); | ||
| 564 | } | ||
| 565 | if (offset < EEPROM_SIZE) { | ||
| 566 | while (p <= end) { | ||
| 567 | val = *p++; | ||
| 568 | if ((val & 255) == offset) data = val >> 8; | ||
| 569 | } | ||
| 570 | } | ||
| 571 | return data; | ||
| 572 | } | ||
| 573 | |||
| 574 | static void flash_write(const uint16_t *code, uint32_t addr, uint32_t data) { | ||
| 575 | // with great power comes great responsibility.... | ||
| 576 | uint32_t stat; | ||
| 577 | *(uint32_t *)&(FTFA->FCCOB3) = 0x06000000 | (addr & 0x00FFFFFC); | ||
| 578 | *(uint32_t *)&(FTFA->FCCOB7) = data; | ||
| 579 | __disable_irq(); | ||
| 580 | (*((void (*)(volatile uint8_t *))((uint32_t)code | 1)))(&(FTFA->FSTAT)); | ||
| 581 | __enable_irq(); | ||
| 582 | stat = FTFA->FSTAT & (FTFA_FSTAT_RDCOLERR | FTFA_FSTAT_ACCERR | FTFA_FSTAT_FPVIOL); | ||
| 583 | if (stat) { | ||
| 584 | FTFA->FSTAT = stat; | ||
| 585 | } | ||
| 586 | MCM->PLACR |= MCM_PLACR_CFCC; | ||
| 587 | } | ||
| 588 | |||
| 589 | void eeprom_write_byte(uint8_t *addr, uint8_t data) { | ||
| 590 | uint32_t offset = (uint32_t)addr; | ||
| 591 | const uint16_t *p, *end = (const uint16_t *)((uint32_t)flashend); | ||
| 592 | uint32_t i, val, flashaddr; | ||
| 593 | uint16_t do_flash_cmd[] = {0x2380, 0x7003, 0x7803, 0xb25b, 0x2b00, 0xdafb, 0x4770}; | ||
| 594 | uint8_t buf[EEPROM_SIZE]; | ||
| 595 | |||
| 596 | if (offset >= EEPROM_SIZE) return; | ||
| 597 | if (!end) { | ||
| 598 | eeprom_initialize(); | ||
| 599 | end = (const uint16_t *)((uint32_t)flashend); | ||
| 600 | } | ||
| 601 | if (++end < (uint16_t *)SYMVAL(__eeprom_workarea_end__)) { | ||
| 602 | val = (data << 8) | offset; | ||
| 603 | flashaddr = (uint32_t)end; | ||
| 604 | flashend = flashaddr; | ||
| 605 | if ((flashaddr & 2) == 0) { | ||
| 606 | val |= 0xFFFF0000; | ||
| 607 | } else { | ||
| 608 | val <<= 16; | ||
| 609 | val |= 0x0000FFFF; | ||
| 610 | } | ||
| 611 | flash_write(do_flash_cmd, flashaddr, val); | ||
| 612 | } else { | ||
| 613 | for (i = 0; i < EEPROM_SIZE; i++) { | ||
| 614 | buf[i] = 0xFF; | ||
| 615 | } | ||
| 616 | val = 0; | ||
| 617 | for (p = (uint16_t *)SYMVAL(__eeprom_workarea_start__); p < (uint16_t *)SYMVAL(__eeprom_workarea_end__); p++) { | ||
| 618 | val = *p; | ||
| 619 | if ((val & 255) < EEPROM_SIZE) { | ||
| 620 | buf[val & 255] = val >> 8; | ||
| 621 | } | ||
| 622 | } | ||
| 623 | buf[offset] = data; | ||
| 624 | for (flashaddr = (uint32_t)(uint16_t *)SYMVAL(__eeprom_workarea_start__); flashaddr < (uint32_t)(uint16_t *)SYMVAL(__eeprom_workarea_end__); flashaddr += 1024) { | ||
| 625 | *(uint32_t *)&(FTFA->FCCOB3) = 0x09000000 | flashaddr; | ||
| 626 | __disable_irq(); | ||
| 627 | (*((void (*)(volatile uint8_t *))((uint32_t)do_flash_cmd | 1)))(&(FTFA->FSTAT)); | ||
| 628 | __enable_irq(); | ||
| 629 | val = FTFA->FSTAT & (FTFA_FSTAT_RDCOLERR | FTFA_FSTAT_ACCERR | FTFA_FSTAT_FPVIOL); | ||
| 630 | ; | ||
| 631 | if (val) FTFA->FSTAT = val; | ||
| 632 | MCM->PLACR |= MCM_PLACR_CFCC; | ||
| 633 | } | ||
| 634 | flashaddr = (uint32_t)(uint16_t *)SYMVAL(__eeprom_workarea_start__); | ||
| 635 | for (i = 0; i < EEPROM_SIZE; i++) { | ||
| 636 | if (buf[i] == 0xFF) continue; | ||
| 637 | if ((flashaddr & 2) == 0) { | ||
| 638 | val = (buf[i] << 8) | i; | ||
| 639 | } else { | ||
| 640 | val = val | (buf[i] << 24) | (i << 16); | ||
| 641 | flash_write(do_flash_cmd, flashaddr, val); | ||
| 642 | } | ||
| 643 | flashaddr += 2; | ||
| 644 | } | ||
| 645 | flashend = flashaddr; | ||
| 646 | if ((flashaddr & 2)) { | ||
| 647 | val |= 0xFFFF0000; | ||
| 648 | flash_write(do_flash_cmd, flashaddr, val); | ||
| 649 | } | ||
| 650 | } | ||
| 651 | } | ||
| 652 | |||
| 653 | /* | ||
| 654 | void do_flash_cmd(volatile uint8_t *fstat) | ||
| 655 | { | ||
| 656 | *fstat = 0x80; | ||
| 657 | while ((*fstat & 0x80) == 0) ; // wait | ||
| 658 | } | ||
| 659 | 00000000 <do_flash_cmd>: | ||
| 660 | 0: 2380 movs r3, #128 ; 0x80 | ||
| 661 | 2: 7003 strb r3, [r0, #0] | ||
| 662 | 4: 7803 ldrb r3, [r0, #0] | ||
| 663 | 6: b25b sxtb r3, r3 | ||
| 664 | 8: 2b00 cmp r3, #0 | ||
| 665 | a: dafb bge.n 4 <do_flash_cmd+0x4> | ||
| 666 | c: 4770 bx lr | ||
| 667 | */ | ||
| 668 | |||
| 669 | uint16_t eeprom_read_word(const uint16_t *addr) { | ||
| 670 | const uint8_t *p = (const uint8_t *)addr; | ||
| 671 | return eeprom_read_byte(p) | (eeprom_read_byte(p + 1) << 8); | ||
| 672 | } | ||
| 673 | |||
| 674 | uint32_t eeprom_read_dword(const uint32_t *addr) { | ||
| 675 | const uint8_t *p = (const uint8_t *)addr; | ||
| 676 | return eeprom_read_byte(p) | (eeprom_read_byte(p + 1) << 8) | (eeprom_read_byte(p + 2) << 16) | (eeprom_read_byte(p + 3) << 24); | ||
| 677 | } | ||
| 678 | |||
| 679 | void eeprom_read_block(void *buf, const void *addr, uint32_t len) { | ||
| 680 | const uint8_t *p = (const uint8_t *)addr; | ||
| 681 | uint8_t * dest = (uint8_t *)buf; | ||
| 682 | while (len--) { | ||
| 683 | *dest++ = eeprom_read_byte(p++); | ||
| 684 | } | ||
| 685 | } | ||
| 686 | |||
| 687 | int eeprom_is_ready(void) { return 1; } | ||
| 688 | |||
| 689 | void eeprom_write_word(uint16_t *addr, uint16_t value) { | ||
| 690 | uint8_t *p = (uint8_t *)addr; | ||
| 691 | eeprom_write_byte(p++, value); | ||
| 692 | eeprom_write_byte(p, value >> 8); | ||
| 693 | } | ||
| 694 | |||
| 695 | void eeprom_write_dword(uint32_t *addr, uint32_t value) { | ||
| 696 | uint8_t *p = (uint8_t *)addr; | ||
| 697 | eeprom_write_byte(p++, value); | ||
| 698 | eeprom_write_byte(p++, value >> 8); | ||
| 699 | eeprom_write_byte(p++, value >> 16); | ||
| 700 | eeprom_write_byte(p, value >> 24); | ||
| 701 | } | ||
| 702 | |||
| 703 | void eeprom_write_block(const void *buf, void *addr, uint32_t len) { | ||
| 704 | uint8_t * p = (uint8_t *)addr; | ||
| 705 | const uint8_t *src = (const uint8_t *)buf; | ||
| 706 | while (len--) { | ||
| 707 | eeprom_write_byte(p++, *src++); | ||
| 708 | } | ||
| 709 | } | ||
| 710 | |||
| 711 | #else | ||
| 712 | // No EEPROM supported, so emulate it | ||
| 713 | |||
| 714 | # ifndef EEPROM_SIZE | ||
| 715 | # include "eeconfig.h" | ||
| 716 | # define EEPROM_SIZE (((EECONFIG_SIZE + 3) / 4) * 4) // based off eeconfig's current usage, aligned to 4-byte sizes, to deal with LTO | ||
| 717 | # endif | ||
| 718 | __attribute__((aligned(4))) static uint8_t buffer[EEPROM_SIZE]; | ||
| 719 | |||
| 720 | uint8_t eeprom_read_byte(const uint8_t *addr) { | ||
| 721 | uint32_t offset = (uint32_t)addr; | ||
| 722 | return buffer[offset]; | ||
| 723 | } | ||
| 724 | |||
| 725 | void eeprom_write_byte(uint8_t *addr, uint8_t value) { | ||
| 726 | uint32_t offset = (uint32_t)addr; | ||
| 727 | buffer[offset] = value; | ||
| 728 | } | ||
| 729 | |||
| 730 | uint16_t eeprom_read_word(const uint16_t *addr) { | ||
| 731 | const uint8_t *p = (const uint8_t *)addr; | ||
| 732 | return eeprom_read_byte(p) | (eeprom_read_byte(p + 1) << 8); | ||
| 733 | } | ||
| 734 | |||
| 735 | uint32_t eeprom_read_dword(const uint32_t *addr) { | ||
| 736 | const uint8_t *p = (const uint8_t *)addr; | ||
| 737 | return eeprom_read_byte(p) | (eeprom_read_byte(p + 1) << 8) | (eeprom_read_byte(p + 2) << 16) | (eeprom_read_byte(p + 3) << 24); | ||
| 738 | } | ||
| 739 | |||
| 740 | void eeprom_read_block(void *buf, const void *addr, size_t len) { | ||
| 741 | const uint8_t *p = (const uint8_t *)addr; | ||
| 742 | uint8_t * dest = (uint8_t *)buf; | ||
| 743 | while (len--) { | ||
| 744 | *dest++ = eeprom_read_byte(p++); | ||
| 745 | } | ||
| 746 | } | ||
| 747 | |||
| 748 | void eeprom_write_word(uint16_t *addr, uint16_t value) { | ||
| 749 | uint8_t *p = (uint8_t *)addr; | ||
| 750 | eeprom_write_byte(p++, value); | ||
| 751 | eeprom_write_byte(p, value >> 8); | ||
| 752 | } | ||
| 753 | |||
| 754 | void eeprom_write_dword(uint32_t *addr, uint32_t value) { | ||
| 755 | uint8_t *p = (uint8_t *)addr; | ||
| 756 | eeprom_write_byte(p++, value); | ||
| 757 | eeprom_write_byte(p++, value >> 8); | ||
| 758 | eeprom_write_byte(p++, value >> 16); | ||
| 759 | eeprom_write_byte(p, value >> 24); | ||
| 760 | } | ||
| 761 | |||
| 762 | void eeprom_write_block(const void *buf, void *addr, size_t len) { | ||
| 763 | uint8_t * p = (uint8_t *)addr; | ||
| 764 | const uint8_t *src = (const uint8_t *)buf; | ||
| 765 | while (len--) { | ||
| 766 | eeprom_write_byte(p++, *src++); | ||
| 767 | } | ||
| 768 | } | ||
| 769 | |||
| 770 | #endif /* chip selection */ | ||
| 771 | // The update functions just calls write for now, but could probably be optimized | ||
| 772 | |||
| 773 | void eeprom_update_byte(uint8_t *addr, uint8_t value) { eeprom_write_byte(addr, value); } | ||
| 774 | |||
| 775 | void eeprom_update_word(uint16_t *addr, uint16_t value) { | ||
| 776 | uint8_t *p = (uint8_t *)addr; | ||
| 777 | eeprom_write_byte(p++, value); | ||
| 778 | eeprom_write_byte(p, value >> 8); | ||
| 779 | } | ||
| 780 | |||
| 781 | void eeprom_update_dword(uint32_t *addr, uint32_t value) { | ||
| 782 | uint8_t *p = (uint8_t *)addr; | ||
| 783 | eeprom_write_byte(p++, value); | ||
| 784 | eeprom_write_byte(p++, value >> 8); | ||
| 785 | eeprom_write_byte(p++, value >> 16); | ||
| 786 | eeprom_write_byte(p, value >> 24); | ||
| 787 | } | ||
| 788 | |||
| 789 | void eeprom_update_block(const void *buf, void *addr, size_t len) { | ||
| 790 | uint8_t * p = (uint8_t *)addr; | ||
| 791 | const uint8_t *src = (const uint8_t *)buf; | ||
| 792 | while (len--) { | ||
| 793 | eeprom_write_byte(p++, *src++); | ||
| 794 | } | ||
| 795 | } | ||
