eeprom_i2c.c (5194B)
1 /* Copyright 2019 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 #if defined(EXTERNAL_EEPROM_WP_PIN) 20 # include "gpio.h" 21 #endif 22 23 /* 24 Note that the implementations of eeprom_XXXX_YYYY on AVR are normally 25 provided by avr-libc. The same functions are reimplemented below and are 26 rerouted to the external i2c equivalent. 27 28 Seemingly, as this is compiled from within QMK, the object file generated 29 during the build overrides the avr-libc implementation during the linking 30 stage. 31 32 On other platforms such as ARM, there are no provided implementations, so 33 there is nothing to override during linkage. 34 */ 35 36 #include "wait.h" 37 #include "i2c_master.h" 38 #include "eeprom.h" 39 #include "eeprom_driver.h" 40 #include "eeprom_i2c.h" 41 42 // #define DEBUG_EEPROM_OUTPUT 43 44 #if defined(CONSOLE_ENABLE) && defined(DEBUG_EEPROM_OUTPUT) 45 # include "timer.h" 46 # include "debug.h" 47 #endif // DEBUG_EEPROM_OUTPUT 48 49 static inline void fill_target_address(uint8_t *buffer, const void *addr) { 50 uintptr_t p = (uintptr_t)addr; 51 for (int i = 0; i < EXTERNAL_EEPROM_ADDRESS_SIZE; ++i) { 52 buffer[EXTERNAL_EEPROM_ADDRESS_SIZE - 1 - i] = p & 0xFF; 53 p >>= 8; 54 } 55 } 56 57 void eeprom_driver_init(void) { 58 i2c_init(); 59 #if defined(EXTERNAL_EEPROM_WP_PIN) 60 /* We are setting the WP pin to high in a way that requires at least two bit-flips to change back to 0 */ 61 gpio_write_pin(EXTERNAL_EEPROM_WP_PIN, 1); 62 gpio_set_pin_input_high(EXTERNAL_EEPROM_WP_PIN); 63 #endif 64 } 65 66 void eeprom_driver_format(bool erase) { 67 /* i2c eeproms do not need to be formatted before use */ 68 if (erase) { 69 eeprom_driver_erase(); 70 } 71 } 72 73 void eeprom_driver_erase(void) { 74 #if defined(CONSOLE_ENABLE) && defined(DEBUG_EEPROM_OUTPUT) 75 uint32_t start = timer_read32(); 76 #endif 77 78 uint8_t buf[EXTERNAL_EEPROM_PAGE_SIZE]; 79 memset(buf, 0x00, EXTERNAL_EEPROM_PAGE_SIZE); 80 for (uint32_t addr = 0; addr < EXTERNAL_EEPROM_BYTE_COUNT; addr += EXTERNAL_EEPROM_PAGE_SIZE) { 81 eeprom_write_block(buf, (void *)(uintptr_t)addr, EXTERNAL_EEPROM_PAGE_SIZE); 82 } 83 84 #if defined(CONSOLE_ENABLE) && defined(DEBUG_EEPROM_OUTPUT) 85 dprintf("EEPROM erase took %ldms to complete\n", ((long)(timer_read32() - start))); 86 #endif 87 } 88 89 void eeprom_read_block(void *buf, const void *addr, size_t len) { 90 uint8_t complete_packet[EXTERNAL_EEPROM_ADDRESS_SIZE]; 91 fill_target_address(complete_packet, addr); 92 93 i2c_transmit(EXTERNAL_EEPROM_I2C_ADDRESS((uintptr_t)addr), complete_packet, EXTERNAL_EEPROM_ADDRESS_SIZE, 100); 94 i2c_receive(EXTERNAL_EEPROM_I2C_ADDRESS((uintptr_t)addr), buf, len, 100); 95 96 #if defined(CONSOLE_ENABLE) && defined(DEBUG_EEPROM_OUTPUT) 97 dprintf("[EEPROM R] 0x%04X: ", ((int)addr)); 98 for (size_t i = 0; i < len; ++i) { 99 dprintf(" %02X", (int)(((uint8_t *)buf)[i])); 100 } 101 dprintf("\n"); 102 #endif // DEBUG_EEPROM_OUTPUT 103 } 104 105 void eeprom_write_block(const void *buf, void *addr, size_t len) { 106 uint8_t complete_packet[EXTERNAL_EEPROM_ADDRESS_SIZE + EXTERNAL_EEPROM_PAGE_SIZE]; 107 uint8_t *read_buf = (uint8_t *)buf; 108 uintptr_t target_addr = (uintptr_t)addr; 109 110 #if defined(EXTERNAL_EEPROM_WP_PIN) 111 gpio_set_pin_output(EXTERNAL_EEPROM_WP_PIN); 112 gpio_write_pin(EXTERNAL_EEPROM_WP_PIN, 0); 113 #endif 114 115 while (len > 0) { 116 uintptr_t page_offset = target_addr % EXTERNAL_EEPROM_PAGE_SIZE; 117 int write_length = EXTERNAL_EEPROM_PAGE_SIZE - page_offset; 118 if (write_length > len) { 119 write_length = len; 120 } 121 122 fill_target_address(complete_packet, (const void *)target_addr); 123 for (uint8_t i = 0; i < write_length; i++) { 124 complete_packet[EXTERNAL_EEPROM_ADDRESS_SIZE + i] = read_buf[i]; 125 } 126 127 #if defined(CONSOLE_ENABLE) && defined(DEBUG_EEPROM_OUTPUT) 128 dprintf("[EEPROM W] 0x%04X: ", ((int)target_addr)); 129 for (uint8_t i = 0; i < write_length; i++) { 130 dprintf(" %02X", (int)(read_buf[i])); 131 } 132 dprintf("\n"); 133 #endif // DEBUG_EEPROM_OUTPUT 134 135 i2c_transmit(EXTERNAL_EEPROM_I2C_ADDRESS((uintptr_t)addr), complete_packet, EXTERNAL_EEPROM_ADDRESS_SIZE + write_length, 100); 136 wait_ms(EXTERNAL_EEPROM_WRITE_TIME); 137 138 read_buf += write_length; 139 target_addr += write_length; 140 len -= write_length; 141 } 142 143 #if defined(EXTERNAL_EEPROM_WP_PIN) 144 /* We are setting the WP pin to high in a way that requires at least two bit-flips to change back to 0 */ 145 gpio_write_pin(EXTERNAL_EEPROM_WP_PIN, 1); 146 gpio_set_pin_input_high(EXTERNAL_EEPROM_WP_PIN); 147 #endif 148 }