mirror of
https://github.com/ARM-software/arm-trusted-firmware.git
synced 2025-04-24 05:54:08 +00:00
drivers: Add SPI Nor flash support
Add SPI Nor flash support Change-Id: I0cde3fdb4dcad5bcaf445b3bb48e279332bd28af Signed-off-by: Sheetal Tigadoli <sheetal.tigadoli@broadcom.com>
This commit is contained in:
parent
e3ee7b7dc8
commit
49dec7f7f2
5 changed files with 482 additions and 0 deletions
308
drivers/brcm/spi_flash.c
Normal file
308
drivers/brcm/spi_flash.c
Normal file
|
@ -0,0 +1,308 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2019-2020, Broadcom
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#include <common/debug.h>
|
||||||
|
#include <drivers/delay_timer.h>
|
||||||
|
#include <errno.h>
|
||||||
|
|
||||||
|
#include <sf.h>
|
||||||
|
#include <spi.h>
|
||||||
|
|
||||||
|
#define SPI_FLASH_CMD_LEN 4
|
||||||
|
#define QSPI_WAIT_TIMEOUT_US 200000U /* usec */
|
||||||
|
|
||||||
|
#define FINFO(jedec_id, ext_id, _sector_size, _n_sectors, _page_size, _flags) \
|
||||||
|
.id = { \
|
||||||
|
((jedec_id) >> 16) & 0xff, \
|
||||||
|
((jedec_id) >> 8) & 0xff, \
|
||||||
|
(jedec_id) & 0xff, \
|
||||||
|
((ext_id) >> 8) & 0xff, \
|
||||||
|
(ext_id) & 0xff, \
|
||||||
|
}, \
|
||||||
|
.id_len = (!(jedec_id) ? 0 : (3 + ((ext_id) ? 2 : 0))), \
|
||||||
|
.sector_size = (_sector_size), \
|
||||||
|
.n_sectors = (_n_sectors), \
|
||||||
|
.page_size = _page_size, \
|
||||||
|
.flags = (_flags),
|
||||||
|
|
||||||
|
/* SPI/QSPI flash device params structure */
|
||||||
|
const struct spi_flash_info spi_flash_ids[] = {
|
||||||
|
{"W25Q64CV", FINFO(0xef4017, 0x0, 64 * 1024, 128, 256, WR_QPP | SECT_4K)},
|
||||||
|
{"W25Q64DW", FINFO(0xef6017, 0x0, 64 * 1024, 128, 256, WR_QPP | SECT_4K)},
|
||||||
|
{"W25Q32", FINFO(0xef4016, 0x0, 64 * 1024, 64, 256, SECT_4K)},
|
||||||
|
{"MX25l3205D", FINFO(0xc22016, 0x0, 64 * 1024, 64, 256, SECT_4K)},
|
||||||
|
};
|
||||||
|
|
||||||
|
static void spi_flash_addr(uint32_t addr, uint8_t *cmd)
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* cmd[0] holds a SPI Flash command, stored earlier
|
||||||
|
* cmd[1/2/3] holds 24bit flash address
|
||||||
|
*/
|
||||||
|
cmd[1] = addr >> 16;
|
||||||
|
cmd[2] = addr >> 8;
|
||||||
|
cmd[3] = addr >> 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const struct spi_flash_info *spi_flash_read_id(void)
|
||||||
|
{
|
||||||
|
const struct spi_flash_info *info;
|
||||||
|
uint8_t id[SPI_FLASH_MAX_ID_LEN];
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
ret = spi_flash_cmd(CMD_READ_ID, id, SPI_FLASH_MAX_ID_LEN);
|
||||||
|
if (ret < 0) {
|
||||||
|
ERROR("SF: Error %d reading JEDEC ID\n", ret);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (info = spi_flash_ids; info->name != NULL; info++) {
|
||||||
|
if (info->id_len) {
|
||||||
|
if (!memcmp(info->id, id, info->id_len))
|
||||||
|
return info;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
printf("SF: unrecognized JEDEC id bytes: %02x, %02x, %02x\n",
|
||||||
|
id[0], id[1], id[2]);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Enable writing on the SPI flash */
|
||||||
|
static inline int spi_flash_cmd_write_enable(struct spi_flash *flash)
|
||||||
|
{
|
||||||
|
return spi_flash_cmd(CMD_WRITE_ENABLE, NULL, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int spi_flash_cmd_wait(struct spi_flash *flash)
|
||||||
|
{
|
||||||
|
uint8_t cmd;
|
||||||
|
uint32_t i;
|
||||||
|
uint8_t status;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
i = 0;
|
||||||
|
while (1) {
|
||||||
|
cmd = CMD_RDSR;
|
||||||
|
ret = spi_flash_cmd_read(&cmd, 1, &status, 1);
|
||||||
|
if (ret < 0) {
|
||||||
|
ERROR("SF: cmd wait failed\n");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (!(status & STATUS_WIP))
|
||||||
|
break;
|
||||||
|
|
||||||
|
i++;
|
||||||
|
if (i >= QSPI_WAIT_TIMEOUT_US) {
|
||||||
|
ERROR("SF: cmd wait timeout\n");
|
||||||
|
ret = -1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
udelay(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int spi_flash_write_common(struct spi_flash *flash, const uint8_t *cmd,
|
||||||
|
size_t cmd_len, const void *buf,
|
||||||
|
size_t buf_len)
|
||||||
|
{
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
ret = spi_flash_cmd_write_enable(flash);
|
||||||
|
if (ret < 0) {
|
||||||
|
ERROR("SF: enabling write failed\n");
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = spi_flash_cmd_write(cmd, cmd_len, buf, buf_len);
|
||||||
|
if (ret < 0) {
|
||||||
|
ERROR("SF: write cmd failed\n");
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = spi_flash_cmd_wait(flash);
|
||||||
|
if (ret < 0) {
|
||||||
|
ERROR("SF: write timed out\n");
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int spi_flash_read_common(const uint8_t *cmd, size_t cmd_len,
|
||||||
|
void *data, size_t data_len)
|
||||||
|
{
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
ret = spi_flash_cmd_read(cmd, cmd_len, data, data_len);
|
||||||
|
if (ret < 0) {
|
||||||
|
ERROR("SF: read cmd failed\n");
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
int spi_flash_read(struct spi_flash *flash, uint32_t offset,
|
||||||
|
uint32_t len, void *data)
|
||||||
|
{
|
||||||
|
uint32_t read_len = 0, read_addr;
|
||||||
|
uint8_t cmd[SPI_FLASH_CMD_LEN];
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
ret = spi_claim_bus();
|
||||||
|
if (ret) {
|
||||||
|
ERROR("SF: unable to claim SPI bus\n");
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd[0] = CMD_READ_NORMAL;
|
||||||
|
while (len) {
|
||||||
|
read_addr = offset;
|
||||||
|
read_len = MIN(flash->page_size, (len - read_len));
|
||||||
|
spi_flash_addr(read_addr, cmd);
|
||||||
|
|
||||||
|
ret = spi_flash_read_common(cmd, sizeof(cmd), data, read_len);
|
||||||
|
if (ret < 0) {
|
||||||
|
ERROR("SF: read failed\n");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
offset += read_len;
|
||||||
|
len -= read_len;
|
||||||
|
data += read_len;
|
||||||
|
}
|
||||||
|
SPI_DEBUG("SF read done\n");
|
||||||
|
|
||||||
|
spi_release_bus();
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
int spi_flash_write(struct spi_flash *flash, uint32_t offset,
|
||||||
|
uint32_t len, void *buf)
|
||||||
|
{
|
||||||
|
unsigned long byte_addr, page_size;
|
||||||
|
uint8_t cmd[SPI_FLASH_CMD_LEN];
|
||||||
|
uint32_t chunk_len, actual;
|
||||||
|
uint32_t write_addr;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
ret = spi_claim_bus();
|
||||||
|
if (ret) {
|
||||||
|
ERROR("SF: unable to claim SPI bus\n");
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
page_size = flash->page_size;
|
||||||
|
|
||||||
|
cmd[0] = flash->write_cmd;
|
||||||
|
for (actual = 0; actual < len; actual += chunk_len) {
|
||||||
|
write_addr = offset;
|
||||||
|
byte_addr = offset % page_size;
|
||||||
|
chunk_len = MIN(len - actual,
|
||||||
|
(uint32_t)(page_size - byte_addr));
|
||||||
|
spi_flash_addr(write_addr, cmd);
|
||||||
|
|
||||||
|
SPI_DEBUG("SF:0x%p=>cmd:{0x%02x 0x%02x%02x%02x} chunk_len:%d\n",
|
||||||
|
buf + actual, cmd[0], cmd[1],
|
||||||
|
cmd[2], cmd[3], chunk_len);
|
||||||
|
|
||||||
|
ret = spi_flash_write_common(flash, cmd, sizeof(cmd),
|
||||||
|
buf + actual, chunk_len);
|
||||||
|
if (ret < 0) {
|
||||||
|
ERROR("SF: write cmd failed\n");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
offset += chunk_len;
|
||||||
|
}
|
||||||
|
SPI_DEBUG("SF write done\n");
|
||||||
|
|
||||||
|
spi_release_bus();
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
int spi_flash_erase(struct spi_flash *flash, uint32_t offset, uint32_t len)
|
||||||
|
{
|
||||||
|
uint8_t cmd[SPI_FLASH_CMD_LEN];
|
||||||
|
uint32_t erase_size, erase_addr;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
erase_size = flash->erase_size;
|
||||||
|
|
||||||
|
if (offset % erase_size || len % erase_size) {
|
||||||
|
ERROR("SF: Erase offset/length not multiple of erase size\n");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = spi_claim_bus();
|
||||||
|
if (ret) {
|
||||||
|
ERROR("SF: unable to claim SPI bus\n");
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd[0] = flash->erase_cmd;
|
||||||
|
while (len) {
|
||||||
|
erase_addr = offset;
|
||||||
|
spi_flash_addr(erase_addr, cmd);
|
||||||
|
|
||||||
|
SPI_DEBUG("SF: erase %2x %2x %2x %2x (%x)\n", cmd[0], cmd[1],
|
||||||
|
cmd[2], cmd[3], erase_addr);
|
||||||
|
|
||||||
|
ret = spi_flash_write_common(flash, cmd, sizeof(cmd), NULL, 0);
|
||||||
|
if (ret < 0) {
|
||||||
|
ERROR("SF: erase failed\n");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
offset += erase_size;
|
||||||
|
len -= erase_size;
|
||||||
|
}
|
||||||
|
SPI_DEBUG("sf erase done\n");
|
||||||
|
|
||||||
|
spi_release_bus();
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
int spi_flash_probe(struct spi_flash *flash)
|
||||||
|
{
|
||||||
|
const struct spi_flash_info *info = NULL;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
ret = spi_claim_bus();
|
||||||
|
if (ret) {
|
||||||
|
ERROR("SF: Unable to claim SPI bus\n");
|
||||||
|
ERROR("SF: probe failed\n");
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
info = spi_flash_read_id();
|
||||||
|
if (!info)
|
||||||
|
goto probe_fail;
|
||||||
|
|
||||||
|
INFO("Flash Name: %s sectors %x, sec size %x\n",
|
||||||
|
info->name, info->n_sectors,
|
||||||
|
info->sector_size);
|
||||||
|
flash->size = info->n_sectors * info->sector_size;
|
||||||
|
flash->sector_size = info->sector_size;
|
||||||
|
flash->page_size = info->page_size;
|
||||||
|
flash->flags = info->flags;
|
||||||
|
|
||||||
|
flash->read_cmd = CMD_READ_NORMAL;
|
||||||
|
flash->write_cmd = CMD_PAGE_PROGRAM;
|
||||||
|
flash->erase_cmd = CMD_ERASE_64K;
|
||||||
|
flash->erase_size = ERASE_SIZE_64K;
|
||||||
|
|
||||||
|
probe_fail:
|
||||||
|
spi_release_bus();
|
||||||
|
return ret;
|
||||||
|
}
|
60
drivers/brcm/spi_sf.c
Normal file
60
drivers/brcm/spi_sf.c
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2019-2020, Broadcom
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <common/debug.h>
|
||||||
|
|
||||||
|
#include <spi.h>
|
||||||
|
|
||||||
|
#define BITS_PER_BYTE 8
|
||||||
|
#define CMD_LEN1 1
|
||||||
|
|
||||||
|
static int spi_flash_read_write(const uint8_t *cmd,
|
||||||
|
size_t cmd_len,
|
||||||
|
const uint8_t *data_out,
|
||||||
|
uint8_t *data_in,
|
||||||
|
size_t data_len)
|
||||||
|
{
|
||||||
|
unsigned long flags = SPI_XFER_BEGIN;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
if (data_len == 0)
|
||||||
|
flags |= SPI_XFER_END;
|
||||||
|
|
||||||
|
ret = spi_xfer(cmd_len * BITS_PER_BYTE, cmd, NULL, flags);
|
||||||
|
if (ret) {
|
||||||
|
ERROR("SF: Failed to send command (%zu bytes): %d\n",
|
||||||
|
cmd_len, ret);
|
||||||
|
} else if (data_len != 0) {
|
||||||
|
ret = spi_xfer(data_len * BITS_PER_BYTE, data_out,
|
||||||
|
data_in, SPI_XFER_END);
|
||||||
|
if (ret)
|
||||||
|
ERROR("SF: Failed to transfer %zu bytes of data: %d\n",
|
||||||
|
data_len, ret);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
int spi_flash_cmd_read(const uint8_t *cmd,
|
||||||
|
size_t cmd_len,
|
||||||
|
void *data,
|
||||||
|
size_t data_len)
|
||||||
|
{
|
||||||
|
return spi_flash_read_write(cmd, cmd_len, NULL, data, data_len);
|
||||||
|
}
|
||||||
|
|
||||||
|
int spi_flash_cmd(uint8_t cmd, void *response, size_t len)
|
||||||
|
{
|
||||||
|
return spi_flash_cmd_read(&cmd, CMD_LEN1, response, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
int spi_flash_cmd_write(const uint8_t *cmd,
|
||||||
|
size_t cmd_len,
|
||||||
|
const void *data,
|
||||||
|
size_t data_len)
|
||||||
|
{
|
||||||
|
return spi_flash_read_write(cmd, cmd_len, data, NULL, data_len);
|
||||||
|
}
|
90
include/drivers/brcm/sf.h
Normal file
90
include/drivers/brcm/sf.h
Normal file
|
@ -0,0 +1,90 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2019-2020, Broadcom
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef SF_H
|
||||||
|
#define SF_H
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
|
||||||
|
#ifdef SPI_DEBUG
|
||||||
|
#define SPI_DEBUG(fmt, ...) INFO(fmt, ##__VA_ARGS__)
|
||||||
|
#else
|
||||||
|
#define SPI_DEBUG(fmt, ...)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define SPI_FLASH_MAX_ID_LEN 6
|
||||||
|
|
||||||
|
#define CMD_WRSR 0x01 /* Write status register */
|
||||||
|
#define CMD_PAGE_PROGRAM 0x02
|
||||||
|
#define CMD_READ_NORMAL 0x03
|
||||||
|
#define CMD_RDSR 0x05
|
||||||
|
#define CMD_WRITE_ENABLE 0x06
|
||||||
|
#define CMD_RDFSR 0x70
|
||||||
|
#define CMD_READ_ID 0x9f
|
||||||
|
#define CMD_ERASE_4K 0x20
|
||||||
|
#define CMD_ERASE_64K 0xd8
|
||||||
|
#define ERASE_SIZE_64K (64 * 1024)
|
||||||
|
|
||||||
|
/* Common status */
|
||||||
|
#define STATUS_WIP BIT(0)
|
||||||
|
|
||||||
|
struct spi_flash {
|
||||||
|
struct spi_slave *spi;
|
||||||
|
uint32_t size;
|
||||||
|
uint32_t page_size;
|
||||||
|
uint32_t sector_size;
|
||||||
|
uint32_t erase_size;
|
||||||
|
uint8_t erase_cmd;
|
||||||
|
uint8_t read_cmd;
|
||||||
|
uint8_t write_cmd;
|
||||||
|
uint8_t flags;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct spi_flash_info {
|
||||||
|
const char *name;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This array stores the ID bytes.
|
||||||
|
* The first three bytes are the JEDIC ID.
|
||||||
|
* JEDEC ID zero means "no ID" (mostly older chips).
|
||||||
|
*/
|
||||||
|
uint8_t id[SPI_FLASH_MAX_ID_LEN];
|
||||||
|
uint8_t id_len;
|
||||||
|
|
||||||
|
uint32_t sector_size;
|
||||||
|
uint32_t n_sectors;
|
||||||
|
uint16_t page_size;
|
||||||
|
|
||||||
|
uint8_t flags;
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Enum list - Full read commands */
|
||||||
|
enum spi_read_cmds {
|
||||||
|
ARRAY_SLOW = BIT(0),
|
||||||
|
ARRAY_FAST = BIT(1),
|
||||||
|
DUAL_OUTPUT_FAST = BIT(2),
|
||||||
|
DUAL_IO_FAST = BIT(3),
|
||||||
|
QUAD_OUTPUT_FAST = BIT(4),
|
||||||
|
QUAD_IO_FAST = BIT(5),
|
||||||
|
};
|
||||||
|
|
||||||
|
/* sf param flags */
|
||||||
|
enum spi_param_flag {
|
||||||
|
SECT_4K = BIT(0),
|
||||||
|
SECT_32K = BIT(1),
|
||||||
|
E_FSR = BIT(2),
|
||||||
|
SST_BP = BIT(3),
|
||||||
|
SST_WP = BIT(4),
|
||||||
|
WR_QPP = BIT(5),
|
||||||
|
};
|
||||||
|
|
||||||
|
int spi_flash_cmd_read(const uint8_t *cmd, size_t cmd_len,
|
||||||
|
void *data, size_t data_len);
|
||||||
|
int spi_flash_cmd(uint8_t cmd, void *response, size_t len);
|
||||||
|
int spi_flash_cmd_write(const uint8_t *cmd, size_t cmd_len,
|
||||||
|
const void *data, size_t data_len);
|
||||||
|
#endif
|
18
include/drivers/brcm/spi_flash.h
Normal file
18
include/drivers/brcm/spi_flash.h
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2019-2020, Broadcom
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef SPI_FLASH_H
|
||||||
|
#define SPI_FLASH_H
|
||||||
|
|
||||||
|
#include <sf.h>
|
||||||
|
|
||||||
|
int spi_flash_probe(struct spi_flash *flash);
|
||||||
|
int spi_flash_erase(struct spi_flash *flash, uint32_t offset, uint32_t len);
|
||||||
|
int spi_flash_write(struct spi_flash *flash, uint32_t offset,
|
||||||
|
uint32_t len, void *buf);
|
||||||
|
int spi_flash_read(struct spi_flash *flash, uint32_t offset,
|
||||||
|
uint32_t len, void *data);
|
||||||
|
#endif /* _SPI_FLASH_H_ */
|
|
@ -169,6 +169,12 @@ PLAT_BL_COMMON_SOURCES += drivers/brcm/spi/iproc_spi.c \
|
||||||
drivers/brcm/spi/iproc_qspi.c
|
drivers/brcm/spi/iproc_qspi.c
|
||||||
endif
|
endif
|
||||||
|
|
||||||
|
# Add spi nor/flash driver
|
||||||
|
ifeq (${DRIVER_SPI_NOR_ENABLE},1)
|
||||||
|
PLAT_BL_COMMON_SOURCES += drivers/brcm/spi_sf.c \
|
||||||
|
drivers/brcm/spi_flash.c
|
||||||
|
endif
|
||||||
|
|
||||||
ifeq (${DRIVER_OCOTP_ENABLE},1)
|
ifeq (${DRIVER_OCOTP_ENABLE},1)
|
||||||
$(eval $(call add_define,DRIVER_OCOTP_ENABLE))
|
$(eval $(call add_define,DRIVER_OCOTP_ENABLE))
|
||||||
BL2_SOURCES += drivers/brcm/ocotp.c
|
BL2_SOURCES += drivers/brcm/ocotp.c
|
||||||
|
|
Loading…
Add table
Reference in a new issue