Merge tag 'x86_microcode_for_5.8' of git://git.kernel.org/pub/scm/linux/kernel/git...
[linux-2.6-microblaze.git] / drivers / mtd / nand / spi / core.c
index 89f6bee..e2c382f 100644 (file)
@@ -16,6 +16,7 @@
 #include <linux/mtd/spinand.h>
 #include <linux/of.h>
 #include <linux/slab.h>
+#include <linux/string.h>
 #include <linux/spi/spi.h>
 #include <linux/spi/spi-mem.h>
 
@@ -370,10 +371,11 @@ out:
        return status & STATUS_BUSY ? -ETIMEDOUT : 0;
 }
 
-static int spinand_read_id_op(struct spinand_device *spinand, u8 *buf)
+static int spinand_read_id_op(struct spinand_device *spinand, u8 naddr,
+                             u8 ndummy, u8 *buf)
 {
-       struct spi_mem_op op = SPINAND_READID_OP(0, spinand->scratchbuf,
-                                                SPINAND_MAX_ID_LEN);
+       struct spi_mem_op op = SPINAND_READID_OP(
+               naddr, ndummy, spinand->scratchbuf, SPINAND_MAX_ID_LEN);
        int ret;
 
        ret = spi_mem_exec_op(spinand->spimem, &op);
@@ -568,18 +570,18 @@ static int spinand_mtd_write(struct mtd_info *mtd, loff_t to,
 static bool spinand_isbad(struct nand_device *nand, const struct nand_pos *pos)
 {
        struct spinand_device *spinand = nand_to_spinand(nand);
+       u8 marker[2] = { };
        struct nand_page_io_req req = {
                .pos = *pos,
-               .ooblen = 2,
+               .ooblen = sizeof(marker),
                .ooboffs = 0,
-               .oobbuf.in = spinand->oobbuf,
+               .oobbuf.in = marker,
                .mode = MTD_OPS_RAW,
        };
 
-       memset(spinand->oobbuf, 0, 2);
        spinand_select_target(spinand, pos->target);
        spinand_read_page(spinand, &req, false);
-       if (spinand->oobbuf[0] != 0xff || spinand->oobbuf[1] != 0xff)
+       if (marker[0] != 0xff || marker[1] != 0xff)
                return true;
 
        return false;
@@ -603,15 +605,16 @@ static int spinand_mtd_block_isbad(struct mtd_info *mtd, loff_t offs)
 static int spinand_markbad(struct nand_device *nand, const struct nand_pos *pos)
 {
        struct spinand_device *spinand = nand_to_spinand(nand);
+       u8 marker[2] = { };
        struct nand_page_io_req req = {
                .pos = *pos,
                .ooboffs = 0,
-               .ooblen = 2,
-               .oobbuf.out = spinand->oobbuf,
+               .ooblen = sizeof(marker),
+               .oobbuf.out = marker,
+               .mode = MTD_OPS_RAW,
        };
        int ret;
 
-       /* Erase block before marking it bad. */
        ret = spinand_select_target(spinand, pos->target);
        if (ret)
                return ret;
@@ -620,9 +623,6 @@ static int spinand_markbad(struct nand_device *nand, const struct nand_pos *pos)
        if (ret)
                return ret;
 
-       spinand_erase_op(spinand, pos);
-
-       memset(spinand->oobbuf, 0, 2);
        return spinand_write_page(spinand, &req);
 }
 
@@ -762,24 +762,62 @@ static const struct spinand_manufacturer *spinand_manufacturers[] = {
        &winbond_spinand_manufacturer,
 };
 
-static int spinand_manufacturer_detect(struct spinand_device *spinand)
+static int spinand_manufacturer_match(struct spinand_device *spinand,
+                                     enum spinand_readid_method rdid_method)
 {
+       u8 *id = spinand->id.data;
        unsigned int i;
        int ret;
 
        for (i = 0; i < ARRAY_SIZE(spinand_manufacturers); i++) {
-               ret = spinand_manufacturers[i]->ops->detect(spinand);
-               if (ret > 0) {
-                       spinand->manufacturer = spinand_manufacturers[i];
-                       return 0;
-               } else if (ret < 0) {
-                       return ret;
-               }
-       }
+               const struct spinand_manufacturer *manufacturer =
+                       spinand_manufacturers[i];
+
+               if (id[0] != manufacturer->id)
+                       continue;
+
+               ret = spinand_match_and_init(spinand,
+                                            manufacturer->chips,
+                                            manufacturer->nchips,
+                                            rdid_method);
+               if (ret < 0)
+                       continue;
 
+               spinand->manufacturer = manufacturer;
+               return 0;
+       }
        return -ENOTSUPP;
 }
 
+static int spinand_id_detect(struct spinand_device *spinand)
+{
+       u8 *id = spinand->id.data;
+       int ret;
+
+       ret = spinand_read_id_op(spinand, 0, 0, id);
+       if (ret)
+               return ret;
+       ret = spinand_manufacturer_match(spinand, SPINAND_READID_METHOD_OPCODE);
+       if (!ret)
+               return 0;
+
+       ret = spinand_read_id_op(spinand, 1, 0, id);
+       if (ret)
+               return ret;
+       ret = spinand_manufacturer_match(spinand,
+                                        SPINAND_READID_METHOD_OPCODE_ADDR);
+       if (!ret)
+               return 0;
+
+       ret = spinand_read_id_op(spinand, 0, 1, id);
+       if (ret)
+               return ret;
+       ret = spinand_manufacturer_match(spinand,
+                                        SPINAND_READID_METHOD_OPCODE_DUMMY);
+
+       return ret;
+}
+
 static int spinand_manufacturer_init(struct spinand_device *spinand)
 {
        if (spinand->manufacturer->ops->init)
@@ -835,9 +873,9 @@ spinand_select_op_variant(struct spinand_device *spinand,
  * @spinand: SPI NAND object
  * @table: SPI NAND device description table
  * @table_size: size of the device description table
+ * @rdid_method: read id method to match
  *
- * Should be used by SPI NAND manufacturer drivers when they want to find a
- * match between a device ID retrieved through the READ_ID command and an
+ * Match between a device ID retrieved through the READ_ID command and an
  * entry in the SPI NAND description table. If a match is found, the spinand
  * object will be initialized with information provided by the matching
  * spinand_info entry.
@@ -846,8 +884,10 @@ spinand_select_op_variant(struct spinand_device *spinand,
  */
 int spinand_match_and_init(struct spinand_device *spinand,
                           const struct spinand_info *table,
-                          unsigned int table_size, u16 devid)
+                          unsigned int table_size,
+                          enum spinand_readid_method rdid_method)
 {
+       u8 *id = spinand->id.data;
        struct nand_device *nand = spinand_to_nand(spinand);
        unsigned int i;
 
@@ -855,13 +895,17 @@ int spinand_match_and_init(struct spinand_device *spinand,
                const struct spinand_info *info = &table[i];
                const struct spi_mem_op *op;
 
-               if (devid != info->devid)
+               if (rdid_method != info->devid.method)
+                       continue;
+
+               if (memcmp(id + 1, info->devid.id, info->devid.len))
                        continue;
 
                nand->memorg = table[i].memorg;
                nand->eccreq = table[i].eccreq;
                spinand->eccinfo = table[i].eccinfo;
                spinand->flags = table[i].flags;
+               spinand->id.len = 1 + table[i].devid.len;
                spinand->select_target = table[i].select_target;
 
                op = spinand_select_op_variant(spinand,
@@ -898,13 +942,7 @@ static int spinand_detect(struct spinand_device *spinand)
        if (ret)
                return ret;
 
-       ret = spinand_read_id_op(spinand, spinand->id.data);
-       if (ret)
-               return ret;
-
-       spinand->id.len = SPINAND_MAX_ID_LEN;
-
-       ret = spinand_manufacturer_detect(spinand);
+       ret = spinand_id_detect(spinand);
        if (ret) {
                dev_err(dev, "unknown raw ID %*phN\n", SPINAND_MAX_ID_LEN,
                        spinand->id.data);
@@ -1051,6 +1089,10 @@ static int spinand_init(struct spinand_device *spinand)
 
        mtd->oobavail = ret;
 
+       /* Propagate ECC information to mtd_info */
+       mtd->ecc_strength = nand->eccreq.strength;
+       mtd->ecc_step_size = nand->eccreq.step_size;
+
        return 0;
 
 err_cleanup_nanddev: