summaryrefslogtreecommitdiff
path: root/drivers/mtd
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/mtd')
-rw-r--r--drivers/mtd/Kconfig9
-rw-r--r--drivers/mtd/Makefile1
-rw-r--r--drivers/mtd/devices/docg3.c3
-rw-r--r--drivers/mtd/maps/Kconfig11
-rw-r--r--drivers/mtd/maps/Makefile1
-rw-r--r--drivers/mtd/maps/physmap-bt1-rom.c125
-rw-r--r--drivers/mtd/maps/physmap-bt1-rom.h17
-rw-r--r--drivers/mtd/maps/physmap-core.c5
-rw-r--r--drivers/mtd/maps/physmap-gemini.c2
-rw-r--r--drivers/mtd/mtd_virt_concat.c350
-rw-r--r--drivers/mtd/mtdconcat.c17
-rw-r--r--drivers/mtd/mtdcore.c21
-rw-r--r--drivers/mtd/mtdpart.c6
-rw-r--r--drivers/mtd/nand/ecc-realtek.c18
-rw-r--r--drivers/mtd/nand/raw/cafe_nand.c7
-rw-r--r--drivers/mtd/nand/raw/fsl_ifc_nand.c10
-rw-r--r--drivers/mtd/nand/raw/gpmi-nand/gpmi-nand.c11
-rw-r--r--drivers/mtd/nand/raw/mxc_nand.c10
-rw-r--r--drivers/mtd/nand/raw/nand_base.c19
-rw-r--r--drivers/mtd/nand/raw/sunxi_nand.c369
-rw-r--r--drivers/mtd/nand/spi/winbond.c17
-rw-r--r--drivers/mtd/parsers/cmdlinepart.c3
-rw-r--r--drivers/mtd/parsers/ofpart_core.c4
-rw-r--r--drivers/mtd/spi-nor/core.c2
-rw-r--r--drivers/mtd/spi-nor/core.h2
-rw-r--r--drivers/mtd/spi-nor/micron-st.c27
-rw-r--r--drivers/mtd/spi-nor/sst.c13
-rw-r--r--drivers/mtd/spi-nor/swp.c4
-rw-r--r--drivers/mtd/spi-nor/winbond.c4
29 files changed, 779 insertions, 309 deletions
diff --git a/drivers/mtd/Kconfig b/drivers/mtd/Kconfig
index 796a2eccbef0..0421c6208de7 100644
--- a/drivers/mtd/Kconfig
+++ b/drivers/mtd/Kconfig
@@ -206,6 +206,15 @@ config MTD_PARTITIONED_MASTER
the parent of the partition device be the master device, rather than
what lies behind the master.
+config MTD_VIRT_CONCAT
+ bool "Virtual concatenated MTD devices"
+ depends on MTD_PARTITIONED_MASTER
+ help
+ The driver enables the creation of virtual MTD device by
+ concatenating multiple physical MTD devices into a single
+ entity. This allows for the creation of partitions larger than
+ the individual physical chips, extending across chip boundaries.
+
source "drivers/mtd/chips/Kconfig"
source "drivers/mtd/maps/Kconfig"
diff --git a/drivers/mtd/Makefile b/drivers/mtd/Makefile
index 593d0593a038..7b6dd53e8150 100644
--- a/drivers/mtd/Makefile
+++ b/drivers/mtd/Makefile
@@ -6,6 +6,7 @@
# Core functionality.
obj-$(CONFIG_MTD) += mtd.o
mtd-y := mtdcore.o mtdsuper.o mtdconcat.o mtdpart.o mtdchar.o
+mtd-$(CONFIG_MTD_VIRT_CONCAT) += mtd_virt_concat.o
obj-y += parsers/
diff --git a/drivers/mtd/devices/docg3.c b/drivers/mtd/devices/docg3.c
index 33050a2a80f7..603fd0efc2ea 100644
--- a/drivers/mtd/devices/docg3.c
+++ b/drivers/mtd/devices/docg3.c
@@ -2049,7 +2049,6 @@ err_probe:
static void docg3_release(struct platform_device *pdev)
{
struct docg3_cascade *cascade = platform_get_drvdata(pdev);
- struct docg3 *docg3 = cascade->floors[0]->priv;
int floor;
doc_unregister_sysfs(pdev, cascade);
@@ -2057,7 +2056,7 @@ static void docg3_release(struct platform_device *pdev)
if (cascade->floors[floor])
doc_release_device(cascade->floors[floor]);
- bch_free(docg3->cascade->bch);
+ bch_free(cascade->bch);
}
#ifdef CONFIG_OF
diff --git a/drivers/mtd/maps/Kconfig b/drivers/mtd/maps/Kconfig
index 8a8b19874e23..99d5ff9a1fbe 100644
--- a/drivers/mtd/maps/Kconfig
+++ b/drivers/mtd/maps/Kconfig
@@ -75,17 +75,6 @@ config MTD_PHYSMAP_OF
physically into the CPU's memory. The mapping description here is
taken from OF device tree.
-config MTD_PHYSMAP_BT1_ROM
- bool "Baikal-T1 Boot ROMs OF-based physical memory map handling"
- depends on MTD_PHYSMAP_OF
- depends on MIPS_BAIKAL_T1 || COMPILE_TEST
- select MTD_COMPLEX_MAPPINGS
- select MULTIPLEXER
- select MUX_MMIO
- help
- This provides some extra DT physmap parsing for the Baikal-T1
- platforms, some detection and setting up ROMs-specific accessors.
-
config MTD_PHYSMAP_VERSATILE
bool "ARM Versatile OF-based physical memory map handling"
depends on MTD_PHYSMAP_OF
diff --git a/drivers/mtd/maps/Makefile b/drivers/mtd/maps/Makefile
index 019f1e92cc41..51af1d2ebd52 100644
--- a/drivers/mtd/maps/Makefile
+++ b/drivers/mtd/maps/Makefile
@@ -19,7 +19,6 @@ obj-$(CONFIG_MTD_TSUNAMI) += tsunami_flash.o
obj-$(CONFIG_MTD_PXA2XX) += pxa2xx-flash.o
obj-$(CONFIG_MTD_PHYSMAP) += physmap.o
physmap-y := physmap-core.o
-physmap-$(CONFIG_MTD_PHYSMAP_BT1_ROM) += physmap-bt1-rom.o
physmap-$(CONFIG_MTD_PHYSMAP_VERSATILE) += physmap-versatile.o
physmap-$(CONFIG_MTD_PHYSMAP_GEMINI) += physmap-gemini.o
physmap-$(CONFIG_MTD_PHYSMAP_IXP4XX) += physmap-ixp4xx.o
diff --git a/drivers/mtd/maps/physmap-bt1-rom.c b/drivers/mtd/maps/physmap-bt1-rom.c
deleted file mode 100644
index 60dccc48f99e..000000000000
--- a/drivers/mtd/maps/physmap-bt1-rom.c
+++ /dev/null
@@ -1,125 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0-only
-/*
- * Copyright (C) 2020 BAIKAL ELECTRONICS, JSC
- *
- * Authors:
- * Serge Semin <Sergey.Semin@baikalelectronics.ru>
- *
- * Baikal-T1 Physically Mapped Internal ROM driver
- */
-#include <linux/bits.h>
-#include <linux/device.h>
-#include <linux/kernel.h>
-#include <linux/mtd/map.h>
-#include <linux/mtd/xip.h>
-#include <linux/mux/consumer.h>
-#include <linux/of.h>
-#include <linux/platform_device.h>
-#include <linux/string.h>
-#include <linux/types.h>
-
-#include "physmap-bt1-rom.h"
-
-/*
- * Baikal-T1 SoC ROMs are only accessible by the dword-aligned instructions.
- * We have to take this into account when implementing the data read-methods.
- * Note there is no need in bothering with endianness, since both Baikal-T1
- * CPU and MMIO are LE.
- */
-static map_word __xipram bt1_rom_map_read(struct map_info *map,
- unsigned long ofs)
-{
- void __iomem *src = map->virt + ofs;
- unsigned int shift;
- map_word ret;
- u32 data;
-
- /* Read data within offset dword. */
- shift = (uintptr_t)src & 0x3;
- data = readl_relaxed(src - shift);
- if (!shift) {
- ret.x[0] = data;
- return ret;
- }
- ret.x[0] = data >> (shift * BITS_PER_BYTE);
-
- /* Read data from the next dword. */
- shift = 4 - shift;
- if (ofs + shift >= map->size)
- return ret;
-
- data = readl_relaxed(src + shift);
- ret.x[0] |= data << (shift * BITS_PER_BYTE);
-
- return ret;
-}
-
-static void __xipram bt1_rom_map_copy_from(struct map_info *map,
- void *to, unsigned long from,
- ssize_t len)
-{
- void __iomem *src = map->virt + from;
- unsigned int shift, chunk;
- u32 data;
-
- if (len <= 0 || from >= map->size)
- return;
-
- /* Make sure we don't go over the map limit. */
- len = min_t(ssize_t, map->size - from, len);
-
- /*
- * Since requested data size can be pretty big we have to implement
- * the copy procedure as optimal as possible. That's why it's split
- * up into the next three stages: unaligned head, aligned body,
- * unaligned tail.
- */
- shift = (uintptr_t)src & 0x3;
- if (shift) {
- chunk = min_t(ssize_t, 4 - shift, len);
- data = readl_relaxed(src - shift);
- memcpy(to, (char *)&data + shift, chunk);
- src += chunk;
- to += chunk;
- len -= chunk;
- }
-
- while (len >= 4) {
- data = readl_relaxed(src);
- memcpy(to, &data, 4);
- src += 4;
- to += 4;
- len -= 4;
- }
-
- if (len) {
- data = readl_relaxed(src);
- memcpy(to, &data, len);
- }
-}
-
-int of_flash_probe_bt1_rom(struct platform_device *pdev,
- struct device_node *np,
- struct map_info *map)
-{
- struct device *dev = &pdev->dev;
-
- /* It's supposed to be read-only MTD. */
- if (!of_device_is_compatible(np, "mtd-rom")) {
- dev_info(dev, "No mtd-rom compatible string\n");
- return 0;
- }
-
- /* Multiplatform guard. */
- if (!of_device_is_compatible(np, "baikal,bt1-int-rom"))
- return 0;
-
- /* Sanity check the device parameters retrieved from DTB. */
- if (map->bankwidth != 4)
- dev_warn(dev, "Bank width is supposed to be 32 bits wide\n");
-
- map->read = bt1_rom_map_read;
- map->copy_from = bt1_rom_map_copy_from;
-
- return 0;
-}
diff --git a/drivers/mtd/maps/physmap-bt1-rom.h b/drivers/mtd/maps/physmap-bt1-rom.h
deleted file mode 100644
index 6782899598a4..000000000000
--- a/drivers/mtd/maps/physmap-bt1-rom.h
+++ /dev/null
@@ -1,17 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0-only */
-#include <linux/mtd/map.h>
-#include <linux/of.h>
-
-#ifdef CONFIG_MTD_PHYSMAP_BT1_ROM
-int of_flash_probe_bt1_rom(struct platform_device *pdev,
- struct device_node *np,
- struct map_info *map);
-#else
-static inline
-int of_flash_probe_bt1_rom(struct platform_device *pdev,
- struct device_node *np,
- struct map_info *map)
-{
- return 0;
-}
-#endif
diff --git a/drivers/mtd/maps/physmap-core.c b/drivers/mtd/maps/physmap-core.c
index 0dcc25b7ff98..dcda7685fc99 100644
--- a/drivers/mtd/maps/physmap-core.c
+++ b/drivers/mtd/maps/physmap-core.c
@@ -42,7 +42,6 @@
#include <linux/pm_runtime.h>
#include <linux/gpio/consumer.h>
-#include "physmap-bt1-rom.h"
#include "physmap-gemini.h"
#include "physmap-ixp4xx.h"
#include "physmap-versatile.h"
@@ -365,10 +364,6 @@ static int physmap_flash_of_init(struct platform_device *dev)
info->maps[i].bankwidth = bankwidth;
info->maps[i].device_node = dp;
- err = of_flash_probe_bt1_rom(dev, dp, &info->maps[i]);
- if (err)
- return err;
-
err = of_flash_probe_gemini(dev, dp, &info->maps[i]);
if (err)
return err;
diff --git a/drivers/mtd/maps/physmap-gemini.c b/drivers/mtd/maps/physmap-gemini.c
index 9d3b4bf84a1a..1c34b4ef77ea 100644
--- a/drivers/mtd/maps/physmap-gemini.c
+++ b/drivers/mtd/maps/physmap-gemini.c
@@ -181,7 +181,7 @@ int of_flash_probe_gemini(struct platform_device *pdev,
dev_err(dev, "no enabled pin control state\n");
gf->disabled_state = pinctrl_lookup_state(gf->p, "disabled");
- if (IS_ERR(gf->enabled_state)) {
+ if (IS_ERR(gf->disabled_state)) {
dev_err(dev, "no disabled pin control state\n");
} else {
ret = pinctrl_select_state(gf->p, gf->disabled_state);
diff --git a/drivers/mtd/mtd_virt_concat.c b/drivers/mtd/mtd_virt_concat.c
new file mode 100644
index 000000000000..37075ead0f33
--- /dev/null
+++ b/drivers/mtd/mtd_virt_concat.c
@@ -0,0 +1,350 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Virtual concat MTD device driver
+ *
+ * Copyright (C) 2018 Bernhard Frauendienst
+ * Author: Bernhard Frauendienst <kernel@nospam.obeliks.de>
+ */
+
+#include <linux/device.h>
+#include <linux/mtd/mtd.h>
+#include "mtdcore.h"
+#include <linux/mtd/partitions.h>
+#include <linux/of.h>
+#include <linux/of_platform.h>
+#include <linux/slab.h>
+#include <linux/mtd/concat.h>
+
+#define CONCAT_PROP "part-concat-next"
+#define CONCAT_POSTFIX "concat"
+#define MIN_DEV_PER_CONCAT 1
+
+static LIST_HEAD(concat_node_list);
+
+/**
+ * struct mtd_virt_concat_node - components of a concatenation
+ * @head: List handle
+ * @count: Number of nodes
+ * @nodes: Pointer to the nodes (partitions) to concatenate
+ * @concat: Concatenation container
+ */
+struct mtd_virt_concat_node {
+ struct list_head head;
+ unsigned int count;
+ struct mtd_concat *concat;
+ struct device_node *nodes[] __counted_by(count);
+};
+
+/**
+ * mtd_is_part_concat - Check if the device is already part
+ * of a concatenated device
+ * @dev: pointer to 'device_node'
+ *
+ * Return: true if the device is already part of a concatenation,
+ * false otherwise.
+ */
+static bool mtd_is_part_concat(struct device_node *dev)
+{
+ struct mtd_virt_concat_node *item;
+ int idx;
+
+ list_for_each_entry(item, &concat_node_list, head) {
+ for (idx = 0; idx < item->count; idx++) {
+ if (item->nodes[idx] == dev)
+ return true;
+ }
+ }
+ return false;
+}
+
+static void mtd_virt_concat_put_mtd_devices(struct mtd_concat *concat)
+{
+ int i;
+
+ for (i = 0; i < concat->num_subdev; i++)
+ put_mtd_device(concat->subdev[i]);
+}
+
+void mtd_virt_concat_destroy_joins(void)
+{
+ struct mtd_virt_concat_node *item, *tmp;
+ struct mtd_info *mtd;
+
+ list_for_each_entry_safe(item, tmp, &concat_node_list, head) {
+ mtd = &item->concat->mtd;
+ if (item->concat) {
+ mtd_device_unregister(mtd);
+ kfree(mtd->name);
+ mtd_concat_destroy(mtd);
+ mtd_virt_concat_put_mtd_devices(item->concat);
+ }
+ }
+}
+
+/**
+ * mtd_virt_concat_destroy - Destroy the concat that includes the mtd object
+ * @mtd: pointer to 'mtd_info'
+ *
+ * Return: 0 on success, -error otherwise.
+ */
+int mtd_virt_concat_destroy(struct mtd_info *mtd)
+{
+ struct mtd_info *child, *master = mtd_get_master(mtd);
+ struct mtd_virt_concat_node *item, *tmp;
+ struct mtd_concat *concat;
+ int idx, ret = 0;
+ bool is_mtd_found;
+
+ list_for_each_entry_safe(item, tmp, &concat_node_list, head) {
+ is_mtd_found = false;
+
+ /* Find the concat item that hold the mtd device */
+ for (idx = 0; idx < item->count; idx++) {
+ if (item->nodes[idx] == mtd->dev.of_node) {
+ is_mtd_found = true;
+ break;
+ }
+ }
+ if (!is_mtd_found)
+ continue;
+ concat = item->concat;
+
+ /*
+ * Since this concatenated device is being removed, retrieve
+ * all MTD devices that are part of it and register them
+ * individually.
+ */
+ for (idx = 0; idx < concat->num_subdev; idx++) {
+ child = concat->subdev[idx];
+ if (child->dev.of_node != mtd->dev.of_node) {
+ ret = add_mtd_device(child);
+ if (ret)
+ goto out;
+ }
+ }
+ /* Destroy the concat */
+ if (concat->mtd.name) {
+ del_mtd_device(&concat->mtd);
+ kfree(concat->mtd.name);
+ mtd_concat_destroy(&concat->mtd);
+ mtd_virt_concat_put_mtd_devices(item->concat);
+ }
+
+ for (idx = 0; idx < item->count; idx++)
+ of_node_put(item->nodes[idx]);
+
+ kfree(item);
+ }
+ return 0;
+out:
+ mutex_lock(&master->master.partitions_lock);
+ list_del(&child->part.node);
+ mutex_unlock(&master->master.partitions_lock);
+ kfree(mtd->name);
+ kfree(mtd);
+
+ return ret;
+}
+
+/**
+ * mtd_virt_concat_create_item - Create a concat item
+ * @parts: pointer to 'device_node'
+ * @count: number of mtd devices that make up
+ * the concatenated device.
+ *
+ * Return: 0 on success, -error otherwise.
+ */
+static int mtd_virt_concat_create_item(struct device_node *parts,
+ unsigned int count)
+{
+ struct mtd_virt_concat_node *item;
+ struct mtd_concat *concat;
+ int i;
+
+ for (i = 0; i < (count - 1); i++) {
+ if (mtd_is_part_concat(of_parse_phandle(parts, CONCAT_PROP, i)))
+ return 0;
+ }
+
+ item = kzalloc_flex(*item, nodes, count, GFP_KERNEL);
+ if (!item)
+ return -ENOMEM;
+
+ item->count = count;
+
+ /*
+ * The partition in which "part-concat-next" property
+ * is defined is the first device in the list of concat
+ * devices.
+ */
+ item->nodes[0] = parts;
+
+ for (i = 1; i < count; i++)
+ item->nodes[i] = of_parse_phandle(parts, CONCAT_PROP, (i - 1));
+
+ concat = kzalloc_flex(*concat, subdev, count, GFP_KERNEL);
+ if (!concat) {
+ kfree(item);
+ return -ENOMEM;
+ }
+
+ item->concat = concat;
+
+ list_add_tail(&item->head, &concat_node_list);
+
+ return 0;
+}
+
+void mtd_virt_concat_destroy_items(void)
+{
+ struct mtd_virt_concat_node *item, *temp;
+ int i;
+
+ list_for_each_entry_safe(item, temp, &concat_node_list, head) {
+ for (i = 0; i < item->count; i++)
+ of_node_put(item->nodes[i]);
+
+ kfree(item);
+ }
+}
+
+/**
+ * mtd_virt_concat_add - Add a mtd device to the concat list
+ * @mtd: pointer to 'mtd_info'
+ *
+ * Return: true on success, false otherwise.
+ */
+bool mtd_virt_concat_add(struct mtd_info *mtd)
+{
+ struct mtd_virt_concat_node *item;
+ struct mtd_concat *concat;
+ int idx;
+
+ list_for_each_entry(item, &concat_node_list, head) {
+ concat = item->concat;
+ for (idx = 0; idx < item->count; idx++) {
+ if (item->nodes[idx] == mtd->dev.of_node) {
+ concat->subdev[concat->num_subdev++] = mtd;
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+/**
+ * mtd_virt_concat_node_create - List all the concatenations found in DT
+ *
+ * Return: 0 on success, -error otherwise.
+ */
+int mtd_virt_concat_node_create(void)
+{
+ struct device_node *parts = NULL;
+ int ret = 0, count = 0;
+
+ /* List all the concatenations found in DT */
+ do {
+ parts = of_find_node_with_property(parts, CONCAT_PROP);
+ if (!of_device_is_available(parts))
+ continue;
+
+ if (mtd_is_part_concat(parts))
+ continue;
+
+ count = of_count_phandle_with_args(parts, CONCAT_PROP, NULL);
+ if (count < MIN_DEV_PER_CONCAT)
+ continue;
+
+ /*
+ * The partition in which "part-concat-next" property is defined
+ * is also part of the concat device, so increament count by 1.
+ */
+ count++;
+
+ ret = mtd_virt_concat_create_item(parts, count);
+ if (ret) {
+ of_node_put(parts);
+ goto destroy_items;
+ }
+ } while (parts);
+
+ return ret;
+
+destroy_items:
+ mtd_virt_concat_destroy_items();
+
+ return ret;
+}
+
+/**
+ * mtd_virt_concat_create_join - Create and register the concatenated
+ * MTD device.
+ *
+ * Return: 0 on success, -error otherwise.
+ */
+int mtd_virt_concat_create_join(void)
+{
+ struct mtd_virt_concat_node *item;
+ struct mtd_concat *concat;
+ struct mtd_info *mtd;
+ ssize_t name_sz;
+ int ret, idx;
+ char *name;
+
+ list_for_each_entry(item, &concat_node_list, head) {
+ concat = item->concat;
+ /*
+ * Check if item->count != concat->num_subdev, it indicates
+ * that the MTD information for all devices included in the
+ * concatenation are not handy, concat MTD device can't be
+ * created hence switch to next concat device.
+ */
+ if (item->count != concat->num_subdev) {
+ continue;
+ } else {
+ /* Calculate the legth of the name of the virtual device */
+ for (idx = 0, name_sz = 0; idx < concat->num_subdev; idx++)
+ name_sz += (strlen(concat->subdev[idx]->name) + 1);
+ name_sz += strlen(CONCAT_POSTFIX);
+ name = kmalloc(name_sz + 1, GFP_KERNEL);
+ if (!name) {
+ mtd_virt_concat_put_mtd_devices(concat);
+ return -ENOMEM;
+ }
+
+ ret = 0;
+ for (idx = 0; idx < concat->num_subdev; idx++) {
+ ret += sprintf((name + ret), "%s-",
+ concat->subdev[idx]->name);
+ }
+ sprintf((name + ret), CONCAT_POSTFIX);
+
+ if (concat->mtd.name) {
+ ret = memcmp(concat->mtd.name, name, name_sz);
+ if (ret == 0)
+ continue;
+ }
+ mtd = mtd_concat_create(concat->subdev, concat->num_subdev, name);
+ if (!mtd) {
+ kfree(name);
+ return -ENXIO;
+ }
+ concat->mtd = *mtd;
+ /* Arbitrary set the first device as parent */
+ concat->mtd.dev.parent = concat->subdev[0]->dev.parent;
+ concat->mtd.dev = concat->subdev[0]->dev;
+
+ /* Add the mtd device */
+ ret = add_mtd_device(&concat->mtd);
+ if (ret)
+ goto destroy_concat;
+ }
+ }
+
+ return 0;
+
+destroy_concat:
+ mtd_concat_destroy(mtd);
+
+ return ret;
+}
diff --git a/drivers/mtd/mtdconcat.c b/drivers/mtd/mtdconcat.c
index 9eb5d919d9ba..c97167d51fe2 100644
--- a/drivers/mtd/mtdconcat.c
+++ b/drivers/mtd/mtdconcat.c
@@ -21,18 +21,6 @@
#include <asm/div64.h>
/*
- * Our storage structure:
- * Subdev points to an array of pointers to struct mtd_info objects
- * which is allocated along with this structure
- *
- */
-struct mtd_concat {
- struct mtd_info mtd;
- int num_subdev;
- struct mtd_info **subdev;
-};
-
-/*
* how to calculate the size required for the above structure,
* including the pointer array subdev points to:
*/
@@ -639,7 +627,6 @@ struct mtd_info *mtd_concat_create(struct mtd_info *subdev[], /* subdevices to c
const char *name)
{ /* name for the new device */
int i;
- size_t size;
struct mtd_concat *concat;
struct mtd_info *subdev_master = NULL;
uint32_t max_erasesize, curr_erasesize;
@@ -652,15 +639,13 @@ struct mtd_info *mtd_concat_create(struct mtd_info *subdev[], /* subdevices to c
printk(KERN_NOTICE "into device \"%s\"\n", name);
/* allocate the device structure */
- size = SIZEOF_STRUCT_MTD_CONCAT(num_devs);
- concat = kzalloc(size, GFP_KERNEL);
+ concat = kzalloc_flex(*concat, subdev, num_devs, GFP_KERNEL);
if (!concat) {
printk
("memory allocation error while creating concatenated device \"%s\"\n",
name);
return NULL;
}
- concat->subdev = (struct mtd_info **) (concat + 1);
/*
* Set up the new "super" device's MTD object structure, check for
diff --git a/drivers/mtd/mtdcore.c b/drivers/mtd/mtdcore.c
index 64808493b4f5..576537774628 100644
--- a/drivers/mtd/mtdcore.c
+++ b/drivers/mtd/mtdcore.c
@@ -34,6 +34,7 @@
#include <linux/mtd/mtd.h>
#include <linux/mtd/partitions.h>
+#include <linux/mtd/concat.h>
#include "mtdcore.h"
@@ -1120,6 +1121,12 @@ int mtd_device_parse_register(struct mtd_info *mtd, const char * const *types,
goto out;
}
+ if (IS_REACHABLE(CONFIG_MTD_VIRT_CONCAT)) {
+ ret = mtd_virt_concat_node_create();
+ if (ret < 0)
+ goto out;
+ }
+
/* Prefer parsed partitions over driver-provided fallback */
ret = parse_mtd_partitions(mtd, types, parser_data);
if (ret == -EPROBE_DEFER)
@@ -1137,6 +1144,11 @@ int mtd_device_parse_register(struct mtd_info *mtd, const char * const *types,
if (ret)
goto out;
+ if (IS_REACHABLE(CONFIG_MTD_VIRT_CONCAT)) {
+ ret = mtd_virt_concat_create_join();
+ if (ret < 0)
+ goto out;
+ }
/*
* FIXME: some drivers unfortunately call this function more than once.
* So we have to check if we've already assigned the reboot notifier.
@@ -1186,6 +1198,11 @@ int mtd_device_unregister(struct mtd_info *master)
nvmem_unregister(master->otp_user_nvmem);
nvmem_unregister(master->otp_factory_nvmem);
+ if (IS_REACHABLE(CONFIG_MTD_VIRT_CONCAT)) {
+ err = mtd_virt_concat_destroy(master);
+ if (err)
+ return err;
+ }
err = del_mtd_partitions(master);
if (err)
return err;
@@ -2621,6 +2638,10 @@ err_reg:
static void __exit cleanup_mtd(void)
{
+ if (IS_REACHABLE(CONFIG_MTD_VIRT_CONCAT)) {
+ mtd_virt_concat_destroy_joins();
+ mtd_virt_concat_destroy_items();
+ }
debugfs_remove_recursive(dfs_dir_mtd);
cleanup_mtdchar();
if (proc_mtd)
diff --git a/drivers/mtd/mtdpart.c b/drivers/mtd/mtdpart.c
index e016cfbc7224..795a94e6b482 100644
--- a/drivers/mtd/mtdpart.c
+++ b/drivers/mtd/mtdpart.c
@@ -18,6 +18,7 @@
#include <linux/err.h>
#include <linux/of.h>
#include <linux/of_platform.h>
+#include <linux/mtd/concat.h>
#include "mtdcore.h"
@@ -409,6 +410,11 @@ int add_mtd_partitions(struct mtd_info *parent,
goto err_del_partitions;
}
+ if (IS_REACHABLE(CONFIG_MTD_VIRT_CONCAT)) {
+ if (mtd_virt_concat_add(child))
+ continue;
+ }
+
mutex_lock(&master->master.partitions_lock);
list_add_tail(&child->part.node, &parent->partitions);
mutex_unlock(&master->master.partitions_lock);
diff --git a/drivers/mtd/nand/ecc-realtek.c b/drivers/mtd/nand/ecc-realtek.c
index 0046da37ea3e..7d003fd72027 100644
--- a/drivers/mtd/nand/ecc-realtek.c
+++ b/drivers/mtd/nand/ecc-realtek.c
@@ -17,10 +17,12 @@
* - BCH12 : Generate 20 ECC bytes from 512 data bytes plus 6 free bytes
*
* It can run for arbitrary NAND flash chips with different block and OOB sizes. Currently there
- * are only two known devices in the wild that have NAND flash and make use of this ECC engine
- * (Linksys LGS328C & LGS352C). To keep compatibility with vendor firmware, new modes can only
- * be added when new data layouts have been analyzed. For now allow BCH6 on flash with 2048 byte
- * blocks and 64 bytes oob.
+ * are a few known devices in the wild that make use of this ECC engine
+ * (Linksys LGS328C, LGS352C & Netlink HG323DAC). To keep compatibility with vendor firmware,
+ * new modes can only be added when new data layouts have been analyzed. For now allow BCH6 on
+ * flash with 2048 byte blocks and at least 64 bytes oob. Some vendors make use of
+ * 128 bytes OOB NAND chips (e.g. Macronix MX35LF1G24AD) but only use BCH6 and thus the first
+ * 64 bytes of the OOB area. In this case the engine leaves any extra bytes unused.
*
* This driver aligns with kernel ECC naming conventions. Neverthless a short notice on the
* Realtek naming conventions for the different structures in the OOB area.
@@ -39,7 +41,7 @@
*/
#define RTL_ECC_ALLOWED_PAGE_SIZE 2048
-#define RTL_ECC_ALLOWED_OOB_SIZE 64
+#define RTL_ECC_ALLOWED_MIN_OOB_SIZE 64
#define RTL_ECC_ALLOWED_STRENGTH 6
#define RTL_ECC_BLOCK_SIZE 512
@@ -310,10 +312,10 @@ static int rtl_ecc_check_support(struct nand_device *nand)
struct mtd_info *mtd = nanddev_to_mtd(nand);
struct device *dev = nand->ecc.engine->dev;
- if (mtd->oobsize != RTL_ECC_ALLOWED_OOB_SIZE ||
+ if (mtd->oobsize < RTL_ECC_ALLOWED_MIN_OOB_SIZE ||
mtd->writesize != RTL_ECC_ALLOWED_PAGE_SIZE) {
- dev_err(dev, "only flash geometry data=%d, oob=%d supported\n",
- RTL_ECC_ALLOWED_PAGE_SIZE, RTL_ECC_ALLOWED_OOB_SIZE);
+ dev_err(dev, "only flash geometry data=%d, oob>=%d supported\n",
+ RTL_ECC_ALLOWED_PAGE_SIZE, RTL_ECC_ALLOWED_MIN_OOB_SIZE);
return -EINVAL;
}
diff --git a/drivers/mtd/nand/raw/cafe_nand.c b/drivers/mtd/nand/raw/cafe_nand.c
index 65a36d5de742..c4018bc59670 100644
--- a/drivers/mtd/nand/raw/cafe_nand.c
+++ b/drivers/mtd/nand/raw/cafe_nand.c
@@ -837,9 +837,10 @@ static const struct pci_device_id cafe_nand_tbl[] = {
MODULE_DEVICE_TABLE(pci, cafe_nand_tbl);
-static int cafe_nand_resume(struct pci_dev *pdev)
+static int cafe_nand_resume(struct device *dev)
{
uint32_t ctrl;
+ struct pci_dev *pdev = to_pci_dev(dev);
struct mtd_info *mtd = pci_get_drvdata(pdev);
struct nand_chip *chip = mtd_to_nand(mtd);
struct cafe_priv *cafe = nand_get_controller_data(chip);
@@ -877,12 +878,14 @@ static int cafe_nand_resume(struct pci_dev *pdev)
return 0;
}
+static DEFINE_SIMPLE_DEV_PM_OPS(cafe_nand_ops, NULL, cafe_nand_resume);
+
static struct pci_driver cafe_nand_pci_driver = {
.name = "CAFÉ NAND",
.id_table = cafe_nand_tbl,
.probe = cafe_nand_probe,
.remove = cafe_nand_remove,
- .resume = cafe_nand_resume,
+ .driver.pm = &cafe_nand_ops,
};
module_pci_driver(cafe_nand_pci_driver);
diff --git a/drivers/mtd/nand/raw/fsl_ifc_nand.c b/drivers/mtd/nand/raw/fsl_ifc_nand.c
index dd88b22a91bd..fad0334f759d 100644
--- a/drivers/mtd/nand/raw/fsl_ifc_nand.c
+++ b/drivers/mtd/nand/raw/fsl_ifc_nand.c
@@ -7,6 +7,7 @@
* Author: Dipen Dudhat <Dipen.Dudhat@freescale.com>
*/
+#include <linux/cleanup.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/types.h>
@@ -863,7 +864,14 @@ static int fsl_ifc_chip_init(struct fsl_ifc_mtd *priv)
/* Fill in fsl_ifc_mtd structure */
mtd->dev.parent = priv->dev;
- nand_set_flash_node(chip, priv->dev->of_node);
+
+ struct device_node *np __free(device_node) =
+ of_get_next_child_with_prefix(priv->dev->of_node, NULL, "nand");
+
+ if (np)
+ nand_set_flash_node(chip, np);
+ else
+ nand_set_flash_node(chip, priv->dev->of_node);
/* fill in nand_chip structure */
/* set up function call table */
diff --git a/drivers/mtd/nand/raw/gpmi-nand/gpmi-nand.c b/drivers/mtd/nand/raw/gpmi-nand/gpmi-nand.c
index 51f595fbc834..c1f766cb225a 100644
--- a/drivers/mtd/nand/raw/gpmi-nand/gpmi-nand.c
+++ b/drivers/mtd/nand/raw/gpmi-nand/gpmi-nand.c
@@ -5,6 +5,7 @@
* Copyright (C) 2010-2015 Freescale Semiconductor, Inc.
* Copyright (C) 2008 Embedded Alley Solutions, Inc.
*/
+#include <linux/cleanup.h>
#include <linux/clk.h>
#include <linux/delay.h>
#include <linux/slab.h>
@@ -2688,7 +2689,15 @@ static int gpmi_nand_init(struct gpmi_nand_data *this)
/* init the nand_chip{}, we don't support a 16-bit NAND Flash bus. */
nand_set_controller_data(chip, this);
- nand_set_flash_node(chip, this->pdev->dev.of_node);
+
+ struct device_node *np __free(device_node) =
+ of_get_next_child_with_prefix(this->pdev->dev.of_node, NULL, "nand");
+
+ if (np)
+ nand_set_flash_node(chip, np);
+ else
+ nand_set_flash_node(chip, this->pdev->dev.of_node);
+
chip->legacy.block_markbad = gpmi_block_markbad;
chip->badblock_pattern = &gpmi_bbt_descr;
chip->options |= NAND_NO_SUBPAGE_WRITE;
diff --git a/drivers/mtd/nand/raw/mxc_nand.c b/drivers/mtd/nand/raw/mxc_nand.c
index 8c56b685bf91..4d8b92e7e672 100644
--- a/drivers/mtd/nand/raw/mxc_nand.c
+++ b/drivers/mtd/nand/raw/mxc_nand.c
@@ -4,6 +4,7 @@
* Copyright 2008 Sascha Hauer, kernel@pengutronix.de
*/
+#include <linux/cleanup.h>
#include <linux/delay.h>
#include <linux/slab.h>
#include <linux/init.h>
@@ -1714,7 +1715,14 @@ static int mxcnd_probe(struct platform_device *pdev)
this->legacy.chip_delay = 5;
nand_set_controller_data(this, host);
- nand_set_flash_node(this, pdev->dev.of_node);
+
+ struct device_node *np __free(device_node) =
+ of_get_next_child_with_prefix(pdev->dev.of_node, NULL, "nand");
+
+ if (np)
+ nand_set_flash_node(this, np);
+ else
+ nand_set_flash_node(this, pdev->dev.of_node);
host->clk = devm_clk_get(&pdev->dev, NULL);
if (IS_ERR(host->clk))
diff --git a/drivers/mtd/nand/raw/nand_base.c b/drivers/mtd/nand/raw/nand_base.c
index dfd8361bdd36..d6d3e17ab407 100644
--- a/drivers/mtd/nand/raw/nand_base.c
+++ b/drivers/mtd/nand/raw/nand_base.c
@@ -43,6 +43,7 @@
#include <linux/mtd/partitions.h>
#include <linux/of.h>
#include <linux/gpio/consumer.h>
+#include <linux/cleanup.h>
#include "internals.h"
@@ -4704,16 +4705,16 @@ static void nand_resume(struct mtd_info *mtd)
{
struct nand_chip *chip = mtd_to_nand(mtd);
- mutex_lock(&chip->lock);
- if (chip->suspended) {
- if (chip->ops.resume)
- chip->ops.resume(chip);
- chip->suspended = 0;
- } else {
- pr_err("%s called for a chip which is not in suspended state\n",
- __func__);
+ scoped_guard(mutex, &chip->lock) {
+ if (chip->suspended) {
+ if (chip->ops.resume)
+ chip->ops.resume(chip);
+ chip->suspended = 0;
+ } else {
+ pr_err("%s called for a chip which is not in suspended state\n",
+ __func__);
+ }
}
- mutex_unlock(&chip->lock);
wake_up_all(&chip->resume_wq);
}
diff --git a/drivers/mtd/nand/raw/sunxi_nand.c b/drivers/mtd/nand/raw/sunxi_nand.c
index e66adfcca7cd..02647565c8ba 100644
--- a/drivers/mtd/nand/raw/sunxi_nand.c
+++ b/drivers/mtd/nand/raw/sunxi_nand.c
@@ -209,9 +209,8 @@
/*
* On A10/A23, this is the size of the NDFC User Data Register, containing the
- * mandatory user data bytes following the ECC for each ECC step.
+ * mandatory user data bytes preceding the ECC for each ECC step.
* Thus, for each ECC step, we need the ECC bytes + USER_DATA_SZ.
- * Those bits are currently unsused, and kept as default value 0xffffffff.
*
* On H6/H616, this size became configurable, from 0 bytes to 32, via the
* USER_DATA_LEN registers.
@@ -249,6 +248,7 @@ struct sunxi_nand_hw_ecc {
* @timing_ctl: TIMING_CTL register value for this NAND chip
* @nsels: number of CS lines required by the NAND chip
* @sels: array of CS lines descriptions
+ * @user_data_bytes: array of user data lengths for all ECC steps
*/
struct sunxi_nand_chip {
struct list_head node;
@@ -257,6 +257,7 @@ struct sunxi_nand_chip {
unsigned long clk_rate;
u32 timing_cfg;
u32 timing_ctl;
+ u8 *user_data_bytes;
int nsels;
struct sunxi_nand_chip_sel sels[] __counted_by(nsels);
};
@@ -272,9 +273,11 @@ static inline struct sunxi_nand_chip *to_sunxi_nand(struct nand_chip *nand)
*
* @has_mdma: Use mbus dma mode, otherwise general dma
* through MBUS on A23/A33 needs extra configuration.
- * @has_ecc_block_512: If the ECC can handle 512B or only 1024B chuncks
+ * @has_ecc_block_512: If the ECC can handle 512B or only 1024B chunks
* @has_ecc_clk: If the controller needs an ECC clock.
* @has_mbus_clk: If the controller needs a mbus clock.
+ * @legacy_max_strength:If the maximize strength function was off by 2 bytes
+ * NB: this should not be used in new controllers
* @reg_io_data: I/O data register
* @reg_ecc_err_cnt: ECC error counter register
* @reg_user_data: User data register
@@ -292,7 +295,7 @@ static inline struct sunxi_nand_chip *to_sunxi_nand(struct nand_chip *nand)
* @nstrengths: Size of @ecc_strengths
* @max_ecc_steps: Maximum supported steps for ECC, this is also the
* number of user data registers
- * @user_data_len_tab: Table of lenghts supported by USER_DATA_LEN register
+ * @user_data_len_tab: Table of lengths supported by USER_DATA_LEN register
* The table index is the value to set in NFC_USER_DATA_LEN
* registers, and the corresponding value is the number of
* bytes to write
@@ -304,6 +307,7 @@ struct sunxi_nfc_caps {
bool has_ecc_block_512;
bool has_ecc_clk;
bool has_mbus_clk;
+ bool legacy_max_strength;
unsigned int reg_io_data;
unsigned int reg_ecc_err_cnt;
unsigned int reg_user_data;
@@ -820,12 +824,50 @@ static inline u32 sunxi_nfc_buf_to_user_data(const u8 *buf)
return buf[0] | (buf[1] << 8) | (buf[2] << 16) | (buf[3] << 24);
}
+static u8 sunxi_nfc_user_data_sz(struct sunxi_nand_chip *sunxi_nand, int step)
+{
+ if (!sunxi_nand->user_data_bytes)
+ return USER_DATA_SZ;
+
+ return sunxi_nand->user_data_bytes[step];
+}
+
static void sunxi_nfc_hw_ecc_get_prot_oob_bytes(struct nand_chip *nand, u8 *oob,
- int step, bool bbm, int page)
+ int step, bool bbm, int page,
+ unsigned int user_data_sz)
{
+ struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(nand);
struct sunxi_nfc *nfc = to_sunxi_nfc(nand->controller);
+ u32 user_data;
- sunxi_nfc_user_data_to_buf(readl(nfc->regs + NFC_REG_USER_DATA(nfc, step)), oob);
+ if (!nfc->caps->reg_user_data_len) {
+ /*
+ * For A10, the user data for step n is in the nth
+ * REG_USER_DATA
+ */
+ user_data = readl(nfc->regs + NFC_REG_USER_DATA(nfc, step));
+ sunxi_nfc_user_data_to_buf(user_data, oob);
+ } else {
+ /*
+ * For H6 NAND controller, the user data for all steps is
+ * contained in 32 user data registers, but not at a specific
+ * offset for each step, they are just concatenated.
+ */
+ unsigned int user_data_off = 0;
+ unsigned int reg_off;
+ u8 *ptr = oob;
+ unsigned int i;
+
+ for (i = 0; i < step; i++)
+ user_data_off += sunxi_nfc_user_data_sz(sunxi_nand, i);
+
+ user_data_off /= 4;
+ for (i = 0; i < user_data_sz / 4; i++, ptr += 4) {
+ reg_off = NFC_REG_USER_DATA(nfc, user_data_off + i);
+ user_data = readl(nfc->regs + reg_off);
+ sunxi_nfc_user_data_to_buf(user_data, ptr);
+ }
+ }
/* De-randomize the Bad Block Marker. */
if (bbm && (nand->options & NAND_NEED_SCRAMBLING))
@@ -884,17 +926,46 @@ static void sunxi_nfc_hw_ecc_set_prot_oob_bytes(struct nand_chip *nand,
bool bbm, int page)
{
struct sunxi_nfc *nfc = to_sunxi_nfc(nand->controller);
- u8 user_data[USER_DATA_SZ];
+ struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(nand);
+ unsigned int user_data_sz = sunxi_nfc_user_data_sz(sunxi_nand, step);
+ u8 *user_data = NULL;
/* Randomize the Bad Block Marker. */
if (bbm && (nand->options & NAND_NEED_SCRAMBLING)) {
- memcpy(user_data, oob, sizeof(user_data));
+ user_data = kmalloc(user_data_sz, GFP_KERNEL);
+ memcpy(user_data, oob, user_data_sz);
sunxi_nfc_randomize_bbm(nand, page, user_data);
oob = user_data;
}
- writel(sunxi_nfc_buf_to_user_data(oob),
- nfc->regs + NFC_REG_USER_DATA(nfc, step));
+ if (!nfc->caps->reg_user_data_len) {
+ /*
+ * For A10, the user data for step n is in the nth
+ * REG_USER_DATA
+ */
+ writel(sunxi_nfc_buf_to_user_data(oob),
+ nfc->regs + NFC_REG_USER_DATA(nfc, step));
+ } else {
+ /*
+ * For H6 NAND controller, the user data for all steps is
+ * contained in 32 user data registers, but not at a specific
+ * offset for each step, they are just concatenated.
+ */
+ unsigned int user_data_off = 0;
+ const u8 *ptr = oob;
+ unsigned int i;
+
+ for (i = 0; i < step; i++)
+ user_data_off += sunxi_nfc_user_data_sz(sunxi_nand, i);
+
+ user_data_off /= 4;
+ for (i = 0; i < user_data_sz / 4; i++, ptr += 4) {
+ writel(sunxi_nfc_buf_to_user_data(ptr),
+ nfc->regs + NFC_REG_USER_DATA(nfc, user_data_off + i));
+ }
+ }
+
+ kfree(user_data);
}
static void sunxi_nfc_hw_ecc_update_stats(struct nand_chip *nand,
@@ -915,6 +986,8 @@ static int sunxi_nfc_hw_ecc_correct(struct nand_chip *nand, u8 *data, u8 *oob,
bool *erased)
{
struct sunxi_nfc *nfc = to_sunxi_nfc(nand->controller);
+ struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(nand);
+ unsigned int user_data_sz = sunxi_nfc_user_data_sz(sunxi_nand, step);
struct nand_ecc_ctrl *ecc = &nand->ecc;
u32 tmp;
@@ -937,7 +1010,7 @@ static int sunxi_nfc_hw_ecc_correct(struct nand_chip *nand, u8 *data, u8 *oob,
memset(data, pattern, ecc->size);
if (oob)
- memset(oob, pattern, ecc->bytes + USER_DATA_SZ);
+ memset(oob, pattern, ecc->bytes + user_data_sz);
return 0;
}
@@ -952,14 +1025,19 @@ static int sunxi_nfc_hw_ecc_read_chunk(struct nand_chip *nand,
u8 *oob, int oob_off,
int *cur_off,
unsigned int *max_bitflips,
- bool bbm, bool oob_required, int page)
+ int step, bool oob_required, int page)
{
struct sunxi_nfc *nfc = to_sunxi_nfc(nand->controller);
+ struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(nand);
+ unsigned int user_data_sz = sunxi_nfc_user_data_sz(sunxi_nand, step);
struct nand_ecc_ctrl *ecc = &nand->ecc;
int raw_mode = 0;
u32 pattern_found;
+ bool bbm = !step;
bool erased;
int ret;
+ /* From the controller point of view, we are at step 0 */
+ const int nfc_step = 0;
if (*cur_off != data_off)
nand_change_read_column_op(nand, data_off, NULL, 0, false);
@@ -973,8 +1051,7 @@ static int sunxi_nfc_hw_ecc_read_chunk(struct nand_chip *nand,
if (ret)
return ret;
- sunxi_nfc_reset_user_data_len(nfc);
- sunxi_nfc_set_user_data_len(nfc, USER_DATA_SZ, 0);
+ sunxi_nfc_set_user_data_len(nfc, user_data_sz, nfc_step);
sunxi_nfc_randomizer_config(nand, page, false);
sunxi_nfc_randomizer_enable(nand);
writel(NFC_DATA_TRANS | NFC_DATA_SWAP_METHOD | NFC_ECC_OP,
@@ -985,15 +1062,14 @@ static int sunxi_nfc_hw_ecc_read_chunk(struct nand_chip *nand,
if (ret)
return ret;
- *cur_off = oob_off + ecc->bytes + USER_DATA_SZ;
+ *cur_off = oob_off + ecc->bytes + user_data_sz;
pattern_found = readl(nfc->regs + nfc->caps->reg_pat_found);
pattern_found = field_get(NFC_ECC_PAT_FOUND_MSK(nfc), pattern_found);
- ret = sunxi_nfc_hw_ecc_correct(nand, data, oob_required ? oob : NULL, 0,
- readl(nfc->regs + NFC_REG_ECC_ST),
- pattern_found,
- &erased);
+ ret = sunxi_nfc_hw_ecc_correct(nand, data, oob_required ? oob : NULL,
+ nfc_step, readl(nfc->regs + NFC_REG_ECC_ST),
+ pattern_found, &erased);
if (erased)
return 1;
@@ -1010,10 +1086,10 @@ static int sunxi_nfc_hw_ecc_read_chunk(struct nand_chip *nand,
ecc->size);
nand_change_read_column_op(nand, oob_off, oob,
- ecc->bytes + USER_DATA_SZ, false);
+ ecc->bytes + user_data_sz, false);
ret = nand_check_erased_ecc_chunk(data, ecc->size, oob,
- ecc->bytes + USER_DATA_SZ,
+ ecc->bytes + user_data_sz,
NULL, 0, ecc->strength);
if (ret >= 0)
raw_mode = 1;
@@ -1023,11 +1099,11 @@ static int sunxi_nfc_hw_ecc_read_chunk(struct nand_chip *nand,
if (oob_required) {
nand_change_read_column_op(nand, oob_off, NULL, 0,
false);
- sunxi_nfc_randomizer_read_buf(nand, oob, ecc->bytes + USER_DATA_SZ,
+ sunxi_nfc_randomizer_read_buf(nand, oob, ecc->bytes + user_data_sz,
true, page);
- sunxi_nfc_hw_ecc_get_prot_oob_bytes(nand, oob, 0,
- bbm, page);
+ sunxi_nfc_hw_ecc_get_prot_oob_bytes(nand, oob, nfc_step,
+ bbm, page, user_data_sz);
}
}
@@ -1036,21 +1112,50 @@ static int sunxi_nfc_hw_ecc_read_chunk(struct nand_chip *nand,
return raw_mode;
}
+/*
+ * Returns the offset of the OOB for each step.
+ * (it includes the user data before the ECC data.)
+ */
+static int sunxi_get_oob_offset(struct sunxi_nand_chip *sunxi_nand,
+ struct nand_ecc_ctrl *ecc, int step)
+{
+ int ecc_off = step * ecc->bytes;
+ int i;
+
+ for (i = 0; i < step; i++)
+ ecc_off += sunxi_nfc_user_data_sz(sunxi_nand, i);
+
+ return ecc_off;
+}
+
+/*
+ * Returns the offset of the ECC for each step.
+ * So, it's the same as sunxi_get_oob_offset(),
+ * but it skips the next user data.
+ */
+static int sunxi_get_ecc_offset(struct sunxi_nand_chip *sunxi_nand,
+ struct nand_ecc_ctrl *ecc, int step)
+{
+ return sunxi_get_oob_offset(sunxi_nand, ecc, step) +
+ sunxi_nfc_user_data_sz(sunxi_nand, step);
+}
+
static void sunxi_nfc_hw_ecc_read_extra_oob(struct nand_chip *nand,
u8 *oob, int *cur_off,
bool randomize, int page)
{
+ struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(nand);
struct mtd_info *mtd = nand_to_mtd(nand);
struct nand_ecc_ctrl *ecc = &nand->ecc;
- int offset = ((ecc->bytes + 4) * ecc->steps);
+ int offset = sunxi_get_oob_offset(sunxi_nand, ecc, ecc->steps);
int len = mtd->oobsize - offset;
if (len <= 0)
return;
- if (!cur_off || *cur_off != offset)
- nand_change_read_column_op(nand, mtd->writesize, NULL, 0,
- false);
+ if (!cur_off || *cur_off != (offset + mtd->writesize))
+ nand_change_read_column_op(nand, mtd->writesize + offset,
+ NULL, 0, false);
if (!randomize)
sunxi_nfc_read_buf(nand, oob + offset, len);
@@ -1067,6 +1172,7 @@ static int sunxi_nfc_hw_ecc_read_chunks_dma(struct nand_chip *nand, uint8_t *buf
int nchunks)
{
bool randomized = nand->options & NAND_NEED_SCRAMBLING;
+ struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(nand);
struct sunxi_nfc *nfc = to_sunxi_nfc(nand->controller);
struct mtd_info *mtd = nand_to_mtd(nand);
struct nand_ecc_ctrl *ecc = &nand->ecc;
@@ -1086,7 +1192,8 @@ static int sunxi_nfc_hw_ecc_read_chunks_dma(struct nand_chip *nand, uint8_t *buf
sunxi_nfc_hw_ecc_enable(nand);
sunxi_nfc_reset_user_data_len(nfc);
- sunxi_nfc_set_user_data_len(nfc, USER_DATA_SZ, 0);
+ for (i = 0; i < nchunks; i++)
+ sunxi_nfc_set_user_data_len(nfc, sunxi_nfc_user_data_sz(sunxi_nand, i), i);
sunxi_nfc_randomizer_config(nand, page, false);
sunxi_nfc_randomizer_enable(nand);
@@ -1121,7 +1228,8 @@ static int sunxi_nfc_hw_ecc_read_chunks_dma(struct nand_chip *nand, uint8_t *buf
for (i = 0; i < nchunks; i++) {
int data_off = i * ecc->size;
- int oob_off = i * (ecc->bytes + USER_DATA_SZ);
+ unsigned int user_data_sz = sunxi_nfc_user_data_sz(sunxi_nand, i);
+ int oob_off = sunxi_get_oob_offset(sunxi_nand, ecc, i);
u8 *data = buf + data_off;
u8 *oob = nand->oob_poi + oob_off;
bool erased;
@@ -1139,10 +1247,10 @@ static int sunxi_nfc_hw_ecc_read_chunks_dma(struct nand_chip *nand, uint8_t *buf
/* TODO: use DMA to retrieve OOB */
nand_change_read_column_op(nand,
mtd->writesize + oob_off,
- oob, ecc->bytes + USER_DATA_SZ, false);
+ oob, ecc->bytes + user_data_sz, false);
- sunxi_nfc_hw_ecc_get_prot_oob_bytes(nand, oob, i,
- !i, page);
+ sunxi_nfc_hw_ecc_get_prot_oob_bytes(nand, oob, i, !i,
+ page, user_data_sz);
}
if (erased)
@@ -1154,7 +1262,8 @@ static int sunxi_nfc_hw_ecc_read_chunks_dma(struct nand_chip *nand, uint8_t *buf
if (status & NFC_ECC_ERR_MSK(nfc)) {
for (i = 0; i < nchunks; i++) {
int data_off = i * ecc->size;
- int oob_off = i * (ecc->bytes + USER_DATA_SZ);
+ unsigned int user_data_sz = sunxi_nfc_user_data_sz(sunxi_nand, i);
+ int oob_off = sunxi_get_oob_offset(sunxi_nand, ecc, i);
u8 *data = buf + data_off;
u8 *oob = nand->oob_poi + oob_off;
@@ -1174,10 +1283,10 @@ static int sunxi_nfc_hw_ecc_read_chunks_dma(struct nand_chip *nand, uint8_t *buf
/* TODO: use DMA to retrieve OOB */
nand_change_read_column_op(nand,
mtd->writesize + oob_off,
- oob, ecc->bytes + USER_DATA_SZ, false);
+ oob, ecc->bytes + user_data_sz, false);
ret = nand_check_erased_ecc_chunk(data, ecc->size, oob,
- ecc->bytes + USER_DATA_SZ,
+ ecc->bytes + user_data_sz,
NULL, 0,
ecc->strength);
if (ret >= 0)
@@ -1198,12 +1307,17 @@ static int sunxi_nfc_hw_ecc_read_chunks_dma(struct nand_chip *nand, uint8_t *buf
static int sunxi_nfc_hw_ecc_write_chunk(struct nand_chip *nand,
const u8 *data, int data_off,
const u8 *oob, int oob_off,
- int *cur_off, bool bbm,
+ int *cur_off, int step,
int page)
{
struct sunxi_nfc *nfc = to_sunxi_nfc(nand->controller);
+ struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(nand);
+ unsigned int user_data_sz = sunxi_nfc_user_data_sz(sunxi_nand, step);
struct nand_ecc_ctrl *ecc = &nand->ecc;
+ bool bbm = !step;
int ret;
+ /* From the controller point of view, we are at step 0 */
+ const int nfc_step = 0;
if (data_off != *cur_off)
nand_change_write_column_op(nand, data_off, NULL, 0, false);
@@ -1219,9 +1333,8 @@ static int sunxi_nfc_hw_ecc_write_chunk(struct nand_chip *nand,
sunxi_nfc_randomizer_config(nand, page, false);
sunxi_nfc_randomizer_enable(nand);
- sunxi_nfc_reset_user_data_len(nfc);
- sunxi_nfc_set_user_data_len(nfc, USER_DATA_SZ, 0);
- sunxi_nfc_hw_ecc_set_prot_oob_bytes(nand, oob, 0, bbm, page);
+ sunxi_nfc_set_user_data_len(nfc, user_data_sz, nfc_step);
+ sunxi_nfc_hw_ecc_set_prot_oob_bytes(nand, oob, nfc_step, bbm, page);
writel(NFC_DATA_TRANS | NFC_DATA_SWAP_METHOD |
NFC_ACCESS_DIR | NFC_ECC_OP,
@@ -1232,7 +1345,7 @@ static int sunxi_nfc_hw_ecc_write_chunk(struct nand_chip *nand,
if (ret)
return ret;
- *cur_off = oob_off + ecc->bytes + USER_DATA_SZ;
+ *cur_off = oob_off + ecc->bytes + user_data_sz;
return 0;
}
@@ -1242,8 +1355,9 @@ static void sunxi_nfc_hw_ecc_write_extra_oob(struct nand_chip *nand,
int page)
{
struct mtd_info *mtd = nand_to_mtd(nand);
+ struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(nand);
struct nand_ecc_ctrl *ecc = &nand->ecc;
- int offset = ((ecc->bytes + USER_DATA_SZ) * ecc->steps);
+ int offset = sunxi_get_oob_offset(sunxi_nand, ecc, ecc->steps);
int len = mtd->oobsize - offset;
if (len <= 0)
@@ -1262,6 +1376,8 @@ static void sunxi_nfc_hw_ecc_write_extra_oob(struct nand_chip *nand,
static int sunxi_nfc_hw_ecc_read_page(struct nand_chip *nand, uint8_t *buf,
int oob_required, int page)
{
+ struct sunxi_nfc *nfc = to_sunxi_nfc(nand->controller);
+ struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(nand);
struct mtd_info *mtd = nand_to_mtd(nand);
struct nand_ecc_ctrl *ecc = &nand->ecc;
unsigned int max_bitflips = 0;
@@ -1274,16 +1390,17 @@ static int sunxi_nfc_hw_ecc_read_page(struct nand_chip *nand, uint8_t *buf,
sunxi_nfc_hw_ecc_enable(nand);
+ sunxi_nfc_reset_user_data_len(nfc);
for (i = 0; i < ecc->steps; i++) {
int data_off = i * ecc->size;
- int oob_off = i * (ecc->bytes + USER_DATA_SZ);
+ int oob_off = sunxi_get_oob_offset(sunxi_nand, ecc, i);
u8 *data = buf + data_off;
u8 *oob = nand->oob_poi + oob_off;
ret = sunxi_nfc_hw_ecc_read_chunk(nand, data, data_off, oob,
oob_off + mtd->writesize,
&cur_off, &max_bitflips,
- !i, oob_required, page);
+ i, oob_required, page);
if (ret < 0)
return ret;
else if (ret)
@@ -1321,6 +1438,8 @@ static int sunxi_nfc_hw_ecc_read_subpage(struct nand_chip *nand,
u32 data_offs, u32 readlen,
u8 *bufpoi, int page)
{
+ struct sunxi_nfc *nfc = to_sunxi_nfc(nand->controller);
+ struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(nand);
struct mtd_info *mtd = nand_to_mtd(nand);
struct nand_ecc_ctrl *ecc = &nand->ecc;
int ret, i, cur_off = 0;
@@ -1332,17 +1451,18 @@ static int sunxi_nfc_hw_ecc_read_subpage(struct nand_chip *nand,
sunxi_nfc_hw_ecc_enable(nand);
+ sunxi_nfc_reset_user_data_len(nfc);
for (i = data_offs / ecc->size;
i < DIV_ROUND_UP(data_offs + readlen, ecc->size); i++) {
int data_off = i * ecc->size;
- int oob_off = i * (ecc->bytes + USER_DATA_SZ);
+ int oob_off = sunxi_get_oob_offset(sunxi_nand, ecc, i);
u8 *data = bufpoi + data_off;
u8 *oob = nand->oob_poi + oob_off;
ret = sunxi_nfc_hw_ecc_read_chunk(nand, data, data_off,
oob,
oob_off + mtd->writesize,
- &cur_off, &max_bitflips, !i,
+ &cur_off, &max_bitflips, i,
false, page);
if (ret < 0)
return ret;
@@ -1377,6 +1497,8 @@ static int sunxi_nfc_hw_ecc_write_page(struct nand_chip *nand,
const uint8_t *buf, int oob_required,
int page)
{
+ struct sunxi_nfc *nfc = to_sunxi_nfc(nand->controller);
+ struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(nand);
struct mtd_info *mtd = nand_to_mtd(nand);
struct nand_ecc_ctrl *ecc = &nand->ecc;
int ret, i, cur_off = 0;
@@ -1387,15 +1509,16 @@ static int sunxi_nfc_hw_ecc_write_page(struct nand_chip *nand,
sunxi_nfc_hw_ecc_enable(nand);
+ sunxi_nfc_reset_user_data_len(nfc);
for (i = 0; i < ecc->steps; i++) {
int data_off = i * ecc->size;
- int oob_off = i * (ecc->bytes + USER_DATA_SZ);
+ int oob_off = sunxi_get_oob_offset(sunxi_nand, ecc, i);
const u8 *data = buf + data_off;
const u8 *oob = nand->oob_poi + oob_off;
ret = sunxi_nfc_hw_ecc_write_chunk(nand, data, data_off, oob,
oob_off + mtd->writesize,
- &cur_off, !i, page);
+ &cur_off, i, page);
if (ret)
return ret;
}
@@ -1414,6 +1537,8 @@ static int sunxi_nfc_hw_ecc_write_subpage(struct nand_chip *nand,
const u8 *buf, int oob_required,
int page)
{
+ struct sunxi_nfc *nfc = to_sunxi_nfc(nand->controller);
+ struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(nand);
struct mtd_info *mtd = nand_to_mtd(nand);
struct nand_ecc_ctrl *ecc = &nand->ecc;
int ret, i, cur_off = 0;
@@ -1424,16 +1549,17 @@ static int sunxi_nfc_hw_ecc_write_subpage(struct nand_chip *nand,
sunxi_nfc_hw_ecc_enable(nand);
+ sunxi_nfc_reset_user_data_len(nfc);
for (i = data_offs / ecc->size;
i < DIV_ROUND_UP(data_offs + data_len, ecc->size); i++) {
int data_off = i * ecc->size;
- int oob_off = i * (ecc->bytes + USER_DATA_SZ);
+ int oob_off = sunxi_get_oob_offset(sunxi_nand, ecc, i);
const u8 *data = buf + data_off;
const u8 *oob = nand->oob_poi + oob_off;
ret = sunxi_nfc_hw_ecc_write_chunk(nand, data, data_off, oob,
oob_off + mtd->writesize,
- &cur_off, !i, page);
+ &cur_off, i, page);
if (ret)
return ret;
}
@@ -1449,6 +1575,7 @@ static int sunxi_nfc_hw_ecc_write_page_dma(struct nand_chip *nand,
int page)
{
struct sunxi_nfc *nfc = to_sunxi_nfc(nand->controller);
+ struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(nand);
struct nand_ecc_ctrl *ecc = &nand->ecc;
struct scatterlist sg;
u32 wait;
@@ -1467,10 +1594,12 @@ static int sunxi_nfc_hw_ecc_write_page_dma(struct nand_chip *nand,
sunxi_nfc_reset_user_data_len(nfc);
for (i = 0; i < ecc->steps; i++) {
- const u8 *oob = nand->oob_poi + (i * (ecc->bytes + USER_DATA_SZ));
+ unsigned int user_data_sz = sunxi_nfc_user_data_sz(sunxi_nand, i);
+ int oob_off = sunxi_get_oob_offset(sunxi_nand, ecc, i);
+ const u8 *oob = nand->oob_poi + oob_off;
sunxi_nfc_hw_ecc_set_prot_oob_bytes(nand, oob, i, !i, page);
- sunxi_nfc_set_user_data_len(nfc, USER_DATA_SZ, i);
+ sunxi_nfc_set_user_data_len(nfc, user_data_sz, i);
}
nand_prog_page_begin_op(nand, page, 0, NULL, 0);
@@ -1734,11 +1863,12 @@ static int sunxi_nand_ooblayout_ecc(struct mtd_info *mtd, int section,
{
struct nand_chip *nand = mtd_to_nand(mtd);
struct nand_ecc_ctrl *ecc = &nand->ecc;
+ struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(nand);
if (section >= ecc->steps)
return -ERANGE;
- oobregion->offset = section * (ecc->bytes + USER_DATA_SZ) + 4;
+ oobregion->offset = sunxi_get_ecc_offset(sunxi_nand, ecc, section);
oobregion->length = ecc->bytes;
return 0;
@@ -1749,35 +1879,30 @@ static int sunxi_nand_ooblayout_free(struct mtd_info *mtd, int section,
{
struct nand_chip *nand = mtd_to_nand(mtd);
struct nand_ecc_ctrl *ecc = &nand->ecc;
+ struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(nand);
+ unsigned int user_data_sz = sunxi_nfc_user_data_sz(sunxi_nand, section);
- if (section > ecc->steps)
+ /*
+ * The controller does not provide access to OOB bytes
+ * past the end of the ECC data.
+ */
+ if (section >= ecc->steps)
return -ERANGE;
/*
* The first 2 bytes are used for BB markers, hence we
- * only have 2 bytes available in the first user data
+ * only have user_data_sz - 2 bytes available in the first user data
* section.
*/
- if (!section && ecc->engine_type == NAND_ECC_ENGINE_TYPE_ON_HOST) {
+ if (section == 0) {
oobregion->offset = 2;
- oobregion->length = 2;
+ oobregion->length = user_data_sz - 2;
return 0;
}
- /*
- * The controller does not provide access to OOB bytes
- * past the end of the ECC data.
- */
- if (section == ecc->steps && ecc->engine_type == NAND_ECC_ENGINE_TYPE_ON_HOST)
- return -ERANGE;
-
- oobregion->offset = section * (ecc->bytes + USER_DATA_SZ);
-
- if (section < ecc->steps)
- oobregion->length = USER_DATA_SZ;
- else
- oobregion->length = mtd->oobsize - oobregion->offset;
+ oobregion->offset = sunxi_get_ecc_offset(sunxi_nand, ecc, section);
+ oobregion->length = user_data_sz;
return 0;
}
@@ -1787,6 +1912,43 @@ static const struct mtd_ooblayout_ops sunxi_nand_ooblayout_ops = {
.free = sunxi_nand_ooblayout_free,
};
+static void sunxi_nand_detach_chip(struct nand_chip *nand)
+{
+ struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(nand);
+ struct sunxi_nfc *nfc = to_sunxi_nfc(nand->controller);
+
+ devm_kfree(nfc->dev, sunxi_nand->user_data_bytes);
+ sunxi_nand->user_data_bytes = NULL;
+}
+
+static int sunxi_nfc_maximize_user_data(struct nand_chip *nand, uint32_t oobsize,
+ int ecc_bytes, int nsectors)
+{
+ struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(nand);
+ struct sunxi_nfc *nfc = to_sunxi_nfc(nand->controller);
+ const struct sunxi_nfc_caps *c = nfc->caps;
+ int remaining_bytes = oobsize - (ecc_bytes * nsectors);
+ int i, step;
+
+ sunxi_nand->user_data_bytes = devm_kzalloc(nfc->dev, nsectors,
+ GFP_KERNEL);
+ if (!sunxi_nand->user_data_bytes)
+ return -ENOMEM;
+
+ for (step = 0; (step < nsectors) && (remaining_bytes > 0); step++) {
+ for (i = 0; i < c->nuser_data_tab; i++) {
+ if (c->user_data_len_tab[i] > remaining_bytes)
+ break;
+ sunxi_nand->user_data_bytes[step] = c->user_data_len_tab[i];
+ }
+ remaining_bytes -= sunxi_nand->user_data_bytes[step];
+ if (sunxi_nand->user_data_bytes[step] == 0)
+ break;
+ }
+
+ return 0;
+}
+
static int sunxi_nand_hw_ecc_ctrl_init(struct nand_chip *nand,
struct nand_ecc_ctrl *ecc,
struct device_node *np)
@@ -1796,20 +1958,50 @@ static int sunxi_nand_hw_ecc_ctrl_init(struct nand_chip *nand,
const u8 *strengths = nfc->caps->ecc_strengths;
struct mtd_info *mtd = nand_to_mtd(nand);
struct nand_device *nanddev = mtd_to_nanddev(mtd);
+ int total_user_data_sz = 0;
int nsectors;
+ int ecc_mode;
int i;
if (nanddev->ecc.user_conf.flags & NAND_ECC_MAXIMIZE_STRENGTH) {
- int bytes;
+ int bytes = mtd->oobsize;
ecc->size = 1024;
nsectors = mtd->writesize / ecc->size;
- /* Reserve 2 bytes for the BBM */
- bytes = (mtd->oobsize - 2) / nsectors;
+ if (!nfc->caps->reg_user_data_len) {
+ /*
+ * If there's a fixed user data length, subtract it before
+ * computing the max ECC strength
+ */
- /* 4 non-ECC bytes are added before each ECC bytes section */
- bytes -= USER_DATA_SZ;
+ for (i = 0; i < nsectors; i++)
+ total_user_data_sz += sunxi_nfc_user_data_sz(sunxi_nand, i);
+
+ /*
+ * The 2 BBM bytes should not be removed from the grand total,
+ * because they are part of the USER_DATA_SZ.
+ * But we can't modify that for older platform since it may
+ * result in a stronger ECC at the end, and break the
+ * compatibility.
+ */
+ if (nfc->caps->legacy_max_strength)
+ bytes -= 2;
+
+ bytes -= total_user_data_sz;
+ } else {
+ /*
+ * remove at least the BBM size before computing the
+ * max ECC
+ */
+ bytes -= 2;
+ }
+
+ /*
+ * Once all user data has been subtracted, the rest can be used
+ * for ECC bytes
+ */
+ bytes /= nsectors;
/* and bytes has to be even. */
if (bytes % 2)
@@ -1838,18 +2030,18 @@ static int sunxi_nand_hw_ecc_ctrl_init(struct nand_chip *nand,
}
/* Add ECC info retrieval from DT */
- for (i = 0; i < nfc->caps->nstrengths; i++) {
- if (ecc->strength <= strengths[i]) {
+ for (ecc_mode = 0; ecc_mode < nfc->caps->nstrengths; ecc_mode++) {
+ if (ecc->strength <= strengths[ecc_mode]) {
/*
* Update ecc->strength value with the actual strength
* that will be used by the ECC engine.
*/
- ecc->strength = strengths[i];
+ ecc->strength = strengths[ecc_mode];
break;
}
}
- if (i >= nfc->caps->nstrengths) {
+ if (ecc_mode >= nfc->caps->nstrengths) {
dev_err(nfc->dev, "unsupported strength\n");
return -ENOTSUPP;
}
@@ -1862,7 +2054,19 @@ static int sunxi_nand_hw_ecc_ctrl_init(struct nand_chip *nand,
nsectors = mtd->writesize / ecc->size;
- if (mtd->oobsize < ((ecc->bytes + USER_DATA_SZ) * nsectors))
+ /*
+ * The rationale for variable data length is to prioritize maximum ECC
+ * strength, and then use the remaining space for user data.
+ */
+ if (nfc->caps->reg_user_data_len)
+ sunxi_nfc_maximize_user_data(nand, mtd->oobsize, ecc->bytes,
+ nsectors);
+
+ if (total_user_data_sz == 0)
+ for (i = 0; i < nsectors; i++)
+ total_user_data_sz += sunxi_nfc_user_data_sz(sunxi_nand, i);
+
+ if (mtd->oobsize < (ecc->bytes * nsectors + total_user_data_sz))
return -EINVAL;
ecc->read_oob = sunxi_nfc_hw_ecc_read_oob;
@@ -1885,7 +2089,7 @@ static int sunxi_nand_hw_ecc_ctrl_init(struct nand_chip *nand,
ecc->read_oob_raw = nand_read_oob_std;
ecc->write_oob_raw = nand_write_oob_std;
- sunxi_nand->ecc.ecc_ctl = NFC_ECC_MODE(nfc, i) | NFC_ECC_EXCEPTION |
+ sunxi_nand->ecc.ecc_ctl = NFC_ECC_MODE(nfc, ecc_mode) | NFC_ECC_EXCEPTION |
NFC_ECC_PIPELINE | NFC_ECC_EN;
if (ecc->size == 512) {
@@ -2092,6 +2296,7 @@ static int sunxi_nfc_exec_op(struct nand_chip *nand,
static const struct nand_controller_ops sunxi_nand_controller_ops = {
.attach_chip = sunxi_nand_attach_chip,
+ .detach_chip = sunxi_nand_detach_chip,
.setup_interface = sunxi_nfc_setup_interface,
.exec_op = sunxi_nfc_exec_op,
};
@@ -2373,6 +2578,7 @@ static const u8 sunxi_user_data_len_h6[] = {
static const struct sunxi_nfc_caps sunxi_nfc_a10_caps = {
.has_ecc_block_512 = true,
+ .legacy_max_strength = true,
.reg_io_data = NFC_REG_A10_IO_DATA,
.reg_ecc_err_cnt = NFC_REG_A10_ECC_ERR_CNT,
.reg_user_data = NFC_REG_A10_USER_DATA,
@@ -2394,6 +2600,7 @@ static const struct sunxi_nfc_caps sunxi_nfc_a10_caps = {
static const struct sunxi_nfc_caps sunxi_nfc_a23_caps = {
.has_mdma = true,
.has_ecc_block_512 = true,
+ .legacy_max_strength = true,
.reg_io_data = NFC_REG_A23_IO_DATA,
.reg_ecc_err_cnt = NFC_REG_A10_ECC_ERR_CNT,
.reg_user_data = NFC_REG_A10_USER_DATA,
diff --git a/drivers/mtd/nand/spi/winbond.c b/drivers/mtd/nand/spi/winbond.c
index 6dfd0dcc8ee7..ad22774096e6 100644
--- a/drivers/mtd/nand/spi/winbond.c
+++ b/drivers/mtd/nand/spi/winbond.c
@@ -337,16 +337,19 @@ static int w25n0xjw_hs_cfg(struct spinand_device *spinand,
if (iface != SSDR)
return -EOPNOTSUPP;
+ /*
+ * SDR dual and quad I/O operations over 104MHz require the HS bit to
+ * enable a few more dummy cycles.
+ */
op = spinand->op_templates->read_cache;
if (op->cmd.dtr || op->addr.dtr || op->dummy.dtr || op->data.dtr)
hs = false;
- else if (op->cmd.buswidth == 1 && op->addr.buswidth == 1 &&
- op->dummy.buswidth == 1 && op->data.buswidth == 1)
+ else if (op->cmd.buswidth != 1 || op->addr.buswidth == 1)
hs = false;
- else if (!op->max_freq)
- hs = true;
- else
+ else if (op->max_freq && op->max_freq <= 104 * HZ_PER_MHZ)
hs = false;
+ else
+ hs = true;
ret = spinand_read_reg_op(spinand, W25N0XJW_SR4, &sr4);
if (ret)
@@ -485,7 +488,7 @@ static const struct spinand_info winbond_spinand_table[] = {
SPINAND_INFO_OP_VARIANTS(&read_cache_dual_quad_dtr_variants,
&write_cache_variants,
&update_cache_variants),
- 0,
+ SPINAND_HAS_QE_BIT,
SPINAND_ECCINFO(&w25n01jw_ooblayout, NULL),
SPINAND_CONFIGURE_CHIP(w25n0xjw_hs_cfg)),
SPINAND_INFO("W25N01KV", /* 3.3V */
@@ -549,7 +552,7 @@ static const struct spinand_info winbond_spinand_table[] = {
SPINAND_INFO_OP_VARIANTS(&read_cache_dual_quad_dtr_variants,
&write_cache_variants,
&update_cache_variants),
- 0,
+ SPINAND_HAS_QE_BIT,
SPINAND_ECCINFO(&w25m02gv_ooblayout, NULL),
SPINAND_CONFIGURE_CHIP(w25n0xjw_hs_cfg)),
SPINAND_INFO("W25N02KV", /* 3.3V */
diff --git a/drivers/mtd/parsers/cmdlinepart.c b/drivers/mtd/parsers/cmdlinepart.c
index 504e5fa2b45b..4caf1b3804f2 100644
--- a/drivers/mtd/parsers/cmdlinepart.c
+++ b/drivers/mtd/parsers/cmdlinepart.c
@@ -50,9 +50,9 @@
struct cmdline_mtd_partition {
struct cmdline_mtd_partition *next;
- char *mtd_id;
int num_parts;
struct mtd_partition *parts;
+ char mtd_id[];
};
/* mtdpart_setup() parses into here */
@@ -289,7 +289,6 @@ static int mtdpart_setup_real(char *s)
/* enter results */
this_mtd->parts = parts;
this_mtd->num_parts = num_parts;
- this_mtd->mtd_id = (char*)(this_mtd + 1);
strscpy(this_mtd->mtd_id, mtd_id, mtd_id_len + 1);
/* link into chain */
diff --git a/drivers/mtd/parsers/ofpart_core.c b/drivers/mtd/parsers/ofpart_core.c
index 0029bda165bd..262c4221d23f 100644
--- a/drivers/mtd/parsers/ofpart_core.c
+++ b/drivers/mtd/parsers/ofpart_core.c
@@ -75,7 +75,7 @@ static int parse_fixed_partitions(struct mtd_info *master,
dedicated = false;
}
} else { /* Partition */
- ofpart_node = mtd_node;
+ ofpart_node = of_node_get(mtd_node);
}
of_id = of_match_node(parse_ofpart_match_table, ofpart_node);
@@ -195,11 +195,11 @@ static int parse_fixed_partitions(struct mtd_info *master,
ofpart_fail:
pr_err("%s: error parsing ofpart partition %pOF (%pOF)\n",
master->name, pp, mtd_node);
+ of_node_put(pp);
ret = -EINVAL;
ofpart_none:
if (dedicated)
of_node_put(ofpart_node);
- of_node_put(pp);
kfree(parts);
return ret;
}
diff --git a/drivers/mtd/spi-nor/core.c b/drivers/mtd/spi-nor/core.c
index 1eee519c01e5..5dd0b3cb5250 100644
--- a/drivers/mtd/spi-nor/core.c
+++ b/drivers/mtd/spi-nor/core.c
@@ -2393,7 +2393,7 @@ static int spi_nor_spimem_check_readop(struct spi_nor *nor,
/* convert the dummy cycles to the number of bytes */
op.dummy.nbytes = (read->num_mode_clocks + read->num_wait_states) *
op.dummy.buswidth / 8;
- if (spi_nor_protocol_is_dtr(nor->read_proto))
+ if (spi_nor_protocol_is_dtr(read->proto))
op.dummy.nbytes *= 2;
return spi_nor_spimem_check_read_pp_op(nor, &op);
diff --git a/drivers/mtd/spi-nor/core.h b/drivers/mtd/spi-nor/core.h
index 16b382d4f04f..e838c40a2589 100644
--- a/drivers/mtd/spi-nor/core.h
+++ b/drivers/mtd/spi-nor/core.h
@@ -413,7 +413,7 @@ struct spi_nor_flash_parameter {
* number of dummy cycles in read register ops.
* @smpt_map_id: called after map ID in SMPT table has been determined for the
* case the map ID is wrong and needs to be fixed.
- * @post_sfdp: called after SFDP has been parsed (is also called for SPI NORs
+ * @post_sfdp: called after SFDP has been parsed (is not called for SPI NORs
* that do not support RDSFDP). Typically used to tweak various
* parameters that could not be extracted by other means (i.e.
* when information provided by the SFDP/flash_info tables are
diff --git a/drivers/mtd/spi-nor/micron-st.c b/drivers/mtd/spi-nor/micron-st.c
index 88033384a71e..c75b0a1cd567 100644
--- a/drivers/mtd/spi-nor/micron-st.c
+++ b/drivers/mtd/spi-nor/micron-st.c
@@ -167,6 +167,16 @@ static int mt35xu512aba_post_sfdp_fixup(struct spi_nor *nor)
0, 20, SPINOR_OP_MT_DTR_RD,
SNOR_PROTO_8_8_8_DTR);
+ /*
+ * Some batches of mt35xu512aba do not contain the OCT DTR command
+ * information, but do support OCT DTR mode. Add the settings for
+ * SNOR_CMD_PP_8_8_8_DTR here. This also makes sure the flash can switch
+ * to OCT DTR mode.
+ */
+ nor->params->hwcaps.mask |= SNOR_HWCAPS_PP_8_8_8_DTR;
+ spi_nor_set_pp_settings(&nor->params->page_programs[SNOR_CMD_PP_8_8_8_DTR],
+ SPINOR_OP_PP_4B, SNOR_PROTO_8_8_8_DTR);
+
nor->cmd_ext_type = SPI_NOR_EXT_REPEAT;
nor->params->rdsr_dummy = 8;
nor->params->rdsr_addr_nbytes = 0;
@@ -185,7 +195,7 @@ static const struct spi_nor_fixups mt35xu512aba_fixups = {
.post_sfdp = mt35xu512aba_post_sfdp_fixup,
};
-static const struct spi_nor_fixups mt35xu01gbba_fixups = {
+static const struct spi_nor_fixups mt35_two_die_fixups = {
.post_sfdp = mt35xu512aba_post_sfdp_fixup,
.late_init = micron_st_nor_two_die_late_init,
};
@@ -202,25 +212,16 @@ static const struct flash_info micron_nor_parts[] = {
.id = SNOR_ID(0x2c, 0x5b, 0x1b),
.mfr_flags = USE_FSR,
.fixup_flags = SPI_NOR_IO_MODE_EN_VOLATILE,
- .fixups = &mt35xu01gbba_fixups,
+ .fixups = &mt35_two_die_fixups,
}, {
- /*
- * The MT35XU02GCBA flash device does not support chip erase,
- * according to its datasheet. It supports die erase, which
- * means the current driver implementation will likely need to
- * be converted to use die erase. Furthermore, similar to the
- * MT35XU01GBBA, the SPI_NOR_IO_MODE_EN_VOLATILE flag probably
- * needs to be enabled.
- *
- * TODO: Fix these and test on real hardware.
- */
.id = SNOR_ID(0x2c, 0x5b, 0x1c),
.name = "mt35xu02g",
.sector_size = SZ_128K,
.size = SZ_256M,
.no_sfdp_flags = SECT_4K | SPI_NOR_OCTAL_READ,
.mfr_flags = USE_FSR,
- .fixup_flags = SPI_NOR_4B_OPCODES,
+ .fixup_flags = SPI_NOR_4B_OPCODES | SPI_NOR_IO_MODE_EN_VOLATILE,
+ .fixups = &mt35_two_die_fixups,
},
};
diff --git a/drivers/mtd/spi-nor/sst.c b/drivers/mtd/spi-nor/sst.c
index 175211fe6a5e..db02c14ba16f 100644
--- a/drivers/mtd/spi-nor/sst.c
+++ b/drivers/mtd/spi-nor/sst.c
@@ -203,6 +203,8 @@ static int sst_nor_write(struct mtd_info *mtd, loff_t to, size_t len,
/* Start write from odd address. */
if (to % 2) {
+ bool needs_write_enable = (len > 1);
+
/* write one byte. */
ret = sst_nor_write_data(nor, to, 1, buf);
if (ret < 0)
@@ -210,6 +212,17 @@ static int sst_nor_write(struct mtd_info *mtd, loff_t to, size_t len,
to++;
actual++;
+
+ /*
+ * Byte program clears the write enable latch. If more
+ * data needs to be written using the AAI sequence,
+ * re-enable writes.
+ */
+ if (needs_write_enable) {
+ ret = spi_nor_write_enable(nor);
+ if (ret)
+ goto out;
+ }
}
/* Write out most of the data here. */
diff --git a/drivers/mtd/spi-nor/swp.c b/drivers/mtd/spi-nor/swp.c
index 9b07f83aeac7..e67a81dbb6bf 100644
--- a/drivers/mtd/spi-nor/swp.c
+++ b/drivers/mtd/spi-nor/swp.c
@@ -28,8 +28,10 @@ static u8 spi_nor_get_sr_tb_mask(struct spi_nor *nor)
{
if (nor->flags & SNOR_F_HAS_SR_TB_BIT6)
return SR_TB_BIT6;
- else
+ else if (nor->flags & SNOR_F_HAS_SR_TB)
return SR_TB_BIT5;
+ else
+ return 0;
}
static u64 spi_nor_get_min_prot_length_sr(struct spi_nor *nor)
diff --git a/drivers/mtd/spi-nor/winbond.c b/drivers/mtd/spi-nor/winbond.c
index fb855fe44733..eaa547d36aad 100644
--- a/drivers/mtd/spi-nor/winbond.c
+++ b/drivers/mtd/spi-nor/winbond.c
@@ -274,6 +274,7 @@ static const struct flash_info winbond_nor_parts[] = {
.id = SNOR_ID(0xef, 0x60, 0x19),
.name = "w25q256jw",
.size = SZ_32M,
+ .flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_TB_SR_BIT6 | SPI_NOR_4BIT_BP,
.no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ,
}, {
.id = SNOR_ID(0xef, 0x60, 0x20),
@@ -295,6 +296,7 @@ static const struct flash_info winbond_nor_parts[] = {
.id = SNOR_ID(0xef, 0x70, 0x17),
.name = "w25q64jvm",
.size = SZ_8M,
+ .flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB,
.no_sfdp_flags = SECT_4K,
}, {
.id = SNOR_ID(0xef, 0x70, 0x18),
@@ -337,7 +339,7 @@ static const struct flash_info winbond_nor_parts[] = {
.id = SNOR_ID(0xef, 0x80, 0x19),
.name = "w25q256jwm",
.size = SZ_32M,
- .flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB,
+ .flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_TB_SR_BIT6 | SPI_NOR_4BIT_BP,
.no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ,
}, {
.id = SNOR_ID(0xef, 0x80, 0x20),