/*
* Copyright 2014 Free Software Foundation, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
#include
#include
#include
#include
#include //for memset, memcpy
#define S25FLXX_CMD_WIDTH 8
#define S25FLXX_ADDR_WIDTH 24
/* S25FLxx-specific commands */
#define S25FLXX_CMD_READID 0x90 /* Read Manufacturer and Device Identification */
#define S25FLXX_CMD_READSIG 0xAB /* Read Electronic Signature (Will release from Deep PD) */
#define S25FLXX_CMD_READ 0x03 /* Read Data Bytes */
#define S25FLXX_CMD_FAST_READ 0x0B /* Read Data Bytes at Higher Speed */
#define S25FLXX_CMD_WREN 0x06 /* Write Enable */
#define S25FLXX_CMD_WRDI 0x04 /* Write Disable */
#define S25FLXX_CMD_PP 0x02 /* Page Program */
#define S25FLXX_CMD_SE 0xD8 /* Sector Erase */
#define S25FLXX_CMD_BE 0xC7 /* Bulk Erase */
#define S25FLXX_CMD_DP 0xB9 /* Deep Power-down */
#define S25FLXX_CMD_RDSR 0x05 /* Read Status Register */
#define S25FLXX_CMD_WRSR 0x01 /* Write Status Register */
#define S25FLXX_STATUS_WIP 0x01 /* Write in Progress */
#define S25FLXX_STATUS_E_ERR 0x20 /* Erase Error Occured */
#define S25FLXX_STATUS_P_ERR 0x40 /* Programming Error Occured */
#define S25FLXX_SECTOR_ERASE_TIME_MS 750 //Spec: 650ms
#define S25FLXX_PAGE_WRITE_TIME_MS 1 //Spec: 750us
#define S25FLXX_SMALL_SECTORS_PER_LOGICAL 16 //16 4-kB physical sectors per logical sector
#define S25FLXX_LARGE_SECTOR_BASE 0x20000 //Large physical sectors start at logical sector 2
inline static uint8_t _spif_read_status(const spi_flash_dev_t* flash)
{
uint16_t cmd = S25FLXX_CMD_RDSR << 8, status = 0xFFFF;
wb_spi_transact(flash->bus, WRITE_READ, &cmd, &status, S25FLXX_CMD_WIDTH + 8 /* 8 bits of status */);
return status;
}
inline static bool _spif_wait_ready(const spi_flash_dev_t* flash, uint32_t timeout_ms)
{
uint32_t start_ticks = cron_get_ticks();
do {
if ((_spif_read_status(flash) & S25FLXX_STATUS_WIP) == 0) {
return true;
}
} while (get_elapsed_time(start_ticks, cron_get_ticks(), MILLISEC) < timeout_ms);
return false; // Timed out
}
inline static void _spi_flash_set_write_enabled(const spi_flash_dev_t* flash, bool enabled)
{
uint8_t cmd = enabled ? S25FLXX_CMD_WREN : S25FLXX_CMD_WRDI;
wb_spi_transact(flash->bus, WRITE, &cmd, NULL, S25FLXX_CMD_WIDTH);
}
const spi_flash_ops_t spif_spsn_s25flxx_ops =
{
.read_id = spif_spsn_s25flxx_read_id,
.read = spif_spsn_s25flxx_read,
.erase_sector_dispatch = spif_spsn_s25flxx_erase_sector_dispatch,
.erase_sector_commit = spif_spsn_s25flxx_erase_sector_commit,
.erase_sector_busy = spif_spsn_s25flxx_device_busy,
.write_page_dispatch = spif_spsn_s25flxx_write_page_dispatch,
.write_page_commit = spif_spsn_s25flxx_write_page_commit,
.write_page_busy = spif_spsn_s25flxx_device_busy
};
const spi_flash_ops_t* spif_spsn_s25flxx_operations()
{
return &spif_spsn_s25flxx_ops;
}
uint16_t spif_spsn_s25flxx_read_id(const spi_flash_dev_t* flash)
{
wb_spi_slave_select(flash->bus);
uint32_t command = S25FLXX_CMD_READID << 24;
wb_spi_transact_man_ss(flash->bus, WRITE, &command, NULL, 32);
uint16_t id = 0;
wb_spi_transact_man_ss(flash->bus, WRITE_READ, NULL, &id, 16);
wb_spi_slave_deselect(flash->bus);
return id;
}
void spif_spsn_s25flxx_read(const spi_flash_dev_t* flash, uint32_t offset, void *buf, uint32_t num_bytes)
{
//We explicitly control the slave select here, so that we can
//do the entire read operation as a single transaction from
//device's point of view. (The most our SPI peripheral can transfer
//in a single shot is 16 bytes.)
//Do the 5 byte instruction tranfer:
//FAST_READ_CMD, ADDR2, ADDR1, ADDR0, DUMMY (0)
uint8_t read_cmd[5];
read_cmd[4] = S25FLXX_CMD_FAST_READ;
*((uint32_t*)(read_cmd + 3)) = (offset << 8);
wb_spi_slave_select(flash->bus);
wb_spi_transact_man_ss(flash->bus, WRITE_READ, read_cmd, NULL, 5*8);
//Read up to 4 bytes at a time until done
uint8_t data_sw[16], data[16];
size_t xact_size = 16;
unsigned char *bytes = (unsigned char *) buf;
for (size_t i = 0; i < num_bytes; i += 16) {
if (xact_size > num_bytes - i) xact_size = num_bytes - i;
wb_spi_transact_man_ss(flash->bus, WRITE_READ, NULL, data_sw, xact_size*8);
for (size_t k = 0; k < 4; k++) { //Fix word level significance
((uint32_t*)data)[k] = ((uint32_t*)data_sw)[3-k];
}
for (size_t j = 0; j < xact_size; j++) {
*bytes = data[j];
bytes++;
}
}
wb_spi_slave_deselect(flash->bus);
}
bool spif_spsn_s25flxx_erase_sector_dispatch(const spi_flash_dev_t* flash, uint32_t offset)
{
//Sanity check sector size
if (offset % flash->sector_size) {
UHD_FW_TRACE(ERROR, "spif_spsn_s25flxx_erase_sector: Erase offset not a multiple of sector size.");
return false;
}
if (!_spif_wait_ready(flash, S25FLXX_SECTOR_ERASE_TIME_MS)) {
UHD_FW_TRACE_FSTR(ERROR, "spif_spsn_s25flxx_erase_sector: Timeout. Sector at 0x%X was not ready for erase.", offset);
return false;
}
_spi_flash_set_write_enabled(flash, true);
//Send sector erase command
uint32_t command = (S25FLXX_CMD_SE << 24) | (offset & 0x00FFFFFF);
wb_spi_transact(flash->bus, WRITE_READ, &command, NULL, 32);
return true;
}
bool spif_spsn_s25flxx_erase_sector_commit(const spi_flash_dev_t* flash, uint32_t offset)
{
//Poll status until write done
uint8_t phy_sector_count = (offset < S25FLXX_LARGE_SECTOR_BASE) ? S25FLXX_SMALL_SECTORS_PER_LOGICAL : 1;
bool status = false;
for (uint8_t i = 0; i < phy_sector_count && !status; i++) {
status = _spif_wait_ready(flash, S25FLXX_SECTOR_ERASE_TIME_MS);
}
if (!status) {
UHD_FW_TRACE_FSTR(ERROR, "spif_spsn_s25flxx_erase_sector_commit: Timeout. Sector at 0x%X did not finish erasing in time.", offset);
}
_spi_flash_set_write_enabled(flash, false);
return status;
}
bool spif_spsn_s25flxx_write_page_dispatch(const spi_flash_dev_t* flash, uint32_t offset, const void *buf, uint32_t num_bytes)
{
if (num_bytes == 0 || num_bytes > flash->page_size) {
UHD_FW_TRACE(ERROR, "spif_spsn_s25flxx_write_page: Invalid size. Must be > 0 and <= Page Size.");
return false;
}
if (num_bytes > (flash->sector_size * flash->num_sectors)) {
UHD_FW_TRACE(ERROR, "spif_spsn_s25flxx_write_page: Cannot write past flash boundary.");
return false;
}
//Wait until ready and enable write enabled
if (!_spif_wait_ready(flash, S25FLXX_PAGE_WRITE_TIME_MS)) {
UHD_FW_TRACE_FSTR(ERROR, "spif_spsn_s25flxx_write_page: Timeout. Page at 0x%X was not ready for write.", offset);
return false;
}
_spi_flash_set_write_enabled(flash, true);
//We explicitly control the slave select here, so that we can
//do the entire read operation as a single transaction from
//device's point of view. (The most our SPI peripheral can transfer
//in a single shot is 16 bytes.)
//Do the 4 byte instruction tranfer:
//PP_CMD, ADDR2, ADDR1, ADDR0
uint32_t write_cmd = (S25FLXX_CMD_PP << 24) | (offset & 0x00FFFFFF);
wb_spi_slave_select(flash->bus);
wb_spi_transact_man_ss(flash->bus, WRITE, &write_cmd, NULL, 32);
//Write the page 16 bytes at a time.
uint8_t bytes_sw[16];
uint8_t* bytes = (uint8_t*) buf;
for (int32_t bytes_left = num_bytes; bytes_left > 0; bytes_left -= 16) {
const uint32_t xact_size = (bytes_left < 16) ? bytes_left : 16;
for (size_t k = 0; k < 4; k++) { //Fix word level significance
((uint32_t*)bytes_sw)[k] = ((uint32_t*)bytes)[3-k];
}
wb_spi_transact_man_ss(flash->bus, WRITE, bytes_sw, NULL, xact_size * 8);
bytes += xact_size;
}
wb_spi_slave_deselect(flash->bus);
return true;
}
bool spif_spsn_s25flxx_write_page_commit(const spi_flash_dev_t* flash, uint32_t offset, const void *buf, uint32_t num_bytes)
{
//Wait until write done
if (!_spif_wait_ready(flash, S25FLXX_PAGE_WRITE_TIME_MS)) {
UHD_FW_TRACE(ERROR, "spif_spsn_s25flxx_commit_write: Timeout. Page did not finish writing in time.");
return false;
}
_spi_flash_set_write_enabled(flash, false);
return true;
}
bool spif_spsn_s25flxx_device_busy(const spi_flash_dev_t* flash)
{
return (_spif_read_status(flash) & S25FLXX_STATUS_WIP);
}