Merge tag 'mtd/for-7.1' of git://git.kernel.org/pub/scm/linux/kernel/git/mtd/linux

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 7f77c561e2 ("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
  ...
This commit is contained in:
Linus Torvalds
2026-04-17 17:57:04 -07:00
40 changed files with 1126 additions and 440 deletions

View File

@@ -101,7 +101,7 @@ required:
unevaluatedProperties: false
allOf:
- $ref: nand-controller.yaml
- $ref: nand-controller-legacy.yaml
- if:
properties:

View File

@@ -10,22 +10,43 @@ maintainers:
- Uwe Kleine-König <u.kleine-koenig@pengutronix.de>
allOf:
- $ref: nand-controller.yaml
- $ref: nand-controller-legacy.yaml
properties:
compatible:
oneOf:
- const: fsl,imx27-nand
- enum:
- fsl,imx25-nand
- fsl,imx27-nand
- fsl,imx51-nand
- fsl,imx53-nand
- items:
- enum:
- fsl,imx35-nand
- const: fsl,imx25-nand
- items:
- enum:
- fsl,imx31-nand
- const: fsl,imx27-nand
reg:
maxItems: 1
minItems: 1
items:
- description: IP register space
- description: Nand flash internal buffer space
interrupts:
maxItems: 1
clocks:
maxItems: 1
dmas:
maxItems: 1
dma-names:
items:
- const: rx-tx
required:
- compatible
- reg

View File

@@ -11,6 +11,7 @@ maintainers:
allOf:
- $ref: mtd.yaml#
- $ref: nand-property.yaml
description: |
This file covers the generic description of a NAND chip. It implies that the
@@ -22,51 +23,6 @@ properties:
description:
Contains the chip-select IDs.
nand-ecc-engine:
description: |
A phandle on the hardware ECC engine if any. There are
basically three possibilities:
1/ The ECC engine is part of the NAND controller, in this
case the phandle should reference the parent node.
2/ The ECC engine is part of the NAND part (on-die), in this
case the phandle should reference the node itself.
3/ The ECC engine is external, in this case the phandle should
reference the specific ECC engine node.
$ref: /schemas/types.yaml#/definitions/phandle
nand-use-soft-ecc-engine:
description: Use a software ECC engine.
type: boolean
nand-no-ecc-engine:
description: Do not use any ECC correction.
type: boolean
nand-ecc-algo:
description:
Desired ECC algorithm.
$ref: /schemas/types.yaml#/definitions/string
enum: [hamming, bch, rs]
nand-ecc-strength:
description:
Maximum number of bits that can be corrected per ECC step.
$ref: /schemas/types.yaml#/definitions/uint32
minimum: 1
nand-ecc-step-size:
description:
Number of data bytes covered by a single ECC step.
$ref: /schemas/types.yaml#/definitions/uint32
minimum: 1
secure-regions:
description:
Regions in the NAND chip which are protected using a secure element
like Trustzone. This property contains the start address and size of
the secure regions present.
$ref: /schemas/types.yaml#/definitions/uint64-matrix
required:
- reg

View File

@@ -0,0 +1,65 @@
# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
%YAML 1.2
---
$id: http://devicetree.org/schemas/mtd/nand-controller-legacy.yaml#
$schema: http://devicetree.org/meta-schemas/core.yaml#
title: NAND Controller Common Properties
maintainers:
- Miquel Raynal <miquel.raynal@bootlin.com>
- Richard Weinberger <richard@nod.at>
description: >
The NAND controller should be represented with its own DT node, and
all NAND chips attached to this controller should be defined as
children nodes of the NAND controller. This representation should be
enforced even for simple controllers supporting only one chip.
This is only for legacy nand controller, new controller should use
nand-controller.yaml
properties:
"#address-cells":
const: 1
"#size-cells":
enum: [0, 1]
ranges: true
cs-gpios:
description:
Array of chip-select available to the controller. The first
entries are a 1:1 mapping of the available chip-select on the
NAND controller (even if they are not used). As many additional
chip-select as needed may follow and should be phandles of GPIO
lines. 'reg' entries of the NAND chip subnodes become indexes of
this array when this property is present.
minItems: 1
maxItems: 8
partitions:
type: object
required:
- compatible
patternProperties:
"^nand@[a-f0-9]$":
type: object
$ref: raw-nand-chip.yaml#
"^partition@[0-9a-f]+$":
type: object
$ref: /schemas/mtd/partitions/partition.yaml#/$defs/partition-node
deprecated: true
allOf:
- $ref: raw-nand-property.yaml#
- $ref: nand-property.yaml#
# This is a generic file other binding inherit from and extend
additionalProperties: true

View File

@@ -16,6 +16,8 @@ description: |
children nodes of the NAND controller. This representation should be
enforced even for simple controllers supporting only one chip.
select: false
properties:
$nodename:
pattern: "^nand-controller(@.*)?"

View File

@@ -0,0 +1,64 @@
# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
%YAML 1.2
---
$id: http://devicetree.org/schemas/mtd/nand-property.yaml#
$schema: http://devicetree.org/meta-schemas/core.yaml#
title: NAND Chip Common Properties
maintainers:
- Miquel Raynal <miquel.raynal@bootlin.com>
description: |
This file covers the generic properties of a NAND chip. It implies that the
bus interface should not be taken into account: both raw NAND devices and
SPI-NAND devices are concerned by this description.
properties:
nand-ecc-engine:
description: |
A phandle on the hardware ECC engine if any. There are
basically three possibilities:
1/ The ECC engine is part of the NAND controller, in this
case the phandle should reference the parent node.
2/ The ECC engine is part of the NAND part (on-die), in this
case the phandle should reference the node itself.
3/ The ECC engine is external, in this case the phandle should
reference the specific ECC engine node.
$ref: /schemas/types.yaml#/definitions/phandle
nand-use-soft-ecc-engine:
description: Use a software ECC engine.
type: boolean
nand-no-ecc-engine:
description: Do not use any ECC correction.
type: boolean
nand-ecc-algo:
description:
Desired ECC algorithm.
$ref: /schemas/types.yaml#/definitions/string
enum: [hamming, bch, rs]
nand-ecc-strength:
description:
Maximum number of bits that can be corrected per ECC step.
$ref: /schemas/types.yaml#/definitions/uint32
minimum: 1
nand-ecc-step-size:
description:
Number of data bytes covered by a single ECC step.
$ref: /schemas/types.yaml#/definitions/uint32
minimum: 1
secure-regions:
description:
Regions in the NAND chip which are protected using a secure element
like Trustzone. This property contains the start address and size of
the secure regions present.
$ref: /schemas/types.yaml#/definitions/uint64-matrix
# This file can be referenced by more specific devices (like spi-nands)
additionalProperties: true

View File

@@ -57,6 +57,15 @@ properties:
user space from
type: boolean
part-concat-next:
description: List of phandles to MTD partitions that need be concatenated
with the current partition.
$ref: /schemas/types.yaml#/definitions/phandle-array
minItems: 1
maxItems: 16
items:
maxItems: 1
align:
$ref: /schemas/types.yaml#/definitions/uint32
minimum: 2
@@ -180,4 +189,15 @@ examples:
reg = <0x200000 0x100000>;
align = <0x4000>;
};
part0: partition@400000 {
part-concat-next = <&part1>;
label = "part0_0";
reg = <0x400000 0x100000>;
};
part1: partition@800000 {
label = "part0_1";
reg = <0x800000 0x800000>;
};
};

View File

@@ -11,6 +11,7 @@ maintainers:
allOf:
- $ref: nand-chip.yaml#
- $ref: raw-nand-property.yaml#
description: |
The ECC strength and ECC step size properties define the user
@@ -31,79 +32,6 @@ properties:
description:
Contains the chip-select IDs.
nand-ecc-placement:
description:
Location of the ECC bytes. This location is unknown by default
but can be explicitly set to "oob", if all ECC bytes are
known to be stored in the OOB area, or "interleaved" if ECC
bytes will be interleaved with regular data in the main area.
$ref: /schemas/types.yaml#/definitions/string
enum: [ oob, interleaved ]
deprecated: true
nand-ecc-mode:
description:
Legacy ECC configuration mixing the ECC engine choice and
configuration.
$ref: /schemas/types.yaml#/definitions/string
enum: [none, soft, soft_bch, hw, hw_syndrome, on-die]
deprecated: true
nand-bus-width:
description:
Bus width to the NAND chip
$ref: /schemas/types.yaml#/definitions/uint32
enum: [8, 16]
default: 8
nand-on-flash-bbt:
description:
With this property, the OS will search the device for a Bad
Block Table (BBT). If not found, it will create one, reserve
a few blocks at the end of the device to store it and update
it as the device ages. Otherwise, the out-of-band area of a
few pages of all the blocks will be scanned at boot time to
find Bad Block Markers (BBM). These markers will help to
build a volatile BBT in RAM.
$ref: /schemas/types.yaml#/definitions/flag
nand-ecc-maximize:
description:
Whether or not the ECC strength should be maximized. The
maximum ECC strength is both controller and chip
dependent. The ECC engine has to select the ECC config
providing the best strength and taking the OOB area size
constraint into account. This is particularly useful when
only the in-band area is used by the upper layers, and you
want to make your NAND as reliable as possible.
$ref: /schemas/types.yaml#/definitions/flag
nand-is-boot-medium:
description:
Whether or not the NAND chip is a boot medium. Drivers might
use this information to select ECC algorithms supported by
the boot ROM or similar restrictions.
$ref: /schemas/types.yaml#/definitions/flag
nand-rb:
description:
Contains the native Ready/Busy IDs.
$ref: /schemas/types.yaml#/definitions/uint32-array
rb-gpios:
description:
Contains one or more GPIO descriptor (the numper of descriptor
depends on the number of R/B pins exposed by the flash) for the
Ready/Busy pins. Active state refers to the NAND ready state and
should be set to GPIOD_ACTIVE_HIGH unless the signal is inverted.
wp-gpios:
description:
Contains one GPIO descriptor for the Write Protect pin.
Active state refers to the NAND Write Protect state and should be
set to GPIOD_ACTIVE_LOW unless the signal is inverted.
maxItems: 1
required:
- reg

View File

@@ -0,0 +1,98 @@
# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
%YAML 1.2
---
$id: http://devicetree.org/schemas/mtd/raw-nand-property.yaml#
$schema: http://devicetree.org/meta-schemas/core.yaml#
title: Raw NAND Chip Common Properties
maintainers:
- Miquel Raynal <miquel.raynal@bootlin.com>
description: |
The ECC strength and ECC step size properties define the user
desires in terms of correction capability of a controller. Together,
they request the ECC engine to correct {strength} bit errors per
{size} bytes for a particular raw NAND chip.
The interpretation of these parameters is implementation-defined, so
not all implementations must support all possible
combinations. However, implementations are encouraged to further
specify the value(s) they support.
properties:
nand-ecc-placement:
description:
Location of the ECC bytes. This location is unknown by default
but can be explicitly set to "oob", if all ECC bytes are
known to be stored in the OOB area, or "interleaved" if ECC
bytes will be interleaved with regular data in the main area.
$ref: /schemas/types.yaml#/definitions/string
enum: [ oob, interleaved ]
deprecated: true
nand-ecc-mode:
description:
Legacy ECC configuration mixing the ECC engine choice and
configuration.
$ref: /schemas/types.yaml#/definitions/string
enum: [none, soft, soft_bch, hw, hw_syndrome, on-die]
deprecated: true
nand-bus-width:
description:
Bus width to the NAND chip
$ref: /schemas/types.yaml#/definitions/uint32
enum: [8, 16]
default: 8
nand-on-flash-bbt:
description:
With this property, the OS will search the device for a Bad
Block Table (BBT). If not found, it will create one, reserve
a few blocks at the end of the device to store it and update
it as the device ages. Otherwise, the out-of-band area of a
few pages of all the blocks will be scanned at boot time to
find Bad Block Markers (BBM). These markers will help to
build a volatile BBT in RAM.
$ref: /schemas/types.yaml#/definitions/flag
nand-ecc-maximize:
description:
Whether or not the ECC strength should be maximized. The
maximum ECC strength is both controller and chip
dependent. The ECC engine has to select the ECC config
providing the best strength and taking the OOB area size
constraint into account. This is particularly useful when
only the in-band area is used by the upper layers, and you
want to make your NAND as reliable as possible.
$ref: /schemas/types.yaml#/definitions/flag
nand-is-boot-medium:
description:
Whether or not the NAND chip is a boot medium. Drivers might
use this information to select ECC algorithms supported by
the boot ROM or similar restrictions.
$ref: /schemas/types.yaml#/definitions/flag
nand-rb:
description:
Contains the native Ready/Busy IDs.
$ref: /schemas/types.yaml#/definitions/uint32-array
rb-gpios:
description:
Contains one or more GPIO descriptor (the numper of descriptor
depends on the number of R/B pins exposed by the flash) for the
Ready/Busy pins. Active state refers to the NAND ready state and
should be set to GPIOD_ACTIVE_HIGH unless the signal is inverted.
wp-gpios:
description:
Contains one GPIO descriptor for the Write Protect pin.
Active state refers to the NAND Write Protect state and should be
set to GPIOD_ACTIVE_LOW unless the signal is inverted.
maxItems: 1
# This is a generic file other binding inherit from and extend
additionalProperties: true

View File

@@ -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"

View File

@@ -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/

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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;
}

View File

@@ -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

View File

@@ -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;

View File

@@ -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);

View File

@@ -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;
}

View File

@@ -20,18 +20,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

View File

@@ -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)

View File

@@ -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);

View File

@@ -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;
}

View File

@@ -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);

View File

@@ -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,6 +864,13 @@ static int fsl_ifc_chip_init(struct fsl_ifc_mtd *priv)
/* Fill in fsl_ifc_mtd structure */
mtd->dev.parent = priv->dev;
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 */

View File

@@ -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);
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;

View File

@@ -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,6 +1715,13 @@ static int mxcnd_probe(struct platform_device *pdev)
this->legacy.chip_delay = 5;
nand_set_controller_data(this, host);
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);

View File

@@ -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,7 +4705,7 @@ static void nand_resume(struct mtd_info *mtd)
{
struct nand_chip *chip = mtd_to_nand(mtd);
mutex_lock(&chip->lock);
scoped_guard(mutex, &chip->lock) {
if (chip->suspended) {
if (chip->ops.resume)
chip->ops.resume(chip);
@@ -4713,7 +4714,7 @@ static void nand_resume(struct mtd_info *mtd)
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);
}

View File

@@ -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 void sunxi_nfc_hw_ecc_get_prot_oob_bytes(struct nand_chip *nand, u8 *oob,
int step, bool bbm, int page)
static u8 sunxi_nfc_user_data_sz(struct sunxi_nand_chip *sunxi_nand, int step)
{
struct sunxi_nfc *nfc = to_sunxi_nfc(nand->controller);
if (!sunxi_nand->user_data_bytes)
return USER_DATA_SZ;
sunxi_nfc_user_data_to_buf(readl(nfc->regs + NFC_REG_USER_DATA(nfc, step)), oob);
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,
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;
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;
}
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;
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
* section.
*/
if (!section && ecc->engine_type == NAND_ECC_ENGINE_TYPE_ON_HOST) {
oobregion->offset = 2;
oobregion->length = 2;
return 0;
}
struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(nand);
unsigned int user_data_sz = sunxi_nfc_user_data_sz(sunxi_nand, section);
/*
* 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)
if (section >= ecc->steps)
return -ERANGE;
oobregion->offset = section * (ecc->bytes + USER_DATA_SZ);
/*
* The first 2 bytes are used for BB markers, hence we
* only have user_data_sz - 2 bytes available in the first user data
* section.
*/
if (section == 0) {
oobregion->offset = 2;
oobregion->length = user_data_sz - 2;
if (section < ecc->steps)
oobregion->length = USER_DATA_SZ;
else
oobregion->length = mtd->oobsize - oobregion->offset;
return 0;
}
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,

View File

@@ -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 && op->max_freq <= 104 * HZ_PER_MHZ)
hs = false;
else if (!op->max_freq)
hs = true;
else
hs = false;
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 */

View File

@@ -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 */

View File

@@ -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;
}

View File

@@ -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);

View File

@@ -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

View File

@@ -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,
},
};

View File

@@ -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. */

View File

@@ -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)

View File

@@ -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),

View File

@@ -9,6 +9,18 @@
#define MTD_CONCAT_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[];
};
struct mtd_info *mtd_concat_create(
struct mtd_info *subdev[], /* subdevices to concatenate */
int num_devs, /* number of subdevices */
@@ -16,5 +28,54 @@ struct mtd_info *mtd_concat_create(
void mtd_concat_destroy(struct mtd_info *mtd);
#endif
/**
* mtd_virt_concat_node_create - Create a component for concatenation
*
* Returns a positive number representing the no. of devices found for
* concatenation, or a negative error code.
*
* List all the devices for concatenations found in DT and create a
* component for concatenation.
*/
int mtd_virt_concat_node_create(void);
/**
* mtd_virt_concat_add - add mtd_info object to the list of subdevices for concatenation
* @mtd: pointer to new MTD device info structure
*
* Returns true if the mtd_info object is added successfully else returns false.
*
* The mtd_info object is added to the list of subdevices for concatenation.
* It returns true if a match is found, and false if all subdevices have
* already been added or if the mtd_info object does not match any of the
* intended MTD devices.
*/
bool mtd_virt_concat_add(struct mtd_info *mtd);
/**
* mtd_virt_concat_create_join - Create and register the concatenated MTD device
*
* Returns 0 on succes, or a negative error code.
*
* Creates and registers the concatenated MTD device
*/
int mtd_virt_concat_create_join(void);
/**
* mtd_virt_concat_destroy - Remove the concat that includes a specific mtd device
* as one of its components.
* @mtd: pointer to MTD device info structure.
*
* Returns 0 on succes, or a negative error code.
*
* If the mtd_info object is part of a concatenated device, all other MTD devices
* within that concat are registered individually. The concatenated device is then
* removed, along with its concatenation component.
*
*/
int mtd_virt_concat_destroy(struct mtd_info *mtd);
void mtd_virt_concat_destroy_joins(void);
void mtd_virt_concat_destroy_items(void);
#endif

View File

@@ -477,6 +477,7 @@ struct spinand_ecc_info {
const struct mtd_ooblayout_ops *ooblayout;
};
/* SPI NAND flags */
#define SPINAND_HAS_QE_BIT BIT(0)
#define SPINAND_HAS_CR_FEAT_BIT BIT(1)
#define SPINAND_HAS_PROG_PLANE_SELECT_BIT BIT(2)