eeprom_spi.c (6777B)
1 /* Copyright 2020 Nick Brassel (tzarc) 2 * 3 * This program is free software: you can redistribute it and/or modify 4 * it under the terms of the GNU General Public License as published by 5 * the Free Software Foundation, either version 2 of the License, or 6 * (at your option) any later version. 7 * 8 * This program is distributed in the hope that it will be useful, 9 * but WITHOUT ANY WARRANTY; without even the implied warranty of 10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 * GNU General Public License for more details. 12 * 13 * You should have received a copy of the GNU General Public License 14 * along with this program. If not, see <http://www.gnu.org/licenses/>. 15 */ 16 17 #include <stdint.h> 18 #include <string.h> 19 20 /* 21 Note that the implementations of eeprom_XXXX_YYYY on AVR are normally 22 provided by avr-libc. The same functions are reimplemented below and are 23 rerouted to the external SPI equivalent. 24 25 Seemingly, as this is compiled from within QMK, the object file generated 26 during the build overrides the avr-libc implementation during the linking 27 stage. 28 29 On other platforms such as ARM, there are no provided implementations, so 30 there is nothing to override during linkage. 31 */ 32 33 #include "wait.h" 34 #include "debug.h" 35 #include "timer.h" 36 #include "spi_master.h" 37 #include "eeprom.h" 38 #include "eeprom_driver.h" 39 #include "eeprom_spi.h" 40 41 #define CMD_WREN 6 42 #define CMD_WRDI 4 43 #define CMD_RDSR 5 44 #define CMD_WRSR 1 45 #define CMD_READ 3 46 #define CMD_WRITE 2 47 48 #define SR_WIP 0x01 49 50 // #define DEBUG_EEPROM_OUTPUT 51 52 #ifndef EXTERNAL_EEPROM_SPI_TIMEOUT 53 # define EXTERNAL_EEPROM_SPI_TIMEOUT 100 54 #endif 55 56 static bool spi_eeprom_start(void) { 57 return spi_start(EXTERNAL_EEPROM_SPI_SLAVE_SELECT_PIN, EXTERNAL_EEPROM_SPI_LSBFIRST, EXTERNAL_EEPROM_SPI_MODE, EXTERNAL_EEPROM_SPI_CLOCK_DIVISOR); 58 } 59 60 static spi_status_t spi_eeprom_wait_while_busy(int timeout) { 61 uint32_t deadline = timer_read32() + timeout; 62 spi_status_t response = SR_WIP; 63 while (response & SR_WIP) { 64 if (!spi_eeprom_start()) { 65 return SPI_STATUS_ERROR; 66 } 67 68 spi_write(CMD_RDSR); 69 response = spi_read(); 70 spi_stop(); 71 72 if (timer_read32() >= deadline) { 73 return SPI_STATUS_TIMEOUT; 74 } 75 } 76 return SPI_STATUS_SUCCESS; 77 } 78 79 static void spi_eeprom_transmit_address(uintptr_t addr) { 80 uint8_t buffer[EXTERNAL_EEPROM_ADDRESS_SIZE]; 81 82 for (int i = 0; i < EXTERNAL_EEPROM_ADDRESS_SIZE; ++i) { 83 buffer[EXTERNAL_EEPROM_ADDRESS_SIZE - 1 - i] = addr & 0xFF; 84 addr >>= 8; 85 } 86 87 spi_transmit(buffer, EXTERNAL_EEPROM_ADDRESS_SIZE); 88 } 89 90 //---------------------------------------------------------------------------------------------------------------------- 91 92 void eeprom_driver_init(void) { 93 spi_init(); 94 } 95 96 void eeprom_driver_format(bool erase) { 97 /* spi eeproms do not need to be formatted before use */ 98 if (erase) { 99 eeprom_driver_erase(); 100 } 101 } 102 103 void eeprom_driver_erase(void) { 104 #if defined(CONSOLE_ENABLE) && defined(DEBUG_EEPROM_OUTPUT) 105 uint32_t start = timer_read32(); 106 #endif 107 108 uint8_t buf[EXTERNAL_EEPROM_PAGE_SIZE]; 109 memset(buf, 0x00, EXTERNAL_EEPROM_PAGE_SIZE); 110 for (uint32_t addr = 0; addr < EXTERNAL_EEPROM_BYTE_COUNT; addr += EXTERNAL_EEPROM_PAGE_SIZE) { 111 eeprom_write_block(buf, (void *)(uintptr_t)addr, EXTERNAL_EEPROM_PAGE_SIZE); 112 } 113 114 #if defined(CONSOLE_ENABLE) && defined(DEBUG_EEPROM_OUTPUT) 115 dprintf("EEPROM erase took %ldms to complete\n", ((long)(timer_read32() - start))); 116 #endif 117 } 118 119 void eeprom_read_block(void *buf, const void *addr, size_t len) { 120 //------------------------------------------------- 121 // Wait for the write-in-progress bit to be cleared 122 spi_status_t response = spi_eeprom_wait_while_busy(EXTERNAL_EEPROM_SPI_TIMEOUT); 123 if (response != SPI_STATUS_SUCCESS) { 124 spi_stop(); 125 memset(buf, 0, len); 126 dprint("SPI timeout for WIP check\n"); 127 return; 128 } 129 130 //------------------------------------------------- 131 // Perform read 132 bool res = spi_eeprom_start(); 133 if (!res) { 134 spi_stop(); 135 memset(buf, 0, len); 136 dprint("failed to start SPI for read\n"); 137 return; 138 } 139 140 spi_write(CMD_READ); 141 spi_eeprom_transmit_address((uintptr_t)addr); 142 spi_receive(buf, len); 143 144 #if defined(CONSOLE_ENABLE) && defined(DEBUG_EEPROM_OUTPUT) 145 dprintf("[EEPROM R] 0x%08lX: ", ((uint32_t)(uintptr_t)addr)); 146 for (size_t i = 0; i < len; ++i) { 147 dprintf(" %02X", (int)(((uint8_t *)buf)[i])); 148 } 149 dprintf("\n"); 150 #endif // DEBUG_EEPROM_OUTPUT 151 152 spi_stop(); 153 } 154 155 void eeprom_write_block(const void *buf, void *addr, size_t len) { 156 bool res; 157 uint8_t *read_buf = (uint8_t *)buf; 158 uintptr_t target_addr = (uintptr_t)addr; 159 160 while (len > 0) { 161 uintptr_t page_offset = target_addr % EXTERNAL_EEPROM_PAGE_SIZE; 162 int write_length = EXTERNAL_EEPROM_PAGE_SIZE - page_offset; 163 if (write_length > len) { 164 write_length = len; 165 } 166 167 //------------------------------------------------- 168 // Wait for the write-in-progress bit to be cleared 169 spi_status_t response = spi_eeprom_wait_while_busy(EXTERNAL_EEPROM_SPI_TIMEOUT); 170 if (response != SPI_STATUS_SUCCESS) { 171 spi_stop(); 172 dprint("SPI timeout for WIP check\n"); 173 return; 174 } 175 176 //------------------------------------------------- 177 // Enable writes 178 res = spi_eeprom_start(); 179 if (!res) { 180 spi_stop(); 181 dprint("failed to start SPI for write-enable\n"); 182 return; 183 } 184 185 spi_write(CMD_WREN); 186 spi_stop(); 187 188 //------------------------------------------------- 189 // Perform the write 190 res = spi_eeprom_start(); 191 if (!res) { 192 spi_stop(); 193 dprint("failed to start SPI for write\n"); 194 return; 195 } 196 197 #if defined(CONSOLE_ENABLE) && defined(DEBUG_EEPROM_OUTPUT) 198 dprintf("[EEPROM W] 0x%08lX: ", ((uint32_t)(uintptr_t)target_addr)); 199 for (size_t i = 0; i < write_length; i++) { 200 dprintf(" %02X", (int)(uint8_t)(read_buf[i])); 201 } 202 dprintf("\n"); 203 #endif // DEBUG_EEPROM_OUTPUT 204 205 spi_write(CMD_WRITE); 206 spi_eeprom_transmit_address(target_addr); 207 spi_transmit(read_buf, write_length); 208 spi_stop(); 209 210 read_buf += write_length; 211 target_addr += write_length; 212 len -= write_length; 213 } 214 215 //------------------------------------------------- 216 // Disable writes 217 res = spi_eeprom_start(); 218 if (!res) { 219 dprint("failed to start SPI for write-disable\n"); 220 return; 221 } 222 223 spi_write(CMD_WRDI); 224 spi_stop(); 225 }