Parent directory

eeprom_i2c.c

5194 bytes
  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
 49static 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
 57void 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
 66void 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
 73void 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
 89void 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
105void 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}