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}