Sat, 29 Jun 2024 16:09:03 +0200
Version 0.4.1 Fixed deprecation message. Ignore WiFi channel change event
0 | 1 | // Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD |
2 | // | |
3 | // Licensed under the Apache License, Version 2.0 (the "License"); | |
4 | // you may not use this file except in compliance with the License. | |
5 | // You may obtain a copy of the License at | |
6 | ||
7 | // http://www.apache.org/licenses/LICENSE-2.0 | |
8 | // | |
9 | // Unless required by applicable law or agreed to in writing, software | |
10 | // distributed under the License is distributed on an "AS IS" BASIS, | |
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
12 | // See the License for the specific language governing permissions and | |
13 | // limitations under the License. | |
14 | ||
15 | ||
16 | /* | |
17 | ---------------------------------------- | |
18 | Non DMA version of the spi_master driver | |
19 | ---------------------------------------- | |
20 | ------------------------------------------------------------------------------------ | |
21 | Based on esp-idf 'spi_master', modified by LoBo (https://github.com/loboris) 03/2017 | |
22 | ------------------------------------------------------------------------------------ | |
23 | ||
24 | * Transfers data to SPI device in direct mode, not using DMA | |
25 | * All configuration options (bus, device, transaction) are the same as in spi_master driver | |
26 | * Transfers uses the semaphore (taken in select function & given in deselect function) to protect the transfer | |
27 | * Number of the devices attached to the bus which uses hardware CS can be 3 ('NO_CS') | |
28 | * Additional devices which uses software CS can be attached to the bus, up to 'NO_DEV' | |
29 | * 'spi_bus_initialize' & 'spi_bus_remove' functions are removed, spi bus is initiated/removed in spi_lobo_bus_add_device/spi_lobo_bus_remove_device when needed | |
30 | * 'spi_lobo_bus_add_device' function has added parameter 'bus_config' and automatically initializes spi bus device if not already initialized | |
31 | * 'spi_lobo_bus_remove_device' automatically removes spi bus device if no other devices are attached to it. | |
32 | * Devices can have individual bus_configs, so different mosi, miso, sck pins can be configured for each device | |
33 | Reconfiguring the bus is done automaticaly in 'spi_lobo_device_select' function | |
34 | * 'spi_lobo_device_select' & 'spi_lobo_device_deselect' functions handles devices configuration changes and software CS | |
35 | * Some helper functions are added ('spi_lobo_get_speed', 'spi_lobo_set_speed', ...) | |
36 | * All structures are available in header file for easy creation of user low level spi functions. See **tftfunc.c** source for examples. | |
37 | * Transimt and receive lenghts are limited only by available memory | |
38 | ||
39 | ||
40 | Main driver's function is 'spi_lobo_transfer_data()' | |
41 | ||
42 | * TRANSMIT 8-bit data to spi device from 'trans->tx_buffer' or 'trans->tx_data' (trans->lenght/8 bytes) | |
43 | * and RECEIVE data to 'trans->rx_buffer' or 'trans->rx_data' (trans->rx_length/8 bytes) | |
44 | * Lengths must be 8-bit multiples! | |
45 | * If trans->rx_buffer is NULL or trans->rx_length is 0, only transmits data | |
46 | * If trans->tx_buffer is NULL or trans->length is 0, only receives data | |
47 | * If the device is in duplex mode (LB_SPI_DEVICE_HALFDUPLEX flag NOT set), data are transmitted and received simultaneously. | |
48 | * If the device is in half duplex mode (LB_SPI_DEVICE_HALFDUPLEX flag IS set), data are received after transmission | |
49 | * 'address', 'command' and 'dummy bits' are transmitted before data phase IF set in device's configuration | |
50 | * and IF 'trans->length' and 'trans->rx_length' are NOT both 0 | |
51 | * If configured, devices 'pre_cb' callback is called before and 'post_cb' after the transmission | |
52 | * If device was not previously selected, it will be selected before transmission and deselected after transmission. | |
53 | ||
54 | */ | |
55 | ||
56 | /* | |
57 | Replace this include with | |
58 | #include "driver/spi_master_lobo.h" | |
59 | if the driver is located in esp-isf/components | |
60 | */ | |
61 | #include "freertos/FreeRTOS.h" | |
62 | #include <string.h> | |
63 | #include <stdio.h> | |
39
e5900c9b9a7b
Use PROJECT_VER for version number. Updated README and info screen.
Michiel Broek <mbroek@mbse.eu>
parents:
1
diff
changeset
|
64 | #include "esp32/rom/ets_sys.h" |
0 | 65 | #include "esp_types.h" |
66 | #include "esp_attr.h" | |
67 | #include "esp_log.h" | |
68 | #include "esp_err.h" | |
69 | #include "freertos/semphr.h" | |
140
ac6ad7bd55c3
Version 0.4.1 Fixed deprecation message. Ignore WiFi channel change event
Michiel Broek <mbroek@mbse.eu>
parents:
129
diff
changeset
|
70 | #if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 2, 0) |
0 | 71 | #include "freertos/xtensa_api.h" |
140
ac6ad7bd55c3
Version 0.4.1 Fixed deprecation message. Ignore WiFi channel change event
Michiel Broek <mbroek@mbse.eu>
parents:
129
diff
changeset
|
72 | #else |
ac6ad7bd55c3
Version 0.4.1 Fixed deprecation message. Ignore WiFi channel change event
Michiel Broek <mbroek@mbse.eu>
parents:
129
diff
changeset
|
73 | #include <xtensa_api.h> |
ac6ad7bd55c3
Version 0.4.1 Fixed deprecation message. Ignore WiFi channel change event
Michiel Broek <mbroek@mbse.eu>
parents:
129
diff
changeset
|
74 | #endif |
0 | 75 | #include "freertos/task.h" |
76 | #include "driver/uart.h" | |
77 | #include "driver/gpio.h" | |
129 | 78 | #include "esp_private/periph_ctrl.h" |
0 | 79 | #include "esp_heap_caps.h" |
80 | #include "spi_master_lobo.h" | |
81 | ||
129 | 82 | #include "rom/gpio.h" |
83 | #include "soc/soc.h" | |
84 | #include "soc/io_mux_reg.h" | |
85 | #include "soc/gpio_periph.h" | |
86 | #include "soc/periph_defs.h" | |
87 | #include "soc/dport_reg.h" | |
88 | #include "soc/spi_reg.h" | |
89 | ||
0 | 90 | |
91 | static spi_lobo_host_t *spihost[3] = {NULL}; | |
92 | ||
93 | ||
94 | static const char *SPI_TAG = "spi_lobo_master"; | |
95 | #define SPI_CHECK(a, str, ret_val) \ | |
96 | if (!(a)) { \ | |
97 | ESP_LOGE(SPI_TAG,"%s(%d): %s", __FUNCTION__, __LINE__, str); \ | |
98 | return (ret_val); \ | |
99 | } | |
100 | ||
1
ad2c8b13eb88
Updated lots of doxygen comments
Michiel Broek <mbroek@mbse.eu>
parents:
0
diff
changeset
|
101 | /** |
ad2c8b13eb88
Updated lots of doxygen comments
Michiel Broek <mbroek@mbse.eu>
parents:
0
diff
changeset
|
102 | * @brief Stores a bunch of per-spi-peripheral data. |
ad2c8b13eb88
Updated lots of doxygen comments
Michiel Broek <mbroek@mbse.eu>
parents:
0
diff
changeset
|
103 | */ |
0 | 104 | typedef struct { |
105 | const uint8_t spiclk_out; //GPIO mux output signals | |
106 | const uint8_t spid_out; | |
107 | const uint8_t spiq_out; | |
108 | const uint8_t spiwp_out; | |
109 | const uint8_t spihd_out; | |
110 | const uint8_t spid_in; //GPIO mux input signals | |
111 | const uint8_t spiq_in; | |
112 | const uint8_t spiwp_in; | |
113 | const uint8_t spihd_in; | |
114 | const uint8_t spics_out[3]; // /CS GPIO output mux signals | |
115 | const uint8_t spiclk_native; //IO pins of IO_MUX muxed signals | |
116 | const uint8_t spid_native; | |
117 | const uint8_t spiq_native; | |
118 | const uint8_t spiwp_native; | |
119 | const uint8_t spihd_native; | |
120 | const uint8_t spics0_native; | |
121 | const uint8_t irq; //irq source for interrupt mux | |
122 | const uint8_t irq_dma; //dma irq source for interrupt mux | |
123 | const periph_module_t module; //peripheral module, for enabling clock etc | |
124 | spi_dev_t *hw; //Pointer to the hardware registers | |
125 | } spi_signal_conn_t; | |
126 | ||
127 | /* | |
128 | Bunch of constants for every SPI peripheral: GPIO signals, irqs, hw addr of registers etc | |
129 | */ | |
130 | static const spi_signal_conn_t io_signal[3]={ | |
131 | { | |
132 | .spiclk_out=SPICLK_OUT_IDX, | |
133 | .spid_out=SPID_OUT_IDX, | |
134 | .spiq_out=SPIQ_OUT_IDX, | |
135 | .spiwp_out=SPIWP_OUT_IDX, | |
136 | .spihd_out=SPIHD_OUT_IDX, | |
137 | .spid_in=SPID_IN_IDX, | |
138 | .spiq_in=SPIQ_IN_IDX, | |
139 | .spiwp_in=SPIWP_IN_IDX, | |
140 | .spihd_in=SPIHD_IN_IDX, | |
141 | .spics_out={SPICS0_OUT_IDX, SPICS1_OUT_IDX, SPICS2_OUT_IDX}, | |
142 | .spiclk_native=6, | |
143 | .spid_native=8, | |
144 | .spiq_native=7, | |
145 | .spiwp_native=10, | |
146 | .spihd_native=9, | |
147 | .spics0_native=11, | |
148 | .irq=ETS_SPI1_INTR_SOURCE, | |
149 | .irq_dma=ETS_SPI1_DMA_INTR_SOURCE, | |
150 | .module=PERIPH_SPI_MODULE, | |
151 | .hw=&SPI1 | |
152 | }, { | |
153 | .spiclk_out=HSPICLK_OUT_IDX, | |
154 | .spid_out=HSPID_OUT_IDX, | |
155 | .spiq_out=HSPIQ_OUT_IDX, | |
156 | .spiwp_out=HSPIWP_OUT_IDX, | |
157 | .spihd_out=HSPIHD_OUT_IDX, | |
158 | .spid_in=HSPID_IN_IDX, | |
159 | .spiq_in=HSPIQ_IN_IDX, | |
160 | .spiwp_in=HSPIWP_IN_IDX, | |
161 | .spihd_in=HSPIHD_IN_IDX, | |
162 | .spics_out={HSPICS0_OUT_IDX, HSPICS1_OUT_IDX, HSPICS2_OUT_IDX}, | |
163 | .spiclk_native=14, | |
164 | .spid_native=13, | |
165 | .spiq_native=12, | |
166 | .spiwp_native=2, | |
167 | .spihd_native=4, | |
168 | .spics0_native=15, | |
169 | .irq=ETS_SPI2_INTR_SOURCE, | |
170 | .irq_dma=ETS_SPI2_DMA_INTR_SOURCE, | |
171 | .module=PERIPH_HSPI_MODULE, | |
172 | .hw=&SPI2 | |
173 | }, { | |
174 | .spiclk_out=VSPICLK_OUT_IDX, | |
175 | .spid_out=VSPID_OUT_IDX, | |
176 | .spiq_out=VSPIQ_OUT_IDX, | |
177 | .spiwp_out=VSPIWP_OUT_IDX, | |
178 | .spihd_out=VSPIHD_OUT_IDX, | |
179 | .spid_in=VSPID_IN_IDX, | |
180 | .spiq_in=VSPIQ_IN_IDX, | |
181 | .spiwp_in=VSPIWP_IN_IDX, | |
182 | .spihd_in=VSPIHD_IN_IDX, | |
183 | .spics_out={VSPICS0_OUT_IDX, VSPICS1_OUT_IDX, VSPICS2_OUT_IDX}, | |
184 | .spiclk_native=18, | |
185 | .spid_native=23, | |
186 | .spiq_native=19, | |
187 | .spiwp_native=22, | |
188 | .spihd_native=21, | |
189 | .spics0_native=5, | |
190 | .irq=ETS_SPI3_INTR_SOURCE, | |
191 | .irq_dma=ETS_SPI3_DMA_INTR_SOURCE, | |
192 | .module=PERIPH_VSPI_MODULE, | |
193 | .hw=&SPI3 | |
194 | } | |
195 | }; | |
196 | ||
197 | ||
198 | //====================================================================================================== | |
199 | ||
200 | #define DMA_CHANNEL_ENABLED(dma_chan) (BIT(dma_chan-1)) | |
201 | ||
202 | typedef void(*dmaworkaround_cb_t)(void *arg); | |
203 | ||
204 | //Set up a list of dma descriptors. dmadesc is an array of descriptors. Data is the buffer to point to. | |
205 | //-------------------------------------------------------------------------------------------- | |
206 | void spi_lobo_setup_dma_desc_links(lldesc_t *dmadesc, int len, const uint8_t *data, bool isrx) | |
207 | { | |
208 | int n = 0; | |
209 | while (len) { | |
210 | int dmachunklen = len; | |
211 | if (dmachunklen > SPI_MAX_DMA_LEN) dmachunklen = SPI_MAX_DMA_LEN; | |
212 | if (isrx) { | |
213 | //Receive needs DMA length rounded to next 32-bit boundary | |
214 | dmadesc[n].size = (dmachunklen + 3) & (~3); | |
215 | dmadesc[n].length = (dmachunklen + 3) & (~3); | |
216 | } else { | |
217 | dmadesc[n].size = dmachunklen; | |
218 | dmadesc[n].length = dmachunklen; | |
219 | } | |
220 | dmadesc[n].buf = (uint8_t *)data; | |
221 | dmadesc[n].eof = 0; | |
222 | dmadesc[n].sosf = 0; | |
223 | dmadesc[n].owner = 1; | |
224 | dmadesc[n].qe.stqe_next = &dmadesc[n + 1]; | |
225 | len -= dmachunklen; | |
226 | data += dmachunklen; | |
227 | n++; | |
228 | } | |
229 | dmadesc[n - 1].eof = 1; //Mark last DMA desc as end of stream. | |
230 | dmadesc[n - 1].qe.stqe_next = NULL; | |
231 | } | |
232 | ||
233 | ||
234 | /* | |
235 | Code for workaround for DMA issue in ESP32 v0/v1 silicon | |
236 | */ | |
237 | ||
238 | ||
239 | static volatile int dmaworkaround_channels_busy[2] = {0, 0}; | |
240 | static dmaworkaround_cb_t dmaworkaround_cb; | |
241 | static void *dmaworkaround_cb_arg; | |
242 | static portMUX_TYPE dmaworkaround_mux = portMUX_INITIALIZER_UNLOCKED; | |
243 | static int dmaworkaround_waiting_for_chan = 0; | |
244 | static bool spi_periph_claimed[3] = {true, false, false}; | |
245 | static uint8_t spi_dma_chan_enabled = 0; | |
246 | static portMUX_TYPE spi_dma_spinlock = portMUX_INITIALIZER_UNLOCKED; | |
247 | ||
248 | //-------------------------------------------------------------------------------------------- | |
249 | bool IRAM_ATTR spi_lobo_dmaworkaround_req_reset(int dmachan, dmaworkaround_cb_t cb, void *arg) | |
250 | { | |
251 | int otherchan = (dmachan == 1) ? 2 : 1; | |
252 | bool ret; | |
253 | portENTER_CRITICAL(&dmaworkaround_mux); | |
254 | if (dmaworkaround_channels_busy[otherchan-1]) { | |
255 | //Other channel is busy. Call back when it's done. | |
256 | dmaworkaround_cb = cb; | |
257 | dmaworkaround_cb_arg = arg; | |
258 | dmaworkaround_waiting_for_chan = otherchan; | |
259 | ret = false; | |
260 | } else { | |
261 | //Reset DMA | |
262 | DPORT_SET_PERI_REG_MASK(DPORT_PERIP_RST_EN_REG, DPORT_SPI_DMA_RST); | |
263 | DPORT_CLEAR_PERI_REG_MASK(DPORT_PERIP_RST_EN_REG, DPORT_SPI_DMA_RST); | |
264 | ret = true; | |
265 | } | |
266 | portEXIT_CRITICAL(&dmaworkaround_mux); | |
267 | return ret; | |
268 | } | |
269 | ||
270 | //------------------------------------------------------- | |
271 | bool IRAM_ATTR spi_lobo_dmaworkaround_reset_in_progress() | |
272 | { | |
273 | return (dmaworkaround_waiting_for_chan != 0); | |
274 | } | |
275 | ||
276 | //----------------------------------------------------- | |
277 | void IRAM_ATTR spi_lobo_dmaworkaround_idle(int dmachan) | |
278 | { | |
279 | portENTER_CRITICAL(&dmaworkaround_mux); | |
280 | dmaworkaround_channels_busy[dmachan-1] = 0; | |
281 | if (dmaworkaround_waiting_for_chan == dmachan) { | |
282 | //Reset DMA | |
283 | DPORT_SET_PERI_REG_MASK(DPORT_PERIP_RST_EN_REG, DPORT_SPI_DMA_RST); | |
284 | DPORT_CLEAR_PERI_REG_MASK(DPORT_PERIP_RST_EN_REG, DPORT_SPI_DMA_RST); | |
285 | dmaworkaround_waiting_for_chan = 0; | |
286 | //Call callback | |
287 | dmaworkaround_cb(dmaworkaround_cb_arg); | |
288 | ||
289 | } | |
290 | portEXIT_CRITICAL(&dmaworkaround_mux); | |
291 | } | |
292 | ||
293 | //---------------------------------------------------------------- | |
294 | void IRAM_ATTR spi_lobo_dmaworkaround_transfer_active(int dmachan) | |
295 | { | |
296 | portENTER_CRITICAL(&dmaworkaround_mux); | |
297 | dmaworkaround_channels_busy[dmachan-1] = 1; | |
298 | portEXIT_CRITICAL(&dmaworkaround_mux); | |
299 | } | |
300 | ||
301 | //Returns true if this peripheral is successfully claimed, false if otherwise. | |
302 | //----------------------------------------------------- | |
303 | bool spi_lobo_periph_claim(spi_lobo_host_device_t host) | |
304 | { | |
305 | bool ret = __sync_bool_compare_and_swap(&spi_periph_claimed[host], false, true); | |
306 | if (ret) periph_module_enable(io_signal[host].module); | |
307 | return ret; | |
308 | } | |
309 | ||
310 | //Returns true if this peripheral is successfully freed, false if otherwise. | |
311 | //----------------------------------------------- | |
312 | bool spi_lobo_periph_free(spi_lobo_host_device_t host) | |
313 | { | |
314 | bool ret = __sync_bool_compare_and_swap(&spi_periph_claimed[host], true, false); | |
315 | if (ret) periph_module_disable(io_signal[host].module); | |
316 | return ret; | |
317 | } | |
318 | ||
319 | //----------------------------------------- | |
320 | bool spi_lobo_dma_chan_claim (int dma_chan) | |
321 | { | |
322 | bool ret = false; | |
323 | assert( dma_chan == 1 || dma_chan == 2 ); | |
324 | ||
325 | portENTER_CRITICAL(&spi_dma_spinlock); | |
326 | if ( !(spi_dma_chan_enabled & DMA_CHANNEL_ENABLED(dma_chan)) ) { | |
327 | // get the channel only when it's not claimed yet. | |
328 | spi_dma_chan_enabled |= DMA_CHANNEL_ENABLED(dma_chan); | |
329 | ret = true; | |
330 | } | |
331 | periph_module_enable( PERIPH_SPI_DMA_MODULE ); | |
332 | portEXIT_CRITICAL(&spi_dma_spinlock); | |
333 | ||
334 | return ret; | |
335 | } | |
336 | ||
337 | //--------------------------------------- | |
338 | bool spi_lobo_dma_chan_free(int dma_chan) | |
339 | { | |
340 | assert( dma_chan == 1 || dma_chan == 2 ); | |
341 | assert( spi_dma_chan_enabled & DMA_CHANNEL_ENABLED(dma_chan) ); | |
342 | ||
343 | portENTER_CRITICAL(&spi_dma_spinlock); | |
344 | spi_dma_chan_enabled &= ~DMA_CHANNEL_ENABLED(dma_chan); | |
345 | if ( spi_dma_chan_enabled == 0 ) { | |
346 | //disable the DMA only when all the channels are freed. | |
347 | periph_module_disable( PERIPH_SPI_DMA_MODULE ); | |
348 | } | |
349 | portEXIT_CRITICAL(&spi_dma_spinlock); | |
350 | ||
351 | return true; | |
352 | } | |
353 | ||
354 | ||
355 | //====================================================================================================== | |
356 | ||
357 | ||
358 | //---------------------------------------------------------------------------------------------------------------- | |
359 | static esp_err_t spi_lobo_bus_initialize(spi_lobo_host_device_t host, spi_lobo_bus_config_t *bus_config, int init) | |
360 | { | |
361 | bool native=true, spi_chan_claimed, dma_chan_claimed; | |
362 | ||
363 | if (init > 0) { | |
364 | /* ToDo: remove this when we have flash operations cooperating with this */ | |
365 | SPI_CHECK(host!=TFT_SPI_HOST, "SPI1 is not supported", ESP_ERR_NOT_SUPPORTED); | |
366 | ||
367 | SPI_CHECK(host>=TFT_SPI_HOST && host<=TFT_VSPI_HOST, "invalid host", ESP_ERR_INVALID_ARG); | |
368 | SPI_CHECK(spihost[host]==NULL, "host already in use", ESP_ERR_INVALID_STATE); | |
369 | } | |
370 | else { | |
371 | SPI_CHECK(spihost[host]!=NULL, "host not in use", ESP_ERR_INVALID_STATE); | |
372 | } | |
373 | ||
374 | SPI_CHECK(bus_config->mosi_io_num<0 || GPIO_IS_VALID_OUTPUT_GPIO(bus_config->mosi_io_num), "spid pin invalid", ESP_ERR_INVALID_ARG); | |
375 | SPI_CHECK(bus_config->sclk_io_num<0 || GPIO_IS_VALID_OUTPUT_GPIO(bus_config->sclk_io_num), "spiclk pin invalid", ESP_ERR_INVALID_ARG); | |
376 | SPI_CHECK(bus_config->miso_io_num<0 || GPIO_IS_VALID_GPIO(bus_config->miso_io_num), "spiq pin invalid", ESP_ERR_INVALID_ARG); | |
377 | SPI_CHECK(bus_config->quadwp_io_num<0 || GPIO_IS_VALID_OUTPUT_GPIO(bus_config->quadwp_io_num), "spiwp pin invalid", ESP_ERR_INVALID_ARG); | |
378 | SPI_CHECK(bus_config->quadhd_io_num<0 || GPIO_IS_VALID_OUTPUT_GPIO(bus_config->quadhd_io_num), "spihd pin invalid", ESP_ERR_INVALID_ARG); | |
379 | ||
380 | if (init > 0) { | |
381 | spi_chan_claimed=spi_lobo_periph_claim(host); | |
382 | SPI_CHECK(spi_chan_claimed, "host already in use", ESP_ERR_INVALID_STATE); | |
383 | ||
384 | //spihost[host]=malloc(sizeof(spi_lobo_host_t)); | |
385 | spihost[host]=heap_caps_malloc(sizeof(spi_lobo_host_t), MALLOC_CAP_DMA); | |
386 | if (spihost[host]==NULL) return ESP_ERR_NO_MEM; | |
387 | memset(spihost[host], 0, sizeof(spi_lobo_host_t)); | |
388 | // Create semaphore | |
389 | spihost[host]->spi_lobo_bus_mutex = xSemaphoreCreateMutex(); | |
390 | if (!spihost[host]->spi_lobo_bus_mutex) return ESP_ERR_NO_MEM; | |
391 | } | |
392 | ||
393 | spihost[host]->cur_device = -1; | |
394 | memcpy(&spihost[host]->cur_bus_config, bus_config, sizeof(spi_lobo_bus_config_t)); | |
395 | ||
396 | //Check if the selected pins correspond to the native pins of the peripheral | |
397 | if (bus_config->mosi_io_num >= 0 && bus_config->mosi_io_num!=io_signal[host].spid_native) native=false; | |
398 | if (bus_config->miso_io_num >= 0 && bus_config->miso_io_num!=io_signal[host].spiq_native) native=false; | |
399 | if (bus_config->sclk_io_num >= 0 && bus_config->sclk_io_num!=io_signal[host].spiclk_native) native=false; | |
400 | if (bus_config->quadwp_io_num >= 0 && bus_config->quadwp_io_num!=io_signal[host].spiwp_native) native=false; | |
401 | if (bus_config->quadhd_io_num >= 0 && bus_config->quadhd_io_num!=io_signal[host].spihd_native) native=false; | |
402 | ||
403 | spihost[host]->no_gpio_matrix=native; | |
404 | if (native) { | |
405 | //All SPI native pin selections resolve to 1, so we put that here instead of trying to figure | |
406 | //out which FUNC_GPIOx_xSPIxx to grab; they all are defined to 1 anyway. | |
407 | if (bus_config->mosi_io_num > 0) PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[bus_config->mosi_io_num], 1); | |
408 | if (bus_config->miso_io_num > 0) PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[bus_config->miso_io_num], 1); | |
409 | if (bus_config->quadwp_io_num > 0) PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[bus_config->quadwp_io_num], 1); | |
410 | if (bus_config->quadhd_io_num > 0) PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[bus_config->quadhd_io_num], 1); | |
411 | if (bus_config->sclk_io_num > 0) PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[bus_config->sclk_io_num], 1); | |
412 | } else { | |
413 | //Use GPIO | |
414 | if (bus_config->mosi_io_num>0) { | |
415 | PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[bus_config->mosi_io_num], PIN_FUNC_GPIO); | |
416 | gpio_set_direction(bus_config->mosi_io_num, GPIO_MODE_OUTPUT); | |
417 | gpio_matrix_out(bus_config->mosi_io_num, io_signal[host].spid_out, false, false); | |
418 | gpio_matrix_in(bus_config->mosi_io_num, io_signal[host].spid_in, false); | |
419 | } | |
420 | if (bus_config->miso_io_num>0) { | |
421 | PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[bus_config->miso_io_num], PIN_FUNC_GPIO); | |
422 | gpio_set_direction(bus_config->miso_io_num, GPIO_MODE_INPUT); | |
423 | gpio_matrix_out(bus_config->miso_io_num, io_signal[host].spiq_out, false, false); | |
424 | gpio_matrix_in(bus_config->miso_io_num, io_signal[host].spiq_in, false); | |
425 | } | |
426 | if (bus_config->quadwp_io_num>0) { | |
427 | PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[bus_config->quadwp_io_num], PIN_FUNC_GPIO); | |
428 | gpio_set_direction(bus_config->quadwp_io_num, GPIO_MODE_OUTPUT); | |
429 | gpio_matrix_out(bus_config->quadwp_io_num, io_signal[host].spiwp_out, false, false); | |
430 | gpio_matrix_in(bus_config->quadwp_io_num, io_signal[host].spiwp_in, false); | |
431 | } | |
432 | if (bus_config->quadhd_io_num>0) { | |
433 | PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[bus_config->quadhd_io_num], PIN_FUNC_GPIO); | |
434 | gpio_set_direction(bus_config->quadhd_io_num, GPIO_MODE_OUTPUT); | |
435 | gpio_matrix_out(bus_config->quadhd_io_num, io_signal[host].spihd_out, false, false); | |
436 | gpio_matrix_in(bus_config->quadhd_io_num, io_signal[host].spihd_in, false); | |
437 | } | |
438 | if (bus_config->sclk_io_num>0) { | |
439 | PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[bus_config->sclk_io_num], PIN_FUNC_GPIO); | |
440 | gpio_set_direction(bus_config->sclk_io_num, GPIO_MODE_OUTPUT); | |
441 | gpio_matrix_out(bus_config->sclk_io_num, io_signal[host].spiclk_out, false, false); | |
442 | } | |
443 | } | |
444 | periph_module_enable(io_signal[host].module); | |
445 | spihost[host]->hw=io_signal[host].hw; | |
446 | ||
447 | if (init > 0) { | |
448 | dma_chan_claimed=spi_lobo_dma_chan_claim(init); | |
449 | if ( !dma_chan_claimed ) { | |
450 | spi_lobo_periph_free( host ); | |
451 | SPI_CHECK(dma_chan_claimed, "dma channel already in use", ESP_ERR_INVALID_STATE); | |
452 | } | |
453 | spihost[host]->dma_chan = init; | |
454 | //See how many dma descriptors we need and allocate them | |
455 | int dma_desc_ct=(bus_config->max_transfer_sz+SPI_MAX_DMA_LEN-1)/SPI_MAX_DMA_LEN; | |
456 | if (dma_desc_ct==0) dma_desc_ct=1; //default to 4k when max is not given | |
457 | spihost[host]->max_transfer_sz = dma_desc_ct*SPI_MAX_DMA_LEN; | |
458 | ||
459 | spihost[host]->dmadesc_tx=heap_caps_malloc(sizeof(lldesc_t)*dma_desc_ct, MALLOC_CAP_DMA); | |
460 | spihost[host]->dmadesc_rx=heap_caps_malloc(sizeof(lldesc_t)*dma_desc_ct, MALLOC_CAP_DMA); | |
461 | if (!spihost[host]->dmadesc_tx || !spihost[host]->dmadesc_rx) goto nomem; | |
462 | ||
463 | //Tell common code DMA workaround that our DMA channel is idle. If needed, the code will do a DMA reset. | |
464 | spi_lobo_dmaworkaround_idle(spihost[host]->dma_chan); | |
465 | ||
466 | // Reset DMA | |
467 | spihost[host]->hw->dma_conf.val |= SPI_OUT_RST|SPI_IN_RST|SPI_AHBM_RST|SPI_AHBM_FIFO_RST; | |
468 | spihost[host]->hw->dma_out_link.start=0; | |
469 | spihost[host]->hw->dma_in_link.start=0; | |
470 | spihost[host]->hw->dma_conf.val &= ~(SPI_OUT_RST|SPI_IN_RST|SPI_AHBM_RST|SPI_AHBM_FIFO_RST); | |
471 | spihost[host]->hw->dma_conf.out_data_burst_en=1; | |
472 | ||
473 | //Reset timing | |
474 | spihost[host]->hw->ctrl2.val=0; | |
475 | ||
476 | //Disable unneeded ints | |
477 | spihost[host]->hw->slave.rd_buf_done=0; | |
478 | spihost[host]->hw->slave.wr_buf_done=0; | |
479 | spihost[host]->hw->slave.rd_sta_done=0; | |
480 | spihost[host]->hw->slave.wr_sta_done=0; | |
481 | spihost[host]->hw->slave.rd_buf_inten=0; | |
482 | spihost[host]->hw->slave.wr_buf_inten=0; | |
483 | spihost[host]->hw->slave.rd_sta_inten=0; | |
484 | spihost[host]->hw->slave.wr_sta_inten=0; | |
485 | ||
486 | //Force a transaction done interrupt. This interrupt won't fire yet because we initialized the SPI interrupt as | |
487 | //disabled. This way, we can just enable the SPI interrupt and the interrupt handler will kick in, handling | |
488 | //any transactions that are queued. | |
489 | spihost[host]->hw->slave.trans_inten=1; | |
490 | spihost[host]->hw->slave.trans_done=1; | |
491 | ||
492 | //Select DMA channel. | |
493 | DPORT_SET_PERI_REG_BITS(DPORT_SPI_DMA_CHAN_SEL_REG, 3, init, (host * 2)); | |
494 | } | |
495 | return ESP_OK; | |
496 | ||
497 | nomem: | |
498 | if (spihost[host]) { | |
499 | free(spihost[host]->dmadesc_tx); | |
500 | free(spihost[host]->dmadesc_rx); | |
501 | } | |
502 | free(spihost[host]); | |
503 | spi_lobo_periph_free(host); | |
504 | return ESP_ERR_NO_MEM; | |
505 | } | |
506 | ||
507 | //--------------------------------------------------------------------------- | |
508 | static esp_err_t spi_lobo_bus_free(spi_lobo_host_device_t host, int dofree) | |
509 | { | |
510 | if ((host == TFT_SPI_HOST) || (host > TFT_VSPI_HOST)) return ESP_ERR_NOT_SUPPORTED; // invalid host | |
511 | ||
512 | if (spihost[host] == NULL) return ESP_ERR_INVALID_STATE; // host not in use | |
513 | ||
514 | if (dofree) { | |
515 | for (int x=0; x<NO_DEV; x++) { | |
516 | if (spihost[host]->device[x] != NULL) return ESP_ERR_INVALID_STATE; // not all devices freed | |
517 | } | |
518 | } | |
519 | if ( spihost[host]->dma_chan > 0 ) { | |
520 | spi_lobo_dma_chan_free ( spihost[host]->dma_chan ); | |
521 | } | |
522 | spihost[host]->hw->slave.trans_inten=0; | |
523 | spihost[host]->hw->slave.trans_done=0; | |
524 | spi_lobo_periph_free(host); | |
525 | ||
526 | if (dofree) { | |
527 | vSemaphoreDelete(spihost[host]->spi_lobo_bus_mutex); | |
528 | free(spihost[host]->dmadesc_tx); | |
529 | free(spihost[host]->dmadesc_rx); | |
530 | free(spihost[host]); | |
531 | spihost[host] = NULL; | |
532 | } | |
533 | return ESP_OK; | |
534 | } | |
535 | ||
536 | //--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | |
537 | esp_err_t spi_lobo_bus_add_device(spi_lobo_host_device_t host, spi_lobo_bus_config_t *bus_config, spi_lobo_device_interface_config_t *dev_config, spi_lobo_device_handle_t *handle) | |
538 | { | |
539 | if ((host == TFT_SPI_HOST) || (host > TFT_VSPI_HOST)) return ESP_ERR_NOT_SUPPORTED; // invalid host | |
540 | ||
541 | if (spihost[host] == NULL) { | |
542 | esp_err_t ret = spi_lobo_bus_initialize(host, bus_config, 1); | |
543 | if (ret) return ret; | |
544 | } | |
545 | ||
546 | int freecs, maxdev; | |
547 | int apbclk=APB_CLK_FREQ; | |
548 | ||
549 | if (spihost[host] == NULL) return ESP_ERR_INVALID_STATE; | |
550 | ||
551 | if (dev_config->spics_io_num >= 0) { | |
552 | if (!GPIO_IS_VALID_OUTPUT_GPIO(dev_config->spics_io_num)) return ESP_ERR_INVALID_ARG; | |
553 | if (dev_config->spics_ext_io_num > 0) dev_config->spics_ext_io_num = -1; | |
554 | } | |
555 | else { | |
556 | //if ((dev_config->spics_ext_io_num <= 0) || (!GPIO_IS_VALID_OUTPUT_GPIO(dev_config->spics_ext_io_num))) return ESP_ERR_INVALID_ARG; | |
557 | } | |
558 | ||
559 | //ToDo: Check if some other device uses the same 'spics_ext_io_num' | |
560 | ||
561 | if (dev_config->clock_speed_hz == 0) return ESP_ERR_INVALID_ARG; | |
562 | if (dev_config->spics_io_num > 0) maxdev = NO_CS; | |
563 | else maxdev = NO_DEV; | |
564 | ||
565 | for (freecs=0; freecs<maxdev; freecs++) { | |
566 | //See if this slot is free; reserve if it is by putting a dummy pointer in the slot. We use an atomic compare&swap to make this thread-safe. | |
567 | if (__sync_bool_compare_and_swap(&spihost[host]->device[freecs], NULL, (spi_lobo_device_t *)1)) break; | |
568 | } | |
569 | if (freecs == maxdev) return ESP_ERR_NOT_FOUND; | |
570 | ||
571 | // The hardware looks like it would support this, but actually setting cs_ena_pretrans when transferring in full | |
572 | // duplex mode does absolutely nothing on the ESP32. | |
573 | if ((dev_config->cs_ena_pretrans != 0) && (dev_config->flags & LB_SPI_DEVICE_HALFDUPLEX)) return ESP_ERR_INVALID_ARG; | |
574 | ||
575 | // Speeds >=40MHz over GPIO matrix needs a dummy cycle, but these don't work for full-duplex connections. | |
576 | if (((dev_config->flags & LB_SPI_DEVICE_HALFDUPLEX)==0) && (dev_config->clock_speed_hz > ((apbclk*2)/5)) && (!spihost[host]->no_gpio_matrix)) return ESP_ERR_INVALID_ARG; | |
577 | ||
578 | //Allocate memory for device | |
579 | spi_lobo_device_t *dev=malloc(sizeof(spi_lobo_device_t)); | |
580 | if (dev==NULL) return ESP_ERR_NO_MEM; | |
581 | ||
582 | memset(dev, 0, sizeof(spi_lobo_device_t)); | |
583 | spihost[host]->device[freecs]=dev; | |
584 | ||
585 | if (dev_config->duty_cycle_pos==0) dev_config->duty_cycle_pos=128; | |
586 | dev->host=spihost[host]; | |
587 | dev->host_dev = host; | |
588 | ||
589 | //We want to save a copy of the dev config in the dev struct. | |
590 | memcpy(&dev->cfg, dev_config, sizeof(spi_lobo_device_interface_config_t)); | |
591 | //We want to save a copy of the bus config in the dev struct. | |
592 | memcpy(&dev->bus_config, bus_config, sizeof(spi_lobo_bus_config_t)); | |
593 | ||
594 | //Set CS pin, CS options | |
595 | if (dev_config->spics_io_num > 0) { | |
596 | if (spihost[host]->no_gpio_matrix &&dev_config->spics_io_num == io_signal[host].spics0_native && freecs==0) { | |
597 | //Again, the cs0s for all SPI peripherals map to pin mux source 1, so we use that instead of a define. | |
598 | PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[dev_config->spics_io_num], 1); | |
599 | } else { | |
600 | //Use GPIO matrix | |
601 | PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[dev_config->spics_io_num], PIN_FUNC_GPIO); | |
602 | gpio_set_direction(dev_config->spics_io_num, GPIO_MODE_OUTPUT); | |
603 | gpio_matrix_out(dev_config->spics_io_num, io_signal[host].spics_out[freecs], false, false); | |
604 | } | |
605 | } | |
606 | else if (dev_config->spics_ext_io_num >= 0) { | |
607 | gpio_set_direction(dev_config->spics_ext_io_num, GPIO_MODE_OUTPUT); | |
608 | gpio_set_level(dev_config->spics_ext_io_num, 1); | |
609 | } | |
610 | if (dev_config->flags & LB_SPI_DEVICE_CLK_AS_CS) { | |
611 | spihost[host]->hw->pin.master_ck_sel |= (1<<freecs); | |
612 | } else { | |
613 | spihost[host]->hw->pin.master_ck_sel &= (1<<freecs); | |
614 | } | |
615 | if (dev_config->flags & LB_SPI_DEVICE_POSITIVE_CS) { | |
616 | spihost[host]->hw->pin.master_cs_pol |= (1<<freecs); | |
617 | } else { | |
618 | spihost[host]->hw->pin.master_cs_pol &= (1<<freecs); | |
619 | } | |
620 | ||
621 | *handle = dev; | |
622 | return ESP_OK; | |
623 | } | |
624 | ||
625 | //------------------------------------------------------------------- | |
626 | esp_err_t spi_lobo_bus_remove_device(spi_lobo_device_handle_t handle) | |
627 | { | |
628 | int x; | |
629 | if (handle == NULL) return ESP_ERR_INVALID_ARG; | |
630 | ||
631 | //Remove device from list of csses and free memory | |
632 | for (x=0; x<NO_DEV; x++) { | |
633 | if (handle->host->device[x] == handle) handle->host->device[x]=NULL; | |
634 | } | |
635 | ||
636 | // Check if all devices are removed from this host and free the bus if yes | |
637 | for (x=0; x<NO_DEV; x++) { | |
638 | if (spihost[handle->host_dev]->device[x] !=NULL) break; | |
639 | } | |
640 | if (x == NO_DEV) { | |
129 | 641 | spi_lobo_bus_free(handle->host_dev, 1); |
0 | 642 | free(handle); |
643 | } | |
644 | else free(handle); | |
645 | ||
646 | return ESP_OK; | |
647 | } | |
648 | ||
649 | //----------------------------------------------------------------- | |
650 | static int IRAM_ATTR spi_freq_for_pre_n(int fapb, int pre, int n) { | |
651 | return (fapb / (pre * n)); | |
652 | } | |
653 | ||
654 | /* | |
655 | * Set the SPI clock to a certain frequency. Returns the effective frequency set, which may be slightly | |
656 | * different from the requested frequency. | |
657 | */ | |
658 | //----------------------------------------------------------------------------------- | |
659 | static int IRAM_ATTR spi_set_clock(spi_dev_t *hw, int fapb, int hz, int duty_cycle) { | |
660 | int pre, n, h, l, eff_clk; | |
661 | ||
662 | //In hw, n, h and l are 1-64, pre is 1-8K. Value written to register is one lower than used value. | |
663 | if (hz>((fapb/4)*3)) { | |
664 | //Using Fapb directly will give us the best result here. | |
665 | hw->clock.clkcnt_l=0; | |
666 | hw->clock.clkcnt_h=0; | |
667 | hw->clock.clkcnt_n=0; | |
668 | hw->clock.clkdiv_pre=0; | |
669 | hw->clock.clk_equ_sysclk=1; | |
670 | eff_clk=fapb; | |
671 | } else { | |
672 | //For best duty cycle resolution, we want n to be as close to 32 as possible, but | |
673 | //we also need a pre/n combo that gets us as close as possible to the intended freq. | |
674 | //To do this, we bruteforce n and calculate the best pre to go along with that. | |
675 | //If there's a choice between pre/n combos that give the same result, use the one | |
676 | //with the higher n. | |
677 | int bestn=-1; | |
678 | int bestpre=-1; | |
679 | int besterr=0; | |
680 | int errval; | |
681 | for (n=1; n<=64; n++) { | |
682 | //Effectively, this does pre=round((fapb/n)/hz). | |
683 | pre=((fapb/n)+(hz/2))/hz; | |
684 | if (pre<=0) pre=1; | |
685 | if (pre>8192) pre=8192; | |
686 | errval=abs(spi_freq_for_pre_n(fapb, pre, n)-hz); | |
687 | if (bestn==-1 || errval<=besterr) { | |
688 | besterr=errval; | |
689 | bestn=n; | |
690 | bestpre=pre; | |
691 | } | |
692 | } | |
693 | ||
694 | n=bestn; | |
695 | pre=bestpre; | |
696 | l=n; | |
697 | //This effectively does round((duty_cycle*n)/256) | |
698 | h=(duty_cycle*n+127)/256; | |
699 | if (h<=0) h=1; | |
700 | ||
701 | hw->clock.clk_equ_sysclk=0; | |
702 | hw->clock.clkcnt_n=n-1; | |
703 | hw->clock.clkdiv_pre=pre-1; | |
704 | hw->clock.clkcnt_h=h-1; | |
705 | hw->clock.clkcnt_l=l-1; | |
706 | eff_clk=spi_freq_for_pre_n(fapb, pre, n); | |
707 | } | |
708 | return eff_clk; | |
709 | } | |
710 | ||
711 | ||
712 | ||
713 | //------------------------------------------------------------------------------------ | |
714 | esp_err_t IRAM_ATTR spi_lobo_device_select(spi_lobo_device_handle_t handle, int force) | |
715 | { | |
716 | if (handle == NULL) return ESP_ERR_INVALID_ARG; | |
717 | ||
718 | if ((handle->cfg.selected == 1) && (!force)) return ESP_OK; // already selected | |
719 | ||
720 | int i; | |
721 | spi_lobo_host_t *host=(spi_lobo_host_t*)handle->host; | |
722 | ||
723 | // find device's host bus | |
724 | for (i=0; i<NO_DEV; i++) { | |
725 | if (host->device[i] == handle) break; | |
726 | } | |
727 | if (i == NO_DEV) return ESP_ERR_INVALID_ARG; | |
728 | ||
729 | if (!(xSemaphoreTake(host->spi_lobo_bus_mutex, SPI_SEMAPHORE_WAIT))) return ESP_ERR_INVALID_STATE; | |
730 | ||
731 | // Check if previously used device's bus device is the same | |
732 | if (memcmp(&host->cur_bus_config, &handle->bus_config, sizeof(spi_lobo_bus_config_t)) != 0) { | |
733 | // device has different bus configuration, we need to reconfigure the bus | |
734 | esp_err_t err = spi_lobo_bus_free(1, 0); | |
735 | if (err) { | |
736 | xSemaphoreGive(host->spi_lobo_bus_mutex); | |
737 | return err; | |
738 | } | |
739 | err = spi_lobo_bus_initialize(i, &handle->bus_config, -1); | |
740 | if (err) { | |
741 | xSemaphoreGive(host->spi_lobo_bus_mutex); | |
742 | return err; | |
743 | } | |
744 | } | |
745 | ||
746 | //Reconfigure according to device settings, but only if the device changed or forced. | |
747 | if ((force) || (host->device[host->cur_device] != handle)) { | |
748 | //Assumes a hardcoded 80MHz Fapb for now. ToDo: figure out something better once we have clock scaling working. | |
749 | int apbclk=APB_CLK_FREQ; | |
750 | ||
751 | //Speeds >=40MHz over GPIO matrix needs a dummy cycle, but these don't work for full-duplex connections. | |
752 | if (((handle->cfg.flags & LB_SPI_DEVICE_HALFDUPLEX) == 0) && (handle->cfg.clock_speed_hz > ((apbclk*2)/5)) && (!host->no_gpio_matrix)) { | |
753 | // set speed to 32 MHz | |
754 | handle->cfg.clock_speed_hz = (apbclk*2)/5; | |
755 | } | |
756 | ||
757 | int effclk=spi_set_clock(host->hw, apbclk, handle->cfg.clock_speed_hz, handle->cfg.duty_cycle_pos); | |
758 | //Configure bit order | |
759 | host->hw->ctrl.rd_bit_order=(handle->cfg.flags & LB_SPI_DEVICE_RXBIT_LSBFIRST)?1:0; | |
760 | host->hw->ctrl.wr_bit_order=(handle->cfg.flags & LB_SPI_DEVICE_TXBIT_LSBFIRST)?1:0; | |
761 | ||
762 | //Configure polarity | |
763 | //SPI iface needs to be configured for a delay in some cases. | |
764 | int nodelay=0; | |
765 | int extra_dummy=0; | |
766 | if (host->no_gpio_matrix) { | |
767 | if (effclk >= apbclk/2) { | |
768 | nodelay=1; | |
769 | } | |
770 | } else { | |
771 | if (effclk >= apbclk/2) { | |
772 | nodelay=1; | |
773 | extra_dummy=1; //Note: This only works on half-duplex connections. spi_lobo_bus_add_device checks for this. | |
774 | } else if (effclk >= apbclk/4) { | |
775 | nodelay=1; | |
776 | } | |
777 | } | |
778 | if (handle->cfg.mode==0) { | |
779 | host->hw->pin.ck_idle_edge=0; | |
780 | host->hw->user.ck_out_edge=0; | |
781 | host->hw->ctrl2.miso_delay_mode=nodelay?0:2; | |
782 | } else if (handle->cfg.mode==1) { | |
783 | host->hw->pin.ck_idle_edge=0; | |
784 | host->hw->user.ck_out_edge=1; | |
785 | host->hw->ctrl2.miso_delay_mode=nodelay?0:1; | |
786 | } else if (handle->cfg.mode==2) { | |
787 | host->hw->pin.ck_idle_edge=1; | |
788 | host->hw->user.ck_out_edge=1; | |
789 | host->hw->ctrl2.miso_delay_mode=nodelay?0:1; | |
790 | } else if (handle->cfg.mode==3) { | |
791 | host->hw->pin.ck_idle_edge=1; | |
792 | host->hw->user.ck_out_edge=0; | |
793 | host->hw->ctrl2.miso_delay_mode=nodelay?0:2; | |
794 | } | |
795 | ||
796 | //Configure bit sizes, load addr and command | |
797 | host->hw->user.usr_dummy=(handle->cfg.dummy_bits+extra_dummy)?1:0; | |
798 | host->hw->user.usr_addr=(handle->cfg.address_bits)?1:0; | |
799 | host->hw->user.usr_command=(handle->cfg.command_bits)?1:0; | |
800 | host->hw->user1.usr_addr_bitlen=handle->cfg.address_bits-1; | |
801 | host->hw->user1.usr_dummy_cyclelen=handle->cfg.dummy_bits+extra_dummy-1; | |
802 | host->hw->user2.usr_command_bitlen=handle->cfg.command_bits-1; | |
803 | //Configure misc stuff | |
804 | host->hw->user.doutdin=(handle->cfg.flags & LB_SPI_DEVICE_HALFDUPLEX)?0:1; | |
805 | host->hw->user.sio=(handle->cfg.flags & LB_SPI_DEVICE_3WIRE)?1:0; | |
806 | ||
807 | host->hw->ctrl2.setup_time=handle->cfg.cs_ena_pretrans-1; | |
808 | host->hw->user.cs_setup=handle->cfg.cs_ena_pretrans?1:0; | |
809 | host->hw->ctrl2.hold_time=handle->cfg.cs_ena_posttrans-1; | |
810 | host->hw->user.cs_hold=(handle->cfg.cs_ena_posttrans)?1:0; | |
811 | ||
812 | //Configure CS pin | |
813 | host->hw->pin.cs0_dis=(i==0)?0:1; | |
814 | host->hw->pin.cs1_dis=(i==1)?0:1; | |
815 | host->hw->pin.cs2_dis=(i==2)?0:1; | |
816 | ||
817 | host->cur_device = i; | |
818 | } | |
819 | ||
820 | if ((handle->cfg.spics_io_num < 0) && (handle->cfg.spics_ext_io_num > 0)) { | |
821 | gpio_set_level(handle->cfg.spics_ext_io_num, 0); | |
822 | } | |
823 | ||
824 | handle->cfg.selected = 1; | |
825 | ||
826 | return ESP_OK; | |
827 | } | |
828 | ||
829 | //--------------------------------------------------------------------------- | |
830 | esp_err_t IRAM_ATTR spi_lobo_device_deselect(spi_lobo_device_handle_t handle) | |
831 | { | |
832 | if (handle == NULL) return ESP_ERR_INVALID_ARG; | |
833 | ||
834 | if (handle->cfg.selected == 0) return ESP_OK; // already deselected | |
835 | ||
836 | int i; | |
837 | spi_lobo_host_t *host=(spi_lobo_host_t*)handle->host; | |
838 | ||
839 | for (i=0; i<NO_DEV; i++) { | |
840 | if (host->device[i] == handle) break; | |
841 | } | |
842 | if (i == NO_DEV) return ESP_ERR_INVALID_ARG; | |
843 | ||
844 | if (host->device[host->cur_device] == handle) { | |
845 | if ((handle->cfg.spics_io_num < 0) && (handle->cfg.spics_ext_io_num > 0)) { | |
846 | gpio_set_level(handle->cfg.spics_ext_io_num, 1); | |
847 | } | |
848 | } | |
849 | ||
850 | handle->cfg.selected = 0; | |
851 | xSemaphoreGive(host->spi_lobo_bus_mutex); | |
852 | ||
853 | return ESP_OK; | |
854 | } | |
855 | ||
856 | //-------------------------------------------------------------------------------- | |
857 | esp_err_t IRAM_ATTR spi_lobo_device_TakeSemaphore(spi_lobo_device_handle_t handle) | |
858 | { | |
859 | if (!(xSemaphoreTake(handle->host->spi_lobo_bus_mutex, SPI_SEMAPHORE_WAIT))) return ESP_ERR_INVALID_STATE; | |
860 | else return ESP_OK; | |
861 | } | |
862 | ||
863 | //--------------------------------------------------------------------------- | |
864 | void IRAM_ATTR spi_lobo_device_GiveSemaphore(spi_lobo_device_handle_t handle) | |
865 | { | |
866 | xSemaphoreTake(handle->host->spi_lobo_bus_mutex, portMAX_DELAY); | |
867 | } | |
868 | ||
869 | //---------------------------------------------------------- | |
870 | uint32_t spi_lobo_get_speed(spi_lobo_device_handle_t handle) | |
871 | { | |
872 | spi_lobo_host_t *host=(spi_lobo_host_t*)handle->host; | |
873 | uint32_t speed = 0; | |
874 | if (spi_lobo_device_select(handle, 0) == ESP_OK) { | |
875 | if (host->hw->clock.clk_equ_sysclk == 1) speed = 80000000; | |
876 | else speed = 80000000/(host->hw->clock.clkdiv_pre+1)/(host->hw->clock.clkcnt_n+1); | |
877 | } | |
878 | spi_lobo_device_deselect(handle); | |
879 | return speed; | |
880 | } | |
881 | ||
882 | //-------------------------------------------------------------------------- | |
883 | uint32_t spi_lobo_set_speed(spi_lobo_device_handle_t handle, uint32_t speed) | |
884 | { | |
885 | spi_lobo_host_t *host=(spi_lobo_host_t*)handle->host; | |
886 | uint32_t newspeed = 0; | |
887 | if (spi_lobo_device_select(handle, 0) == ESP_OK) { | |
888 | spi_lobo_device_deselect(handle); | |
889 | handle->cfg.clock_speed_hz = speed; | |
890 | if (spi_lobo_device_select(handle, 1) == ESP_OK) { | |
891 | if (host->hw->clock.clk_equ_sysclk == 1) newspeed = 80000000; | |
892 | else newspeed = 80000000/(host->hw->clock.clkdiv_pre+1)/(host->hw->clock.clkcnt_n+1); | |
893 | } | |
894 | } | |
895 | spi_lobo_device_deselect(handle); | |
896 | ||
897 | return newspeed; | |
898 | } | |
899 | ||
900 | //------------------------------------------------------------- | |
901 | bool spi_lobo_uses_native_pins(spi_lobo_device_handle_t handle) | |
902 | { | |
903 | return handle->host->no_gpio_matrix; | |
904 | } | |
905 | ||
906 | //------------------------------------------------------------------- | |
907 | void spi_lobo_get_native_pins(int host, int *sdi, int *sdo, int *sck) | |
908 | { | |
909 | *sdo = io_signal[host].spid_native; | |
910 | *sdi = io_signal[host].spiq_native; | |
911 | *sck = io_signal[host].spiclk_native; | |
912 | } | |
913 | ||
914 | /* | |
915 | When using 'spi_lobo_transfer_data' function we can have several scenarios: | |
916 | ||
917 | A: Send only (trans->rxlength = 0) | |
918 | B: Receive only (trans->txlength = 0) | |
919 | C: Send & receive (trans->txlength > 0 & trans->rxlength > 0) | |
920 | D: No operation (trans->txlength = 0 & trans->rxlength = 0) | |
921 | ||
922 | */ | |
923 | //---------------------------------------------------------------------------------------------------------- | |
924 | esp_err_t IRAM_ATTR spi_lobo_transfer_data(spi_lobo_device_handle_t handle, spi_lobo_transaction_t *trans) { | |
925 | if (!handle) return ESP_ERR_INVALID_ARG; | |
926 | ||
927 | // *** For now we can only handle 8-bit bytes transmission | |
928 | if (((trans->length % 8) != 0) || ((trans->rxlength % 8) != 0)) return ESP_ERR_INVALID_ARG; | |
929 | ||
930 | spi_lobo_host_t *host=(spi_lobo_host_t*)handle->host; | |
931 | esp_err_t ret; | |
932 | uint8_t do_deselect = 0; | |
933 | const uint8_t *txbuffer = NULL; | |
934 | uint8_t *rxbuffer = NULL; | |
935 | ||
936 | if (trans->flags & LB_SPI_TRANS_USE_TXDATA) { | |
937 | // Send data from 'trans->tx_data' | |
938 | txbuffer=(uint8_t*)&trans->tx_data[0]; | |
939 | } else { | |
940 | // Send data from 'trans->tx_buffer' | |
941 | txbuffer=(uint8_t*)trans->tx_buffer; | |
942 | } | |
943 | if (trans->flags & LB_SPI_TRANS_USE_RXDATA) { | |
944 | // Receive data to 'trans->rx_data' | |
945 | rxbuffer=(uint8_t*)&trans->rx_data[0]; | |
946 | } else { | |
947 | // Receive data to 'trans->rx_buffer' | |
948 | rxbuffer=(uint8_t*)trans->rx_buffer; | |
949 | } | |
950 | ||
951 | // ** Set transmit & receive length in bytes | |
952 | uint32_t txlen = trans->length / 8; | |
953 | uint32_t rxlen = trans->rxlength / 8; | |
954 | ||
955 | if (txbuffer == NULL) txlen = 0; | |
956 | if (rxbuffer == NULL) rxlen = 0; | |
957 | if ((rxlen == 0) && (txlen == 0)) { | |
958 | // ** NOTHING TO SEND or RECEIVE, return | |
959 | return ESP_ERR_INVALID_ARG; | |
960 | } | |
961 | ||
962 | // If using 'trans->tx_data' and/or 'trans->rx_data', maximum 4 bytes can be sent/received | |
963 | if ((txbuffer == &trans->tx_data[0]) && (txlen > 4)) return ESP_ERR_INVALID_ARG; | |
964 | if ((rxbuffer == &trans->rx_data[0]) && (rxlen > 4)) return ESP_ERR_INVALID_ARG; | |
965 | ||
966 | // --- Wait for SPI bus ready --- | |
967 | while (host->hw->cmd.usr); | |
968 | ||
969 | // ** If the device was not selected, select it | |
970 | if (handle->cfg.selected == 0) { | |
971 | ret = spi_lobo_device_select(handle, 0); | |
972 | if (ret) return ret; | |
973 | do_deselect = 1; // We will deselect the device after the operation ! | |
974 | } | |
975 | ||
976 | // ** Call pre-transmission callback, if any | |
977 | if (handle->cfg.pre_cb) handle->cfg.pre_cb(trans); | |
978 | ||
979 | // Test if operating in full duplex mode | |
980 | uint8_t duplex = 1; | |
981 | if (handle->cfg.flags & LB_SPI_DEVICE_HALFDUPLEX) duplex = 0; // Half duplex mode ! | |
982 | ||
983 | uint32_t bits, rdbits; | |
984 | uint32_t wd; | |
985 | uint8_t bc, rdidx; | |
986 | uint32_t rdcount = rxlen; // Total number of bytes to read | |
987 | uint32_t count = 0; // number of bytes transmitted | |
988 | uint32_t rd_read = 0; // Number of bytes read so far | |
989 | ||
990 | host->hw->user.usr_mosi_highpart = 0; // use the whole spi buffer | |
991 | ||
992 | // ** Check if address phase will be used | |
993 | host->hw->user2.usr_command_value=trans->command; | |
994 | if (handle->cfg.address_bits>32) { | |
995 | host->hw->addr=trans->address >> 32; | |
996 | host->hw->slv_wr_status=trans->address & 0xffffffff; | |
997 | } else { | |
998 | host->hw->addr=trans->address & 0xffffffff; | |
999 | } | |
1000 | ||
1001 | // Check if we have to transmit some data | |
1002 | if (txlen > 0) { | |
1003 | host->hw->user.usr_mosi = 1; | |
1004 | uint8_t idx; | |
1005 | bits = 0; // remaining bits to send | |
1006 | idx = 0; // index to spi hw data_buf (16 32-bit words, 64 bytes, 512 bits) | |
1007 | ||
1008 | // ** Transmit 'txlen' bytes | |
1009 | while (count < txlen) { | |
1010 | wd = 0; | |
1011 | for (bc=0;bc<32;bc+=8) { | |
1012 | wd |= (uint32_t)txbuffer[count] << bc; | |
1013 | count++; // Increment sent data count | |
1014 | bits += 8; // Increment bits count | |
1015 | if (count == txlen) break; // If all transmit data pushed to hw spi buffer break from the loop | |
1016 | } | |
1017 | host->hw->data_buf[idx] = wd; | |
1018 | idx++; | |
1019 | if (idx == 16) { | |
1020 | // hw SPI buffer full (all 64 bytes filled, START THE TRANSSACTION | |
1021 | host->hw->mosi_dlen.usr_mosi_dbitlen=bits-1; // Set mosi dbitlen | |
1022 | ||
1023 | if ((duplex) && (rdcount > 0)) { | |
1024 | // In full duplex mode we are receiving while sending ! | |
1025 | host->hw->miso_dlen.usr_miso_dbitlen = bits-1; // Set miso dbitlen | |
1026 | host->hw->user.usr_miso = 1; | |
1027 | } | |
1028 | else { | |
1029 | host->hw->miso_dlen.usr_miso_dbitlen = 0; // In half duplex mode nothing will be received | |
1030 | host->hw->user.usr_miso = 0; | |
1031 | } | |
1032 | ||
1033 | // ** Start the transaction *** | |
1034 | host->hw->cmd.usr=1; | |
1035 | // Wait the transaction to finish | |
1036 | while (host->hw->cmd.usr); | |
1037 | ||
1038 | if ((duplex) && (rdcount > 0)) { | |
1039 | // *** in full duplex mode transfer received data to input buffer *** | |
1040 | rdidx = 0; | |
1041 | while (bits > 0) { | |
1042 | wd = host->hw->data_buf[rdidx]; | |
1043 | rdidx++; | |
1044 | for (bc=0;bc<32;bc+=8) { // get max 4 bytes | |
1045 | rxbuffer[rd_read++] = (uint8_t)((wd >> bc) & 0xFF); | |
1046 | rdcount--; | |
1047 | bits -= 8; | |
1048 | if (rdcount == 0) { | |
1049 | bits = 0; | |
1050 | break; // Finished reading data | |
1051 | } | |
1052 | } | |
1053 | } | |
1054 | } | |
1055 | bits = 0; // nothing in hw spi buffer yet | |
1056 | idx = 0; // start from the beginning of the hw spi buffer | |
1057 | } | |
1058 | } | |
1059 | // *** All transmit data are sent or pushed to hw spi buffer | |
1060 | // bits > 0 IF THERE ARE SOME DATA STILL WAITING IN THE HW SPI TRANSMIT BUFFER | |
1061 | if (bits > 0) { | |
1062 | // ** WE HAVE SOME DATA IN THE HW SPI TRANSMIT BUFFER | |
1063 | host->hw->mosi_dlen.usr_mosi_dbitlen = bits-1; // Set mosi dbitlen | |
1064 | ||
1065 | if ((duplex) && (rdcount > 0)) { | |
1066 | // In full duplex mode we are receiving while sending ! | |
1067 | host->hw->miso_dlen.usr_miso_dbitlen = bits-1; // Set miso dbitlen | |
1068 | host->hw->user.usr_miso = 1; | |
1069 | } | |
1070 | else { | |
1071 | host->hw->miso_dlen.usr_miso_dbitlen = 0; // In half duplex mode nothing will be received | |
1072 | host->hw->user.usr_miso = 0; | |
1073 | } | |
1074 | ||
1075 | // ** Start the transaction *** | |
1076 | host->hw->cmd.usr=1; | |
1077 | // Wait the transaction to finish | |
1078 | while (host->hw->cmd.usr); | |
1079 | ||
1080 | if ((duplex) && (rdcount > 0)) { | |
1081 | // *** in full duplex mode transfer received data to input buffer *** | |
1082 | rdidx = 0; | |
1083 | while (bits > 0) { | |
1084 | wd = host->hw->data_buf[rdidx]; | |
1085 | rdidx++; | |
1086 | for (bc=0;bc<32;bc+=8) { // get max 4 bytes | |
1087 | rxbuffer[rd_read++] = (uint8_t)((wd >> bc) & 0xFF); | |
1088 | rdcount--; | |
1089 | bits -= 8; | |
1090 | if (bits == 0) break; | |
1091 | if (rdcount == 0) { | |
1092 | bits = 0; | |
1093 | break; // Finished reading data | |
1094 | } | |
1095 | } | |
1096 | } | |
1097 | } | |
1098 | } | |
1099 | //if (duplex) rdcount = 0; // In duplex mode receive only as many bytes as was transmitted | |
1100 | } | |
1101 | ||
1102 | // ------------------------------------------------------------------------ | |
1103 | // *** If rdcount = 0 we have nothing to receive and we exit the function | |
1104 | // This is true if no data receive was requested, | |
1105 | // or all the data was received in Full duplex mode during the transmission | |
1106 | // ------------------------------------------------------------------------ | |
1107 | if (rdcount > 0) { | |
1108 | // ---------------------------------------------------------------------------------------------------------------- | |
1109 | // *** rdcount > 0, we have to receive some data | |
1110 | // This is true if we operate in Half duplex mode when receiving after transmission is done, | |
1111 | // or not all data was received in Full duplex mode during the transmission (trans->rxlength > trans->txlength) | |
1112 | // ---------------------------------------------------------------------------------------------------------------- | |
1113 | host->hw->user.usr_mosi = 0; // do not send | |
1114 | host->hw->user.usr_miso = 1; // do receive | |
1115 | while (rdcount > 0) { | |
1116 | if (rdcount <= 64) rdbits = rdcount * 8; | |
1117 | else rdbits = 64 * 8; | |
1118 | ||
1119 | // Load receive buffer | |
1120 | host->hw->mosi_dlen.usr_mosi_dbitlen=0; | |
1121 | host->hw->miso_dlen.usr_miso_dbitlen=rdbits-1; | |
1122 | ||
1123 | // ** Start the transaction *** | |
1124 | host->hw->cmd.usr=1; | |
1125 | // Wait the transaction to finish | |
1126 | while (host->hw->cmd.usr); | |
1127 | ||
1128 | // *** transfer received data to input buffer *** | |
1129 | rdidx = 0; | |
1130 | while (rdbits > 0) { | |
1131 | wd = host->hw->data_buf[rdidx]; | |
1132 | rdidx++; | |
1133 | for (bc=0;bc<32;bc+=8) { | |
1134 | rxbuffer[rd_read++] = (uint8_t)((wd >> bc) & 0xFF); | |
1135 | rdcount--; | |
1136 | rdbits -= 8; | |
1137 | if (rdcount == 0) { | |
1138 | rdbits = 0; | |
1139 | break; | |
1140 | } | |
1141 | } | |
1142 | } | |
1143 | } | |
1144 | } | |
1145 | ||
1146 | // ** Call post-transmission callback, if any | |
1147 | if (handle->cfg.post_cb) handle->cfg.post_cb(trans); | |
1148 | ||
1149 | if (do_deselect) { | |
1150 | // Spi device was selected in this function, we have to deselect it now | |
1151 | ret = spi_lobo_device_deselect(handle); | |
1152 | if (ret) return ret; | |
1153 | } | |
1154 | ||
1155 | return ESP_OK; | |
1156 | } |