diff --git a/Documentation/devicetree/bindings/mtd/m25p80.rst b/Documentation/devicetree/bindings/mtd/m25p80.rst new file mode 100644 index 0000000..d7c8914 --- /dev/null +++ b/Documentation/devicetree/bindings/mtd/m25p80.rst @@ -0,0 +1,10 @@ +MTD SPI driver for ST M25Pxx (and similar) serial flash chips +============================================================= + +Additionally to the Linux bindings in ``dts/Bindings/mtd/m25p80.txt`` +the barebox driver has the following optional properties: + +- use-large-blocks : Use large blocks rather than the 4K blocks some devices + support. 4K erase blocks do not work with UBIFS which needs + a minimum erase block size of 15360 bytes. Also bigger sectors + are faster to erase. diff --git a/drivers/mtd/devices/m25p80.c b/drivers/mtd/devices/m25p80.c index 794c9db..d627690 100644 --- a/drivers/mtd/devices/m25p80.c +++ b/drivers/mtd/devices/m25p80.c @@ -235,6 +235,7 @@ enum read_mode mode = SPI_NOR_NORMAL; const char *flash_name = NULL; int device_id; + bool use_large_blocks; int ret; data = dev->platform_data; @@ -272,7 +273,10 @@ else flash_name = NULL; /* auto-detect */ - ret = spi_nor_scan(nor, flash_name, mode); + use_large_blocks = of_property_read_bool(dev->device_node, + "use-large-blocks"); + + ret = spi_nor_scan(nor, flash_name, mode, use_large_blocks); if (ret) return ret; diff --git a/drivers/mtd/spi-nor/cadence-quadspi.c b/drivers/mtd/spi-nor/cadence-quadspi.c index dce29ca..ff7bb7a 100644 --- a/drivers/mtd/spi-nor/cadence-quadspi.c +++ b/drivers/mtd/spi-nor/cadence-quadspi.c @@ -1078,7 +1078,7 @@ nor->write = cqspi_write; nor->erase = cqspi_erase; - ret = spi_nor_scan(nor, NULL, SPI_NOR_QUAD); + ret = spi_nor_scan(nor, NULL, SPI_NOR_QUAD, false); if (ret) goto probe_failed; diff --git a/drivers/mtd/spi-nor/spi-nor.c b/drivers/mtd/spi-nor/spi-nor.c index 9435424..b357e5a 100644 --- a/drivers/mtd/spi-nor/spi-nor.c +++ b/drivers/mtd/spi-nor/spi-nor.c @@ -370,85 +370,27 @@ static int spi_nor_lock(struct mtd_info *mtd, loff_t ofs, size_t len) { - struct spi_nor *nor = mtd_to_spi_nor(mtd); - uint32_t offset = ofs; - uint8_t status_old, status_new; - int ret = 0; - - ret = spi_nor_lock_and_prep(nor, SPI_NOR_OPS_LOCK); - if (ret) - return ret; - - status_old = read_sr(nor); - - if (offset < mtd->size - (mtd->size / 2)) - status_new = status_old | SR_BP2 | SR_BP1 | SR_BP0; - else if (offset < mtd->size - (mtd->size / 4)) - status_new = (status_old & ~SR_BP0) | SR_BP2 | SR_BP1; - else if (offset < mtd->size - (mtd->size / 8)) - status_new = (status_old & ~SR_BP1) | SR_BP2 | SR_BP0; - else if (offset < mtd->size - (mtd->size / 16)) - status_new = (status_old & ~(SR_BP0 | SR_BP1)) | SR_BP2; - else if (offset < mtd->size - (mtd->size / 32)) - status_new = (status_old & ~SR_BP2) | SR_BP1 | SR_BP0; - else if (offset < mtd->size - (mtd->size / 64)) - status_new = (status_old & ~(SR_BP2 | SR_BP0)) | SR_BP1; - else - status_new = (status_old & ~(SR_BP2 | SR_BP1)) | SR_BP0; - - /* Only modify protection if it will not unlock other areas */ - if ((status_new & (SR_BP2 | SR_BP1 | SR_BP0)) > - (status_old & (SR_BP2 | SR_BP1 | SR_BP0))) { - write_enable(nor); - ret = write_sr(nor, status_new); - if (ret) - goto err; - } - -err: - spi_nor_unlock_and_unprep(nor, SPI_NOR_OPS_LOCK); - return ret; + return 0; } static int spi_nor_unlock(struct mtd_info *mtd, loff_t ofs, size_t len) { struct spi_nor *nor = mtd_to_spi_nor(mtd); - uint32_t offset = ofs; - uint8_t status_old, status_new; - int ret = 0; + uint8_t status; + int ret; ret = spi_nor_lock_and_prep(nor, SPI_NOR_OPS_UNLOCK); if (ret) return ret; - status_old = read_sr(nor); + status = read_sr(nor); + status &= ~(SR_BP2 | SR_BP1 | SR_BP0); + write_enable(nor); - if (offset+len > mtd->size - (mtd->size / 64)) - status_new = status_old & ~(SR_BP2 | SR_BP1 | SR_BP0); - else if (offset+len > mtd->size - (mtd->size / 32)) - status_new = (status_old & ~(SR_BP2 | SR_BP1)) | SR_BP0; - else if (offset+len > mtd->size - (mtd->size / 16)) - status_new = (status_old & ~(SR_BP2 | SR_BP0)) | SR_BP1; - else if (offset+len > mtd->size - (mtd->size / 8)) - status_new = (status_old & ~SR_BP2) | SR_BP1 | SR_BP0; - else if (offset+len > mtd->size - (mtd->size / 4)) - status_new = (status_old & ~(SR_BP0 | SR_BP1)) | SR_BP2; - else if (offset+len > mtd->size - (mtd->size / 2)) - status_new = (status_old & ~SR_BP1) | SR_BP2 | SR_BP0; - else - status_new = (status_old & ~SR_BP0) | SR_BP2 | SR_BP1; + ret = write_sr(nor, status); - /* Only modify protection if it will not lock other areas */ - if ((status_new & (SR_BP2 | SR_BP1 | SR_BP0)) < - (status_old & (SR_BP2 | SR_BP1 | SR_BP0))) { - write_enable(nor); - ret = write_sr(nor, status_new); - if (ret) - goto err; - } - -err: spi_nor_unlock_and_unprep(nor, SPI_NOR_OPS_UNLOCK); + return ret; } @@ -928,7 +870,8 @@ return 0; } -int spi_nor_scan(struct spi_nor *nor, const char *name, enum read_mode mode) +int spi_nor_scan(struct spi_nor *nor, const char *name, enum read_mode mode, + bool use_large_blocks) { const struct spi_device_id *id = NULL; struct flash_info *info; @@ -1017,10 +960,10 @@ #ifdef CONFIG_MTD_SPI_NOR_USE_4K_SECTORS /* prefer "small sector" erase if possible */ - if (info->flags & SECT_4K) { + if (info->flags & SECT_4K && !use_large_blocks) { nor->erase_opcode = SPINOR_OP_BE_4K; mtd->erasesize = 4096; - } else if (info->flags & SECT_4K_PMC) { + } else if (info->flags & SECT_4K_PMC && !use_large_blocks) { nor->erase_opcode = SPINOR_OP_BE_4K_PMC; mtd->erasesize = 4096; } else diff --git a/drivers/spi/imx_spi.c b/drivers/spi/imx_spi.c index 6805d22..80dfc78 100644 --- a/drivers/spi/imx_spi.c +++ b/drivers/spi/imx_spi.c @@ -39,11 +39,18 @@ struct clk *clk; unsigned int (*xchg_single)(struct imx_spi *imx, u32 data); + void (*do_transfer)(struct spi_device *spi); void (*chipselect)(struct spi_device *spi, int active); + + const void *tx_buf; + void *rx_buf; + int xfer_len; + int bits_per_word; }; struct spi_imx_devtype_data { unsigned int (*xchg_single)(struct imx_spi *imx, u32 data); + void (*do_transfer)(struct spi_device *spi); void (*chipselect)(struct spi_device *spi, int active); void (*init)(struct imx_spi *imx); }; @@ -230,14 +237,8 @@ { void __iomem *base = imx->regs; - unsigned int cfg_reg = readl(base + CSPI_2_3_CTRL); - writel(data, base + CSPI_2_3_TXDATA); - cfg_reg |= CSPI_2_3_CTRL_XCH; - - writel(cfg_reg, base + CSPI_2_3_CTRL); - while (!(readl(base + CSPI_2_3_STAT) & CSPI_2_3_STAT_RR)); return readl(base + CSPI_2_3_RXDATA); @@ -306,6 +307,8 @@ ctrl |= (spi->bits_per_word - 1) << CSPI_2_3_CTRL_BL_OFFSET; + ctrl |= CSPI_2_3_CTRL_SMC; + cfg |= CSPI_2_3_CONFIG_SBBCTRL(cs); if (spi->mode & SPI_CPHA) @@ -336,38 +339,39 @@ return imx_spi_maybe_reverse_bits(spi, rx_val); } -static void imx_spi_do_transfer(struct spi_device *spi, struct spi_transfer *t) +static void imx_spi_do_transfer(struct spi_device *spi) { + struct imx_spi *imx = container_of(spi->master, struct imx_spi, master); unsigned i; - if (spi->bits_per_word <= 8) { - const u8 *tx_buf = t->tx_buf; - u8 *rx_buf = t->rx_buf; + if (imx->bits_per_word <= 8) { + const u8 *tx_buf = imx->tx_buf; + u8 *rx_buf = imx->rx_buf; u8 rx_val; - for (i = 0; i < t->len; i++) { + for (i = 0; i < imx->xfer_len; i++) { rx_val = imx_xchg_single(spi, tx_buf ? tx_buf[i] : 0); if (rx_buf) rx_buf[i] = rx_val; } - } else if (spi->bits_per_word <= 16) { - const u16 *tx_buf = t->tx_buf; - u16 *rx_buf = t->rx_buf; + } else if (imx->bits_per_word <= 16) { + const u16 *tx_buf = imx->tx_buf; + u16 *rx_buf = imx->rx_buf; u16 rx_val; - for (i = 0; i < t->len >> 1; i++) { + for (i = 0; i < imx->xfer_len >> 1; i++) { rx_val = imx_xchg_single(spi, tx_buf ? tx_buf[i] : 0); if (rx_buf) rx_buf[i] = rx_val; } - } else if (spi->bits_per_word <= 32) { - const u32 *tx_buf = t->tx_buf; - u32 *rx_buf = t->rx_buf; + } else if (imx->bits_per_word <= 32) { + const u32 *tx_buf = imx->tx_buf; + u32 *rx_buf = imx->rx_buf; u32 rx_val; - for (i = 0; i < t->len >> 2; i++) { + for (i = 0; i < imx->xfer_len >> 2; i++) { rx_val = imx_xchg_single(spi, tx_buf ? tx_buf[i] : 0); if (rx_buf) @@ -376,6 +380,77 @@ } } +static int cspi_2_3_xchg_burst(struct spi_device *spi) +{ + struct imx_spi *imx = container_of(spi->master, struct imx_spi, master); + int now, txlen, rxlen; + u32 ctrl; + void __iomem *base = imx->regs; + + now = min(imx->xfer_len, 512); + now >>= 2; + + if (!now) + return 0; + + txlen = rxlen = now; + + ctrl = readl(base + CSPI_2_3_CTRL); + ctrl &= ~(0xfff << CSPI_2_3_CTRL_BL_OFFSET); + ctrl |= ((txlen * 32) - 1) << CSPI_2_3_CTRL_BL_OFFSET; + ctrl |= 1 << 3; + writel(ctrl, base + CSPI_2_3_CTRL); + + while (txlen || rxlen) { + u32 status = readl(base + CSPI_2_3_STAT); + + if (txlen && !(status & CSPI_2_3_STAT_TF)) { + if (imx->tx_buf) { + u32 data = swab32(*(u32 *)imx->tx_buf); + writel(data, base + CSPI_2_3_TXDATA); + imx->tx_buf += sizeof(u32); + } else { + writel(0, base + CSPI_2_3_TXDATA); + } + txlen--; + } + + if (rxlen && (status & CSPI_2_3_STAT_RR)) { + u32 data = readl(base + CSPI_2_3_RXDATA); + + if (imx->rx_buf) { + *(u32 *)imx->rx_buf = swab32(data); + imx->rx_buf += sizeof(u32); + } + + rxlen--; + } + } + + imx->xfer_len -= now * 4; + + return now; +} + +static void cspi_2_3_do_transfer(struct spi_device *spi) +{ + struct imx_spi *imx = container_of(spi->master, struct imx_spi, master); + u32 ctrl; + + if (imx->bits_per_word == 8 || imx->bits_per_word == 16 || imx->bits_per_word == 32) + while (cspi_2_3_xchg_burst(spi) > 0); + + if (!imx->xfer_len) + return; + + ctrl = readl(imx->regs + CSPI_2_3_CTRL); + ctrl &= ~(0xfff << CSPI_2_3_CTRL_BL_OFFSET); + ctrl |= (spi->bits_per_word - 1) << CSPI_2_3_CTRL_BL_OFFSET; + writel(ctrl, imx->regs + CSPI_2_3_CTRL); + + imx_spi_do_transfer(spi); +} + static int imx_spi_transfer(struct spi_device *spi, struct spi_message *mesg) { struct imx_spi *imx = container_of(spi->master, struct imx_spi, master); @@ -399,7 +474,12 @@ cs_change = t->cs_change; - imx_spi_do_transfer(spi, t); + imx->tx_buf = t->tx_buf; + imx->rx_buf = t->rx_buf; + imx->xfer_len = t->len; + imx->bits_per_word = spi->bits_per_word; + imx->do_transfer(spi); + mesg->actual_length += t->len; if (cs_change) @@ -415,17 +495,20 @@ static __maybe_unused struct spi_imx_devtype_data spi_imx_devtype_data_0_0 = { .chipselect = cspi_0_0_chipselect, .xchg_single = cspi_0_0_xchg_single, + .do_transfer = imx_spi_do_transfer, .init = cspi_0_0_init, }; static __maybe_unused struct spi_imx_devtype_data spi_imx_devtype_data_0_7 = { .chipselect = cspi_0_7_chipselect, .xchg_single = cspi_0_7_xchg_single, + .do_transfer = imx_spi_do_transfer, .init = cspi_0_7_init, }; static __maybe_unused struct spi_imx_devtype_data spi_imx_devtype_data_2_3 = { .chipselect = cspi_2_3_chipselect, + .do_transfer = cspi_2_3_do_transfer, .xchg_single = cspi_2_3_xchg_single, }; @@ -490,6 +573,7 @@ imx->chipselect = devdata->chipselect; imx->xchg_single = devdata->xchg_single; + imx->do_transfer = devdata->do_transfer; imx->regs = dev_request_mem_region(dev, 0); if (devdata->init) diff --git a/include/linux/mtd/spi-nor.h b/include/linux/mtd/spi-nor.h index f099406..bd2b16d 100644 --- a/include/linux/mtd/spi-nor.h +++ b/include/linux/mtd/spi-nor.h @@ -190,6 +190,7 @@ * @nor: the spi_nor structure * @name: the chip type name * @mode: the read mode supported by the driver + * @use_large_blocks: prefer large blocks even if 4k blocks are supported * * The drivers can use this fuction to scan the SPI NOR. * In the scanning, it will try to get all the necessary information to @@ -199,6 +200,7 @@ * * Return: 0 for success, others for failure. */ -int spi_nor_scan(struct spi_nor *nor, const char *name, enum read_mode mode); +int spi_nor_scan(struct spi_nor *nor, const char *name, enum read_mode mode, + bool use_large_blocks); #endif diff --git a/include/spi/imx-spi.h b/include/spi/imx-spi.h index 560b092..221c665 100644 --- a/include/spi/imx-spi.h +++ b/include/spi/imx-spi.h @@ -61,6 +61,7 @@ #define CSPI_2_3_CTRL 0x08 #define CSPI_2_3_CTRL_ENABLE (1 << 0) #define CSPI_2_3_CTRL_XCH (1 << 2) +#define CSPI_2_3_CTRL_SMC (1 << 3) #define CSPI_2_3_CTRL_MODE(cs) (1 << ((cs) + 4)) #define CSPI_2_3_CTRL_POSTDIV_OFFSET 8 #define CSPI_2_3_CTRL_PREDIV_OFFSET 12 @@ -78,6 +79,7 @@ #define CSPI_2_3_INT_RREN (1 << 3) #define CSPI_2_3_STAT 0x18 -#define CSPI_2_3_STAT_RR (1 << 3) +#define CSPI_2_3_STAT_TF (1 << 2) +#define CSPI_2_3_STAT_RR (1 << 3) #endif /* __SPI_IMX_SPI_H */