Topic branch bringing-in changes related to the support of ECC engines
that can be used by SPI controllers to manage SPI NANDs as well as possibly by parallel NAND controllers. In particular, it brings support for Macronix ECC engine that can be used with Macronix SPI controller. The changes touch the NAND core, the NAND ECC core, the spi-mem layer, a SPI controller driver and add a new NAND ECC driver, as well as a number of binding updates. Binding changes: * Vendor prefixes: Clarify Macronix prefix * SPI NAND: Convert spi-nand description file to yaml * Raw NAND chip: Create a NAND chip description * Raw NAND controller: - Harmonize the property types - Fix a comment in the examples - Fix the reg property description * Describe Macronix NAND ECC engine * Macronix SPI controller: - Document the nand-ecc-engine property - Convert to yaml - The interrupt property is not mandatory NAND core changes: * ECC: - Add infrastructure to support hardware engines - Add a new helper to retrieve the ECC context - Provide a helper to retrieve a pilelined engine device NAND-ECC changes: * Macronix ECC engine: - Add Macronix external ECC engine support - Support SPI pipelined mode SPI-NAND core changes: * Delay a little bit the dirmap creation * Create direct mapping descriptors for ECC operations SPI-NAND driver changes: * macronix: Use random program load SPI changes: * Macronix SPI controller: - Fix the transmit path - Create a helper to configure the controller before an operation - Create a helper to ease the start of an operation - Add support for direct mapping - Add support for pipelined ECC operations * spi-mem: - Introduce a capability structure - Check the controller extra capabilities - cadence-quadspi/mxic: Provide capability structures - Kill the spi_mem_dtr_supports_op() helper - Add an ecc parameter to the spi_mem_op structure -----BEGIN PGP SIGNATURE----- iQEzBAABCgAdFiEE9HuaYnbmDhq/XIDIJWrqGEe9VoQFAmIPpwcACgkQJWrqGEe9 VoRbnwgAgW9tSKGp1B6eA3Xf7Or0SZfmC6H0scV8kfQ2i9OnMOuYMAGs+7whNrcx Dvb9IfFOMra7umid98EI58YhLsu4IMDtc79Lp04HGY/emjZh47FpAEXZ/vr/45e9 lclUEmjHwUVJ5+XvFwnPLpIWiM0xeL3CN2rAi76uI5sII+Hxt6KkV7L9+N9IjhcA GsG/8/A16ihbNjVrHoN+ofwmmZkySXvhK7IIg93Tv+VoJCBnn0eEDgtRXvsGoDuq 2xD87MEAhnXw+q9LtSSUwmA/uUCR2RscidGQ+LML0PwKDVUZhzBV8sNrNoPQy374 51XXDCkSF8NRK+FVXIUFTEctdVrE1A== =UuhB -----END PGP SIGNATURE----- Merge tag 'mtd/spi-mem-ecc-for-5.18' into mtd/next Topic branch bringing-in changes related to the support of ECC engines that can be used by SPI controllers to manage SPI NANDs as well as possibly by parallel NAND controllers. In particular, it brings support for Macronix ECC engine that can be used with Macronix SPI controller. The changes touch the NAND core, the NAND ECC core, the spi-mem layer, a SPI controller driver and add a new NAND ECC driver, as well as a number of binding updates. Binding changes: * Vendor prefixes: Clarify Macronix prefix * SPI NAND: Convert spi-nand description file to yaml * Raw NAND chip: Create a NAND chip description * Raw NAND controller: - Harmonize the property types - Fix a comment in the examples - Fix the reg property description * Describe Macronix NAND ECC engine * Macronix SPI controller: - Document the nand-ecc-engine property - Convert to yaml - The interrupt property is not mandatory NAND core changes: * ECC: - Add infrastructure to support hardware engines - Add a new helper to retrieve the ECC context - Provide a helper to retrieve a pilelined engine device NAND-ECC changes: * Macronix ECC engine: - Add Macronix external ECC engine support - Support SPI pipelined mode SPI-NAND core changes: * Delay a little bit the dirmap creation * Create direct mapping descriptors for ECC operations SPI-NAND driver changes: * macronix: Use random program load SPI changes: * Macronix SPI controller: - Fix the transmit path - Create a helper to configure the controller before an operation - Create a helper to ease the start of an operation - Add support for direct mapping - Add support for pipelined ECC operations * spi-mem: - Introduce a capability structure - Check the controller extra capabilities - cadence-quadspi/mxic: Provide capability structures - Kill the spi_mem_dtr_supports_op() helper - Add an ecc parameter to the spi_mem_op structure
This commit is contained in:
commit
d71dac3b89
|
@ -0,0 +1,77 @@
|
|||
# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
|
||||
%YAML 1.2
|
||||
---
|
||||
$id: http://devicetree.org/schemas/mtd/mxicy,nand-ecc-engine.yaml#
|
||||
$schema: http://devicetree.org/meta-schemas/core.yaml#
|
||||
|
||||
title: Macronix NAND ECC engine device tree bindings
|
||||
|
||||
maintainers:
|
||||
- Miquel Raynal <miquel.raynal@bootlin.com>
|
||||
|
||||
properties:
|
||||
compatible:
|
||||
const: mxicy,nand-ecc-engine-rev3
|
||||
|
||||
reg:
|
||||
maxItems: 1
|
||||
|
||||
clocks:
|
||||
maxItems: 1
|
||||
|
||||
interrupts:
|
||||
maxItems: 1
|
||||
|
||||
required:
|
||||
- compatible
|
||||
- reg
|
||||
|
||||
additionalProperties: false
|
||||
|
||||
examples:
|
||||
- |
|
||||
/* External configuration */
|
||||
spi_controller0: spi@43c30000 {
|
||||
compatible = "mxicy,mx25f0a-spi";
|
||||
reg = <0x43c30000 0x10000>, <0xa0000000 0x4000000>;
|
||||
reg-names = "regs", "dirmap";
|
||||
clocks = <&clkwizard 0>, <&clkwizard 1>, <&clkc 15>;
|
||||
clock-names = "send_clk", "send_dly_clk", "ps_clk";
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
|
||||
flash@0 {
|
||||
compatible = "spi-nand";
|
||||
reg = <0>;
|
||||
nand-ecc-engine = <&ecc_engine0>;
|
||||
};
|
||||
};
|
||||
|
||||
ecc_engine0: ecc@43c40000 {
|
||||
compatible = "mxicy,nand-ecc-engine-rev3";
|
||||
reg = <0x43c40000 0x10000>;
|
||||
};
|
||||
|
||||
- |
|
||||
/* Pipelined configuration */
|
||||
spi_controller1: spi@43c30000 {
|
||||
compatible = "mxicy,mx25f0a-spi";
|
||||
reg = <0x43c30000 0x10000>, <0xa0000000 0x4000000>;
|
||||
reg-names = "regs", "dirmap";
|
||||
clocks = <&clkwizard 0>, <&clkwizard 1>, <&clkc 15>;
|
||||
clock-names = "send_clk", "send_dly_clk", "ps_clk";
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
nand-ecc-engine = <&ecc_engine1>;
|
||||
|
||||
flash@0 {
|
||||
compatible = "spi-nand";
|
||||
reg = <0>;
|
||||
nand-ecc-engine = <&spi_controller1>;
|
||||
};
|
||||
};
|
||||
|
||||
ecc_engine1: ecc@43c40000 {
|
||||
compatible = "mxicy,nand-ecc-engine-rev3";
|
||||
reg = <0x43c40000 0x10000>;
|
||||
};
|
|
@ -0,0 +1,70 @@
|
|||
# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
|
||||
%YAML 1.2
|
||||
---
|
||||
$id: http://devicetree.org/schemas/mtd/nand-chip.yaml#
|
||||
$schema: http://devicetree.org/meta-schemas/core.yaml#
|
||||
|
||||
title: NAND Chip and NAND Controller Generic Binding
|
||||
|
||||
maintainers:
|
||||
- Miquel Raynal <miquel.raynal@bootlin.com>
|
||||
|
||||
description: |
|
||||
This file covers the generic description 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:
|
||||
reg:
|
||||
description:
|
||||
Contains the chip-select IDs.
|
||||
|
||||
nand-ecc-engine:
|
||||
description: |
|
||||
A phandle on the hardware ECC engine if any. There are
|
||||
basically three possibilities:
|
||||
1/ The ECC engine is part of the NAND controller, in this
|
||||
case the phandle should reference the parent node.
|
||||
2/ The ECC engine is part of the NAND part (on-die), in this
|
||||
case the phandle should reference the node itself.
|
||||
3/ The ECC engine is external, in this case the phandle should
|
||||
reference the specific ECC engine node.
|
||||
$ref: /schemas/types.yaml#/definitions/phandle
|
||||
|
||||
nand-use-soft-ecc-engine:
|
||||
description: Use a software ECC engine.
|
||||
type: boolean
|
||||
|
||||
nand-no-ecc-engine:
|
||||
description: Do not use any ECC correction.
|
||||
type: boolean
|
||||
|
||||
nand-ecc-algo:
|
||||
description:
|
||||
Desired ECC algorithm.
|
||||
$ref: /schemas/types.yaml#/definitions/string
|
||||
enum: [hamming, bch, rs]
|
||||
|
||||
nand-ecc-strength:
|
||||
description:
|
||||
Maximum number of bits that can be corrected per ECC step.
|
||||
$ref: /schemas/types.yaml#/definitions/uint32
|
||||
minimum: 1
|
||||
|
||||
nand-ecc-step-size:
|
||||
description:
|
||||
Number of data bytes covered by a single ECC step.
|
||||
$ref: /schemas/types.yaml#/definitions/uint32
|
||||
minimum: 1
|
||||
|
||||
secure-regions:
|
||||
description:
|
||||
Regions in the NAND chip which are protected using a secure element
|
||||
like Trustzone. This property contains the start address and size of
|
||||
the secure regions present.
|
||||
$ref: /schemas/types.yaml#/definitions/uint64-matrix
|
||||
|
||||
required:
|
||||
- reg
|
||||
|
||||
additionalProperties: true
|
|
@ -39,8 +39,6 @@ properties:
|
|||
ranges: true
|
||||
|
||||
cs-gpios:
|
||||
minItems: 1
|
||||
maxItems: 8
|
||||
description:
|
||||
Array of chip-select available to the controller. The first
|
||||
entries are a 1:1 mapping of the available chip-select on the
|
||||
|
@ -48,51 +46,27 @@ properties:
|
|||
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
|
||||
|
||||
patternProperties:
|
||||
"^nand@[a-f0-9]$":
|
||||
type: object
|
||||
$ref: "nand-chip.yaml#"
|
||||
|
||||
properties:
|
||||
reg:
|
||||
description:
|
||||
Contains the native Ready/Busy IDs.
|
||||
|
||||
nand-ecc-engine:
|
||||
allOf:
|
||||
- $ref: /schemas/types.yaml#/definitions/phandle
|
||||
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.
|
||||
|
||||
nand-use-soft-ecc-engine:
|
||||
type: boolean
|
||||
description: Use a software ECC engine.
|
||||
|
||||
nand-no-ecc-engine:
|
||||
type: boolean
|
||||
description: Do not use any ECC correction.
|
||||
Contains the chip-select IDs.
|
||||
|
||||
nand-ecc-placement:
|
||||
allOf:
|
||||
- $ref: /schemas/types.yaml#/definitions/string
|
||||
- enum: [ oob, interleaved ]
|
||||
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.
|
||||
|
||||
nand-ecc-algo:
|
||||
description:
|
||||
Desired ECC algorithm.
|
||||
$ref: /schemas/types.yaml#/definitions/string
|
||||
enum: [hamming, bch, rs]
|
||||
enum: [ oob, interleaved ]
|
||||
|
||||
nand-bus-width:
|
||||
description:
|
||||
|
@ -102,7 +76,6 @@ patternProperties:
|
|||
default: 8
|
||||
|
||||
nand-on-flash-bbt:
|
||||
$ref: /schemas/types.yaml#/definitions/flag
|
||||
description:
|
||||
With this property, the OS will search the device for a Bad
|
||||
Block Table (BBT). If not found, it will create one, reserve
|
||||
|
@ -111,21 +84,9 @@ patternProperties:
|
|||
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.
|
||||
|
||||
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
|
||||
$ref: /schemas/types.yaml#/definitions/flag
|
||||
|
||||
nand-ecc-maximize:
|
||||
$ref: /schemas/types.yaml#/definitions/flag
|
||||
description:
|
||||
Whether or not the ECC strength should be maximized. The
|
||||
maximum ECC strength is both controller and chip
|
||||
|
@ -134,18 +95,19 @@ patternProperties:
|
|||
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:
|
||||
$ref: /schemas/types.yaml#/definitions/flag
|
||||
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:
|
||||
$ref: /schemas/types.yaml#/definitions/uint32-array
|
||||
description:
|
||||
Contains the native Ready/Busy IDs.
|
||||
$ref: /schemas/types.yaml#/definitions/uint32-array
|
||||
|
||||
rb-gpios:
|
||||
description:
|
||||
|
@ -154,13 +116,6 @@ patternProperties:
|
|||
Ready/Busy pins. Active state refers to the NAND ready state and
|
||||
should be set to GPIOD_ACTIVE_HIGH unless the signal is inverted.
|
||||
|
||||
secure-regions:
|
||||
$ref: /schemas/types.yaml#/definitions/uint64-matrix
|
||||
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.
|
||||
|
||||
required:
|
||||
- reg
|
||||
|
||||
|
@ -181,10 +136,7 @@ examples:
|
|||
|
||||
nand@0 {
|
||||
reg = <0>; /* Native CS */
|
||||
nand-use-soft-ecc-engine;
|
||||
nand-ecc-algo = "bch";
|
||||
|
||||
/* controller specific properties */
|
||||
/* NAND chip specific properties */
|
||||
};
|
||||
|
||||
nand@1 {
|
||||
|
|
|
@ -1,5 +0,0 @@
|
|||
SPI NAND flash
|
||||
|
||||
Required properties:
|
||||
- compatible: should be "spi-nand"
|
||||
- reg: should encode the chip-select line used to access the NAND chip
|
|
@ -0,0 +1,27 @@
|
|||
# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
|
||||
%YAML 1.2
|
||||
---
|
||||
$id: http://devicetree.org/schemas/mtd/spi-nand.yaml#
|
||||
$schema: http://devicetree.org/meta-schemas/core.yaml#
|
||||
|
||||
title: SPI-NAND flash device tree bindings
|
||||
|
||||
maintainers:
|
||||
- Miquel Raynal <miquel.raynal@bootlin.com>
|
||||
|
||||
allOf:
|
||||
- $ref: "nand-chip.yaml#"
|
||||
|
||||
properties:
|
||||
compatible:
|
||||
const: spi-nand
|
||||
|
||||
reg:
|
||||
description: Encode the chip-select line on the SPI bus
|
||||
maxItems: 1
|
||||
|
||||
required:
|
||||
- compatible
|
||||
- reg
|
||||
|
||||
unevaluatedProperties: false
|
|
@ -0,0 +1,65 @@
|
|||
# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
|
||||
%YAML 1.2
|
||||
---
|
||||
$id: http://devicetree.org/schemas/spi/mxicy,mx25f0a-spi.yaml#
|
||||
$schema: http://devicetree.org/meta-schemas/core.yaml#
|
||||
|
||||
title: Macronix SPI controller device tree bindings
|
||||
|
||||
maintainers:
|
||||
- Miquel Raynal <miquel.raynal@bootlin.com>
|
||||
|
||||
allOf:
|
||||
- $ref: "spi-controller.yaml#"
|
||||
|
||||
properties:
|
||||
compatible:
|
||||
const: mxicy,mx25f0a-spi
|
||||
|
||||
reg:
|
||||
minItems: 2
|
||||
maxItems: 2
|
||||
|
||||
reg-names:
|
||||
items:
|
||||
- const: regs
|
||||
- const: dirmap
|
||||
|
||||
interrupts:
|
||||
maxItems: 1
|
||||
|
||||
clocks:
|
||||
minItems: 3
|
||||
maxItems: 3
|
||||
|
||||
clock-names:
|
||||
items:
|
||||
- const: send_clk
|
||||
- const: send_dly_clk
|
||||
- const: ps_clk
|
||||
|
||||
nand-ecc-engine:
|
||||
description: NAND ECC engine used by the SPI controller in order to perform
|
||||
on-the-fly correction when using a SPI-NAND memory.
|
||||
$ref: /schemas/types.yaml#/definitions/phandle
|
||||
|
||||
required:
|
||||
- compatible
|
||||
- reg
|
||||
- reg-names
|
||||
- clocks
|
||||
- clock-names
|
||||
|
||||
unevaluatedProperties: false
|
||||
|
||||
examples:
|
||||
- |
|
||||
spi@43c30000 {
|
||||
compatible = "mxicy,mx25f0a-spi";
|
||||
reg = <0x43c30000 0x10000>, <0xa0000000 0x20000000>;
|
||||
reg-names = "regs", "dirmap";
|
||||
clocks = <&clkwizard 0>, <&clkwizard 1>, <&clkc 18>;
|
||||
clock-names = "send_clk", "send_dly_clk", "ps_clk";
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
};
|
|
@ -1,34 +0,0 @@
|
|||
Macronix SPI controller Device Tree Bindings
|
||||
--------------------------------------------
|
||||
|
||||
Required properties:
|
||||
- compatible: should be "mxicy,mx25f0a-spi"
|
||||
- #address-cells: should be 1
|
||||
- #size-cells: should be 0
|
||||
- reg: should contain 2 entries, one for the registers and one for the direct
|
||||
mapping area
|
||||
- reg-names: should contain "regs" and "dirmap"
|
||||
- interrupts: interrupt line connected to the SPI controller
|
||||
- clock-names: should contain "ps_clk", "send_clk" and "send_dly_clk"
|
||||
- clocks: should contain 3 entries for the "ps_clk", "send_clk" and
|
||||
"send_dly_clk" clocks
|
||||
|
||||
Example:
|
||||
|
||||
spi@43c30000 {
|
||||
compatible = "mxicy,mx25f0a-spi";
|
||||
reg = <0x43c30000 0x10000>, <0xa0000000 0x20000000>;
|
||||
reg-names = "regs", "dirmap";
|
||||
clocks = <&clkwizard 0>, <&clkwizard 1>, <&clkc 18>;
|
||||
clock-names = "send_clk", "send_dly_clk", "ps_clk";
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
|
||||
flash@0 {
|
||||
compatible = "jedec,spi-nor";
|
||||
reg = <0>;
|
||||
spi-max-frequency = <25000000>;
|
||||
spi-tx-bus-width = <4>;
|
||||
spi-rx-bus-width = <4>;
|
||||
};
|
||||
};
|
|
@ -802,6 +802,9 @@ patternProperties:
|
|||
description: Mundo Reader S.L.
|
||||
"^murata,.*":
|
||||
description: Murata Manufacturing Co., Ltd.
|
||||
"^mxic,.*":
|
||||
description: Macronix International Co., Ltd.
|
||||
deprecated: true
|
||||
"^mxicy,.*":
|
||||
description: Macronix International Co., Ltd.
|
||||
"^myir,.*":
|
||||
|
|
|
@ -46,6 +46,12 @@ config MTD_NAND_ECC_SW_BCH
|
|||
ECC codes. They are used with NAND devices requiring more than 1 bit
|
||||
of error correction.
|
||||
|
||||
config MTD_NAND_ECC_MXIC
|
||||
bool "Macronix external hardware ECC engine"
|
||||
select MTD_NAND_ECC
|
||||
help
|
||||
This enables support for the hardware ECC engine from Macronix.
|
||||
|
||||
endmenu
|
||||
|
||||
endmenu
|
||||
|
|
|
@ -10,3 +10,4 @@ obj-y += spi/
|
|||
nandcore-$(CONFIG_MTD_NAND_ECC) += ecc.o
|
||||
nandcore-$(CONFIG_MTD_NAND_ECC_SW_HAMMING) += ecc-sw-hamming.o
|
||||
nandcore-$(CONFIG_MTD_NAND_ECC_SW_BCH) += ecc-sw-bch.o
|
||||
nandcore-$(CONFIG_MTD_NAND_ECC_MXIC) += ecc-mxic.o
|
||||
|
|
|
@ -235,7 +235,9 @@ static int nanddev_get_ecc_engine(struct nand_device *nand)
|
|||
nand->ecc.engine = nand_ecc_get_on_die_hw_engine(nand);
|
||||
break;
|
||||
case NAND_ECC_ENGINE_TYPE_ON_HOST:
|
||||
pr_err("On-host hardware ECC engines not supported yet\n");
|
||||
nand->ecc.engine = nand_ecc_get_on_host_hw_engine(nand);
|
||||
if (PTR_ERR(nand->ecc.engine) == -EPROBE_DEFER)
|
||||
return -EPROBE_DEFER;
|
||||
break;
|
||||
default:
|
||||
pr_err("Missing ECC engine type\n");
|
||||
|
@ -255,7 +257,7 @@ static int nanddev_put_ecc_engine(struct nand_device *nand)
|
|||
{
|
||||
switch (nand->ecc.ctx.conf.engine_type) {
|
||||
case NAND_ECC_ENGINE_TYPE_ON_HOST:
|
||||
pr_err("On-host hardware ECC engines not supported yet\n");
|
||||
nand_ecc_put_on_host_hw_engine(nand);
|
||||
break;
|
||||
case NAND_ECC_ENGINE_TYPE_NONE:
|
||||
case NAND_ECC_ENGINE_TYPE_SOFT:
|
||||
|
@ -300,7 +302,9 @@ int nanddev_ecc_engine_init(struct nand_device *nand)
|
|||
/* Look for the ECC engine to use */
|
||||
ret = nanddev_get_ecc_engine(nand);
|
||||
if (ret) {
|
||||
pr_err("No ECC engine found\n");
|
||||
if (ret != -EPROBE_DEFER)
|
||||
pr_err("No ECC engine found\n");
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,879 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Support for Macronix external hardware ECC engine for NAND devices, also
|
||||
* called DPE for Data Processing Engine.
|
||||
*
|
||||
* Copyright © 2019 Macronix
|
||||
* Author: Miquel Raynal <miquel.raynal@bootlin.com>
|
||||
*/
|
||||
|
||||
#include <linux/dma-mapping.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/iopoll.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/mtd/mtd.h>
|
||||
#include <linux/mtd/nand.h>
|
||||
#include <linux/mtd/nand-ecc-mxic.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/of_device.h>
|
||||
#include <linux/of_platform.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/slab.h>
|
||||
|
||||
/* DPE Configuration */
|
||||
#define DP_CONFIG 0x00
|
||||
#define ECC_EN BIT(0)
|
||||
#define ECC_TYP(idx) (((idx) << 3) & GENMASK(6, 3))
|
||||
/* DPE Interrupt Status */
|
||||
#define INTRPT_STS 0x04
|
||||
#define TRANS_CMPLT BIT(0)
|
||||
#define SDMA_MAIN BIT(1)
|
||||
#define SDMA_SPARE BIT(2)
|
||||
#define ECC_ERR BIT(3)
|
||||
#define TO_SPARE BIT(4)
|
||||
#define TO_MAIN BIT(5)
|
||||
/* DPE Interrupt Status Enable */
|
||||
#define INTRPT_STS_EN 0x08
|
||||
/* DPE Interrupt Signal Enable */
|
||||
#define INTRPT_SIG_EN 0x0C
|
||||
/* Host Controller Configuration */
|
||||
#define HC_CONFIG 0x10
|
||||
#define DEV2MEM 0 /* TRANS_TYP_DMA in the spec */
|
||||
#define MEM2MEM BIT(4) /* TRANS_TYP_IO in the spec */
|
||||
#define MAPPING BIT(5) /* TRANS_TYP_MAPPING in the spec */
|
||||
#define ECC_PACKED 0 /* LAYOUT_TYP_INTEGRATED in the spec */
|
||||
#define ECC_INTERLEAVED BIT(2) /* LAYOUT_TYP_DISTRIBUTED in the spec */
|
||||
#define BURST_TYP_FIXED 0
|
||||
#define BURST_TYP_INCREASING BIT(0)
|
||||
/* Host Controller Slave Address */
|
||||
#define HC_SLV_ADDR 0x14
|
||||
/* ECC Chunk Size */
|
||||
#define CHUNK_SIZE 0x20
|
||||
/* Main Data Size */
|
||||
#define MAIN_SIZE 0x24
|
||||
/* Spare Data Size */
|
||||
#define SPARE_SIZE 0x28
|
||||
#define META_SZ(reg) ((reg) & GENMASK(7, 0))
|
||||
#define PARITY_SZ(reg) (((reg) & GENMASK(15, 8)) >> 8)
|
||||
#define RSV_SZ(reg) (((reg) & GENMASK(23, 16)) >> 16)
|
||||
#define SPARE_SZ(reg) ((reg) >> 24)
|
||||
/* ECC Chunk Count */
|
||||
#define CHUNK_CNT 0x30
|
||||
/* SDMA Control */
|
||||
#define SDMA_CTRL 0x40
|
||||
#define WRITE_NAND 0
|
||||
#define READ_NAND BIT(1)
|
||||
#define CONT_NAND BIT(29)
|
||||
#define CONT_SYSM BIT(30) /* Continue System Memory? */
|
||||
#define SDMA_STRT BIT(31)
|
||||
/* SDMA Address of Main Data */
|
||||
#define SDMA_MAIN_ADDR 0x44
|
||||
/* SDMA Address of Spare Data */
|
||||
#define SDMA_SPARE_ADDR 0x48
|
||||
/* DPE Version Number */
|
||||
#define DP_VER 0xD0
|
||||
#define DP_VER_OFFSET 16
|
||||
|
||||
/* Status bytes between each chunk of spare data */
|
||||
#define STAT_BYTES 4
|
||||
#define NO_ERR 0x00
|
||||
#define MAX_CORR_ERR 0x28
|
||||
#define UNCORR_ERR 0xFE
|
||||
#define ERASED_CHUNK 0xFF
|
||||
|
||||
struct mxic_ecc_engine {
|
||||
struct device *dev;
|
||||
void __iomem *regs;
|
||||
int irq;
|
||||
struct completion complete;
|
||||
struct nand_ecc_engine external_engine;
|
||||
struct nand_ecc_engine pipelined_engine;
|
||||
struct mutex lock;
|
||||
};
|
||||
|
||||
struct mxic_ecc_ctx {
|
||||
/* ECC machinery */
|
||||
unsigned int data_step_sz;
|
||||
unsigned int oob_step_sz;
|
||||
unsigned int parity_sz;
|
||||
unsigned int meta_sz;
|
||||
u8 *status;
|
||||
int steps;
|
||||
|
||||
/* DMA boilerplate */
|
||||
struct nand_ecc_req_tweak_ctx req_ctx;
|
||||
u8 *oobwithstat;
|
||||
struct scatterlist sg[2];
|
||||
struct nand_page_io_req *req;
|
||||
unsigned int pageoffs;
|
||||
};
|
||||
|
||||
static struct mxic_ecc_engine *ext_ecc_eng_to_mxic(struct nand_ecc_engine *eng)
|
||||
{
|
||||
return container_of(eng, struct mxic_ecc_engine, external_engine);
|
||||
}
|
||||
|
||||
static struct mxic_ecc_engine *pip_ecc_eng_to_mxic(struct nand_ecc_engine *eng)
|
||||
{
|
||||
return container_of(eng, struct mxic_ecc_engine, pipelined_engine);
|
||||
}
|
||||
|
||||
static struct mxic_ecc_engine *nand_to_mxic(struct nand_device *nand)
|
||||
{
|
||||
struct nand_ecc_engine *eng = nand->ecc.engine;
|
||||
|
||||
if (eng->integration == NAND_ECC_ENGINE_INTEGRATION_EXTERNAL)
|
||||
return ext_ecc_eng_to_mxic(eng);
|
||||
else
|
||||
return pip_ecc_eng_to_mxic(eng);
|
||||
}
|
||||
|
||||
static int mxic_ecc_ooblayout_ecc(struct mtd_info *mtd, int section,
|
||||
struct mtd_oob_region *oobregion)
|
||||
{
|
||||
struct nand_device *nand = mtd_to_nanddev(mtd);
|
||||
struct mxic_ecc_ctx *ctx = nand_to_ecc_ctx(nand);
|
||||
|
||||
if (section < 0 || section >= ctx->steps)
|
||||
return -ERANGE;
|
||||
|
||||
oobregion->offset = (section * ctx->oob_step_sz) + ctx->meta_sz;
|
||||
oobregion->length = ctx->parity_sz;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mxic_ecc_ooblayout_free(struct mtd_info *mtd, int section,
|
||||
struct mtd_oob_region *oobregion)
|
||||
{
|
||||
struct nand_device *nand = mtd_to_nanddev(mtd);
|
||||
struct mxic_ecc_ctx *ctx = nand_to_ecc_ctx(nand);
|
||||
|
||||
if (section < 0 || section >= ctx->steps)
|
||||
return -ERANGE;
|
||||
|
||||
if (!section) {
|
||||
oobregion->offset = 2;
|
||||
oobregion->length = ctx->meta_sz - 2;
|
||||
} else {
|
||||
oobregion->offset = section * ctx->oob_step_sz;
|
||||
oobregion->length = ctx->meta_sz;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct mtd_ooblayout_ops mxic_ecc_ooblayout_ops = {
|
||||
.ecc = mxic_ecc_ooblayout_ecc,
|
||||
.free = mxic_ecc_ooblayout_free,
|
||||
};
|
||||
|
||||
static void mxic_ecc_disable_engine(struct mxic_ecc_engine *mxic)
|
||||
{
|
||||
u32 reg;
|
||||
|
||||
reg = readl(mxic->regs + DP_CONFIG);
|
||||
reg &= ~ECC_EN;
|
||||
writel(reg, mxic->regs + DP_CONFIG);
|
||||
}
|
||||
|
||||
static void mxic_ecc_enable_engine(struct mxic_ecc_engine *mxic)
|
||||
{
|
||||
u32 reg;
|
||||
|
||||
reg = readl(mxic->regs + DP_CONFIG);
|
||||
reg |= ECC_EN;
|
||||
writel(reg, mxic->regs + DP_CONFIG);
|
||||
}
|
||||
|
||||
static void mxic_ecc_disable_int(struct mxic_ecc_engine *mxic)
|
||||
{
|
||||
writel(0, mxic->regs + INTRPT_SIG_EN);
|
||||
}
|
||||
|
||||
static void mxic_ecc_enable_int(struct mxic_ecc_engine *mxic)
|
||||
{
|
||||
writel(TRANS_CMPLT, mxic->regs + INTRPT_SIG_EN);
|
||||
}
|
||||
|
||||
static irqreturn_t mxic_ecc_isr(int irq, void *dev_id)
|
||||
{
|
||||
struct mxic_ecc_engine *mxic = dev_id;
|
||||
u32 sts;
|
||||
|
||||
sts = readl(mxic->regs + INTRPT_STS);
|
||||
if (!sts)
|
||||
return IRQ_NONE;
|
||||
|
||||
if (sts & TRANS_CMPLT)
|
||||
complete(&mxic->complete);
|
||||
|
||||
writel(sts, mxic->regs + INTRPT_STS);
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static int mxic_ecc_init_ctx(struct nand_device *nand, struct device *dev)
|
||||
{
|
||||
struct mxic_ecc_engine *mxic = nand_to_mxic(nand);
|
||||
struct nand_ecc_props *conf = &nand->ecc.ctx.conf;
|
||||
struct nand_ecc_props *reqs = &nand->ecc.requirements;
|
||||
struct nand_ecc_props *user = &nand->ecc.user_conf;
|
||||
struct mtd_info *mtd = nanddev_to_mtd(nand);
|
||||
int step_size = 0, strength = 0, desired_correction = 0, steps, idx;
|
||||
int possible_strength[] = {4, 8, 40, 48};
|
||||
int spare_size[] = {32, 32, 96, 96};
|
||||
struct mxic_ecc_ctx *ctx;
|
||||
u32 spare_reg;
|
||||
int ret;
|
||||
|
||||
ctx = devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL);
|
||||
if (!ctx)
|
||||
return -ENOMEM;
|
||||
|
||||
nand->ecc.ctx.priv = ctx;
|
||||
|
||||
/* Only large page NAND chips may use BCH */
|
||||
if (mtd->oobsize < 64) {
|
||||
pr_err("BCH cannot be used with small page NAND chips\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
mtd_set_ooblayout(mtd, &mxic_ecc_ooblayout_ops);
|
||||
|
||||
/* Enable all status bits */
|
||||
writel(TRANS_CMPLT | SDMA_MAIN | SDMA_SPARE | ECC_ERR |
|
||||
TO_SPARE | TO_MAIN, mxic->regs + INTRPT_STS_EN);
|
||||
|
||||
/* Configure the correction depending on the NAND device topology */
|
||||
if (user->step_size && user->strength) {
|
||||
step_size = user->step_size;
|
||||
strength = user->strength;
|
||||
} else if (reqs->step_size && reqs->strength) {
|
||||
step_size = reqs->step_size;
|
||||
strength = reqs->strength;
|
||||
}
|
||||
|
||||
if (step_size && strength) {
|
||||
steps = mtd->writesize / step_size;
|
||||
desired_correction = steps * strength;
|
||||
}
|
||||
|
||||
/* Step size is fixed to 1kiB, strength may vary (4 possible values) */
|
||||
conf->step_size = SZ_1K;
|
||||
steps = mtd->writesize / conf->step_size;
|
||||
|
||||
ctx->status = devm_kzalloc(dev, steps * sizeof(u8), GFP_KERNEL);
|
||||
if (!ctx->status)
|
||||
return -ENOMEM;
|
||||
|
||||
if (desired_correction) {
|
||||
strength = desired_correction / steps;
|
||||
|
||||
for (idx = 0; idx < ARRAY_SIZE(possible_strength); idx++)
|
||||
if (possible_strength[idx] >= strength)
|
||||
break;
|
||||
|
||||
idx = min_t(unsigned int, idx,
|
||||
ARRAY_SIZE(possible_strength) - 1);
|
||||
} else {
|
||||
/* Missing data, maximize the correction */
|
||||
idx = ARRAY_SIZE(possible_strength) - 1;
|
||||
}
|
||||
|
||||
/* Tune the selected strength until it fits in the OOB area */
|
||||
for (; idx >= 0; idx--) {
|
||||
if (spare_size[idx] * steps <= mtd->oobsize)
|
||||
break;
|
||||
}
|
||||
|
||||
/* This engine cannot be used with this NAND device */
|
||||
if (idx < 0)
|
||||
return -EINVAL;
|
||||
|
||||
/* Configure the engine for the desired strength */
|
||||
writel(ECC_TYP(idx), mxic->regs + DP_CONFIG);
|
||||
conf->strength = possible_strength[idx];
|
||||
spare_reg = readl(mxic->regs + SPARE_SIZE);
|
||||
|
||||
ctx->steps = steps;
|
||||
ctx->data_step_sz = mtd->writesize / steps;
|
||||
ctx->oob_step_sz = mtd->oobsize / steps;
|
||||
ctx->parity_sz = PARITY_SZ(spare_reg);
|
||||
ctx->meta_sz = META_SZ(spare_reg);
|
||||
|
||||
/* Ensure buffers will contain enough bytes to store the STAT_BYTES */
|
||||
ctx->req_ctx.oob_buffer_size = nanddev_per_page_oobsize(nand) +
|
||||
(ctx->steps * STAT_BYTES);
|
||||
ret = nand_ecc_init_req_tweaking(&ctx->req_ctx, nand);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ctx->oobwithstat = kmalloc(mtd->oobsize + (ctx->steps * STAT_BYTES),
|
||||
GFP_KERNEL);
|
||||
if (!ctx->oobwithstat) {
|
||||
ret = -ENOMEM;
|
||||
goto cleanup_req_tweak;
|
||||
}
|
||||
|
||||
sg_init_table(ctx->sg, 2);
|
||||
|
||||
/* Configuration dump and sanity checks */
|
||||
dev_err(dev, "DPE version number: %d\n",
|
||||
readl(mxic->regs + DP_VER) >> DP_VER_OFFSET);
|
||||
dev_err(dev, "Chunk size: %d\n", readl(mxic->regs + CHUNK_SIZE));
|
||||
dev_err(dev, "Main size: %d\n", readl(mxic->regs + MAIN_SIZE));
|
||||
dev_err(dev, "Spare size: %d\n", SPARE_SZ(spare_reg));
|
||||
dev_err(dev, "Rsv size: %ld\n", RSV_SZ(spare_reg));
|
||||
dev_err(dev, "Parity size: %d\n", ctx->parity_sz);
|
||||
dev_err(dev, "Meta size: %d\n", ctx->meta_sz);
|
||||
|
||||
if ((ctx->meta_sz + ctx->parity_sz + RSV_SZ(spare_reg)) !=
|
||||
SPARE_SZ(spare_reg)) {
|
||||
dev_err(dev, "Wrong OOB configuration: %d + %d + %ld != %d\n",
|
||||
ctx->meta_sz, ctx->parity_sz, RSV_SZ(spare_reg),
|
||||
SPARE_SZ(spare_reg));
|
||||
ret = -EINVAL;
|
||||
goto free_oobwithstat;
|
||||
}
|
||||
|
||||
if (ctx->oob_step_sz != SPARE_SZ(spare_reg)) {
|
||||
dev_err(dev, "Wrong OOB configuration: %d != %d\n",
|
||||
ctx->oob_step_sz, SPARE_SZ(spare_reg));
|
||||
ret = -EINVAL;
|
||||
goto free_oobwithstat;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
free_oobwithstat:
|
||||
kfree(ctx->oobwithstat);
|
||||
cleanup_req_tweak:
|
||||
nand_ecc_cleanup_req_tweaking(&ctx->req_ctx);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int mxic_ecc_init_ctx_external(struct nand_device *nand)
|
||||
{
|
||||
struct mxic_ecc_engine *mxic = nand_to_mxic(nand);
|
||||
struct device *dev = nand->ecc.engine->dev;
|
||||
int ret;
|
||||
|
||||
dev_info(dev, "Macronix ECC engine in external mode\n");
|
||||
|
||||
ret = mxic_ecc_init_ctx(nand, dev);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* Trigger each step manually */
|
||||
writel(1, mxic->regs + CHUNK_CNT);
|
||||
writel(BURST_TYP_INCREASING | ECC_PACKED | MEM2MEM,
|
||||
mxic->regs + HC_CONFIG);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mxic_ecc_init_ctx_pipelined(struct nand_device *nand)
|
||||
{
|
||||
struct mxic_ecc_engine *mxic = nand_to_mxic(nand);
|
||||
struct mxic_ecc_ctx *ctx;
|
||||
struct device *dev;
|
||||
int ret;
|
||||
|
||||
dev = nand_ecc_get_engine_dev(nand->ecc.engine->dev);
|
||||
if (!dev)
|
||||
return -EINVAL;
|
||||
|
||||
dev_info(dev, "Macronix ECC engine in pipelined/mapping mode\n");
|
||||
|
||||
ret = mxic_ecc_init_ctx(nand, dev);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ctx = nand_to_ecc_ctx(nand);
|
||||
|
||||
/* All steps should be handled in one go directly by the internal DMA */
|
||||
writel(ctx->steps, mxic->regs + CHUNK_CNT);
|
||||
|
||||
/*
|
||||
* Interleaved ECC scheme cannot be used otherwise factory bad block
|
||||
* markers would be lost. A packed layout is mandatory.
|
||||
*/
|
||||
writel(BURST_TYP_INCREASING | ECC_PACKED | MAPPING,
|
||||
mxic->regs + HC_CONFIG);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void mxic_ecc_cleanup_ctx(struct nand_device *nand)
|
||||
{
|
||||
struct mxic_ecc_ctx *ctx = nand_to_ecc_ctx(nand);
|
||||
|
||||
if (ctx) {
|
||||
nand_ecc_cleanup_req_tweaking(&ctx->req_ctx);
|
||||
kfree(ctx->oobwithstat);
|
||||
}
|
||||
}
|
||||
|
||||
static int mxic_ecc_data_xfer_wait_for_completion(struct mxic_ecc_engine *mxic)
|
||||
{
|
||||
u32 val;
|
||||
int ret;
|
||||
|
||||
if (mxic->irq) {
|
||||
reinit_completion(&mxic->complete);
|
||||
mxic_ecc_enable_int(mxic);
|
||||
ret = wait_for_completion_timeout(&mxic->complete,
|
||||
msecs_to_jiffies(1000));
|
||||
mxic_ecc_disable_int(mxic);
|
||||
} else {
|
||||
ret = readl_poll_timeout(mxic->regs + INTRPT_STS, val,
|
||||
val & TRANS_CMPLT, 10, USEC_PER_SEC);
|
||||
writel(val, mxic->regs + INTRPT_STS);
|
||||
}
|
||||
|
||||
if (ret) {
|
||||
dev_err(mxic->dev, "Timeout on data xfer completion\n");
|
||||
return -ETIMEDOUT;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mxic_ecc_process_data(struct mxic_ecc_engine *mxic,
|
||||
unsigned int direction)
|
||||
{
|
||||
unsigned int dir = (direction == NAND_PAGE_READ) ?
|
||||
READ_NAND : WRITE_NAND;
|
||||
int ret;
|
||||
|
||||
mxic_ecc_enable_engine(mxic);
|
||||
|
||||
/* Trigger processing */
|
||||
writel(SDMA_STRT | dir, mxic->regs + SDMA_CTRL);
|
||||
|
||||
/* Wait for completion */
|
||||
ret = mxic_ecc_data_xfer_wait_for_completion(mxic);
|
||||
|
||||
mxic_ecc_disable_engine(mxic);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int mxic_ecc_process_data_pipelined(struct nand_ecc_engine *eng,
|
||||
unsigned int direction, dma_addr_t dirmap)
|
||||
{
|
||||
struct mxic_ecc_engine *mxic = pip_ecc_eng_to_mxic(eng);
|
||||
|
||||
if (dirmap)
|
||||
writel(dirmap, mxic->regs + HC_SLV_ADDR);
|
||||
|
||||
return mxic_ecc_process_data(mxic, direction);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(mxic_ecc_process_data_pipelined);
|
||||
|
||||
static void mxic_ecc_extract_status_bytes(struct mxic_ecc_ctx *ctx)
|
||||
{
|
||||
u8 *buf = ctx->oobwithstat;
|
||||
int next_stat_pos;
|
||||
int step;
|
||||
|
||||
/* Extract the ECC status */
|
||||
for (step = 0; step < ctx->steps; step++) {
|
||||
next_stat_pos = ctx->oob_step_sz +
|
||||
((STAT_BYTES + ctx->oob_step_sz) * step);
|
||||
|
||||
ctx->status[step] = buf[next_stat_pos];
|
||||
}
|
||||
}
|
||||
|
||||
static void mxic_ecc_reconstruct_oobbuf(struct mxic_ecc_ctx *ctx,
|
||||
u8 *dst, const u8 *src)
|
||||
{
|
||||
int step;
|
||||
|
||||
/* Reconstruct the OOB buffer linearly (without the ECC status bytes) */
|
||||
for (step = 0; step < ctx->steps; step++)
|
||||
memcpy(dst + (step * ctx->oob_step_sz),
|
||||
src + (step * (ctx->oob_step_sz + STAT_BYTES)),
|
||||
ctx->oob_step_sz);
|
||||
}
|
||||
|
||||
static void mxic_ecc_add_room_in_oobbuf(struct mxic_ecc_ctx *ctx,
|
||||
u8 *dst, const u8 *src)
|
||||
{
|
||||
int step;
|
||||
|
||||
/* Add some space in the OOB buffer for the status bytes */
|
||||
for (step = 0; step < ctx->steps; step++)
|
||||
memcpy(dst + (step * (ctx->oob_step_sz + STAT_BYTES)),
|
||||
src + (step * ctx->oob_step_sz),
|
||||
ctx->oob_step_sz);
|
||||
}
|
||||
|
||||
static int mxic_ecc_count_biterrs(struct mxic_ecc_engine *mxic,
|
||||
struct nand_device *nand)
|
||||
{
|
||||
struct mxic_ecc_ctx *ctx = nand_to_ecc_ctx(nand);
|
||||
struct mtd_info *mtd = nanddev_to_mtd(nand);
|
||||
struct device *dev = mxic->dev;
|
||||
unsigned int max_bf = 0;
|
||||
bool failure = false;
|
||||
int step;
|
||||
|
||||
for (step = 0; step < ctx->steps; step++) {
|
||||
u8 stat = ctx->status[step];
|
||||
|
||||
if (stat == NO_ERR) {
|
||||
dev_dbg(dev, "ECC step %d: no error\n", step);
|
||||
} else if (stat == ERASED_CHUNK) {
|
||||
dev_dbg(dev, "ECC step %d: erased\n", step);
|
||||
} else if (stat == UNCORR_ERR || stat > MAX_CORR_ERR) {
|
||||
dev_dbg(dev, "ECC step %d: uncorrectable\n", step);
|
||||
mtd->ecc_stats.failed++;
|
||||
failure = true;
|
||||
} else {
|
||||
dev_dbg(dev, "ECC step %d: %d bits corrected\n",
|
||||
step, stat);
|
||||
max_bf = max_t(unsigned int, max_bf, stat);
|
||||
mtd->ecc_stats.corrected += stat;
|
||||
}
|
||||
}
|
||||
|
||||
return failure ? -EBADMSG : max_bf;
|
||||
}
|
||||
|
||||
/* External ECC engine helpers */
|
||||
static int mxic_ecc_prepare_io_req_external(struct nand_device *nand,
|
||||
struct nand_page_io_req *req)
|
||||
{
|
||||
struct mxic_ecc_engine *mxic = nand_to_mxic(nand);
|
||||
struct mxic_ecc_ctx *ctx = nand_to_ecc_ctx(nand);
|
||||
struct mtd_info *mtd = nanddev_to_mtd(nand);
|
||||
int offset, nents, step, ret;
|
||||
|
||||
if (req->mode == MTD_OPS_RAW)
|
||||
return 0;
|
||||
|
||||
nand_ecc_tweak_req(&ctx->req_ctx, req);
|
||||
ctx->req = req;
|
||||
|
||||
if (req->type == NAND_PAGE_READ)
|
||||
return 0;
|
||||
|
||||
mxic_ecc_add_room_in_oobbuf(ctx, ctx->oobwithstat,
|
||||
ctx->req->oobbuf.out);
|
||||
|
||||
sg_set_buf(&ctx->sg[0], req->databuf.out, req->datalen);
|
||||
sg_set_buf(&ctx->sg[1], ctx->oobwithstat,
|
||||
req->ooblen + (ctx->steps * STAT_BYTES));
|
||||
|
||||
nents = dma_map_sg(mxic->dev, ctx->sg, 2, DMA_BIDIRECTIONAL);
|
||||
if (!nents)
|
||||
return -EINVAL;
|
||||
|
||||
mutex_lock(&mxic->lock);
|
||||
|
||||
for (step = 0; step < ctx->steps; step++) {
|
||||
writel(sg_dma_address(&ctx->sg[0]) + (step * ctx->data_step_sz),
|
||||
mxic->regs + SDMA_MAIN_ADDR);
|
||||
writel(sg_dma_address(&ctx->sg[1]) + (step * (ctx->oob_step_sz + STAT_BYTES)),
|
||||
mxic->regs + SDMA_SPARE_ADDR);
|
||||
ret = mxic_ecc_process_data(mxic, ctx->req->type);
|
||||
if (ret)
|
||||
break;
|
||||
}
|
||||
|
||||
mutex_unlock(&mxic->lock);
|
||||
|
||||
dma_unmap_sg(mxic->dev, ctx->sg, 2, DMA_BIDIRECTIONAL);
|
||||
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* Retrieve the calculated ECC bytes */
|
||||
for (step = 0; step < ctx->steps; step++) {
|
||||
offset = ctx->meta_sz + (step * ctx->oob_step_sz);
|
||||
mtd_ooblayout_get_eccbytes(mtd,
|
||||
(u8 *)ctx->req->oobbuf.out + offset,
|
||||
ctx->oobwithstat + (step * STAT_BYTES),
|
||||
step * ctx->parity_sz,
|
||||
ctx->parity_sz);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mxic_ecc_finish_io_req_external(struct nand_device *nand,
|
||||
struct nand_page_io_req *req)
|
||||
{
|
||||
struct mxic_ecc_engine *mxic = nand_to_mxic(nand);
|
||||
struct mxic_ecc_ctx *ctx = nand_to_ecc_ctx(nand);
|
||||
int nents, step, ret;
|
||||
|
||||
if (req->mode == MTD_OPS_RAW)
|
||||
return 0;
|
||||
|
||||
if (req->type == NAND_PAGE_WRITE) {
|
||||
nand_ecc_restore_req(&ctx->req_ctx, req);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Copy the OOB buffer and add room for the ECC engine status bytes */
|
||||
mxic_ecc_add_room_in_oobbuf(ctx, ctx->oobwithstat, ctx->req->oobbuf.in);
|
||||
|
||||
sg_set_buf(&ctx->sg[0], req->databuf.in, req->datalen);
|
||||
sg_set_buf(&ctx->sg[1], ctx->oobwithstat,
|
||||
req->ooblen + (ctx->steps * STAT_BYTES));
|
||||
nents = dma_map_sg(mxic->dev, ctx->sg, 2, DMA_BIDIRECTIONAL);
|
||||
if (!nents)
|
||||
return -EINVAL;
|
||||
|
||||
mutex_lock(&mxic->lock);
|
||||
|
||||
for (step = 0; step < ctx->steps; step++) {
|
||||
writel(sg_dma_address(&ctx->sg[0]) + (step * ctx->data_step_sz),
|
||||
mxic->regs + SDMA_MAIN_ADDR);
|
||||
writel(sg_dma_address(&ctx->sg[1]) + (step * (ctx->oob_step_sz + STAT_BYTES)),
|
||||
mxic->regs + SDMA_SPARE_ADDR);
|
||||
ret = mxic_ecc_process_data(mxic, ctx->req->type);
|
||||
if (ret)
|
||||
break;
|
||||
}
|
||||
|
||||
mutex_unlock(&mxic->lock);
|
||||
|
||||
dma_unmap_sg(mxic->dev, ctx->sg, 2, DMA_BIDIRECTIONAL);
|
||||
|
||||
if (ret) {
|
||||
nand_ecc_restore_req(&ctx->req_ctx, req);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Extract the status bytes and reconstruct the buffer */
|
||||
mxic_ecc_extract_status_bytes(ctx);
|
||||
mxic_ecc_reconstruct_oobbuf(ctx, ctx->req->oobbuf.in, ctx->oobwithstat);
|
||||
|
||||
nand_ecc_restore_req(&ctx->req_ctx, req);
|
||||
|
||||
return mxic_ecc_count_biterrs(mxic, nand);
|
||||
}
|
||||
|
||||
/* Pipelined ECC engine helpers */
|
||||
static int mxic_ecc_prepare_io_req_pipelined(struct nand_device *nand,
|
||||
struct nand_page_io_req *req)
|
||||
{
|
||||
struct mxic_ecc_engine *mxic = nand_to_mxic(nand);
|
||||
struct mxic_ecc_ctx *ctx = nand_to_ecc_ctx(nand);
|
||||
int nents;
|
||||
|
||||
if (req->mode == MTD_OPS_RAW)
|
||||
return 0;
|
||||
|
||||
nand_ecc_tweak_req(&ctx->req_ctx, req);
|
||||
ctx->req = req;
|
||||
|
||||
/* Copy the OOB buffer and add room for the ECC engine status bytes */
|
||||
mxic_ecc_add_room_in_oobbuf(ctx, ctx->oobwithstat, ctx->req->oobbuf.in);
|
||||
|
||||
sg_set_buf(&ctx->sg[0], req->databuf.in, req->datalen);
|
||||
sg_set_buf(&ctx->sg[1], ctx->oobwithstat,
|
||||
req->ooblen + (ctx->steps * STAT_BYTES));
|
||||
|
||||
nents = dma_map_sg(mxic->dev, ctx->sg, 2, DMA_BIDIRECTIONAL);
|
||||
if (!nents)
|
||||
return -EINVAL;
|
||||
|
||||
mutex_lock(&mxic->lock);
|
||||
|
||||
writel(sg_dma_address(&ctx->sg[0]), mxic->regs + SDMA_MAIN_ADDR);
|
||||
writel(sg_dma_address(&ctx->sg[1]), mxic->regs + SDMA_SPARE_ADDR);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mxic_ecc_finish_io_req_pipelined(struct nand_device *nand,
|
||||
struct nand_page_io_req *req)
|
||||
{
|
||||
struct mxic_ecc_engine *mxic = nand_to_mxic(nand);
|
||||
struct mxic_ecc_ctx *ctx = nand_to_ecc_ctx(nand);
|
||||
int ret = 0;
|
||||
|
||||
if (req->mode == MTD_OPS_RAW)
|
||||
return 0;
|
||||
|
||||
mutex_unlock(&mxic->lock);
|
||||
|
||||
dma_unmap_sg(mxic->dev, ctx->sg, 2, DMA_BIDIRECTIONAL);
|
||||
|
||||
if (req->type == NAND_PAGE_READ) {
|
||||
mxic_ecc_extract_status_bytes(ctx);
|
||||
mxic_ecc_reconstruct_oobbuf(ctx, ctx->req->oobbuf.in,
|
||||
ctx->oobwithstat);
|
||||
ret = mxic_ecc_count_biterrs(mxic, nand);
|
||||
}
|
||||
|
||||
nand_ecc_restore_req(&ctx->req_ctx, req);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static struct nand_ecc_engine_ops mxic_ecc_engine_external_ops = {
|
||||
.init_ctx = mxic_ecc_init_ctx_external,
|
||||
.cleanup_ctx = mxic_ecc_cleanup_ctx,
|
||||
.prepare_io_req = mxic_ecc_prepare_io_req_external,
|
||||
.finish_io_req = mxic_ecc_finish_io_req_external,
|
||||
};
|
||||
|
||||
static struct nand_ecc_engine_ops mxic_ecc_engine_pipelined_ops = {
|
||||
.init_ctx = mxic_ecc_init_ctx_pipelined,
|
||||
.cleanup_ctx = mxic_ecc_cleanup_ctx,
|
||||
.prepare_io_req = mxic_ecc_prepare_io_req_pipelined,
|
||||
.finish_io_req = mxic_ecc_finish_io_req_pipelined,
|
||||
};
|
||||
|
||||
struct nand_ecc_engine_ops *mxic_ecc_get_pipelined_ops(void)
|
||||
{
|
||||
return &mxic_ecc_engine_pipelined_ops;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(mxic_ecc_get_pipelined_ops);
|
||||
|
||||
static struct platform_device *
|
||||
mxic_ecc_get_pdev(struct platform_device *spi_pdev)
|
||||
{
|
||||
struct platform_device *eng_pdev;
|
||||
struct device_node *np;
|
||||
|
||||
/* Retrieve the nand-ecc-engine phandle */
|
||||
np = of_parse_phandle(spi_pdev->dev.of_node, "nand-ecc-engine", 0);
|
||||
if (!np)
|
||||
return NULL;
|
||||
|
||||
/* Jump to the engine's device node */
|
||||
eng_pdev = of_find_device_by_node(np);
|
||||
of_node_put(np);
|
||||
|
||||
return eng_pdev;
|
||||
}
|
||||
|
||||
void mxic_ecc_put_pipelined_engine(struct nand_ecc_engine *eng)
|
||||
{
|
||||
struct mxic_ecc_engine *mxic = pip_ecc_eng_to_mxic(eng);
|
||||
|
||||
platform_device_put(to_platform_device(mxic->dev));
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(mxic_ecc_put_pipelined_engine);
|
||||
|
||||
struct nand_ecc_engine *
|
||||
mxic_ecc_get_pipelined_engine(struct platform_device *spi_pdev)
|
||||
{
|
||||
struct platform_device *eng_pdev;
|
||||
struct mxic_ecc_engine *mxic;
|
||||
|
||||
eng_pdev = mxic_ecc_get_pdev(spi_pdev);
|
||||
if (!eng_pdev)
|
||||
return ERR_PTR(-ENODEV);
|
||||
|
||||
mxic = platform_get_drvdata(eng_pdev);
|
||||
if (!mxic) {
|
||||
platform_device_put(eng_pdev);
|
||||
return ERR_PTR(-EPROBE_DEFER);
|
||||
}
|
||||
|
||||
return &mxic->pipelined_engine;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(mxic_ecc_get_pipelined_engine);
|
||||
|
||||
/*
|
||||
* Only the external ECC engine is exported as the pipelined is SoC specific, so
|
||||
* it is registered directly by the drivers that wrap it.
|
||||
*/
|
||||
static int mxic_ecc_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct device *dev = &pdev->dev;
|
||||
struct mxic_ecc_engine *mxic;
|
||||
int ret;
|
||||
|
||||
mxic = devm_kzalloc(&pdev->dev, sizeof(*mxic), GFP_KERNEL);
|
||||
if (!mxic)
|
||||
return -ENOMEM;
|
||||
|
||||
mxic->dev = &pdev->dev;
|
||||
|
||||
/*
|
||||
* Both memory regions for the ECC engine itself and the AXI slave
|
||||
* address are mandatory.
|
||||
*/
|
||||
mxic->regs = devm_platform_ioremap_resource(pdev, 0);
|
||||
if (IS_ERR(mxic->regs)) {
|
||||
dev_err(&pdev->dev, "Missing memory region\n");
|
||||
return PTR_ERR(mxic->regs);
|
||||
}
|
||||
|
||||
mxic_ecc_disable_engine(mxic);
|
||||
mxic_ecc_disable_int(mxic);
|
||||
|
||||
/* IRQ is optional yet much more efficient */
|
||||
mxic->irq = platform_get_irq_byname_optional(pdev, "ecc-engine");
|
||||
if (mxic->irq > 0) {
|
||||
ret = devm_request_irq(&pdev->dev, mxic->irq, mxic_ecc_isr, 0,
|
||||
"mxic-ecc", mxic);
|
||||
if (ret)
|
||||
return ret;
|
||||
} else {
|
||||
dev_info(dev, "Invalid or missing IRQ, fallback to polling\n");
|
||||
mxic->irq = 0;
|
||||
}
|
||||
|
||||
mutex_init(&mxic->lock);
|
||||
|
||||
/*
|
||||
* In external mode, the device is the ECC engine. In pipelined mode,
|
||||
* the device is the host controller. The device is used to match the
|
||||
* right ECC engine based on the DT properties.
|
||||
*/
|
||||
mxic->external_engine.dev = &pdev->dev;
|
||||
mxic->external_engine.integration = NAND_ECC_ENGINE_INTEGRATION_EXTERNAL;
|
||||
mxic->external_engine.ops = &mxic_ecc_engine_external_ops;
|
||||
|
||||
nand_ecc_register_on_host_hw_engine(&mxic->external_engine);
|
||||
|
||||
platform_set_drvdata(pdev, mxic);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mxic_ecc_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct mxic_ecc_engine *mxic = platform_get_drvdata(pdev);
|
||||
|
||||
nand_ecc_unregister_on_host_hw_engine(&mxic->external_engine);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct of_device_id mxic_ecc_of_ids[] = {
|
||||
{
|
||||
.compatible = "mxicy,nand-ecc-engine-rev3",
|
||||
},
|
||||
{ /* sentinel */ },
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, mxic_ecc_of_ids);
|
||||
|
||||
static struct platform_driver mxic_ecc_driver = {
|
||||
.driver = {
|
||||
.name = "mxic-nand-ecc-engine",
|
||||
.of_match_table = mxic_ecc_of_ids,
|
||||
},
|
||||
.probe = mxic_ecc_probe,
|
||||
.remove = mxic_ecc_remove,
|
||||
};
|
||||
module_platform_driver(mxic_ecc_driver);
|
||||
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_AUTHOR("Miquel Raynal <miquel.raynal@bootlin.com>");
|
||||
MODULE_DESCRIPTION("Macronix NAND hardware ECC controller");
|
|
@ -96,6 +96,12 @@
|
|||
#include <linux/module.h>
|
||||
#include <linux/mtd/nand.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/of_device.h>
|
||||
#include <linux/of_platform.h>
|
||||
|
||||
static LIST_HEAD(on_host_hw_engines);
|
||||
static DEFINE_MUTEX(on_host_hw_engines_mutex);
|
||||
|
||||
/**
|
||||
* nand_ecc_init_ctx - Init the ECC engine context
|
||||
|
@ -611,6 +617,119 @@ struct nand_ecc_engine *nand_ecc_get_on_die_hw_engine(struct nand_device *nand)
|
|||
}
|
||||
EXPORT_SYMBOL(nand_ecc_get_on_die_hw_engine);
|
||||
|
||||
int nand_ecc_register_on_host_hw_engine(struct nand_ecc_engine *engine)
|
||||
{
|
||||
struct nand_ecc_engine *item;
|
||||
|
||||
if (!engine)
|
||||
return -EINVAL;
|
||||
|
||||
/* Prevent multiple registrations of one engine */
|
||||
list_for_each_entry(item, &on_host_hw_engines, node)
|
||||
if (item == engine)
|
||||
return 0;
|
||||
|
||||
mutex_lock(&on_host_hw_engines_mutex);
|
||||
list_add_tail(&engine->node, &on_host_hw_engines);
|
||||
mutex_unlock(&on_host_hw_engines_mutex);
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL(nand_ecc_register_on_host_hw_engine);
|
||||
|
||||
int nand_ecc_unregister_on_host_hw_engine(struct nand_ecc_engine *engine)
|
||||
{
|
||||
if (!engine)
|
||||
return -EINVAL;
|
||||
|
||||
mutex_lock(&on_host_hw_engines_mutex);
|
||||
list_del(&engine->node);
|
||||
mutex_unlock(&on_host_hw_engines_mutex);
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL(nand_ecc_unregister_on_host_hw_engine);
|
||||
|
||||
static struct nand_ecc_engine *nand_ecc_match_on_host_hw_engine(struct device *dev)
|
||||
{
|
||||
struct nand_ecc_engine *item;
|
||||
|
||||
list_for_each_entry(item, &on_host_hw_engines, node)
|
||||
if (item->dev == dev)
|
||||
return item;
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
struct nand_ecc_engine *nand_ecc_get_on_host_hw_engine(struct nand_device *nand)
|
||||
{
|
||||
struct nand_ecc_engine *engine = NULL;
|
||||
struct device *dev = &nand->mtd.dev;
|
||||
struct platform_device *pdev;
|
||||
struct device_node *np;
|
||||
|
||||
if (list_empty(&on_host_hw_engines))
|
||||
return NULL;
|
||||
|
||||
/* Check for an explicit nand-ecc-engine property */
|
||||
np = of_parse_phandle(dev->of_node, "nand-ecc-engine", 0);
|
||||
if (np) {
|
||||
pdev = of_find_device_by_node(np);
|
||||
if (!pdev)
|
||||
return ERR_PTR(-EPROBE_DEFER);
|
||||
|
||||
engine = nand_ecc_match_on_host_hw_engine(&pdev->dev);
|
||||
platform_device_put(pdev);
|
||||
of_node_put(np);
|
||||
|
||||
if (!engine)
|
||||
return ERR_PTR(-EPROBE_DEFER);
|
||||
}
|
||||
|
||||
if (engine)
|
||||
get_device(engine->dev);
|
||||
|
||||
return engine;
|
||||
}
|
||||
EXPORT_SYMBOL(nand_ecc_get_on_host_hw_engine);
|
||||
|
||||
void nand_ecc_put_on_host_hw_engine(struct nand_device *nand)
|
||||
{
|
||||
put_device(nand->ecc.engine->dev);
|
||||
}
|
||||
EXPORT_SYMBOL(nand_ecc_put_on_host_hw_engine);
|
||||
|
||||
/*
|
||||
* In the case of a pipelined engine, the device registering the ECC
|
||||
* engine is not necessarily the ECC engine itself but may be a host controller.
|
||||
* It is then useful to provide a helper to retrieve the right device object
|
||||
* which actually represents the ECC engine.
|
||||
*/
|
||||
struct device *nand_ecc_get_engine_dev(struct device *host)
|
||||
{
|
||||
struct platform_device *ecc_pdev;
|
||||
struct device_node *np;
|
||||
|
||||
/*
|
||||
* If the device node contains this property, it means we need to follow
|
||||
* it in order to get the right ECC engine device we are looking for.
|
||||
*/
|
||||
np = of_parse_phandle(host->of_node, "nand-ecc-engine", 0);
|
||||
if (!np)
|
||||
return host;
|
||||
|
||||
ecc_pdev = of_find_device_by_node(np);
|
||||
if (!ecc_pdev) {
|
||||
of_node_put(np);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
platform_device_put(ecc_pdev);
|
||||
of_node_put(np);
|
||||
|
||||
return &ecc_pdev->dev;
|
||||
}
|
||||
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_AUTHOR("Miquel Raynal <miquel.raynal@bootlin.com>");
|
||||
MODULE_DESCRIPTION("Generic ECC engine");
|
||||
|
|
|
@ -381,7 +381,10 @@ static int spinand_read_from_cache_op(struct spinand_device *spinand,
|
|||
}
|
||||
}
|
||||
|
||||
rdesc = spinand->dirmaps[req->pos.plane].rdesc;
|
||||
if (req->mode == MTD_OPS_RAW)
|
||||
rdesc = spinand->dirmaps[req->pos.plane].rdesc;
|
||||
else
|
||||
rdesc = spinand->dirmaps[req->pos.plane].rdesc_ecc;
|
||||
|
||||
while (nbytes) {
|
||||
ret = spi_mem_dirmap_read(rdesc, column, nbytes, buf);
|
||||
|
@ -452,7 +455,10 @@ static int spinand_write_to_cache_op(struct spinand_device *spinand,
|
|||
req->ooblen);
|
||||
}
|
||||
|
||||
wdesc = spinand->dirmaps[req->pos.plane].wdesc;
|
||||
if (req->mode == MTD_OPS_RAW)
|
||||
wdesc = spinand->dirmaps[req->pos.plane].wdesc;
|
||||
else
|
||||
wdesc = spinand->dirmaps[req->pos.plane].wdesc_ecc;
|
||||
|
||||
while (nbytes) {
|
||||
ret = spi_mem_dirmap_write(wdesc, column, nbytes, buf);
|
||||
|
@ -865,6 +871,31 @@ static int spinand_create_dirmap(struct spinand_device *spinand,
|
|||
|
||||
spinand->dirmaps[plane].rdesc = desc;
|
||||
|
||||
if (nand->ecc.engine->integration != NAND_ECC_ENGINE_INTEGRATION_PIPELINED) {
|
||||
spinand->dirmaps[plane].wdesc_ecc = spinand->dirmaps[plane].wdesc;
|
||||
spinand->dirmaps[plane].rdesc_ecc = spinand->dirmaps[plane].rdesc;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
info.op_tmpl = *spinand->op_templates.update_cache;
|
||||
info.op_tmpl.data.ecc = true;
|
||||
desc = devm_spi_mem_dirmap_create(&spinand->spimem->spi->dev,
|
||||
spinand->spimem, &info);
|
||||
if (IS_ERR(desc))
|
||||
return PTR_ERR(desc);
|
||||
|
||||
spinand->dirmaps[plane].wdesc_ecc = desc;
|
||||
|
||||
info.op_tmpl = *spinand->op_templates.read_cache;
|
||||
info.op_tmpl.data.ecc = true;
|
||||
desc = devm_spi_mem_dirmap_create(&spinand->spimem->spi->dev,
|
||||
spinand->spimem, &info);
|
||||
if (IS_ERR(desc))
|
||||
return PTR_ERR(desc);
|
||||
|
||||
spinand->dirmaps[plane].rdesc_ecc = desc;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -1208,14 +1239,6 @@ static int spinand_init(struct spinand_device *spinand)
|
|||
if (ret)
|
||||
goto err_free_bufs;
|
||||
|
||||
ret = spinand_create_dirmaps(spinand);
|
||||
if (ret) {
|
||||
dev_err(dev,
|
||||
"Failed to create direct mappings for read/write operations (err = %d)\n",
|
||||
ret);
|
||||
goto err_manuf_cleanup;
|
||||
}
|
||||
|
||||
ret = nanddev_init(nand, &spinand_ops, THIS_MODULE);
|
||||
if (ret)
|
||||
goto err_manuf_cleanup;
|
||||
|
@ -1250,6 +1273,14 @@ static int spinand_init(struct spinand_device *spinand)
|
|||
mtd->ecc_strength = nanddev_get_ecc_conf(nand)->strength;
|
||||
mtd->ecc_step_size = nanddev_get_ecc_conf(nand)->step_size;
|
||||
|
||||
ret = spinand_create_dirmaps(spinand);
|
||||
if (ret) {
|
||||
dev_err(dev,
|
||||
"Failed to create direct mappings for read/write operations (err = %d)\n",
|
||||
ret);
|
||||
goto err_cleanup_ecc_engine;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
err_cleanup_ecc_engine:
|
||||
|
|
|
@ -20,7 +20,7 @@ static SPINAND_OP_VARIANTS(read_cache_variants,
|
|||
|
||||
static SPINAND_OP_VARIANTS(write_cache_variants,
|
||||
SPINAND_PROG_LOAD_X4(true, 0, NULL, 0),
|
||||
SPINAND_PROG_LOAD(true, 0, NULL, 0));
|
||||
SPINAND_PROG_LOAD(false, 0, NULL, 0));
|
||||
|
||||
static SPINAND_OP_VARIANTS(update_cache_variants,
|
||||
SPINAND_PROG_LOAD_X4(false, 0, NULL, 0),
|
||||
|
|
|
@ -879,6 +879,7 @@ config SPI_SYNQUACER
|
|||
config SPI_MXIC
|
||||
tristate "Macronix MX25F0A SPI controller"
|
||||
depends on SPI_MASTER
|
||||
imply MTD_NAND_ECC_MXIC
|
||||
help
|
||||
This selects the Macronix MX25F0A SPI controller driver.
|
||||
|
||||
|
|
|
@ -1441,10 +1441,7 @@ static bool cqspi_supports_mem_op(struct spi_mem *mem,
|
|||
if (!(all_true || all_false))
|
||||
return false;
|
||||
|
||||
if (all_true)
|
||||
return spi_mem_dtr_supports_op(mem, op);
|
||||
else
|
||||
return spi_mem_default_supports_op(mem, op);
|
||||
return spi_mem_default_supports_op(mem, op);
|
||||
}
|
||||
|
||||
static int cqspi_of_get_flash_pdata(struct platform_device *pdev,
|
||||
|
@ -1595,6 +1592,10 @@ static const struct spi_controller_mem_ops cqspi_mem_ops = {
|
|||
.supports_op = cqspi_supports_mem_op,
|
||||
};
|
||||
|
||||
static const struct spi_controller_mem_caps cqspi_mem_caps = {
|
||||
.dtr = true,
|
||||
};
|
||||
|
||||
static int cqspi_setup_flash(struct cqspi_st *cqspi)
|
||||
{
|
||||
struct platform_device *pdev = cqspi->pdev;
|
||||
|
@ -1652,6 +1653,7 @@ static int cqspi_probe(struct platform_device *pdev)
|
|||
}
|
||||
master->mode_bits = SPI_RX_QUAD | SPI_RX_DUAL;
|
||||
master->mem_ops = &cqspi_mem_ops;
|
||||
master->mem_caps = &cqspi_mem_caps;
|
||||
master->dev.of_node = pdev->dev.of_node;
|
||||
|
||||
cqspi = spi_master_get_devdata(master);
|
||||
|
|
|
@ -160,24 +160,28 @@ static bool spi_mem_check_buswidth(struct spi_mem *mem,
|
|||
return true;
|
||||
}
|
||||
|
||||
bool spi_mem_dtr_supports_op(struct spi_mem *mem,
|
||||
const struct spi_mem_op *op)
|
||||
{
|
||||
if (op->cmd.nbytes != 2)
|
||||
return false;
|
||||
|
||||
return spi_mem_check_buswidth(mem, op);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(spi_mem_dtr_supports_op);
|
||||
|
||||
bool spi_mem_default_supports_op(struct spi_mem *mem,
|
||||
const struct spi_mem_op *op)
|
||||
{
|
||||
if (op->cmd.dtr || op->addr.dtr || op->dummy.dtr || op->data.dtr)
|
||||
return false;
|
||||
struct spi_controller *ctlr = mem->spi->controller;
|
||||
bool op_is_dtr =
|
||||
op->cmd.dtr || op->addr.dtr || op->dummy.dtr || op->data.dtr;
|
||||
|
||||
if (op->cmd.nbytes != 1)
|
||||
return false;
|
||||
if (op_is_dtr) {
|
||||
if (!spi_mem_controller_is_capable(ctlr, dtr))
|
||||
return false;
|
||||
|
||||
if (op->cmd.nbytes != 2)
|
||||
return false;
|
||||
} else {
|
||||
if (op->cmd.nbytes != 1)
|
||||
return false;
|
||||
}
|
||||
|
||||
if (op->data.ecc) {
|
||||
if (!spi_mem_controller_is_capable(ctlr, ecc))
|
||||
return false;
|
||||
}
|
||||
|
||||
return spi_mem_check_buswidth(mem, op);
|
||||
}
|
||||
|
|
|
@ -12,6 +12,8 @@
|
|||
#include <linux/io.h>
|
||||
#include <linux/iopoll.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/mtd/nand.h>
|
||||
#include <linux/mtd/nand-ecc-mxic.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/pm_runtime.h>
|
||||
#include <linux/spi/spi.h>
|
||||
|
@ -167,11 +169,23 @@
|
|||
#define HW_TEST(x) (0xe0 + ((x) * 4))
|
||||
|
||||
struct mxic_spi {
|
||||
struct device *dev;
|
||||
struct clk *ps_clk;
|
||||
struct clk *send_clk;
|
||||
struct clk *send_dly_clk;
|
||||
void __iomem *regs;
|
||||
u32 cur_speed_hz;
|
||||
struct {
|
||||
void __iomem *map;
|
||||
dma_addr_t dma;
|
||||
size_t size;
|
||||
} linear;
|
||||
|
||||
struct {
|
||||
bool use_pipelined_conf;
|
||||
struct nand_ecc_engine *pipelined_engine;
|
||||
void *ctx;
|
||||
} ecc;
|
||||
};
|
||||
|
||||
static int mxic_spi_clk_enable(struct mxic_spi *mxic)
|
||||
|
@ -280,6 +294,51 @@ static void mxic_spi_hw_init(struct mxic_spi *mxic)
|
|||
mxic->regs + HC_CFG);
|
||||
}
|
||||
|
||||
static u32 mxic_spi_prep_hc_cfg(struct spi_device *spi, u32 flags)
|
||||
{
|
||||
int nio = 1;
|
||||
|
||||
if (spi->mode & (SPI_TX_OCTAL | SPI_RX_OCTAL))
|
||||
nio = 8;
|
||||
else if (spi->mode & (SPI_TX_QUAD | SPI_RX_QUAD))
|
||||
nio = 4;
|
||||
else if (spi->mode & (SPI_TX_DUAL | SPI_RX_DUAL))
|
||||
nio = 2;
|
||||
|
||||
return flags | HC_CFG_NIO(nio) |
|
||||
HC_CFG_TYPE(spi->chip_select, HC_CFG_TYPE_SPI_NOR) |
|
||||
HC_CFG_SLV_ACT(spi->chip_select) | HC_CFG_IDLE_SIO_LVL(1);
|
||||
}
|
||||
|
||||
static u32 mxic_spi_mem_prep_op_cfg(const struct spi_mem_op *op,
|
||||
unsigned int data_len)
|
||||
{
|
||||
u32 cfg = OP_CMD_BYTES(op->cmd.nbytes) |
|
||||
OP_CMD_BUSW(fls(op->cmd.buswidth) - 1) |
|
||||
(op->cmd.dtr ? OP_CMD_DDR : 0);
|
||||
|
||||
if (op->addr.nbytes)
|
||||
cfg |= OP_ADDR_BYTES(op->addr.nbytes) |
|
||||
OP_ADDR_BUSW(fls(op->addr.buswidth) - 1) |
|
||||
(op->addr.dtr ? OP_ADDR_DDR : 0);
|
||||
|
||||
if (op->dummy.nbytes)
|
||||
cfg |= OP_DUMMY_CYC(op->dummy.nbytes);
|
||||
|
||||
/* Direct mapping data.nbytes field is not populated */
|
||||
if (data_len) {
|
||||
cfg |= OP_DATA_BUSW(fls(op->data.buswidth) - 1) |
|
||||
(op->data.dtr ? OP_DATA_DDR : 0);
|
||||
if (op->data.dir == SPI_MEM_DATA_IN) {
|
||||
cfg |= OP_READ;
|
||||
if (op->data.dtr)
|
||||
cfg |= OP_DQS_EN;
|
||||
}
|
||||
}
|
||||
|
||||
return cfg;
|
||||
}
|
||||
|
||||
static int mxic_spi_data_xfer(struct mxic_spi *mxic, const void *txbuf,
|
||||
void *rxbuf, unsigned int len)
|
||||
{
|
||||
|
@ -304,25 +363,21 @@ static int mxic_spi_data_xfer(struct mxic_spi *mxic, const void *txbuf,
|
|||
|
||||
writel(data, mxic->regs + TXD(nbytes % 4));
|
||||
|
||||
ret = readl_poll_timeout(mxic->regs + INT_STS, sts,
|
||||
sts & INT_TX_EMPTY, 0, USEC_PER_SEC);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = readl_poll_timeout(mxic->regs + INT_STS, sts,
|
||||
sts & INT_RX_NOT_EMPTY, 0,
|
||||
USEC_PER_SEC);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
data = readl(mxic->regs + RXD);
|
||||
if (rxbuf) {
|
||||
ret = readl_poll_timeout(mxic->regs + INT_STS, sts,
|
||||
sts & INT_TX_EMPTY, 0,
|
||||
USEC_PER_SEC);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = readl_poll_timeout(mxic->regs + INT_STS, sts,
|
||||
sts & INT_RX_NOT_EMPTY, 0,
|
||||
USEC_PER_SEC);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
data = readl(mxic->regs + RXD);
|
||||
data >>= (8 * (4 - nbytes));
|
||||
memcpy(rxbuf + pos, &data, nbytes);
|
||||
WARN_ON(readl(mxic->regs + INT_STS) & INT_RX_NOT_EMPTY);
|
||||
} else {
|
||||
readl(mxic->regs + RXD);
|
||||
}
|
||||
WARN_ON(readl(mxic->regs + INT_STS) & INT_RX_NOT_EMPTY);
|
||||
|
||||
|
@ -332,11 +387,96 @@ static int mxic_spi_data_xfer(struct mxic_spi *mxic, const void *txbuf,
|
|||
return 0;
|
||||
}
|
||||
|
||||
static ssize_t mxic_spi_mem_dirmap_read(struct spi_mem_dirmap_desc *desc,
|
||||
u64 offs, size_t len, void *buf)
|
||||
{
|
||||
struct mxic_spi *mxic = spi_master_get_devdata(desc->mem->spi->master);
|
||||
int ret;
|
||||
u32 sts;
|
||||
|
||||
if (WARN_ON(offs + desc->info.offset + len > U32_MAX))
|
||||
return -EINVAL;
|
||||
|
||||
writel(mxic_spi_prep_hc_cfg(desc->mem->spi, 0), mxic->regs + HC_CFG);
|
||||
|
||||
writel(mxic_spi_mem_prep_op_cfg(&desc->info.op_tmpl, len),
|
||||
mxic->regs + LRD_CFG);
|
||||
writel(desc->info.offset + offs, mxic->regs + LRD_ADDR);
|
||||
len = min_t(size_t, len, mxic->linear.size);
|
||||
writel(len, mxic->regs + LRD_RANGE);
|
||||
writel(LMODE_CMD0(desc->info.op_tmpl.cmd.opcode) |
|
||||
LMODE_SLV_ACT(desc->mem->spi->chip_select) |
|
||||
LMODE_EN,
|
||||
mxic->regs + LRD_CTRL);
|
||||
|
||||
if (mxic->ecc.use_pipelined_conf && desc->info.op_tmpl.data.ecc) {
|
||||
ret = mxic_ecc_process_data_pipelined(mxic->ecc.pipelined_engine,
|
||||
NAND_PAGE_READ,
|
||||
mxic->linear.dma + offs);
|
||||
if (ret)
|
||||
return ret;
|
||||
} else {
|
||||
memcpy_fromio(buf, mxic->linear.map, len);
|
||||
}
|
||||
|
||||
writel(INT_LRD_DIS, mxic->regs + INT_STS);
|
||||
writel(0, mxic->regs + LRD_CTRL);
|
||||
|
||||
ret = readl_poll_timeout(mxic->regs + INT_STS, sts,
|
||||
sts & INT_LRD_DIS, 0, USEC_PER_SEC);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
static ssize_t mxic_spi_mem_dirmap_write(struct spi_mem_dirmap_desc *desc,
|
||||
u64 offs, size_t len,
|
||||
const void *buf)
|
||||
{
|
||||
struct mxic_spi *mxic = spi_master_get_devdata(desc->mem->spi->master);
|
||||
u32 sts;
|
||||
int ret;
|
||||
|
||||
if (WARN_ON(offs + desc->info.offset + len > U32_MAX))
|
||||
return -EINVAL;
|
||||
|
||||
writel(mxic_spi_prep_hc_cfg(desc->mem->spi, 0), mxic->regs + HC_CFG);
|
||||
|
||||
writel(mxic_spi_mem_prep_op_cfg(&desc->info.op_tmpl, len),
|
||||
mxic->regs + LWR_CFG);
|
||||
writel(desc->info.offset + offs, mxic->regs + LWR_ADDR);
|
||||
len = min_t(size_t, len, mxic->linear.size);
|
||||
writel(len, mxic->regs + LWR_RANGE);
|
||||
writel(LMODE_CMD0(desc->info.op_tmpl.cmd.opcode) |
|
||||
LMODE_SLV_ACT(desc->mem->spi->chip_select) |
|
||||
LMODE_EN,
|
||||
mxic->regs + LWR_CTRL);
|
||||
|
||||
if (mxic->ecc.use_pipelined_conf && desc->info.op_tmpl.data.ecc) {
|
||||
ret = mxic_ecc_process_data_pipelined(mxic->ecc.pipelined_engine,
|
||||
NAND_PAGE_WRITE,
|
||||
mxic->linear.dma + offs);
|
||||
if (ret)
|
||||
return ret;
|
||||
} else {
|
||||
memcpy_toio(mxic->linear.map, buf, len);
|
||||
}
|
||||
|
||||
writel(INT_LWR_DIS, mxic->regs + INT_STS);
|
||||
writel(0, mxic->regs + LWR_CTRL);
|
||||
|
||||
ret = readl_poll_timeout(mxic->regs + INT_STS, sts,
|
||||
sts & INT_LWR_DIS, 0, USEC_PER_SEC);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
static bool mxic_spi_mem_supports_op(struct spi_mem *mem,
|
||||
const struct spi_mem_op *op)
|
||||
{
|
||||
bool all_false;
|
||||
|
||||
if (op->data.buswidth > 8 || op->addr.buswidth > 8 ||
|
||||
op->dummy.buswidth > 8 || op->cmd.buswidth > 8)
|
||||
return false;
|
||||
|
@ -348,64 +488,43 @@ static bool mxic_spi_mem_supports_op(struct spi_mem *mem,
|
|||
if (op->addr.nbytes > 7)
|
||||
return false;
|
||||
|
||||
all_false = !op->cmd.dtr && !op->addr.dtr && !op->dummy.dtr &&
|
||||
!op->data.dtr;
|
||||
return spi_mem_default_supports_op(mem, op);
|
||||
}
|
||||
|
||||
if (all_false)
|
||||
return spi_mem_default_supports_op(mem, op);
|
||||
else
|
||||
return spi_mem_dtr_supports_op(mem, op);
|
||||
static int mxic_spi_mem_dirmap_create(struct spi_mem_dirmap_desc *desc)
|
||||
{
|
||||
struct mxic_spi *mxic = spi_master_get_devdata(desc->mem->spi->master);
|
||||
|
||||
if (!mxic->linear.map)
|
||||
return -EINVAL;
|
||||
|
||||
if (desc->info.offset + desc->info.length > U32_MAX)
|
||||
return -EINVAL;
|
||||
|
||||
if (!mxic_spi_mem_supports_op(desc->mem, &desc->info.op_tmpl))
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mxic_spi_mem_exec_op(struct spi_mem *mem,
|
||||
const struct spi_mem_op *op)
|
||||
{
|
||||
struct mxic_spi *mxic = spi_master_get_devdata(mem->spi->master);
|
||||
int nio = 1, i, ret;
|
||||
u32 ss_ctrl;
|
||||
int i, ret;
|
||||
u8 addr[8], cmd[2];
|
||||
|
||||
ret = mxic_spi_set_freq(mxic, mem->spi->max_speed_hz);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (mem->spi->mode & (SPI_TX_OCTAL | SPI_RX_OCTAL))
|
||||
nio = 8;
|
||||
else if (mem->spi->mode & (SPI_TX_QUAD | SPI_RX_QUAD))
|
||||
nio = 4;
|
||||
else if (mem->spi->mode & (SPI_TX_DUAL | SPI_RX_DUAL))
|
||||
nio = 2;
|
||||
|
||||
writel(HC_CFG_NIO(nio) |
|
||||
HC_CFG_TYPE(mem->spi->chip_select, HC_CFG_TYPE_SPI_NOR) |
|
||||
HC_CFG_SLV_ACT(mem->spi->chip_select) | HC_CFG_IDLE_SIO_LVL(1) |
|
||||
HC_CFG_MAN_CS_EN,
|
||||
writel(mxic_spi_prep_hc_cfg(mem->spi, HC_CFG_MAN_CS_EN),
|
||||
mxic->regs + HC_CFG);
|
||||
|
||||
writel(HC_EN_BIT, mxic->regs + HC_EN);
|
||||
|
||||
ss_ctrl = OP_CMD_BYTES(op->cmd.nbytes) |
|
||||
OP_CMD_BUSW(fls(op->cmd.buswidth) - 1) |
|
||||
(op->cmd.dtr ? OP_CMD_DDR : 0);
|
||||
|
||||
if (op->addr.nbytes)
|
||||
ss_ctrl |= OP_ADDR_BYTES(op->addr.nbytes) |
|
||||
OP_ADDR_BUSW(fls(op->addr.buswidth) - 1) |
|
||||
(op->addr.dtr ? OP_ADDR_DDR : 0);
|
||||
|
||||
if (op->dummy.nbytes)
|
||||
ss_ctrl |= OP_DUMMY_CYC(op->dummy.nbytes);
|
||||
|
||||
if (op->data.nbytes) {
|
||||
ss_ctrl |= OP_DATA_BUSW(fls(op->data.buswidth) - 1) |
|
||||
(op->data.dtr ? OP_DATA_DDR : 0);
|
||||
if (op->data.dir == SPI_MEM_DATA_IN) {
|
||||
ss_ctrl |= OP_READ;
|
||||
if (op->data.dtr)
|
||||
ss_ctrl |= OP_DQS_EN;
|
||||
}
|
||||
}
|
||||
|
||||
writel(ss_ctrl, mxic->regs + SS_CTRL(mem->spi->chip_select));
|
||||
writel(mxic_spi_mem_prep_op_cfg(op, op->data.nbytes),
|
||||
mxic->regs + SS_CTRL(mem->spi->chip_select));
|
||||
|
||||
writel(readl(mxic->regs + HC_CFG) | HC_CFG_MAN_CS_ASSERT,
|
||||
mxic->regs + HC_CFG);
|
||||
|
@ -446,6 +565,14 @@ out:
|
|||
static const struct spi_controller_mem_ops mxic_spi_mem_ops = {
|
||||
.supports_op = mxic_spi_mem_supports_op,
|
||||
.exec_op = mxic_spi_mem_exec_op,
|
||||
.dirmap_create = mxic_spi_mem_dirmap_create,
|
||||
.dirmap_read = mxic_spi_mem_dirmap_read,
|
||||
.dirmap_write = mxic_spi_mem_dirmap_write,
|
||||
};
|
||||
|
||||
static const struct spi_controller_mem_caps mxic_spi_mem_caps = {
|
||||
.dtr = true,
|
||||
.ecc = true,
|
||||
};
|
||||
|
||||
static void mxic_spi_set_cs(struct spi_device *spi, bool lvl)
|
||||
|
@ -510,6 +637,80 @@ static int mxic_spi_transfer_one(struct spi_master *master,
|
|||
return 0;
|
||||
}
|
||||
|
||||
/* ECC wrapper */
|
||||
static int mxic_spi_mem_ecc_init_ctx(struct nand_device *nand)
|
||||
{
|
||||
struct nand_ecc_engine_ops *ops = mxic_ecc_get_pipelined_ops();
|
||||
struct mxic_spi *mxic = nand->ecc.engine->priv;
|
||||
|
||||
mxic->ecc.use_pipelined_conf = true;
|
||||
|
||||
return ops->init_ctx(nand);
|
||||
}
|
||||
|
||||
static void mxic_spi_mem_ecc_cleanup_ctx(struct nand_device *nand)
|
||||
{
|
||||
struct nand_ecc_engine_ops *ops = mxic_ecc_get_pipelined_ops();
|
||||
struct mxic_spi *mxic = nand->ecc.engine->priv;
|
||||
|
||||
mxic->ecc.use_pipelined_conf = false;
|
||||
|
||||
ops->cleanup_ctx(nand);
|
||||
}
|
||||
|
||||
static int mxic_spi_mem_ecc_prepare_io_req(struct nand_device *nand,
|
||||
struct nand_page_io_req *req)
|
||||
{
|
||||
struct nand_ecc_engine_ops *ops = mxic_ecc_get_pipelined_ops();
|
||||
|
||||
return ops->prepare_io_req(nand, req);
|
||||
}
|
||||
|
||||
static int mxic_spi_mem_ecc_finish_io_req(struct nand_device *nand,
|
||||
struct nand_page_io_req *req)
|
||||
{
|
||||
struct nand_ecc_engine_ops *ops = mxic_ecc_get_pipelined_ops();
|
||||
|
||||
return ops->finish_io_req(nand, req);
|
||||
}
|
||||
|
||||
static struct nand_ecc_engine_ops mxic_spi_mem_ecc_engine_pipelined_ops = {
|
||||
.init_ctx = mxic_spi_mem_ecc_init_ctx,
|
||||
.cleanup_ctx = mxic_spi_mem_ecc_cleanup_ctx,
|
||||
.prepare_io_req = mxic_spi_mem_ecc_prepare_io_req,
|
||||
.finish_io_req = mxic_spi_mem_ecc_finish_io_req,
|
||||
};
|
||||
|
||||
static void mxic_spi_mem_ecc_remove(struct mxic_spi *mxic)
|
||||
{
|
||||
if (mxic->ecc.pipelined_engine) {
|
||||
mxic_ecc_put_pipelined_engine(mxic->ecc.pipelined_engine);
|
||||
nand_ecc_unregister_on_host_hw_engine(mxic->ecc.pipelined_engine);
|
||||
}
|
||||
}
|
||||
|
||||
static int mxic_spi_mem_ecc_probe(struct platform_device *pdev,
|
||||
struct mxic_spi *mxic)
|
||||
{
|
||||
struct nand_ecc_engine *eng;
|
||||
|
||||
if (!mxic_ecc_get_pipelined_ops())
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
eng = mxic_ecc_get_pipelined_engine(pdev);
|
||||
if (IS_ERR(eng))
|
||||
return PTR_ERR(eng);
|
||||
|
||||
eng->dev = &pdev->dev;
|
||||
eng->integration = NAND_ECC_ENGINE_INTEGRATION_PIPELINED;
|
||||
eng->ops = &mxic_spi_mem_ecc_engine_pipelined_ops;
|
||||
eng->priv = mxic;
|
||||
mxic->ecc.pipelined_engine = eng;
|
||||
nand_ecc_register_on_host_hw_engine(eng);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int __maybe_unused mxic_spi_runtime_suspend(struct device *dev)
|
||||
{
|
||||
struct spi_master *master = dev_get_drvdata(dev);
|
||||
|
@ -555,6 +756,7 @@ static int mxic_spi_probe(struct platform_device *pdev)
|
|||
platform_set_drvdata(pdev, master);
|
||||
|
||||
mxic = spi_master_get_devdata(master);
|
||||
mxic->dev = &pdev->dev;
|
||||
|
||||
master->dev.of_node = pdev->dev.of_node;
|
||||
|
||||
|
@ -575,11 +777,21 @@ static int mxic_spi_probe(struct platform_device *pdev)
|
|||
if (IS_ERR(mxic->regs))
|
||||
return PTR_ERR(mxic->regs);
|
||||
|
||||
res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "dirmap");
|
||||
mxic->linear.map = devm_ioremap_resource(&pdev->dev, res);
|
||||
if (!IS_ERR(mxic->linear.map)) {
|
||||
mxic->linear.dma = res->start;
|
||||
mxic->linear.size = resource_size(res);
|
||||
} else {
|
||||
mxic->linear.map = NULL;
|
||||
}
|
||||
|
||||
pm_runtime_enable(&pdev->dev);
|
||||
master->auto_runtime_pm = true;
|
||||
|
||||
master->num_chipselect = 1;
|
||||
master->mem_ops = &mxic_spi_mem_ops;
|
||||
master->mem_caps = &mxic_spi_mem_caps;
|
||||
|
||||
master->set_cs = mxic_spi_set_cs;
|
||||
master->transfer_one = mxic_spi_transfer_one;
|
||||
|
@ -591,6 +803,12 @@ static int mxic_spi_probe(struct platform_device *pdev)
|
|||
|
||||
mxic_spi_hw_init(mxic);
|
||||
|
||||
ret = mxic_spi_mem_ecc_probe(pdev, mxic);
|
||||
if (ret == -EPROBE_DEFER) {
|
||||
pm_runtime_disable(&pdev->dev);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = spi_register_master(master);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "spi_register_master failed\n");
|
||||
|
@ -603,8 +821,10 @@ static int mxic_spi_probe(struct platform_device *pdev)
|
|||
static int mxic_spi_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct spi_master *master = platform_get_drvdata(pdev);
|
||||
struct mxic_spi *mxic = spi_master_get_devdata(master);
|
||||
|
||||
pm_runtime_disable(&pdev->dev);
|
||||
mxic_spi_mem_ecc_remove(mxic);
|
||||
spi_unregister_master(master);
|
||||
|
||||
return 0;
|
||||
|
|
|
@ -0,0 +1,49 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0 */
|
||||
/*
|
||||
* Copyright © 2019 Macronix
|
||||
* Author: Miquèl Raynal <miquel.raynal@bootlin.com>
|
||||
*
|
||||
* Header for the Macronix external ECC engine.
|
||||
*/
|
||||
|
||||
#ifndef __MTD_NAND_ECC_MXIC_H__
|
||||
#define __MTD_NAND_ECC_MXIC_H__
|
||||
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/device.h>
|
||||
|
||||
struct mxic_ecc_engine;
|
||||
|
||||
#if IS_ENABLED(CONFIG_MTD_NAND_ECC_MXIC) && IS_REACHABLE(CONFIG_MTD_NAND_CORE)
|
||||
|
||||
struct nand_ecc_engine_ops *mxic_ecc_get_pipelined_ops(void);
|
||||
struct nand_ecc_engine *mxic_ecc_get_pipelined_engine(struct platform_device *spi_pdev);
|
||||
void mxic_ecc_put_pipelined_engine(struct nand_ecc_engine *eng);
|
||||
int mxic_ecc_process_data_pipelined(struct nand_ecc_engine *eng,
|
||||
unsigned int direction, dma_addr_t dirmap);
|
||||
|
||||
#else /* !CONFIG_MTD_NAND_ECC_MXIC */
|
||||
|
||||
static inline struct nand_ecc_engine_ops *mxic_ecc_get_pipelined_ops(void)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static inline struct nand_ecc_engine *
|
||||
mxic_ecc_get_pipelined_engine(struct platform_device *spi_pdev)
|
||||
{
|
||||
return ERR_PTR(-EOPNOTSUPP);
|
||||
}
|
||||
|
||||
static inline void mxic_ecc_put_pipelined_engine(struct nand_ecc_engine *eng) {}
|
||||
|
||||
static inline int mxic_ecc_process_data_pipelined(struct nand_ecc_engine *eng,
|
||||
unsigned int direction,
|
||||
dma_addr_t dirmap)
|
||||
{
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
|
||||
#endif /* CONFIG_MTD_NAND_ECC_MXIC */
|
||||
|
||||
#endif /* __MTD_NAND_ECC_MXIC_H__ */
|
|
@ -263,12 +263,36 @@ struct nand_ecc_engine_ops {
|
|||
struct nand_page_io_req *req);
|
||||
};
|
||||
|
||||
/**
|
||||
* enum nand_ecc_engine_integration - How the NAND ECC engine is integrated
|
||||
* @NAND_ECC_ENGINE_INTEGRATION_INVALID: Invalid value
|
||||
* @NAND_ECC_ENGINE_INTEGRATION_PIPELINED: Pipelined engine, performs on-the-fly
|
||||
* correction, does not need to copy
|
||||
* data around
|
||||
* @NAND_ECC_ENGINE_INTEGRATION_EXTERNAL: External engine, needs to bring the
|
||||
* data into its own area before use
|
||||
*/
|
||||
enum nand_ecc_engine_integration {
|
||||
NAND_ECC_ENGINE_INTEGRATION_INVALID,
|
||||
NAND_ECC_ENGINE_INTEGRATION_PIPELINED,
|
||||
NAND_ECC_ENGINE_INTEGRATION_EXTERNAL,
|
||||
};
|
||||
|
||||
/**
|
||||
* struct nand_ecc_engine - ECC engine abstraction for NAND devices
|
||||
* @dev: Host device
|
||||
* @node: Private field for registration time
|
||||
* @ops: ECC engine operations
|
||||
* @integration: How the engine is integrated with the host
|
||||
* (only relevant on %NAND_ECC_ENGINE_TYPE_ON_HOST engines)
|
||||
* @priv: Private data
|
||||
*/
|
||||
struct nand_ecc_engine {
|
||||
struct device *dev;
|
||||
struct list_head node;
|
||||
struct nand_ecc_engine_ops *ops;
|
||||
enum nand_ecc_engine_integration integration;
|
||||
void *priv;
|
||||
};
|
||||
|
||||
void of_get_nand_ecc_user_config(struct nand_device *nand);
|
||||
|
@ -279,8 +303,28 @@ int nand_ecc_prepare_io_req(struct nand_device *nand,
|
|||
int nand_ecc_finish_io_req(struct nand_device *nand,
|
||||
struct nand_page_io_req *req);
|
||||
bool nand_ecc_is_strong_enough(struct nand_device *nand);
|
||||
|
||||
#if IS_REACHABLE(CONFIG_MTD_NAND_CORE)
|
||||
int nand_ecc_register_on_host_hw_engine(struct nand_ecc_engine *engine);
|
||||
int nand_ecc_unregister_on_host_hw_engine(struct nand_ecc_engine *engine);
|
||||
#else
|
||||
static inline int
|
||||
nand_ecc_register_on_host_hw_engine(struct nand_ecc_engine *engine)
|
||||
{
|
||||
return -ENOTSUPP;
|
||||
}
|
||||
static inline int
|
||||
nand_ecc_unregister_on_host_hw_engine(struct nand_ecc_engine *engine)
|
||||
{
|
||||
return -ENOTSUPP;
|
||||
}
|
||||
#endif
|
||||
|
||||
struct nand_ecc_engine *nand_ecc_get_sw_engine(struct nand_device *nand);
|
||||
struct nand_ecc_engine *nand_ecc_get_on_die_hw_engine(struct nand_device *nand);
|
||||
struct nand_ecc_engine *nand_ecc_get_on_host_hw_engine(struct nand_device *nand);
|
||||
void nand_ecc_put_on_host_hw_engine(struct nand_device *nand);
|
||||
struct device *nand_ecc_get_engine_dev(struct device *host);
|
||||
|
||||
#if IS_ENABLED(CONFIG_MTD_NAND_ECC_SW_HAMMING)
|
||||
struct nand_ecc_engine *nand_ecc_sw_hamming_get_engine(void);
|
||||
|
@ -962,6 +1006,11 @@ int nanddev_markbad(struct nand_device *nand, const struct nand_pos *pos);
|
|||
int nanddev_ecc_engine_init(struct nand_device *nand);
|
||||
void nanddev_ecc_engine_cleanup(struct nand_device *nand);
|
||||
|
||||
static inline void *nand_to_ecc_ctx(struct nand_device *nand)
|
||||
{
|
||||
return nand->ecc.ctx.priv;
|
||||
}
|
||||
|
||||
/* BBT related functions */
|
||||
enum nand_bbt_block_status {
|
||||
NAND_BBT_BLOCK_STATUS_UNKNOWN,
|
||||
|
|
|
@ -389,6 +389,8 @@ struct spinand_info {
|
|||
struct spinand_dirmap {
|
||||
struct spi_mem_dirmap_desc *wdesc;
|
||||
struct spi_mem_dirmap_desc *rdesc;
|
||||
struct spi_mem_dirmap_desc *wdesc_ecc;
|
||||
struct spi_mem_dirmap_desc *rdesc_ecc;
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
@ -89,6 +89,7 @@ enum spi_mem_data_dir {
|
|||
* @dummy.dtr: whether the dummy bytes should be sent in DTR mode or not
|
||||
* @data.buswidth: number of IO lanes used to send/receive the data
|
||||
* @data.dtr: whether the data should be sent in DTR mode or not
|
||||
* @data.ecc: whether error correction is required or not
|
||||
* @data.dir: direction of the transfer
|
||||
* @data.nbytes: number of data bytes to send/receive. Can be zero if the
|
||||
* operation does not involve transferring data
|
||||
|
@ -119,6 +120,7 @@ struct spi_mem_op {
|
|||
struct {
|
||||
u8 buswidth;
|
||||
u8 dtr : 1;
|
||||
u8 ecc : 1;
|
||||
enum spi_mem_data_dir dir;
|
||||
unsigned int nbytes;
|
||||
union {
|
||||
|
@ -285,6 +287,19 @@ struct spi_controller_mem_ops {
|
|||
unsigned long timeout_ms);
|
||||
};
|
||||
|
||||
/**
|
||||
* struct spi_controller_mem_caps - SPI memory controller capabilities
|
||||
* @dtr: Supports DTR operations
|
||||
* @ecc: Supports operations with error correction
|
||||
*/
|
||||
struct spi_controller_mem_caps {
|
||||
bool dtr;
|
||||
bool ecc;
|
||||
};
|
||||
|
||||
#define spi_mem_controller_is_capable(ctlr, cap) \
|
||||
((ctlr)->mem_caps && (ctlr)->mem_caps->cap)
|
||||
|
||||
/**
|
||||
* struct spi_mem_driver - SPI memory driver
|
||||
* @spidrv: inherit from a SPI driver
|
||||
|
@ -319,10 +334,6 @@ void spi_controller_dma_unmap_mem_op_data(struct spi_controller *ctlr,
|
|||
|
||||
bool spi_mem_default_supports_op(struct spi_mem *mem,
|
||||
const struct spi_mem_op *op);
|
||||
|
||||
bool spi_mem_dtr_supports_op(struct spi_mem *mem,
|
||||
const struct spi_mem_op *op);
|
||||
|
||||
#else
|
||||
static inline int
|
||||
spi_controller_dma_map_mem_op_data(struct spi_controller *ctlr,
|
||||
|
@ -345,13 +356,6 @@ bool spi_mem_default_supports_op(struct spi_mem *mem,
|
|||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
static inline
|
||||
bool spi_mem_dtr_supports_op(struct spi_mem *mem,
|
||||
const struct spi_mem_op *op)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
#endif /* CONFIG_SPI_MEM */
|
||||
|
||||
int spi_mem_adjust_op_size(struct spi_mem *mem, struct spi_mem_op *op);
|
||||
|
|
|
@ -23,6 +23,7 @@ struct ptp_system_timestamp;
|
|||
struct spi_controller;
|
||||
struct spi_transfer;
|
||||
struct spi_controller_mem_ops;
|
||||
struct spi_controller_mem_caps;
|
||||
|
||||
/*
|
||||
* INTERFACES between SPI master-side drivers and SPI slave protocol handlers,
|
||||
|
@ -415,6 +416,7 @@ extern struct spi_device *spi_new_ancillary_device(struct spi_device *spi, u8 ch
|
|||
* @mem_ops: optimized/dedicated operations for interactions with SPI memory.
|
||||
* This field is optional and should only be implemented if the
|
||||
* controller has native support for memory like operations.
|
||||
* @mem_caps: controller capabilities for the handling of memory operations.
|
||||
* @unprepare_message: undo any work done by prepare_message().
|
||||
* @slave_abort: abort the ongoing transfer request on an SPI slave controller
|
||||
* @cs_gpios: LEGACY: array of GPIO descs to use as chip select lines; one per
|
||||
|
@ -639,6 +641,7 @@ struct spi_controller {
|
|||
|
||||
/* Optimized handlers for SPI memory-like operations. */
|
||||
const struct spi_controller_mem_ops *mem_ops;
|
||||
const struct spi_controller_mem_caps *mem_caps;
|
||||
|
||||
/* gpio chip select */
|
||||
int *cs_gpios;
|
||||
|
|
Loading…
Reference in New Issue