mtd: spi-nor-core: Rework hwcaps selection

The spi-mem layer provides a spi_mem_supports_op() function to check
whether a specific operation is supported by the controller or not.
This is much more accurate than the hwcaps selection logic based on
SPI_{RX,TX}_ flags.

Rework the hwcaps selection logic to use spi_mem_supports_op().

To make sure the build doesn't break for boards not using CONFIG_DM_SPI,
add a simple SPI_{RX,TX}_ based hwcaps selection logic in spi-mem-nodm
similar to spi_mem_default_supports_op(). This change is only
compile-tested.

To avoid SPL size problems on the x530 board, the old hwcaps selection
is still kept around. Leaving the code in-place was getting difficult to
read and understand, so the code is restructured to have it all in one
isolated function. As a result of this, the parameter hwcaps to
spi_nor_setup() is no longer needed. Remove it.

Based on the Linux commit c76f5089796a (mtd: spi-nor: Rework hwcaps
selection for the spi-mem case, 2019-08-06)

Signed-off-by: Pratyush Yadav <p.yadav@ti.com>
Reviewed-by: Jagan Teki <jagan@amarulasolutions.com>
This commit is contained in:
Pratyush Yadav 2021-06-26 00:47:14 +05:30 committed by Jagan Teki
parent 8702188ce5
commit 71025f013c
4 changed files with 280 additions and 52 deletions

View file

@ -88,6 +88,15 @@ config SPI_FLASH_SFDP_SUPPORT
SPI NOR flashes using Serial Flash Discoverable Parameters (SFDP) SPI NOR flashes using Serial Flash Discoverable Parameters (SFDP)
tables as per JESD216 standard. tables as per JESD216 standard.
config SPI_FLASH_SMART_HWCAPS
bool "Smart hardware capability detection based on SPI MEM supports_op() hook"
default y
help
Enable support for smart hardware capability detection based on SPI
MEM supports_op() hook that lets controllers express whether they
can support a type of operation in a much more refined way compared
to using flags like SPI_RX_DUAL, SPI_TX_QUAD, etc.
config SPI_FLASH_BAR config SPI_FLASH_BAR
bool "SPI flash Bank/Extended address register support" bool "SPI flash Bank/Extended address register support"
help help

View file

@ -2299,6 +2299,194 @@ static int spi_nor_hwcaps_pp2cmd(u32 hwcaps)
ARRAY_SIZE(hwcaps_pp2cmd)); ARRAY_SIZE(hwcaps_pp2cmd));
} }
#ifdef CONFIG_SPI_FLASH_SMART_HWCAPS
/**
* spi_nor_check_op - check if the operation is supported by controller
* @nor: pointer to a 'struct spi_nor'
* @op: pointer to op template to be checked
*
* Returns 0 if operation is supported, -ENOTSUPP otherwise.
*/
static int spi_nor_check_op(struct spi_nor *nor,
struct spi_mem_op *op)
{
/*
* First test with 4 address bytes. The opcode itself might be a 3B
* addressing opcode but we don't care, because SPI controller
* implementation should not check the opcode, but just the sequence.
*/
op->addr.nbytes = 4;
if (!spi_mem_supports_op(nor->spi, op)) {
if (nor->mtd.size > SZ_16M)
return -ENOTSUPP;
/* If flash size <= 16MB, 3 address bytes are sufficient */
op->addr.nbytes = 3;
if (!spi_mem_supports_op(nor->spi, op))
return -ENOTSUPP;
}
return 0;
}
/**
* spi_nor_check_readop - check if the read op is supported by controller
* @nor: pointer to a 'struct spi_nor'
* @read: pointer to op template to be checked
*
* Returns 0 if operation is supported, -ENOTSUPP otherwise.
*/
static int spi_nor_check_readop(struct spi_nor *nor,
const struct spi_nor_read_command *read)
{
struct spi_mem_op op = SPI_MEM_OP(SPI_MEM_OP_CMD(read->opcode, 1),
SPI_MEM_OP_ADDR(3, 0, 1),
SPI_MEM_OP_DUMMY(0, 1),
SPI_MEM_OP_DATA_IN(0, NULL, 1));
op.cmd.buswidth = spi_nor_get_protocol_inst_nbits(read->proto);
op.addr.buswidth = spi_nor_get_protocol_addr_nbits(read->proto);
op.data.buswidth = spi_nor_get_protocol_data_nbits(read->proto);
op.dummy.buswidth = op.addr.buswidth;
op.dummy.nbytes = (read->num_mode_clocks + read->num_wait_states) *
op.dummy.buswidth / 8;
return spi_nor_check_op(nor, &op);
}
/**
* spi_nor_check_pp - check if the page program op is supported by controller
* @nor: pointer to a 'struct spi_nor'
* @pp: pointer to op template to be checked
*
* Returns 0 if operation is supported, -ENOTSUPP otherwise.
*/
static int spi_nor_check_pp(struct spi_nor *nor,
const struct spi_nor_pp_command *pp)
{
struct spi_mem_op op = SPI_MEM_OP(SPI_MEM_OP_CMD(pp->opcode, 1),
SPI_MEM_OP_ADDR(3, 0, 1),
SPI_MEM_OP_NO_DUMMY,
SPI_MEM_OP_DATA_OUT(0, NULL, 1));
op.cmd.buswidth = spi_nor_get_protocol_inst_nbits(pp->proto);
op.addr.buswidth = spi_nor_get_protocol_addr_nbits(pp->proto);
op.data.buswidth = spi_nor_get_protocol_data_nbits(pp->proto);
return spi_nor_check_op(nor, &op);
}
/**
* spi_nor_adjust_hwcaps - Find optimal Read/Write protocol based on SPI
* controller capabilities
* @nor: pointer to a 'struct spi_nor'
* @params: pointer to the 'struct spi_nor_flash_parameter'
* representing SPI NOR flash capabilities
* @hwcaps: pointer to resulting capabilities after adjusting
* according to controller and flash's capability
*
* Discard caps based on what the SPI controller actually supports (using
* spi_mem_supports_op()).
*/
static void
spi_nor_adjust_hwcaps(struct spi_nor *nor,
const struct spi_nor_flash_parameter *params,
u32 *hwcaps)
{
unsigned int cap;
/*
* Enable all caps by default. We will mask them after checking what's
* really supported using spi_mem_supports_op().
*/
*hwcaps = SNOR_HWCAPS_ALL;
/* DTR modes are not supported yet, mask them all. */
*hwcaps &= ~SNOR_HWCAPS_DTR;
/* X-X-X modes are not supported yet, mask them all. */
*hwcaps &= ~SNOR_HWCAPS_X_X_X;
for (cap = 0; cap < sizeof(*hwcaps) * BITS_PER_BYTE; cap++) {
int rdidx, ppidx;
if (!(*hwcaps & BIT(cap)))
continue;
rdidx = spi_nor_hwcaps_read2cmd(BIT(cap));
if (rdidx >= 0 &&
spi_nor_check_readop(nor, &params->reads[rdidx]))
*hwcaps &= ~BIT(cap);
ppidx = spi_nor_hwcaps_pp2cmd(BIT(cap));
if (ppidx < 0)
continue;
if (spi_nor_check_pp(nor, &params->page_programs[ppidx]))
*hwcaps &= ~BIT(cap);
}
}
#else
/**
* spi_nor_adjust_hwcaps - Find optimal Read/Write protocol based on SPI
* controller capabilities
* @nor: pointer to a 'struct spi_nor'
* @params: pointer to the 'struct spi_nor_flash_parameter'
* representing SPI NOR flash capabilities
* @hwcaps: pointer to resulting capabilities after adjusting
* according to controller and flash's capability
*
* Select caps based on what the SPI controller and SPI flash both support.
*/
static void
spi_nor_adjust_hwcaps(struct spi_nor *nor,
const struct spi_nor_flash_parameter *params,
u32 *hwcaps)
{
struct spi_slave *spi = nor->spi;
u32 ignored_mask = (SNOR_HWCAPS_READ_2_2_2 |
SNOR_HWCAPS_READ_4_4_4 |
SNOR_HWCAPS_READ_8_8_8 |
SNOR_HWCAPS_PP_4_4_4 |
SNOR_HWCAPS_PP_8_8_8);
u32 spi_hwcaps = (SNOR_HWCAPS_READ | SNOR_HWCAPS_READ_FAST |
SNOR_HWCAPS_PP);
/* Get the hardware capabilities the SPI controller supports. */
if (spi->mode & SPI_RX_OCTAL) {
spi_hwcaps |= SNOR_HWCAPS_READ_1_1_8;
if (spi->mode & SPI_TX_OCTAL)
spi_hwcaps |= (SNOR_HWCAPS_READ_1_8_8 |
SNOR_HWCAPS_PP_1_1_8 |
SNOR_HWCAPS_PP_1_8_8);
} else if (spi->mode & SPI_RX_QUAD) {
spi_hwcaps |= SNOR_HWCAPS_READ_1_1_4;
if (spi->mode & SPI_TX_QUAD)
spi_hwcaps |= (SNOR_HWCAPS_READ_1_4_4 |
SNOR_HWCAPS_PP_1_1_4 |
SNOR_HWCAPS_PP_1_4_4);
} else if (spi->mode & SPI_RX_DUAL) {
spi_hwcaps |= SNOR_HWCAPS_READ_1_1_2;
if (spi->mode & SPI_TX_DUAL)
spi_hwcaps |= SNOR_HWCAPS_READ_1_2_2;
}
/*
* Keep only the hardware capabilities supported by both the SPI
* controller and the SPI flash memory.
*/
*hwcaps = spi_hwcaps & params->hwcaps.mask;
if (*hwcaps & ignored_mask) {
dev_dbg(nor->dev,
"SPI n-n-n protocols are not supported yet.\n");
*hwcaps &= ~ignored_mask;
}
}
#endif /* CONFIG_SPI_FLASH_SMART_HWCAPS */
static int spi_nor_select_read(struct spi_nor *nor, static int spi_nor_select_read(struct spi_nor *nor,
const struct spi_nor_flash_parameter *params, const struct spi_nor_flash_parameter *params,
u32 shared_hwcaps) u32 shared_hwcaps)
@ -2379,30 +2567,13 @@ static int spi_nor_select_erase(struct spi_nor *nor,
static int spi_nor_default_setup(struct spi_nor *nor, static int spi_nor_default_setup(struct spi_nor *nor,
const struct flash_info *info, const struct flash_info *info,
const struct spi_nor_flash_parameter *params, const struct spi_nor_flash_parameter *params)
const struct spi_nor_hwcaps *hwcaps)
{ {
u32 ignored_mask, shared_mask; u32 shared_mask;
bool enable_quad_io; bool enable_quad_io;
int err; int err;
/* spi_nor_adjust_hwcaps(nor, params, &shared_mask);
* Keep only the hardware capabilities supported by both the SPI
* controller and the SPI flash memory.
*/
shared_mask = hwcaps->mask & params->hwcaps.mask;
/* SPI n-n-n protocols are not supported yet. */
ignored_mask = (SNOR_HWCAPS_READ_2_2_2 |
SNOR_HWCAPS_READ_4_4_4 |
SNOR_HWCAPS_READ_8_8_8 |
SNOR_HWCAPS_PP_4_4_4 |
SNOR_HWCAPS_PP_8_8_8);
if (shared_mask & ignored_mask) {
dev_dbg(nor->dev,
"SPI n-n-n protocols are not supported yet.\n");
shared_mask &= ~ignored_mask;
}
/* Select the (Fast) Read command. */ /* Select the (Fast) Read command. */
err = spi_nor_select_read(nor, params, shared_mask); err = spi_nor_select_read(nor, params, shared_mask);
@ -2440,13 +2611,12 @@ static int spi_nor_default_setup(struct spi_nor *nor,
} }
static int spi_nor_setup(struct spi_nor *nor, const struct flash_info *info, static int spi_nor_setup(struct spi_nor *nor, const struct flash_info *info,
const struct spi_nor_flash_parameter *params, const struct spi_nor_flash_parameter *params)
const struct spi_nor_hwcaps *hwcaps)
{ {
if (!nor->setup) if (!nor->setup)
return 0; return 0;
return nor->setup(nor, info, params, hwcaps); return nor->setup(nor, info, params);
} }
static int spi_nor_init(struct spi_nor *nor) static int spi_nor_init(struct spi_nor *nor)
@ -2502,11 +2672,6 @@ int spi_nor_scan(struct spi_nor *nor)
struct spi_nor_flash_parameter params; struct spi_nor_flash_parameter params;
const struct flash_info *info = NULL; const struct flash_info *info = NULL;
struct mtd_info *mtd = &nor->mtd; struct mtd_info *mtd = &nor->mtd;
struct spi_nor_hwcaps hwcaps = {
.mask = SNOR_HWCAPS_READ |
SNOR_HWCAPS_READ_FAST |
SNOR_HWCAPS_PP,
};
struct spi_slave *spi = nor->spi; struct spi_slave *spi = nor->spi;
int ret; int ret;
@ -2521,27 +2686,6 @@ int spi_nor_scan(struct spi_nor *nor)
nor->setup = spi_nor_default_setup; nor->setup = spi_nor_default_setup;
if (spi->mode & SPI_RX_OCTAL) {
hwcaps.mask |= SNOR_HWCAPS_READ_1_1_8;
if (spi->mode & SPI_TX_OCTAL)
hwcaps.mask |= (SNOR_HWCAPS_READ_1_8_8 |
SNOR_HWCAPS_PP_1_1_8 |
SNOR_HWCAPS_PP_1_8_8);
} else if (spi->mode & SPI_RX_QUAD) {
hwcaps.mask |= SNOR_HWCAPS_READ_1_1_4;
if (spi->mode & SPI_TX_QUAD)
hwcaps.mask |= (SNOR_HWCAPS_READ_1_4_4 |
SNOR_HWCAPS_PP_1_1_4 |
SNOR_HWCAPS_PP_1_4_4);
} else if (spi->mode & SPI_RX_DUAL) {
hwcaps.mask |= SNOR_HWCAPS_READ_1_1_2;
if (spi->mode & SPI_TX_DUAL)
hwcaps.mask |= SNOR_HWCAPS_READ_1_2_2;
}
info = spi_nor_read_id(nor); info = spi_nor_read_id(nor);
if (IS_ERR_OR_NULL(info)) if (IS_ERR_OR_NULL(info))
return -ENOENT; return -ENOENT;
@ -2616,7 +2760,7 @@ int spi_nor_scan(struct spi_nor *nor)
* - set the SPI protocols for register and memory accesses. * - set the SPI protocols for register and memory accesses.
* - set the Quad Enable bit if needed (required by SPI x-y-4 protos). * - set the Quad Enable bit if needed (required by SPI x-y-4 protos).
*/ */
ret = spi_nor_setup(nor, info, &params, &hwcaps); ret = spi_nor_setup(nor, info, &params);
if (ret) if (ret)
return ret; return ret;

View file

@ -105,3 +105,65 @@ int spi_mem_adjust_op_size(struct spi_slave *slave,
return 0; return 0;
} }
static int spi_check_buswidth_req(struct spi_slave *slave, u8 buswidth, bool tx)
{
u32 mode = slave->mode;
switch (buswidth) {
case 1:
return 0;
case 2:
if ((tx && (mode & (SPI_TX_DUAL | SPI_TX_QUAD))) ||
(!tx && (mode & (SPI_RX_DUAL | SPI_RX_QUAD))))
return 0;
break;
case 4:
if ((tx && (mode & SPI_TX_QUAD)) ||
(!tx && (mode & SPI_RX_QUAD)))
return 0;
break;
case 8:
if ((tx && (mode & SPI_TX_OCTAL)) ||
(!tx && (mode & SPI_RX_OCTAL)))
return 0;
break;
default:
break;
}
return -ENOTSUPP;
}
bool spi_mem_supports_op(struct spi_slave *slave, const struct spi_mem_op *op)
{
if (spi_check_buswidth_req(slave, op->cmd.buswidth, true))
return false;
if (op->addr.nbytes &&
spi_check_buswidth_req(slave, op->addr.buswidth, true))
return false;
if (op->dummy.nbytes &&
spi_check_buswidth_req(slave, op->dummy.buswidth, true))
return false;
if (op->data.nbytes &&
spi_check_buswidth_req(slave, op->data.buswidth,
op->data.dir == SPI_MEM_DATA_OUT))
return false;
if (op->cmd.dtr || op->addr.dtr || op->dummy.dtr || op->data.dtr)
return false;
if (op->cmd.nbytes != 1)
return false;
return true;
}

View file

@ -312,6 +312,20 @@ struct spi_nor_hwcaps {
#define SNOR_HWCAPS_PP_1_8_8 BIT(21) #define SNOR_HWCAPS_PP_1_8_8 BIT(21)
#define SNOR_HWCAPS_PP_8_8_8 BIT(22) #define SNOR_HWCAPS_PP_8_8_8 BIT(22)
#define SNOR_HWCAPS_X_X_X (SNOR_HWCAPS_READ_2_2_2 | \
SNOR_HWCAPS_READ_4_4_4 | \
SNOR_HWCAPS_READ_8_8_8 | \
SNOR_HWCAPS_PP_4_4_4 | \
SNOR_HWCAPS_PP_8_8_8)
#define SNOR_HWCAPS_DTR (SNOR_HWCAPS_READ_1_1_1_DTR | \
SNOR_HWCAPS_READ_1_2_2_DTR | \
SNOR_HWCAPS_READ_1_4_4_DTR | \
SNOR_HWCAPS_READ_1_8_8_DTR)
#define SNOR_HWCAPS_ALL (SNOR_HWCAPS_READ_MASK | \
SNOR_HWCAPS_PP_MASK)
struct spi_nor_read_command { struct spi_nor_read_command {
u8 num_mode_clocks; u8 num_mode_clocks;
u8 num_wait_states; u8 num_wait_states;
@ -461,8 +475,7 @@ struct spi_nor {
struct spi_nor_fixups *fixups; struct spi_nor_fixups *fixups;
int (*setup)(struct spi_nor *nor, const struct flash_info *info, int (*setup)(struct spi_nor *nor, const struct flash_info *info,
const struct spi_nor_flash_parameter *params, const struct spi_nor_flash_parameter *params);
const struct spi_nor_hwcaps *hwcaps);
int (*prepare)(struct spi_nor *nor, enum spi_nor_ops ops); int (*prepare)(struct spi_nor *nor, enum spi_nor_ops ops);
void (*unprepare)(struct spi_nor *nor, enum spi_nor_ops ops); void (*unprepare)(struct spi_nor *nor, enum spi_nor_ops ops);
int (*read_reg)(struct spi_nor *nor, u8 opcode, u8 *buf, int len); int (*read_reg)(struct spi_nor *nor, u8 opcode, u8 *buf, int len);