Skip to content

A Reduced SPIF Block Device for Boot Loader #8349

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Oct 18, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,344 @@

/* mbed Microcontroller Library
* Copyright (c) 2018 ARM Limited
*
* 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 "SPIFReducedBlockDevice.h"
#include "mbed_wait_api.h"

// Read/write/erase sizes
#define SPIF_READ_SIZE 1
#define SPIF_PROG_SIZE 1
#define SPIF_SE_SIZE 4096
#define SPIF_TIMEOUT 10000

// Debug available
#define SPIF_DEBUG 0

// Legacy SFDP Instruction Table.
enum ops {
SPIF_NOP = 0x00, // No operation
SPIF_READ = 0x03, // Read data
SPIF_PROG = 0x02, // Program data
SPIF_SE = 0x20, // 4KB Sector Erase
SPIF_CE = 0xc7, // Chip Erase
SPIF_SFDP = 0x5a, // Read SFDP
SPIF_WREN = 0x06, // Write Enable
SPIF_WRDI = 0x04, // Write Disable
SPIF_RDSR = 0x05, // Read Status Register
SPIF_RDID = 0x9f, // Read Manufacturer and JDEC Device ID
};

// Status register from RDSR
// [---------| wel | wip ]
// [- 6 -| 1 | 1 ]
#define SPIF_WEL 0x2
#define SPIF_WIP 0x1


SPIFReducedBlockDevice::SPIFReducedBlockDevice(
PinName mosi, PinName miso, PinName sclk, PinName cs, int freq)
: _spi(mosi, miso, sclk), _cs(cs), _size(0)
{
_cs = 1;
_spi.frequency(freq);
}

int SPIFReducedBlockDevice::init()
{
// Check for vendor specific hacks, these should move into more general
// handling when possible. RDID is not used to verify a device is attached.
uint8_t id[3];
_cmdread(SPIF_RDID, 0, 3, 0x0, id);

switch (id[0]) {
case 0xbf:
// SST devices come preset with block protection
// enabled for some regions, issue gbpu instruction to clear
_wren();
_cmdwrite(0x98, 0, 0, 0x0, NULL);
break;
}

// Check that device is doing ok
int err = _sync();
if (err) {
return BD_ERROR_DEVICE_ERROR;
}

// Check JEDEC serial flash discoverable parameters for device
// specific info
uint8_t header[16];
_cmdread(SPIF_SFDP, 4, 16, 0x0, header);

// Verify SFDP signature for sanity
// Also check that major/minor version is acceptable
if (!(memcmp(&header[0], "SFDP", 4) == 0 && header[5] == 1)) {
return BD_ERROR_DEVICE_ERROR;
}

// The SFDP spec indicates the standard table is always at offset 0
// in the parameter headers, we check just to be safe
if (!(header[8] == 0 && header[10] == 1)) {
return BD_ERROR_DEVICE_ERROR;
}

// Parameter table pointer, spi commands are BE, SFDP is LE,
// also sfdp command expects extra read wait byte
// header 12-14 3 bytes building the parameter table address
uint32_t table_addr = (
(header[14] << 24) |
(header[13] << 16) |
(header[12] << 8 ));

uint8_t table[8];
_cmdread(SPIF_SFDP, 4, 8, table_addr, table);

// Check erase size, currently only supports 4kbytes
if ((table[0] & 0x3) != 0x1 || table[1] != SPIF_SE) {
// First byte of table, bits 0 and 1 = 0x1 indicating 4 KB Erase is supported
// Second Byte of table = Sector Erase Command (0x20)
return BD_ERROR_DEVICE_ERROR;
}

// Check address size, currently only supports 3byte addresses
if ((table[2] & 0x4) != 0 || (table[7] & 0x80) != 0) {
return BD_ERROR_DEVICE_ERROR;
}

// Get device density, stored as size in bits - 1
uint32_t density = (
(table[7] << 24) |
(table[6] << 16) |
(table[5] << 8 ) |
(table[4] << 0 ));
// Table bytes 5-8 : Bits 0|30 indicate Flash Density (size) in bits (divide by 8 for Bytes)
_size = (density / 8) + 1;

return 0;
}

int SPIFReducedBlockDevice::deinit()
{
// Latch write disable just to keep noise
// from changing the device
_cmdwrite(SPIF_WRDI, 0, 0, 0x0, NULL);

return 0;
}

void SPIFReducedBlockDevice::_cmdread(
uint8_t op, uint32_t addrc, uint32_t retc,
uint32_t addr, uint8_t *rets)
{
_cs = 0;
_spi.write(op);

for (uint32_t i = 0; i < addrc; i++) {
_spi.write(0xff & (addr >> 8 * (addrc - 1 - i)));
}

for (uint32_t i = 0; i < retc; i++) {
rets[i] = _spi.write(0);
}
_cs = 1;

if (SPIF_DEBUG) {
printf("spif <- %02x", op);
for (uint32_t i = 0; i < addrc; i++) {
if (i < addrc) {
printf("%02lx", 0xff & (addr >> 8 * (addrc - 1 - i)));
} else {
printf(" ");
}
}
printf(" ");
for (uint32_t i = 0; i < 16 && i < retc; i++) {
printf("%02x", rets[i]);
}
if (retc > 16) {
printf("...");
}
printf("\n");
}
}

void SPIFReducedBlockDevice::_cmdwrite(
uint8_t op, uint32_t addrc, uint32_t argc,
uint32_t addr, const uint8_t *args)
{
_cs = 0;
_spi.write(op);

for (uint32_t i = 0; i < addrc; i++) {
_spi.write(0xff & (addr >> 8 * (addrc - 1 - i)));
}

for (uint32_t i = 0; i < argc; i++) {
_spi.write(args[i]);
}
_cs = 1;

if (SPIF_DEBUG) {
printf("spif -> %02x", op);
for (uint32_t i = 0; i < addrc; i++) {
if (i < addrc) {
printf("%02lx", 0xff & (addr >> 8 * (addrc - 1 - i)));
} else {
printf(" ");
}
}
printf(" ");
for (uint32_t i = 0; i < 16 && i < argc; i++) {
printf("%02x", args[i]);
}
if (argc > 16) {
printf("...");
}
printf("\n");
}
}

int SPIFReducedBlockDevice::_sync()
{
for (int i = 0; i < SPIF_TIMEOUT; i++) {
// Read status register until write not-in-progress
uint8_t status;
_cmdread(SPIF_RDSR, 0, 1, 0x0, &status);

// Check WIP bit
if (!(status & SPIF_WIP)) {
return 0;
}

wait_ms(1);
}

return BD_ERROR_DEVICE_ERROR;
}

int SPIFReducedBlockDevice::_wren()
{
_cmdwrite(SPIF_WREN, 0, 0, 0x0, NULL);

for (int i = 0; i < SPIF_TIMEOUT; i++) {
// Read status register until write latch is enabled
uint8_t status;
_cmdread(SPIF_RDSR, 0, 1, 0x0, &status);

// Check WEL bit
if (status & SPIF_WEL) {
return 0;
}

wait_ms(1);
}

return BD_ERROR_DEVICE_ERROR;
}

int SPIFReducedBlockDevice::read(void *buffer, bd_addr_t addr, bd_size_t size)
{
// Check the address and size fit onto the chip.
MBED_ASSERT(is_valid_read(addr, size));

_cmdread(SPIF_READ, 3, size, addr, static_cast<uint8_t *>(buffer));
return 0;
}

int SPIFReducedBlockDevice::program(const void *buffer, bd_addr_t addr, bd_size_t size)
{
// Check the address and size fit onto the chip.
MBED_ASSERT(is_valid_program(addr, size));

while (size > 0) {
int err = _wren();
if (err) {
return err;
}

// Write up to 256 bytes a page
uint32_t off = addr % 256;
uint32_t chunk = (off + size < 256) ? size : (256 - off);
_cmdwrite(SPIF_PROG, 3, chunk, addr, static_cast<const uint8_t *>(buffer));
buffer = static_cast<const uint8_t *>(buffer) + chunk;
addr += chunk;
size -= chunk;

wait_ms(1);

err = _sync();
if (err) {
return err;
}
}

return 0;
}

int SPIFReducedBlockDevice::erase(bd_addr_t addr, bd_size_t size)
{
// Check the address and size fit onto the chip.
MBED_ASSERT(is_valid_erase(addr, size));

while (size > 0) {
int err = _wren();
if (err) {
return err;
}

// Erase 4kbyte sectors
uint32_t chunk = 4096;
_cmdwrite(SPIF_SE, 3, 0, addr, NULL);
addr += chunk;
size -= chunk;

err = _sync();
if (err) {
return err;
}
}

return 0;
}

bd_size_t SPIFReducedBlockDevice::get_read_size() const
{
return SPIF_READ_SIZE;
}

bd_size_t SPIFReducedBlockDevice::get_program_size() const
{
return SPIF_PROG_SIZE;
}

bd_size_t SPIFReducedBlockDevice::get_erase_size() const
{
return SPIF_SE_SIZE;
}

bd_size_t SPIFReducedBlockDevice::get_erase_size(bd_addr_t addr) const
{
return SPIF_SE_SIZE;
}

int SPIFReducedBlockDevice::get_erase_value() const
{
return 0xFF;
}

bd_size_t SPIFReducedBlockDevice::size() const
{
return _size;
}
Loading