From b82ab1c26962a952755d9b9c1d083436d88281a7 Mon Sep 17 00:00:00 2001 From: Piotr Bugalski Date: Mon, 5 Nov 2018 11:36:20 +0100 Subject: [PATCH 01/11] mtd: spi-nor: atmel-quaspi: Typo fix Just minor typo fix. Fixed in preparation of new driver. Signed-off: Piotr Bugalski Signed-off-by: Mark Brown --- drivers/mtd/spi-nor/atmel-quadspi.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/mtd/spi-nor/atmel-quadspi.c b/drivers/mtd/spi-nor/atmel-quadspi.c index 820048726b4f..1c5ba8feaa5e 100644 --- a/drivers/mtd/spi-nor/atmel-quadspi.c +++ b/drivers/mtd/spi-nor/atmel-quadspi.c @@ -67,7 +67,7 @@ #define QSPI_CR_LASTXFER BIT(24) /* Bitfields in QSPI_MR (Mode Register) */ -#define QSPI_MR_SSM BIT(0) +#define QSPI_MR_SMM BIT(0) #define QSPI_MR_LLB BIT(1) #define QSPI_MR_WDRBT BIT(2) #define QSPI_MR_SMRM BIT(3) @@ -563,7 +563,7 @@ static int atmel_qspi_init(struct atmel_qspi *aq) qspi_writel(aq, QSPI_CR, QSPI_CR_SWRST); /* Set the QSPI controller in Serial Memory Mode */ - mr = QSPI_MR_NBBITS(8) | QSPI_MR_SSM; + mr = QSPI_MR_NBBITS(8) | QSPI_MR_SMM; qspi_writel(aq, QSPI_MR, mr); src_rate = clk_get_rate(aq->clk); From d5433def31531bd07984f167f6ab0afef70b6a3e Mon Sep 17 00:00:00 2001 From: Piotr Bugalski Date: Mon, 5 Nov 2018 11:36:21 +0100 Subject: [PATCH 02/11] mtd: spi-nor: atmel-quadspi: Add spi-mem support to atmel-quadspi This patch adds new interface to existing driver. New code is not used yet, it will be enabled later. Changes are prepared in small steps to keep patches readable. Suggested-by: Boris Brezillon Signed-off-by: Piotr Bugalski Signed-off-by: Mark Brown --- drivers/mtd/spi-nor/atmel-quadspi.c | 211 ++++++++++++++++++++++++++++ 1 file changed, 211 insertions(+) diff --git a/drivers/mtd/spi-nor/atmel-quadspi.c b/drivers/mtd/spi-nor/atmel-quadspi.c index 1c5ba8feaa5e..896478a290ec 100644 --- a/drivers/mtd/spi-nor/atmel-quadspi.c +++ b/drivers/mtd/spi-nor/atmel-quadspi.c @@ -2,8 +2,10 @@ * Driver for Atmel QSPI Controller * * Copyright (C) 2015 Atmel Corporation + * Copyright (C) 2018 Cryptera A/S * * Author: Cyrille Pitchen + * Author: Piotr Bugalski * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as @@ -35,6 +37,7 @@ #include #include +#include /* QSPI register offsets */ #define QSPI_CR 0x0000 /* Control Register */ @@ -186,6 +189,23 @@ struct atmel_qspi_command { void *rx_buf; }; +struct qspi_mode { + u8 cmd_buswidth; + u8 addr_buswidth; + u8 data_buswidth; + u32 config; +}; + +static const struct qspi_mode sama5d2_qspi_modes[] = { + { 1, 1, 1, QSPI_IFR_WIDTH_SINGLE_BIT_SPI }, + { 1, 1, 2, QSPI_IFR_WIDTH_DUAL_OUTPUT }, + { 1, 1, 4, QSPI_IFR_WIDTH_QUAD_OUTPUT }, + { 1, 2, 2, QSPI_IFR_WIDTH_DUAL_IO }, + { 1, 4, 4, QSPI_IFR_WIDTH_QUAD_IO }, + { 2, 2, 2, QSPI_IFR_WIDTH_DUAL_CMD }, + { 4, 4, 4, QSPI_IFR_WIDTH_QUAD_CMD }, +}; + /* Register access functions */ static inline u32 qspi_readl(struct atmel_qspi *aq, u32 reg) { @@ -197,6 +217,196 @@ static inline void qspi_writel(struct atmel_qspi *aq, u32 reg, u32 value) writel_relaxed(value, aq->regs + reg); } +static inline bool is_compatible(const struct spi_mem_op *op, + const struct qspi_mode *mode) +{ + if (op->cmd.buswidth != mode->cmd_buswidth) + return false; + + if (op->addr.nbytes && op->addr.buswidth != mode->addr_buswidth) + return false; + + if (op->data.nbytes && op->data.buswidth != mode->data_buswidth) + return false; + + return true; +} + +static int find_mode(const struct spi_mem_op *op) +{ + u32 i; + + for (i = 0; i < ARRAY_SIZE(sama5d2_qspi_modes); i++) + if (is_compatible(op, &sama5d2_qspi_modes[i])) + return i; + + return -1; +} + +static bool atmel_qspi_supports_op(struct spi_mem *mem, + const struct spi_mem_op *op) +{ + if (find_mode(op) < 0) + return false; + + /* special case not supported by hardware */ + if (op->addr.nbytes == 2 && op->cmd.buswidth != op->addr.buswidth && + op->dummy.nbytes == 0) + return false; + + return true; +} + +static int atmel_qspi_exec_op(struct spi_mem *mem, const struct spi_mem_op *op) +{ + struct atmel_qspi *aq = spi_controller_get_devdata(mem->spi->master); + int mode; + u32 dummy_cycles = 0; + u32 iar, icr, ifr, sr; + int err = 0; + + iar = 0; + icr = QSPI_ICR_INST(op->cmd.opcode); + ifr = QSPI_IFR_INSTEN; + + qspi_writel(aq, QSPI_MR, QSPI_MR_SMM); + + mode = find_mode(op); + if (mode < 0) + return -ENOTSUPP; + + ifr |= sama5d2_qspi_modes[mode].config; + + if (op->dummy.buswidth && op->dummy.nbytes) + dummy_cycles = op->dummy.nbytes * 8 / op->dummy.buswidth; + + if (op->addr.buswidth) { + switch (op->addr.nbytes) { + case 0: + break; + case 1: + ifr |= QSPI_IFR_OPTEN | QSPI_IFR_OPTL_8BIT; + icr |= QSPI_ICR_OPT(op->addr.val & 0xff); + break; + case 2: + if (dummy_cycles < 8 / op->addr.buswidth) { + ifr &= ~QSPI_IFR_INSTEN; + ifr |= QSPI_IFR_ADDREN; + iar = (op->cmd.opcode << 16) | + (op->addr.val & 0xffff); + } else { + ifr |= QSPI_IFR_ADDREN; + iar = (op->addr.val << 8) & 0xffffff; + dummy_cycles -= 8 / op->addr.buswidth; + } + break; + case 3: + ifr |= QSPI_IFR_ADDREN; + iar = op->addr.val & 0xffffff; + break; + case 4: + ifr |= QSPI_IFR_ADDREN | QSPI_IFR_ADDRL; + iar = op->addr.val & 0x7ffffff; + break; + default: + return -ENOTSUPP; + } + } + + /* Set number of dummy cycles */ + if (dummy_cycles) + ifr |= QSPI_IFR_NBDUM(dummy_cycles); + + /* Set data enable */ + if (op->data.nbytes) + ifr |= QSPI_IFR_DATAEN; + + if (op->data.dir == SPI_MEM_DATA_IN && op->data.nbytes) + ifr |= QSPI_IFR_TFRTYP_TRSFR_READ; + else + ifr |= QSPI_IFR_TFRTYP_TRSFR_WRITE; + + /* Clear pending interrupts */ + (void)qspi_readl(aq, QSPI_SR); + + /* Set QSPI Instruction Frame registers */ + qspi_writel(aq, QSPI_IAR, iar); + qspi_writel(aq, QSPI_ICR, icr); + qspi_writel(aq, QSPI_IFR, ifr); + + /* Skip to the final steps if there is no data */ + if (op->data.nbytes) { + /* Dummy read of QSPI_IFR to synchronize APB and AHB accesses */ + (void)qspi_readl(aq, QSPI_IFR); + + /* Send/Receive data */ + if (op->data.dir == SPI_MEM_DATA_IN) + _memcpy_fromio(op->data.buf.in, + aq->mem + iar, op->data.nbytes); + else + _memcpy_toio(aq->mem + iar, + op->data.buf.out, op->data.nbytes); + + /* Release the chip-select */ + qspi_writel(aq, QSPI_CR, QSPI_CR_LASTXFER); + } + + /* Poll INSTRuction End status */ + sr = qspi_readl(aq, QSPI_SR); + if ((sr & QSPI_SR_CMD_COMPLETED) == QSPI_SR_CMD_COMPLETED) + return err; + + /* Wait for INSTRuction End interrupt */ + reinit_completion(&aq->cmd_completion); + aq->pending = sr & QSPI_SR_CMD_COMPLETED; + qspi_writel(aq, QSPI_IER, QSPI_SR_CMD_COMPLETED); + if (!wait_for_completion_timeout(&aq->cmd_completion, + msecs_to_jiffies(1000))) + err = -ETIMEDOUT; + qspi_writel(aq, QSPI_IDR, QSPI_SR_CMD_COMPLETED); + + return err; +} + +const char *atmel_qspi_get_name(struct spi_mem *spimem) +{ + return dev_name(spimem->spi->dev.parent); +} + +static const struct spi_controller_mem_ops atmel_qspi_mem_ops = { + .supports_op = atmel_qspi_supports_op, + .exec_op = atmel_qspi_exec_op, + .get_name = atmel_qspi_get_name +}; + +static int atmel_qspi_setup(struct spi_device *spi) +{ + struct spi_controller *ctrl = spi->master; + struct atmel_qspi *aq = spi_controller_get_devdata(ctrl); + unsigned long src_rate; + u32 scr, scbr; + + if (ctrl->busy) + return -EBUSY; + + if (!spi->max_speed_hz) + return -EINVAL; + + src_rate = clk_get_rate(aq->clk); + if (!src_rate) + return -EINVAL; + + /* Compute the QSPI baudrate */ + scbr = DIV_ROUND_UP(src_rate, spi->max_speed_hz); + if (scbr > 0) + scbr--; + + scr = QSPI_SCR_SCBR(scbr); + qspi_writel(aq, QSPI_SCR, scr); + + return 0; +} + static int atmel_qspi_run_transfer(struct atmel_qspi *aq, const struct atmel_qspi_command *cmd) { @@ -777,5 +987,6 @@ static struct platform_driver atmel_qspi_driver = { module_platform_driver(atmel_qspi_driver); MODULE_AUTHOR("Cyrille Pitchen "); +MODULE_AUTHOR("Piotr Bugalski Date: Mon, 5 Nov 2018 11:36:22 +0100 Subject: [PATCH 03/11] mtd: spi-nor: atmel-quadspi: Use spi-mem interface for atmel-quadspi driver Previously added spi-mem interface is now used instead of older approach. Suggested-by: Boris Brezillon Signed-off-by: Piotr Bugalski Signed-off-by: Mark Brown --- drivers/mtd/spi-nor/atmel-quadspi.c | 91 ++++++----------------------- 1 file changed, 18 insertions(+), 73 deletions(-) diff --git a/drivers/mtd/spi-nor/atmel-quadspi.c b/drivers/mtd/spi-nor/atmel-quadspi.c index 896478a290ec..644e3f0c1558 100644 --- a/drivers/mtd/spi-nor/atmel-quadspi.c +++ b/drivers/mtd/spi-nor/atmel-quadspi.c @@ -766,27 +766,9 @@ static ssize_t atmel_qspi_read(struct spi_nor *nor, loff_t from, size_t len, static int atmel_qspi_init(struct atmel_qspi *aq) { - unsigned long src_rate; - u32 mr, scr, scbr; - /* Reset the QSPI controller */ qspi_writel(aq, QSPI_CR, QSPI_CR_SWRST); - /* Set the QSPI controller in Serial Memory Mode */ - mr = QSPI_MR_NBBITS(8) | QSPI_MR_SMM; - qspi_writel(aq, QSPI_MR, mr); - - src_rate = clk_get_rate(aq->clk); - if (!src_rate) - return -EINVAL; - - /* Compute the QSPI baudrate */ - scbr = DIV_ROUND_UP(src_rate, aq->clk_rate); - if (scbr > 0) - scbr--; - scr = QSPI_SCR_SCBR(scbr); - qspi_writel(aq, QSPI_SCR, scr); - /* Enable the QSPI controller */ qspi_writel(aq, QSPI_CR, QSPI_CR_QSPIEN); @@ -814,38 +796,25 @@ static irqreturn_t atmel_qspi_interrupt(int irq, void *dev_id) static int atmel_qspi_probe(struct platform_device *pdev) { - const struct spi_nor_hwcaps hwcaps = { - .mask = SNOR_HWCAPS_READ | - SNOR_HWCAPS_READ_FAST | - SNOR_HWCAPS_READ_1_1_2 | - SNOR_HWCAPS_READ_1_2_2 | - SNOR_HWCAPS_READ_2_2_2 | - SNOR_HWCAPS_READ_1_1_4 | - SNOR_HWCAPS_READ_1_4_4 | - SNOR_HWCAPS_READ_4_4_4 | - SNOR_HWCAPS_PP | - SNOR_HWCAPS_PP_1_1_4 | - SNOR_HWCAPS_PP_1_4_4 | - SNOR_HWCAPS_PP_4_4_4, - }; - struct device_node *child, *np = pdev->dev.of_node; + struct spi_controller *ctrl; struct atmel_qspi *aq; struct resource *res; - struct spi_nor *nor; - struct mtd_info *mtd; int irq, err = 0; - if (of_get_child_count(np) != 1) - return -ENODEV; - child = of_get_next_child(np, NULL); + ctrl = spi_alloc_master(&pdev->dev, sizeof(*aq)); + if (!ctrl) + return -ENOMEM; - aq = devm_kzalloc(&pdev->dev, sizeof(*aq), GFP_KERNEL); - if (!aq) { - err = -ENOMEM; - goto exit; - } + ctrl->mode_bits = SPI_RX_DUAL | SPI_RX_QUAD | SPI_TX_DUAL | SPI_TX_QUAD; + ctrl->setup = atmel_qspi_setup; + ctrl->bus_num = -1; + ctrl->mem_ops = &atmel_qspi_mem_ops; + ctrl->num_chipselect = 1; + ctrl->dev.of_node = pdev->dev.of_node; + platform_set_drvdata(pdev, ctrl); + + aq = spi_controller_get_devdata(ctrl); - platform_set_drvdata(pdev, aq); init_completion(&aq->cmd_completion); aq->pdev = pdev; @@ -894,54 +863,30 @@ static int atmel_qspi_probe(struct platform_device *pdev) if (err) goto disable_clk; - /* Setup the spi-nor */ - nor = &aq->nor; - mtd = &nor->mtd; - - nor->dev = &pdev->dev; - spi_nor_set_flash_node(nor, child); - nor->priv = aq; - mtd->priv = nor; - - nor->read_reg = atmel_qspi_read_reg; - nor->write_reg = atmel_qspi_write_reg; - nor->read = atmel_qspi_read; - nor->write = atmel_qspi_write; - nor->erase = atmel_qspi_erase; - - err = of_property_read_u32(child, "spi-max-frequency", &aq->clk_rate); - if (err < 0) - goto disable_clk; - err = atmel_qspi_init(aq); if (err) goto disable_clk; - err = spi_nor_scan(nor, NULL, &hwcaps); + err = spi_register_controller(ctrl); if (err) goto disable_clk; - err = mtd_device_register(mtd, NULL, 0); - if (err) - goto disable_clk; - - of_node_put(child); - return 0; disable_clk: clk_disable_unprepare(aq->clk); exit: - of_node_put(child); + spi_controller_put(ctrl); return err; } static int atmel_qspi_remove(struct platform_device *pdev) { - struct atmel_qspi *aq = platform_get_drvdata(pdev); + struct spi_controller *ctrl = platform_get_drvdata(pdev); + struct atmel_qspi *aq = spi_controller_get_devdata(ctrl); - mtd_device_unregister(&aq->nor.mtd); + spi_unregister_controller(ctrl); qspi_writel(aq, QSPI_CR, QSPI_CR_QSPIDIS); clk_disable_unprepare(aq->clk); return 0; From 6ca622c87149a20a47abbb954f896f515e2292a7 Mon Sep 17 00:00:00 2001 From: Piotr Bugalski Date: Mon, 5 Nov 2018 11:36:23 +0100 Subject: [PATCH 04/11] mtd: spi-nor: atmel-quadspi: Remove unused code from atmel-quadspi driver Code used for previous interface is no longer needed. This change just removes obsolete code. Suggested-by: Boris Brezillon Signed-off-by: Piotr Bugalski Signed-off-by: Mark Brown --- drivers/mtd/spi-nor/atmel-quadspi.c | 388 ---------------------------- 1 file changed, 388 deletions(-) diff --git a/drivers/mtd/spi-nor/atmel-quadspi.c b/drivers/mtd/spi-nor/atmel-quadspi.c index 644e3f0c1558..ddc712410812 100644 --- a/drivers/mtd/spi-nor/atmel-quadspi.c +++ b/drivers/mtd/spi-nor/atmel-quadspi.c @@ -29,14 +29,9 @@ #include #include #include -#include -#include -#include -#include #include #include -#include #include /* QSPI register offsets */ @@ -160,35 +155,9 @@ struct atmel_qspi { struct clk *clk; struct platform_device *pdev; u32 pending; - - struct spi_nor nor; - u32 clk_rate; struct completion cmd_completion; }; -struct atmel_qspi_command { - union { - struct { - u32 instruction:1; - u32 address:3; - u32 mode:1; - u32 dummy:1; - u32 data:1; - u32 reserved:25; - } bits; - u32 word; - } enable; - u8 instruction; - u8 mode; - u8 num_mode_cycles; - u8 num_dummy_cycles; - u32 address; - - size_t buf_len; - const void *tx_buf; - void *rx_buf; -}; - struct qspi_mode { u8 cmd_buswidth; u8 addr_buswidth; @@ -407,363 +376,6 @@ static int atmel_qspi_setup(struct spi_device *spi) return 0; } -static int atmel_qspi_run_transfer(struct atmel_qspi *aq, - const struct atmel_qspi_command *cmd) -{ - void __iomem *ahb_mem; - - /* Then fallback to a PIO transfer (memcpy() DOES NOT work!) */ - ahb_mem = aq->mem; - if (cmd->enable.bits.address) - ahb_mem += cmd->address; - if (cmd->tx_buf) - _memcpy_toio(ahb_mem, cmd->tx_buf, cmd->buf_len); - else - _memcpy_fromio(cmd->rx_buf, ahb_mem, cmd->buf_len); - - return 0; -} - -#ifdef DEBUG -static void atmel_qspi_debug_command(struct atmel_qspi *aq, - const struct atmel_qspi_command *cmd, - u32 ifr) -{ - u8 cmd_buf[SPI_NOR_MAX_CMD_SIZE]; - size_t len = 0; - int i; - - if (cmd->enable.bits.instruction) - cmd_buf[len++] = cmd->instruction; - - for (i = cmd->enable.bits.address-1; i >= 0; --i) - cmd_buf[len++] = (cmd->address >> (i << 3)) & 0xff; - - if (cmd->enable.bits.mode) - cmd_buf[len++] = cmd->mode; - - if (cmd->enable.bits.dummy) { - int num = cmd->num_dummy_cycles; - - switch (ifr & QSPI_IFR_WIDTH_MASK) { - case QSPI_IFR_WIDTH_SINGLE_BIT_SPI: - case QSPI_IFR_WIDTH_DUAL_OUTPUT: - case QSPI_IFR_WIDTH_QUAD_OUTPUT: - num >>= 3; - break; - case QSPI_IFR_WIDTH_DUAL_IO: - case QSPI_IFR_WIDTH_DUAL_CMD: - num >>= 2; - break; - case QSPI_IFR_WIDTH_QUAD_IO: - case QSPI_IFR_WIDTH_QUAD_CMD: - num >>= 1; - break; - default: - return; - } - - for (i = 0; i < num; ++i) - cmd_buf[len++] = 0; - } - - /* Dump the SPI command */ - print_hex_dump(KERN_DEBUG, "qspi cmd: ", DUMP_PREFIX_NONE, - 32, 1, cmd_buf, len, false); - -#ifdef VERBOSE_DEBUG - /* If verbose debug is enabled, also dump the TX data */ - if (cmd->enable.bits.data && cmd->tx_buf) - print_hex_dump(KERN_DEBUG, "qspi tx : ", DUMP_PREFIX_NONE, - 32, 1, cmd->tx_buf, cmd->buf_len, false); -#endif -} -#else -#define atmel_qspi_debug_command(aq, cmd, ifr) -#endif - -static int atmel_qspi_run_command(struct atmel_qspi *aq, - const struct atmel_qspi_command *cmd, - u32 ifr_tfrtyp, enum spi_nor_protocol proto) -{ - u32 iar, icr, ifr, sr; - int err = 0; - - iar = 0; - icr = 0; - ifr = ifr_tfrtyp; - - /* Set the SPI protocol */ - switch (proto) { - case SNOR_PROTO_1_1_1: - ifr |= QSPI_IFR_WIDTH_SINGLE_BIT_SPI; - break; - - case SNOR_PROTO_1_1_2: - ifr |= QSPI_IFR_WIDTH_DUAL_OUTPUT; - break; - - case SNOR_PROTO_1_1_4: - ifr |= QSPI_IFR_WIDTH_QUAD_OUTPUT; - break; - - case SNOR_PROTO_1_2_2: - ifr |= QSPI_IFR_WIDTH_DUAL_IO; - break; - - case SNOR_PROTO_1_4_4: - ifr |= QSPI_IFR_WIDTH_QUAD_IO; - break; - - case SNOR_PROTO_2_2_2: - ifr |= QSPI_IFR_WIDTH_DUAL_CMD; - break; - - case SNOR_PROTO_4_4_4: - ifr |= QSPI_IFR_WIDTH_QUAD_CMD; - break; - - default: - return -EINVAL; - } - - /* Compute instruction parameters */ - if (cmd->enable.bits.instruction) { - icr |= QSPI_ICR_INST(cmd->instruction); - ifr |= QSPI_IFR_INSTEN; - } - - /* Compute address parameters */ - switch (cmd->enable.bits.address) { - case 4: - ifr |= QSPI_IFR_ADDRL; - /* fall through to the 24bit (3 byte) address case. */ - case 3: - iar = (cmd->enable.bits.data) ? 0 : cmd->address; - ifr |= QSPI_IFR_ADDREN; - break; - case 0: - break; - default: - return -EINVAL; - } - - /* Compute option parameters */ - if (cmd->enable.bits.mode && cmd->num_mode_cycles) { - u32 mode_cycle_bits, mode_bits; - - icr |= QSPI_ICR_OPT(cmd->mode); - ifr |= QSPI_IFR_OPTEN; - - switch (ifr & QSPI_IFR_WIDTH_MASK) { - case QSPI_IFR_WIDTH_SINGLE_BIT_SPI: - case QSPI_IFR_WIDTH_DUAL_OUTPUT: - case QSPI_IFR_WIDTH_QUAD_OUTPUT: - mode_cycle_bits = 1; - break; - case QSPI_IFR_WIDTH_DUAL_IO: - case QSPI_IFR_WIDTH_DUAL_CMD: - mode_cycle_bits = 2; - break; - case QSPI_IFR_WIDTH_QUAD_IO: - case QSPI_IFR_WIDTH_QUAD_CMD: - mode_cycle_bits = 4; - break; - default: - return -EINVAL; - } - - mode_bits = cmd->num_mode_cycles * mode_cycle_bits; - switch (mode_bits) { - case 1: - ifr |= QSPI_IFR_OPTL_1BIT; - break; - - case 2: - ifr |= QSPI_IFR_OPTL_2BIT; - break; - - case 4: - ifr |= QSPI_IFR_OPTL_4BIT; - break; - - case 8: - ifr |= QSPI_IFR_OPTL_8BIT; - break; - - default: - return -EINVAL; - } - } - - /* Set number of dummy cycles */ - if (cmd->enable.bits.dummy) - ifr |= QSPI_IFR_NBDUM(cmd->num_dummy_cycles); - - /* Set data enable */ - if (cmd->enable.bits.data) { - ifr |= QSPI_IFR_DATAEN; - - /* Special case for Continuous Read Mode */ - if (!cmd->tx_buf && !cmd->rx_buf) - ifr |= QSPI_IFR_CRM; - } - - /* Clear pending interrupts */ - (void)qspi_readl(aq, QSPI_SR); - - /* Set QSPI Instruction Frame registers */ - atmel_qspi_debug_command(aq, cmd, ifr); - qspi_writel(aq, QSPI_IAR, iar); - qspi_writel(aq, QSPI_ICR, icr); - qspi_writel(aq, QSPI_IFR, ifr); - - /* Skip to the final steps if there is no data */ - if (!cmd->enable.bits.data) - goto no_data; - - /* Dummy read of QSPI_IFR to synchronize APB and AHB accesses */ - (void)qspi_readl(aq, QSPI_IFR); - - /* Stop here for continuous read */ - if (!cmd->tx_buf && !cmd->rx_buf) - return 0; - /* Send/Receive data */ - err = atmel_qspi_run_transfer(aq, cmd); - - /* Release the chip-select */ - qspi_writel(aq, QSPI_CR, QSPI_CR_LASTXFER); - - if (err) - return err; - -#if defined(DEBUG) && defined(VERBOSE_DEBUG) - /* - * If verbose debug is enabled, also dump the RX data in addition to - * the SPI command previously dumped by atmel_qspi_debug_command() - */ - if (cmd->rx_buf) - print_hex_dump(KERN_DEBUG, "qspi rx : ", DUMP_PREFIX_NONE, - 32, 1, cmd->rx_buf, cmd->buf_len, false); -#endif -no_data: - /* Poll INSTRuction End status */ - sr = qspi_readl(aq, QSPI_SR); - if ((sr & QSPI_SR_CMD_COMPLETED) == QSPI_SR_CMD_COMPLETED) - return err; - - /* Wait for INSTRuction End interrupt */ - reinit_completion(&aq->cmd_completion); - aq->pending = sr & QSPI_SR_CMD_COMPLETED; - qspi_writel(aq, QSPI_IER, QSPI_SR_CMD_COMPLETED); - if (!wait_for_completion_timeout(&aq->cmd_completion, - msecs_to_jiffies(1000))) - err = -ETIMEDOUT; - qspi_writel(aq, QSPI_IDR, QSPI_SR_CMD_COMPLETED); - - return err; -} - -static int atmel_qspi_read_reg(struct spi_nor *nor, u8 opcode, - u8 *buf, int len) -{ - struct atmel_qspi *aq = nor->priv; - struct atmel_qspi_command cmd; - - memset(&cmd, 0, sizeof(cmd)); - cmd.enable.bits.instruction = 1; - cmd.enable.bits.data = 1; - cmd.instruction = opcode; - cmd.rx_buf = buf; - cmd.buf_len = len; - return atmel_qspi_run_command(aq, &cmd, QSPI_IFR_TFRTYP_TRSFR_READ, - nor->reg_proto); -} - -static int atmel_qspi_write_reg(struct spi_nor *nor, u8 opcode, - u8 *buf, int len) -{ - struct atmel_qspi *aq = nor->priv; - struct atmel_qspi_command cmd; - - memset(&cmd, 0, sizeof(cmd)); - cmd.enable.bits.instruction = 1; - cmd.enable.bits.data = (buf != NULL && len > 0); - cmd.instruction = opcode; - cmd.tx_buf = buf; - cmd.buf_len = len; - return atmel_qspi_run_command(aq, &cmd, QSPI_IFR_TFRTYP_TRSFR_WRITE, - nor->reg_proto); -} - -static ssize_t atmel_qspi_write(struct spi_nor *nor, loff_t to, size_t len, - const u_char *write_buf) -{ - struct atmel_qspi *aq = nor->priv; - struct atmel_qspi_command cmd; - ssize_t ret; - - memset(&cmd, 0, sizeof(cmd)); - cmd.enable.bits.instruction = 1; - cmd.enable.bits.address = nor->addr_width; - cmd.enable.bits.data = 1; - cmd.instruction = nor->program_opcode; - cmd.address = (u32)to; - cmd.tx_buf = write_buf; - cmd.buf_len = len; - ret = atmel_qspi_run_command(aq, &cmd, QSPI_IFR_TFRTYP_TRSFR_WRITE_MEM, - nor->write_proto); - return (ret < 0) ? ret : len; -} - -static int atmel_qspi_erase(struct spi_nor *nor, loff_t offs) -{ - struct atmel_qspi *aq = nor->priv; - struct atmel_qspi_command cmd; - - memset(&cmd, 0, sizeof(cmd)); - cmd.enable.bits.instruction = 1; - cmd.enable.bits.address = nor->addr_width; - cmd.instruction = nor->erase_opcode; - cmd.address = (u32)offs; - return atmel_qspi_run_command(aq, &cmd, QSPI_IFR_TFRTYP_TRSFR_WRITE, - nor->reg_proto); -} - -static ssize_t atmel_qspi_read(struct spi_nor *nor, loff_t from, size_t len, - u_char *read_buf) -{ - struct atmel_qspi *aq = nor->priv; - struct atmel_qspi_command cmd; - u8 num_mode_cycles, num_dummy_cycles; - ssize_t ret; - - if (nor->read_dummy >= 2) { - num_mode_cycles = 2; - num_dummy_cycles = nor->read_dummy - 2; - } else { - num_mode_cycles = nor->read_dummy; - num_dummy_cycles = 0; - } - - memset(&cmd, 0, sizeof(cmd)); - cmd.enable.bits.instruction = 1; - cmd.enable.bits.address = nor->addr_width; - cmd.enable.bits.mode = (num_mode_cycles > 0); - cmd.enable.bits.dummy = (num_dummy_cycles > 0); - cmd.enable.bits.data = 1; - cmd.instruction = nor->read_opcode; - cmd.address = (u32)from; - cmd.mode = 0xff; /* This value prevents from entering the 0-4-4 mode */ - cmd.num_mode_cycles = num_mode_cycles; - cmd.num_dummy_cycles = num_dummy_cycles; - cmd.rx_buf = read_buf; - cmd.buf_len = len; - ret = atmel_qspi_run_command(aq, &cmd, QSPI_IFR_TFRTYP_TRSFR_READ_MEM, - nor->read_proto); - return (ret < 0) ? ret : len; -} - static int atmel_qspi_init(struct atmel_qspi *aq) { /* Reset the QSPI controller */ From 0e6aae08e9ae7c2dc3c83bf6960d824feb14b706 Mon Sep 17 00:00:00 2001 From: Piotr Bugalski Date: Mon, 5 Nov 2018 11:36:24 +0100 Subject: [PATCH 05/11] spi: Add QuadSPI driver for Atmel SAMA5D2 Kernel contains QSPI driver strongly tied to MTD and nor-flash memory. New spi-mem interface allows usage also other memory types, especially much larger NAND with SPI interface. This driver works as SPI controller and is not related to MTD, however can work with NAND-flash or other peripherals using spi-mem interface. Suggested-by: Boris Brezillon Signed-off-by: Piotr Bugalski Signed-off-by: Mark Brown --- drivers/mtd/spi-nor/Kconfig | 9 --------- drivers/mtd/spi-nor/Makefile | 1 - drivers/spi/Kconfig | 9 +++++++++ drivers/spi/Makefile | 1 + drivers/{mtd/spi-nor => spi}/atmel-quadspi.c | 0 5 files changed, 10 insertions(+), 10 deletions(-) rename drivers/{mtd/spi-nor => spi}/atmel-quadspi.c (100%) diff --git a/drivers/mtd/spi-nor/Kconfig b/drivers/mtd/spi-nor/Kconfig index 6cc9c929ff57..44fe8018733c 100644 --- a/drivers/mtd/spi-nor/Kconfig +++ b/drivers/mtd/spi-nor/Kconfig @@ -39,15 +39,6 @@ config SPI_ASPEED_SMC and support for the SPI flash memory controller (SPI) for the host firmware. The implementation only supports SPI NOR. -config SPI_ATMEL_QUADSPI - tristate "Atmel Quad SPI Controller" - depends on ARCH_AT91 || (ARM && COMPILE_TEST) - depends on OF && HAS_IOMEM - help - This enables support for the Quad SPI controller in master mode. - This driver does not support generic SPI. The implementation only - supports SPI NOR. - config SPI_CADENCE_QUADSPI tristate "Cadence Quad SPI controller" depends on OF && (ARM || ARM64 || COMPILE_TEST) diff --git a/drivers/mtd/spi-nor/Makefile b/drivers/mtd/spi-nor/Makefile index f4c61d282abd..a552efd22958 100644 --- a/drivers/mtd/spi-nor/Makefile +++ b/drivers/mtd/spi-nor/Makefile @@ -1,7 +1,6 @@ # SPDX-License-Identifier: GPL-2.0 obj-$(CONFIG_MTD_SPI_NOR) += spi-nor.o obj-$(CONFIG_SPI_ASPEED_SMC) += aspeed-smc.o -obj-$(CONFIG_SPI_ATMEL_QUADSPI) += atmel-quadspi.o obj-$(CONFIG_SPI_CADENCE_QUADSPI) += cadence-quadspi.o obj-$(CONFIG_SPI_FSL_QUADSPI) += fsl-quadspi.o obj-$(CONFIG_SPI_HISI_SFC) += hisi-sfc.o diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig index 7d3a5c94727e..ca68ac63c6c3 100644 --- a/drivers/spi/Kconfig +++ b/drivers/spi/Kconfig @@ -91,6 +91,15 @@ config SPI_AT91_USART This selects a driver for the AT91 USART Controller as SPI Master, present on AT91 and SAMA5 SoC series. +config SPI_ATMEL_QUADSPI + tristate "Atmel Quad SPI Controller" + depends on ARCH_AT91 || (ARM && COMPILE_TEST) + depends on OF && HAS_IOMEM + help + This enables support for the Quad SPI controller in master mode. + This driver does not support generic SPI. The implementation only + supports spi-mem interface. + config SPI_AU1550 tristate "Au1550/Au1200/Au1300 SPI Controller" depends on MIPS_ALCHEMY diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile index 3575205c5c27..258917db3f56 100644 --- a/drivers/spi/Makefile +++ b/drivers/spi/Makefile @@ -16,6 +16,7 @@ obj-$(CONFIG_SPI_LOOPBACK_TEST) += spi-loopback-test.o obj-$(CONFIG_SPI_ALTERA) += spi-altera.o obj-$(CONFIG_SPI_ARMADA_3700) += spi-armada-3700.o obj-$(CONFIG_SPI_ATMEL) += spi-atmel.o +obj-$(CONFIG_SPI_ATMEL_QUADSPI) += atmel-quadspi.o obj-$(CONFIG_SPI_AT91_USART) += spi-at91-usart.o obj-$(CONFIG_SPI_ATH79) += spi-ath79.o obj-$(CONFIG_SPI_AU1550) += spi-au1550.o diff --git a/drivers/mtd/spi-nor/atmel-quadspi.c b/drivers/spi/atmel-quadspi.c similarity index 100% rename from drivers/mtd/spi-nor/atmel-quadspi.c rename to drivers/spi/atmel-quadspi.c From a24626d72915fef2b38bb8467b3714f17efae6c0 Mon Sep 17 00:00:00 2001 From: Piotr Bugalski Date: Mon, 5 Nov 2018 11:36:25 +0100 Subject: [PATCH 06/11] dt-bindings: spi: QuadSPI driver for Atmel SAMA5D2 Atmel SAMA5D2 QuadSPI driver was moved from mtd to spi subsystem, this change is just moving DT-binding documentation. Suggested-by: Boris Brezillon Signed-off-by: Piotr Bugalski Reviewed-by: Rob Herring Signed-off-by: Mark Brown --- Documentation/devicetree/bindings/{mtd => spi}/atmel-quadspi.txt | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename Documentation/devicetree/bindings/{mtd => spi}/atmel-quadspi.txt (100%) diff --git a/Documentation/devicetree/bindings/mtd/atmel-quadspi.txt b/Documentation/devicetree/bindings/spi/atmel-quadspi.txt similarity index 100% rename from Documentation/devicetree/bindings/mtd/atmel-quadspi.txt rename to Documentation/devicetree/bindings/spi/atmel-quadspi.txt From 6afe76a6723975391d06c42a422370a588395f84 Mon Sep 17 00:00:00 2001 From: Boris Brezillon Date: Tue, 6 Nov 2018 17:05:30 +0100 Subject: [PATCH 07/11] spi: spi-mem: Add missing word in the SPI_MEM_DATA_OUT description Missing 'to' in the SPI_MEM_DATA_OUT description. Signed-off-by: Boris Brezillon Reviewed-by: Miquel Raynal Signed-off-by: Mark Brown --- include/linux/spi/spi-mem.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/linux/spi/spi-mem.h b/include/linux/spi/spi-mem.h index 69ee30456864..867839cc69a7 100644 --- a/include/linux/spi/spi-mem.h +++ b/include/linux/spi/spi-mem.h @@ -58,7 +58,7 @@ * enum spi_mem_data_dir - describes the direction of a SPI memory data * transfer from the controller perspective * @SPI_MEM_DATA_IN: data coming from the SPI memory - * @SPI_MEM_DATA_OUT: data sent the SPI memory + * @SPI_MEM_DATA_OUT: data sent to the SPI memory */ enum spi_mem_data_dir { SPI_MEM_DATA_IN, From 0ebb261a0b2d090de618a383d2378d4a00834958 Mon Sep 17 00:00:00 2001 From: Boris Brezillon Date: Tue, 6 Nov 2018 17:05:31 +0100 Subject: [PATCH 08/11] spi: spi-mem: Add SPI_MEM_NO_DATA to the spi_mem_data_dir enum When defining spi_mem_op templates we don't necessarily know the size that will be passed when the template is actually used, and basing the supports_op() check on op->data.nbytes to know whether there will be data transferred for a specific operation is this not possible. Add SPI_MEM_NO_DATA to the spi_mem_data_dir enum so that we can base our checks on op->data.dir instead of op->data.nbytes. Signed-off-by: Boris Brezillon Reviewed-by: Miquel Raynal Signed-off-by: Mark Brown --- drivers/spi/spi-mem.c | 2 +- include/linux/spi/spi-mem.h | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/spi/spi-mem.c b/drivers/spi/spi-mem.c index 62a7b80801d2..967f581bca4f 100644 --- a/drivers/spi/spi-mem.c +++ b/drivers/spi/spi-mem.c @@ -142,7 +142,7 @@ static bool spi_mem_default_supports_op(struct spi_mem *mem, spi_check_buswidth_req(mem, op->dummy.buswidth, true)) return false; - if (op->data.nbytes && + if (op->data.dir != SPI_MEM_NO_DATA && spi_check_buswidth_req(mem, op->data.buswidth, op->data.dir == SPI_MEM_DATA_OUT)) return false; diff --git a/include/linux/spi/spi-mem.h b/include/linux/spi/spi-mem.h index 867839cc69a7..250b6f5c47c2 100644 --- a/include/linux/spi/spi-mem.h +++ b/include/linux/spi/spi-mem.h @@ -57,10 +57,12 @@ /** * enum spi_mem_data_dir - describes the direction of a SPI memory data * transfer from the controller perspective + * @SPI_MEM_NO_DATA: no data transferred * @SPI_MEM_DATA_IN: data coming from the SPI memory * @SPI_MEM_DATA_OUT: data sent to the SPI memory */ enum spi_mem_data_dir { + SPI_MEM_NO_DATA, SPI_MEM_DATA_IN, SPI_MEM_DATA_OUT, }; From f86c24f4795303e4024bc113196de32782f6ccb5 Mon Sep 17 00:00:00 2001 From: Boris Brezillon Date: Tue, 6 Nov 2018 17:05:32 +0100 Subject: [PATCH 09/11] spi: spi-mem: Split spi_mem_exec_op() code The logic surrounding the ->exec_op() call applies to direct mapping accessors. Move this code to separate functions to avoid duplicating code. Signed-off-by: Boris Brezillon Reviewed-by: Miquel Raynal Signed-off-by: Mark Brown --- drivers/spi/spi-mem.c | 63 ++++++++++++++++++++++++++++--------------- 1 file changed, 42 insertions(+), 21 deletions(-) diff --git a/drivers/spi/spi-mem.c b/drivers/spi/spi-mem.c index 967f581bca4f..7916e655afc8 100644 --- a/drivers/spi/spi-mem.c +++ b/drivers/spi/spi-mem.c @@ -213,6 +213,44 @@ bool spi_mem_supports_op(struct spi_mem *mem, const struct spi_mem_op *op) } EXPORT_SYMBOL_GPL(spi_mem_supports_op); +static int spi_mem_access_start(struct spi_mem *mem) +{ + struct spi_controller *ctlr = mem->spi->controller; + + /* + * Flush the message queue before executing our SPI memory + * operation to prevent preemption of regular SPI transfers. + */ + spi_flush_queue(ctlr); + + if (ctlr->auto_runtime_pm) { + int ret; + + ret = pm_runtime_get_sync(ctlr->dev.parent); + if (ret < 0) { + dev_err(&ctlr->dev, "Failed to power device: %d\n", + ret); + return ret; + } + } + + mutex_lock(&ctlr->bus_lock_mutex); + mutex_lock(&ctlr->io_mutex); + + return 0; +} + +static void spi_mem_access_end(struct spi_mem *mem) +{ + struct spi_controller *ctlr = mem->spi->controller; + + mutex_unlock(&ctlr->io_mutex); + mutex_unlock(&ctlr->bus_lock_mutex); + + if (ctlr->auto_runtime_pm) + pm_runtime_put(ctlr->dev.parent); +} + /** * spi_mem_exec_op() - Execute a memory operation * @mem: the SPI memory @@ -242,30 +280,13 @@ int spi_mem_exec_op(struct spi_mem *mem, const struct spi_mem_op *op) return -ENOTSUPP; if (ctlr->mem_ops) { - /* - * Flush the message queue before executing our SPI memory - * operation to prevent preemption of regular SPI transfers. - */ - spi_flush_queue(ctlr); + ret = spi_mem_access_start(mem); + if (ret) + return ret; - if (ctlr->auto_runtime_pm) { - ret = pm_runtime_get_sync(ctlr->dev.parent); - if (ret < 0) { - dev_err(&ctlr->dev, - "Failed to power device: %d\n", - ret); - return ret; - } - } - - mutex_lock(&ctlr->bus_lock_mutex); - mutex_lock(&ctlr->io_mutex); ret = ctlr->mem_ops->exec_op(mem, op); - mutex_unlock(&ctlr->io_mutex); - mutex_unlock(&ctlr->bus_lock_mutex); - if (ctlr->auto_runtime_pm) - pm_runtime_put(ctlr->dev.parent); + spi_mem_access_end(mem); /* * Some controllers only optimize specific paths (typically the From aa167f3fed0c37e0e4c707d4331d827661f46644 Mon Sep 17 00:00:00 2001 From: Boris Brezillon Date: Tue, 6 Nov 2018 17:05:33 +0100 Subject: [PATCH 10/11] spi: spi-mem: Add a new API to support direct mapping 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. This new API allows SPI memory drivers to create direct mappings and then use them to access the memory instead of using spi_mem_exec_op(). Signed-off-by: Boris Brezillon Reviewed-by: Miquel Raynal Signed-off-by: Mark Brown --- drivers/spi/spi-mem.c | 204 ++++++++++++++++++++++++++++++++++++ include/linux/spi/spi-mem.h | 80 ++++++++++++++ 2 files changed, 284 insertions(+) diff --git a/drivers/spi/spi-mem.c b/drivers/spi/spi-mem.c index 7916e655afc8..b12a7974b665 100644 --- a/drivers/spi/spi-mem.c +++ b/drivers/spi/spi-mem.c @@ -432,6 +432,210 @@ int spi_mem_adjust_op_size(struct spi_mem *mem, struct spi_mem_op *op) } EXPORT_SYMBOL_GPL(spi_mem_adjust_op_size); +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->mem, &op); + if (ret) + return ret; + + ret = spi_mem_exec_op(desc->mem, &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->mem, &op); + if (ret) + return ret; + + ret = spi_mem_exec_op(desc->mem, &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 + * fallback 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_mem *mem, + const struct spi_mem_dirmap_info *info) +{ + struct spi_controller *ctlr = mem->spi->controller; + 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->mem = mem; + desc->info = *info; + if (ctlr->mem_ops && ctlr->mem_ops->dirmap_create) + ret = ctlr->mem_ops->dirmap_create(desc); + + if (ret) { + desc->nodirmap = true; + if (!spi_mem_supports_op(desc->mem, &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 + * @info: direct mapping information + * + * 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 spi_controller *ctlr = desc->mem->spi->controller; + + if (!desc->nodirmap && ctlr->mem_ops && ctlr->mem_ops->dirmap_destroy) + ctlr->mem_ops->dirmap_destroy(desc); +} +EXPORT_SYMBOL_GPL(spi_mem_dirmap_destroy); + +/** + * spi_mem_dirmap_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 spi_controller *ctlr = desc->mem->spi->controller; + 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 (ctlr->mem_ops && ctlr->mem_ops->dirmap_read) { + ret = spi_mem_access_start(desc->mem); + if (ret) + return ret; + + ret = ctlr->mem_ops->dirmap_read(desc, offs, len, buf); + + spi_mem_access_end(desc->mem); + } else { + ret = -ENOTSUPP; + } + + return ret; +} +EXPORT_SYMBOL_GPL(spi_mem_dirmap_read); + +/** + * spi_mem_dirmap_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 spi_controller *ctlr = desc->mem->spi->controller; + 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 (ctlr->mem_ops && ctlr->mem_ops->dirmap_write) { + ret = spi_mem_access_start(desc->mem); + if (ret) + return ret; + + ret = ctlr->mem_ops->dirmap_write(desc, offs, len, buf); + + spi_mem_access_end(desc->mem); + } else { + ret = -ENOTSUPP; + } + + return ret; +} +EXPORT_SYMBOL_GPL(spi_mem_dirmap_write); + static inline struct spi_mem_driver *to_spi_mem_drv(struct device_driver *drv) { return container_of(drv, struct spi_mem_driver, spidrv.driver); diff --git a/include/linux/spi/spi-mem.h b/include/linux/spi/spi-mem.h index 250b6f5c47c2..3fe24500c5ee 100644 --- a/include/linux/spi/spi-mem.h +++ b/include/linux/spi/spi-mem.h @@ -124,6 +124,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_mem *mem; + struct spi_mem_dirmap_info info; + unsigned int nodirmap; + void *priv; +}; + /** * struct spi_mem - describes a SPI memory device * @spi: the underlying SPI device @@ -179,10 +222,32 @@ static inline void *spi_mem_get_drvdata(struct spi_mem *mem) * Note that if the implementation of this function allocates memory * dynamically, then it should do so with devm_xxx(), as we don't * have a ->free_name() function. + * @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_mem *mem, struct spi_mem_op *op); @@ -191,6 +256,12 @@ struct spi_controller_mem_ops { int (*exec_op)(struct spi_mem *mem, const struct spi_mem_op *op); const char *(*get_name)(struct spi_mem *mem); + 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); }; /** @@ -251,6 +322,15 @@ int spi_mem_exec_op(struct spi_mem *mem, const char *spi_mem_get_name(struct spi_mem *mem); +struct spi_mem_dirmap_desc * +spi_mem_dirmap_create(struct spi_mem *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); + int spi_mem_driver_register_with_owner(struct spi_mem_driver *drv, struct module *owner); From 2a9d92fb3a1282a4659f1bb6d5684018846537b7 Mon Sep 17 00:00:00 2001 From: Arnd Bergmann Date: Mon, 10 Dec 2018 21:38:16 +0100 Subject: [PATCH 11/11] mtd: atmel-quadspi: disallow building on ebsa110 I ran into a link-time error with the atmel-quadspi driver on the EBSA110 platform: drivers/mtd/built-in.o: In function `atmel_qspi_run_command': :(.text+0x1ee3c): undefined reference to `_memcpy_toio' :(.text+0x1ee48): undefined reference to `_memcpy_fromio' The problem is that _memcpy_toio/_memcpy_fromio are not available on that platform, and we have to prevent building the driver there. In case we want to backport this to older kernels: between linux-4.8 and linux-4.20, the Kconfig entry was in drivers/mtd/spi-nor/Kconfig but had the same problem. Link: https://lore.kernel.org/patchwork/patch/812860/ Fixes: 161aaab8a067 ("mtd: atmel-quadspi: add driver for Atmel QSPI controller") Signed-off-by: Arnd Bergmann Reviewed-by: Boris Brezillon Signed-off-by: Mark Brown Cc: stable@vger.kernel.org --- drivers/spi/Kconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig index ca68ac63c6c3..3135f80ec853 100644 --- a/drivers/spi/Kconfig +++ b/drivers/spi/Kconfig @@ -93,7 +93,7 @@ config SPI_AT91_USART config SPI_ATMEL_QUADSPI tristate "Atmel Quad SPI Controller" - depends on ARCH_AT91 || (ARM && COMPILE_TEST) + depends on ARCH_AT91 || (ARM && COMPILE_TEST && !ARCH_EBSA110) depends on OF && HAS_IOMEM help This enables support for the Quad SPI controller in master mode.