aboutsummaryrefslogtreecommitdiff
path: root/drivers/eeprom
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
parent4604c70c4c1a0cb2cebc010dba34b04e902bd982 (diff)
downloadqmk_firmware-54b04d96651a166088477ccf513fa534b30b2132.tar.gz
qmk_firmware-54b04d96651a166088477ccf513fa534b30b2132.zip
Add SPI 25xx EEPROM support. (#8780)
Diffstat (limited to 'drivers/eeprom')
-rw-r--r--drivers/eeprom/eeprom_driver.c6
-rw-r--r--drivers/eeprom/eeprom_i2c.c30
-rw-r--r--drivers/eeprom/eeprom_spi.c231
-rw-r--r--drivers/eeprom/eeprom_spi.h80
4 files changed, 333 insertions, 14 deletions
diff --git a/drivers/eeprom/eeprom_driver.c b/drivers/eeprom/eeprom_driver.c
index 3835e5e9d..6ce47faf7 100644
--- a/drivers/eeprom/eeprom_driver.c
+++ b/drivers/eeprom/eeprom_driver.c
@@ -20,19 +20,19 @@
20#include "eeprom_driver.h" 20#include "eeprom_driver.h"
21 21
22uint8_t eeprom_read_byte(const uint8_t *addr) { 22uint8_t eeprom_read_byte(const uint8_t *addr) {
23 uint8_t ret; 23 uint8_t ret = 0;
24 eeprom_read_block(&ret, addr, 1); 24 eeprom_read_block(&ret, addr, 1);
25 return ret; 25 return ret;
26} 26}
27 27
28uint16_t eeprom_read_word(const uint16_t *addr) { 28uint16_t eeprom_read_word(const uint16_t *addr) {
29 uint16_t ret; 29 uint16_t ret = 0;
30 eeprom_read_block(&ret, addr, 2); 30 eeprom_read_block(&ret, addr, 2);
31 return ret; 31 return ret;
32} 32}
33 33
34uint32_t eeprom_read_dword(const uint32_t *addr) { 34uint32_t eeprom_read_dword(const uint32_t *addr) {
35 uint32_t ret; 35 uint32_t ret = 0;
36 eeprom_read_block(&ret, addr, 4); 36 eeprom_read_block(&ret, addr, 4);
37 return ret; 37 return ret;
38} 38}
diff --git a/drivers/eeprom/eeprom_i2c.c b/drivers/eeprom/eeprom_i2c.c
index 03dbc5e51..88be2be06 100644
--- a/drivers/eeprom/eeprom_i2c.c
+++ b/drivers/eeprom/eeprom_i2c.c
@@ -50,7 +50,7 @@ static inline void init_i2c_if_required(void) {
50} 50}
51 51
52static inline void fill_target_address(uint8_t *buffer, const void *addr) { 52static inline void fill_target_address(uint8_t *buffer, const void *addr) {
53 intptr_t p = (intptr_t)addr; 53 uintptr_t p = (uintptr_t)addr;
54 for (int i = 0; i < EXTERNAL_EEPROM_ADDRESS_SIZE; ++i) { 54 for (int i = 0; i < EXTERNAL_EEPROM_ADDRESS_SIZE; ++i) {
55 buffer[EXTERNAL_EEPROM_ADDRESS_SIZE - 1 - i] = p & 0xFF; 55 buffer[EXTERNAL_EEPROM_ADDRESS_SIZE - 1 - i] = p & 0xFF;
56 p >>= 8; 56 p >>= 8;
@@ -60,11 +60,19 @@ static inline void fill_target_address(uint8_t *buffer, const void *addr) {
60void eeprom_driver_init(void) {} 60void eeprom_driver_init(void) {}
61 61
62void eeprom_driver_erase(void) { 62void eeprom_driver_erase(void) {
63#ifdef CONSOLE_ENABLE
64 uint32_t start = timer_read32();
65#endif
66
63 uint8_t buf[EXTERNAL_EEPROM_PAGE_SIZE]; 67 uint8_t buf[EXTERNAL_EEPROM_PAGE_SIZE];
64 memset(buf, 0x00, EXTERNAL_EEPROM_PAGE_SIZE); 68 memset(buf, 0x00, EXTERNAL_EEPROM_PAGE_SIZE);
65 for (intptr_t addr = 0; addr < EXTERNAL_EEPROM_BYTE_COUNT; addr += EXTERNAL_EEPROM_PAGE_SIZE) { 69 for (uint32_t addr = 0; addr < EXTERNAL_EEPROM_BYTE_COUNT; addr += EXTERNAL_EEPROM_PAGE_SIZE) {
66 eeprom_write_block(buf, (void *)addr, EXTERNAL_EEPROM_PAGE_SIZE); 70 eeprom_write_block(buf, (void *)(uintptr_t)addr, EXTERNAL_EEPROM_PAGE_SIZE);
67 } 71 }
72
73#ifdef CONSOLE_ENABLE
74 dprintf("EEPROM erase took %ldms to complete\n", ((long)(timer_read32() - start)));
75#endif
68} 76}
69 77
70void eeprom_read_block(void *buf, const void *addr, size_t len) { 78void eeprom_read_block(void *buf, const void *addr, size_t len) {
@@ -72,8 +80,8 @@ void eeprom_read_block(void *buf, const void *addr, size_t len) {
72 fill_target_address(complete_packet, addr); 80 fill_target_address(complete_packet, addr);
73 81
74 init_i2c_if_required(); 82 init_i2c_if_required();
75 i2c_transmit(EXTERNAL_EEPROM_I2C_ADDRESS((intptr_t)addr), complete_packet, EXTERNAL_EEPROM_ADDRESS_SIZE, 100); 83 i2c_transmit(EXTERNAL_EEPROM_I2C_ADDRESS((uintptr_t)addr), complete_packet, EXTERNAL_EEPROM_ADDRESS_SIZE, 100);
76 i2c_receive(EXTERNAL_EEPROM_I2C_ADDRESS((intptr_t)addr), buf, len, 100); 84 i2c_receive(EXTERNAL_EEPROM_I2C_ADDRESS((uintptr_t)addr), buf, len, 100);
77 85
78#ifdef DEBUG_EEPROM_OUTPUT 86#ifdef DEBUG_EEPROM_OUTPUT
79 dprintf("[EEPROM R] 0x%04X: ", ((int)addr)); 87 dprintf("[EEPROM R] 0x%04X: ", ((int)addr));
@@ -85,14 +93,14 @@ void eeprom_read_block(void *buf, const void *addr, size_t len) {
85} 93}
86 94
87void eeprom_write_block(const void *buf, void *addr, size_t len) { 95void eeprom_write_block(const void *buf, void *addr, size_t len) {
88 uint8_t complete_packet[EXTERNAL_EEPROM_ADDRESS_SIZE + EXTERNAL_EEPROM_PAGE_SIZE]; 96 uint8_t complete_packet[EXTERNAL_EEPROM_ADDRESS_SIZE + EXTERNAL_EEPROM_PAGE_SIZE];
89 uint8_t *read_buf = (uint8_t *)buf; 97 uint8_t * read_buf = (uint8_t *)buf;
90 intptr_t target_addr = (intptr_t)addr; 98 uintptr_t target_addr = (uintptr_t)addr;
91 99
92 init_i2c_if_required(); 100 init_i2c_if_required();
93 while (len > 0) { 101 while (len > 0) {
94 intptr_t page_offset = target_addr % EXTERNAL_EEPROM_PAGE_SIZE; 102 uintptr_t page_offset = target_addr % EXTERNAL_EEPROM_PAGE_SIZE;
95 int write_length = EXTERNAL_EEPROM_PAGE_SIZE - page_offset; 103 int write_length = EXTERNAL_EEPROM_PAGE_SIZE - page_offset;
96 if (write_length > len) { 104 if (write_length > len) {
97 write_length = len; 105 write_length = len;
98 } 106 }
@@ -110,7 +118,7 @@ void eeprom_write_block(const void *buf, void *addr, size_t len) {
110 dprintf("\n"); 118 dprintf("\n");
111#endif // DEBUG_EEPROM_OUTPUT 119#endif // DEBUG_EEPROM_OUTPUT
112 120
113 i2c_transmit(EXTERNAL_EEPROM_I2C_ADDRESS((intptr_t)addr), complete_packet, EXTERNAL_EEPROM_ADDRESS_SIZE + write_length, 100); 121 i2c_transmit(EXTERNAL_EEPROM_I2C_ADDRESS((uintptr_t)addr), complete_packet, EXTERNAL_EEPROM_ADDRESS_SIZE + write_length, 100);
114 wait_ms(EXTERNAL_EEPROM_WRITE_TIME); 122 wait_ms(EXTERNAL_EEPROM_WRITE_TIME);
115 123
116 read_buf += write_length; 124 read_buf += write_length;
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}
diff --git a/drivers/eeprom/eeprom_spi.h b/drivers/eeprom/eeprom_spi.h
new file mode 100644
index 000000000..282c60356
--- /dev/null
+++ b/drivers/eeprom/eeprom_spi.h
@@ -0,0 +1,80 @@
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#pragma once
18
19/*
20 The slave select pin of the EEPROM.
21 This needs to be a normal GPIO pin_t value, such as A7.
22*/
23#ifndef EXTERNAL_EEPROM_SPI_SLAVE_SELECT_PIN
24# error "No chip select pin defined -- missing EXTERNAL_EEPROM_SPI_SLAVE_SELECT_PIN"
25#endif
26
27/*
28 The clock divisor for SPI to ensure that the MCU is within the
29 specifications of the EEPROM chip. Generally this will be PCLK divided by
30 the intended divisor -- check your clock settings and the datasheet of
31 your EEPROM.
32*/
33#ifndef EXTERNAL_EEPROM_SPI_CLOCK_DIVISOR
34# ifdef __AVR__
35# define EXTERNAL_EEPROM_SPI_CLOCK_DIVISOR 8
36# else
37# define EXTERNAL_EEPROM_SPI_CLOCK_DIVISOR 64
38# endif
39#endif
40
41/*
42 The SPI mode to communicate with the EEPROM.
43*/
44#ifndef EXTERNAL_EEPROM_SPI_MODE
45# define EXTERNAL_EEPROM_SPI_MODE 0
46#endif
47
48/*
49 Whether or not the SPI communication between the MCU and EEPROM should be
50 LSB-first.
51*/
52#ifndef EXTERNAL_EEPROM_SPI_LSBFIRST
53# define EXTERNAL_EEPROM_SPI_LSBFIRST false
54#endif
55
56/*
57 The total size of the EEPROM, in bytes. The EEPROM datasheet will usually
58 specify this value in kbits, and will require conversion to bytes.
59*/
60#ifndef EXTERNAL_EEPROM_BYTE_COUNT
61# define EXTERNAL_EEPROM_BYTE_COUNT 8192
62#endif
63
64/*
65 The page size in bytes of the EEPROM, as specified in the datasheet.
66*/
67#ifndef EXTERNAL_EEPROM_PAGE_SIZE
68# define EXTERNAL_EEPROM_PAGE_SIZE 32
69#endif
70
71/*
72 The address size in bytes of the EEPROM. For EEPROMs with <=256 bytes, this
73 will likely be 1. For EEPROMs >256 and <=65536, this will be 2. For EEPROMs
74 >65536, this will likely need to be 4.
75
76 As expected, consult the datasheet for specifics of your EEPROM.
77*/
78#ifndef EXTERNAL_EEPROM_ADDRESS_SIZE
79# define EXTERNAL_EEPROM_ADDRESS_SIZE 2
80#endif