mirror of
https://github.com/torvalds/linux.git
synced 2026-04-18 06:44:00 -04:00
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:
@@ -101,7 +101,7 @@ required:
|
|||||||
unevaluatedProperties: false
|
unevaluatedProperties: false
|
||||||
|
|
||||||
allOf:
|
allOf:
|
||||||
- $ref: nand-controller.yaml
|
- $ref: nand-controller-legacy.yaml
|
||||||
|
|
||||||
- if:
|
- if:
|
||||||
properties:
|
properties:
|
||||||
|
|||||||
@@ -10,22 +10,43 @@ maintainers:
|
|||||||
- Uwe Kleine-König <u.kleine-koenig@pengutronix.de>
|
- Uwe Kleine-König <u.kleine-koenig@pengutronix.de>
|
||||||
|
|
||||||
allOf:
|
allOf:
|
||||||
- $ref: nand-controller.yaml
|
- $ref: nand-controller-legacy.yaml
|
||||||
|
|
||||||
properties:
|
properties:
|
||||||
compatible:
|
compatible:
|
||||||
oneOf:
|
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:
|
- items:
|
||||||
- enum:
|
- enum:
|
||||||
- fsl,imx31-nand
|
- fsl,imx31-nand
|
||||||
- const: fsl,imx27-nand
|
- const: fsl,imx27-nand
|
||||||
reg:
|
reg:
|
||||||
maxItems: 1
|
minItems: 1
|
||||||
|
items:
|
||||||
|
- description: IP register space
|
||||||
|
- description: Nand flash internal buffer space
|
||||||
|
|
||||||
interrupts:
|
interrupts:
|
||||||
maxItems: 1
|
maxItems: 1
|
||||||
|
|
||||||
|
clocks:
|
||||||
|
maxItems: 1
|
||||||
|
|
||||||
|
dmas:
|
||||||
|
maxItems: 1
|
||||||
|
|
||||||
|
dma-names:
|
||||||
|
items:
|
||||||
|
- const: rx-tx
|
||||||
|
|
||||||
required:
|
required:
|
||||||
- compatible
|
- compatible
|
||||||
- reg
|
- reg
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ maintainers:
|
|||||||
|
|
||||||
allOf:
|
allOf:
|
||||||
- $ref: mtd.yaml#
|
- $ref: mtd.yaml#
|
||||||
|
- $ref: nand-property.yaml
|
||||||
|
|
||||||
description: |
|
description: |
|
||||||
This file covers the generic description of a NAND chip. It implies that the
|
This file covers the generic description of a NAND chip. It implies that the
|
||||||
@@ -22,51 +23,6 @@ properties:
|
|||||||
description:
|
description:
|
||||||
Contains the chip-select IDs.
|
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:
|
required:
|
||||||
- reg
|
- reg
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
@@ -16,6 +16,8 @@ description: |
|
|||||||
children nodes of the NAND controller. This representation should be
|
children nodes of the NAND controller. This representation should be
|
||||||
enforced even for simple controllers supporting only one chip.
|
enforced even for simple controllers supporting only one chip.
|
||||||
|
|
||||||
|
select: false
|
||||||
|
|
||||||
properties:
|
properties:
|
||||||
$nodename:
|
$nodename:
|
||||||
pattern: "^nand-controller(@.*)?"
|
pattern: "^nand-controller(@.*)?"
|
||||||
|
|||||||
64
Documentation/devicetree/bindings/mtd/nand-property.yaml
Normal file
64
Documentation/devicetree/bindings/mtd/nand-property.yaml
Normal 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
|
||||||
@@ -57,6 +57,15 @@ properties:
|
|||||||
user space from
|
user space from
|
||||||
type: boolean
|
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:
|
align:
|
||||||
$ref: /schemas/types.yaml#/definitions/uint32
|
$ref: /schemas/types.yaml#/definitions/uint32
|
||||||
minimum: 2
|
minimum: 2
|
||||||
@@ -180,4 +189,15 @@ examples:
|
|||||||
reg = <0x200000 0x100000>;
|
reg = <0x200000 0x100000>;
|
||||||
align = <0x4000>;
|
align = <0x4000>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
part0: partition@400000 {
|
||||||
|
part-concat-next = <&part1>;
|
||||||
|
label = "part0_0";
|
||||||
|
reg = <0x400000 0x100000>;
|
||||||
|
};
|
||||||
|
|
||||||
|
part1: partition@800000 {
|
||||||
|
label = "part0_1";
|
||||||
|
reg = <0x800000 0x800000>;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ maintainers:
|
|||||||
|
|
||||||
allOf:
|
allOf:
|
||||||
- $ref: nand-chip.yaml#
|
- $ref: nand-chip.yaml#
|
||||||
|
- $ref: raw-nand-property.yaml#
|
||||||
|
|
||||||
description: |
|
description: |
|
||||||
The ECC strength and ECC step size properties define the user
|
The ECC strength and ECC step size properties define the user
|
||||||
@@ -31,79 +32,6 @@ properties:
|
|||||||
description:
|
description:
|
||||||
Contains the chip-select IDs.
|
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:
|
required:
|
||||||
- reg
|
- reg
|
||||||
|
|
||||||
|
|||||||
98
Documentation/devicetree/bindings/mtd/raw-nand-property.yaml
Normal file
98
Documentation/devicetree/bindings/mtd/raw-nand-property.yaml
Normal 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
|
||||||
@@ -206,6 +206,15 @@ config MTD_PARTITIONED_MASTER
|
|||||||
the parent of the partition device be the master device, rather than
|
the parent of the partition device be the master device, rather than
|
||||||
what lies behind the master.
|
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/chips/Kconfig"
|
||||||
|
|
||||||
source "drivers/mtd/maps/Kconfig"
|
source "drivers/mtd/maps/Kconfig"
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
# Core functionality.
|
# Core functionality.
|
||||||
obj-$(CONFIG_MTD) += mtd.o
|
obj-$(CONFIG_MTD) += mtd.o
|
||||||
mtd-y := mtdcore.o mtdsuper.o mtdconcat.o mtdpart.o mtdchar.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/
|
obj-y += parsers/
|
||||||
|
|
||||||
|
|||||||
@@ -2049,7 +2049,6 @@ err_probe:
|
|||||||
static void docg3_release(struct platform_device *pdev)
|
static void docg3_release(struct platform_device *pdev)
|
||||||
{
|
{
|
||||||
struct docg3_cascade *cascade = platform_get_drvdata(pdev);
|
struct docg3_cascade *cascade = platform_get_drvdata(pdev);
|
||||||
struct docg3 *docg3 = cascade->floors[0]->priv;
|
|
||||||
int floor;
|
int floor;
|
||||||
|
|
||||||
doc_unregister_sysfs(pdev, cascade);
|
doc_unregister_sysfs(pdev, cascade);
|
||||||
@@ -2057,7 +2056,7 @@ static void docg3_release(struct platform_device *pdev)
|
|||||||
if (cascade->floors[floor])
|
if (cascade->floors[floor])
|
||||||
doc_release_device(cascade->floors[floor]);
|
doc_release_device(cascade->floors[floor]);
|
||||||
|
|
||||||
bch_free(docg3->cascade->bch);
|
bch_free(cascade->bch);
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef CONFIG_OF
|
#ifdef CONFIG_OF
|
||||||
|
|||||||
@@ -75,17 +75,6 @@ config MTD_PHYSMAP_OF
|
|||||||
physically into the CPU's memory. The mapping description here is
|
physically into the CPU's memory. The mapping description here is
|
||||||
taken from OF device tree.
|
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
|
config MTD_PHYSMAP_VERSATILE
|
||||||
bool "ARM Versatile OF-based physical memory map handling"
|
bool "ARM Versatile OF-based physical memory map handling"
|
||||||
depends on MTD_PHYSMAP_OF
|
depends on MTD_PHYSMAP_OF
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ obj-$(CONFIG_MTD_TSUNAMI) += tsunami_flash.o
|
|||||||
obj-$(CONFIG_MTD_PXA2XX) += pxa2xx-flash.o
|
obj-$(CONFIG_MTD_PXA2XX) += pxa2xx-flash.o
|
||||||
obj-$(CONFIG_MTD_PHYSMAP) += physmap.o
|
obj-$(CONFIG_MTD_PHYSMAP) += physmap.o
|
||||||
physmap-y := physmap-core.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_VERSATILE) += physmap-versatile.o
|
||||||
physmap-$(CONFIG_MTD_PHYSMAP_GEMINI) += physmap-gemini.o
|
physmap-$(CONFIG_MTD_PHYSMAP_GEMINI) += physmap-gemini.o
|
||||||
physmap-$(CONFIG_MTD_PHYSMAP_IXP4XX) += physmap-ixp4xx.o
|
physmap-$(CONFIG_MTD_PHYSMAP_IXP4XX) += physmap-ixp4xx.o
|
||||||
|
|||||||
@@ -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;
|
|
||||||
}
|
|
||||||
@@ -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
|
|
||||||
@@ -42,7 +42,6 @@
|
|||||||
#include <linux/pm_runtime.h>
|
#include <linux/pm_runtime.h>
|
||||||
#include <linux/gpio/consumer.h>
|
#include <linux/gpio/consumer.h>
|
||||||
|
|
||||||
#include "physmap-bt1-rom.h"
|
|
||||||
#include "physmap-gemini.h"
|
#include "physmap-gemini.h"
|
||||||
#include "physmap-ixp4xx.h"
|
#include "physmap-ixp4xx.h"
|
||||||
#include "physmap-versatile.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].bankwidth = bankwidth;
|
||||||
info->maps[i].device_node = dp;
|
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]);
|
err = of_flash_probe_gemini(dev, dp, &info->maps[i]);
|
||||||
if (err)
|
if (err)
|
||||||
return err;
|
return err;
|
||||||
|
|||||||
@@ -181,7 +181,7 @@ int of_flash_probe_gemini(struct platform_device *pdev,
|
|||||||
dev_err(dev, "no enabled pin control state\n");
|
dev_err(dev, "no enabled pin control state\n");
|
||||||
|
|
||||||
gf->disabled_state = pinctrl_lookup_state(gf->p, "disabled");
|
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");
|
dev_err(dev, "no disabled pin control state\n");
|
||||||
} else {
|
} else {
|
||||||
ret = pinctrl_select_state(gf->p, gf->disabled_state);
|
ret = pinctrl_select_state(gf->p, gf->disabled_state);
|
||||||
|
|||||||
350
drivers/mtd/mtd_virt_concat.c
Normal file
350
drivers/mtd/mtd_virt_concat.c
Normal 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;
|
||||||
|
}
|
||||||
@@ -20,18 +20,6 @@
|
|||||||
|
|
||||||
#include <asm/div64.h>
|
#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,
|
* how to calculate the size required for the above structure,
|
||||||
* including the pointer array subdev points to:
|
* 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)
|
const char *name)
|
||||||
{ /* name for the new device */
|
{ /* name for the new device */
|
||||||
int i;
|
int i;
|
||||||
size_t size;
|
|
||||||
struct mtd_concat *concat;
|
struct mtd_concat *concat;
|
||||||
struct mtd_info *subdev_master = NULL;
|
struct mtd_info *subdev_master = NULL;
|
||||||
uint32_t max_erasesize, curr_erasesize;
|
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);
|
printk(KERN_NOTICE "into device \"%s\"\n", name);
|
||||||
|
|
||||||
/* allocate the device structure */
|
/* allocate the device structure */
|
||||||
size = SIZEOF_STRUCT_MTD_CONCAT(num_devs);
|
concat = kzalloc_flex(*concat, subdev, num_devs, GFP_KERNEL);
|
||||||
concat = kzalloc(size, GFP_KERNEL);
|
|
||||||
if (!concat) {
|
if (!concat) {
|
||||||
printk
|
printk
|
||||||
("memory allocation error while creating concatenated device \"%s\"\n",
|
("memory allocation error while creating concatenated device \"%s\"\n",
|
||||||
name);
|
name);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
concat->subdev = (struct mtd_info **) (concat + 1);
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Set up the new "super" device's MTD object structure, check for
|
* Set up the new "super" device's MTD object structure, check for
|
||||||
|
|||||||
@@ -34,6 +34,7 @@
|
|||||||
|
|
||||||
#include <linux/mtd/mtd.h>
|
#include <linux/mtd/mtd.h>
|
||||||
#include <linux/mtd/partitions.h>
|
#include <linux/mtd/partitions.h>
|
||||||
|
#include <linux/mtd/concat.h>
|
||||||
|
|
||||||
#include "mtdcore.h"
|
#include "mtdcore.h"
|
||||||
|
|
||||||
@@ -1120,6 +1121,12 @@ int mtd_device_parse_register(struct mtd_info *mtd, const char * const *types,
|
|||||||
goto out;
|
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 */
|
/* Prefer parsed partitions over driver-provided fallback */
|
||||||
ret = parse_mtd_partitions(mtd, types, parser_data);
|
ret = parse_mtd_partitions(mtd, types, parser_data);
|
||||||
if (ret == -EPROBE_DEFER)
|
if (ret == -EPROBE_DEFER)
|
||||||
@@ -1137,6 +1144,11 @@ int mtd_device_parse_register(struct mtd_info *mtd, const char * const *types,
|
|||||||
if (ret)
|
if (ret)
|
||||||
goto out;
|
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.
|
* FIXME: some drivers unfortunately call this function more than once.
|
||||||
* So we have to check if we've already assigned the reboot notifier.
|
* 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_user_nvmem);
|
||||||
nvmem_unregister(master->otp_factory_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);
|
err = del_mtd_partitions(master);
|
||||||
if (err)
|
if (err)
|
||||||
return err;
|
return err;
|
||||||
@@ -2621,6 +2638,10 @@ err_reg:
|
|||||||
|
|
||||||
static void __exit cleanup_mtd(void)
|
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);
|
debugfs_remove_recursive(dfs_dir_mtd);
|
||||||
cleanup_mtdchar();
|
cleanup_mtdchar();
|
||||||
if (proc_mtd)
|
if (proc_mtd)
|
||||||
|
|||||||
@@ -18,6 +18,7 @@
|
|||||||
#include <linux/err.h>
|
#include <linux/err.h>
|
||||||
#include <linux/of.h>
|
#include <linux/of.h>
|
||||||
#include <linux/of_platform.h>
|
#include <linux/of_platform.h>
|
||||||
|
#include <linux/mtd/concat.h>
|
||||||
|
|
||||||
#include "mtdcore.h"
|
#include "mtdcore.h"
|
||||||
|
|
||||||
@@ -409,6 +410,11 @@ int add_mtd_partitions(struct mtd_info *parent,
|
|||||||
goto err_del_partitions;
|
goto err_del_partitions;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (IS_REACHABLE(CONFIG_MTD_VIRT_CONCAT)) {
|
||||||
|
if (mtd_virt_concat_add(child))
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
mutex_lock(&master->master.partitions_lock);
|
mutex_lock(&master->master.partitions_lock);
|
||||||
list_add_tail(&child->part.node, &parent->partitions);
|
list_add_tail(&child->part.node, &parent->partitions);
|
||||||
mutex_unlock(&master->master.partitions_lock);
|
mutex_unlock(&master->master.partitions_lock);
|
||||||
|
|||||||
@@ -17,10 +17,12 @@
|
|||||||
* - BCH12 : Generate 20 ECC bytes from 512 data bytes plus 6 free bytes
|
* - 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
|
* 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
|
* are a few known devices in the wild that make use of this ECC engine
|
||||||
* (Linksys LGS328C & LGS352C). To keep compatibility with vendor firmware, new modes can only
|
* (Linksys LGS328C, LGS352C & Netlink HG323DAC). To keep compatibility with vendor firmware,
|
||||||
* be added when new data layouts have been analyzed. For now allow BCH6 on flash with 2048 byte
|
* new modes can only be added when new data layouts have been analyzed. For now allow BCH6 on
|
||||||
* blocks and 64 bytes oob.
|
* 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
|
* 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.
|
* 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_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_ALLOWED_STRENGTH 6
|
||||||
|
|
||||||
#define RTL_ECC_BLOCK_SIZE 512
|
#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 mtd_info *mtd = nanddev_to_mtd(nand);
|
||||||
struct device *dev = nand->ecc.engine->dev;
|
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) {
|
mtd->writesize != RTL_ECC_ALLOWED_PAGE_SIZE) {
|
||||||
dev_err(dev, "only flash geometry data=%d, oob=%d supported\n",
|
dev_err(dev, "only flash geometry data=%d, oob>=%d supported\n",
|
||||||
RTL_ECC_ALLOWED_PAGE_SIZE, RTL_ECC_ALLOWED_OOB_SIZE);
|
RTL_ECC_ALLOWED_PAGE_SIZE, RTL_ECC_ALLOWED_MIN_OOB_SIZE);
|
||||||
return -EINVAL;
|
return -EINVAL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -837,9 +837,10 @@ static const struct pci_device_id cafe_nand_tbl[] = {
|
|||||||
|
|
||||||
MODULE_DEVICE_TABLE(pci, 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;
|
uint32_t ctrl;
|
||||||
|
struct pci_dev *pdev = to_pci_dev(dev);
|
||||||
struct mtd_info *mtd = pci_get_drvdata(pdev);
|
struct mtd_info *mtd = pci_get_drvdata(pdev);
|
||||||
struct nand_chip *chip = mtd_to_nand(mtd);
|
struct nand_chip *chip = mtd_to_nand(mtd);
|
||||||
struct cafe_priv *cafe = nand_get_controller_data(chip);
|
struct cafe_priv *cafe = nand_get_controller_data(chip);
|
||||||
@@ -877,12 +878,14 @@ static int cafe_nand_resume(struct pci_dev *pdev)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static DEFINE_SIMPLE_DEV_PM_OPS(cafe_nand_ops, NULL, cafe_nand_resume);
|
||||||
|
|
||||||
static struct pci_driver cafe_nand_pci_driver = {
|
static struct pci_driver cafe_nand_pci_driver = {
|
||||||
.name = "CAFÉ NAND",
|
.name = "CAFÉ NAND",
|
||||||
.id_table = cafe_nand_tbl,
|
.id_table = cafe_nand_tbl,
|
||||||
.probe = cafe_nand_probe,
|
.probe = cafe_nand_probe,
|
||||||
.remove = cafe_nand_remove,
|
.remove = cafe_nand_remove,
|
||||||
.resume = cafe_nand_resume,
|
.driver.pm = &cafe_nand_ops,
|
||||||
};
|
};
|
||||||
|
|
||||||
module_pci_driver(cafe_nand_pci_driver);
|
module_pci_driver(cafe_nand_pci_driver);
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
* Author: Dipen Dudhat <Dipen.Dudhat@freescale.com>
|
* Author: Dipen Dudhat <Dipen.Dudhat@freescale.com>
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include <linux/cleanup.h>
|
||||||
#include <linux/module.h>
|
#include <linux/module.h>
|
||||||
#include <linux/platform_device.h>
|
#include <linux/platform_device.h>
|
||||||
#include <linux/types.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 */
|
/* Fill in fsl_ifc_mtd structure */
|
||||||
mtd->dev.parent = priv->dev;
|
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 */
|
/* fill in nand_chip structure */
|
||||||
/* set up function call table */
|
/* set up function call table */
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
* Copyright (C) 2010-2015 Freescale Semiconductor, Inc.
|
* Copyright (C) 2010-2015 Freescale Semiconductor, Inc.
|
||||||
* Copyright (C) 2008 Embedded Alley Solutions, Inc.
|
* Copyright (C) 2008 Embedded Alley Solutions, Inc.
|
||||||
*/
|
*/
|
||||||
|
#include <linux/cleanup.h>
|
||||||
#include <linux/clk.h>
|
#include <linux/clk.h>
|
||||||
#include <linux/delay.h>
|
#include <linux/delay.h>
|
||||||
#include <linux/slab.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. */
|
/* init the nand_chip{}, we don't support a 16-bit NAND Flash bus. */
|
||||||
nand_set_controller_data(chip, this);
|
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->legacy.block_markbad = gpmi_block_markbad;
|
||||||
chip->badblock_pattern = &gpmi_bbt_descr;
|
chip->badblock_pattern = &gpmi_bbt_descr;
|
||||||
chip->options |= NAND_NO_SUBPAGE_WRITE;
|
chip->options |= NAND_NO_SUBPAGE_WRITE;
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
* Copyright 2008 Sascha Hauer, kernel@pengutronix.de
|
* Copyright 2008 Sascha Hauer, kernel@pengutronix.de
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include <linux/cleanup.h>
|
||||||
#include <linux/delay.h>
|
#include <linux/delay.h>
|
||||||
#include <linux/slab.h>
|
#include <linux/slab.h>
|
||||||
#include <linux/init.h>
|
#include <linux/init.h>
|
||||||
@@ -1714,7 +1715,14 @@ static int mxcnd_probe(struct platform_device *pdev)
|
|||||||
this->legacy.chip_delay = 5;
|
this->legacy.chip_delay = 5;
|
||||||
|
|
||||||
nand_set_controller_data(this, host);
|
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);
|
host->clk = devm_clk_get(&pdev->dev, NULL);
|
||||||
if (IS_ERR(host->clk))
|
if (IS_ERR(host->clk))
|
||||||
|
|||||||
@@ -43,6 +43,7 @@
|
|||||||
#include <linux/mtd/partitions.h>
|
#include <linux/mtd/partitions.h>
|
||||||
#include <linux/of.h>
|
#include <linux/of.h>
|
||||||
#include <linux/gpio/consumer.h>
|
#include <linux/gpio/consumer.h>
|
||||||
|
#include <linux/cleanup.h>
|
||||||
|
|
||||||
#include "internals.h"
|
#include "internals.h"
|
||||||
|
|
||||||
@@ -4704,16 +4705,16 @@ static void nand_resume(struct mtd_info *mtd)
|
|||||||
{
|
{
|
||||||
struct nand_chip *chip = mtd_to_nand(mtd);
|
struct nand_chip *chip = mtd_to_nand(mtd);
|
||||||
|
|
||||||
mutex_lock(&chip->lock);
|
scoped_guard(mutex, &chip->lock) {
|
||||||
if (chip->suspended) {
|
if (chip->suspended) {
|
||||||
if (chip->ops.resume)
|
if (chip->ops.resume)
|
||||||
chip->ops.resume(chip);
|
chip->ops.resume(chip);
|
||||||
chip->suspended = 0;
|
chip->suspended = 0;
|
||||||
} else {
|
} else {
|
||||||
pr_err("%s called for a chip which is not in suspended state\n",
|
pr_err("%s called for a chip which is not in suspended state\n",
|
||||||
__func__);
|
__func__);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
mutex_unlock(&chip->lock);
|
|
||||||
|
|
||||||
wake_up_all(&chip->resume_wq);
|
wake_up_all(&chip->resume_wq);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -209,9 +209,8 @@
|
|||||||
|
|
||||||
/*
|
/*
|
||||||
* On A10/A23, this is the size of the NDFC User Data Register, containing the
|
* 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.
|
* 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
|
* On H6/H616, this size became configurable, from 0 bytes to 32, via the
|
||||||
* USER_DATA_LEN registers.
|
* USER_DATA_LEN registers.
|
||||||
@@ -249,6 +248,7 @@ struct sunxi_nand_hw_ecc {
|
|||||||
* @timing_ctl: TIMING_CTL register value for this NAND chip
|
* @timing_ctl: TIMING_CTL register value for this NAND chip
|
||||||
* @nsels: number of CS lines required by the NAND chip
|
* @nsels: number of CS lines required by the NAND chip
|
||||||
* @sels: array of CS lines descriptions
|
* @sels: array of CS lines descriptions
|
||||||
|
* @user_data_bytes: array of user data lengths for all ECC steps
|
||||||
*/
|
*/
|
||||||
struct sunxi_nand_chip {
|
struct sunxi_nand_chip {
|
||||||
struct list_head node;
|
struct list_head node;
|
||||||
@@ -257,6 +257,7 @@ struct sunxi_nand_chip {
|
|||||||
unsigned long clk_rate;
|
unsigned long clk_rate;
|
||||||
u32 timing_cfg;
|
u32 timing_cfg;
|
||||||
u32 timing_ctl;
|
u32 timing_ctl;
|
||||||
|
u8 *user_data_bytes;
|
||||||
int nsels;
|
int nsels;
|
||||||
struct sunxi_nand_chip_sel sels[] __counted_by(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
|
* @has_mdma: Use mbus dma mode, otherwise general dma
|
||||||
* through MBUS on A23/A33 needs extra configuration.
|
* 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_ecc_clk: If the controller needs an ECC clock.
|
||||||
* @has_mbus_clk: If the controller needs a mbus 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_io_data: I/O data register
|
||||||
* @reg_ecc_err_cnt: ECC error counter register
|
* @reg_ecc_err_cnt: ECC error counter register
|
||||||
* @reg_user_data: User data 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
|
* @nstrengths: Size of @ecc_strengths
|
||||||
* @max_ecc_steps: Maximum supported steps for ECC, this is also the
|
* @max_ecc_steps: Maximum supported steps for ECC, this is also the
|
||||||
* number of user data registers
|
* 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
|
* The table index is the value to set in NFC_USER_DATA_LEN
|
||||||
* registers, and the corresponding value is the number of
|
* registers, and the corresponding value is the number of
|
||||||
* bytes to write
|
* bytes to write
|
||||||
@@ -304,6 +307,7 @@ struct sunxi_nfc_caps {
|
|||||||
bool has_ecc_block_512;
|
bool has_ecc_block_512;
|
||||||
bool has_ecc_clk;
|
bool has_ecc_clk;
|
||||||
bool has_mbus_clk;
|
bool has_mbus_clk;
|
||||||
|
bool legacy_max_strength;
|
||||||
unsigned int reg_io_data;
|
unsigned int reg_io_data;
|
||||||
unsigned int reg_ecc_err_cnt;
|
unsigned int reg_ecc_err_cnt;
|
||||||
unsigned int reg_user_data;
|
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);
|
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,
|
static u8 sunxi_nfc_user_data_sz(struct sunxi_nand_chip *sunxi_nand, int step)
|
||||||
int step, bool bbm, int page)
|
|
||||||
{
|
{
|
||||||
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. */
|
/* De-randomize the Bad Block Marker. */
|
||||||
if (bbm && (nand->options & NAND_NEED_SCRAMBLING))
|
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)
|
bool bbm, int page)
|
||||||
{
|
{
|
||||||
struct sunxi_nfc *nfc = to_sunxi_nfc(nand->controller);
|
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. */
|
/* Randomize the Bad Block Marker. */
|
||||||
if (bbm && (nand->options & NAND_NEED_SCRAMBLING)) {
|
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);
|
sunxi_nfc_randomize_bbm(nand, page, user_data);
|
||||||
oob = user_data;
|
oob = user_data;
|
||||||
}
|
}
|
||||||
|
|
||||||
writel(sunxi_nfc_buf_to_user_data(oob),
|
if (!nfc->caps->reg_user_data_len) {
|
||||||
nfc->regs + NFC_REG_USER_DATA(nfc, step));
|
/*
|
||||||
|
* 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,
|
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)
|
bool *erased)
|
||||||
{
|
{
|
||||||
struct sunxi_nfc *nfc = to_sunxi_nfc(nand->controller);
|
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;
|
struct nand_ecc_ctrl *ecc = &nand->ecc;
|
||||||
u32 tmp;
|
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);
|
memset(data, pattern, ecc->size);
|
||||||
|
|
||||||
if (oob)
|
if (oob)
|
||||||
memset(oob, pattern, ecc->bytes + USER_DATA_SZ);
|
memset(oob, pattern, ecc->bytes + user_data_sz);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@@ -952,14 +1025,19 @@ static int sunxi_nfc_hw_ecc_read_chunk(struct nand_chip *nand,
|
|||||||
u8 *oob, int oob_off,
|
u8 *oob, int oob_off,
|
||||||
int *cur_off,
|
int *cur_off,
|
||||||
unsigned int *max_bitflips,
|
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_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;
|
struct nand_ecc_ctrl *ecc = &nand->ecc;
|
||||||
int raw_mode = 0;
|
int raw_mode = 0;
|
||||||
u32 pattern_found;
|
u32 pattern_found;
|
||||||
|
bool bbm = !step;
|
||||||
bool erased;
|
bool erased;
|
||||||
int ret;
|
int ret;
|
||||||
|
/* From the controller point of view, we are at step 0 */
|
||||||
|
const int nfc_step = 0;
|
||||||
|
|
||||||
if (*cur_off != data_off)
|
if (*cur_off != data_off)
|
||||||
nand_change_read_column_op(nand, data_off, NULL, 0, false);
|
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)
|
if (ret)
|
||||||
return ret;
|
return ret;
|
||||||
|
|
||||||
sunxi_nfc_reset_user_data_len(nfc);
|
sunxi_nfc_set_user_data_len(nfc, user_data_sz, nfc_step);
|
||||||
sunxi_nfc_set_user_data_len(nfc, USER_DATA_SZ, 0);
|
|
||||||
sunxi_nfc_randomizer_config(nand, page, false);
|
sunxi_nfc_randomizer_config(nand, page, false);
|
||||||
sunxi_nfc_randomizer_enable(nand);
|
sunxi_nfc_randomizer_enable(nand);
|
||||||
writel(NFC_DATA_TRANS | NFC_DATA_SWAP_METHOD | NFC_ECC_OP,
|
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)
|
if (ret)
|
||||||
return 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 = readl(nfc->regs + nfc->caps->reg_pat_found);
|
||||||
pattern_found = field_get(NFC_ECC_PAT_FOUND_MSK(nfc), pattern_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,
|
ret = sunxi_nfc_hw_ecc_correct(nand, data, oob_required ? oob : NULL,
|
||||||
readl(nfc->regs + NFC_REG_ECC_ST),
|
nfc_step, readl(nfc->regs + NFC_REG_ECC_ST),
|
||||||
pattern_found,
|
pattern_found, &erased);
|
||||||
&erased);
|
|
||||||
if (erased)
|
if (erased)
|
||||||
return 1;
|
return 1;
|
||||||
|
|
||||||
@@ -1010,10 +1086,10 @@ static int sunxi_nfc_hw_ecc_read_chunk(struct nand_chip *nand,
|
|||||||
ecc->size);
|
ecc->size);
|
||||||
|
|
||||||
nand_change_read_column_op(nand, oob_off, oob,
|
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,
|
ret = nand_check_erased_ecc_chunk(data, ecc->size, oob,
|
||||||
ecc->bytes + USER_DATA_SZ,
|
ecc->bytes + user_data_sz,
|
||||||
NULL, 0, ecc->strength);
|
NULL, 0, ecc->strength);
|
||||||
if (ret >= 0)
|
if (ret >= 0)
|
||||||
raw_mode = 1;
|
raw_mode = 1;
|
||||||
@@ -1023,11 +1099,11 @@ static int sunxi_nfc_hw_ecc_read_chunk(struct nand_chip *nand,
|
|||||||
if (oob_required) {
|
if (oob_required) {
|
||||||
nand_change_read_column_op(nand, oob_off, NULL, 0,
|
nand_change_read_column_op(nand, oob_off, NULL, 0,
|
||||||
false);
|
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);
|
true, page);
|
||||||
|
|
||||||
sunxi_nfc_hw_ecc_get_prot_oob_bytes(nand, oob, 0,
|
sunxi_nfc_hw_ecc_get_prot_oob_bytes(nand, oob, nfc_step,
|
||||||
bbm, page);
|
bbm, page, user_data_sz);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1036,21 +1112,50 @@ static int sunxi_nfc_hw_ecc_read_chunk(struct nand_chip *nand,
|
|||||||
return raw_mode;
|
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,
|
static void sunxi_nfc_hw_ecc_read_extra_oob(struct nand_chip *nand,
|
||||||
u8 *oob, int *cur_off,
|
u8 *oob, int *cur_off,
|
||||||
bool randomize, int page)
|
bool randomize, int page)
|
||||||
{
|
{
|
||||||
|
struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(nand);
|
||||||
struct mtd_info *mtd = nand_to_mtd(nand);
|
struct mtd_info *mtd = nand_to_mtd(nand);
|
||||||
struct nand_ecc_ctrl *ecc = &nand->ecc;
|
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;
|
int len = mtd->oobsize - offset;
|
||||||
|
|
||||||
if (len <= 0)
|
if (len <= 0)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (!cur_off || *cur_off != offset)
|
if (!cur_off || *cur_off != (offset + mtd->writesize))
|
||||||
nand_change_read_column_op(nand, mtd->writesize, NULL, 0,
|
nand_change_read_column_op(nand, mtd->writesize + offset,
|
||||||
false);
|
NULL, 0, false);
|
||||||
|
|
||||||
if (!randomize)
|
if (!randomize)
|
||||||
sunxi_nfc_read_buf(nand, oob + offset, len);
|
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)
|
int nchunks)
|
||||||
{
|
{
|
||||||
bool randomized = nand->options & NAND_NEED_SCRAMBLING;
|
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 sunxi_nfc *nfc = to_sunxi_nfc(nand->controller);
|
||||||
struct mtd_info *mtd = nand_to_mtd(nand);
|
struct mtd_info *mtd = nand_to_mtd(nand);
|
||||||
struct nand_ecc_ctrl *ecc = &nand->ecc;
|
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_hw_ecc_enable(nand);
|
||||||
sunxi_nfc_reset_user_data_len(nfc);
|
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_config(nand, page, false);
|
||||||
sunxi_nfc_randomizer_enable(nand);
|
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++) {
|
for (i = 0; i < nchunks; i++) {
|
||||||
int data_off = i * ecc->size;
|
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 *data = buf + data_off;
|
||||||
u8 *oob = nand->oob_poi + oob_off;
|
u8 *oob = nand->oob_poi + oob_off;
|
||||||
bool erased;
|
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 */
|
/* TODO: use DMA to retrieve OOB */
|
||||||
nand_change_read_column_op(nand,
|
nand_change_read_column_op(nand,
|
||||||
mtd->writesize + oob_off,
|
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,
|
sunxi_nfc_hw_ecc_get_prot_oob_bytes(nand, oob, i, !i,
|
||||||
!i, page);
|
page, user_data_sz);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (erased)
|
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)) {
|
if (status & NFC_ECC_ERR_MSK(nfc)) {
|
||||||
for (i = 0; i < nchunks; i++) {
|
for (i = 0; i < nchunks; i++) {
|
||||||
int data_off = i * ecc->size;
|
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 *data = buf + data_off;
|
||||||
u8 *oob = nand->oob_poi + oob_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 */
|
/* TODO: use DMA to retrieve OOB */
|
||||||
nand_change_read_column_op(nand,
|
nand_change_read_column_op(nand,
|
||||||
mtd->writesize + oob_off,
|
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,
|
ret = nand_check_erased_ecc_chunk(data, ecc->size, oob,
|
||||||
ecc->bytes + USER_DATA_SZ,
|
ecc->bytes + user_data_sz,
|
||||||
NULL, 0,
|
NULL, 0,
|
||||||
ecc->strength);
|
ecc->strength);
|
||||||
if (ret >= 0)
|
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,
|
static int sunxi_nfc_hw_ecc_write_chunk(struct nand_chip *nand,
|
||||||
const u8 *data, int data_off,
|
const u8 *data, int data_off,
|
||||||
const u8 *oob, int oob_off,
|
const u8 *oob, int oob_off,
|
||||||
int *cur_off, bool bbm,
|
int *cur_off, int step,
|
||||||
int page)
|
int page)
|
||||||
{
|
{
|
||||||
struct sunxi_nfc *nfc = to_sunxi_nfc(nand->controller);
|
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;
|
struct nand_ecc_ctrl *ecc = &nand->ecc;
|
||||||
|
bool bbm = !step;
|
||||||
int ret;
|
int ret;
|
||||||
|
/* From the controller point of view, we are at step 0 */
|
||||||
|
const int nfc_step = 0;
|
||||||
|
|
||||||
if (data_off != *cur_off)
|
if (data_off != *cur_off)
|
||||||
nand_change_write_column_op(nand, data_off, NULL, 0, false);
|
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_config(nand, page, false);
|
||||||
sunxi_nfc_randomizer_enable(nand);
|
sunxi_nfc_randomizer_enable(nand);
|
||||||
sunxi_nfc_reset_user_data_len(nfc);
|
sunxi_nfc_set_user_data_len(nfc, user_data_sz, nfc_step);
|
||||||
sunxi_nfc_set_user_data_len(nfc, USER_DATA_SZ, 0);
|
sunxi_nfc_hw_ecc_set_prot_oob_bytes(nand, oob, nfc_step, bbm, page);
|
||||||
sunxi_nfc_hw_ecc_set_prot_oob_bytes(nand, oob, 0, bbm, page);
|
|
||||||
|
|
||||||
writel(NFC_DATA_TRANS | NFC_DATA_SWAP_METHOD |
|
writel(NFC_DATA_TRANS | NFC_DATA_SWAP_METHOD |
|
||||||
NFC_ACCESS_DIR | NFC_ECC_OP,
|
NFC_ACCESS_DIR | NFC_ECC_OP,
|
||||||
@@ -1232,7 +1345,7 @@ static int sunxi_nfc_hw_ecc_write_chunk(struct nand_chip *nand,
|
|||||||
if (ret)
|
if (ret)
|
||||||
return ret;
|
return ret;
|
||||||
|
|
||||||
*cur_off = oob_off + ecc->bytes + USER_DATA_SZ;
|
*cur_off = oob_off + ecc->bytes + user_data_sz;
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@@ -1242,8 +1355,9 @@ static void sunxi_nfc_hw_ecc_write_extra_oob(struct nand_chip *nand,
|
|||||||
int page)
|
int page)
|
||||||
{
|
{
|
||||||
struct mtd_info *mtd = nand_to_mtd(nand);
|
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;
|
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;
|
int len = mtd->oobsize - offset;
|
||||||
|
|
||||||
if (len <= 0)
|
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,
|
static int sunxi_nfc_hw_ecc_read_page(struct nand_chip *nand, uint8_t *buf,
|
||||||
int oob_required, int page)
|
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 mtd_info *mtd = nand_to_mtd(nand);
|
||||||
struct nand_ecc_ctrl *ecc = &nand->ecc;
|
struct nand_ecc_ctrl *ecc = &nand->ecc;
|
||||||
unsigned int max_bitflips = 0;
|
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_hw_ecc_enable(nand);
|
||||||
|
|
||||||
|
sunxi_nfc_reset_user_data_len(nfc);
|
||||||
for (i = 0; i < ecc->steps; i++) {
|
for (i = 0; i < ecc->steps; i++) {
|
||||||
int data_off = i * ecc->size;
|
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 *data = buf + data_off;
|
||||||
u8 *oob = nand->oob_poi + oob_off;
|
u8 *oob = nand->oob_poi + oob_off;
|
||||||
|
|
||||||
ret = sunxi_nfc_hw_ecc_read_chunk(nand, data, data_off, oob,
|
ret = sunxi_nfc_hw_ecc_read_chunk(nand, data, data_off, oob,
|
||||||
oob_off + mtd->writesize,
|
oob_off + mtd->writesize,
|
||||||
&cur_off, &max_bitflips,
|
&cur_off, &max_bitflips,
|
||||||
!i, oob_required, page);
|
i, oob_required, page);
|
||||||
if (ret < 0)
|
if (ret < 0)
|
||||||
return ret;
|
return ret;
|
||||||
else if (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,
|
u32 data_offs, u32 readlen,
|
||||||
u8 *bufpoi, int page)
|
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 mtd_info *mtd = nand_to_mtd(nand);
|
||||||
struct nand_ecc_ctrl *ecc = &nand->ecc;
|
struct nand_ecc_ctrl *ecc = &nand->ecc;
|
||||||
int ret, i, cur_off = 0;
|
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_hw_ecc_enable(nand);
|
||||||
|
|
||||||
|
sunxi_nfc_reset_user_data_len(nfc);
|
||||||
for (i = data_offs / ecc->size;
|
for (i = data_offs / ecc->size;
|
||||||
i < DIV_ROUND_UP(data_offs + readlen, ecc->size); i++) {
|
i < DIV_ROUND_UP(data_offs + readlen, ecc->size); i++) {
|
||||||
int data_off = i * ecc->size;
|
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 *data = bufpoi + data_off;
|
||||||
u8 *oob = nand->oob_poi + oob_off;
|
u8 *oob = nand->oob_poi + oob_off;
|
||||||
|
|
||||||
ret = sunxi_nfc_hw_ecc_read_chunk(nand, data, data_off,
|
ret = sunxi_nfc_hw_ecc_read_chunk(nand, data, data_off,
|
||||||
oob,
|
oob,
|
||||||
oob_off + mtd->writesize,
|
oob_off + mtd->writesize,
|
||||||
&cur_off, &max_bitflips, !i,
|
&cur_off, &max_bitflips, i,
|
||||||
false, page);
|
false, page);
|
||||||
if (ret < 0)
|
if (ret < 0)
|
||||||
return ret;
|
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,
|
const uint8_t *buf, int oob_required,
|
||||||
int page)
|
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 mtd_info *mtd = nand_to_mtd(nand);
|
||||||
struct nand_ecc_ctrl *ecc = &nand->ecc;
|
struct nand_ecc_ctrl *ecc = &nand->ecc;
|
||||||
int ret, i, cur_off = 0;
|
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_hw_ecc_enable(nand);
|
||||||
|
|
||||||
|
sunxi_nfc_reset_user_data_len(nfc);
|
||||||
for (i = 0; i < ecc->steps; i++) {
|
for (i = 0; i < ecc->steps; i++) {
|
||||||
int data_off = i * ecc->size;
|
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 *data = buf + data_off;
|
||||||
const u8 *oob = nand->oob_poi + oob_off;
|
const u8 *oob = nand->oob_poi + oob_off;
|
||||||
|
|
||||||
ret = sunxi_nfc_hw_ecc_write_chunk(nand, data, data_off, oob,
|
ret = sunxi_nfc_hw_ecc_write_chunk(nand, data, data_off, oob,
|
||||||
oob_off + mtd->writesize,
|
oob_off + mtd->writesize,
|
||||||
&cur_off, !i, page);
|
&cur_off, i, page);
|
||||||
if (ret)
|
if (ret)
|
||||||
return 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,
|
const u8 *buf, int oob_required,
|
||||||
int page)
|
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 mtd_info *mtd = nand_to_mtd(nand);
|
||||||
struct nand_ecc_ctrl *ecc = &nand->ecc;
|
struct nand_ecc_ctrl *ecc = &nand->ecc;
|
||||||
int ret, i, cur_off = 0;
|
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_hw_ecc_enable(nand);
|
||||||
|
|
||||||
|
sunxi_nfc_reset_user_data_len(nfc);
|
||||||
for (i = data_offs / ecc->size;
|
for (i = data_offs / ecc->size;
|
||||||
i < DIV_ROUND_UP(data_offs + data_len, ecc->size); i++) {
|
i < DIV_ROUND_UP(data_offs + data_len, ecc->size); i++) {
|
||||||
int data_off = i * ecc->size;
|
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 *data = buf + data_off;
|
||||||
const u8 *oob = nand->oob_poi + oob_off;
|
const u8 *oob = nand->oob_poi + oob_off;
|
||||||
|
|
||||||
ret = sunxi_nfc_hw_ecc_write_chunk(nand, data, data_off, oob,
|
ret = sunxi_nfc_hw_ecc_write_chunk(nand, data, data_off, oob,
|
||||||
oob_off + mtd->writesize,
|
oob_off + mtd->writesize,
|
||||||
&cur_off, !i, page);
|
&cur_off, i, page);
|
||||||
if (ret)
|
if (ret)
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
@@ -1449,6 +1575,7 @@ static int sunxi_nfc_hw_ecc_write_page_dma(struct nand_chip *nand,
|
|||||||
int page)
|
int page)
|
||||||
{
|
{
|
||||||
struct sunxi_nfc *nfc = to_sunxi_nfc(nand->controller);
|
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 nand_ecc_ctrl *ecc = &nand->ecc;
|
||||||
struct scatterlist sg;
|
struct scatterlist sg;
|
||||||
u32 wait;
|
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);
|
sunxi_nfc_reset_user_data_len(nfc);
|
||||||
for (i = 0; i < ecc->steps; i++) {
|
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_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);
|
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_chip *nand = mtd_to_nand(mtd);
|
||||||
struct nand_ecc_ctrl *ecc = &nand->ecc;
|
struct nand_ecc_ctrl *ecc = &nand->ecc;
|
||||||
|
struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(nand);
|
||||||
|
|
||||||
if (section >= ecc->steps)
|
if (section >= ecc->steps)
|
||||||
return -ERANGE;
|
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;
|
oobregion->length = ecc->bytes;
|
||||||
|
|
||||||
return 0;
|
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_chip *nand = mtd_to_nand(mtd);
|
||||||
struct nand_ecc_ctrl *ecc = &nand->ecc;
|
struct nand_ecc_ctrl *ecc = &nand->ecc;
|
||||||
|
struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(nand);
|
||||||
if (section > ecc->steps)
|
unsigned int user_data_sz = sunxi_nfc_user_data_sz(sunxi_nand, section);
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* The controller does not provide access to OOB bytes
|
* The controller does not provide access to OOB bytes
|
||||||
* past the end of the ECC data.
|
* 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;
|
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)
|
return 0;
|
||||||
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;
|
return 0;
|
||||||
}
|
}
|
||||||
@@ -1787,6 +1912,43 @@ static const struct mtd_ooblayout_ops sunxi_nand_ooblayout_ops = {
|
|||||||
.free = sunxi_nand_ooblayout_free,
|
.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,
|
static int sunxi_nand_hw_ecc_ctrl_init(struct nand_chip *nand,
|
||||||
struct nand_ecc_ctrl *ecc,
|
struct nand_ecc_ctrl *ecc,
|
||||||
struct device_node *np)
|
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;
|
const u8 *strengths = nfc->caps->ecc_strengths;
|
||||||
struct mtd_info *mtd = nand_to_mtd(nand);
|
struct mtd_info *mtd = nand_to_mtd(nand);
|
||||||
struct nand_device *nanddev = mtd_to_nanddev(mtd);
|
struct nand_device *nanddev = mtd_to_nanddev(mtd);
|
||||||
|
int total_user_data_sz = 0;
|
||||||
int nsectors;
|
int nsectors;
|
||||||
|
int ecc_mode;
|
||||||
int i;
|
int i;
|
||||||
|
|
||||||
if (nanddev->ecc.user_conf.flags & NAND_ECC_MAXIMIZE_STRENGTH) {
|
if (nanddev->ecc.user_conf.flags & NAND_ECC_MAXIMIZE_STRENGTH) {
|
||||||
int bytes;
|
int bytes = mtd->oobsize;
|
||||||
|
|
||||||
ecc->size = 1024;
|
ecc->size = 1024;
|
||||||
nsectors = mtd->writesize / ecc->size;
|
nsectors = mtd->writesize / ecc->size;
|
||||||
|
|
||||||
/* Reserve 2 bytes for the BBM */
|
if (!nfc->caps->reg_user_data_len) {
|
||||||
bytes = (mtd->oobsize - 2) / nsectors;
|
/*
|
||||||
|
* 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 */
|
for (i = 0; i < nsectors; i++)
|
||||||
bytes -= USER_DATA_SZ;
|
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. */
|
/* and bytes has to be even. */
|
||||||
if (bytes % 2)
|
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 */
|
/* Add ECC info retrieval from DT */
|
||||||
for (i = 0; i < nfc->caps->nstrengths; i++) {
|
for (ecc_mode = 0; ecc_mode < nfc->caps->nstrengths; ecc_mode++) {
|
||||||
if (ecc->strength <= strengths[i]) {
|
if (ecc->strength <= strengths[ecc_mode]) {
|
||||||
/*
|
/*
|
||||||
* Update ecc->strength value with the actual strength
|
* Update ecc->strength value with the actual strength
|
||||||
* that will be used by the ECC engine.
|
* that will be used by the ECC engine.
|
||||||
*/
|
*/
|
||||||
ecc->strength = strengths[i];
|
ecc->strength = strengths[ecc_mode];
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (i >= nfc->caps->nstrengths) {
|
if (ecc_mode >= nfc->caps->nstrengths) {
|
||||||
dev_err(nfc->dev, "unsupported strength\n");
|
dev_err(nfc->dev, "unsupported strength\n");
|
||||||
return -ENOTSUPP;
|
return -ENOTSUPP;
|
||||||
}
|
}
|
||||||
@@ -1862,7 +2054,19 @@ static int sunxi_nand_hw_ecc_ctrl_init(struct nand_chip *nand,
|
|||||||
|
|
||||||
nsectors = mtd->writesize / ecc->size;
|
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;
|
return -EINVAL;
|
||||||
|
|
||||||
ecc->read_oob = sunxi_nfc_hw_ecc_read_oob;
|
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->read_oob_raw = nand_read_oob_std;
|
||||||
ecc->write_oob_raw = nand_write_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;
|
NFC_ECC_PIPELINE | NFC_ECC_EN;
|
||||||
|
|
||||||
if (ecc->size == 512) {
|
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 = {
|
static const struct nand_controller_ops sunxi_nand_controller_ops = {
|
||||||
.attach_chip = sunxi_nand_attach_chip,
|
.attach_chip = sunxi_nand_attach_chip,
|
||||||
|
.detach_chip = sunxi_nand_detach_chip,
|
||||||
.setup_interface = sunxi_nfc_setup_interface,
|
.setup_interface = sunxi_nfc_setup_interface,
|
||||||
.exec_op = sunxi_nfc_exec_op,
|
.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 = {
|
static const struct sunxi_nfc_caps sunxi_nfc_a10_caps = {
|
||||||
.has_ecc_block_512 = true,
|
.has_ecc_block_512 = true,
|
||||||
|
.legacy_max_strength = true,
|
||||||
.reg_io_data = NFC_REG_A10_IO_DATA,
|
.reg_io_data = NFC_REG_A10_IO_DATA,
|
||||||
.reg_ecc_err_cnt = NFC_REG_A10_ECC_ERR_CNT,
|
.reg_ecc_err_cnt = NFC_REG_A10_ECC_ERR_CNT,
|
||||||
.reg_user_data = NFC_REG_A10_USER_DATA,
|
.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 = {
|
static const struct sunxi_nfc_caps sunxi_nfc_a23_caps = {
|
||||||
.has_mdma = true,
|
.has_mdma = true,
|
||||||
.has_ecc_block_512 = true,
|
.has_ecc_block_512 = true,
|
||||||
|
.legacy_max_strength = true,
|
||||||
.reg_io_data = NFC_REG_A23_IO_DATA,
|
.reg_io_data = NFC_REG_A23_IO_DATA,
|
||||||
.reg_ecc_err_cnt = NFC_REG_A10_ECC_ERR_CNT,
|
.reg_ecc_err_cnt = NFC_REG_A10_ECC_ERR_CNT,
|
||||||
.reg_user_data = NFC_REG_A10_USER_DATA,
|
.reg_user_data = NFC_REG_A10_USER_DATA,
|
||||||
|
|||||||
@@ -337,16 +337,19 @@ static int w25n0xjw_hs_cfg(struct spinand_device *spinand,
|
|||||||
if (iface != SSDR)
|
if (iface != SSDR)
|
||||||
return -EOPNOTSUPP;
|
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;
|
op = spinand->op_templates->read_cache;
|
||||||
if (op->cmd.dtr || op->addr.dtr || op->dummy.dtr || op->data.dtr)
|
if (op->cmd.dtr || op->addr.dtr || op->dummy.dtr || op->data.dtr)
|
||||||
hs = false;
|
hs = false;
|
||||||
else if (op->cmd.buswidth == 1 && op->addr.buswidth == 1 &&
|
else if (op->cmd.buswidth != 1 || op->addr.buswidth == 1)
|
||||||
op->dummy.buswidth == 1 && op->data.buswidth == 1)
|
hs = false;
|
||||||
|
else if (op->max_freq && op->max_freq <= 104 * HZ_PER_MHZ)
|
||||||
hs = false;
|
hs = false;
|
||||||
else if (!op->max_freq)
|
|
||||||
hs = true;
|
|
||||||
else
|
else
|
||||||
hs = false;
|
hs = true;
|
||||||
|
|
||||||
ret = spinand_read_reg_op(spinand, W25N0XJW_SR4, &sr4);
|
ret = spinand_read_reg_op(spinand, W25N0XJW_SR4, &sr4);
|
||||||
if (ret)
|
if (ret)
|
||||||
@@ -485,7 +488,7 @@ static const struct spinand_info winbond_spinand_table[] = {
|
|||||||
SPINAND_INFO_OP_VARIANTS(&read_cache_dual_quad_dtr_variants,
|
SPINAND_INFO_OP_VARIANTS(&read_cache_dual_quad_dtr_variants,
|
||||||
&write_cache_variants,
|
&write_cache_variants,
|
||||||
&update_cache_variants),
|
&update_cache_variants),
|
||||||
0,
|
SPINAND_HAS_QE_BIT,
|
||||||
SPINAND_ECCINFO(&w25n01jw_ooblayout, NULL),
|
SPINAND_ECCINFO(&w25n01jw_ooblayout, NULL),
|
||||||
SPINAND_CONFIGURE_CHIP(w25n0xjw_hs_cfg)),
|
SPINAND_CONFIGURE_CHIP(w25n0xjw_hs_cfg)),
|
||||||
SPINAND_INFO("W25N01KV", /* 3.3V */
|
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,
|
SPINAND_INFO_OP_VARIANTS(&read_cache_dual_quad_dtr_variants,
|
||||||
&write_cache_variants,
|
&write_cache_variants,
|
||||||
&update_cache_variants),
|
&update_cache_variants),
|
||||||
0,
|
SPINAND_HAS_QE_BIT,
|
||||||
SPINAND_ECCINFO(&w25m02gv_ooblayout, NULL),
|
SPINAND_ECCINFO(&w25m02gv_ooblayout, NULL),
|
||||||
SPINAND_CONFIGURE_CHIP(w25n0xjw_hs_cfg)),
|
SPINAND_CONFIGURE_CHIP(w25n0xjw_hs_cfg)),
|
||||||
SPINAND_INFO("W25N02KV", /* 3.3V */
|
SPINAND_INFO("W25N02KV", /* 3.3V */
|
||||||
|
|||||||
@@ -50,9 +50,9 @@
|
|||||||
|
|
||||||
struct cmdline_mtd_partition {
|
struct cmdline_mtd_partition {
|
||||||
struct cmdline_mtd_partition *next;
|
struct cmdline_mtd_partition *next;
|
||||||
char *mtd_id;
|
|
||||||
int num_parts;
|
int num_parts;
|
||||||
struct mtd_partition *parts;
|
struct mtd_partition *parts;
|
||||||
|
char mtd_id[];
|
||||||
};
|
};
|
||||||
|
|
||||||
/* mtdpart_setup() parses into here */
|
/* mtdpart_setup() parses into here */
|
||||||
@@ -289,7 +289,6 @@ static int mtdpart_setup_real(char *s)
|
|||||||
/* enter results */
|
/* enter results */
|
||||||
this_mtd->parts = parts;
|
this_mtd->parts = parts;
|
||||||
this_mtd->num_parts = num_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);
|
strscpy(this_mtd->mtd_id, mtd_id, mtd_id_len + 1);
|
||||||
|
|
||||||
/* link into chain */
|
/* link into chain */
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ static int parse_fixed_partitions(struct mtd_info *master,
|
|||||||
dedicated = false;
|
dedicated = false;
|
||||||
}
|
}
|
||||||
} else { /* Partition */
|
} else { /* Partition */
|
||||||
ofpart_node = mtd_node;
|
ofpart_node = of_node_get(mtd_node);
|
||||||
}
|
}
|
||||||
|
|
||||||
of_id = of_match_node(parse_ofpart_match_table, ofpart_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:
|
ofpart_fail:
|
||||||
pr_err("%s: error parsing ofpart partition %pOF (%pOF)\n",
|
pr_err("%s: error parsing ofpart partition %pOF (%pOF)\n",
|
||||||
master->name, pp, mtd_node);
|
master->name, pp, mtd_node);
|
||||||
|
of_node_put(pp);
|
||||||
ret = -EINVAL;
|
ret = -EINVAL;
|
||||||
ofpart_none:
|
ofpart_none:
|
||||||
if (dedicated)
|
if (dedicated)
|
||||||
of_node_put(ofpart_node);
|
of_node_put(ofpart_node);
|
||||||
of_node_put(pp);
|
|
||||||
kfree(parts);
|
kfree(parts);
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2393,7 +2393,7 @@ static int spi_nor_spimem_check_readop(struct spi_nor *nor,
|
|||||||
/* convert the dummy cycles to the number of bytes */
|
/* convert the dummy cycles to the number of bytes */
|
||||||
op.dummy.nbytes = (read->num_mode_clocks + read->num_wait_states) *
|
op.dummy.nbytes = (read->num_mode_clocks + read->num_wait_states) *
|
||||||
op.dummy.buswidth / 8;
|
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;
|
op.dummy.nbytes *= 2;
|
||||||
|
|
||||||
return spi_nor_spimem_check_read_pp_op(nor, &op);
|
return spi_nor_spimem_check_read_pp_op(nor, &op);
|
||||||
|
|||||||
@@ -413,7 +413,7 @@ struct spi_nor_flash_parameter {
|
|||||||
* number of dummy cycles in read register ops.
|
* number of dummy cycles in read register ops.
|
||||||
* @smpt_map_id: called after map ID in SMPT table has been determined for the
|
* @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.
|
* 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
|
* that do not support RDSFDP). Typically used to tweak various
|
||||||
* parameters that could not be extracted by other means (i.e.
|
* parameters that could not be extracted by other means (i.e.
|
||||||
* when information provided by the SFDP/flash_info tables are
|
* when information provided by the SFDP/flash_info tables are
|
||||||
|
|||||||
@@ -167,6 +167,16 @@ static int mt35xu512aba_post_sfdp_fixup(struct spi_nor *nor)
|
|||||||
0, 20, SPINOR_OP_MT_DTR_RD,
|
0, 20, SPINOR_OP_MT_DTR_RD,
|
||||||
SNOR_PROTO_8_8_8_DTR);
|
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->cmd_ext_type = SPI_NOR_EXT_REPEAT;
|
||||||
nor->params->rdsr_dummy = 8;
|
nor->params->rdsr_dummy = 8;
|
||||||
nor->params->rdsr_addr_nbytes = 0;
|
nor->params->rdsr_addr_nbytes = 0;
|
||||||
@@ -185,7 +195,7 @@ static const struct spi_nor_fixups mt35xu512aba_fixups = {
|
|||||||
.post_sfdp = mt35xu512aba_post_sfdp_fixup,
|
.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,
|
.post_sfdp = mt35xu512aba_post_sfdp_fixup,
|
||||||
.late_init = micron_st_nor_two_die_late_init,
|
.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),
|
.id = SNOR_ID(0x2c, 0x5b, 0x1b),
|
||||||
.mfr_flags = USE_FSR,
|
.mfr_flags = USE_FSR,
|
||||||
.fixup_flags = SPI_NOR_IO_MODE_EN_VOLATILE,
|
.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),
|
.id = SNOR_ID(0x2c, 0x5b, 0x1c),
|
||||||
.name = "mt35xu02g",
|
.name = "mt35xu02g",
|
||||||
.sector_size = SZ_128K,
|
.sector_size = SZ_128K,
|
||||||
.size = SZ_256M,
|
.size = SZ_256M,
|
||||||
.no_sfdp_flags = SECT_4K | SPI_NOR_OCTAL_READ,
|
.no_sfdp_flags = SECT_4K | SPI_NOR_OCTAL_READ,
|
||||||
.mfr_flags = USE_FSR,
|
.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,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -203,6 +203,8 @@ static int sst_nor_write(struct mtd_info *mtd, loff_t to, size_t len,
|
|||||||
|
|
||||||
/* Start write from odd address. */
|
/* Start write from odd address. */
|
||||||
if (to % 2) {
|
if (to % 2) {
|
||||||
|
bool needs_write_enable = (len > 1);
|
||||||
|
|
||||||
/* write one byte. */
|
/* write one byte. */
|
||||||
ret = sst_nor_write_data(nor, to, 1, buf);
|
ret = sst_nor_write_data(nor, to, 1, buf);
|
||||||
if (ret < 0)
|
if (ret < 0)
|
||||||
@@ -210,6 +212,17 @@ static int sst_nor_write(struct mtd_info *mtd, loff_t to, size_t len,
|
|||||||
|
|
||||||
to++;
|
to++;
|
||||||
actual++;
|
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. */
|
/* Write out most of the data here. */
|
||||||
|
|||||||
@@ -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)
|
if (nor->flags & SNOR_F_HAS_SR_TB_BIT6)
|
||||||
return SR_TB_BIT6;
|
return SR_TB_BIT6;
|
||||||
else
|
else if (nor->flags & SNOR_F_HAS_SR_TB)
|
||||||
return SR_TB_BIT5;
|
return SR_TB_BIT5;
|
||||||
|
else
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static u64 spi_nor_get_min_prot_length_sr(struct spi_nor *nor)
|
static u64 spi_nor_get_min_prot_length_sr(struct spi_nor *nor)
|
||||||
|
|||||||
@@ -274,6 +274,7 @@ static const struct flash_info winbond_nor_parts[] = {
|
|||||||
.id = SNOR_ID(0xef, 0x60, 0x19),
|
.id = SNOR_ID(0xef, 0x60, 0x19),
|
||||||
.name = "w25q256jw",
|
.name = "w25q256jw",
|
||||||
.size = SZ_32M,
|
.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,
|
.no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ,
|
||||||
}, {
|
}, {
|
||||||
.id = SNOR_ID(0xef, 0x60, 0x20),
|
.id = SNOR_ID(0xef, 0x60, 0x20),
|
||||||
@@ -295,6 +296,7 @@ static const struct flash_info winbond_nor_parts[] = {
|
|||||||
.id = SNOR_ID(0xef, 0x70, 0x17),
|
.id = SNOR_ID(0xef, 0x70, 0x17),
|
||||||
.name = "w25q64jvm",
|
.name = "w25q64jvm",
|
||||||
.size = SZ_8M,
|
.size = SZ_8M,
|
||||||
|
.flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB,
|
||||||
.no_sfdp_flags = SECT_4K,
|
.no_sfdp_flags = SECT_4K,
|
||||||
}, {
|
}, {
|
||||||
.id = SNOR_ID(0xef, 0x70, 0x18),
|
.id = SNOR_ID(0xef, 0x70, 0x18),
|
||||||
@@ -337,7 +339,7 @@ static const struct flash_info winbond_nor_parts[] = {
|
|||||||
.id = SNOR_ID(0xef, 0x80, 0x19),
|
.id = SNOR_ID(0xef, 0x80, 0x19),
|
||||||
.name = "w25q256jwm",
|
.name = "w25q256jwm",
|
||||||
.size = SZ_32M,
|
.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,
|
.no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ,
|
||||||
}, {
|
}, {
|
||||||
.id = SNOR_ID(0xef, 0x80, 0x20),
|
.id = SNOR_ID(0xef, 0x80, 0x20),
|
||||||
|
|||||||
@@ -9,6 +9,18 @@
|
|||||||
#define MTD_CONCAT_H
|
#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 *mtd_concat_create(
|
||||||
struct mtd_info *subdev[], /* subdevices to concatenate */
|
struct mtd_info *subdev[], /* subdevices to concatenate */
|
||||||
int num_devs, /* number of subdevices */
|
int num_devs, /* number of subdevices */
|
||||||
@@ -16,5 +28,54 @@ struct mtd_info *mtd_concat_create(
|
|||||||
|
|
||||||
void mtd_concat_destroy(struct mtd_info *mtd);
|
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
|
||||||
|
|||||||
@@ -477,8 +477,9 @@ struct spinand_ecc_info {
|
|||||||
const struct mtd_ooblayout_ops *ooblayout;
|
const struct mtd_ooblayout_ops *ooblayout;
|
||||||
};
|
};
|
||||||
|
|
||||||
#define SPINAND_HAS_QE_BIT BIT(0)
|
/* SPI NAND flags */
|
||||||
#define SPINAND_HAS_CR_FEAT_BIT BIT(1)
|
#define SPINAND_HAS_QE_BIT BIT(0)
|
||||||
|
#define SPINAND_HAS_CR_FEAT_BIT BIT(1)
|
||||||
#define SPINAND_HAS_PROG_PLANE_SELECT_BIT BIT(2)
|
#define SPINAND_HAS_PROG_PLANE_SELECT_BIT BIT(2)
|
||||||
#define SPINAND_HAS_READ_PLANE_SELECT_BIT BIT(3)
|
#define SPINAND_HAS_READ_PLANE_SELECT_BIT BIT(3)
|
||||||
#define SPINAND_NO_RAW_ACCESS BIT(4)
|
#define SPINAND_NO_RAW_ACCESS BIT(4)
|
||||||
|
|||||||
Reference in New Issue
Block a user