Skip to content

Commit 60c8975

Browse files
ChocolateFrogsNutsdevyte
authored andcommitted
Spi0command (#6674)
* precache() - preload code into the flash cache. By preloading code into the flash cache we can take control over when SPI Flash reads will occur when code is executing. This can be useful where the timing of a section of code is extremely critical and we don't want random pauses to pull code in from the SPI flash chip. It can also be useful for code that accesses/uses SPI0 which is connected to the flash chip. Non interrupt handler code that is infrequently called but might otherwise require being in valuable IRAM - such as bit-banging I/O code or some code run at bootup can avoid being permanently in IRAM. Macros are provided to make precaching one or more blocks of code in any function easy. * Fix missing include * Make precache extern "C" * Attempt 2 at making precache extern "C" * Fix calculation of number of cache lines to preload With certain alignments/lengths of code it was possible to not read enough into the flash cache. This commit makes the length calculation clearer and adds an extra cache line to ensure we precache enough code. * SPI0Command - A utility function for generic SPI commands on SPI0 The rom code does not support some flash functions, or have a generic way of sending custom commands to the flash chip. In particular XMC flash chips have a third status register, and the ROM only supports two. There are also certain requirements for using SPI0 such as waiting for the flash to be idle and not allowing your code to trigger a flash cache miss while using SPI0. * Clean some trailing spaces * Upgrade _SPI0Command to _SPICommand We needed to assess the SPI registers as base+offset to avoid referring to the registers using constant addresses as these addresses were loaded from flash and had the potential to trigger a flash cache miss. For similar reasons functions need to be called via function pointers stored in RAM. Also avoid constants in FLASH, use a copy stored in RAM. As a side effect we can now select which controller to access as a parameter. * Tidy up a comment thats no longer applicable * Comments, formatting and variable renames Added a number of comments to better explain the code and improved the formatting. Also renamed some variables for consistency. * put SPI0Command in namespace experimental * Add a comment noting that the code has only been tested on bus 0 * Replace use of memcpy with for loops in _SPICommand() memcpy is not guaranteed to be safe (IRAM_ATTR or ROM) like I thought. As a bonus the for loop is guaranteed to do 32-bit wide transfers, unlike memcpy. * Typo fix what happens when you forget to edit after copy/paste * Move the SpiOpResult enum into experimental namespace
1 parent 692e542 commit 60c8975

File tree

2 files changed

+237
-0
lines changed

2 files changed

+237
-0
lines changed
+191
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
/*
2+
core_esp8266_spi_utils.cpp
3+
4+
Copyright (c) 2019 Mike Nix. All rights reserved.
5+
This file is part of the esp8266 core for Arduino environment.
6+
7+
This library is free software; you can redistribute it and/or
8+
modify it under the terms of the GNU Lesser General Public
9+
License as published by the Free Software Foundation; either
10+
version 2.1 of the License, or (at your option) any later version.
11+
12+
This library is distributed in the hope that it will be useful,
13+
but WITHOUT ANY WARRANTY; without even the implied warranty of
14+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15+
Lesser General Public License for more details.
16+
17+
You should have received a copy of the GNU Lesser General Public
18+
License along with this library; if not, write to the Free Software
19+
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
20+
*/
21+
22+
#include <stdint.h>
23+
#include <string.h>
24+
25+
// register names
26+
#include "esp8266_peri.h"
27+
28+
// for flashchip
29+
#include "spi_flash.h"
30+
31+
// for PRECACHE_*
32+
#include "core_esp8266_features.h"
33+
34+
#include "spi_utils.h"
35+
36+
extern "C" uint32_t Wait_SPI_Idle(SpiFlashChip *fc);
37+
38+
namespace experimental {
39+
40+
/*
41+
* critical part of SPICommand.
42+
* Kept in a separate function to aid with precaching
43+
* PRECACHE_* saves having to make the function IRAM_ATTR.
44+
*
45+
* spiIfNum needs to be volatile to keep the optimiser from
46+
* deciding it can be treated as a constant (due to this being a
47+
* static function only called with spiIfNum set to 0)
48+
*
49+
* Note: if porting to ESP32 mosi/miso bits are set in 2 registers, not 1.
50+
*/
51+
static SpiOpResult PRECACHE_ATTR
52+
_SPICommand(volatile uint32_t spiIfNum,
53+
uint32_t spic,uint32_t spiu,uint32_t spiu1,uint32_t spiu2,
54+
uint32_t *data,uint32_t writeWords,uint32_t readWords)
55+
{
56+
if (spiIfNum>1)
57+
return SPI_RESULT_ERR;
58+
59+
// force SPI register access via base+offest.
60+
// Prevents loading individual address constants from flash.
61+
uint32_t *spibase = (uint32_t*)(spiIfNum ? &(SPI1CMD) : &(SPI0CMD));
62+
#define SPIREG(reg) (*((volatile uint32_t *)(spibase+(&(reg) - &(SPI0CMD)))))
63+
64+
// preload any constants and functions we need into variables
65+
// Everything defined here must be volatile or the optimizer can
66+
// treat them as constants, resulting in the flash reads we're
67+
// trying to avoid
68+
uint32_t (* volatile Wait_SPI_Idlep)(SpiFlashChip *) = Wait_SPI_Idle;
69+
volatile SpiFlashChip *fchip=flashchip;
70+
volatile uint32_t spicmdusr=SPICMDUSR;
71+
72+
if (!spiIfNum) {
73+
// Only need to precache when using SPI0
74+
PRECACHE_START();
75+
Wait_SPI_Idlep((SpiFlashChip *)fchip);
76+
}
77+
78+
// preserve essential controller state such as incoming/outgoing
79+
// data lengths and IO mode.
80+
uint32_t oldSPI0U = SPIREG(SPI0U);
81+
uint32_t oldSPI0U2= SPIREG(SPI0U2);
82+
uint32_t oldSPI0C = SPIREG(SPI0C);
83+
84+
//SPI0S &= ~(SPISE|SPISBE|SPISSE|SPISCD);
85+
SPIREG(SPI0C) = spic;
86+
SPIREG(SPI0U) = spiu;
87+
SPIREG(SPI0U1)= spiu1;
88+
SPIREG(SPI0U2)= spiu2;
89+
90+
if (writeWords>0) {
91+
// copy the outgoing data to the SPI hardware
92+
uint32_t *src=data;
93+
volatile uint32_t *dst=&SPIREG(SPI0W0);
94+
for (uint32_t i=0; i<writeWords; i++)
95+
*dst++ = *src++;
96+
}
97+
98+
// Start the transfer
99+
SPIREG(SPI0CMD) = spicmdusr;
100+
101+
// wait for the command to complete (typically only 1-3 iterations)
102+
uint32_t timeout = 1000;
103+
while ((SPIREG(SPI0CMD) & spicmdusr) && timeout--);
104+
105+
if ((readWords>0) && (timeout>0)) {
106+
// copy the response back to the buffer
107+
uint32_t *dst=data;
108+
volatile uint32_t *src=&SPIREG(SPI0W0);
109+
for (uint32_t i=0; i<readWords; i++)
110+
*dst++ = *src++;
111+
}
112+
113+
// Restore saved registers
114+
SPIREG(SPI0U) = oldSPI0U;
115+
SPIREG(SPI0U2)= oldSPI0U2;
116+
SPIREG(SPI0C) = oldSPI0C;
117+
118+
PRECACHE_END();
119+
return (timeout>0 ? SPI_RESULT_OK : SPI_RESULT_TIMEOUT);
120+
}
121+
122+
123+
/* SPI0Command: send a custom SPI command.
124+
* This part calculates register values and passes them to _SPI0Command().
125+
* Parameters:
126+
* cmd The command byte (first 8 bits) to send to the SPI device
127+
* *data The buffer containing the outgoing data for the SPI bus.
128+
* The data is expected to be mosi_bits long, and the buffer
129+
* is overwritten by the incoming bus data, which will be
130+
* miso_bits long.
131+
* mosi_bits
132+
* Number of bits to be sent after the command byte.
133+
* miso_bits
134+
* Number of bits to read from the SPI bus after the outgoing
135+
* data has been sent.
136+
*
137+
* Note: This code has only been tested with SPI bus 0, but should work
138+
* equally well with other busses. The ESP8266 has bus 0 and 1,
139+
* newer chips may have more one day.
140+
*/
141+
SpiOpResult SPI0Command(uint8_t cmd, uint32_t *data, uint32_t mosi_bits, uint32_t miso_bits) {
142+
if (mosi_bits>(64*8))
143+
return SPI_RESULT_ERR;
144+
if (miso_bits>(64*8))
145+
return SPI_RESULT_ERR;
146+
147+
// Calculate the number of data words (aka registers) that need to be copied
148+
// to/from the SPI controller.
149+
uint32_t mosi_words=mosi_bits/32;
150+
uint32_t miso_words=miso_bits/32;
151+
if (mosi_bits % 32 != 0)
152+
mosi_words++;
153+
if (miso_bits % 32 != 0)
154+
miso_words++;
155+
156+
// Select user defined command mode in the controller
157+
uint32_t spiu=SPIUCOMMAND; //SPI_USR_COMMAND
158+
159+
// Set the command byte to send
160+
uint32_t spiu2 = ((7 & SPIMCOMMAND)<<SPILCOMMAND) | cmd;
161+
162+
uint32_t spiu1 = 0;
163+
if (mosi_bits>0) {
164+
// set the number of outgoing data bits to send
165+
spiu1 |= ((mosi_bits-1) & SPIMMOSI) << SPILMOSI;
166+
spiu |= SPIUMOSI; // SPI_USR_MOSI
167+
}
168+
if (miso_bits>0) {
169+
// set the number of incoming bits to read
170+
spiu1 |= ((miso_bits-1) & SPIMMISO) << SPILMISO;
171+
spiu |= SPIUMISO; // SPI_USR_MISO
172+
}
173+
174+
uint32_t spic = SPI0C;
175+
// Select the most basic IO mode for maximum compatibility
176+
// Some flash commands are only available in this mode.
177+
spic &= ~(SPICQIO | SPICDIO | SPICQOUT | SPICDOUT | SPICAHB | SPICFASTRD);
178+
spic |= (SPICRESANDRES | SPICSHARE | SPICWPR | SPIC2BSE);
179+
180+
SpiOpResult rc =_SPICommand(0,spic,spiu,spiu1,spiu2,data,mosi_words,miso_words);
181+
182+
if (rc==SPI_RESULT_OK) {
183+
// clear any bits we did not read in the last word.
184+
if (miso_bits % 32) {
185+
data[miso_bits/32] &= ~(0xFFFFFFFF << (miso_bits % 32));
186+
}
187+
}
188+
return rc;
189+
}
190+
191+
} // namespace experimental

cores/esp8266/spi_utils.h

+46
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
/*
2+
spi_utils.h - SPI utility function
3+
Copyright (c) 2015 Ivan Grokhotkov. All right reserved.
4+
5+
This library is free software; you can redistribute it and/or
6+
modify it under the terms of the GNU Lesser General Public
7+
License as published by the Free Software Foundation; either
8+
version 2.1 of the License, or (at your option) any later version.
9+
10+
This library is distributed in the hope that it will be useful,
11+
but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13+
Lesser General Public License for more details.
14+
15+
You should have received a copy of the GNU Lesser General Public
16+
License along with this library; if not, write to the Free Software
17+
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
18+
*/
19+
20+
21+
#ifndef SPI_UTILS_H
22+
#define SPI_UTILS_H
23+
24+
25+
#ifdef __cplusplus
26+
extern "C" {
27+
#endif
28+
29+
#include <stdint.h>
30+
31+
namespace experimental {
32+
typedef enum {
33+
SPI_RESULT_OK,
34+
SPI_RESULT_ERR,
35+
SPI_RESULT_TIMEOUT
36+
} SpiOpResult;
37+
38+
SpiOpResult SPI0Command(uint8_t cmd, uint32_t *data, uint32_t mosi_bits, uint32_t miso_bits);
39+
}
40+
41+
#ifdef __cplusplus
42+
}
43+
#endif
44+
45+
46+
#endif //SPI_UTILS_H

0 commit comments

Comments
 (0)