diff --git a/cores/esp8266/core_esp8266_spi_utils.cpp b/cores/esp8266/core_esp8266_spi_utils.cpp new file mode 100644 index 000000000..110b13968 --- /dev/null +++ b/cores/esp8266/core_esp8266_spi_utils.cpp @@ -0,0 +1,191 @@ +/* + core_esp8266_spi_utils.cpp + + Copyright (c) 2019 Mike Nix. All rights reserved. + This file is part of the esp8266 core for Arduino environment. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include +#include + +// register names +#include "esp8266_peri.h" + +// for flashchip +#include "spi_flash.h" + +// for PRECACHE_* +#include "core_esp8266_features.h" + +#include "spi_utils.h" + +extern "C" uint32_t Wait_SPI_Idle(SpiFlashChip *fc); + +namespace experimental { + +/* + * critical part of SPICommand. + * Kept in a separate function to aid with precaching + * PRECACHE_* saves having to make the function IRAM_ATTR. + * + * spiIfNum needs to be volatile to keep the optimiser from + * deciding it can be treated as a constant (due to this being a + * static function only called with spiIfNum set to 0) + * + * Note: if porting to ESP32 mosi/miso bits are set in 2 registers, not 1. + */ +static SpiOpResult PRECACHE_ATTR +_SPICommand(volatile uint32_t spiIfNum, + uint32_t spic,uint32_t spiu,uint32_t spiu1,uint32_t spiu2, + uint32_t *data,uint32_t writeWords,uint32_t readWords) +{ + if (spiIfNum>1) + return SPI_RESULT_ERR; + + // force SPI register access via base+offest. + // Prevents loading individual address constants from flash. + uint32_t *spibase = (uint32_t*)(spiIfNum ? &(SPI1CMD) : &(SPI0CMD)); + #define SPIREG(reg) (*((volatile uint32_t *)(spibase+(&(reg) - &(SPI0CMD))))) + + // preload any constants and functions we need into variables + // Everything defined here must be volatile or the optimizer can + // treat them as constants, resulting in the flash reads we're + // trying to avoid + uint32_t (* volatile Wait_SPI_Idlep)(SpiFlashChip *) = Wait_SPI_Idle; + volatile SpiFlashChip *fchip=flashchip; + volatile uint32_t spicmdusr=SPICMDUSR; + + if (!spiIfNum) { + // Only need to precache when using SPI0 + PRECACHE_START(); + Wait_SPI_Idlep((SpiFlashChip *)fchip); + } + + // preserve essential controller state such as incoming/outgoing + // data lengths and IO mode. + uint32_t oldSPI0U = SPIREG(SPI0U); + uint32_t oldSPI0U2= SPIREG(SPI0U2); + uint32_t oldSPI0C = SPIREG(SPI0C); + + //SPI0S &= ~(SPISE|SPISBE|SPISSE|SPISCD); + SPIREG(SPI0C) = spic; + SPIREG(SPI0U) = spiu; + SPIREG(SPI0U1)= spiu1; + SPIREG(SPI0U2)= spiu2; + + if (writeWords>0) { + // copy the outgoing data to the SPI hardware + uint32_t *src=data; + volatile uint32_t *dst=&SPIREG(SPI0W0); + for (uint32_t i=0; i0) && (timeout>0)) { + // copy the response back to the buffer + uint32_t *dst=data; + volatile uint32_t *src=&SPIREG(SPI0W0); + for (uint32_t i=0; i0 ? SPI_RESULT_OK : SPI_RESULT_TIMEOUT); +} + + +/* SPI0Command: send a custom SPI command. + * This part calculates register values and passes them to _SPI0Command(). + * Parameters: + * cmd The command byte (first 8 bits) to send to the SPI device + * *data The buffer containing the outgoing data for the SPI bus. + * The data is expected to be mosi_bits long, and the buffer + * is overwritten by the incoming bus data, which will be + * miso_bits long. + * mosi_bits + * Number of bits to be sent after the command byte. + * miso_bits + * Number of bits to read from the SPI bus after the outgoing + * data has been sent. + * + * Note: This code has only been tested with SPI bus 0, but should work + * equally well with other busses. The ESP8266 has bus 0 and 1, + * newer chips may have more one day. + */ +SpiOpResult SPI0Command(uint8_t cmd, uint32_t *data, uint32_t mosi_bits, uint32_t miso_bits) { + if (mosi_bits>(64*8)) + return SPI_RESULT_ERR; + if (miso_bits>(64*8)) + return SPI_RESULT_ERR; + + // Calculate the number of data words (aka registers) that need to be copied + // to/from the SPI controller. + uint32_t mosi_words=mosi_bits/32; + uint32_t miso_words=miso_bits/32; + if (mosi_bits % 32 != 0) + mosi_words++; + if (miso_bits % 32 != 0) + miso_words++; + + // Select user defined command mode in the controller + uint32_t spiu=SPIUCOMMAND; //SPI_USR_COMMAND + + // Set the command byte to send + uint32_t spiu2 = ((7 & SPIMCOMMAND)<0) { + // set the number of outgoing data bits to send + spiu1 |= ((mosi_bits-1) & SPIMMOSI) << SPILMOSI; + spiu |= SPIUMOSI; // SPI_USR_MOSI + } + if (miso_bits>0) { + // set the number of incoming bits to read + spiu1 |= ((miso_bits-1) & SPIMMISO) << SPILMISO; + spiu |= SPIUMISO; // SPI_USR_MISO + } + + uint32_t spic = SPI0C; + // Select the most basic IO mode for maximum compatibility + // Some flash commands are only available in this mode. + spic &= ~(SPICQIO | SPICDIO | SPICQOUT | SPICDOUT | SPICAHB | SPICFASTRD); + spic |= (SPICRESANDRES | SPICSHARE | SPICWPR | SPIC2BSE); + + SpiOpResult rc =_SPICommand(0,spic,spiu,spiu1,spiu2,data,mosi_words,miso_words); + + if (rc==SPI_RESULT_OK) { + // clear any bits we did not read in the last word. + if (miso_bits % 32) { + data[miso_bits/32] &= ~(0xFFFFFFFF << (miso_bits % 32)); + } + } + return rc; +} + +} // namespace experimental diff --git a/cores/esp8266/spi_utils.h b/cores/esp8266/spi_utils.h new file mode 100644 index 000000000..bf0928f28 --- /dev/null +++ b/cores/esp8266/spi_utils.h @@ -0,0 +1,46 @@ +/* + spi_utils.h - SPI utility function + Copyright (c) 2015 Ivan Grokhotkov. All right reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + + +#ifndef SPI_UTILS_H +#define SPI_UTILS_H + + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +namespace experimental { +typedef enum { + SPI_RESULT_OK, + SPI_RESULT_ERR, + SPI_RESULT_TIMEOUT +} SpiOpResult; + +SpiOpResult SPI0Command(uint8_t cmd, uint32_t *data, uint32_t mosi_bits, uint32_t miso_bits); +} + +#ifdef __cplusplus +} +#endif + + +#endif //SPI_UTILS_H