1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238
|
/*
* 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 <http://www.gnu.org/licenses/>.
*/
#include <wb_spi.h>
#include <flash/spif_spsn_s25flxx.h>
#include <cron.h>
#include <trace.h>
#include <string.h> //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);
}
|