]> asedeno.scripts.mit.edu Git - linux.git/blobdiff - drivers/mtd/spi-nor/spi-nor.c
Merge tag 'v5.3-rc6' into spi-nor/next
[linux.git] / drivers / mtd / spi-nor / spi-nor.c
index 654bdc41fc99e4aee9a2ce3b0472769c0706f079..0597cb8257b04352267380a17d03e8b0375af292 100644 (file)
@@ -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.
@@ -296,15 +445,25 @@ struct flash_info {
 static int read_sr(struct spi_nor *nor)
 {
        int ret;
-       u8 val;
 
-       ret = nor->read_reg(nor, SPINOR_OP_RDSR, &val, 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;
        }
 
-       return val;
+       return nor->bouncebuf[0];
 }
 
 /*
@@ -315,15 +474,25 @@ static int read_sr(struct spi_nor *nor)
 static int read_fsr(struct spi_nor *nor)
 {
        int ret;
-       u8 val;
 
-       ret = nor->read_reg(nor, SPINOR_OP_RDFSR, &val, 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;
        }
 
-       return val;
+       return nor->bouncebuf[0];
 }
 
 /*
@@ -334,15 +503,25 @@ static int read_fsr(struct spi_nor *nor)
 static int read_cr(struct spi_nor *nor)
 {
        int ret;
-       u8 val;
 
-       ret = nor->read_reg(nor, SPINOR_OP_RDCR, &val, 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;
        }
 
-       return val;
+       return nor->bouncebuf[0];
 }
 
 /*
@@ -351,8 +530,18 @@ static int read_cr(struct spi_nor *nor)
  */
 static int write_sr(struct spi_nor *nor, u8 val)
 {
-       nor->cmd_buf[0] = val;
-       return nor->write_reg(nor, SPINOR_OP_WRSR, nor->cmd_buf, 1);
+       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);
 }
 
 /*
@@ -361,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);
 }
 
@@ -369,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);
 }
 
@@ -468,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:
@@ -486,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);
 
@@ -500,31 +760,58 @@ static int set_4byte(struct spi_nor *nor, bool enable)
                         * We must clear the register to enable normal behavior.
                         */
                        write_enable(nor);
-                       nor->cmd_buf[0] = 0;
-                       nor->write_reg(nor, SPINOR_OP_WREAR, nor->cmd_buf, 1);
+                       spi_nor_write_ear(nor, 0);
                        write_disable(nor);
                }
 
                return status;
        default:
                /* Spansion style */
-               nor->cmd_buf[0] = enable << 7;
-               return nor->write_reg(nor, SPINOR_OP_BRWR, nor->cmd_buf, 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;
-       u8 val;
 
-       ret = nor->read_reg(nor, SPINOR_OP_XRDSR, &val, 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;
        }
 
-       return !!(val & XSR_RDY);
+       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)
@@ -539,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);
@@ -562,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;
        }
 
@@ -630,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);
 }
 
@@ -683,7 +995,6 @@ static loff_t spi_nor_s3an_addr_convert(struct spi_nor *nor, unsigned int addr)
  */
 static int spi_nor_erase_sector(struct spi_nor *nor, u32 addr)
 {
-       u8 buf[SPI_NOR_MAX_ADDR_WIDTH];
        int i;
 
        if (nor->flags & SNOR_F_S3AN_ADDR_DEFAULT)
@@ -692,16 +1003,27 @@ 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
         */
        for (i = nor->addr_width - 1; i >= 0; i--) {
-               buf[i] = addr & 0xff;
+               nor->bouncebuf[i] = addr & 0xff;
                addr >>= 8;
        }
 
-       return nor->write_reg(nor, nor->erase_opcode, buf, nor->addr_width);
+       return nor->write_reg(nor, nor->erase_opcode, nor->bouncebuf,
+                             nor->addr_width);
 }
 
 /**
@@ -1406,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");
@@ -1485,9 +1818,11 @@ static int macronix_quad_enable(struct spi_nor *nor)
  */
 static int spansion_quad_enable(struct spi_nor *nor)
 {
-       u8 sr_cr[2] = {0, CR_QUAD_EN_SPAN};
+       u8 *sr_cr = nor->bouncebuf;
        int ret;
 
+       sr_cr[0] = 0;
+       sr_cr[1] = CR_QUAD_EN_SPAN;
        ret = write_sr_cr(nor, sr_cr);
        if (ret)
                return ret;
@@ -1517,7 +1852,7 @@ static int spansion_quad_enable(struct spi_nor *nor)
  */
 static int spansion_no_read_cr_quad_enable(struct spi_nor *nor)
 {
-       u8 sr_cr[2];
+       u8 *sr_cr = nor->bouncebuf;
        int ret;
 
        /* Keep the current value of the Status Register. */
@@ -1548,7 +1883,7 @@ static int spansion_no_read_cr_quad_enable(struct spi_nor *nor)
 static int spansion_read_cr_quad_enable(struct spi_nor *nor)
 {
        struct device *dev = nor->dev;
-       u8 sr_cr[2];
+       u8 *sr_cr = nor->bouncebuf;
        int ret;
 
        /* Check current Quad Enable bit value. */
@@ -1585,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'
@@ -1599,22 +1964,22 @@ static int spansion_read_cr_quad_enable(struct spi_nor *nor)
  */
 static int sr2_bit7_quad_enable(struct spi_nor *nor)
 {
-       u8 sr2;
+       u8 *sr2 = nor->bouncebuf;
        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)
+       if (*sr2 & SR2_QUAD_EN_BIT7)
                return 0;
 
        /* Update the Quad Enable bit. */
-       sr2 |= SR2_QUAD_EN_BIT7;
+       *sr2 |= SR2_QUAD_EN_BIT7;
 
        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;
@@ -1627,8 +1992,8 @@ 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);
-       if (!(ret > 0 && (sr2 & SR2_QUAD_EN_BIT7))) {
+       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;
        }
@@ -1687,7 +2052,7 @@ static int spi_nor_spansion_clear_sr_bp(struct spi_nor *nor)
 {
        int ret;
        u8 mask = SR_BP2 | SR_BP1 | SR_BP0;
-       u8 sr_cr[2] = {0};
+       u8 *sr_cr =  nor->bouncebuf;
 
        /* Check current Quad Enable bit value. */
        ret = read_cr(nor);
@@ -2022,7 +2387,7 @@ static const struct flash_info spi_nor_ids[] = {
        { "s25fl256s1", INFO(0x010219, 0x4d01,  64 * 1024, 512, SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ | USE_CLSR) },
        { "s25fl512s",  INFO6(0x010220, 0x4d0080, 256 * 1024, 256,
                        SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ |
-                       SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | USE_CLSR) },
+                       SPI_NOR_HAS_LOCK | USE_CLSR) },
        { "s25fs512s",  INFO6(0x010220, 0x4d0081, 256 * 1024, 256, SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ | USE_CLSR) },
        { "s70fl01gs",  INFO(0x010221, 0x4d00, 256 * 1024, 256, 0) },
        { "s25sl12800", INFO(0x012018, 0x0300, 256 * 1024,  64, 0) },
@@ -2060,6 +2425,8 @@ static const struct flash_info spi_nor_ids[] = {
        { "sst25wf040b", INFO(0x621613, 0, 64 * 1024,  8, SECT_4K) },
        { "sst25wf040",  INFO(0xbf2504, 0, 64 * 1024,  8, SECT_4K | SST_WRITE) },
        { "sst25wf080",  INFO(0xbf2505, 0, 64 * 1024, 16, SECT_4K | SST_WRITE) },
+       { "sst26wf016b", INFO(0xbf2651, 0, 64 * 1024, 32, SECT_4K |
+                             SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ) },
        { "sst26vf064b", INFO(0xbf2643, 0, 64 * 1024, 128, SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ) },
 
        /* ST Microelectronics -- newer production may have feature updates */
@@ -2151,6 +2518,8 @@ static const struct flash_info spi_nor_ids[] = {
        { "w25q80bl", INFO(0xef4014, 0, 64 * 1024,  16, SECT_4K) },
        { "w25q128", INFO(0xef4018, 0, 64 * 1024, 256, SECT_4K) },
        { "w25q256", INFO(0xef4019, 0, 64 * 1024, 512, SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ) },
+       { "w25q256jvm", INFO(0xef7019, 0, 64 * 1024, 512,
+                            SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ) },
        { "w25m512jv", INFO(0xef7119, 0, 64 * 1024, 1024,
                        SECT_4K | SPI_NOR_QUAD_READ | SPI_NOR_DUAL_READ) },
 
@@ -2177,10 +2546,21 @@ static const struct flash_info spi_nor_ids[] = {
 static const struct flash_info *spi_nor_read_id(struct spi_nor *nor)
 {
        int                     tmp;
-       u8                      id[SPI_NOR_MAX_ID_LEN];
+       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);
@@ -2216,7 +2596,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;
@@ -2261,7 +2641,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",
@@ -2277,7 +2657,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",
@@ -2300,7 +2680,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",
@@ -2362,7 +2742,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;
@@ -2381,8 +2761,10 @@ static int spi_nor_write(struct mtd_info *mtd, loff_t to, size_t len,
 
 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,9 +2775,8 @@ static int spi_nor_check(struct spi_nor *nor)
 static int s3an_nor_scan(struct spi_nor *nor)
 {
        int ret;
-       u8 val;
 
-       ret = nor->read_reg(nor, SPINOR_OP_XRDSR, &val, 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;
@@ -2417,7 +2798,7 @@ static int s3an_nor_scan(struct spi_nor *nor)
         * The current addressing mode can be read from the XRDSR register
         * and should not be changed, because is a destructive operation.
         */
-       if (val & XSR_PAGESIZE) {
+       if (nor->bouncebuf[0] & XSR_PAGESIZE) {
                /* Flash in Power of 2 mode */
                nor->page_size = (nor->page_size == 264) ? 256 : 512;
                nor->mtd.writebufsize = nor->page_size;
@@ -2525,11 +2906,11 @@ 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);
-               if (!ret || ret > len)
-                       return -EIO;
+               ret = spi_nor_read_data(nor, addr, len, buf);
                if (ret < 0)
                        return ret;
+               if (!ret || ret > len)
+                       return -EIO;
 
                buf += ret;
                addr += ret;
@@ -2574,6 +2955,129 @@ static int spi_nor_read_sfdp(struct spi_nor *nor, u32 addr,
        return ret;
 }
 
+/**
+ * spi_nor_spimem_check_op - check if the operation is supported
+ *                           by controller
+ *@nor:        pointer to a 'struct spi_nor'
+ *@op:         pointer to op template to be checked
+ *
+ * Returns 0 if operation is supported, -ENOTSUPP otherwise.
+ */
+static int spi_nor_spimem_check_op(struct spi_nor *nor,
+                                  struct spi_mem_op *op)
+{
+       /*
+        * First test with 4 address bytes. The opcode itself might
+        * be a 3B addressing opcode but we don't care, because
+        * SPI controller implementation should not check the opcode,
+        * but just the sequence.
+        */
+       op->addr.nbytes = 4;
+       if (!spi_mem_supports_op(nor->spimem, op)) {
+               if (nor->mtd.size > SZ_16M)
+                       return -ENOTSUPP;
+
+               /* If flash size <= 16MB, 3 address bytes are sufficient */
+               op->addr.nbytes = 3;
+               if (!spi_mem_supports_op(nor->spimem, op))
+                       return -ENOTSUPP;
+       }
+
+       return 0;
+}
+
+/**
+ * spi_nor_spimem_check_readop - check if the read op is supported
+ *                               by controller
+ *@nor:         pointer to a 'struct spi_nor'
+ *@read:        pointer to op template to be checked
+ *
+ * Returns 0 if operation is supported, -ENOTSUPP otherwise.
+ */
+static int spi_nor_spimem_check_readop(struct spi_nor *nor,
+                                      const struct spi_nor_read_command *read)
+{
+       struct spi_mem_op op = SPI_MEM_OP(SPI_MEM_OP_CMD(read->opcode, 1),
+                                         SPI_MEM_OP_ADDR(3, 0, 1),
+                                         SPI_MEM_OP_DUMMY(0, 1),
+                                         SPI_MEM_OP_DATA_IN(0, NULL, 1));
+
+       op.cmd.buswidth = spi_nor_get_protocol_inst_nbits(read->proto);
+       op.addr.buswidth = spi_nor_get_protocol_addr_nbits(read->proto);
+       op.data.buswidth = spi_nor_get_protocol_data_nbits(read->proto);
+       op.dummy.buswidth = op.addr.buswidth;
+       op.dummy.nbytes = (read->num_mode_clocks + read->num_wait_states) *
+                         op.dummy.buswidth / 8;
+
+       return spi_nor_spimem_check_op(nor, &op);
+}
+
+/**
+ * spi_nor_spimem_check_pp - check if the page program op is supported
+ *                           by controller
+ *@nor:         pointer to a 'struct spi_nor'
+ *@pp:          pointer to op template to be checked
+ *
+ * Returns 0 if operation is supported, -ENOTSUPP otherwise.
+ */
+static int spi_nor_spimem_check_pp(struct spi_nor *nor,
+                                  const struct spi_nor_pp_command *pp)
+{
+       struct spi_mem_op op = SPI_MEM_OP(SPI_MEM_OP_CMD(pp->opcode, 1),
+                                         SPI_MEM_OP_ADDR(3, 0, 1),
+                                         SPI_MEM_OP_NO_DUMMY,
+                                         SPI_MEM_OP_DATA_OUT(0, NULL, 1));
+
+       op.cmd.buswidth = spi_nor_get_protocol_inst_nbits(pp->proto);
+       op.addr.buswidth = spi_nor_get_protocol_addr_nbits(pp->proto);
+       op.data.buswidth = spi_nor_get_protocol_data_nbits(pp->proto);
+
+       return spi_nor_spimem_check_op(nor, &op);
+}
+
+/**
+ * spi_nor_spimem_adjust_hwcaps - Find optimal Read/Write protocol
+ *                                based on SPI controller capabilities
+ * @nor:        pointer to a 'struct spi_nor'
+ * @params:     pointer to the 'struct spi_nor_flash_parameter'
+ *              representing SPI NOR flash capabilities
+ * @hwcaps:     pointer to resulting capabilities after adjusting
+ *              according to controller and flash's capability
+ */
+static void
+spi_nor_spimem_adjust_hwcaps(struct spi_nor *nor,
+                            const struct spi_nor_flash_parameter *params,
+                            u32 *hwcaps)
+{
+       unsigned int cap;
+
+       /* DTR modes are not supported yet, mask them all. */
+       *hwcaps &= ~SNOR_HWCAPS_DTR;
+
+       /* X-X-X modes are not supported yet, mask them all. */
+       *hwcaps &= ~SNOR_HWCAPS_X_X_X;
+
+       for (cap = 0; cap < sizeof(*hwcaps) * BITS_PER_BYTE; cap++) {
+               int rdidx, ppidx;
+
+               if (!(*hwcaps & BIT(cap)))
+                       continue;
+
+               rdidx = spi_nor_hwcaps_read2cmd(BIT(cap));
+               if (rdidx >= 0 &&
+                   spi_nor_spimem_check_readop(nor, &params->reads[rdidx]))
+                       *hwcaps &= ~BIT(cap);
+
+               ppidx = spi_nor_hwcaps_pp2cmd(BIT(cap));
+               if (ppidx < 0)
+                       continue;
+
+               if (spi_nor_spimem_check_pp(nor,
+                                           &params->page_programs[ppidx]))
+                       *hwcaps &= ~BIT(cap);
+       }
+}
+
 /**
  * spi_nor_read_sfdp_dma_unsafe() - read Serial Flash Discoverable Parameters.
  * @nor:       pointer to a 'struct spi_nor'
@@ -3453,7 +3957,7 @@ static int spi_nor_parse_4bait(struct spi_nor *nor,
        addr = SFDP_PARAM_HEADER_PTP(param_header);
        ret = spi_nor_read_sfdp(nor, addr, len, dwords);
        if (ret)
-               return ret;
+               goto out;
 
        /* Fix endianness of the 4BAIT DWORDs. */
        for (i = 0; i < SFDP_4BAIT_DWORD_MAX; i++)
@@ -3981,16 +4485,25 @@ static int spi_nor_setup(struct spi_nor *nor,
         */
        shared_mask = hwcaps->mask & params->hwcaps.mask;
 
-       /* SPI n-n-n protocols are not supported yet. */
-       ignored_mask = (SNOR_HWCAPS_READ_2_2_2 |
-                       SNOR_HWCAPS_READ_4_4_4 |
-                       SNOR_HWCAPS_READ_8_8_8 |
-                       SNOR_HWCAPS_PP_4_4_4 |
-                       SNOR_HWCAPS_PP_8_8_8);
-       if (shared_mask & ignored_mask) {
-               dev_dbg(nor->dev,
-                       "SPI n-n-n protocols are not supported yet.\n");
-               shared_mask &= ~ignored_mask;
+       if (nor->spimem) {
+               /*
+                * When called from spi_nor_probe(), all caps are set and we
+                * need to discard some of them based on what the SPI
+                * controller actually supports (using spi_mem_supports_op()).
+                */
+               spi_nor_spimem_adjust_hwcaps(nor, params, &shared_mask);
+       } else {
+               /*
+                * SPI n-n-n protocols are not supported when the SPI
+                * controller directly implements the spi_nor interface.
+                * Yet another reason to switch to spi-mem.
+                */
+               ignored_mask = SNOR_HWCAPS_X_X_X;
+               if (shared_mask & ignored_mask) {
+                       dev_dbg(nor->dev,
+                               "SPI n-n-n protocols are not supported.\n");
+                       shared_mask &= ~ignored_mask;
+               }
        }
 
        /* Select the (Fast) Read command. */
@@ -4122,6 +4635,20 @@ int spi_nor_scan(struct spi_nor *nor, const char *name,
        nor->read_proto = SNOR_PROTO_1_1_1;
        nor->write_proto = SNOR_PROTO_1_1_1;
 
+       /*
+        * 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,
+                                     GFP_KERNEL);
+       if (!nor->bouncebuf)
+               return -ENOMEM;
+
        if (name)
                info = spi_nor_match_id(name);
        /* Try to auto-detect if chip name wasn't specified or not found */
@@ -4317,6 +4844,174 @@ 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;
+       /*
+        * Enable all caps by default. The core will mask them after
+        * checking what's really supported using spi_mem_supports_op().
+        */
+       const struct spi_nor_hwcaps hwcaps = { .mask = SNOR_HWCAPS_ALL };
+       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 (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");