1 /*
2     This code demonstrates how to use the SPI master half duplex mode to read/write a AT932C46D
3     EEPROM (8-bit mode).
4 
5    This example code is in the Public Domain (or CC0 licensed, at your option.)
6 
7    Unless required by applicable law or agreed to in writing, this
8    software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
9    CONDITIONS OF ANY KIND, either express or implied.
10 */
11 
12 #include "spi_eeprom.h"
13 #include "freertos/FreeRTOS.h"
14 #include "freertos/task.h"
15 #include "freertos/semphr.h"
16 #include "driver/gpio.h"
17 #include <unistd.h>
18 #include "esp_log.h"
19 #include <sys/param.h>
20 #include "sdkconfig.h"
21 
22 
23 #define EEPROM_BUSY_TIMEOUT_MS  5
24 
25 #define EEPROM_CLK_FREQ         (1*1000*1000)   //When powered by 3.3V, EEPROM max freq is 1MHz
26 #define EEPROM_INPUT_DELAY_NS   ((1000*1000*1000/EEPROM_CLK_FREQ)/2+20)
27 
28 #define ADDR_MASK   0x7f
29 
30 #define CMD_EWDS    0x200
31 #define CMD_WRAL    0x200
32 #define CMD_ERAL    0x200
33 #define CMD_EWEN    0x200
34 #define CMD_CKBS    0x000
35 #define CMD_READ    0x300
36 #define CMD_ERASE   0x380
37 #define CMD_WRITE   0x280
38 
39 #define ADD_EWDS    0x00
40 #define ADD_WRAL    0x20
41 #define ADD_ERAL    0x40
42 #define ADD_EWEN    0x60
43 
44 /// Context (config and data) of the spi_eeprom
45 struct eeprom_context_t{
46     eeprom_config_t cfg;        ///< Configuration by the caller.
47     spi_device_handle_t spi;    ///< SPI device handle
48     xSemaphoreHandle ready_sem; ///< Semaphore for ready signal
49 };
50 
51 typedef struct eeprom_context_t eeprom_context_t;
52 
53 static const char TAG[] = "eeprom";
54 
55 
56 // Workaround: The driver depends on some data in the flash and cannot be placed to DRAM easily for
57 // now. Using the version in LL instead.
58 #define gpio_set_level  gpio_set_level_patch
59 #include "hal/gpio_ll.h"
gpio_set_level_patch(gpio_num_t gpio_num,uint32_t level)60 static inline esp_err_t gpio_set_level_patch(gpio_num_t gpio_num, uint32_t level)
61 {
62     gpio_ll_set_level(&GPIO, gpio_num, level);
63     return ESP_OK;
64 }
65 
66 
eeprom_simple_cmd(eeprom_context_t * ctx,uint16_t cmd)67 static esp_err_t eeprom_simple_cmd(eeprom_context_t *ctx, uint16_t cmd)
68 {
69     spi_transaction_t t = {
70         .cmd = cmd,
71         .user = ctx
72     };
73     return spi_device_polling_transmit(ctx->spi, &t);
74 }
75 
eeprom_wait_done(eeprom_context_t * ctx)76 static esp_err_t eeprom_wait_done(eeprom_context_t* ctx)
77 {
78     //have to keep cs low for 250ns
79     usleep(1);
80     //clear signal
81     if (ctx->cfg.intr_used) {
82         xSemaphoreTake(ctx->ready_sem, 0);
83         gpio_set_level(ctx->cfg.cs_io, 1);
84         gpio_intr_enable(ctx->cfg.miso_io);
85 
86         //Max processing time is 5ms, tick=1 may happen very soon, set to 2 at least
87         uint32_t tick_to_wait = MAX(EEPROM_BUSY_TIMEOUT_MS / portTICK_PERIOD_MS, 2);
88         BaseType_t ret = xSemaphoreTake(ctx->ready_sem, tick_to_wait);
89         gpio_intr_disable(ctx->cfg.miso_io);
90         gpio_set_level(ctx->cfg.cs_io, 0);
91 
92         if (ret != pdTRUE) return ESP_ERR_TIMEOUT;
93     } else {
94         bool timeout = true;
95         gpio_set_level(ctx->cfg.cs_io, 1);
96         for (int i = 0; i < EEPROM_BUSY_TIMEOUT_MS * 1000; i ++) {
97             if (gpio_get_level(ctx->cfg.miso_io)) {
98                 timeout = false;
99                 break;
100             }
101             usleep(1);
102         }
103         gpio_set_level(ctx->cfg.cs_io, 0);
104         if (timeout) return ESP_ERR_TIMEOUT;
105     }
106     return ESP_OK;
107 }
108 
cs_high(spi_transaction_t * t)109 static void cs_high(spi_transaction_t* t)
110 {
111     ESP_EARLY_LOGV(TAG, "cs high %d.", ((eeprom_context_t*)t->user)->cfg.cs_io);
112     gpio_set_level(((eeprom_context_t*)t->user)->cfg.cs_io, 1);
113 }
114 
cs_low(spi_transaction_t * t)115 static void cs_low(spi_transaction_t* t)
116 {
117     gpio_set_level(((eeprom_context_t*)t->user)->cfg.cs_io, 0);
118     ESP_EARLY_LOGV(TAG, "cs low %d.", ((eeprom_context_t*)t->user)->cfg.cs_io);
119 }
120 
ready_rising_isr(void * arg)121 void ready_rising_isr(void* arg)
122 {
123     eeprom_context_t* ctx = (eeprom_context_t*)arg;
124     xSemaphoreGive(ctx->ready_sem);
125     ESP_EARLY_LOGV(TAG, "ready detected.");
126 }
127 
spi_eeprom_deinit(eeprom_context_t * ctx)128 esp_err_t spi_eeprom_deinit(eeprom_context_t* ctx)
129 {
130     spi_bus_remove_device(ctx->spi);
131     if (ctx->cfg.intr_used) {
132         vSemaphoreDelete(ctx->ready_sem);
133     }
134     free(ctx);
135     return ESP_OK;
136 }
137 
spi_eeprom_init(const eeprom_config_t * cfg,eeprom_context_t ** out_ctx)138 esp_err_t spi_eeprom_init(const eeprom_config_t *cfg, eeprom_context_t** out_ctx)
139 {
140     esp_err_t err = ESP_OK;
141     if (cfg->intr_used && cfg->host == SPI1_HOST) {
142         ESP_LOGE(TAG, "interrupt cannot be used on SPI1 host.");
143         return ESP_ERR_INVALID_ARG;
144     }
145 
146     eeprom_context_t* ctx = (eeprom_context_t*)malloc(sizeof(eeprom_context_t));
147     if (!ctx) return ESP_ERR_NO_MEM;
148 
149     *ctx = (eeprom_context_t) {
150         .cfg = *cfg,
151     };
152 
153     spi_device_interface_config_t devcfg={
154         .command_bits = 10,
155         .clock_speed_hz = EEPROM_CLK_FREQ,
156         .mode = 0,          //SPI mode 0
157         /*
158          * The timing requirements to read the busy signal from the EEPROM cannot be easily emulated
159          * by SPI transactions. We need to control CS pin by SW to check the busy signal manually.
160          */
161         .spics_io_num = -1,
162         .queue_size = 1,
163         .flags = SPI_DEVICE_HALFDUPLEX | SPI_DEVICE_POSITIVE_CS,
164         .pre_cb = cs_high,
165         .post_cb = cs_low,
166         .input_delay_ns = EEPROM_INPUT_DELAY_NS,  //the EEPROM output the data half a SPI clock behind.
167     };
168     //Attach the EEPROM to the SPI bus
169     err = spi_bus_add_device(ctx->cfg.host, &devcfg, &ctx->spi);
170     if  (err != ESP_OK) {
171         goto cleanup;
172     }
173 
174     gpio_set_level(ctx->cfg.cs_io, 0);
175     gpio_config_t cs_cfg = {
176         .pin_bit_mask = BIT64(ctx->cfg.cs_io),
177         .mode = GPIO_MODE_OUTPUT,
178     };
179     gpio_config(&cs_cfg);
180 
181     if (ctx->cfg.intr_used) {
182         ctx->ready_sem = xSemaphoreCreateBinary();
183         if (ctx->ready_sem == NULL) {
184             err = ESP_ERR_NO_MEM;
185             goto cleanup;
186         }
187 
188         gpio_set_intr_type(ctx->cfg.miso_io, GPIO_INTR_POSEDGE);
189         err = gpio_isr_handler_add(ctx->cfg.miso_io, ready_rising_isr, ctx);
190         if (err != ESP_OK) {
191             goto cleanup;
192         }
193         gpio_intr_disable(ctx->cfg.miso_io);
194     }
195     *out_ctx = ctx;
196     return ESP_OK;
197 
198 cleanup:
199     if (ctx->spi) {
200         spi_bus_remove_device(ctx->spi);
201         ctx->spi = NULL;
202     }
203     if (ctx->ready_sem) {
204         vSemaphoreDelete(ctx->ready_sem);
205         ctx->ready_sem = NULL;
206     }
207     free(ctx);
208     return err;
209 }
210 
spi_eeprom_read(eeprom_context_t * ctx,uint8_t addr,uint8_t * out_data)211 esp_err_t spi_eeprom_read(eeprom_context_t* ctx, uint8_t addr, uint8_t* out_data)
212 {
213     spi_transaction_t t = {
214         .cmd = CMD_READ | (addr & ADDR_MASK),
215         .rxlength = 8,
216         .flags = SPI_TRANS_USE_RXDATA,
217         .user = ctx,
218     };
219     esp_err_t err = spi_device_polling_transmit(ctx->spi, &t);
220     if (err!= ESP_OK) return err;
221 
222     *out_data = t.rx_data[0];
223     return ESP_OK;
224 }
225 
spi_eeprom_erase(eeprom_context_t * ctx,uint8_t addr)226 esp_err_t spi_eeprom_erase(eeprom_context_t* ctx, uint8_t addr)
227 {
228     esp_err_t err;
229     err = spi_device_acquire_bus(ctx->spi, portMAX_DELAY);
230     if (err != ESP_OK) return err;
231 
232     err = eeprom_simple_cmd(ctx, CMD_ERASE | (addr & ADDR_MASK));
233 
234     if (err == ESP_OK) {
235         err = eeprom_wait_done(ctx);
236     }
237 
238     spi_device_release_bus(ctx->spi);
239     return err;
240 }
241 
spi_eeprom_write(eeprom_context_t * ctx,uint8_t addr,uint8_t data)242 esp_err_t spi_eeprom_write(eeprom_context_t* ctx, uint8_t addr, uint8_t data)
243 {
244     esp_err_t err;
245     err = spi_device_acquire_bus(ctx->spi, portMAX_DELAY);
246     if (err != ESP_OK) return err;
247 
248     spi_transaction_t t = {
249         .cmd = CMD_WRITE | (addr & ADDR_MASK),
250         .length = 8,
251         .flags = SPI_TRANS_USE_TXDATA,
252         .tx_data = {data},
253         .user = ctx,
254     };
255     err = spi_device_polling_transmit(ctx->spi, &t);
256 
257     if (err == ESP_OK) {
258         err = eeprom_wait_done(ctx);
259     }
260 
261     spi_device_release_bus(ctx->spi);
262     return err;
263 }
264 
spi_eeprom_write_enable(eeprom_context_t * ctx)265 esp_err_t spi_eeprom_write_enable(eeprom_context_t* ctx)
266 {
267     return eeprom_simple_cmd(ctx, CMD_EWEN | ADD_EWEN);
268 }
269 
spi_eeprom_write_disable(eeprom_context_t * ctx)270 esp_err_t spi_eeprom_write_disable(eeprom_context_t* ctx)
271 {
272     return eeprom_simple_cmd(ctx, CMD_EWDS | ADD_EWDS);
273 }
274 
spi_eeprom_erase_all(eeprom_context_t * ctx)275 esp_err_t spi_eeprom_erase_all(eeprom_context_t* ctx)
276 {
277 #if !CONFIG_EXAMPLE_5V_COMMANDS
278     //not supported in 3.3V VCC
279     ESP_LOGE(TAG, "erase all not supported by EEPROM under 3.3V VCC");
280     return ESP_ERR_NOT_SUPPORTED;
281 #endif
282 
283     esp_err_t err;
284     err = spi_device_acquire_bus(ctx->spi, portMAX_DELAY);
285     if (err != ESP_OK) return err;
286 
287     err = eeprom_simple_cmd(ctx, CMD_ERAL | ADD_ERAL);
288 
289     if (err == ESP_OK) {
290         err = eeprom_wait_done(ctx);
291     }
292 
293     spi_device_release_bus(ctx->spi);
294     return err;
295 }
296 
spi_eeprom_write_all(eeprom_context_t * ctx,uint8_t data)297 esp_err_t spi_eeprom_write_all(eeprom_context_t* ctx, uint8_t data)
298 {
299 #if !CONFIG_EXAMPLE_5V_COMMANDS
300     //not supported in 3.3V VCC
301     ESP_LOGE(TAG, "write all not supported by EEPROM under 3.3V VCC");
302     return ESP_ERR_NOT_SUPPORTED;
303 #endif
304 
305     esp_err_t err;
306     err = spi_device_acquire_bus(ctx->spi, portMAX_DELAY);
307     if (err != ESP_OK) return err;
308 
309     spi_transaction_t t = {
310         .cmd = CMD_WRAL | ADD_WRAL,
311         .length = 8,
312         .flags = SPI_TRANS_USE_TXDATA,
313         .tx_data = {data},
314         .user = ctx,
315     };
316     err = spi_device_polling_transmit(ctx->spi, &t);
317 
318     if (err == ESP_OK) {
319         err = eeprom_wait_done(ctx);
320     }
321 
322     spi_device_release_bus(ctx->spi);
323     return err;
324 }
325