qmk_firmware

QMK firmware for my keyboards (Corne, Sweep Ferris) and trackball (Ploopy Adept)
Log | Files | Refs | Submodules | LICENSE

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 }