diff options
| author | Linus Torvalds <torvalds@linux-foundation.org> | 2026-04-17 17:57:04 -0700 |
|---|---|---|
| committer | Linus Torvalds <torvalds@linux-foundation.org> | 2026-04-17 17:57:04 -0700 |
| commit | 8541d8f725c673db3bd741947f27974358b2e163 (patch) | |
| tree | a1e69d8655620db0043dbddef860b9da13d5f9e6 /drivers | |
| parent | a436a0b847c0fef9ead14f99bc03d8adbf66f15b (diff) | |
| parent | b2a4fe0960aee9a2c8045cfd26fbeacf30b26efe (diff) | |
| download | lwn-master.tar.gz lwn-master.zip | |
Pull MTD updates from Miquel Raynal:
"MTD changes:
- mtdconcat finally makes it in, after several years of being merged
and reverted
- Baikal SoC support is being removed, so MTD bits are being removed
as well
- misc cleanups
NAND changes:
- SunXi driver support for new versions of the Allwinner NAND
controller.
- DT-binding improvements and cleanups.
- A few fixes (Realtek ECC and Winbond SPI NAND), aside with the
usual load of misc changes.
SPI NOR fixes:
- Enable die erase on MT35XU02GCBA. We knew this flash needed this
fixup since 7f77c561e227 ("mtd: spi-nor: micron-st: add TODO for
fixing mt35xu02gcba") but did not add it due to lack of hardware to
test on.
- Fix locking on some Winbond w25q series flashes.
- Fix Auto Address Increment (AAI) writes on SST that flashes that
start on odd address. The write enable latch needs to be set again
after the single byte program"
* tag 'mtd/for-7.1' of git://git.kernel.org/pub/scm/linux/kernel/git/mtd/linux: (44 commits)
mtd: spinand: winbond: Declare the QE bit on W25NxxJW
mtd: spi-nor: micron-st: Enable die erase support for MT35XU02GCBA
mtd: spi-nor: winbond: Fix locking support for w25q256jw
mtd: spi-nor: sst: Fix write enable before AAI sequence
mtd: spi-nor: winbond: Fix locking support for w25q64jvm
mtd: spi-nor: winbond: Fix locking support for w25q256jwm
dt-bindings: mtd: mxc-nand: add missing compatible string and ref to nand-controller-legacy.yaml
dt-bindings: mtd: gpmi-nand: ref to nand-controller-legacy.yaml
dt-bindings: mtd: refactor NAND bindings and add nand-controller-legacy.yaml
mtd: spinand: winbond: Clarify when to enable the HS bit
mtd: rawnand: sunxi: introduce maximize variable user data length
mtd: rawnand: sunxi: fix typos in comments
mtd: rawnand: sunxi: change error prone variable name
mtd: rawnand: sunxi: remove dead code
mtd: rawnand: sunxi: make the code more self-explanatory
mtd: rawnand: sunxi: replace hard coded value by a define - take2
mtd: rawnand: sunxi: do not count BBM bytes twice
mtd: rawnand: sunxi: fix sunxi_nfc_hw_ecc_read_extra_oob
mtd: rawnand: sunxi: sunxi_nand_ooblayout_free code clarification
mtd: cmdlinepart: use a flexible array member
...
Diffstat (limited to 'drivers')
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), |
