summaryrefslogtreecommitdiff
path: root/drivers/mtd/spi-nor/spi-nor.c
diff options
context:
space:
mode:
authorBoris Brezillon <boris.brezillon@bootlin.com>2019-08-06 10:40:40 +0530
committerTudor Ambarus <tudor.ambarus@microchip.com>2019-08-12 10:54:12 +0300
commitb35b9a10362d203451d920d01ab8d6cd55cbaf2a (patch)
tree2fd177bda4ffe597cf2c826ccbbf54a1c7626ea6 /drivers/mtd/spi-nor/spi-nor.c
parentf173f26a4d543fa57e6ed9505bbbbf9bec61f544 (diff)
mtd: spi-nor: Move m25p80 code in spi-nor.c
The m25p80 driver is actually a generic wrapper around the spi-mem layer. Not only the driver name is misleading, but we'd expect such a common logic to be directly available in the core. Another reason for moving this code is that SPI NOR controller drivers should progressively be replaced by SPI controller drivers implementing the spi_mem_ops interface, and when the conversion is done, we should have a single spi-nor driver directly interfacing with the spi-mem layer. While moving the code we also fix a longstanding issue when non-DMA-able buffers are passed by the MTD layer. Signed-off-by: Boris Brezillon <boris.brezillon@bootlin.com> Signed-off-by: Vignesh Raghavendra <vigneshr@ti.com> Signed-off-by: Tudor Ambarus <tudor.ambarus@microchip.com>
Diffstat (limited to 'drivers/mtd/spi-nor/spi-nor.c')
-rw-r--r--drivers/mtd/spi-nor/spi-nor.c628
1 files changed, 600 insertions, 28 deletions
diff --git a/drivers/mtd/spi-nor/spi-nor.c b/drivers/mtd/spi-nor/spi-nor.c
index 7f0831be90a0..de906ab907c7 100644
--- a/drivers/mtd/spi-nor/spi-nor.c
+++ b/drivers/mtd/spi-nor/spi-nor.c
@@ -19,6 +19,7 @@
#include <linux/mtd/mtd.h>
#include <linux/of_platform.h>
+#include <linux/sched/task_stack.h>
#include <linux/spi/flash.h>
#include <linux/mtd/spi-nor.h>
@@ -288,6 +289,154 @@ struct flash_info {
#define JEDEC_MFR(info) ((info)->id[0])
+/**
+ * spi_nor_spimem_xfer_data() - helper function to read/write data to
+ * flash's memory region
+ * @nor: pointer to 'struct spi_nor'
+ * @op: pointer to 'struct spi_mem_op' template for transfer
+ *
+ * Return: number of bytes transferred on success, -errno otherwise
+ */
+static ssize_t spi_nor_spimem_xfer_data(struct spi_nor *nor,
+ struct spi_mem_op *op)
+{
+ bool usebouncebuf = false;
+ void *rdbuf = NULL;
+ const void *buf;
+ int ret;
+
+ if (op->data.dir == SPI_MEM_DATA_IN)
+ buf = op->data.buf.in;
+ else
+ buf = op->data.buf.out;
+
+ if (object_is_on_stack(buf) || !virt_addr_valid(buf))
+ usebouncebuf = true;
+
+ if (usebouncebuf) {
+ if (op->data.nbytes > nor->bouncebuf_size)
+ op->data.nbytes = nor->bouncebuf_size;
+
+ if (op->data.dir == SPI_MEM_DATA_IN) {
+ rdbuf = op->data.buf.in;
+ op->data.buf.in = nor->bouncebuf;
+ } else {
+ op->data.buf.out = nor->bouncebuf;
+ memcpy(nor->bouncebuf, buf,
+ op->data.nbytes);
+ }
+ }
+
+ ret = spi_mem_adjust_op_size(nor->spimem, op);
+ if (ret)
+ return ret;
+
+ ret = spi_mem_exec_op(nor->spimem, op);
+ if (ret)
+ return ret;
+
+ if (usebouncebuf && op->data.dir == SPI_MEM_DATA_IN)
+ memcpy(rdbuf, nor->bouncebuf, op->data.nbytes);
+
+ return op->data.nbytes;
+}
+
+/**
+ * spi_nor_spimem_read_data() - read data from flash's memory region via
+ * spi-mem
+ * @nor: pointer to 'struct spi_nor'
+ * @from: offset to read from
+ * @len: number of bytes to read
+ * @buf: pointer to dst buffer
+ *
+ * Return: number of bytes read successfully, -errno otherwise
+ */
+static ssize_t spi_nor_spimem_read_data(struct spi_nor *nor, loff_t from,
+ size_t len, u8 *buf)
+{
+ struct spi_mem_op op =
+ SPI_MEM_OP(SPI_MEM_OP_CMD(nor->read_opcode, 1),
+ SPI_MEM_OP_ADDR(nor->addr_width, from, 1),
+ SPI_MEM_OP_DUMMY(nor->read_dummy, 1),
+ SPI_MEM_OP_DATA_IN(len, buf, 1));
+
+ /* 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;
+
+ return spi_nor_spimem_xfer_data(nor, &op);
+}
+
+/**
+ * spi_nor_read_data() - read data from flash memory
+ * @nor: pointer to 'struct spi_nor'
+ * @from: offset to read from
+ * @len: number of bytes to read
+ * @buf: pointer to dst buffer
+ *
+ * Return: number of bytes read successfully, -errno otherwise
+ */
+static ssize_t spi_nor_read_data(struct spi_nor *nor, loff_t from, size_t len,
+ u8 *buf)
+{
+ if (nor->spimem)
+ return spi_nor_spimem_read_data(nor, from, len, buf);
+
+ return nor->read(nor, from, len, buf);
+}
+
+/**
+ * spi_nor_spimem_write_data() - write data to flash memory via
+ * spi-mem
+ * @nor: pointer to 'struct spi_nor'
+ * @to: offset to write to
+ * @len: number of bytes to write
+ * @buf: pointer to src buffer
+ *
+ * Return: number of bytes written successfully, -errno otherwise
+ */
+static ssize_t spi_nor_spimem_write_data(struct spi_nor *nor, loff_t to,
+ size_t len, const u8 *buf)
+{
+ struct spi_mem_op op =
+ SPI_MEM_OP(SPI_MEM_OP_CMD(nor->program_opcode, 1),
+ SPI_MEM_OP_ADDR(nor->addr_width, to, 1),
+ SPI_MEM_OP_NO_DUMMY,
+ SPI_MEM_OP_DATA_OUT(len, buf, 1));
+
+ 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.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;
+
+ return spi_nor_spimem_xfer_data(nor, &op);
+}
+
+/**
+ * spi_nor_write_data() - write data to flash memory
+ * @nor: pointer to 'struct spi_nor'
+ * @to: offset to write to
+ * @len: number of bytes to write
+ * @buf: pointer to src buffer
+ *
+ * Return: number of bytes written successfully, -errno otherwise
+ */
+static ssize_t spi_nor_write_data(struct spi_nor *nor, loff_t to, size_t len,
+ const u8 *buf)
+{
+ if (nor->spimem)
+ return spi_nor_spimem_write_data(nor, to, len, buf);
+
+ return nor->write(nor, to, len, buf);
+}
+
/*
* Read the status register, returning its value in the location
* Return the status register value.
@@ -297,7 +446,18 @@ static int read_sr(struct spi_nor *nor)
{
int ret;
- ret = nor->read_reg(nor, SPINOR_OP_RDSR, nor->bouncebuf, 1);
+ if (nor->spimem) {
+ struct spi_mem_op op =
+ SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_RDSR, 1),
+ SPI_MEM_OP_NO_ADDR,
+ SPI_MEM_OP_NO_DUMMY,
+ SPI_MEM_OP_DATA_IN(1, nor->bouncebuf, 1));
+
+ ret = spi_mem_exec_op(nor->spimem, &op);
+ } else {
+ ret = nor->read_reg(nor, SPINOR_OP_RDSR, nor->bouncebuf, 1);
+ }
+
if (ret < 0) {
pr_err("error %d reading SR\n", (int) ret);
return ret;
@@ -315,7 +475,18 @@ static int read_fsr(struct spi_nor *nor)
{
int ret;
- ret = nor->read_reg(nor, SPINOR_OP_RDFSR, nor->bouncebuf, 1);
+ if (nor->spimem) {
+ struct spi_mem_op op =
+ SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_RDFSR, 1),
+ SPI_MEM_OP_NO_ADDR,
+ SPI_MEM_OP_NO_DUMMY,
+ SPI_MEM_OP_DATA_IN(1, nor->bouncebuf, 1));
+
+ ret = spi_mem_exec_op(nor->spimem, &op);
+ } else {
+ ret = nor->read_reg(nor, SPINOR_OP_RDFSR, nor->bouncebuf, 1);
+ }
+
if (ret < 0) {
pr_err("error %d reading FSR\n", ret);
return ret;
@@ -333,7 +504,18 @@ static int read_cr(struct spi_nor *nor)
{
int ret;
- ret = nor->read_reg(nor, SPINOR_OP_RDCR, nor->bouncebuf, 1);
+ if (nor->spimem) {
+ struct spi_mem_op op =
+ SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_RDCR, 1),
+ SPI_MEM_OP_NO_ADDR,
+ SPI_MEM_OP_NO_DUMMY,
+ SPI_MEM_OP_DATA_IN(1, nor->bouncebuf, 1));
+
+ ret = spi_mem_exec_op(nor->spimem, &op);
+ } else {
+ ret = nor->read_reg(nor, SPINOR_OP_RDCR, nor->bouncebuf, 1);
+ }
+
if (ret < 0) {
dev_err(nor->dev, "error %d reading CR\n", ret);
return ret;
@@ -349,6 +531,16 @@ static int read_cr(struct spi_nor *nor)
static int write_sr(struct spi_nor *nor, u8 val)
{
nor->bouncebuf[0] = val;
+ if (nor->spimem) {
+ struct spi_mem_op op =
+ SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_WRSR, 1),
+ SPI_MEM_OP_NO_ADDR,
+ SPI_MEM_OP_NO_DUMMY,
+ SPI_MEM_OP_DATA_IN(1, nor->bouncebuf, 1));
+
+ return spi_mem_exec_op(nor->spimem, &op);
+ }
+
return nor->write_reg(nor, SPINOR_OP_WRSR, nor->bouncebuf, 1);
}
@@ -358,6 +550,16 @@ static int write_sr(struct spi_nor *nor, u8 val)
*/
static int write_enable(struct spi_nor *nor)
{
+ if (nor->spimem) {
+ struct spi_mem_op op =
+ SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_WREN, 1),
+ SPI_MEM_OP_NO_ADDR,
+ SPI_MEM_OP_NO_DUMMY,
+ SPI_MEM_OP_NO_DATA);
+
+ return spi_mem_exec_op(nor->spimem, &op);
+ }
+
return nor->write_reg(nor, SPINOR_OP_WREN, NULL, 0);
}
@@ -366,6 +568,16 @@ static int write_enable(struct spi_nor *nor)
*/
static int write_disable(struct spi_nor *nor)
{
+ if (nor->spimem) {
+ struct spi_mem_op op =
+ SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_WRDI, 1),
+ SPI_MEM_OP_NO_ADDR,
+ SPI_MEM_OP_NO_DUMMY,
+ SPI_MEM_OP_NO_DATA);
+
+ return spi_mem_exec_op(nor->spimem, &op);
+ }
+
return nor->write_reg(nor, SPINOR_OP_WRDI, NULL, 0);
}
@@ -465,12 +677,64 @@ static void spi_nor_set_4byte_opcodes(struct spi_nor *nor)
}
}
+static int macronix_set_4byte(struct spi_nor *nor, bool enable)
+{
+ if (nor->spimem) {
+ struct spi_mem_op op =
+ SPI_MEM_OP(SPI_MEM_OP_CMD(enable ?
+ SPINOR_OP_EN4B :
+ SPINOR_OP_EX4B,
+ 1),
+ SPI_MEM_OP_NO_ADDR,
+ SPI_MEM_OP_NO_DUMMY,
+ SPI_MEM_OP_NO_DATA);
+
+ return spi_mem_exec_op(nor->spimem, &op);
+ }
+
+ return nor->write_reg(nor, enable ? SPINOR_OP_EN4B : SPINOR_OP_EX4B,
+ NULL, 0);
+}
+
+static int spansion_set_4byte(struct spi_nor *nor, bool enable)
+{
+ nor->bouncebuf[0] = enable << 7;
+
+ if (nor->spimem) {
+ struct spi_mem_op op =
+ SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_BRWR, 1),
+ SPI_MEM_OP_NO_ADDR,
+ SPI_MEM_OP_NO_DUMMY,
+ SPI_MEM_OP_DATA_OUT(1, nor->bouncebuf, 1));
+
+ return spi_mem_exec_op(nor->spimem, &op);
+ }
+
+ return nor->write_reg(nor, SPINOR_OP_BRWR, nor->bouncebuf, 1);
+}
+
+static int spi_nor_write_ear(struct spi_nor *nor, u8 ear)
+{
+ nor->bouncebuf[0] = ear;
+
+ if (nor->spimem) {
+ struct spi_mem_op op =
+ SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_WREAR, 1),
+ SPI_MEM_OP_NO_ADDR,
+ SPI_MEM_OP_NO_DUMMY,
+ SPI_MEM_OP_DATA_OUT(1, nor->bouncebuf, 1));
+
+ return spi_mem_exec_op(nor->spimem, &op);
+ }
+
+ return nor->write_reg(nor, SPINOR_OP_WREAR, nor->bouncebuf, 1);
+}
+
/* Enable/disable 4-byte addressing mode. */
static int set_4byte(struct spi_nor *nor, bool enable)
{
int status;
bool need_wren = false;
- u8 cmd;
switch (JEDEC_MFR(nor->info)) {
case SNOR_MFR_ST:
@@ -483,8 +747,7 @@ static int set_4byte(struct spi_nor *nor, bool enable)
if (need_wren)
write_enable(nor);
- cmd = enable ? SPINOR_OP_EN4B : SPINOR_OP_EX4B;
- status = nor->write_reg(nor, cmd, NULL, 0);
+ status = macronix_set_4byte(nor, enable);
if (need_wren)
write_disable(nor);
@@ -497,25 +760,37 @@ static int set_4byte(struct spi_nor *nor, bool enable)
* We must clear the register to enable normal behavior.
*/
write_enable(nor);
- nor->bouncebuf[0] = 0;
- nor->write_reg(nor, SPINOR_OP_WREAR,
- nor->bouncebuf, 1);
+ spi_nor_write_ear(nor, 0);
write_disable(nor);
}
return status;
default:
/* Spansion style */
- nor->bouncebuf[0] = enable << 7;
- return nor->write_reg(nor, SPINOR_OP_BRWR, nor->bouncebuf, 1);
+ return spansion_set_4byte(nor, enable);
}
}
+static int spi_nor_xread_sr(struct spi_nor *nor, u8 *sr)
+{
+ if (nor->spimem) {
+ struct spi_mem_op op =
+ SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_XRDSR, 1),
+ SPI_MEM_OP_NO_ADDR,
+ SPI_MEM_OP_NO_DUMMY,
+ SPI_MEM_OP_DATA_IN(1, sr, 1));
+
+ return spi_mem_exec_op(nor->spimem, &op);
+ }
+
+ return nor->read_reg(nor, SPINOR_OP_XRDSR, sr, 1);
+}
+
static int s3an_sr_ready(struct spi_nor *nor)
{
int ret;
- ret = nor->read_reg(nor, SPINOR_OP_XRDSR, nor->bouncebuf, 1);
+ ret = spi_nor_xread_sr(nor, nor->bouncebuf);
if (ret < 0) {
dev_err(nor->dev, "error %d reading XRDSR\n", (int) ret);
return ret;
@@ -524,6 +799,21 @@ static int s3an_sr_ready(struct spi_nor *nor)
return !!(nor->bouncebuf[0] & XSR_RDY);
}
+static int spi_nor_clear_sr(struct spi_nor *nor)
+{
+ if (nor->spimem) {
+ struct spi_mem_op op =
+ SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_CLSR, 1),
+ SPI_MEM_OP_NO_ADDR,
+ SPI_MEM_OP_NO_DUMMY,
+ SPI_MEM_OP_NO_DATA);
+
+ return spi_mem_exec_op(nor->spimem, &op);
+ }
+
+ return nor->write_reg(nor, SPINOR_OP_CLSR, NULL, 0);
+}
+
static int spi_nor_sr_ready(struct spi_nor *nor)
{
int sr = read_sr(nor);
@@ -536,13 +826,28 @@ static int spi_nor_sr_ready(struct spi_nor *nor)
else
dev_err(nor->dev, "Programming Error occurred\n");
- nor->write_reg(nor, SPINOR_OP_CLSR, NULL, 0);
+ spi_nor_clear_sr(nor);
return -EIO;
}
return !(sr & SR_WIP);
}
+static int spi_nor_clear_fsr(struct spi_nor *nor)
+{
+ if (nor->spimem) {
+ struct spi_mem_op op =
+ SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_CLFSR, 1),
+ SPI_MEM_OP_NO_ADDR,
+ SPI_MEM_OP_NO_DUMMY,
+ SPI_MEM_OP_NO_DATA);
+
+ return spi_mem_exec_op(nor->spimem, &op);
+ }
+
+ return nor->write_reg(nor, SPINOR_OP_CLFSR, NULL, 0);
+}
+
static int spi_nor_fsr_ready(struct spi_nor *nor)
{
int fsr = read_fsr(nor);
@@ -559,7 +864,7 @@ static int spi_nor_fsr_ready(struct spi_nor *nor)
dev_err(nor->dev,
"Attempted to modify a protected sector.\n");
- nor->write_reg(nor, SPINOR_OP_CLFSR, NULL, 0);
+ spi_nor_clear_fsr(nor);
return -EIO;
}
@@ -627,6 +932,16 @@ static int erase_chip(struct spi_nor *nor)
{
dev_dbg(nor->dev, " %lldKiB\n", (long long)(nor->mtd.size >> 10));
+ if (nor->spimem) {
+ struct spi_mem_op op =
+ SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_CHIP_ERASE, 1),
+ SPI_MEM_OP_NO_ADDR,
+ SPI_MEM_OP_NO_DUMMY,
+ SPI_MEM_OP_NO_DATA);
+
+ return spi_mem_exec_op(nor->spimem, &op);
+ }
+
return nor->write_reg(nor, SPINOR_OP_CHIP_ERASE, NULL, 0);
}
@@ -688,6 +1003,16 @@ static int spi_nor_erase_sector(struct spi_nor *nor, u32 addr)
if (nor->erase)
return nor->erase(nor, addr);
+ if (nor->spimem) {
+ struct spi_mem_op op =
+ SPI_MEM_OP(SPI_MEM_OP_CMD(nor->erase_opcode, 1),
+ SPI_MEM_OP_ADDR(nor->addr_width, addr, 1),
+ SPI_MEM_OP_NO_DUMMY,
+ SPI_MEM_OP_NO_DATA);
+
+ return spi_mem_exec_op(nor->spimem, &op);
+ }
+
/*
* Default implementation, if driver doesn't have a specialized HW
* control
@@ -1403,7 +1728,18 @@ static int write_sr_cr(struct spi_nor *nor, u8 *sr_cr)
write_enable(nor);
- ret = nor->write_reg(nor, SPINOR_OP_WRSR, sr_cr, 2);
+ if (nor->spimem) {
+ struct spi_mem_op op =
+ SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_WRSR, 1),
+ SPI_MEM_OP_NO_ADDR,
+ SPI_MEM_OP_NO_DUMMY,
+ SPI_MEM_OP_DATA_OUT(2, sr_cr, 1));
+
+ ret = spi_mem_exec_op(nor->spimem, &op);
+ } else {
+ ret = nor->write_reg(nor, SPINOR_OP_WRSR, sr_cr, 2);
+ }
+
if (ret < 0) {
dev_err(nor->dev,
"error while writing configuration register\n");
@@ -1584,6 +1920,36 @@ static int spansion_read_cr_quad_enable(struct spi_nor *nor)
return 0;
}
+static int spi_nor_write_sr2(struct spi_nor *nor, u8 *sr2)
+{
+ if (nor->spimem) {
+ struct spi_mem_op op =
+ SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_WRSR2, 1),
+ SPI_MEM_OP_NO_ADDR,
+ SPI_MEM_OP_NO_DUMMY,
+ SPI_MEM_OP_DATA_OUT(1, sr2, 1));
+
+ return spi_mem_exec_op(nor->spimem, &op);
+ }
+
+ return nor->write_reg(nor, SPINOR_OP_WRSR2, sr2, 1);
+}
+
+static int spi_nor_read_sr2(struct spi_nor *nor, u8 *sr2)
+{
+ if (nor->spimem) {
+ struct spi_mem_op op =
+ SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_RDSR2, 1),
+ SPI_MEM_OP_NO_ADDR,
+ SPI_MEM_OP_NO_DUMMY,
+ SPI_MEM_OP_DATA_IN(1, sr2, 1));
+
+ return spi_mem_exec_op(nor->spimem, &op);
+ }
+
+ return nor->read_reg(nor, SPINOR_OP_RDSR2, sr2, 1);
+}
+
/**
* sr2_bit7_quad_enable() - set QE bit in Status Register 2.
* @nor: pointer to a 'struct spi_nor'
@@ -1602,7 +1968,7 @@ static int sr2_bit7_quad_enable(struct spi_nor *nor)
int ret;
/* Check current Quad Enable bit value. */
- ret = nor->read_reg(nor, SPINOR_OP_RDSR2, sr2, 1);
+ ret = spi_nor_read_sr2(nor, sr2);
if (ret)
return ret;
if (*sr2 & SR2_QUAD_EN_BIT7)
@@ -1613,7 +1979,7 @@ static int sr2_bit7_quad_enable(struct spi_nor *nor)
write_enable(nor);
- ret = nor->write_reg(nor, SPINOR_OP_WRSR2, sr2, 1);
+ ret = spi_nor_write_sr2(nor, sr2);
if (ret < 0) {
dev_err(nor->dev, "error while writing status register 2\n");
return -EINVAL;
@@ -1626,7 +1992,7 @@ static int sr2_bit7_quad_enable(struct spi_nor *nor)
}
/* Read back and check it. */
- ret = nor->read_reg(nor, SPINOR_OP_RDSR2, sr2, 1);
+ ret = spi_nor_read_sr2(nor, sr2);
if (!(ret > 0 && (*sr2 & SR2_QUAD_EN_BIT7))) {
dev_err(nor->dev, "SR2 Quad bit not set\n");
return -EINVAL;
@@ -2179,7 +2545,18 @@ static const struct flash_info *spi_nor_read_id(struct spi_nor *nor)
u8 *id = nor->bouncebuf;
const struct flash_info *info;
- tmp = nor->read_reg(nor, SPINOR_OP_RDID, id, SPI_NOR_MAX_ID_LEN);
+ if (nor->spimem) {
+ struct spi_mem_op op =
+ SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_RDID, 1),
+ SPI_MEM_OP_NO_ADDR,
+ SPI_MEM_OP_NO_DUMMY,
+ SPI_MEM_OP_DATA_IN(SPI_NOR_MAX_ID_LEN, id, 1));
+
+ tmp = spi_mem_exec_op(nor->spimem, &op);
+ } else {
+ tmp = nor->read_reg(nor, SPINOR_OP_RDID, id,
+ SPI_NOR_MAX_ID_LEN);
+ }
if (tmp < 0) {
dev_err(nor->dev, "error %d reading JEDEC ID\n", tmp);
return ERR_PTR(tmp);
@@ -2215,7 +2592,7 @@ static int spi_nor_read(struct mtd_info *mtd, loff_t from, size_t len,
if (nor->flags & SNOR_F_S3AN_ADDR_DEFAULT)
addr = spi_nor_s3an_addr_convert(nor, addr);
- ret = nor->read(nor, addr, len, buf);
+ ret = spi_nor_read_data(nor, addr, len, buf);
if (ret == 0) {
/* We shouldn't see 0-length reads */
ret = -EIO;
@@ -2260,7 +2637,7 @@ static int sst_write(struct mtd_info *mtd, loff_t to, size_t len,
nor->program_opcode = SPINOR_OP_BP;
/* write one byte. */
- ret = nor->write(nor, to, 1, buf);
+ ret = spi_nor_write_data(nor, to, 1, buf);
if (ret < 0)
goto sst_write_err;
WARN(ret != 1, "While writing 1 byte written %i bytes\n",
@@ -2276,7 +2653,7 @@ static int sst_write(struct mtd_info *mtd, loff_t to, size_t len,
nor->program_opcode = SPINOR_OP_AAI_WP;
/* write two bytes. */
- ret = nor->write(nor, to, 2, buf + actual);
+ ret = spi_nor_write_data(nor, to, 2, buf + actual);
if (ret < 0)
goto sst_write_err;
WARN(ret != 2, "While writing 2 bytes written %i bytes\n",
@@ -2299,7 +2676,7 @@ static int sst_write(struct mtd_info *mtd, loff_t to, size_t len,
write_enable(nor);
nor->program_opcode = SPINOR_OP_BP;
- ret = nor->write(nor, to, 1, buf + actual);
+ ret = spi_nor_write_data(nor, to, 1, buf + actual);
if (ret < 0)
goto sst_write_err;
WARN(ret != 1, "While writing 1 byte written %i bytes\n",
@@ -2361,7 +2738,7 @@ static int spi_nor_write(struct mtd_info *mtd, loff_t to, size_t len,
addr = spi_nor_s3an_addr_convert(nor, addr);
write_enable(nor);
- ret = nor->write(nor, addr, page_remain, buf + i);
+ ret = spi_nor_write_data(nor, addr, page_remain, buf + i);
if (ret < 0)
goto write_err;
written = ret;
@@ -2380,8 +2757,10 @@ write_err:
static int spi_nor_check(struct spi_nor *nor)
{
- if (!nor->dev || !nor->read || !nor->write ||
- !nor->read_reg || !nor->write_reg) {
+ if (!nor->dev ||
+ (!nor->spimem &&
+ (!nor->read || !nor->write || !nor->read_reg ||
+ !nor->write_reg))) {
pr_err("spi-nor: please fill all the necessary fields!\n");
return -EINVAL;
}
@@ -2393,7 +2772,7 @@ static int s3an_nor_scan(struct spi_nor *nor)
{
int ret;
- ret = nor->read_reg(nor, SPINOR_OP_XRDSR, nor->bouncebuf, 1);
+ ret = spi_nor_xread_sr(nor, nor->bouncebuf);
if (ret < 0) {
dev_err(nor->dev, "error %d reading XRDSR\n", (int) ret);
return ret;
@@ -2523,7 +2902,7 @@ static int spi_nor_read_raw(struct spi_nor *nor, u32 addr, size_t len, u8 *buf)
int ret;
while (len) {
- ret = nor->read(nor, addr, len, buf);
+ ret = spi_nor_read_data(nor, addr, len, buf);
if (!ret || ret > len)
return -EIO;
if (ret < 0)
@@ -4122,6 +4501,10 @@ int spi_nor_scan(struct spi_nor *nor, const char *name,
/*
* We need the bounce buffer early to read/write registers when going
* through the spi-mem layer (buffers have to be DMA-able).
+ * For spi-mem drivers, we'll reallocate a new buffer if
+ * nor->page_size turns out to be greater than PAGE_SIZE (which
+ * shouldn't happen before long since NOR pages are usually less
+ * than 1KB) after spi_nor_scan() returns.
*/
nor->bouncebuf_size = PAGE_SIZE;
nor->bouncebuf = devm_kmalloc(dev, nor->bouncebuf_size,
@@ -4324,6 +4707,195 @@ int spi_nor_scan(struct spi_nor *nor, const char *name,
}
EXPORT_SYMBOL_GPL(spi_nor_scan);
+static int spi_nor_probe(struct spi_mem *spimem)
+{
+ struct spi_device *spi = spimem->spi;
+ struct flash_platform_data *data = dev_get_platdata(&spi->dev);
+ struct spi_nor *nor;
+ struct spi_nor_hwcaps hwcaps = {
+ .mask = SNOR_HWCAPS_READ |
+ SNOR_HWCAPS_READ_FAST |
+ SNOR_HWCAPS_PP,
+ };
+ char *flash_name;
+ int ret;
+
+ nor = devm_kzalloc(&spi->dev, sizeof(*nor), GFP_KERNEL);
+ if (!nor)
+ return -ENOMEM;
+
+ nor->spimem = spimem;
+ nor->dev = &spi->dev;
+ spi_nor_set_flash_node(nor, spi->dev.of_node);
+
+ spi_mem_set_drvdata(spimem, nor);
+
+ if (spi->mode & SPI_RX_OCTAL) {
+ hwcaps.mask |= SNOR_HWCAPS_READ_1_1_8;
+
+ if (spi->mode & SPI_TX_OCTAL)
+ hwcaps.mask |= (SNOR_HWCAPS_READ_1_8_8 |
+ SNOR_HWCAPS_PP_1_1_8 |
+ SNOR_HWCAPS_PP_1_8_8);
+ } else if (spi->mode & SPI_RX_QUAD) {
+ hwcaps.mask |= SNOR_HWCAPS_READ_1_1_4;
+
+ if (spi->mode & SPI_TX_QUAD)
+ hwcaps.mask |= (SNOR_HWCAPS_READ_1_4_4 |
+ SNOR_HWCAPS_PP_1_1_4 |
+ SNOR_HWCAPS_PP_1_4_4);
+ } else if (spi->mode & SPI_RX_DUAL) {
+ hwcaps.mask |= SNOR_HWCAPS_READ_1_1_2;
+
+ if (spi->mode & SPI_TX_DUAL)
+ hwcaps.mask |= SNOR_HWCAPS_READ_1_2_2;
+ }
+
+ if (data && data->name)
+ nor->mtd.name = data->name;
+
+ if (!nor->mtd.name)
+ nor->mtd.name = spi_mem_get_name(spimem);
+
+ /*
+ * For some (historical?) reason many platforms provide two different
+ * names in flash_platform_data: "name" and "type". Quite often name is
+ * set to "m25p80" and then "type" provides a real chip name.
+ * If that's the case, respect "type" and ignore a "name".
+ */
+ if (data && data->type)
+ flash_name = data->type;
+ else if (!strcmp(spi->modalias, "spi-nor"))
+ flash_name = NULL; /* auto-detect */
+ else
+ flash_name = spi->modalias;
+
+ ret = spi_nor_scan(nor, flash_name, &hwcaps);
+ if (ret)
+ return ret;
+
+ /*
+ * None of the existing parts have > 512B pages, but let's play safe
+ * and add this logic so that if anyone ever adds support for such
+ * a NOR we don't end up with buffer overflows.
+ */
+ if (nor->page_size > PAGE_SIZE) {
+ nor->bouncebuf_size = nor->page_size;
+ devm_kfree(nor->dev, nor->bouncebuf);
+ nor->bouncebuf = devm_kmalloc(nor->dev,
+ nor->bouncebuf_size,
+ GFP_KERNEL);
+ if (!nor->bouncebuf)
+ return -ENOMEM;
+ }
+
+ return mtd_device_register(&nor->mtd, data ? data->parts : NULL,
+ data ? data->nr_parts : 0);
+}
+
+static int spi_nor_remove(struct spi_mem *spimem)
+{
+ struct spi_nor *nor = spi_mem_get_drvdata(spimem);
+
+ spi_nor_restore(nor);
+
+ /* Clean up MTD stuff. */
+ return mtd_device_unregister(&nor->mtd);
+}
+
+static void spi_nor_shutdown(struct spi_mem *spimem)
+{
+ struct spi_nor *nor = spi_mem_get_drvdata(spimem);
+
+ spi_nor_restore(nor);
+}
+
+/*
+ * Do NOT add to this array without reading the following:
+ *
+ * Historically, many flash devices are bound to this driver by their name. But
+ * since most of these flash are compatible to some extent, and their
+ * differences can often be differentiated by the JEDEC read-ID command, we
+ * encourage new users to add support to the spi-nor library, and simply bind
+ * against a generic string here (e.g., "jedec,spi-nor").
+ *
+ * Many flash names are kept here in this list (as well as in spi-nor.c) to
+ * keep them available as module aliases for existing platforms.
+ */
+static const struct spi_device_id spi_nor_dev_ids[] = {
+ /*
+ * Allow non-DT platform devices to bind to the "spi-nor" modalias, and
+ * hack around the fact that the SPI core does not provide uevent
+ * matching for .of_match_table
+ */
+ {"spi-nor"},
+
+ /*
+ * Entries not used in DTs that should be safe to drop after replacing
+ * them with "spi-nor" in platform data.
+ */
+ {"s25sl064a"}, {"w25x16"}, {"m25p10"}, {"m25px64"},
+
+ /*
+ * Entries that were used in DTs without "jedec,spi-nor" fallback and
+ * should be kept for backward compatibility.
+ */
+ {"at25df321a"}, {"at25df641"}, {"at26df081a"},
+ {"mx25l4005a"}, {"mx25l1606e"}, {"mx25l6405d"}, {"mx25l12805d"},
+ {"mx25l25635e"},{"mx66l51235l"},
+ {"n25q064"}, {"n25q128a11"}, {"n25q128a13"}, {"n25q512a"},
+ {"s25fl256s1"}, {"s25fl512s"}, {"s25sl12801"}, {"s25fl008k"},
+ {"s25fl064k"},
+ {"sst25vf040b"},{"sst25vf016b"},{"sst25vf032b"},{"sst25wf040"},
+ {"m25p40"}, {"m25p80"}, {"m25p16"}, {"m25p32"},
+ {"m25p64"}, {"m25p128"},
+ {"w25x80"}, {"w25x32"}, {"w25q32"}, {"w25q32dw"},
+ {"w25q80bl"}, {"w25q128"}, {"w25q256"},
+
+ /* Flashes that can't be detected using JEDEC */
+ {"m25p05-nonjedec"}, {"m25p10-nonjedec"}, {"m25p20-nonjedec"},
+ {"m25p40-nonjedec"}, {"m25p80-nonjedec"}, {"m25p16-nonjedec"},
+ {"m25p32-nonjedec"}, {"m25p64-nonjedec"}, {"m25p128-nonjedec"},
+
+ /* Everspin MRAMs (non-JEDEC) */
+ { "mr25h128" }, /* 128 Kib, 40 MHz */
+ { "mr25h256" }, /* 256 Kib, 40 MHz */
+ { "mr25h10" }, /* 1 Mib, 40 MHz */
+ { "mr25h40" }, /* 4 Mib, 40 MHz */
+
+ { },
+};
+MODULE_DEVICE_TABLE(spi, spi_nor_dev_ids);
+
+static const struct of_device_id spi_nor_of_table[] = {
+ /*
+ * Generic compatibility for SPI NOR that can be identified by the
+ * JEDEC READ ID opcode (0x9F). Use this, if possible.
+ */
+ { .compatible = "jedec,spi-nor" },
+ { /* sentinel */ },
+};
+MODULE_DEVICE_TABLE(of, spi_nor_of_table);
+
+/*
+ * REVISIT: many of these chips have deep power-down modes, which
+ * should clearly be entered on suspend() to minimize power use.
+ * And also when they're otherwise idle...
+ */
+static struct spi_mem_driver spi_nor_driver = {
+ .spidrv = {
+ .driver = {
+ .name = "spi-nor",
+ .of_match_table = spi_nor_of_table,
+ },
+ .id_table = spi_nor_dev_ids,
+ },
+ .probe = spi_nor_probe,
+ .remove = spi_nor_remove,
+ .shutdown = spi_nor_shutdown,
+};
+module_spi_mem_driver(spi_nor_driver);
+
MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("Huang Shijie <shijie8@gmail.com>");
MODULE_AUTHOR("Mike Lavender");