From 3040e9967951c014d98a6d9c0b45a12777af6372 Mon Sep 17 00:00:00 2001 From: Felix Held Date: Wed, 6 Nov 2024 00:38:13 +0100 Subject: drivers/spi/spi_flash_sfdp: add basic SFDP support Add basic support for the Serial Flash Discoverable Parameters (SFDP) standard which can be used to discover the parameters to interact with any SPI flash chip that supports this mechanism. This commit adds functionality to find specific SFDP parameter headers and print all SFDP parameter headers, but not to parse any SFDP parameter table. This is a preparation for a follow-up patch that adds support to parse the RPMC SFDP parameter table. Since 'find_sfdp_parameter_header' is only used in the next patch, it's marked as static inline in this commit so that the code still build; the 'inline' keyword will be removed again in that follow-up patch. For now, only the legacy access protocol using single bit SPI transfers is supported, but this should cover most of the SPI NOR flash chips. In any other case, the code will error out. It's also assumed that the SFDP data blocks read from the SPI flash chip are small enough to fit into the SPI host controller buffer and don't need to be broken up into multiple transfers. This limitation will be addressed in a follow-up patch. JESD216F.02 was used as a reference. TEST=On a board with a W74M12JW SPI flash chip, calling 'spi_flash_print_sfdp_headers' prints this on the console output: Manufacturer: ef SF: Detected ef 6018 with sector size 0x1000, total 0x1000000 SF: Exiting 4-byte addressing mode SFDP header found in SPI flash. major rev 0x1, minor rev 0x6, access protocol 0xff, number of headers 3 SFPD header with index 0: table ID 0xff00, major rev 0x1, minor rev 0x6 table pointer 0x80, table length DWORDS 0x10 SFPD header with index 1: table ID 0xff84, major rev 0x1, minor rev 0x0 table pointer 0xd0, table length DWORDS 0x2 SFPD header with index 2: table ID 0xff03, major rev 0x1, minor rev 0x0 table pointer 0xf0, table length DWORDS 0x2 Signed-off-by: Felix Held Change-Id: I5a1706acf7d60fd64292e8f0677992ab4aebf46a Reviewed-on: https://review.coreboot.org/c/coreboot/+/84786 Tested-by: build bot (Jenkins) Reviewed-by: Martin Roth --- src/drivers/spi/Kconfig | 5 + src/drivers/spi/Makefile.mk | 1 + src/drivers/spi/spi_flash_sfdp.c | 203 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 209 insertions(+) create mode 100644 src/drivers/spi/spi_flash_sfdp.c (limited to 'src/drivers') diff --git a/src/drivers/spi/Kconfig b/src/drivers/spi/Kconfig index 86e609c7ae..72fcec5e93 100644 --- a/src/drivers/spi/Kconfig +++ b/src/drivers/spi/Kconfig @@ -189,6 +189,11 @@ config SPI_FLASH_FORCE_4_BYTE_ADDR_MODE On some platforms with SPI flashes larger than 16MB, the SPI flash may need to remain in 4-byte addressing mode. +config SPI_FLASH_SFDP + bool + help + Include serial flash discoverable parameters (SFDP) support. + endif # SPI_FLASH config HAVE_EM100PRO_SPI_CONSOLE_SUPPORT diff --git a/src/drivers/spi/Makefile.mk b/src/drivers/spi/Makefile.mk index 97c3d63d49..d873c90627 100644 --- a/src/drivers/spi/Makefile.mk +++ b/src/drivers/spi/Makefile.mk @@ -31,6 +31,7 @@ $(1)-$(CONFIG_SPI_FLASH_SST) += sst.c $(1)-$(CONFIG_SPI_FLASH_STMICRO) += stmicro.c $(1)-$(CONFIG_SPI_FLASH_WINBOND) += winbond.c $(1)-$(CONFIG_SPI_FLASH_ISSI) += issi.c +$(1)-$(CONFIG_SPI_FLASH_SFDP) += spi_flash_sfdp.c endef $(eval $(call add_spi_stage,bootblock,_EARLY)) diff --git a/src/drivers/spi/spi_flash_sfdp.c b/src/drivers/spi/spi_flash_sfdp.c new file mode 100644 index 0000000000..807a33be58 --- /dev/null +++ b/src/drivers/spi/spi_flash_sfdp.c @@ -0,0 +1,203 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#include +#include +#include +#include + +#include "spi_flash_internal.h" + +/* + * Assumptions (not) made in the code: + * - code is supposed to be endian-safe + * - requested SFDP data blocks are small enough to fit into the SPI controller buffer, so no + * spi_crop_chunk calls are needed + * + * Limitations: + * - only legacy access protocol supported: + * - only NOR flash + * - read SFDP instruction is sufficient and no fetch SFDP instruction needed + * - always 3 byte addresses for SFDP + * - only 1 bit SPI mode supported + * - always 8 dummy cycles after the address is sent +*/ + +#define CMD_READ_SFDP 0x5a + +static enum cb_err read_sfdp_data(const struct spi_flash *flash, uint32_t offset, size_t len, + uint8_t *buf) +{ + uint8_t buf_cmd[5]; + + buf_cmd[0] = CMD_READ_SFDP; + buf_cmd[1] = offset >> 16 & 0xff; + buf_cmd[2] = offset >> 8 & 0xff; + buf_cmd[3] = offset >> 0 & 0xff; + buf_cmd[4] = 0; /* dummy byte */ + + /* TODO: use spi_crop_chunk to break up larger transfers */ + if (spi_flash_cmd_multi(&flash->spi, buf_cmd, sizeof(buf_cmd), buf, len)) + return CB_ERR; + + return CB_SUCCESS; +} + +#define SFDP_HEADER_OFFSET 0 +#define SFDP_HEADER_LEN (2 * sizeof(uint32_t)) +#define SFDP_HEADER_SIGNATURE 0x50444653 /* LE: "SFDP" */ +#define SFDP_HEADER_ACCESS_PROTOCOL_LEGACY 0xff +/* header byte offsets */ +#define SFDP_HEADER_SIGNATURE_0 0 +#define SFDP_HEADER_SIGNATURE_1 1 +#define SFDP_HEADER_SIGNATURE_2 2 +#define SFDP_HEADER_SIGNATURE_3 3 +#define SFDP_HEADER_MINOR_REV 4 +#define SFDP_HEADER_MAJOR_REV 5 +#define SFDP_HEADER_PARAMETER_HEADER_NUMBER 6 +#define SFDP_HEADER_ACCESS_PROTOCOL 7 + +static enum cb_err get_sfdp_header(const struct spi_flash *flash, uint16_t *sfdp_revision, + uint8_t *number_of_parameter_headers, + uint8_t *access_prototcol) +{ + uint8_t buf[SFDP_HEADER_LEN]; + uint32_t signature; + + if (read_sfdp_data(flash, SFDP_HEADER_OFFSET, SFDP_HEADER_LEN, buf) != CB_SUCCESS) + return CB_ERR; + + signature = buf[SFDP_HEADER_SIGNATURE_0] | buf[SFDP_HEADER_SIGNATURE_1] << 8 | + buf[SFDP_HEADER_SIGNATURE_2] << 16 | buf[SFDP_HEADER_SIGNATURE_3] << 24; + *sfdp_revision = buf[SFDP_HEADER_MINOR_REV] | buf[SFDP_HEADER_MAJOR_REV] << 8; + *number_of_parameter_headers = buf[SFDP_HEADER_PARAMETER_HEADER_NUMBER] + 1; + *access_prototcol = buf[SFDP_HEADER_ACCESS_PROTOCOL]; + + if (signature != SFDP_HEADER_SIGNATURE) + return CB_ERR; + + /* We make the assumption in the code that the legacy access protocol is used */ + if (*access_prototcol != SFDP_HEADER_ACCESS_PROTOCOL_LEGACY) + return CB_ERR; + + return CB_SUCCESS; +} + +#define SFDP_PARAMETER_HEADER_LEN (2 * sizeof(uint32_t)) +#define SFDP_PARAMETER_HEADER_OFFSET(n) (SFDP_HEADER_OFFSET + SFDP_HEADER_LEN + \ + n * SFDP_PARAMETER_HEADER_LEN) +/* parameter header byte offsets */ +#define SFDP_PARAMETER_HEADER_ID_LSB 0 +#define SFDP_PARAMETER_HEADER_MINOR_REV 1 +#define SFDP_PARAMETER_HEADER_MAJOR_REV 2 +#define SFDP_PARAMETER_HEADER_LENGTH_DWORDS 3 +#define SFDP_PARAMETER_HEADER_POINTER_0 4 +#define SFDP_PARAMETER_HEADER_POINTER_1 5 +#define SFDP_PARAMETER_HEADER_POINTER_2 6 +#define SFDP_PARAMETER_HEADER_ID_MSB 7 + +/* get_sfdp_parameter_header_by_index must be called with a valid index */ +static enum cb_err get_sfdp_parameter_header_by_index(const struct spi_flash *flash, + uint8_t index, uint16_t *id, + uint16_t *revision, + uint8_t *length_dwords, + uint32_t *table_pointer) +{ + uint8_t buf[SFDP_PARAMETER_HEADER_LEN]; + + if (read_sfdp_data(flash, SFDP_PARAMETER_HEADER_OFFSET(index), + SFDP_PARAMETER_HEADER_LEN, buf) != CB_SUCCESS) + return CB_ERR; + + *id = buf[SFDP_PARAMETER_HEADER_ID_LSB] | buf[SFDP_PARAMETER_HEADER_ID_MSB] << 8; + *revision = buf[SFDP_PARAMETER_HEADER_MINOR_REV] | + buf[SFDP_PARAMETER_HEADER_MAJOR_REV] << 8; + *length_dwords = buf[SFDP_PARAMETER_HEADER_LENGTH_DWORDS]; + *table_pointer = buf[SFDP_PARAMETER_HEADER_POINTER_0] | + buf[SFDP_PARAMETER_HEADER_POINTER_1] << 8 | + buf[SFDP_PARAMETER_HEADER_POINTER_2] << 16; + + /* the parameter table pointer byte address must be DWORD-aligned */ + if (!IS_ALIGNED(*table_pointer, sizeof(uint32_t))) + return CB_ERR; + + /* TODO: check id validity? */ + + return CB_SUCCESS; +} + +void spi_flash_print_sfdp_headers(const struct spi_flash *flash) +{ + enum cb_err stat; + uint16_t sfdp_rev; + uint8_t param_header_count; + uint8_t access_protocol; + + if (get_sfdp_header(flash, &sfdp_rev, ¶m_header_count, &access_protocol) != + CB_SUCCESS) { + printk(BIOS_ERR, "Failed to read SFDP header from SPI flash\n"); + return; + } + + printk(BIOS_DEBUG, "SFDP header found in SPI flash.\n"); + printk(BIOS_DEBUG, "major rev %#x, minor rev %#x, access protocol %#x, " + "number of headers %d\n", sfdp_rev >> 8, sfdp_rev & 0xff, access_protocol, + param_header_count); + + uint16_t tbl_id; + uint16_t tbl_rev; + uint8_t tbl_len_dwords; + uint32_t tbl_pointer; + + for (unsigned int i = 0; i < param_header_count; i++) { + stat = get_sfdp_parameter_header_by_index(flash, i, &tbl_id, &tbl_rev, + &tbl_len_dwords, &tbl_pointer); + + if (stat == CB_SUCCESS) { + printk(BIOS_DEBUG, "SFPD header with index %d:\n", i); + printk(BIOS_DEBUG, " table ID %#x, major rev %#x, minor rev %#x\n", + tbl_id, tbl_rev >> 8, tbl_rev & 0xff); + printk(BIOS_DEBUG, " table pointer %#x, table length DWORDS %#x\n", + tbl_pointer, tbl_len_dwords); + } else { + printk(BIOS_ERR, "Cound't read SFPD header with index %d.\n", i); + } + } +} + +static inline enum cb_err find_sfdp_parameter_header(const struct spi_flash *flash, + uint16_t table_id, uint16_t *revision, + uint8_t *length_dwords, + uint32_t *table_pointer) +{ + enum cb_err stat; + uint16_t sfdp_rev; + uint8_t param_header_count; + uint8_t access_protocol; + + if (get_sfdp_header(flash, &sfdp_rev, ¶m_header_count, &access_protocol) != + CB_SUCCESS) + return CB_ERR; + + /* TODO: add version check? */ + + uint16_t tbl_id; + uint16_t tbl_rev; + uint8_t tbl_len_dwords; + uint32_t tbl_pointer; + + for (unsigned int i = 0; i < param_header_count; i++) { + stat = get_sfdp_parameter_header_by_index(flash, i, &tbl_id, &tbl_rev, + &tbl_len_dwords, &tbl_pointer); + + if (stat == CB_SUCCESS && tbl_id == table_id) { + *revision = tbl_rev; + *length_dwords = tbl_len_dwords; + *table_pointer = tbl_pointer; + return CB_SUCCESS; + } + } + + printk(BIOS_WARNING, "Cound't find SFPD header with table ID %#x.\n", table_id); + + return CB_ERR; +} -- cgit v1.2.3