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)
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
bool "SPI flash Bank/Extended address register support"
help

View File

@ -2299,6 +2299,194 @@ static int spi_nor_hwcaps_pp2cmd(u32 hwcaps)
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,
const struct spi_nor_flash_parameter *params,
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,
const struct flash_info *info,
const struct spi_nor_flash_parameter *params,
const struct spi_nor_hwcaps *hwcaps)
const struct spi_nor_flash_parameter *params)
{
u32 ignored_mask, shared_mask;
u32 shared_mask;
bool enable_quad_io;
int err;
/*
* 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;
}
spi_nor_adjust_hwcaps(nor, params, &shared_mask);
/* Select the (Fast) Read command. */
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,
const struct spi_nor_flash_parameter *params,
const struct spi_nor_hwcaps *hwcaps)
const struct spi_nor_flash_parameter *params)
{
if (!nor->setup)
return 0;
return nor->setup(nor, info, params, hwcaps);
return nor->setup(nor, info, params);
}
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;
const struct flash_info *info = NULL;
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;
int ret;
@ -2521,27 +2686,6 @@ int spi_nor_scan(struct spi_nor *nor)
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);
if (IS_ERR_OR_NULL(info))
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 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)
return ret;

View File

@ -105,3 +105,65 @@ int spi_mem_adjust_op_size(struct spi_slave *slave,
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_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 {
u8 num_mode_clocks;
u8 num_wait_states;
@ -461,8 +475,7 @@ struct spi_nor {
struct spi_nor_fixups *fixups;
int (*setup)(struct spi_nor *nor, const struct flash_info *info,
const struct spi_nor_flash_parameter *params,
const struct spi_nor_hwcaps *hwcaps);
const struct spi_nor_flash_parameter *params);
int (*prepare)(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);