spi_flash: Add Dual SPI support
This patch adds support to read SPI flash in Dual SPI mode, where both MISO and MOSI lines are used for output mode (specifically Fast Read Dual Output (0x3b) where the command is still sent normally, not Fast Read Dual I/O (0xbb) whose additional benefit should be extremely marginal for our use cases but which would be more complicated to implement). This feature needs to be supported by both the flash chip and the controller, so we add a new dual_spi flag (and a new flags field to hold it) to the spi_flash structure and a new optional xfer_dual() function pointer to the spi_ctrlr structure. When both are provided, Dual SPI mode is used automatically, otherwise things work as before. This patch only adds the dual_spi flag exemplary to all Winbond and Gigadevice chips, other vendors need to be added as needed. Change-Id: Ic6808224c99af32b6c5c43054135c8f4c03c1feb Signed-off-by: Julius Werner <jwerner@chromium.org> Reviewed-on: https://review.coreboot.org/c/coreboot/+/33283 Tested-by: build bot (Jenkins) <no-reply@coreboot.org> Reviewed-by: Aaron Durbin <adurbin@chromium.org> Reviewed-by: Furquan Shaikh <furquan@google.com>
This commit is contained in:
parent
1b7f99bd6b
commit
99e45ceb35
|
@ -41,7 +41,9 @@
|
|||
|
||||
struct gigadevice_spi_flash_params {
|
||||
uint16_t id;
|
||||
uint8_t l2_page_size_shift;
|
||||
uint8_t dual_spi : 1;
|
||||
uint8_t _reserved_for_flags : 3;
|
||||
uint8_t l2_page_size_shift : 4;
|
||||
uint8_t pages_per_sector_shift : 4;
|
||||
uint8_t sectors_per_block_shift : 4;
|
||||
uint8_t nr_blocks_shift;
|
||||
|
@ -63,6 +65,7 @@ static const struct gigadevice_spi_flash_params gigadevice_spi_flash_table[] = {
|
|||
.pages_per_sector_shift = 4,
|
||||
.sectors_per_block_shift = 4,
|
||||
.nr_blocks_shift = 4,
|
||||
.dual_spi = 1,
|
||||
.name = "GD25Q80",
|
||||
}, /* also GD25Q80B */
|
||||
{
|
||||
|
@ -71,6 +74,7 @@ static const struct gigadevice_spi_flash_params gigadevice_spi_flash_table[] = {
|
|||
.pages_per_sector_shift = 4,
|
||||
.sectors_per_block_shift = 4,
|
||||
.nr_blocks_shift = 5,
|
||||
.dual_spi = 1,
|
||||
.name = "GD25Q16",
|
||||
}, /* also GD25Q16B */
|
||||
{
|
||||
|
@ -79,6 +83,7 @@ static const struct gigadevice_spi_flash_params gigadevice_spi_flash_table[] = {
|
|||
.pages_per_sector_shift = 4,
|
||||
.sectors_per_block_shift = 4,
|
||||
.nr_blocks_shift = 6,
|
||||
.dual_spi = 1,
|
||||
.name = "GD25Q32B",
|
||||
}, /* also GD25Q32B */
|
||||
{
|
||||
|
@ -87,6 +92,7 @@ static const struct gigadevice_spi_flash_params gigadevice_spi_flash_table[] = {
|
|||
.pages_per_sector_shift = 4,
|
||||
.sectors_per_block_shift = 4,
|
||||
.nr_blocks_shift = 7,
|
||||
.dual_spi = 1,
|
||||
.name = "GD25Q64",
|
||||
}, /* also GD25Q64B, GD25B64C */
|
||||
{
|
||||
|
@ -95,6 +101,7 @@ static const struct gigadevice_spi_flash_params gigadevice_spi_flash_table[] = {
|
|||
.pages_per_sector_shift = 4,
|
||||
.sectors_per_block_shift = 4,
|
||||
.nr_blocks_shift = 8,
|
||||
.dual_spi = 1,
|
||||
.name = "GD25Q128",
|
||||
}, /* also GD25Q128B */
|
||||
{
|
||||
|
@ -103,6 +110,7 @@ static const struct gigadevice_spi_flash_params gigadevice_spi_flash_table[] = {
|
|||
.pages_per_sector_shift = 4,
|
||||
.sectors_per_block_shift = 4,
|
||||
.nr_blocks_shift = 4,
|
||||
.dual_spi = 1,
|
||||
.name = "GD25VQ80C",
|
||||
},
|
||||
{
|
||||
|
@ -111,6 +119,7 @@ static const struct gigadevice_spi_flash_params gigadevice_spi_flash_table[] = {
|
|||
.pages_per_sector_shift = 4,
|
||||
.sectors_per_block_shift = 4,
|
||||
.nr_blocks_shift = 5,
|
||||
.dual_spi = 1,
|
||||
.name = "GD25VQ16C",
|
||||
},
|
||||
{
|
||||
|
@ -119,6 +128,7 @@ static const struct gigadevice_spi_flash_params gigadevice_spi_flash_table[] = {
|
|||
.pages_per_sector_shift = 4,
|
||||
.sectors_per_block_shift = 4,
|
||||
.nr_blocks_shift = 4,
|
||||
.dual_spi = 1,
|
||||
.name = "GD25LQ80",
|
||||
},
|
||||
{
|
||||
|
@ -127,6 +137,7 @@ static const struct gigadevice_spi_flash_params gigadevice_spi_flash_table[] = {
|
|||
.pages_per_sector_shift = 4,
|
||||
.sectors_per_block_shift = 4,
|
||||
.nr_blocks_shift = 5,
|
||||
.dual_spi = 1,
|
||||
.name = "GD25LQ16",
|
||||
},
|
||||
{
|
||||
|
@ -135,6 +146,7 @@ static const struct gigadevice_spi_flash_params gigadevice_spi_flash_table[] = {
|
|||
.pages_per_sector_shift = 4,
|
||||
.sectors_per_block_shift = 4,
|
||||
.nr_blocks_shift = 6,
|
||||
.dual_spi = 1,
|
||||
.name = "GD25LQ32",
|
||||
},
|
||||
{
|
||||
|
@ -143,6 +155,7 @@ static const struct gigadevice_spi_flash_params gigadevice_spi_flash_table[] = {
|
|||
.pages_per_sector_shift = 4,
|
||||
.sectors_per_block_shift = 4,
|
||||
.nr_blocks_shift = 7,
|
||||
.dual_spi = 1,
|
||||
.name = "GD25LQ64C",
|
||||
}, /* also GD25LB64C */
|
||||
{
|
||||
|
@ -151,6 +164,7 @@ static const struct gigadevice_spi_flash_params gigadevice_spi_flash_table[] = {
|
|||
.pages_per_sector_shift = 4,
|
||||
.sectors_per_block_shift = 4,
|
||||
.nr_blocks_shift = 8,
|
||||
.dual_spi = 1,
|
||||
.name = "GD25LQ128",
|
||||
},
|
||||
};
|
||||
|
|
|
@ -59,6 +59,34 @@ static int do_spi_flash_cmd(const struct spi_slave *spi, const void *dout,
|
|||
return ret;
|
||||
}
|
||||
|
||||
static int do_dual_read_cmd(const struct spi_slave *spi, const void *dout,
|
||||
size_t bytes_out, void *din, size_t bytes_in)
|
||||
{
|
||||
int ret;
|
||||
|
||||
/*
|
||||
* spi_xfer_vector() will automatically fall back to .xfer() if
|
||||
* .xfer_vector() is unimplemented. So using vector API here is more
|
||||
* flexible, even though a controller that implements .xfer_vector()
|
||||
* and (the non-vector based) .xfer_dual() but not .xfer() would be
|
||||
* pretty odd.
|
||||
*/
|
||||
struct spi_op vector = { .dout = dout, .bytesout = bytes_out,
|
||||
.din = NULL, .bytesin = 0 };
|
||||
|
||||
ret = spi_claim_bus(spi);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = spi_xfer_vector(spi, &vector, 1);
|
||||
|
||||
if (!ret)
|
||||
ret = spi->ctrlr->xfer_dual(spi, NULL, 0, din, bytes_in);
|
||||
|
||||
spi_release_bus(spi);
|
||||
return ret;
|
||||
}
|
||||
|
||||
int spi_flash_cmd(const struct spi_slave *spi, u8 cmd, void *response, size_t len)
|
||||
{
|
||||
int ret = do_spi_flash_cmd(spi, &cmd, sizeof(cmd), response, len);
|
||||
|
@ -105,6 +133,11 @@ static int spi_flash_read_chunked(const struct spi_flash *flash, u32 offset,
|
|||
cmd_len = 4;
|
||||
cmd[0] = CMD_READ_ARRAY_SLOW;
|
||||
do_cmd = do_spi_flash_cmd;
|
||||
} else if (flash->flags.dual_spi && flash->spi.ctrlr->xfer_dual) {
|
||||
cmd_len = 5;
|
||||
cmd[0] = CMD_READ_FAST_DUAL_OUTPUT;
|
||||
cmd[4] = 0;
|
||||
do_cmd = do_dual_read_cmd;
|
||||
} else {
|
||||
cmd_len = 5;
|
||||
cmd[0] = CMD_READ_ARRAY_FAST;
|
||||
|
@ -347,8 +380,12 @@ int spi_flash_probe(unsigned int bus, unsigned int cs, struct spi_flash *flash)
|
|||
return -1;
|
||||
}
|
||||
|
||||
printk(BIOS_INFO, "SF: Detected %s with sector size 0x%x, total 0x%x\n",
|
||||
flash->name, flash->sector_size, flash->size);
|
||||
const char *mode_string = "";
|
||||
if (flash->flags.dual_spi && spi.ctrlr->xfer_dual)
|
||||
mode_string = " (Dual SPI mode)";
|
||||
printk(BIOS_INFO,
|
||||
"SF: Detected %s with sector size 0x%x, total 0x%x%s\n",
|
||||
flash->name, flash->sector_size, flash->size, mode_string);
|
||||
if (bus == CONFIG_BOOT_DEVICE_SPI_FLASH_BUS
|
||||
&& flash->size != CONFIG_ROM_SIZE) {
|
||||
printk(BIOS_ERR, "SF size 0x%x does not correspond to"
|
||||
|
|
|
@ -23,6 +23,8 @@
|
|||
#define CMD_READ_ARRAY_FAST 0x0b
|
||||
#define CMD_READ_ARRAY_LEGACY 0xe8
|
||||
|
||||
#define CMD_READ_FAST_DUAL_OUTPUT 0x3b
|
||||
|
||||
#define CMD_READ_STATUS 0x05
|
||||
#define CMD_WRITE_ENABLE 0x06
|
||||
|
||||
|
|
|
@ -26,7 +26,9 @@
|
|||
|
||||
struct winbond_spi_flash_params {
|
||||
uint16_t id;
|
||||
uint8_t l2_page_size_shift;
|
||||
uint8_t dual_spi : 1;
|
||||
uint8_t _reserved_for_flags : 3;
|
||||
uint8_t l2_page_size_shift : 4;
|
||||
uint8_t pages_per_sector_shift : 4;
|
||||
uint8_t sectors_per_block_shift : 4;
|
||||
uint8_t nr_blocks_shift;
|
||||
|
@ -123,6 +125,7 @@ static const struct winbond_spi_flash_params winbond_spi_flash_table[] = {
|
|||
.sectors_per_block_shift = 4,
|
||||
.nr_blocks_shift = 4,
|
||||
.name = "W25X80",
|
||||
.dual_spi = 1,
|
||||
},
|
||||
{
|
||||
.id = 0x3015,
|
||||
|
@ -131,6 +134,7 @@ static const struct winbond_spi_flash_params winbond_spi_flash_table[] = {
|
|||
.sectors_per_block_shift = 4,
|
||||
.nr_blocks_shift = 5,
|
||||
.name = "W25X16",
|
||||
.dual_spi = 1,
|
||||
},
|
||||
{
|
||||
.id = 0x3016,
|
||||
|
@ -139,6 +143,7 @@ static const struct winbond_spi_flash_params winbond_spi_flash_table[] = {
|
|||
.sectors_per_block_shift = 4,
|
||||
.nr_blocks_shift = 6,
|
||||
.name = "W25X32",
|
||||
.dual_spi = 1,
|
||||
},
|
||||
{
|
||||
.id = 0x3017,
|
||||
|
@ -147,6 +152,7 @@ static const struct winbond_spi_flash_params winbond_spi_flash_table[] = {
|
|||
.sectors_per_block_shift = 4,
|
||||
.nr_blocks_shift = 7,
|
||||
.name = "W25X64",
|
||||
.dual_spi = 1,
|
||||
},
|
||||
{
|
||||
.id = 0x4014,
|
||||
|
@ -155,6 +161,7 @@ static const struct winbond_spi_flash_params winbond_spi_flash_table[] = {
|
|||
.sectors_per_block_shift = 4,
|
||||
.nr_blocks_shift = 4,
|
||||
.name = "W25Q80_V",
|
||||
.dual_spi = 1,
|
||||
},
|
||||
{
|
||||
.id = 0x4015,
|
||||
|
@ -163,6 +170,7 @@ static const struct winbond_spi_flash_params winbond_spi_flash_table[] = {
|
|||
.sectors_per_block_shift = 4,
|
||||
.nr_blocks_shift = 5,
|
||||
.name = "W25Q16_V",
|
||||
.dual_spi = 1,
|
||||
.protection_granularity_shift = 16,
|
||||
.bp_bits = 3,
|
||||
},
|
||||
|
@ -173,6 +181,7 @@ static const struct winbond_spi_flash_params winbond_spi_flash_table[] = {
|
|||
.sectors_per_block_shift = 4,
|
||||
.nr_blocks_shift = 5,
|
||||
.name = "W25Q16DW",
|
||||
.dual_spi = 1,
|
||||
.protection_granularity_shift = 16,
|
||||
.bp_bits = 3,
|
||||
},
|
||||
|
@ -183,6 +192,7 @@ static const struct winbond_spi_flash_params winbond_spi_flash_table[] = {
|
|||
.sectors_per_block_shift = 4,
|
||||
.nr_blocks_shift = 6,
|
||||
.name = "W25Q32_V",
|
||||
.dual_spi = 1,
|
||||
.protection_granularity_shift = 16,
|
||||
.bp_bits = 3,
|
||||
},
|
||||
|
@ -193,6 +203,7 @@ static const struct winbond_spi_flash_params winbond_spi_flash_table[] = {
|
|||
.sectors_per_block_shift = 4,
|
||||
.nr_blocks_shift = 6,
|
||||
.name = "W25Q32DW",
|
||||
.dual_spi = 1,
|
||||
.protection_granularity_shift = 16,
|
||||
.bp_bits = 3,
|
||||
},
|
||||
|
@ -203,6 +214,7 @@ static const struct winbond_spi_flash_params winbond_spi_flash_table[] = {
|
|||
.sectors_per_block_shift = 4,
|
||||
.nr_blocks_shift = 7,
|
||||
.name = "W25Q64_V",
|
||||
.dual_spi = 1,
|
||||
.protection_granularity_shift = 17,
|
||||
.bp_bits = 3,
|
||||
},
|
||||
|
@ -213,6 +225,7 @@ static const struct winbond_spi_flash_params winbond_spi_flash_table[] = {
|
|||
.sectors_per_block_shift = 4,
|
||||
.nr_blocks_shift = 7,
|
||||
.name = "W25Q64DW",
|
||||
.dual_spi = 1,
|
||||
.protection_granularity_shift = 17,
|
||||
.bp_bits = 3,
|
||||
},
|
||||
|
@ -223,6 +236,7 @@ static const struct winbond_spi_flash_params winbond_spi_flash_table[] = {
|
|||
.sectors_per_block_shift = 4,
|
||||
.nr_blocks_shift = 8,
|
||||
.name = "W25Q128_V",
|
||||
.dual_spi = 1,
|
||||
.protection_granularity_shift = 18,
|
||||
.bp_bits = 3,
|
||||
},
|
||||
|
@ -233,6 +247,7 @@ static const struct winbond_spi_flash_params winbond_spi_flash_table[] = {
|
|||
.sectors_per_block_shift = 4,
|
||||
.nr_blocks_shift = 8,
|
||||
.name = "W25Q128FW",
|
||||
.dual_spi = 1,
|
||||
.protection_granularity_shift = 18,
|
||||
.bp_bits = 3,
|
||||
},
|
||||
|
@ -243,6 +258,7 @@ static const struct winbond_spi_flash_params winbond_spi_flash_table[] = {
|
|||
.sectors_per_block_shift = 4,
|
||||
.nr_blocks_shift = 8,
|
||||
.name = "W25Q128J",
|
||||
.dual_spi = 1,
|
||||
.protection_granularity_shift = 18,
|
||||
.bp_bits = 3,
|
||||
},
|
||||
|
@ -253,6 +269,7 @@ static const struct winbond_spi_flash_params winbond_spi_flash_table[] = {
|
|||
.sectors_per_block_shift = 4,
|
||||
.nr_blocks_shift = 9,
|
||||
.name = "W25Q256_V",
|
||||
.dual_spi = 1,
|
||||
.protection_granularity_shift = 16,
|
||||
.bp_bits = 4,
|
||||
},
|
||||
|
@ -263,6 +280,7 @@ static const struct winbond_spi_flash_params winbond_spi_flash_table[] = {
|
|||
.sectors_per_block_shift = 4,
|
||||
.nr_blocks_shift = 9,
|
||||
.name = "W25Q256J",
|
||||
.dual_spi = 1,
|
||||
.protection_granularity_shift = 16,
|
||||
.bp_bits = 4,
|
||||
},
|
||||
|
@ -681,6 +699,8 @@ int spi_flash_probe_winbond(const struct spi_slave *spi, u8 *idcode,
|
|||
flash->erase_cmd = CMD_W25_SE;
|
||||
flash->status_cmd = CMD_W25_RDSR;
|
||||
|
||||
flash->flags.dual_spi = params->dual_spi;
|
||||
|
||||
flash->ops = &spi_flash_ops;
|
||||
flash->driver_private = params;
|
||||
|
||||
|
|
|
@ -125,6 +125,7 @@ enum {
|
|||
* setup: Setup given SPI device bus.
|
||||
* xfer: Perform one SPI transfer operation.
|
||||
* xfer_vector: Vector of SPI transfer operations.
|
||||
* xfer_dual: (optional) Perform one SPI transfer in Dual SPI mode.
|
||||
* max_xfer_size: Maximum transfer size supported by the controller
|
||||
* (0 = invalid,
|
||||
* SPI_CTRLR_DEFAULT_MAX_XFER_SIZE = unlimited)
|
||||
|
@ -145,6 +146,8 @@ struct spi_ctrlr {
|
|||
size_t bytesout, void *din, size_t bytesin);
|
||||
int (*xfer_vector)(const struct spi_slave *slave,
|
||||
struct spi_op vectors[], size_t count);
|
||||
int (*xfer_dual)(const struct spi_slave *slave, const void *dout,
|
||||
size_t bytesout, void *din, size_t bytesin);
|
||||
uint32_t max_xfer_size;
|
||||
uint32_t flags;
|
||||
int (*flash_probe)(const struct spi_slave *slave,
|
||||
|
|
|
@ -90,6 +90,13 @@ struct spi_flash_ops {
|
|||
struct spi_flash {
|
||||
struct spi_slave spi;
|
||||
u8 vendor;
|
||||
union {
|
||||
u8 raw;
|
||||
struct {
|
||||
u8 dual_spi : 1;
|
||||
u8 _reserved : 7;
|
||||
};
|
||||
} flags;
|
||||
u16 model;
|
||||
const char *name;
|
||||
u32 size;
|
||||
|
|
Loading…
Reference in New Issue