2017-01-29 22:29:50 -05:00
|
|
|
// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD
|
|
|
|
//
|
|
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
// you may not use this file except in compliance with the License.
|
|
|
|
// You may obtain a copy of the License at
|
|
|
|
|
|
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
//
|
|
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
// See the License for the specific language governing permissions and
|
|
|
|
// limitations under the License.
|
|
|
|
#include <stddef.h>
|
|
|
|
#include <stdint.h>
|
|
|
|
#include "flash_qio_mode.h"
|
|
|
|
#include "esp_log.h"
|
2017-06-19 20:56:29 -04:00
|
|
|
#include "esp_err.h"
|
2019-03-14 05:29:32 -04:00
|
|
|
#include "esp32/rom/spi_flash.h"
|
|
|
|
#include "esp32/rom/efuse.h"
|
2017-01-29 22:29:50 -05:00
|
|
|
#include "soc/spi_struct.h"
|
global: move the soc component out of the common list
This MR removes the common dependency from every IDF components to the SOC component.
Currently, in the ``idf_functions.cmake`` script, we include the header path of SOC component by default for all components.
But for better code organization (or maybe also benifits to the compiling speed), we may remove the dependency to SOC components for most components except the driver and kernel related components.
In CMAKE, we have two kinds of header visibilities (set by include path visibility):
(Assume component A --(depends on)--> B, B is the current component)
1. public (``COMPONENT_ADD_INCLUDEDIRS``): means this path is visible to other depending components (A) (visible to A and B)
2. private (``COMPONENT_PRIV_INCLUDEDIRS``): means this path is only visible to source files inside the component (visible to B only)
and we have two kinds of depending ways:
(Assume component A --(depends on)--> B --(depends on)--> C, B is the current component)
1. public (```COMPONENT_REQUIRES```): means B can access to public include path of C. All other components rely on you (A) will also be available for the public headers. (visible to A, B)
2. private (``COMPONENT_PRIV_REQUIRES``): means B can access to public include path of C, but don't propagate this relation to other components (A). (visible to B)
1. remove the common requirement in ``idf_functions.cmake``, this makes the SOC components invisible to all other components by default.
2. if a component (for example, DRIVER) really needs the dependency to SOC, add a private dependency to SOC for it.
3. some other components that don't really depends on the SOC may still meet some errors saying "can't find header soc/...", this is because it's depended component (DRIVER) incorrectly include the header of SOC in its public headers. Moving all this kind of #include into source files, or private headers
4. Fix the include requirements for some file which miss sufficient #include directives. (Previously they include some headers by the long long long header include link)
This is a breaking change. Previous code may depends on the long include chain.
You may need to include the following headers for some files after this commit:
- soc/soc.h
- soc/soc_memory_layout.h
- driver/gpio.h
- esp_sleep.h
The major broken include chain includes:
1. esp_system.h no longer includes esp_sleep.h. The latter includes driver/gpio.h and driver/touch_pad.h.
2. ets_sys.h no longer includes soc/soc.h
3. freertos/portmacro.h no longer includes soc/soc_memory_layout.h
some peripheral headers no longer includes their hw related headers, e.g. rom/gpio.h no longer includes soc/gpio_pins.h and soc/gpio_reg.h
BREAKING CHANGE
2019-04-03 01:17:38 -04:00
|
|
|
#include "soc/spi_reg.h"
|
2017-04-13 03:08:53 -04:00
|
|
|
#include "soc/efuse_reg.h"
|
2017-01-29 22:29:50 -05:00
|
|
|
#include "sdkconfig.h"
|
|
|
|
|
|
|
|
/* SPI flash controller */
|
|
|
|
#define SPIFLASH SPI1
|
|
|
|
|
|
|
|
/* SPI commands (actual on-wire commands not SPI controller bitmasks)
|
|
|
|
Suitable for use with the execute_flash_command static function.
|
|
|
|
*/
|
|
|
|
#define CMD_RDID 0x9F
|
|
|
|
#define CMD_WRSR 0x01
|
|
|
|
#define CMD_WRSR2 0x31 /* Not all SPI flash uses this command */
|
|
|
|
#define CMD_WREN 0x06
|
|
|
|
#define CMD_WRDI 0x04
|
|
|
|
#define CMD_RDSR 0x05
|
|
|
|
#define CMD_RDSR2 0x35 /* Not all SPI flash uses this command */
|
2018-07-11 09:36:35 -04:00
|
|
|
#define CMD_OTPEN 0x3A /* Enable OTP mode, not all SPI flash uses this command */
|
2017-01-29 22:29:50 -05:00
|
|
|
|
|
|
|
static const char *TAG = "qio_mode";
|
|
|
|
|
2017-01-31 21:14:09 -05:00
|
|
|
typedef unsigned (*read_status_fn_t)();
|
|
|
|
typedef void (*write_status_fn_t)(unsigned);
|
|
|
|
|
2017-01-29 22:29:50 -05:00
|
|
|
typedef struct __attribute__((packed)) {
|
|
|
|
const char *manufacturer;
|
|
|
|
uint8_t mfg_id; /* 8-bit JEDEC manufacturer ID */
|
|
|
|
uint16_t flash_id; /* 16-bit JEDEC flash chip ID */
|
|
|
|
uint16_t id_mask; /* Bits to match on in flash chip ID */
|
2017-01-31 21:14:09 -05:00
|
|
|
read_status_fn_t read_status_fn;
|
|
|
|
write_status_fn_t write_status_fn;
|
|
|
|
uint8_t status_qio_bit;
|
2017-01-29 22:29:50 -05:00
|
|
|
} qio_info_t;
|
|
|
|
|
2017-01-31 21:14:09 -05:00
|
|
|
/* Read 8 bit status using RDSR command */
|
|
|
|
static unsigned read_status_8b_rdsr();
|
|
|
|
/* Read 8 bit status (second byte) using RDSR2 command */
|
|
|
|
static unsigned read_status_8b_rdsr2();
|
|
|
|
/* read 16 bit status using RDSR & RDSR2 (low and high bytes) */
|
|
|
|
static unsigned read_status_16b_rdsr_rdsr2();
|
|
|
|
|
|
|
|
/* Write 8 bit status using WRSR */
|
|
|
|
static void write_status_8b_wrsr(unsigned new_status);
|
|
|
|
/* Write 8 bit status (second byte) using WRSR2 */
|
|
|
|
static void write_status_8b_wrsr2(unsigned new_status);
|
|
|
|
/* Write 16 bit status using WRSR */
|
|
|
|
static void write_status_16b_wrsr(unsigned new_status);
|
|
|
|
|
2018-07-11 09:36:35 -04:00
|
|
|
/* Read 8 bit status of XM25QU64A */
|
|
|
|
static unsigned read_status_8b_xmc25qu64a();
|
|
|
|
/* Write 8 bit status of XM25QU64A */
|
|
|
|
static void write_status_8b_xmc25qu64a(unsigned new_status);
|
|
|
|
|
2017-06-19 20:47:46 -04:00
|
|
|
#define ESP32_D2WD_WP_GPIO 7 /* ESP32-D2WD has this GPIO wired to WP pin of flash */
|
|
|
|
|
|
|
|
#ifndef CONFIG_BOOTLOADER_SPI_WP_PIN // Set in menuconfig if SPI flasher config is set to a quad mode
|
|
|
|
#define CONFIG_BOOTLOADER_SPI_WP_PIN ESP32_D2WD_WP_GPIO
|
|
|
|
#endif
|
|
|
|
|
2017-01-29 22:29:50 -05:00
|
|
|
/* Array of known flash chips and data to enable Quad I/O mode
|
|
|
|
|
|
|
|
Manufacturer & flash ID can be tested by running "esptool.py
|
|
|
|
flash_id"
|
|
|
|
|
|
|
|
If manufacturer ID matches, and flash ID ORed with flash ID mask
|
|
|
|
matches, enable_qio_mode() will execute "Read Cmd", test if bit
|
|
|
|
number "QIE Bit" is set, and if not set it will call "Write Cmd"
|
|
|
|
with this bit set.
|
|
|
|
|
|
|
|
Searching of this table stops when the first match is found.
|
|
|
|
*/
|
|
|
|
const static qio_info_t chip_data[] = {
|
2018-07-11 09:36:35 -04:00
|
|
|
/* Manufacturer, mfg_id, flash_id, id mask, Read Status, Write Status, QIE Bit */
|
|
|
|
{ "MXIC", 0xC2, 0x2000, 0xFF00, read_status_8b_rdsr, write_status_8b_wrsr, 6 },
|
|
|
|
{ "ISSI", 0x9D, 0x4000, 0xCF00, read_status_8b_rdsr, write_status_8b_wrsr, 6 }, /* IDs 0x40xx, 0x70xx */
|
|
|
|
{ "WinBond", 0xEF, 0x4000, 0xFF00, read_status_16b_rdsr_rdsr2, write_status_16b_wrsr, 9 },
|
|
|
|
{ "GD", 0xC8, 0x6000, 0xFF00, read_status_16b_rdsr_rdsr2, write_status_16b_wrsr, 9 },
|
|
|
|
{ "XM25QU64A", 0x20, 0x3817, 0xFFFF, read_status_8b_xmc25qu64a, write_status_8b_xmc25qu64a, 6 },
|
2017-01-29 22:29:50 -05:00
|
|
|
|
|
|
|
/* Final entry is default entry, if no other IDs have matched.
|
|
|
|
|
|
|
|
This approach works for chips including:
|
|
|
|
GigaDevice (mfg ID 0xC8, flash IDs including 4016),
|
2017-01-31 21:14:09 -05:00
|
|
|
FM25Q32 (QOUT mode only, mfg ID 0xA1, flash IDs including 4016)
|
2017-01-29 22:29:50 -05:00
|
|
|
*/
|
2018-07-11 09:36:35 -04:00
|
|
|
{ NULL, 0xFF, 0xFFFF, 0xFFFF, read_status_8b_rdsr2, write_status_8b_wrsr2, 1 },
|
2017-01-29 22:29:50 -05:00
|
|
|
};
|
|
|
|
|
|
|
|
#define NUM_CHIPS (sizeof(chip_data) / sizeof(qio_info_t))
|
|
|
|
|
2017-06-19 20:56:29 -04:00
|
|
|
static esp_err_t enable_qio_mode(read_status_fn_t read_status_fn,
|
2017-01-31 21:14:09 -05:00
|
|
|
write_status_fn_t write_status_fn,
|
2017-01-29 22:29:50 -05:00
|
|
|
uint8_t status_qio_bit);
|
|
|
|
|
|
|
|
/* Generic function to use the "user command" SPI controller functionality
|
|
|
|
to send commands to the SPI flash and read the respopnse.
|
|
|
|
|
|
|
|
The command passed here is always the on-the-wire command given to the SPI flash unit.
|
|
|
|
*/
|
|
|
|
static uint32_t execute_flash_command(uint8_t command, uint32_t mosi_data, uint8_t mosi_len, uint8_t miso_len);
|
|
|
|
|
2017-04-13 03:08:53 -04:00
|
|
|
/* dummy_len_plus values defined in ROM for SPI flash configuration */
|
|
|
|
extern uint8_t g_rom_spiflash_dummy_len_plus[];
|
2018-04-20 04:59:25 -04:00
|
|
|
uint32_t bootloader_read_flash_id()
|
|
|
|
{
|
|
|
|
uint32_t id = execute_flash_command(CMD_RDID, 0, 0, 24);
|
|
|
|
id = ((id & 0xff) << 16) | ((id >> 16) & 0xff) | (id & 0xff00);
|
|
|
|
return id;
|
|
|
|
}
|
2017-04-13 03:08:53 -04:00
|
|
|
|
2017-01-29 22:29:50 -05:00
|
|
|
void bootloader_enable_qio_mode(void)
|
|
|
|
{
|
|
|
|
uint32_t raw_flash_id;
|
|
|
|
uint8_t mfg_id;
|
|
|
|
uint16_t flash_id;
|
|
|
|
int i;
|
|
|
|
|
|
|
|
ESP_LOGD(TAG, "Probing for QIO mode enable...");
|
2017-03-09 02:29:00 -05:00
|
|
|
esp_rom_spiflash_wait_idle(&g_rom_flashchip);
|
2017-01-29 22:29:50 -05:00
|
|
|
|
2018-04-20 04:59:25 -04:00
|
|
|
raw_flash_id = g_rom_flashchip.device_id;
|
2017-01-29 22:29:50 -05:00
|
|
|
ESP_LOGD(TAG, "Raw SPI flash chip id 0x%x", raw_flash_id);
|
|
|
|
|
2018-04-20 04:59:25 -04:00
|
|
|
mfg_id = (raw_flash_id >> 16) & 0xFF;
|
|
|
|
flash_id = raw_flash_id & 0xFFFF;
|
2017-01-29 22:29:50 -05:00
|
|
|
ESP_LOGD(TAG, "Manufacturer ID 0x%02x chip ID 0x%04x", mfg_id, flash_id);
|
|
|
|
|
|
|
|
for (i = 0; i < NUM_CHIPS-1; i++) {
|
|
|
|
const qio_info_t *chip = &chip_data[i];
|
|
|
|
if (mfg_id == chip->mfg_id && (flash_id & chip->id_mask) == (chip->flash_id & chip->id_mask)) {
|
|
|
|
ESP_LOGI(TAG, "Enabling QIO for flash chip %s", chip_data[i].manufacturer);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (i == NUM_CHIPS - 1) {
|
|
|
|
ESP_LOGI(TAG, "Enabling default flash chip QIO");
|
|
|
|
}
|
|
|
|
|
2018-05-15 05:14:52 -04:00
|
|
|
enable_qio_mode(chip_data[i].read_status_fn,
|
|
|
|
chip_data[i].write_status_fn,
|
|
|
|
chip_data[i].status_qio_bit);
|
2017-01-29 22:29:50 -05:00
|
|
|
}
|
|
|
|
|
2017-06-19 20:56:29 -04:00
|
|
|
static esp_err_t enable_qio_mode(read_status_fn_t read_status_fn,
|
2017-01-31 21:14:09 -05:00
|
|
|
write_status_fn_t write_status_fn,
|
2017-01-29 22:29:50 -05:00
|
|
|
uint8_t status_qio_bit)
|
|
|
|
{
|
|
|
|
uint32_t status;
|
2017-04-13 03:08:53 -04:00
|
|
|
const uint32_t spiconfig = ets_efuse_get_spiconfig();
|
|
|
|
|
|
|
|
if (spiconfig != EFUSE_SPICONFIG_SPI_DEFAULTS && spiconfig != EFUSE_SPICONFIG_HSPI_DEFAULTS) {
|
2017-06-19 20:47:46 -04:00
|
|
|
// spiconfig specifies a custom efuse pin configuration. This config defines all pins -except- WP,
|
|
|
|
// which is compiled into the bootloader instead.
|
2017-04-13 03:08:53 -04:00
|
|
|
//
|
2018-02-28 04:12:34 -05:00
|
|
|
// Most commonly an overriden pin mapping means ESP32-D2WD or ESP32-PICOD4.
|
|
|
|
//Warn if chip is ESP32-D2WD/ESP32-PICOD4 but someone has changed the WP pin
|
|
|
|
//assignment from that chip's WP pin.
|
|
|
|
uint32_t pkg_ver = REG_GET_FIELD(EFUSE_BLK0_RDATA3_REG, EFUSE_RD_CHIP_VER_PKG);
|
|
|
|
if (CONFIG_BOOTLOADER_SPI_WP_PIN != ESP32_D2WD_WP_GPIO &&
|
|
|
|
(pkg_ver == EFUSE_RD_CHIP_VER_PKG_ESP32D2WDQ5 ||
|
|
|
|
pkg_ver == EFUSE_RD_CHIP_VER_PKG_ESP32PICOD2 ||
|
|
|
|
pkg_ver == EFUSE_RD_CHIP_VER_PKG_ESP32PICOD4)) {
|
|
|
|
ESP_LOGW(TAG, "Chip is ESP32-D2WD/ESP32-PICOD4 but flash WP pin is different value to internal flash");
|
2017-04-13 03:08:53 -04:00
|
|
|
}
|
|
|
|
}
|
2017-01-29 22:29:50 -05:00
|
|
|
|
2017-03-09 02:29:00 -05:00
|
|
|
esp_rom_spiflash_wait_idle(&g_rom_flashchip);
|
2017-01-29 22:29:50 -05:00
|
|
|
|
2017-01-31 21:14:09 -05:00
|
|
|
status = read_status_fn();
|
2017-01-29 22:29:50 -05:00
|
|
|
ESP_LOGD(TAG, "Initial flash chip status 0x%x", status);
|
|
|
|
|
|
|
|
if ((status & (1<<status_qio_bit)) == 0) {
|
|
|
|
execute_flash_command(CMD_WREN, 0, 0, 0);
|
2017-01-31 21:14:09 -05:00
|
|
|
write_status_fn(status | (1<<status_qio_bit));
|
2017-01-29 22:29:50 -05:00
|
|
|
|
2017-03-09 02:29:00 -05:00
|
|
|
esp_rom_spiflash_wait_idle(&g_rom_flashchip);
|
2017-01-29 22:29:50 -05:00
|
|
|
|
2017-01-31 21:14:09 -05:00
|
|
|
status = read_status_fn();
|
2017-01-29 22:29:50 -05:00
|
|
|
ESP_LOGD(TAG, "Updated flash chip status 0x%x", status);
|
|
|
|
if ((status & (1<<status_qio_bit)) == 0) {
|
|
|
|
ESP_LOGE(TAG, "Failed to set QIE bit, not enabling QIO mode");
|
2017-06-19 20:56:29 -04:00
|
|
|
return ESP_FAIL;
|
2017-01-29 22:29:50 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
} else {
|
|
|
|
ESP_LOGD(TAG, "QIO mode already enabled in flash");
|
|
|
|
}
|
|
|
|
|
|
|
|
ESP_LOGD(TAG, "Enabling QIO mode...");
|
|
|
|
|
2017-03-09 02:29:00 -05:00
|
|
|
esp_rom_spiflash_read_mode_t mode;
|
2017-01-29 22:29:50 -05:00
|
|
|
#if CONFIG_FLASHMODE_QOUT
|
2017-03-09 02:29:00 -05:00
|
|
|
mode = ESP_ROM_SPIFLASH_QOUT_MODE;
|
2017-01-29 22:29:50 -05:00
|
|
|
#else
|
2017-03-09 02:29:00 -05:00
|
|
|
mode = ESP_ROM_SPIFLASH_QIO_MODE;
|
2017-01-29 22:29:50 -05:00
|
|
|
#endif
|
2017-04-13 03:08:53 -04:00
|
|
|
|
2017-04-11 21:31:26 -04:00
|
|
|
esp_rom_spiflash_config_readmode(mode);
|
|
|
|
|
2017-06-19 20:47:46 -04:00
|
|
|
esp_rom_spiflash_select_qio_pins(CONFIG_BOOTLOADER_SPI_WP_PIN, spiconfig);
|
2017-06-19 20:56:29 -04:00
|
|
|
|
|
|
|
return ESP_OK;
|
2017-01-29 22:29:50 -05:00
|
|
|
}
|
|
|
|
|
2017-01-31 21:14:09 -05:00
|
|
|
static unsigned read_status_8b_rdsr()
|
|
|
|
{
|
|
|
|
return execute_flash_command(CMD_RDSR, 0, 0, 8);
|
|
|
|
}
|
|
|
|
|
|
|
|
static unsigned read_status_8b_rdsr2()
|
|
|
|
{
|
|
|
|
return execute_flash_command(CMD_RDSR2, 0, 0, 8);
|
|
|
|
}
|
|
|
|
|
|
|
|
static unsigned read_status_16b_rdsr_rdsr2()
|
|
|
|
{
|
|
|
|
return execute_flash_command(CMD_RDSR, 0, 0, 8) | (execute_flash_command(CMD_RDSR2, 0, 0, 8) << 8);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void write_status_8b_wrsr(unsigned new_status)
|
|
|
|
{
|
|
|
|
execute_flash_command(CMD_WRSR, new_status, 8, 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void write_status_8b_wrsr2(unsigned new_status)
|
|
|
|
{
|
|
|
|
execute_flash_command(CMD_WRSR2, new_status, 8, 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void write_status_16b_wrsr(unsigned new_status)
|
|
|
|
{
|
|
|
|
execute_flash_command(CMD_WRSR, new_status, 16, 0);
|
|
|
|
}
|
|
|
|
|
2018-07-11 09:36:35 -04:00
|
|
|
static unsigned read_status_8b_xmc25qu64a()
|
|
|
|
{
|
|
|
|
execute_flash_command(CMD_OTPEN, 0, 0, 0); /* Enter OTP mode */
|
|
|
|
esp_rom_spiflash_wait_idle(&g_rom_flashchip);
|
|
|
|
uint32_t read_status = execute_flash_command(CMD_RDSR, 0, 0, 8);
|
|
|
|
execute_flash_command(CMD_WRDI, 0, 0, 0); /* Exit OTP mode */
|
|
|
|
return read_status;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void write_status_8b_xmc25qu64a(unsigned new_status)
|
|
|
|
{
|
|
|
|
execute_flash_command(CMD_OTPEN, 0, 0, 0); /* Enter OTP mode */
|
|
|
|
esp_rom_spiflash_wait_idle(&g_rom_flashchip);
|
|
|
|
execute_flash_command(CMD_WRSR, new_status, 8, 0);
|
|
|
|
esp_rom_spiflash_wait_idle(&g_rom_flashchip);
|
|
|
|
execute_flash_command(CMD_WRDI, 0, 0, 0); /* Exit OTP mode */
|
|
|
|
}
|
|
|
|
|
2017-01-29 22:29:50 -05:00
|
|
|
static uint32_t execute_flash_command(uint8_t command, uint32_t mosi_data, uint8_t mosi_len, uint8_t miso_len)
|
|
|
|
{
|
2018-05-15 05:14:52 -04:00
|
|
|
uint32_t old_ctrl_reg = SPIFLASH.ctrl.val;
|
|
|
|
SPIFLASH.ctrl.val = SPI_WP_REG_M; // keep WP high while idle, otherwise leave DIO mode
|
|
|
|
SPIFLASH.user.usr_dummy = 0;
|
|
|
|
SPIFLASH.user.usr_addr = 0;
|
|
|
|
SPIFLASH.user.usr_command = 1;
|
|
|
|
SPIFLASH.user2.usr_command_bitlen = 7;
|
|
|
|
|
2017-01-29 22:29:50 -05:00
|
|
|
SPIFLASH.user2.usr_command_value = command;
|
|
|
|
SPIFLASH.user.usr_miso = miso_len > 0;
|
|
|
|
SPIFLASH.miso_dlen.usr_miso_dbitlen = miso_len ? (miso_len - 1) : 0;
|
|
|
|
SPIFLASH.user.usr_mosi = mosi_len > 0;
|
|
|
|
SPIFLASH.mosi_dlen.usr_mosi_dbitlen = mosi_len ? (mosi_len - 1) : 0;
|
|
|
|
SPIFLASH.data_buf[0] = mosi_data;
|
|
|
|
|
2017-04-13 03:08:53 -04:00
|
|
|
if (g_rom_spiflash_dummy_len_plus[1]) {
|
|
|
|
/* When flash pins are mapped via GPIO matrix, need a dummy cycle before reading via MISO */
|
|
|
|
if (miso_len > 0) {
|
|
|
|
SPIFLASH.user.usr_dummy = 1;
|
|
|
|
SPIFLASH.user1.usr_dummy_cyclelen = g_rom_spiflash_dummy_len_plus[1] - 1;
|
|
|
|
} else {
|
|
|
|
SPIFLASH.user.usr_dummy = 0;
|
|
|
|
SPIFLASH.user1.usr_dummy_cyclelen = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-01-29 22:29:50 -05:00
|
|
|
SPIFLASH.cmd.usr = 1;
|
|
|
|
while(SPIFLASH.cmd.usr != 0)
|
|
|
|
{ }
|
|
|
|
|
2018-05-15 05:14:52 -04:00
|
|
|
SPIFLASH.ctrl.val = old_ctrl_reg;
|
2017-01-29 22:29:50 -05:00
|
|
|
return SPIFLASH.data_buf[0];
|
|
|
|
}
|