[RFC PATCH 00/13] spi: dw: Add support for XIP mode

This adds support for memory-mapped ("DIRMAP") reads (called XIP by the datasheet). In theory, these have better performance than regular reads. In practice, the CPU is already fast enough to max out the performance of this peripheral.
The real end-goal is to be able to boot a XIP kernel. This SoC has just barely enough SRAM to boot Linux, and using a XIP kernel would decrease the memory pressure to something tolerable. It would also probably absolutely *tank* the performance, since not only is SPI flash ~8x slower than regular memory, there is no cache to mitigate that latency. So it remains to be seen whether this is worth it at all.
This series is RFC because it currently reads all 1s on one board I have. On the other board I have, it reads and writes fine. I will try and investigate this, but I expect any changes to occur in "spi: dw: Add support for DIRMAP".
Sean Anderson (13): linux err: Synchronize with Linux 5.10 spi-mem: Add dirmap API from Linux mtd: spi-nor: use spi-mem dirmap API core: ofnode: Fix inconsistent returns of *_read_u32_array mux: Inline mux functions when CONFIG_MUX is disabled mux: Define a stub for mux_get_by_index if CONFIG_MUX is disabled mux: mmio: Only complain about idle-states if it is malformed spi: dw: Define XIP registers spi: dw: Add XIP and XIP_CONCURRENT caps spi: dw: Use a mux to access registers spi: dw: Add support for DIRMAP riscv: k210: Increase SPI3 bus clock to CPU speed riscv: k210: Add bindings for SPI XIP
arch/riscv/dts/k210.dtsi | 25 ++- drivers/core/ofnode.c | 15 +- drivers/mtd/spi/sf_probe.c | 79 ++++++++ drivers/mtd/spi/spi-nor-core.c | 45 +++-- drivers/mux/mmio.c | 5 +- drivers/spi/Kconfig | 10 + drivers/spi/designware_spi.c | 337 +++++++++++++++++++++++++++++++-- drivers/spi/spi-mem.c | 270 ++++++++++++++++++++++++++ include/linux/err.h | 21 +- include/linux/mtd/spi-nor.h | 6 + include/mux.h | 25 ++- include/spi-mem.h | 99 ++++++++++ 12 files changed, 889 insertions(+), 48 deletions(-)

This synchronizes linux/err.h with Linux 5.10. Notably, this adds PTR_ERR_OR_ZERO.
Signed-off-by: Sean Anderson seanga2@gmail.com ---
include/linux/err.h | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-)
diff --git a/include/linux/err.h b/include/linux/err.h index 5ede82432d..06a80f9f29 100644 --- a/include/linux/err.h +++ b/include/linux/err.h @@ -1,3 +1,4 @@ +/* SPDX-License-Identifier: GPL-2.0 */ #ifndef _LINUX_ERR_H #define _LINUX_ERR_H
@@ -9,7 +10,7 @@
/* * Kernel pointers have redundant information, so we can use a - * scheme where we can return either an error code or a dentry + * scheme where we can return either an error code or a normal * pointer with the same return value. * * This should be a per-architecture thing, to allow different @@ -21,24 +22,24 @@
#define IS_ERR_VALUE(x) unlikely((x) >= (unsigned long)-MAX_ERRNO)
-static inline void *ERR_PTR(long error) +static inline void * __must_check ERR_PTR(long error) { return (void *)(CONFIG_ERR_PTR_OFFSET + error); }
-static inline long PTR_ERR(const void *ptr) +static inline long __must_check PTR_ERR(__force const void *ptr) { return ((long)ptr - CONFIG_ERR_PTR_OFFSET); }
-static inline long IS_ERR(const void *ptr) +static inline bool __must_check IS_ERR(__force const void *ptr) { return IS_ERR_VALUE((unsigned long)PTR_ERR(ptr)); }
-static inline bool IS_ERR_OR_NULL(const void *ptr) +static inline bool __must_check IS_ERR_OR_NULL(__force const void *ptr) { - return !ptr || IS_ERR_VALUE((unsigned long)PTR_ERR(ptr)); + return unlikely(!ptr) || IS_ERR_VALUE((unsigned long)ptr); }
/** @@ -54,6 +55,14 @@ static inline void * __must_check ERR_CAST(__force const void *ptr) return (void *) ptr; }
+static inline int __must_check PTR_ERR_OR_ZERO(__force const void *ptr) +{ + if (IS_ERR(ptr)) + return PTR_ERR(ptr); + else + return 0; +} + #endif
#endif /* _LINUX_ERR_H */

On Thu, 4 Feb 2021 at 21:39, Sean Anderson seanga2@gmail.com wrote:
This synchronizes linux/err.h with Linux 5.10. Notably, this adds PTR_ERR_OR_ZERO.
Signed-off-by: Sean Anderson seanga2@gmail.com
include/linux/err.h | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-)
Reviewed-by: Simon Glass sjg@chromium.org

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
Signed-off-by: Sean Anderson seanga2@gmail.com ---
drivers/spi/Kconfig | 10 ++ drivers/spi/spi-mem.c | 270 ++++++++++++++++++++++++++++++++++++++++++ include/spi-mem.h | 99 ++++++++++++++++ 3 files changed, 379 insertions(+)
diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig index 1494c91763..9663b20c74 100644 --- a/drivers/spi/Kconfig +++ b/drivers/spi/Kconfig @@ -40,6 +40,16 @@ config SPI_MEM This extension is meant to simplify interaction with SPI memories by providing an high-level interface to send memory-like commands.
+config SPI_DIRMAP + bool "SPI direct mapping" + depends on SPI_MEM + help + Enable the SPI direct mapping API. Most modern SPI controllers can + directly map a SPI memory (or a portion of the SPI memory) in the CPU + address space. Most of the time this brings significant performance + improvements as it automates the whole process of sending SPI memory + operations every time a new region is accessed. + if DM_SPI
config ALTERA_SPI diff --git a/drivers/spi/spi-mem.c b/drivers/spi/spi-mem.c index 6772367ef7..597cd63c00 100644 --- a/drivers/spi/spi-mem.c +++ b/drivers/spi/spi-mem.c @@ -21,6 +21,8 @@ #include <spi.h> #include <spi-mem.h> #include <dm/device_compat.h> +#include <dm/devres.h> +#include <linux/bug.h> #endif
#ifndef __UBOOT__ @@ -468,6 +470,274 @@ int spi_mem_adjust_op_size(struct spi_slave *slave, struct spi_mem_op *op) } EXPORT_SYMBOL_GPL(spi_mem_adjust_op_size);
+#if CONFIG_IS_ENABLED(SPI_DIRMAP) +static ssize_t spi_mem_no_dirmap_read(struct spi_mem_dirmap_desc *desc, + u64 offs, size_t len, void *buf) +{ + struct spi_mem_op op = desc->info.op_tmpl; + int ret; + + op.addr.val = desc->info.offset + offs; + op.data.buf.in = buf; + op.data.nbytes = len; + ret = spi_mem_adjust_op_size(desc->slave, &op); + if (ret) + return ret; + + ret = spi_mem_exec_op(desc->slave, &op); + if (ret) + return ret; + + return op.data.nbytes; +} + +static ssize_t spi_mem_no_dirmap_write(struct spi_mem_dirmap_desc *desc, + u64 offs, size_t len, const void *buf) +{ + struct spi_mem_op op = desc->info.op_tmpl; + int ret; + + op.addr.val = desc->info.offset + offs; + op.data.buf.out = buf; + op.data.nbytes = len; + ret = spi_mem_adjust_op_size(desc->slave, &op); + if (ret) + return ret; + + ret = spi_mem_exec_op(desc->slave, &op); + if (ret) + return ret; + + return op.data.nbytes; +} + +/** + * spi_mem_dirmap_create() - Create a direct mapping descriptor + * @mem: SPI mem device this direct mapping should be created for + * @info: direct mapping information + * + * This function is creating a direct mapping descriptor which can then be used + * to access the memory using spi_mem_dirmap_read() or spi_mem_dirmap_write(). + * If the SPI controller driver does not support direct mapping, this function + * falls back to an implementation using spi_mem_exec_op(), so that the caller + * doesn't have to bother implementing a fallback on his own. + * + * Return: a valid pointer in case of success, and ERR_PTR() otherwise. + */ +struct spi_mem_dirmap_desc * +spi_mem_dirmap_create(struct spi_slave *slave, + const struct spi_mem_dirmap_info *info) +{ + struct udevice *bus = slave->dev->parent; + struct dm_spi_ops *ops = spi_get_ops(bus); + struct spi_mem_dirmap_desc *desc; + int ret = -ENOTSUPP; + + /* Make sure the number of address cycles is between 1 and 8 bytes. */ + if (!info->op_tmpl.addr.nbytes || info->op_tmpl.addr.nbytes > 8) + return ERR_PTR(-EINVAL); + + /* data.dir should either be SPI_MEM_DATA_IN or SPI_MEM_DATA_OUT. */ + if (info->op_tmpl.data.dir == SPI_MEM_NO_DATA) + return ERR_PTR(-EINVAL); + + desc = kzalloc(sizeof(*desc), GFP_KERNEL); + if (!desc) + return ERR_PTR(-ENOMEM); + + desc->slave = slave; + desc->info = *info; + if (ops->mem_ops && ops->mem_ops->dirmap_create) + ret = ops->mem_ops->dirmap_create(desc); + + if (ret) { + desc->nodirmap = true; + if (!spi_mem_supports_op(desc->slave, &desc->info.op_tmpl)) + ret = -ENOTSUPP; + else + ret = 0; + } + + if (ret) { + kfree(desc); + return ERR_PTR(ret); + } + + return desc; +} +EXPORT_SYMBOL_GPL(spi_mem_dirmap_create); + +/** + * spi_mem_dirmap_destroy() - Destroy a direct mapping descriptor + * @desc: the direct mapping descriptor to destroy + * + * This function destroys a direct mapping descriptor previously created by + * spi_mem_dirmap_create(). + */ +void spi_mem_dirmap_destroy(struct spi_mem_dirmap_desc *desc) +{ + struct udevice *bus = desc->slave->dev->parent; + struct dm_spi_ops *ops = spi_get_ops(bus); + + if (!desc->nodirmap && ops->mem_ops && ops->mem_ops->dirmap_destroy) + ops->mem_ops->dirmap_destroy(desc); + + kfree(desc); +} +EXPORT_SYMBOL_GPL(spi_mem_dirmap_destroy); + +#ifndef __UBOOT__ +static void devm_spi_mem_dirmap_release(struct udevice *dev, void *res) +{ + struct spi_mem_dirmap_desc *desc = *(struct spi_mem_dirmap_desc **)res; + + spi_mem_dirmap_destroy(desc); +} + +/** + * devm_spi_mem_dirmap_create() - Create a direct mapping descriptor and attach + * it to a device + * @dev: device the dirmap desc will be attached to + * @mem: SPI mem device this direct mapping should be created for + * @info: direct mapping information + * + * devm_ variant of the spi_mem_dirmap_create() function. See + * spi_mem_dirmap_create() for more details. + * + * Return: a valid pointer in case of success, and ERR_PTR() otherwise. + */ +struct spi_mem_dirmap_desc * +devm_spi_mem_dirmap_create(struct udevice *dev, struct spi_slave *slave, + const struct spi_mem_dirmap_info *info) +{ + struct spi_mem_dirmap_desc **ptr, *desc; + + ptr = devres_alloc(devm_spi_mem_dirmap_release, sizeof(*ptr), + GFP_KERNEL); + if (!ptr) + return ERR_PTR(-ENOMEM); + + desc = spi_mem_dirmap_create(slave, info); + if (IS_ERR(desc)) { + devres_free(ptr); + } else { + *ptr = desc; + devres_add(dev, ptr); + } + + return desc; +} +EXPORT_SYMBOL_GPL(devm_spi_mem_dirmap_create); + +static int devm_spi_mem_dirmap_match(struct udevice *dev, void *res, void *data) +{ + struct spi_mem_dirmap_desc **ptr = res; + + if (WARN_ON(!ptr || !*ptr)) + return 0; + + return *ptr == data; +} + +/** + * devm_spi_mem_dirmap_destroy() - Destroy a direct mapping descriptor attached + * to a device + * @dev: device the dirmap desc is attached to + * @desc: the direct mapping descriptor to destroy + * + * devm_ variant of the spi_mem_dirmap_destroy() function. See + * spi_mem_dirmap_destroy() for more details. + */ +void devm_spi_mem_dirmap_destroy(struct udevice *dev, + struct spi_mem_dirmap_desc *desc) +{ + devres_release(dev, devm_spi_mem_dirmap_release, + devm_spi_mem_dirmap_match, desc); +} +EXPORT_SYMBOL_GPL(devm_spi_mem_dirmap_destroy); +#endif /* __UBOOT__ */ + +/** + * spi_mem_dirmap_read() - Read data through a direct mapping + * @desc: direct mapping descriptor + * @offs: offset to start reading from. Note that this is not an absolute + * offset, but the offset within the direct mapping which already has + * its own offset + * @len: length in bytes + * @buf: destination buffer. This buffer must be DMA-able + * + * This function reads data from a memory device using a direct mapping + * previously instantiated with spi_mem_dirmap_create(). + * + * Return: the amount of data read from the memory device or a negative error + * code. Note that the returned size might be smaller than @len, and the caller + * is responsible for calling spi_mem_dirmap_read() again when that happens. + */ +ssize_t spi_mem_dirmap_read(struct spi_mem_dirmap_desc *desc, + u64 offs, size_t len, void *buf) +{ + struct udevice *bus = desc->slave->dev->parent; + struct dm_spi_ops *ops = spi_get_ops(bus); + ssize_t ret; + + if (desc->info.op_tmpl.data.dir != SPI_MEM_DATA_IN) + return -EINVAL; + + if (!len) + return 0; + + if (desc->nodirmap) + ret = spi_mem_no_dirmap_read(desc, offs, len, buf); + else if (ops->mem_ops && ops->mem_ops->dirmap_read) + ret = ops->mem_ops->dirmap_read(desc, offs, len, buf); + else + ret = -ENOTSUPP; + + return ret; +} +EXPORT_SYMBOL_GPL(spi_mem_dirmap_read); + +/** + * spi_mem_dirmap_write() - Write data through a direct mapping + * @desc: direct mapping descriptor + * @offs: offset to start writing from. Note that this is not an absolute + * offset, but the offset within the direct mapping which already has + * its own offset + * @len: length in bytes + * @buf: source buffer. This buffer must be DMA-able + * + * This function writes data to a memory device using a direct mapping + * previously instantiated with spi_mem_dirmap_create(). + * + * Return: the amount of data written to the memory device or a negative error + * code. Note that the returned size might be smaller than @len, and the caller + * is responsible for calling spi_mem_dirmap_write() again when that happens. + */ +ssize_t spi_mem_dirmap_write(struct spi_mem_dirmap_desc *desc, + u64 offs, size_t len, const void *buf) +{ + struct udevice *bus = desc->slave->dev->parent; + struct dm_spi_ops *ops = spi_get_ops(bus); + ssize_t ret; + + if (desc->info.op_tmpl.data.dir != SPI_MEM_DATA_OUT) + return -EINVAL; + + if (!len) + return 0; + + if (desc->nodirmap) + ret = spi_mem_no_dirmap_write(desc, offs, len, buf); + else if (ops->mem_ops && ops->mem_ops->dirmap_write) + ret = ops->mem_ops->dirmap_write(desc, offs, len, buf); + else + ret = -ENOTSUPP; + + return ret; +} +EXPORT_SYMBOL_GPL(spi_mem_dirmap_write); +#endif /* CONFIG_SPI_DIRMAP */ + #ifndef __UBOOT__ static inline struct spi_mem_driver *to_spi_mem_drv(struct device_driver *drv) { diff --git a/include/spi-mem.h b/include/spi-mem.h index 1dd556b6cf..d0a247cffc 100644 --- a/include/spi-mem.h +++ b/include/spi-mem.h @@ -123,6 +123,49 @@ struct spi_mem_op { .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 + * + * These information are 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__ /** * struct spi_mem - describes a SPI memory device @@ -171,10 +214,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); @@ -182,6 +247,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__ @@ -243,6 +314,34 @@ bool spi_mem_supports_op(struct spi_slave *slave, const struct spi_mem_op *op);
int spi_mem_exec_op(struct spi_slave *slave, const struct spi_mem_op *op);
+#if CONFIG_IS_ENABLED(SPI_DIRMAP) +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); +#else +static inline void spi_mem_dirmap_destroy(struct spi_mem_dirmap_desc *desc) +{ +} + +static inline ssize_t spi_mem_dirmap_read(struct spi_mem_dirmap_desc *desc, + u64 offs, size_t len, void *buf) +{ + return -ENOTSUPP; +} + +static inline ssize_t spi_mem_dirmap_write(struct spi_mem_dirmap_desc *desc, + u64 offs, size_t len, + const void *buf) +{ + return -ENOTSUPP; +} +#endif /* CONFIG_SPI_DIRMAP */ + #ifndef __UBOOT__ int spi_mem_driver_register_with_owner(struct spi_mem_driver *drv, struct module *owner);

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").
Signed-off-by: Sean Anderson seanga2@gmail.com ---
drivers/mtd/spi/sf_probe.c | 79 ++++++++++++++++++++++++++++++++++ drivers/mtd/spi/spi-nor-core.c | 45 +++++++++++++------ include/linux/mtd/spi-nor.h | 6 +++ 3 files changed, 116 insertions(+), 14 deletions(-)
diff --git a/drivers/mtd/spi/sf_probe.c b/drivers/mtd/spi/sf_probe.c index 6c87434867..796a2fa6bc 100644 --- a/drivers/mtd/spi/sf_probe.c +++ b/drivers/mtd/spi/sf_probe.c @@ -14,9 +14,72 @@ #include <malloc.h> #include <spi.h> #include <spi_flash.h> +#include <spi-mem.h>
#include "sf_internal.h"
+#if CONFIG_IS_ENABLED(SPI_DIRMAP) +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, 1), + SPI_MEM_OP_ADDR(nor->addr_width, 0, 1), + SPI_MEM_OP_DUMMY(nor->read_dummy, 1), + SPI_MEM_OP_DATA_IN(0, NULL, 1)), + .offset = 0, + .length = nor->mtd.size, + }; + struct spi_mem_op *op = &info.op_tmpl; + + /* get transfer protocols. */ + op->cmd.buswidth = spi_nor_get_protocol_inst_nbits(nor->read_proto); + op->addr.buswidth = spi_nor_get_protocol_addr_nbits(nor->read_proto); + op->dummy.buswidth = op->addr.buswidth; + 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; + + nor->dirmap.rdesc = spi_mem_dirmap_create(nor->spi, &info); + return PTR_ERR_OR_ZERO(nor->dirmap.rdesc); +} + +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, 1), + SPI_MEM_OP_ADDR(nor->addr_width, 0, 1), + SPI_MEM_OP_NO_DUMMY, + SPI_MEM_OP_DATA_OUT(0, NULL, 1)), + .offset = 0, + .length = nor->mtd.size, + }; + struct spi_mem_op *op = &info.op_tmpl; + + /* get transfer protocols. */ + op->cmd.buswidth = spi_nor_get_protocol_inst_nbits(nor->write_proto); + op->addr.buswidth = spi_nor_get_protocol_addr_nbits(nor->write_proto); + op->dummy.buswidth = op->addr.buswidth; + 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); + return PTR_ERR_OR_ZERO(nor->dirmap.wdesc); +} +#else +static int spi_nor_create_read_dirmap(struct spi_nor *nor) +{ + return 0; +} + +static int spi_nor_create_write_dirmap(struct spi_nor *nor) +{ + return 0; +} +#endif /* CONFIG_SPI_DIRMAP */ + /** * spi_flash_probe_slave() - Probe for a SPI flash device on a bus * @@ -45,6 +108,14 @@ static int spi_flash_probe_slave(struct spi_flash *flash) if (ret) goto err_read_id;
+ 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 +154,9 @@ struct spi_flash *spi_flash_probe(unsigned int busnum, unsigned int cs,
void spi_flash_free(struct spi_flash *flash) { + 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();
@@ -143,6 +217,11 @@ int spi_flash_std_probe(struct udevice *dev)
static int spi_flash_std_remove(struct udevice *dev) { + struct spi_flash *flash = dev_get_uclass_priv(dev); + + 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();
diff --git a/drivers/mtd/spi/spi-nor-core.c b/drivers/mtd/spi/spi-nor-core.c index e16b0e1462..32c712a9f1 100644 --- a/drivers/mtd/spi/spi-nor-core.c +++ b/drivers/mtd/spi/spi-nor-core.c @@ -96,13 +96,23 @@ 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) { + 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; @@ -120,6 +130,7 @@ static ssize_t spi_nor_write_data(struct spi_nor *nor, loff_t to, size_t len, SPI_MEM_OP_ADDR(nor->addr_width, to, 1), SPI_MEM_OP_NO_DUMMY, SPI_MEM_OP_DATA_OUT(len, buf, 1)); + ssize_t nbytes; int ret;
/* get transfer protocols. */ @@ -130,16 +141,22 @@ static ssize_t spi_nor_write_data(struct spi_nor *nor, loff_t to, size_t len, if (nor->program_opcode == SPINOR_OP_AAI_WP && nor->sst_write_second) op.addr.nbytes = 0;
- 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) { + 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; + nbytes = op.data.nbytes; + }
- return op.data.nbytes; + return nbytes; }
/* diff --git a/include/linux/mtd/spi-nor.h b/include/linux/mtd/spi-nor.h index 39ef872ebf..ffa4dd3e7d 100644 --- a/include/linux/mtd/spi-nor.h +++ b/include/linux/mtd/spi-nor.h @@ -304,6 +304,7 @@ struct spi_flash { * @flash_is_locked: [FLASH-SPECIFIC] check if a region of the SPI NOR is * completely locked * @quad_enable: [FLASH-SPECIFIC] enables SPI NOR quad mode + * @dirmap: pointers to struct spi_mem_dirmap_desc for reads/writes. * @priv: the private data */ struct spi_nor { @@ -346,6 +347,11 @@ struct spi_nor { int (*flash_is_locked)(struct spi_nor *nor, loff_t ofs, uint64_t len); int (*quad_enable)(struct spi_nor *nor);
+ struct { + struct spi_mem_dirmap_desc *rdesc; + struct spi_mem_dirmap_desc *wdesc; + } dirmap; + void *priv; /* Compatibility for spi_flash, remove once sf layer is merged with mtd */ const char *name;

The documentation for dev_read_u32_array says the return value is an errno, but fdtdec_get_int_array returns FDT_ERRs. Convert the return values so callers can handle errors properly.
Signed-off-by: Sean Anderson seanga2@gmail.com ---
drivers/core/ofnode.c | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-)
diff --git a/drivers/core/ofnode.c b/drivers/core/ofnode.c index 7a5f4c0a73..c071b968d5 100644 --- a/drivers/core/ofnode.c +++ b/drivers/core/ofnode.c @@ -220,9 +220,18 @@ int ofnode_read_u32_array(ofnode node, const char *propname, return of_read_u32_array(ofnode_to_np(node), propname, out_values, sz); } else { - return fdtdec_get_int_array(gd->fdt_blob, - ofnode_to_offset(node), propname, - out_values, sz); + int err = fdtdec_get_int_array(gd->fdt_blob, + ofnode_to_offset(node), propname, + out_values, sz); + + switch (err) { + case FDT_ERR_NOTFOUND: + return -EINVAL; + case FDT_ERR_BADLAYOUT: + return -EOVERFLOW; + default: + return 0; + } } }

On Thu, 4 Feb 2021 at 21:39, Sean Anderson seanga2@gmail.com wrote:
The documentation for dev_read_u32_array says the return value is an errno, but fdtdec_get_int_array returns FDT_ERRs. Convert the return values so callers can handle errors properly.
Signed-off-by: Sean Anderson seanga2@gmail.com
drivers/core/ofnode.c | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-)
Reviewed-by: Simon Glass sjg@chromium.org
This could really use a driver model test so that both options are automatically checked.

This prevents multiple-definition errors.
Fixes: 0ad40b2463 ("drivers: Add a new framework for multiplexer devices")
Signed-off-by: Sean Anderson seanga2@gmail.com ---
include/mux.h | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-)
diff --git a/include/mux.h b/include/mux.h index 23844f480a..91a89bbb9a 100644 --- a/include/mux.h +++ b/include/mux.h @@ -117,40 +117,41 @@ struct mux_control *devm_mux_control_get(struct udevice *dev, int dm_mux_init(void);
#else -unsigned int mux_control_states(struct mux_control *mux) +static inline unsigned int mux_control_states(struct mux_control *mux) { return -ENOSYS; }
-int __must_check mux_control_select(struct mux_control *mux, - unsigned int state) +static inline int __must_check mux_control_select(struct mux_control *mux, + unsigned int state) { return -ENOSYS; }
#define mux_control_try_select(mux) mux_control_select(mux)
-int mux_control_deselect(struct mux_control *mux) +static inline int mux_control_deselect(struct mux_control *mux) { return -ENOSYS; }
-struct mux_control *mux_control_get(struct udevice *dev, const char *mux_name) +static inline struct mux_control *mux_control_get(struct udevice *dev, + const char *mux_name) { return NULL; }
-void mux_control_put(struct mux_control *mux) +static inline void mux_control_put(struct mux_control *mux) { }
-struct mux_control *devm_mux_control_get(struct udevice *dev, - const char *mux_name) +static inline struct mux_control *devm_mux_control_get(struct udevice *dev, + const char *mux_name) { return NULL; }
-int dm_mux_init(void) +static inline int dm_mux_init(void) { return -ENOSYS; }

On 04/02/21 11:39PM, Sean Anderson wrote:
This prevents multiple-definition errors.
Fixes: 0ad40b2463 ("drivers: Add a new framework for multiplexer devices")
Signed-off-by: Sean Anderson seanga2@gmail.com
Reviewed-by: Pratyush Yadav p.yadav@ti.com

This allows code using mux_get_by_index to be agnostic about CONFIG_MUX.
Signed-off-by: Sean Anderson seanga2@gmail.com ---
include/mux.h | 6 ++++++ 1 file changed, 6 insertions(+)
diff --git a/include/mux.h b/include/mux.h index 91a89bbb9a..882a00068b 100644 --- a/include/mux.h +++ b/include/mux.h @@ -135,6 +135,12 @@ static inline int mux_control_deselect(struct mux_control *mux) return -ENOSYS; }
+static inline int mux_get_by_index(struct udevice *dev, int index, + struct mux_control **mux) +{ + return -ENOSYS; +} + static inline struct mux_control *mux_control_get(struct udevice *dev, const char *mux_name) {

On 04/02/21 11:39PM, Sean Anderson wrote:
This allows code using mux_get_by_index to be agnostic about CONFIG_MUX.
Signed-off-by: Sean Anderson seanga2@gmail.com
include/mux.h | 6 ++++++ 1 file changed, 6 insertions(+)
diff --git a/include/mux.h b/include/mux.h index 91a89bbb9a..882a00068b 100644 --- a/include/mux.h +++ b/include/mux.h @@ -135,6 +135,12 @@ static inline int mux_control_deselect(struct mux_control *mux) return -ENOSYS; }
+static inline int mux_get_by_index(struct udevice *dev, int index,
struct mux_control **mux)
+{
- return -ENOSYS;
+}
static inline struct mux_control *mux_control_get(struct udevice *dev, const char *mux_name) {
Hmm... mux_control_get() on the other side of this ifdef has a completely different signature. Can you please add that fix in your series as well? For this patch,
Reviewed-by: Pratyush Yadav p.yadav@ti.com

idle-states is optional, so don't complain if it doesn't exist.
Signed-off-by: Sean Anderson seanga2@gmail.com ---
drivers/mux/mmio.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/drivers/mux/mmio.c b/drivers/mux/mmio.c index 00e0282dcc..82b1cc6aab 100644 --- a/drivers/mux/mmio.c +++ b/drivers/mux/mmio.c @@ -87,8 +87,11 @@ static int mmio_mux_probe(struct udevice *dev)
ret = dev_read_u32_array(dev, "idle-states", idle_states, num_fields); if (ret < 0) { - log_err("idle-states"); devm_kfree(dev, idle_states); + /* dev_read_u32_array returns -EINVAL on missing property */ + if (ret != -EINVAL) + return log_msg_ret("idle-states", -EINVAL); + idle_states = NULL; }

On 04/02/21 11:39PM, Sean Anderson wrote:
idle-states is optional, so don't complain if it doesn't exist.
This commit doesn't just silence the complaint. It also changes the behavior of the function if the error code is ENODATA or EOVERFLOW. Make sure the commit message reflects that.
Signed-off-by: Sean Anderson seanga2@gmail.com
drivers/mux/mmio.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/drivers/mux/mmio.c b/drivers/mux/mmio.c index 00e0282dcc..82b1cc6aab 100644 --- a/drivers/mux/mmio.c +++ b/drivers/mux/mmio.c @@ -87,8 +87,11 @@ static int mmio_mux_probe(struct udevice *dev)
ret = dev_read_u32_array(dev, "idle-states", idle_states, num_fields); if (ret < 0) {
devm_kfree(dev, idle_states);log_err("idle-states");
/* dev_read_u32_array returns -EINVAL on missing property */
if (ret != -EINVAL)
return log_msg_ret("idle-states", -EINVAL);
Return ret here. I don't see any reason to return -EINVAL when the error is _not_ -EINVAL.
- idle_states = NULL; }
-- 2.29.2

On 2/5/21 6:06 AM, Pratyush Yadav wrote:
On 04/02/21 11:39PM, Sean Anderson wrote:
idle-states is optional, so don't complain if it doesn't exist.
This commit doesn't just silence the complaint. It also changes the behavior of the function if the error code is ENODATA or EOVERFLOW. Make sure the commit message reflects that.
Sure.
Signed-off-by: Sean Anderson seanga2@gmail.com
drivers/mux/mmio.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/drivers/mux/mmio.c b/drivers/mux/mmio.c index 00e0282dcc..82b1cc6aab 100644 --- a/drivers/mux/mmio.c +++ b/drivers/mux/mmio.c @@ -87,8 +87,11 @@ static int mmio_mux_probe(struct udevice *dev)
ret = dev_read_u32_array(dev, "idle-states", idle_states, num_fields); if (ret < 0) {
devm_kfree(dev, idle_states);log_err("idle-states");
/* dev_read_u32_array returns -EINVAL on missing property */
if (ret != -EINVAL)
return log_msg_ret("idle-states", -EINVAL);
Return ret here. I don't see any reason to return -EINVAL when the error is _not_ -EINVAL.
EINVAL is the traditional return value for when a binding is malformed. Though I don't mind returning ENODATA or EOVERFLOW here. Will be updated.
--Sean
- idle_states = NULL; }
-- 2.29.2

These registers and fields are necessary for XIP with SSIC_CONCURRENT_XIP_EN.
Signed-off-by: Sean Anderson seanga2@gmail.com ---
drivers/spi/designware_spi.c | 44 +++++++++++++++++++++++++++++++----- 1 file changed, 38 insertions(+), 6 deletions(-)
diff --git a/drivers/spi/designware_spi.c b/drivers/spi/designware_spi.c index 44fb679fdb..d7510646e7 100644 --- a/drivers/spi/designware_spi.c +++ b/drivers/spi/designware_spi.c @@ -58,6 +58,13 @@ #define DW_SPI_DR 0x60 #define DW_SPI_RX_SAMPLE_DLY 0xf0 #define DW_SPI_SPI_CTRL0 0xf4 +#define DW_SPI_XIP_MODE_BITS 0xfc +#define DW_SPI_XIP_INCR_INST 0x100 +#define DW_SPI_XIP_WRAP_INST 0x104 +#define DW_SPI_XIP_CTRL 0x108 +#define DW_SPI_XIP_SER 0x10c +#define DW_SPI_XRXOICR 0x110 +#define DW_SPI_XIP_XNT_TIME_OUT 0x114
/* Bit fields in CTRLR0 */ /* @@ -147,9 +154,9 @@ * FRF_BYTE */ #define SPI_CTRLR0_TRANS_TYPE_MASK GENMASK(1, 0) -#define SPI_CTRLR0_TRANS_TYPE_1_1_X 0x0 -#define SPI_CTRLR0_TRANS_TYPE_1_X_X 0x1 -#define SPI_CTRLR0_TRANS_TYPE_X_X_X 0x2 +#define TRANS_TYPE_1_1_X 0x0 +#define TRANS_TYPE_1_X_X 0x1 +#define TRANS_TYPE_X_X_X 0x2 /* Address length in 4-bit units */ #define SPI_CTRLR0_ADDR_L_MASK GENMASK(5, 2) /* Enable mode bits after address in XIP mode */ @@ -165,6 +172,31 @@ /* Stretch the clock if the FIFO over/underflows */ #define SPI_CTRLR0_CLK_STRETCH_EN BIT(30)
+/* Bit fields in XIP_CTRL */ + +/* XIP SPI frame format */ +#define XIP_CTRL_FRF GENMASK(1, 0) +/* Same as SPI_CTRLR0_TRANS_TYPE */ +#define XIP_CTRL_TRANS_TYPE_MASK GENMASK(3, 2) +/* Address length in 4-bit increments */ +#define XIP_CTRL_ADDR_L_MASK GENMASK(7, 4) +/* Same as SPI_CTRLR0_INST_L */ +#define XIP_CTRL_INST_L_MASK GENMASK(10, 9) +/* Enable mode bits */ +#define XIP_CTRL_MD_BITS_EN BIT(12) +/* Wait cycles */ +#define XIP_CTRL_WAIT_CYCLES_MASK GENMASK(17, 13) +/* Use fixed-size DFS in XIP mode, ignoring AHB request width */ +#define XIP_CTRL_DFS_HC BIT(18) +/* Enable instruction phase */ +#define XIP_CTRL_INST_EN BIT(22) +/* Continuous transfer: don't deselect slave after one transfer */ +#define XIP_CTRL_CONT_XFER_EN BIT(23) +/* Mode bits length; length = 1 << (MBL + 1) */ +#define XIP_CTRL_XIP_MBL_MASK GENMASK(27, 26) +/* Prefetch contiguous data frames */ +#define XIP_CTRL_PREFETCH_EN BIT(29) + #define RX_TIMEOUT 1000 /* timeout in ms */
struct dw_spi_plat { @@ -243,11 +275,11 @@ static u32 dw_spi_update_spi_cr0(const struct spi_mem_op *op)
/* This assumes support_op has filtered invalid types */ if (op->addr.buswidth == 1) - trans_type = SPI_CTRLR0_TRANS_TYPE_1_1_X; + trans_type = TRANS_TYPE_1_1_X; else if (op->cmd.buswidth == 1) - trans_type = SPI_CTRLR0_TRANS_TYPE_1_X_X; + trans_type = TRANS_TYPE_1_X_X; else - trans_type = SPI_CTRLR0_TRANS_TYPE_X_X_X; + trans_type = TRANS_TYPE_X_X_X;
if (op->dummy.buswidth) wait_cycles = op->dummy.nbytes * 8 / op->dummy.buswidth;

Both DW SSI APB and DWC SSI devices have an optional XIP mode. When the xip_en signal is asserted, reads (and writes if SSIC_XIP_WRITE_REG_EN is set) are mapped to SPI transfers.
If SSIC_CONCURRENT_XIP_EN is disabled, then XIP transfers are controlled using SPI_CTRLR0. However, if SSIC_CONCURRENT_XIP_EN is enabled, then XIP transfers can occur concurrently (first-come-first-serve) with non-XIP transfers. To facilitate this, a separate XIP_CTRL register is used for configuration which would otherwise by done using CTRLR0 and SPI_CTRLR0.
Signed-off-by: Sean Anderson seanga2@gmail.com ---
drivers/spi/designware_spi.c | 29 ++++++++++++++++++++++++++--- 1 file changed, 26 insertions(+), 3 deletions(-)
diff --git a/drivers/spi/designware_spi.c b/drivers/spi/designware_spi.c index d7510646e7..c41c5b4982 100644 --- a/drivers/spi/designware_spi.c +++ b/drivers/spi/designware_spi.c @@ -202,6 +202,7 @@ struct dw_spi_plat { s32 frequency; /* Default clock frequency, -1 for none */ void __iomem *regs; + fdt_size_t regs_size; };
struct dw_spi_priv { @@ -210,12 +211,15 @@ struct dw_spi_priv { struct gpio_desc cs_gpio; /* External chip-select gpio */
void __iomem *regs; + fdt_size_t regs_size; /* DW SPI capabilities */ #define DW_SPI_CAP_CS_OVERRIDE BIT(0) /* Unimplemented */ #define DW_SPI_CAP_KEEMBAY_MST BIT(1) /* Unimplemented */ #define DW_SPI_CAP_DWC_SSI BIT(2) #define DW_SPI_CAP_DFS32 BIT(3) #define DW_SPI_CAP_ENHANCED BIT(4) +#define DW_SPI_CAP_XIP BIT(5) +#define DW_SPI_CAP_XIP_CONCURRENT BIT(6) unsigned long caps; unsigned long bus_clk_rate; unsigned int freq; /* Default frequency */ @@ -322,11 +326,13 @@ static int request_gpio_cs(struct udevice *bus)
static int dw_spi_of_to_plat(struct udevice *bus) { + fdt_addr_t regs; struct dw_spi_plat *plat = dev_get_plat(bus);
- plat->regs = dev_read_addr_ptr(bus); - if (!plat->regs) + regs = dev_read_addr_size_index(bus, 0, &plat->regs_size); + if (regs == FDT_ADDR_T_NONE) return -EINVAL; + plat->regs = (void *)regs;
/* Use 500KHz as a suitable default */ plat->frequency = dev_read_u32_default(bus, "spi-max-frequency", @@ -375,6 +381,19 @@ static void spi_hw_init(struct udevice *bus, struct dw_spi_priv *priv) priv->caps |= DW_SPI_CAP_ENHANCED; }
+ /* + * DWC_SPI always has this register with SSIC_XIP_EN. There is no way + * to detect XIP for DW APB SSI + */ + dw_write(priv, DW_SPI_XIP_INCR_INST, 0xffffffff); + if (dw_read(priv, DW_SPI_XIP_INCR_INST)) + priv->caps |= DW_SPI_CAP_XIP; + + /* Exists with SSIC_CONCURRENT_XIP_EN */ + dw_write(priv, DW_SPI_XIP_CTRL, 0xffffffff); + if (dw_read(priv, DW_SPI_XIP_CTRL)) + priv->caps |= DW_SPI_CAP_XIP_CONCURRENT; + dw_write(priv, DW_SPI_SSIENR, 1);
/* @@ -469,6 +488,7 @@ static int dw_spi_probe(struct udevice *bus) u32 version;
priv->regs = plat->regs; + priv->regs_size = plat->regs_size; priv->freq = plat->frequency;
ret = dw_spi_get_clk(bus, &priv->bus_clk_rate); @@ -1022,7 +1042,10 @@ static const struct udevice_id dw_spi_ids[] = { */ { .compatible = "altr,socfpga-spi" }, { .compatible = "altr,socfpga-arria10-spi" }, - { .compatible = "canaan,kendryte-k210-spi" }, + { + .compatible = "canaan,kendryte-k210-spi", + .data = DW_SPI_CAP_XIP, + }, { .compatible = "canaan,kendryte-k210-ssi", .data = DW_SPI_CAP_DWC_SSI,

To enter XIP mode, the xip_en signal must be asserted. The exact method of setting xip_en is integration-specific, but on the K210 (and Baikal-T1) it is set by a bit in a system configuration register. To handle this, use a mux to select the state of xip_en before every access to control registers.
Signed-off-by: Sean Anderson seanga2@gmail.com ---
drivers/spi/designware_spi.c | 133 ++++++++++++++++++++++++++++++++++- 1 file changed, 130 insertions(+), 3 deletions(-)
diff --git a/drivers/spi/designware_spi.c b/drivers/spi/designware_spi.c index c41c5b4982..6f74a471e3 100644 --- a/drivers/spi/designware_spi.c +++ b/drivers/spi/designware_spi.c @@ -19,6 +19,7 @@ #include <fdtdec.h> #include <log.h> #include <malloc.h> +#include <mux.h> #include <reset.h> #include <spi.h> #include <spi-mem.h> @@ -209,6 +210,7 @@ struct dw_spi_priv { struct clk clk; struct reset_ctl_bulk resets; struct gpio_desc cs_gpio; /* External chip-select gpio */ + struct mux_control *mux; /* XIP mode mux */
void __iomem *regs; fdt_size_t regs_size; @@ -225,6 +227,7 @@ struct dw_spi_priv { unsigned int freq; /* Default frequency */ unsigned int mode;
+ u32 mux_xip_state; /* Mux state to enable XIP mode */ u32 fifo_len; /* depth of the FIFO buffer */
int bits_per_word; @@ -346,11 +349,79 @@ static int dw_spi_of_to_plat(struct udevice *bus) return request_gpio_cs(bus); }
-/* Restart the controller, disable all interrupts, clean rx fifo */ -static void spi_hw_init(struct udevice *bus, struct dw_spi_priv *priv) +static int dw_spi_mux(struct udevice *dev, bool xip) { + struct dw_spi_priv *priv = dev_get_priv(dev); + + if (!priv->mux) + return 0; + + if (xip && priv->mux_xip_state) + return mux_control_select(priv->mux, priv->mux_xip_state); + else + return mux_control_select(priv->mux, 0); +} + +/* + * dw_spi_mux_regs() - Select the control registers using the XIP mux + * @dev: The device to mux + * + * This selects the control registers using the XIP mux, driving the xip_en + * signal low. This function must be called before any accesses to control + * registers. + * + * Return: 0 on success or negative error value + */ +static inline int dw_spi_mux_regs(struct udevice *dev) +{ + return dw_spi_mux(dev, false); +} + +/* + * dw_spi_mux_xip() - Select the control registers using the XIP mux + * @dev: The device to mux + * + * This selects XIP mode using the XIP mux, driving the xip_en signal high. This + * function must be called before any XIP accesses. + * + * Return: 0 on success or negative error value + */ +static inline int dw_spi_mux_xip(struct udevice *dev) +{ + return dw_spi_mux(dev, true); +} + +/* + * dw_spi_mux_deselect() + * @dev: The device to mux + * + * This deselects the XIP mux, returning it to its default state. This must be + * called after control register or XIP accesses are finished, before other + * calls to @dw_spi_mux_regs or @dw_spi_mux_xip. + */ +static void dw_spi_mux_deselect(struct udevice *dev) +{ + int err; + struct dw_spi_priv *priv = dev_get_priv(dev); + + if (!priv->mux) + return; + + err = mux_control_deselect(priv->mux); + if (err) + dev_warn(dev, "could not deselect mux (err %d)\n", err); +} + +/* Restart the controller, disable all interrupts, clean rx fifo */ +static int spi_hw_init(struct udevice *bus, struct dw_spi_priv *priv) +{ + int ret; u32 cr0;
+ ret = dw_spi_mux_regs(bus); + if (ret) + return ret; + dw_write(priv, DW_SPI_SSIENR, 0); dw_write(priv, DW_SPI_IMR, 0);
@@ -415,6 +486,9 @@ static void spi_hw_init(struct udevice *bus, struct dw_spi_priv *priv)
/* Set receive fifo interrupt level register for clock stretching */ dw_write(priv, DW_SPI_RXFTLR, priv->fifo_len - 1); + + dw_spi_mux_deselect(bus); + return 0; }
/* @@ -480,6 +554,33 @@ static int dw_spi_reset(struct udevice *bus) return 0; }
+int dw_spi_get_mux(struct udevice *bus) +{ + int ret; + struct dw_spi_priv *priv = dev_get_priv(bus); + + ret = mux_get_by_index(bus, 0, &priv->mux); + if (ret) { + /* + * Return 0 if error due to !CONFIG_MUX or mux + * DT property is not present. + */ + if (ret == -ENOENT || ret == -ENOTSUPP) + return 0; + + dev_warn(bus, "Couldn't get xip mux (error %d)\n", ret); + return ret; + } + + ret = dev_read_u32(bus, "mux-xip-state", &priv->mux_xip_state); + if (ret || priv->mux_xip_state > 1) { + dev_warn(bus, "Invalid/missing mux-xip-state property\n"); + return -EINVAL; + } + + return 0; +} + static int dw_spi_probe(struct udevice *bus) { struct dw_spi_plat *plat = dev_get_plat(bus); @@ -499,6 +600,10 @@ static int dw_spi_probe(struct udevice *bus) if (ret) return ret;
+ ret = dw_spi_get_mux(bus); + if (ret) + return ret; + /* Currently only bits_per_word == 8 supported */ priv->bits_per_word = 8;
@@ -506,7 +611,12 @@ static int dw_spi_probe(struct udevice *bus)
/* Basic HW init */ priv->caps = dev_get_driver_data(bus); - spi_hw_init(bus, priv); + ret = spi_hw_init(bus, priv); + if (ret) + return ret; + + if (!priv->mux) + priv->caps &= DW_SPI_CAP_XIP;
version = dw_read(priv, DW_SPI_VERSION); dev_dbg(bus, @@ -713,6 +823,10 @@ static int dw_spi_xfer(struct udevice *dev, unsigned int bitlen, return -1; }
+ ret = dw_spi_mux_regs(bus); + if (ret) + return ret; + frames = bitlen / priv->bits_per_word;
/* Start the transaction if necessary. */ @@ -779,6 +893,8 @@ static int dw_spi_xfer(struct udevice *dev, unsigned int bitlen, if (flags & SPI_XFER_END) external_cs_manage(dev, true);
+ dw_spi_mux_deselect(bus); + return ret; }
@@ -831,6 +947,10 @@ static int dw_spi_exec_op(struct spi_slave *slave, const struct spi_mem_op *op) else priv->tmode = CTRLR0_TMOD_TO;
+ ret = dw_spi_mux_regs(bus); + if (ret) + return ret; + cr0 = dw_spi_update_cr0(priv); spi_cr0 = dw_spi_update_spi_cr0(op); dev_dbg(bus, "cr0=%08x spi_cr0=%08x buf=%p len=%u [bytes]\n", cr0, @@ -891,6 +1011,7 @@ static int dw_spi_exec_op(struct spi_slave *slave, const struct spi_mem_op *op) } dw_write(priv, DW_SPI_SER, 0); external_cs_manage(slave->dev, true); + dw_spi_mux_deselect(bus);
dev_dbg(bus, "%u bytes xfered\n", op->data.nbytes); return ret; @@ -943,10 +1064,15 @@ static const struct spi_controller_mem_ops dw_spi_mem_ops = {
static int dw_spi_set_speed(struct udevice *bus, uint speed) { + int ret; struct dw_spi_plat *plat = dev_get_plat(bus); struct dw_spi_priv *priv = dev_get_priv(bus); u16 clk_div;
+ ret = dw_spi_mux_regs(bus); + if (ret) + return ret; + if (speed > plat->frequency) speed = plat->frequency;
@@ -960,6 +1086,7 @@ static int dw_spi_set_speed(struct udevice *bus, uint speed)
/* Enable controller after writing control registers */ dw_write(priv, DW_SPI_SSIENR, 1); + mux_control_deselect(priv->mux);
priv->freq = speed; dev_dbg(bus, "speed=%d clk_div=%d\n", priv->freq, clk_div);

This adds support for XIP mode. It is not actually any faster than QPI (yet), but it serves as a good starting point for using XIP mode for other purposes (such as actual eXecuting In Place).
Signed-off-by: Sean Anderson seanga2@gmail.com ---
drivers/spi/designware_spi.c | 131 +++++++++++++++++++++++++++++++++++ 1 file changed, 131 insertions(+)
diff --git a/drivers/spi/designware_spi.c b/drivers/spi/designware_spi.c index 6f74a471e3..cb7a28c3bf 100644 --- a/drivers/spi/designware_spi.c +++ b/drivers/spi/designware_spi.c @@ -1056,10 +1056,141 @@ static int dw_spi_adjust_op_size(struct spi_slave *slave, struct spi_mem_op *op) return 0; }
+#if CONFIG_IS_ENABLED(SPI_DIRMAP) +static int dw_spi_dirmap_create(struct spi_mem_dirmap_desc *desc) +{ + struct dw_spi_priv *priv = dev_get_priv(desc->slave->dev->parent); + + /* + * Currently only DWC XIP is supported. DW APB SSI XIP exists, but + * cannot send an instruction before the address, so it is left for when + * U-Boot supports 0-X-X instructions. In addition, we only support + * concurrent XIP (since I have no non-condcurrent XIP hardware to test + * with) + */ + if (!(priv->caps & (DW_SPI_CAP_XIP)) || + !(priv->caps & (DW_SPI_CAP_DWC_SSI)) || + !(priv->caps & (DW_SPI_CAP_XIP_CONCURRENT))) + return -ENOTSUPP; + + if (!spi_mem_supports_op(desc->slave, &desc->info.op_tmpl)) + return -ENOTSUPP; + + /* + * Make sure the requested region doesn't go out of the physically + * mapped flash memory bounds and the operation is read-only. + */ + if (desc->info.offset + desc->info.length > priv->regs_size || + desc->info.op_tmpl.data.dir != SPI_MEM_DATA_IN) + return -ENOTSUPP; + + /* XIP only supports enhanced SPI modes */ + if (desc->info.op_tmpl.data.buswidth == 1) + return -ENOTSUPP; + + return 0; +} + +static u32 dw_spi_update_xip_cr(const struct spi_mem_op *op, uint frf) +{ + uint trans_type, wait_cycles; + + /* This assumes support_op has filtered invalid types */ + if (op->addr.buswidth == 1) + trans_type = TRANS_TYPE_1_1_X; + else if (op->cmd.buswidth == 1) + trans_type = TRANS_TYPE_1_X_X; + else + trans_type = TRANS_TYPE_X_X_X; + + if (op->dummy.buswidth) + wait_cycles = op->dummy.nbytes * 8 / op->dummy.buswidth; + else + wait_cycles = 0; + + return FIELD_PREP(XIP_CTRL_FRF, frf) + | FIELD_PREP(XIP_CTRL_TRANS_TYPE_MASK, trans_type) + | FIELD_PREP(XIP_CTRL_ADDR_L_MASK, op->addr.nbytes * 2) + | FIELD_PREP(XIP_CTRL_INST_L_MASK, INST_L_8) + | FIELD_PREP(XIP_CTRL_WAIT_CYCLES_MASK, wait_cycles) + //| XIP_CTRL_DFS_HC + | XIP_CTRL_INST_EN + | XIP_CTRL_CONT_XFER_EN + | XIP_CTRL_PREFETCH_EN; +} + +static ssize_t dw_spi_dirmap_read(struct spi_mem_dirmap_desc *desc, u64 offs, + size_t len, void *buf) +{ + int ret; + size_t count = len; + struct spi_slave *slave = desc->slave; + struct udevice *bus = slave->dev->parent; + struct dw_spi_priv *priv = dev_get_priv(bus); + struct spi_mem_op *op = &desc->info.op_tmpl; + u8 *from, *to; + + switch (op->data.buswidth) { + case 2: + priv->spi_frf = CTRLR0_SPI_FRF_DUAL; + break; + case 4: + priv->spi_frf = CTRLR0_SPI_FRF_QUAD; + break; + case 8: + priv->spi_frf = CTRLR0_SPI_FRF_OCTAL; + break; + default: + return -EINVAL; + } + + ret = dw_spi_mux_ctrl(bus); + if (ret) + return ret; + + dw_write(priv, DW_SPI_SSIENR, 0); + //dw_write(priv, DW_SPI_CTRLR0, dw_spi_update_cr0(priv)); + dw_write(priv, DW_SPI_XIP_CTRL, dw_spi_update_xip_cr(op, priv->spi_frf)); + dw_write(priv, DW_SPI_XIP_INCR_INST, op->cmd.opcode); + /* + * FIXME: U-Boot doesn't currently support wrap instructions, but we + * can't control what the AHB master does. Just write 0 to get something + * obviously bogus. + */ + dw_write(priv, DW_SPI_XIP_WRAP_INST, 0); + dw_write(priv, DW_SPI_XIP_SER, 1 << spi_chip_select(slave->dev)); + dw_write(priv, DW_SPI_SSIENR, 1); + + dw_spi_mux_deselect(bus); + + external_cs_manage(slave->dev, true); + + ret = dw_spi_mux_xip(bus); + if (ret) + return ret; + + //memcpy(buf, priv->regs + offs, len); + from = priv->regs + offs; + to = buf; + while (count--) + *to++ = *from++; + + dw_spi_mux_deselect(bus); + + external_cs_manage(slave->dev, false); + + return len; +} +#endif /* CONFIG_SPI_DIRMAP */ + static const struct spi_controller_mem_ops dw_spi_mem_ops = { .exec_op = dw_spi_exec_op, .supports_op = dw_spi_supports_op, .adjust_op_size = dw_spi_adjust_op_size, +#if CONFIG_IS_ENABLED(SPI_DIRMAP) + .dirmap_create = dw_spi_dirmap_create, + .dirmap_read = dw_spi_dirmap_read, +#endif };
static int dw_spi_set_speed(struct udevice *bus, uint speed)

By default the SPI3 bus clock is ~100MHz, 1/4th of the CPU clock. This causes decreased performance when accessing this peripheral.
Signed-off-by: Sean Anderson seanga2@gmail.com ---
arch/riscv/dts/k210.dtsi | 2 ++ 1 file changed, 2 insertions(+)
diff --git a/arch/riscv/dts/k210.dtsi b/arch/riscv/dts/k210.dtsi index 81b04018c6..dac7c62289 100644 --- a/arch/riscv/dts/k210.dtsi +++ b/arch/riscv/dts/k210.dtsi @@ -600,6 +600,8 @@ interrupts = <4>; clocks = <&sysclk K210_CLK_SPI3>; clock-names = "ssi_clk"; + assigned-clocks = <&sysclk K210_CLK_SPI3>; + assigned-clock-rates = <390000000>; resets = <&sysrst K210_RST_SPI3>; /* Could possibly go up to 200 MHz */ spi-max-frequency = <100000000>;

On the K210, a register in sysctl controls whether accessing an SPI device has the xip_en signal set. Add the appropriate bindings.
Signed-off-by: Sean Anderson seanga2@gmail.com ---
arch/riscv/dts/k210.dtsi | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-)
diff --git a/arch/riscv/dts/k210.dtsi b/arch/riscv/dts/k210.dtsi index dac7c62289..aa1c53f09f 100644 --- a/arch/riscv/dts/k210.dtsi +++ b/arch/riscv/dts/k210.dtsi @@ -292,6 +292,8 @@ interrupts = <2>; clocks = <&sysclk K210_CLK_SPI2>; resets = <&sysrst K210_RST_SPI2>; + mux-controls = <&xip_mux 2>; + mux-xip-state = <1>; spi-max-frequency = <25000000>; status = "disabled"; }; @@ -530,6 +532,15 @@ mask = <1>; value = <1>; }; + + xip_mux: mux-controller { + compatible = "mmio-mux"; + #mux-control-cells = <1>; + mux-reg-masks = <K210_SYSCTL_PERI (1 << 12)>, + <K210_SYSCTL_PERI (1 << 13)>, + <K210_SYSCTL_PERI (1 << 14)>, + <K210_SYSCTL_PERI (1 << 15)>; + }; };
aes0: aes@50450000 { @@ -563,11 +574,13 @@ compatible = "canaan,kendryte-k210-spi", "snps,dw-apb-ssi-4.01", "snps,dw-apb-ssi"; - reg = <0x52000000 0x100>; + reg = <0x52000000 0x1000000>; interrupts = <1>; clocks = <&sysclk K210_CLK_SPI0>; clock-names = "ssi_clk"; resets = <&sysrst K210_RST_SPI0>; + mux-controls = <&xip_mux 0>; + mux-xip-state = <1>; spi-max-frequency = <25000000>; num-cs = <4>; reg-io-width = <4>; @@ -580,11 +593,13 @@ compatible = "canaan,kendryte-k210-spi", "snps,dw-apb-ssi-4.01", "snps,dw-apb-ssi"; - reg = <0x53000000 0x100>; + reg = <0x53000000 0x1000000>; interrupts = <2>; clocks = <&sysclk K210_CLK_SPI1>; clock-names = "ssi_clk"; resets = <&sysrst K210_RST_SPI1>; + mux-controls = <&xip_mux 1>; + mux-xip-state = <1>; spi-max-frequency = <25000000>; num-cs = <4>; reg-io-width = <4>; @@ -596,13 +611,15 @@ #size-cells = <0>; compatible = "canaan,kendryte-k210-ssi", "snps,dwc-ssi-1.01a"; - reg = <0x54000000 0x200>; + reg = <0x54000000 0x1000000>; interrupts = <4>; clocks = <&sysclk K210_CLK_SPI3>; clock-names = "ssi_clk"; assigned-clocks = <&sysclk K210_CLK_SPI3>; assigned-clock-rates = <390000000>; resets = <&sysrst K210_RST_SPI3>; + mux-controls = <&xip_mux 3>; + mux-xip-state = <1>; /* Could possibly go up to 200 MHz */ spi-max-frequency = <100000000>; num-cs = <4>;

On 2/4/21 11:39 PM, Sean Anderson wrote:
This adds support for memory-mapped ("DIRMAP") reads (called XIP by the datasheet). In theory, these have better performance than regular reads. In practice, the CPU is already fast enough to max out the performance of this peripheral.
The real end-goal is to be able to boot a XIP kernel. This SoC has just barely enough SRAM to boot Linux, and using a XIP kernel would decrease the memory pressure to something tolerable. It would also probably absolutely *tank* the performance, since not only is SPI flash ~8x slower than regular memory, there is no cache to mitigate that latency. So it remains to be seen whether this is worth it at all.
This series is RFC because it currently reads all 1s on one board I have. On the other board I have, it reads and writes fine. I will try and investigate this, but I expect any changes to occur in "spi: dw: Add support for DIRMAP".
Forgot to mention, but this series depends on [1]. A git branch with this complete series is available at [2].
[1] https://patchwork.ozlabs.org/project/uboot/list/?series=228123 [2] https://github.com/Forty-Bot/u-boot/tree/maix_xip
Sean Anderson (13): linux err: Synchronize with Linux 5.10 spi-mem: Add dirmap API from Linux mtd: spi-nor: use spi-mem dirmap API core: ofnode: Fix inconsistent returns of *_read_u32_array mux: Inline mux functions when CONFIG_MUX is disabled mux: Define a stub for mux_get_by_index if CONFIG_MUX is disabled mux: mmio: Only complain about idle-states if it is malformed spi: dw: Define XIP registers spi: dw: Add XIP and XIP_CONCURRENT caps spi: dw: Use a mux to access registers spi: dw: Add support for DIRMAP riscv: k210: Increase SPI3 bus clock to CPU speed riscv: k210: Add bindings for SPI XIP
arch/riscv/dts/k210.dtsi | 25 ++- drivers/core/ofnode.c | 15 +- drivers/mtd/spi/sf_probe.c | 79 ++++++++ drivers/mtd/spi/spi-nor-core.c | 45 +++-- drivers/mux/mmio.c | 5 +- drivers/spi/Kconfig | 10 + drivers/spi/designware_spi.c | 337 +++++++++++++++++++++++++++++++-- drivers/spi/spi-mem.c | 270 ++++++++++++++++++++++++++ include/linux/err.h | 21 +- include/linux/mtd/spi-nor.h | 6 + include/mux.h | 25 ++- include/spi-mem.h | 99 ++++++++++ 12 files changed, 889 insertions(+), 48 deletions(-)
participants (3)
-
Pratyush Yadav
-
Sean Anderson
-
Simon Glass