aboutsummaryrefslogtreecommitdiff
path: root/drivers/eeprom/eeprom_spi.c
diff options
context:
space:
mode:
authorNick Brassel <nick@tzarc.org>2020-05-19 10:34:00 +1000
committerGitHub <noreply@github.com>2020-05-19 10:34:00 +1000
commit54b04d96651a166088477ccf513fa534b30b2132 (patch)
treebace05e50e18069f269d6826800dd82dc2d8afbb /drivers/eeprom/eeprom_spi.c
parent4604c70c4c1a0cb2cebc010dba34b04e902bd982 (diff)
downloadqmk_firmware-54b04d96651a166088477ccf513fa534b30b2132.tar.gz
qmk_firmware-54b04d96651a166088477ccf513fa534b30b2132.zip
Add SPI 25xx EEPROM support. (#8780)
Diffstat (limited to 'drivers/eeprom/eeprom_spi.c')
-rw-r--r--drivers/eeprom/eeprom_spi.c231
1 files changed, 231 insertions, 0 deletions
diff --git a/drivers/eeprom/eeprom_spi.c b/drivers/eeprom/eeprom_spi.c
new file mode 100644
index 000000000..e10aaf14c
--- /dev/null
+++ b/drivers/eeprom/eeprom_spi.c
@@ -0,0 +1,231 @@
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 "spi_master.h"
35#include "eeprom.h"
36#include "eeprom_spi.h"
37
38#define CMD_WREN 6
39#define CMD_WRDI 4
40#define CMD_RDSR 5
41#define CMD_WRSR 1
42#define CMD_READ 3
43#define CMD_WRITE 2
44
45#define SR_WIP 0x01
46
47// #define DEBUG_EEPROM_OUTPUT
48
49#ifndef EXTERNAL_EEPROM_SPI_TIMEOUT
50# define EXTERNAL_EEPROM_SPI_TIMEOUT 100
51#endif
52
53#ifdef CONSOLE_ENABLE
54# include "print.h"
55#endif // CONSOLE_ENABLE
56
57static void init_spi_if_required(void) {
58 static int done = 0;
59 if (!done) {
60 spi_init();
61 done = 1;
62 }
63}
64
65static bool spi_eeprom_start(void) { return spi_start(EXTERNAL_EEPROM_SPI_SLAVE_SELECT_PIN, EXTERNAL_EEPROM_SPI_LSBFIRST, EXTERNAL_EEPROM_SPI_MODE, EXTERNAL_EEPROM_SPI_CLOCK_DIVISOR); }
66
67static spi_status_t spi_eeprom_wait_while_busy(int timeout) {
68 uint32_t deadline = timer_read32() + timeout;
69 spi_status_t response;
70 do {
71 spi_write(CMD_RDSR);
72 response = spi_read();
73 if (timer_read32() >= deadline) {
74 return SPI_STATUS_TIMEOUT;
75 }
76 } while (response & SR_WIP);
77 return SPI_STATUS_SUCCESS;
78}
79
80static void spi_eeprom_transmit_address(uintptr_t addr) {
81 uint8_t buffer[EXTERNAL_EEPROM_ADDRESS_SIZE];
82
83 for (int i = 0; i < EXTERNAL_EEPROM_ADDRESS_SIZE; ++i) {
84 buffer[EXTERNAL_EEPROM_ADDRESS_SIZE - 1 - i] = addr & 0xFF;
85 addr >>= 8;
86 }
87
88 spi_transmit(buffer, EXTERNAL_EEPROM_ADDRESS_SIZE);
89}
90
91//----------------------------------------------------------------------------------------------------------------------
92
93void eeprom_driver_init(void) {}
94
95void eeprom_driver_erase(void) {
96#ifdef CONSOLE_ENABLE
97 uint32_t start = timer_read32();
98#endif
99
100 uint8_t buf[EXTERNAL_EEPROM_PAGE_SIZE];
101 memset(buf, 0x00, EXTERNAL_EEPROM_PAGE_SIZE);
102 for (uint32_t addr = 0; addr < EXTERNAL_EEPROM_BYTE_COUNT; addr += EXTERNAL_EEPROM_PAGE_SIZE) {
103 eeprom_write_block(buf, (void *)(uintptr_t)addr, EXTERNAL_EEPROM_PAGE_SIZE);
104 }
105
106#ifdef CONSOLE_ENABLE
107 dprintf("EEPROM erase took %ldms to complete\n", ((long)(timer_read32() - start)));
108#endif
109}
110
111void eeprom_read_block(void *buf, const void *addr, size_t len) {
112 init_spi_if_required();
113
114 //-------------------------------------------------
115 // Wait for the write-in-progress bit to be cleared
116 bool res = spi_eeprom_start();
117 if (!res) {
118 dprint("failed to start SPI for WIP check\n");
119 memset(buf, 0, len);
120 return;
121 }
122
123 spi_status_t response = spi_eeprom_wait_while_busy(EXTERNAL_EEPROM_SPI_TIMEOUT);
124 spi_stop();
125 if (response == SPI_STATUS_TIMEOUT) {
126 dprint("SPI timeout for WIP check\n");
127 memset(buf, 0, len);
128 return;
129 }
130
131 //-------------------------------------------------
132 // Perform read
133 res = spi_eeprom_start();
134 if (!res) {
135 dprint("failed to start SPI for read\n");
136 memset(buf, 0, len);
137 return;
138 }
139
140 spi_write(CMD_READ);
141 spi_eeprom_transmit_address((uintptr_t)addr);
142 spi_receive(buf, len);
143
144#ifdef 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
155void eeprom_write_block(const void *buf, void *addr, size_t len) {
156 init_spi_if_required();
157
158 bool res;
159 uint8_t * read_buf = (uint8_t *)buf;
160 uintptr_t target_addr = (uintptr_t)addr;
161
162 while (len > 0) {
163 uintptr_t page_offset = target_addr % EXTERNAL_EEPROM_PAGE_SIZE;
164 int write_length = EXTERNAL_EEPROM_PAGE_SIZE - page_offset;
165 if (write_length > len) {
166 write_length = len;
167 }
168
169 //-------------------------------------------------
170 // Wait for the write-in-progress bit to be cleared
171 res = spi_eeprom_start();
172 if (!res) {
173 dprint("failed to start SPI for WIP check\n");
174 return;
175 }
176
177 spi_status_t response = spi_eeprom_wait_while_busy(EXTERNAL_EEPROM_SPI_TIMEOUT);
178 spi_stop();
179 if (response == SPI_STATUS_TIMEOUT) {
180 dprint("SPI timeout for WIP check\n");
181 return;
182 }
183
184 //-------------------------------------------------
185 // Enable writes
186 res = spi_eeprom_start();
187 if (!res) {
188 dprint("failed to start SPI for write-enable\n");
189 return;
190 }
191
192 spi_write(CMD_WREN);
193 spi_stop();
194
195 //-------------------------------------------------
196 // Perform the write
197 res = spi_eeprom_start();
198 if (!res) {
199 dprint("failed to start SPI for write\n");
200 return;
201 }
202
203#ifdef DEBUG_EEPROM_OUTPUT
204 dprintf("[EEPROM W] 0x%08lX: ", ((uint32_t)(uintptr_t)target_addr));
205 for (size_t i = 0; i < write_length; i++) {
206 dprintf(" %02X", (int)(uint8_t)(read_buf[i]));
207 }
208 dprintf("\n");
209#endif // DEBUG_EEPROM_OUTPUT
210
211 spi_write(CMD_WRITE);
212 spi_eeprom_transmit_address(target_addr);
213 spi_transmit(read_buf, write_length);
214 spi_stop();
215
216 read_buf += write_length;
217 target_addr += write_length;
218 len -= write_length;
219 }
220
221 //-------------------------------------------------
222 // Disable writes
223 res = spi_eeprom_start();
224 if (!res) {
225 dprint("failed to start SPI for write-disable\n");
226 return;
227 }
228
229 spi_write(CMD_WRDI);
230 spi_stop();
231}