From f7e1de4c6a43beec438bce04571469145086efed Mon Sep 17 00:00:00 2001 From: Chin-Ting Kuo Date: Fri, 19 Aug 2022 17:01:08 +0800 Subject: spi-mem: Add dirmap API from Linux This adds the dirmap API originally introduced in Linux commit aa167f3fed0c ("spi: spi-mem: Add a new API to support direct mapping"). This also includes several follow-up patches and fixes. Changes from Linux include: * Added Kconfig option * Changed struct device to struct udevice * Changed struct spi_mem to struct spi_slave This patch is obtained from the following patch https://patchwork.ozlabs.org/project/uboot/patch/20210205043924.149504-3-seanga2@gmail.com/ The corresponding Linux kernel SHA1 is aa167f3fed0c. Signed-off-by: Chin-Ting Kuo Signed-off-by: Sean Anderson Acked-by: Pratyush Yadav --- include/spi-mem.h | 79 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 79 insertions(+) (limited to 'include') diff --git a/include/spi-mem.h b/include/spi-mem.h index 32ffdc2e0f9..b07cf2ed83d 100644 --- a/include/spi-mem.h +++ b/include/spi-mem.h @@ -134,6 +134,48 @@ struct spi_mem_op { .dummy = __dummy, \ .data = __data, \ } +/** + * struct spi_mem_dirmap_info - Direct mapping information + * @op_tmpl: operation template that should be used by the direct mapping when + * the memory device is accessed + * @offset: absolute offset this direct mapping is pointing to + * @length: length in byte of this direct mapping + * + * This information is used by the controller specific implementation to know + * the portion of memory that is directly mapped and the spi_mem_op that should + * be used to access the device. + * A direct mapping is only valid for one direction (read or write) and this + * direction is directly encoded in the ->op_tmpl.data.dir field. + */ +struct spi_mem_dirmap_info { + struct spi_mem_op op_tmpl; + u64 offset; + u64 length; +}; + +/** + * struct spi_mem_dirmap_desc - Direct mapping descriptor + * @mem: the SPI memory device this direct mapping is attached to + * @info: information passed at direct mapping creation time + * @nodirmap: set to 1 if the SPI controller does not implement + * ->mem_ops->dirmap_create() or when this function returned an + * error. If @nodirmap is true, all spi_mem_dirmap_{read,write}() + * calls will use spi_mem_exec_op() to access the memory. This is a + * degraded mode that allows spi_mem drivers to use the same code + * no matter whether the controller supports direct mapping or not + * @priv: field pointing to controller specific data + * + * Common part of a direct mapping descriptor. This object is created by + * spi_mem_dirmap_create() and controller implementation of ->create_dirmap() + * can create/attach direct mapping resources to the descriptor in the ->priv + * field. + */ +struct spi_mem_dirmap_desc { + struct spi_slave *slave; + struct spi_mem_dirmap_info info; + unsigned int nodirmap; + void *priv; +}; #ifndef __UBOOT__ /** @@ -183,10 +225,32 @@ static inline void *spi_mem_get_drvdata(struct spi_mem *mem) * limitations) * @supports_op: check if an operation is supported by the controller * @exec_op: execute a SPI memory operation + * @dirmap_create: create a direct mapping descriptor that can later be used to + * access the memory device. This method is optional + * @dirmap_destroy: destroy a memory descriptor previous created by + * ->dirmap_create() + * @dirmap_read: read data from the memory device using the direct mapping + * created by ->dirmap_create(). The function can return less + * data than requested (for example when the request is crossing + * the currently mapped area), and the caller of + * spi_mem_dirmap_read() is responsible for calling it again in + * this case. + * @dirmap_write: write data to the memory device using the direct mapping + * created by ->dirmap_create(). The function can return less + * data than requested (for example when the request is crossing + * the currently mapped area), and the caller of + * spi_mem_dirmap_write() is responsible for calling it again in + * this case. * * This interface should be implemented by SPI controllers providing an * high-level interface to execute SPI memory operation, which is usually the * case for QSPI controllers. + * + * Note on ->dirmap_{read,write}(): drivers should avoid accessing the direct + * mapping from the CPU because doing that can stall the CPU waiting for the + * SPI mem transaction to finish, and this will make real-time maintainers + * unhappy and might make your system less reactive. Instead, drivers should + * use DMA to access this direct mapping. */ struct spi_controller_mem_ops { int (*adjust_op_size)(struct spi_slave *slave, struct spi_mem_op *op); @@ -194,6 +258,12 @@ struct spi_controller_mem_ops { const struct spi_mem_op *op); int (*exec_op)(struct spi_slave *slave, const struct spi_mem_op *op); + int (*dirmap_create)(struct spi_mem_dirmap_desc *desc); + void (*dirmap_destroy)(struct spi_mem_dirmap_desc *desc); + ssize_t (*dirmap_read)(struct spi_mem_dirmap_desc *desc, + u64 offs, size_t len, void *buf); + ssize_t (*dirmap_write)(struct spi_mem_dirmap_desc *desc, + u64 offs, size_t len, const void *buf); }; #ifndef __UBOOT__ @@ -260,6 +330,15 @@ int spi_mem_exec_op(struct spi_slave *slave, const struct spi_mem_op *op); bool spi_mem_default_supports_op(struct spi_slave *mem, const struct spi_mem_op *op); +struct spi_mem_dirmap_desc * +spi_mem_dirmap_create(struct spi_slave *mem, + const struct spi_mem_dirmap_info *info); +void spi_mem_dirmap_destroy(struct spi_mem_dirmap_desc *desc); +ssize_t spi_mem_dirmap_read(struct spi_mem_dirmap_desc *desc, + u64 offs, size_t len, void *buf); +ssize_t spi_mem_dirmap_write(struct spi_mem_dirmap_desc *desc, + u64 offs, size_t len, const void *buf); + #ifndef __UBOOT__ int spi_mem_driver_register_with_owner(struct spi_mem_driver *drv, struct module *owner); -- cgit v1.3.1 From 463cdf66632a0d67bf824cb43b6c1b41782d0765 Mon Sep 17 00:00:00 2001 From: Chin-Ting Kuo Date: Fri, 19 Aug 2022 17:01:09 +0800 Subject: mtd: spi-nor: Use spi-mem dirmap API This adds support for the dirmap API to the spi-nor subsystem, as introduced in Linux commit df5c21002cf4 ("mtd: spi-nor: use spi-mem dirmap API"). This patch is synchronize from the following patch https://patchwork.ozlabs.org/project/uboot/patch/20210205043924.149504-4-seanga2@gmail.com/ The corresponding Linux kernel SHA1 is df5c21002cf4. Signed-off-by: Chin-Ting Kuo Signed-off-by: Sean Anderson Acked-by: Pratyush Yadav --- drivers/mtd/spi/sf_probe.c | 76 ++++++++++++++++++++++++++++++++++++++++++ drivers/mtd/spi/spi-nor-core.c | 55 +++++++++++++++++++++--------- include/linux/mtd/spi-nor.h | 18 ++++++++++ 3 files changed, 133 insertions(+), 16 deletions(-) (limited to 'include') diff --git a/drivers/mtd/spi/sf_probe.c b/drivers/mtd/spi/sf_probe.c index f461082e03d..e192f97efdc 100644 --- a/drivers/mtd/spi/sf_probe.c +++ b/drivers/mtd/spi/sf_probe.c @@ -10,13 +10,69 @@ #include #include #include +#include #include #include #include #include +#include #include "sf_internal.h" +static int spi_nor_create_read_dirmap(struct spi_nor *nor) +{ + struct spi_mem_dirmap_info info = { + .op_tmpl = SPI_MEM_OP(SPI_MEM_OP_CMD(nor->read_opcode, 0), + SPI_MEM_OP_ADDR(nor->addr_width, 0, 0), + SPI_MEM_OP_DUMMY(nor->read_dummy, 0), + SPI_MEM_OP_DATA_IN(0, NULL, 0)), + .offset = 0, + .length = nor->mtd.size, + }; + struct spi_mem_op *op = &info.op_tmpl; + + /* get transfer protocols. */ + spi_nor_setup_op(nor, op, nor->read_proto); + op->data.buswidth = spi_nor_get_protocol_data_nbits(nor->read_proto); + + /* convert the dummy cycles to the number of bytes */ + op->dummy.nbytes = (nor->read_dummy * op->dummy.buswidth) / 8; + if (spi_nor_protocol_is_dtr(nor->read_proto)) + op->dummy.nbytes *= 2; + + nor->dirmap.rdesc = spi_mem_dirmap_create(nor->spi, &info); + if (IS_ERR(nor->dirmap.rdesc)) + return PTR_ERR(nor->dirmap.rdesc); + + return 0; +} + +static int spi_nor_create_write_dirmap(struct spi_nor *nor) +{ + struct spi_mem_dirmap_info info = { + .op_tmpl = SPI_MEM_OP(SPI_MEM_OP_CMD(nor->program_opcode, 0), + SPI_MEM_OP_ADDR(nor->addr_width, 0, 0), + SPI_MEM_OP_NO_DUMMY, + SPI_MEM_OP_DATA_OUT(0, NULL, 0)), + .offset = 0, + .length = nor->mtd.size, + }; + struct spi_mem_op *op = &info.op_tmpl; + + /* get transfer protocols. */ + spi_nor_setup_op(nor, op, nor->write_proto); + op->data.buswidth = spi_nor_get_protocol_data_nbits(nor->write_proto); + + if (nor->program_opcode == SPINOR_OP_AAI_WP && nor->sst_write_second) + op->addr.nbytes = 0; + + nor->dirmap.wdesc = spi_mem_dirmap_create(nor->spi, &info); + if (IS_ERR(nor->dirmap.wdesc)) + return PTR_ERR(nor->dirmap.wdesc); + + return 0; +} + /** * spi_flash_probe_slave() - Probe for a SPI flash device on a bus * @@ -45,6 +101,16 @@ static int spi_flash_probe_slave(struct spi_flash *flash) if (ret) goto err_read_id; + if (CONFIG_IS_ENABLED(SPI_DIRMAP)) { + ret = spi_nor_create_read_dirmap(flash); + if (ret) + return ret; + + ret = spi_nor_create_write_dirmap(flash); + if (ret) + return ret; + } + if (CONFIG_IS_ENABLED(SPI_FLASH_MTD)) ret = spi_flash_mtd_register(flash); @@ -83,6 +149,11 @@ struct spi_flash *spi_flash_probe(unsigned int busnum, unsigned int cs, void spi_flash_free(struct spi_flash *flash) { + if (CONFIG_IS_ENABLED(SPI_DIRMAP)) { + spi_mem_dirmap_destroy(flash->dirmap.wdesc); + spi_mem_dirmap_destroy(flash->dirmap.rdesc); + } + if (CONFIG_IS_ENABLED(SPI_FLASH_MTD)) spi_flash_mtd_unregister(flash); @@ -153,6 +224,11 @@ static int spi_flash_std_remove(struct udevice *dev) struct spi_flash *flash = dev_get_uclass_priv(dev); int ret; + if (CONFIG_IS_ENABLED(SPI_DIRMAP)) { + spi_mem_dirmap_destroy(flash->dirmap.wdesc); + spi_mem_dirmap_destroy(flash->dirmap.rdesc); + } + ret = spi_nor_remove(flash); if (ret) return ret; diff --git a/drivers/mtd/spi/spi-nor-core.c b/drivers/mtd/spi/spi-nor-core.c index e3c86e080a1..f236e875107 100644 --- a/drivers/mtd/spi/spi-nor-core.c +++ b/drivers/mtd/spi/spi-nor-core.c @@ -246,9 +246,9 @@ static u8 spi_nor_get_cmd_ext(const struct spi_nor *nor, * need to be initialized. * @proto: the protocol from which the properties need to be set. */ -static void spi_nor_setup_op(const struct spi_nor *nor, - struct spi_mem_op *op, - const enum spi_nor_protocol proto) +void spi_nor_setup_op(const struct spi_nor *nor, + struct spi_mem_op *op, + const enum spi_nor_protocol proto) { u8 ext; @@ -369,13 +369,29 @@ static ssize_t spi_nor_read_data(struct spi_nor *nor, loff_t from, size_t len, while (remaining) { op.data.nbytes = remaining < UINT_MAX ? remaining : UINT_MAX; - ret = spi_mem_adjust_op_size(nor->spi, &op); - if (ret) - return ret; - ret = spi_mem_exec_op(nor->spi, &op); - if (ret) - return ret; + if (CONFIG_IS_ENABLED(SPI_DIRMAP) && nor->dirmap.rdesc) { + /* + * Record current operation information which may be used + * when the address or data length exceeds address mapping. + */ + memcpy(&nor->dirmap.rdesc->info.op_tmpl, &op, + sizeof(struct spi_mem_op)); + ret = spi_mem_dirmap_read(nor->dirmap.rdesc, + op.addr.val, op.data.nbytes, + op.data.buf.in); + if (ret < 0) + return ret; + op.data.nbytes = ret; + } else { + ret = spi_mem_adjust_op_size(nor->spi, &op); + if (ret) + return ret; + + ret = spi_mem_exec_op(nor->spi, &op); + if (ret) + return ret; + } op.addr.val += op.data.nbytes; remaining -= op.data.nbytes; @@ -400,14 +416,21 @@ static ssize_t spi_nor_write_data(struct spi_nor *nor, loff_t to, size_t len, spi_nor_setup_op(nor, &op, nor->write_proto); - ret = spi_mem_adjust_op_size(nor->spi, &op); - if (ret) - return ret; - op.data.nbytes = len < op.data.nbytes ? len : op.data.nbytes; + if (CONFIG_IS_ENABLED(SPI_DIRMAP) && nor->dirmap.wdesc) { + memcpy(&nor->dirmap.wdesc->info.op_tmpl, &op, + sizeof(struct spi_mem_op)); + op.data.nbytes = spi_mem_dirmap_write(nor->dirmap.wdesc, op.addr.val, + op.data.nbytes, op.data.buf.out); + } else { + ret = spi_mem_adjust_op_size(nor->spi, &op); + if (ret) + return ret; + op.data.nbytes = len < op.data.nbytes ? len : op.data.nbytes; - ret = spi_mem_exec_op(nor->spi, &op); - if (ret) - return ret; + ret = spi_mem_exec_op(nor->spi, &op); + if (ret) + return ret; + } return op.data.nbytes; } diff --git a/include/linux/mtd/spi-nor.h b/include/linux/mtd/spi-nor.h index 2595bad9dfe..638d807ee55 100644 --- a/include/linux/mtd/spi-nor.h +++ b/include/linux/mtd/spi-nor.h @@ -11,6 +11,7 @@ #include #include #include +#include /* * Manufacturer IDs @@ -522,6 +523,7 @@ struct spi_flash { * @quad_enable: [FLASH-SPECIFIC] enables SPI NOR quad mode * @octal_dtr_enable: [FLASH-SPECIFIC] enables SPI NOR octal DTR mode. * @ready: [FLASH-SPECIFIC] check if the flash is ready + * @dirmap: pointers to struct spi_mem_dirmap_desc for reads/writes. * @priv: the private data */ struct spi_nor { @@ -572,6 +574,11 @@ struct spi_nor { int (*octal_dtr_enable)(struct spi_nor *nor); int (*ready)(struct spi_nor *nor); + struct { + struct spi_mem_dirmap_desc *rdesc; + struct spi_mem_dirmap_desc *wdesc; + } dirmap; + void *priv; char mtd_name[MTD_NAME_SIZE(MTD_DEV_TYPE_NOR)]; /* Compatibility for spi_flash, remove once sf layer is merged with mtd */ @@ -595,6 +602,17 @@ device_node *spi_nor_get_flash_node(struct spi_nor *nor) } #endif /* __UBOOT__ */ +/** + * spi_nor_setup_op() - Set up common properties of a spi-mem op. + * @nor: pointer to a 'struct spi_nor' + * @op: pointer to the 'struct spi_mem_op' whose properties + * need to be initialized. + * @proto: the protocol from which the properties need to be set. + */ +void spi_nor_setup_op(const struct spi_nor *nor, + struct spi_mem_op *op, + const enum spi_nor_protocol proto); + /** * spi_nor_scan() - scan the SPI NOR * @nor: the spi_nor structure -- cgit v1.3.1