mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2025-01-04 04:04:19 +00:00
Core MTD changes:
* Replace the expert mode symbols with a single helper * Fix misuses of of_match_ptr() * Remove partid and partname debugfs files * tests: Fix eraseblock read speed miscalculation for lower partition sizes * TRX parser: Allow to use on MediaTek MIPS SoCs MTD driver changes: * spear_smi: use GFP_KERNEL * mchp48l640: Add SPI ID table * mchp23k256: Add SPI ID table * blkdevs: Avoid soft lockups with some mtd/spi devices * aspeed-smc: Improve probe resilience Hyperbus changes: * HBMC_AM654 should depend on ARCH_K3 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 - Make two read-only arrays static const - Fix compile test issue Raw NAND core changes: * Fix misuses of of_match_node() * Rework of_get_nand_bus_width() * Remove of_get_nand_on_flash_bbt() wrapper * Protect access to rawnand devices while in suspend * bindings: Document the wp-gpios property Rax NAND controller driver changes: * atmel: Fix refcount issue in atmel_nand_controller_init * nandsim: - Add NS_PAGE_BYTE_SHIFT macro to replace the repeat pattern - Merge repeat codes in ns_switch_state - Replace overflow check with kzalloc to single kcalloc * rockchip: Fix platform_get_irq.cocci warning * stm32_fmc2: Add NAND Write Protect support * pl353: Set the nand chip node as the flash node * brcmnand: Fix sparse warnings in bcma_nand * omap_elm: Remove redundant variable 'errors' * gpmi: - Support fast edo timings for mx28 - Validate controller clock rate - Fix controller timings setting * brcmnand: - Add BCMA shim - BCMA controller uses command shift of 0 - Allow platform data instantation - Add platform data structure for BCMA - Allow working without interrupts - Move OF operations out of brcmnand_init_cs() - Avoid pdev in brcmnand_init_cs() - Allow SoC to provide I/O operations - Assign soc as early as possible Onenand changes: * Check for error irq 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 NOR core changes: * Move vendor specific code out of the core into vendor drivers. * Unify all function and object names in the vendor modules. * Make setup() callback optional to improve readability. * Skip erase logic when the SPI_NOR_NO_ERASE flag is set at flash declaration. 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 Binding changes: * Dropped mtd/cortina,gemini-flash.txt * Convert BCM47xx partitions to json-schema * 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 -----BEGIN PGP SIGNATURE----- iQEzBAABCgAdFiEE9HuaYnbmDhq/XIDIJWrqGEe9VoQFAmI7WJcACgkQJWrqGEe9 VoSzpAgAutzRv9TRUiXdBGGlJ851QaZ6ZUvT1bHKTQA+xZi+MZyNmc0cWNg3B70I PnwyxNAmRkUQKwV5Vgy/sQrt42qZnPmr+8XMq+UiziPmgFdjiTdLqGcN619Hi12t JqtoKL828R064LSEq5nWsJ2waoGT1nNtZK8kA2qe8ctvmH0YTThriVZUQR4/Befb OGFheceLFycE/vkktPPr3As4603fMiyDOT7EA3Mtzgjohry0a0TqoakHCaHC/fYo 0/h+x+jJATPtgbWm1ZiV3cZ/Su00+rKuQOsiAWvM/pqDaijsVntBmtK0PRtums2Q m8LCspuQYNnCINeQXqba9RxACpibDg== =+6Zk -----END PGP SIGNATURE----- Merge tag 'mtd/changes-for-5.18' of git://git.kernel.org/pub/scm/linux/kernel/git/mtd/linux Pull MTD updates from Miquel Raynal: "There has been a lot of activity in the MTD subsystem recently, with a number of SPI-NOR cleanups as well as the introduction of ECC engines that can be used by SPI controllers (hence a few SPI patches in here). Core MTD changes: - Replace the expert mode symbols with a single helper - Fix misuses of of_match_ptr() - Remove partid and partname debugfs files - tests: Fix eraseblock read speed miscalculation for lower partition sizes - TRX parser: Allow to use on MediaTek MIPS SoCs MTD driver changes: - spear_smi: use GFP_KERNEL - mchp48l640: Add SPI ID table - mchp23k256: Add SPI ID table - blkdevs: Avoid soft lockups with some mtd/spi devices - aspeed-smc: Improve probe resilience Hyperbus changes: - HBMC_AM654 should depend on ARCH_K3 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 - Make two read-only arrays static const - Fix compile test issue Raw NAND core changes: - Fix misuses of of_match_node() - Rework of_get_nand_bus_width() - Remove of_get_nand_on_flash_bbt() wrapper - Protect access to rawnand devices while in suspend - bindings: Document the wp-gpios property Rax NAND controller driver changes: - atmel: Fix refcount issue in atmel_nand_controller_init - nandsim: - Add NS_PAGE_BYTE_SHIFT macro to replace the repeat pattern - Merge repeat codes in ns_switch_state - Replace overflow check with kzalloc to single kcalloc - rockchip: Fix platform_get_irq.cocci warning - stm32_fmc2: Add NAND Write Protect support - pl353: Set the nand chip node as the flash node - brcmnand: Fix sparse warnings in bcma_nand - omap_elm: Remove redundant variable 'errors' - gpmi: - Support fast edo timings for mx28 - Validate controller clock rate - Fix controller timings setting - brcmnand: - Add BCMA shim - BCMA controller uses command shift of 0 - Allow platform data instantation - Add platform data structure for BCMA - Allow working without interrupts - Move OF operations out of brcmnand_init_cs() - Avoid pdev in brcmnand_init_cs() - Allow SoC to provide I/O operations - Assign soc as early as possible Onenand changes: - Check for error irq 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 NOR core changes: - Move vendor specific code out of the core into vendor drivers. - Unify all function and object names in the vendor modules. - Make setup() callback optional to improve readability. - Skip erase logic when the SPI_NOR_NO_ERASE flag is set at flash declaration. 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 Binding changes: - Dropped mtd/cortina,gemini-flash.txt - Convert BCM47xx partitions to json-schema - 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" * tag 'mtd/changes-for-5.18' of git://git.kernel.org/pub/scm/linux/kernel/git/mtd/linux: (104 commits) mtd: nand: ecc: mxic: Fix compile test issue mtd: nand: mxic-ecc: make two read-only arrays static const mtd: hyperbus: HBMC_AM654 should depend on ARCH_K3 mtd: core: Remove partid and partname debugfs files dt-bindings: mtd: partitions: convert BCM47xx to the json-schema mtd: tests: Fix eraseblock read speed miscalculation for lower partition sizes mtd: rawnand: atmel: fix refcount issue in atmel_nand_controller_init mtd: rawnand: rockchip: fix platform_get_irq.cocci warning mtd: spi-nor: Skip erase logic when SPI_NOR_NO_ERASE is set mtd: spi-nor: renumber flags mtd: spi-nor: slightly change code style in spi_nor_sr_ready() mtd: spi-nor: spansion: rename vendor specific functions and defines mtd: spi-nor: spansion: convert USE_CLSR to a manufacturer flag mtd: spi-nor: move all spansion specifics into spansion.c mtd: spi-nor: spansion: slightly rework control flow in late_init() mtd: spi-nor: micron-st: rename vendor specific functions and defines mtd: spi-nor: micron-st: convert USE_FSR to a manufacturer flag mtd: spi-nor: move all micron-st specifics into micron-st.c mtd: spi-nor: xilinx: correct the debug message mtd: spi-nor: xilinx: rename vendor specific functions and defines ...
This commit is contained in:
commit
e35a4a4e13
@ -1,24 +0,0 @@
|
||||
Flash device on Cortina Systems Gemini SoC
|
||||
|
||||
This flash is regular CFI compatible (Intel or AMD extended) flash chips with
|
||||
some special bits that can be controlled by the machine's system controller.
|
||||
|
||||
Required properties:
|
||||
- compatible : must be "cortina,gemini-flash", "cfi-flash";
|
||||
- reg : memory address for the flash chip
|
||||
- syscon : must be a phandle to the system controller
|
||||
- bank-width : width in bytes of flash interface, should be <2>
|
||||
|
||||
For the rest of the properties, see mtd-physmap.yaml.
|
||||
|
||||
The device tree may optionally contain sub-nodes describing partitions of the
|
||||
address space. See partition.txt for more detail.
|
||||
|
||||
Example:
|
||||
|
||||
flash@30000000 {
|
||||
compatible = "cortina,gemini-flash", "cfi-flash";
|
||||
reg = <0x30000000 0x01000000>;
|
||||
syscon = <&syscon>;
|
||||
bank-width = <2>;
|
||||
};
|
@ -44,7 +44,9 @@ properties:
|
||||
- numonyx,js28f128
|
||||
- sst,sst39vf320
|
||||
- xlnx,xps-mch-emc-2.00.a
|
||||
- const: cfi-flash
|
||||
- enum:
|
||||
- cfi-flash
|
||||
- jedec-flash
|
||||
- items:
|
||||
- enum:
|
||||
- cypress,cy7c1019dv33-10zsxi
|
||||
@ -127,6 +129,20 @@ required:
|
||||
- compatible
|
||||
- reg
|
||||
|
||||
if:
|
||||
properties:
|
||||
compatible:
|
||||
contains:
|
||||
const: cortina,gemini-flash
|
||||
then:
|
||||
properties:
|
||||
syscon:
|
||||
$ref: /schemas/types.yaml#/definitions/phandle
|
||||
description:
|
||||
Phandle to the syscon controller
|
||||
required:
|
||||
- syscon
|
||||
|
||||
# FIXME: A parent bus may define timing properties
|
||||
additionalProperties: true
|
||||
|
||||
|
@ -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>;
|
||||
};
|
70
Documentation/devicetree/bindings/mtd/nand-chip.yaml
Normal file
70
Documentation/devicetree/bindings/mtd/nand-chip.yaml
Normal file
@ -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,12 +116,12 @@ 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
|
||||
wp-gpios:
|
||||
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.
|
||||
Contains one GPIO descriptor for the Write Protect pin.
|
||||
Active state refers to the NAND Write Protect state and should be
|
||||
set to GPIOD_ACTIVE_LOW unless the signal is inverted.
|
||||
maxItems: 1
|
||||
|
||||
required:
|
||||
- reg
|
||||
@ -181,10 +143,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,42 +0,0 @@
|
||||
Broadcom BCM47xx Partitions
|
||||
===========================
|
||||
|
||||
Broadcom is one of hardware manufacturers providing SoCs (BCM47xx) used in
|
||||
home routers. Their BCM947xx boards using CFE bootloader have several partitions
|
||||
without any on-flash partition table. On some devices their sizes and/or
|
||||
meanings can also vary so fixed partitioning can't be used.
|
||||
|
||||
Discovering partitions on these devices is possible thanks to having a special
|
||||
header and/or magic signature at the beginning of each of them. They are also
|
||||
block aligned which is important for determinig a size.
|
||||
|
||||
Most of partitions use ASCII text based magic for determining a type. More
|
||||
complex partitions (like TRX with its HDR0 magic) may include extra header
|
||||
containing some details, including a length.
|
||||
|
||||
A list of supported partitions includes:
|
||||
1) Bootloader with Broadcom's CFE (Common Firmware Environment)
|
||||
2) NVRAM with configuration/calibration data
|
||||
3) Device manufacturer's data with some default values (e.g. SSIDs)
|
||||
4) TRX firmware container which can hold up to 4 subpartitions
|
||||
5) Backup TRX firmware used after failed upgrade
|
||||
|
||||
As mentioned earlier, role of some partitions may depend on extra configuration.
|
||||
For example both: main firmware and backup firmware use the same TRX format with
|
||||
the same header. To distinguish currently used firmware a CFE's environment
|
||||
variable "bootpartition" is used.
|
||||
|
||||
|
||||
Devices using Broadcom partitions described above should should have flash node
|
||||
with a subnode named "partitions" using following properties:
|
||||
|
||||
Required properties:
|
||||
- compatible : (required) must be "brcm,bcm947xx-cfe-partitions"
|
||||
|
||||
Example:
|
||||
|
||||
flash@0 {
|
||||
partitions {
|
||||
compatible = "brcm,bcm947xx-cfe-partitions";
|
||||
};
|
||||
};
|
@ -0,0 +1,48 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
|
||||
%YAML 1.2
|
||||
---
|
||||
$id: http://devicetree.org/schemas/mtd/partitions/brcm,bcm947xx-cfe-partitions.yaml#
|
||||
$schema: http://devicetree.org/meta-schemas/core.yaml#
|
||||
|
||||
title: Broadcom BCM47xx Partitions
|
||||
|
||||
description: |
|
||||
Broadcom is one of hardware manufacturers providing SoCs (BCM47xx) used in
|
||||
home routers. Their BCM947xx boards using CFE bootloader have several
|
||||
partitions without any on-flash partition table. On some devices their sizes
|
||||
and/or meanings can also vary so fixed partitioning can't be used.
|
||||
|
||||
Discovering partitions on these devices is possible thanks to having a special
|
||||
header and/or magic signature at the beginning of each of them. They are also
|
||||
block aligned which is important for determinig a size.
|
||||
|
||||
Most of partitions use ASCII text based magic for determining a type. More
|
||||
complex partitions (like TRX with its HDR0 magic) may include extra header
|
||||
containing some details, including a length.
|
||||
|
||||
A list of supported partitions includes:
|
||||
1) Bootloader with Broadcom's CFE (Common Firmware Environment)
|
||||
2) NVRAM with configuration/calibration data
|
||||
3) Device manufacturer's data with some default values (e.g. SSIDs)
|
||||
4) TRX firmware container which can hold up to 4 subpartitions
|
||||
5) Backup TRX firmware used after failed upgrade
|
||||
|
||||
As mentioned earlier, role of some partitions may depend on extra
|
||||
configuration. For example both: main firmware and backup firmware use the
|
||||
same TRX format with the same header. To distinguish currently used firmware a
|
||||
CFE's environment variable "bootpartition" is used.
|
||||
|
||||
maintainers:
|
||||
- Rafał Miłecki <rafal@milecki.pl>
|
||||
|
||||
properties:
|
||||
compatible:
|
||||
const: brcm,bcm947xx-cfe-partitions
|
||||
|
||||
additionalProperties: false
|
||||
|
||||
examples:
|
||||
- |
|
||||
partitions {
|
||||
compatible = "brcm,bcm947xx-cfe-partitions";
|
||||
};
|
@ -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
|
27
Documentation/devicetree/bindings/mtd/spi-nand.yaml
Normal file
27
Documentation/devicetree/bindings/mtd/spi-nand.yaml
Normal file
@ -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
|
65
Documentation/devicetree/bindings/spi/mxicy,mx25f0a-spi.yaml
Normal file
65
Documentation/devicetree/bindings/spi/mxicy,mx25f0a-spi.yaml
Normal file
@ -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>;
|
||||
};
|
||||
};
|
@ -816,6 +816,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,.*":
|
||||
|
@ -4046,6 +4046,7 @@ L: linux-mtd@lists.infradead.org
|
||||
L: bcm-kernel-feedback-list@broadcom.com
|
||||
S: Maintained
|
||||
F: drivers/mtd/nand/raw/brcmnand/
|
||||
F: include/linux/platform_data/brcmnand.h
|
||||
|
||||
BROADCOM STB PCIE DRIVER
|
||||
M: Jim Quinlan <jim2101024@gmail.com>
|
||||
|
@ -7,18 +7,28 @@
|
||||
|
||||
#include "bcma_private.h"
|
||||
|
||||
#include <linux/bitops.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/platform_data/brcmnand.h>
|
||||
#include <linux/bcma/bcma.h>
|
||||
|
||||
/* Alternate NAND controller driver name in order to allow both bcm47xxnflash
|
||||
* and bcma_brcmnand to be built into the same kernel image.
|
||||
*/
|
||||
static const char *bcma_nflash_alt_name = "bcma_brcmnand";
|
||||
|
||||
struct platform_device bcma_nflash_dev = {
|
||||
.name = "bcma_nflash",
|
||||
.num_resources = 0,
|
||||
};
|
||||
|
||||
static const char *probes[] = { "bcm47xxpart", NULL };
|
||||
|
||||
/* Initialize NAND flash access */
|
||||
int bcma_nflash_init(struct bcma_drv_cc *cc)
|
||||
{
|
||||
struct bcma_bus *bus = cc->core->bus;
|
||||
u32 reg;
|
||||
|
||||
if (bus->chipinfo.id != BCMA_CHIP_ID_BCM4706 &&
|
||||
cc->core->id.rev != 38) {
|
||||
@ -33,8 +43,16 @@ int bcma_nflash_init(struct bcma_drv_cc *cc)
|
||||
|
||||
cc->nflash.present = true;
|
||||
if (cc->core->id.rev == 38 &&
|
||||
(cc->status & BCMA_CC_CHIPST_5357_NAND_BOOT))
|
||||
(cc->status & BCMA_CC_CHIPST_5357_NAND_BOOT)) {
|
||||
cc->nflash.boot = true;
|
||||
/* Determine the chip select that is being used */
|
||||
reg = bcma_cc_read32(cc, BCMA_CC_NAND_CS_NAND_SELECT) & 0xff;
|
||||
cc->nflash.brcmnand_info.chip_select = ffs(reg) - 1;
|
||||
cc->nflash.brcmnand_info.part_probe_types = probes;
|
||||
cc->nflash.brcmnand_info.ecc_stepsize = 512;
|
||||
cc->nflash.brcmnand_info.ecc_strength = 1;
|
||||
bcma_nflash_dev.name = bcma_nflash_alt_name;
|
||||
}
|
||||
|
||||
/* Prepare platform device, but don't register it yet. It's too early,
|
||||
* malloc (required by device_private_init) is not available yet. */
|
||||
|
@ -229,13 +229,27 @@ static const struct of_device_id mchp23k256_of_table[] = {
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, mchp23k256_of_table);
|
||||
|
||||
static const struct spi_device_id mchp23k256_spi_ids[] = {
|
||||
{
|
||||
.name = "mchp23k256",
|
||||
.driver_data = (kernel_ulong_t)&mchp23k256_caps,
|
||||
},
|
||||
{
|
||||
.name = "mchp23lcv1024",
|
||||
.driver_data = (kernel_ulong_t)&mchp23lcv1024_caps,
|
||||
},
|
||||
{}
|
||||
};
|
||||
MODULE_DEVICE_TABLE(spi, mchp23k256_spi_ids);
|
||||
|
||||
static struct spi_driver mchp23k256_driver = {
|
||||
.driver = {
|
||||
.name = "mchp23k256",
|
||||
.of_match_table = of_match_ptr(mchp23k256_of_table),
|
||||
.of_match_table = mchp23k256_of_table,
|
||||
},
|
||||
.probe = mchp23k256_probe,
|
||||
.remove = mchp23k256_remove,
|
||||
.id_table = mchp23k256_spi_ids,
|
||||
};
|
||||
|
||||
module_spi_driver(mchp23k256_driver);
|
||||
|
@ -357,13 +357,23 @@ static const struct of_device_id mchp48l640_of_table[] = {
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, mchp48l640_of_table);
|
||||
|
||||
static const struct spi_device_id mchp48l640_spi_ids[] = {
|
||||
{
|
||||
.name = "48l640",
|
||||
.driver_data = (kernel_ulong_t)&mchp48l640_caps,
|
||||
},
|
||||
{}
|
||||
};
|
||||
MODULE_DEVICE_TABLE(spi, mchp48l640_spi_ids);
|
||||
|
||||
static struct spi_driver mchp48l640_driver = {
|
||||
.driver = {
|
||||
.name = "mchp48l640",
|
||||
.of_match_table = of_match_ptr(mchp48l640_of_table),
|
||||
.of_match_table = mchp48l640_of_table,
|
||||
},
|
||||
.probe = mchp48l640_probe,
|
||||
.remove = mchp48l640_remove,
|
||||
.id_table = mchp48l640_spi_ids,
|
||||
};
|
||||
|
||||
module_spi_driver(mchp48l640_driver);
|
||||
|
@ -969,7 +969,7 @@ static int spear_smi_probe(struct platform_device *pdev)
|
||||
goto err;
|
||||
}
|
||||
|
||||
dev = devm_kzalloc(&pdev->dev, sizeof(*dev), GFP_ATOMIC);
|
||||
dev = devm_kzalloc(&pdev->dev, sizeof(*dev), GFP_KERNEL);
|
||||
if (!dev) {
|
||||
ret = -ENOMEM;
|
||||
goto err;
|
||||
|
@ -15,7 +15,7 @@ if MTD_HYPERBUS
|
||||
|
||||
config HBMC_AM654
|
||||
tristate "HyperBus controller driver for AM65x SoC"
|
||||
depends on ARM64 || COMPILE_TEST
|
||||
depends on ARCH_K3 || COMPILE_TEST
|
||||
select MULTIPLEXER
|
||||
imply MUX_MMIO
|
||||
help
|
||||
|
@ -158,6 +158,7 @@ static void mtd_blktrans_work(struct mtd_blktrans_dev *dev)
|
||||
}
|
||||
|
||||
background_done = 0;
|
||||
cond_resched();
|
||||
spin_lock_irq(&dev->queue_lock);
|
||||
}
|
||||
}
|
||||
|
@ -336,49 +336,31 @@ static const struct device_type mtd_devtype = {
|
||||
.release = mtd_release,
|
||||
};
|
||||
|
||||
static int mtd_partid_debug_show(struct seq_file *s, void *p)
|
||||
static bool mtd_expert_analysis_mode;
|
||||
|
||||
#ifdef CONFIG_DEBUG_FS
|
||||
bool mtd_check_expert_analysis_mode(void)
|
||||
{
|
||||
struct mtd_info *mtd = s->private;
|
||||
const char *mtd_expert_analysis_warning =
|
||||
"Bad block checks have been entirely disabled.\n"
|
||||
"This is only reserved for post-mortem forensics and debug purposes.\n"
|
||||
"Never enable this mode if you do not know what you are doing!\n";
|
||||
|
||||
seq_printf(s, "%s\n", mtd->dbg.partid);
|
||||
|
||||
return 0;
|
||||
return WARN_ONCE(mtd_expert_analysis_mode, mtd_expert_analysis_warning);
|
||||
}
|
||||
|
||||
DEFINE_SHOW_ATTRIBUTE(mtd_partid_debug);
|
||||
|
||||
static int mtd_partname_debug_show(struct seq_file *s, void *p)
|
||||
{
|
||||
struct mtd_info *mtd = s->private;
|
||||
|
||||
seq_printf(s, "%s\n", mtd->dbg.partname);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
DEFINE_SHOW_ATTRIBUTE(mtd_partname_debug);
|
||||
EXPORT_SYMBOL_GPL(mtd_check_expert_analysis_mode);
|
||||
#endif
|
||||
|
||||
static struct dentry *dfs_dir_mtd;
|
||||
|
||||
static void mtd_debugfs_populate(struct mtd_info *mtd)
|
||||
{
|
||||
struct mtd_info *master = mtd_get_master(mtd);
|
||||
struct device *dev = &mtd->dev;
|
||||
struct dentry *root;
|
||||
|
||||
if (IS_ERR_OR_NULL(dfs_dir_mtd))
|
||||
return;
|
||||
|
||||
root = debugfs_create_dir(dev_name(dev), dfs_dir_mtd);
|
||||
mtd->dbg.dfs_dir = root;
|
||||
|
||||
if (master->dbg.partid)
|
||||
debugfs_create_file("partid", 0400, root, master,
|
||||
&mtd_partid_debug_fops);
|
||||
|
||||
if (master->dbg.partname)
|
||||
debugfs_create_file("partname", 0400, root, master,
|
||||
&mtd_partname_debug_fops);
|
||||
mtd->dbg.dfs_dir = debugfs_create_dir(dev_name(dev), dfs_dir_mtd);
|
||||
}
|
||||
|
||||
#ifndef CONFIG_MMU
|
||||
@ -2372,14 +2354,6 @@ static struct backing_dev_info * __init mtd_bdi_init(const char *name)
|
||||
return ret ? ERR_PTR(ret) : bdi;
|
||||
}
|
||||
|
||||
char *mtd_expert_analysis_warning =
|
||||
"Bad block checks have been entirely disabled.\n"
|
||||
"This is only reserved for post-mortem forensics and debug purposes.\n"
|
||||
"Never enable this mode if you do not know what you are doing!\n";
|
||||
EXPORT_SYMBOL_GPL(mtd_expert_analysis_warning);
|
||||
bool mtd_expert_analysis_mode;
|
||||
EXPORT_SYMBOL_GPL(mtd_expert_analysis_mode);
|
||||
|
||||
static struct proc_dir_entry *proc_mtd;
|
||||
|
||||
static int __init init_mtd(void)
|
||||
|
@ -46,6 +46,13 @@ 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"
|
||||
depends on HAS_IOMEM
|
||||
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
|
||||
|
@ -21,7 +21,7 @@
|
||||
*/
|
||||
bool nanddev_isbad(struct nand_device *nand, const struct nand_pos *pos)
|
||||
{
|
||||
if (WARN_ONCE(mtd_expert_analysis_mode, mtd_expert_analysis_warning))
|
||||
if (mtd_check_expert_analysis_mode())
|
||||
return false;
|
||||
|
||||
if (nanddev_bbt_is_initialized(nand)) {
|
||||
@ -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;
|
||||
}
|
||||
|
||||
|
879
drivers/mtd/nand/ecc-mxic.c
Normal file
879
drivers/mtd/nand/ecc-mxic.c
Normal file
@ -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;
|
||||
static const int possible_strength[] = {4, 8, 40, 48};
|
||||
static const 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");
|
||||
|
@ -53,7 +53,12 @@ static int generic_onenand_probe(struct platform_device *pdev)
|
||||
}
|
||||
|
||||
info->onenand.mmcontrol = pdata ? pdata->mmcontrol : NULL;
|
||||
info->onenand.irq = platform_get_irq(pdev, 0);
|
||||
|
||||
err = platform_get_irq(pdev, 0);
|
||||
if (err < 0)
|
||||
goto out_iounmap;
|
||||
|
||||
info->onenand.irq = err;
|
||||
|
||||
info->mtd.dev.parent = &pdev->dev;
|
||||
info->mtd.priv = &info->onenand;
|
||||
|
@ -209,6 +209,19 @@ config MTD_NAND_BRCMNAND
|
||||
originally designed for Set-Top Box but is used on various BCM7xxx,
|
||||
BCM3xxx, BCM63xxx, iProc/Cygnus and more.
|
||||
|
||||
if MTD_NAND_BRCMNAND
|
||||
|
||||
config MTD_NAND_BRCMNAND_BCMA
|
||||
tristate "Broadcom BCMA NAND controller"
|
||||
depends on BCMA_NFLASH
|
||||
depends on BCMA
|
||||
help
|
||||
Enables the BRCMNAND controller over BCMA on BCM47186/BCM5358 SoCs.
|
||||
The glue driver will take care of performing the low-level I/O
|
||||
operations to interface the BRCMNAND controller over the BCMA bus.
|
||||
|
||||
endif # MTD_NAND_BRCMNAND
|
||||
|
||||
config MTD_NAND_BCM47XXNFLASH
|
||||
tristate "BCM4706 BCMA NAND controller"
|
||||
depends on BCMA_NFLASH
|
||||
|
@ -1938,7 +1938,7 @@ static const struct atmel_smc_nand_ebi_csa_cfg sam9x60_ebi_csa = {
|
||||
.nfd0_on_d16 = AT91_SFR_CCFG_NFD0_ON_D16,
|
||||
};
|
||||
|
||||
static const struct of_device_id atmel_ebi_csa_regmap_of_ids[] = {
|
||||
static const struct of_device_id __maybe_unused atmel_ebi_csa_regmap_of_ids[] = {
|
||||
{
|
||||
.compatible = "atmel,at91sam9260-matrix",
|
||||
.data = &at91sam9260_ebi_csa,
|
||||
@ -2060,13 +2060,15 @@ static int atmel_nand_controller_init(struct atmel_nand_controller *nc,
|
||||
nc->mck = of_clk_get(dev->parent->of_node, 0);
|
||||
if (IS_ERR(nc->mck)) {
|
||||
dev_err(dev, "Failed to retrieve MCK clk\n");
|
||||
return PTR_ERR(nc->mck);
|
||||
ret = PTR_ERR(nc->mck);
|
||||
goto out_release_dma;
|
||||
}
|
||||
|
||||
np = of_parse_phandle(dev->parent->of_node, "atmel,smc", 0);
|
||||
if (!np) {
|
||||
dev_err(dev, "Missing or invalid atmel,smc property\n");
|
||||
return -EINVAL;
|
||||
ret = -EINVAL;
|
||||
goto out_release_dma;
|
||||
}
|
||||
|
||||
nc->smc = syscon_node_to_regmap(np);
|
||||
@ -2074,10 +2076,16 @@ static int atmel_nand_controller_init(struct atmel_nand_controller *nc,
|
||||
if (IS_ERR(nc->smc)) {
|
||||
ret = PTR_ERR(nc->smc);
|
||||
dev_err(dev, "Could not get SMC regmap (err = %d)\n", ret);
|
||||
return ret;
|
||||
goto out_release_dma;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
out_release_dma:
|
||||
if (nc->dmac)
|
||||
dma_release_channel(nc->dmac);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int
|
||||
@ -2648,7 +2656,7 @@ static SIMPLE_DEV_PM_OPS(atmel_nand_controller_pm_ops, NULL,
|
||||
static struct platform_driver atmel_nand_controller_driver = {
|
||||
.driver = {
|
||||
.name = "atmel-nand-controller",
|
||||
.of_match_table = of_match_ptr(atmel_nand_controller_of_ids),
|
||||
.of_match_table = atmel_nand_controller_of_ids,
|
||||
.pm = &atmel_nand_controller_pm_ops,
|
||||
},
|
||||
.probe = atmel_nand_controller_probe,
|
||||
|
@ -920,7 +920,7 @@ static struct atmel_pmecc_caps sama5d2_caps = {
|
||||
.correct_erased_chunks = true,
|
||||
};
|
||||
|
||||
static const struct of_device_id atmel_pmecc_legacy_match[] = {
|
||||
static const struct of_device_id __maybe_unused atmel_pmecc_legacy_match[] = {
|
||||
{ .compatible = "atmel,sama5d4-nand", &sama5d4_caps },
|
||||
{ .compatible = "atmel,sama5d2-nand", &sama5d2_caps },
|
||||
{ /* sentinel */ }
|
||||
@ -1003,7 +1003,7 @@ static int atmel_pmecc_probe(struct platform_device *pdev)
|
||||
static struct platform_driver atmel_pmecc_driver = {
|
||||
.driver = {
|
||||
.name = "atmel-pmecc",
|
||||
.of_match_table = of_match_ptr(atmel_pmecc_match),
|
||||
.of_match_table = atmel_pmecc_match,
|
||||
},
|
||||
.probe = atmel_pmecc_probe,
|
||||
};
|
||||
|
@ -6,3 +6,5 @@ obj-$(CONFIG_MTD_NAND_BRCMNAND) += bcm63138_nand.o
|
||||
obj-$(CONFIG_MTD_NAND_BRCMNAND) += bcm6368_nand.o
|
||||
obj-$(CONFIG_MTD_NAND_BRCMNAND) += brcmstb_nand.o
|
||||
obj-$(CONFIG_MTD_NAND_BRCMNAND) += brcmnand.o
|
||||
|
||||
obj-$(CONFIG_MTD_NAND_BRCMNAND_BCMA) += bcma_nand.o
|
||||
|
132
drivers/mtd/nand/raw/brcmnand/bcma_nand.c
Normal file
132
drivers/mtd/nand/raw/brcmnand/bcma_nand.c
Normal file
@ -0,0 +1,132 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* Copyright © 2021 Broadcom
|
||||
*/
|
||||
#include <linux/bcma/bcma.h>
|
||||
#include <linux/bcma/bcma_driver_chipcommon.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/platform_device.h>
|
||||
|
||||
#include "brcmnand.h"
|
||||
|
||||
struct brcmnand_bcma_soc {
|
||||
struct brcmnand_soc soc;
|
||||
struct bcma_drv_cc *cc;
|
||||
};
|
||||
|
||||
static inline bool brcmnand_bcma_needs_swapping(u32 offset)
|
||||
{
|
||||
switch (offset) {
|
||||
case BCMA_CC_NAND_SPARE_RD0:
|
||||
case BCMA_CC_NAND_SPARE_RD4:
|
||||
case BCMA_CC_NAND_SPARE_RD8:
|
||||
case BCMA_CC_NAND_SPARE_RD12:
|
||||
case BCMA_CC_NAND_SPARE_WR0:
|
||||
case BCMA_CC_NAND_SPARE_WR4:
|
||||
case BCMA_CC_NAND_SPARE_WR8:
|
||||
case BCMA_CC_NAND_SPARE_WR12:
|
||||
case BCMA_CC_NAND_DEVID:
|
||||
case BCMA_CC_NAND_DEVID_X:
|
||||
case BCMA_CC_NAND_SPARE_RD16:
|
||||
case BCMA_CC_NAND_SPARE_RD20:
|
||||
case BCMA_CC_NAND_SPARE_RD24:
|
||||
case BCMA_CC_NAND_SPARE_RD28:
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static inline struct brcmnand_bcma_soc *to_bcma_soc(struct brcmnand_soc *soc)
|
||||
{
|
||||
return container_of(soc, struct brcmnand_bcma_soc, soc);
|
||||
}
|
||||
|
||||
static u32 brcmnand_bcma_read_reg(struct brcmnand_soc *soc, u32 offset)
|
||||
{
|
||||
struct brcmnand_bcma_soc *sc = to_bcma_soc(soc);
|
||||
u32 val;
|
||||
|
||||
/* Offset into the NAND block and deal with the flash cache separately */
|
||||
if (offset == BRCMNAND_NON_MMIO_FC_ADDR)
|
||||
offset = BCMA_CC_NAND_CACHE_DATA;
|
||||
else
|
||||
offset += BCMA_CC_NAND_REVISION;
|
||||
|
||||
val = bcma_cc_read32(sc->cc, offset);
|
||||
|
||||
/* Swap if necessary */
|
||||
if (brcmnand_bcma_needs_swapping(offset))
|
||||
val = be32_to_cpu((__force __be32)val);
|
||||
return val;
|
||||
}
|
||||
|
||||
static void brcmnand_bcma_write_reg(struct brcmnand_soc *soc, u32 val,
|
||||
u32 offset)
|
||||
{
|
||||
struct brcmnand_bcma_soc *sc = to_bcma_soc(soc);
|
||||
|
||||
/* Offset into the NAND block */
|
||||
if (offset == BRCMNAND_NON_MMIO_FC_ADDR)
|
||||
offset = BCMA_CC_NAND_CACHE_DATA;
|
||||
else
|
||||
offset += BCMA_CC_NAND_REVISION;
|
||||
|
||||
/* Swap if necessary */
|
||||
if (brcmnand_bcma_needs_swapping(offset))
|
||||
val = (__force u32)cpu_to_be32(val);
|
||||
|
||||
bcma_cc_write32(sc->cc, offset, val);
|
||||
}
|
||||
|
||||
static struct brcmnand_io_ops brcmnand_bcma_io_ops = {
|
||||
.read_reg = brcmnand_bcma_read_reg,
|
||||
.write_reg = brcmnand_bcma_write_reg,
|
||||
};
|
||||
|
||||
static void brcmnand_bcma_prepare_data_bus(struct brcmnand_soc *soc, bool prepare,
|
||||
bool is_param)
|
||||
{
|
||||
struct brcmnand_bcma_soc *sc = to_bcma_soc(soc);
|
||||
|
||||
/* Reset the cache address to ensure we are already accessing the
|
||||
* beginning of a sub-page.
|
||||
*/
|
||||
bcma_cc_write32(sc->cc, BCMA_CC_NAND_CACHE_ADDR, 0);
|
||||
}
|
||||
|
||||
static int brcmnand_bcma_nand_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct bcma_nflash *nflash = dev_get_platdata(&pdev->dev);
|
||||
struct brcmnand_bcma_soc *soc;
|
||||
|
||||
soc = devm_kzalloc(&pdev->dev, sizeof(*soc), GFP_KERNEL);
|
||||
if (!soc)
|
||||
return -ENOMEM;
|
||||
|
||||
soc->cc = container_of(nflash, struct bcma_drv_cc, nflash);
|
||||
soc->soc.prepare_data_bus = brcmnand_bcma_prepare_data_bus;
|
||||
soc->soc.ops = &brcmnand_bcma_io_ops;
|
||||
|
||||
if (soc->cc->core->bus->chipinfo.id == BCMA_CHIP_ID_BCM4706) {
|
||||
dev_err(&pdev->dev, "Use bcm47xxnflash for 4706!\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
return brcmnand_probe(pdev, &soc->soc);
|
||||
}
|
||||
|
||||
static struct platform_driver brcmnand_bcma_nand_driver = {
|
||||
.probe = brcmnand_bcma_nand_probe,
|
||||
.remove = brcmnand_remove,
|
||||
.driver = {
|
||||
.name = "bcma_brcmnand",
|
||||
.pm = &brcmnand_pm_ops,
|
||||
}
|
||||
};
|
||||
module_platform_driver(brcmnand_bcma_nand_driver);
|
||||
|
||||
MODULE_LICENSE("GPL v2");
|
||||
MODULE_AUTHOR("Broadcom");
|
||||
MODULE_DESCRIPTION("NAND controller driver glue for BCMA chips");
|
@ -9,6 +9,7 @@
|
||||
#include <linux/delay.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/platform_data/brcmnand.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/completion.h>
|
||||
#include <linux/interrupt.h>
|
||||
@ -25,6 +26,7 @@
|
||||
#include <linux/of.h>
|
||||
#include <linux/of_platform.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/static_key.h>
|
||||
#include <linux/list.h>
|
||||
#include <linux/log2.h>
|
||||
|
||||
@ -207,13 +209,15 @@ enum {
|
||||
|
||||
struct brcmnand_host;
|
||||
|
||||
static DEFINE_STATIC_KEY_FALSE(brcmnand_soc_has_ops_key);
|
||||
|
||||
struct brcmnand_controller {
|
||||
struct device *dev;
|
||||
struct nand_controller controller;
|
||||
void __iomem *nand_base;
|
||||
void __iomem *nand_fc; /* flash cache */
|
||||
void __iomem *flash_dma_base;
|
||||
unsigned int irq;
|
||||
int irq;
|
||||
unsigned int dma_irq;
|
||||
int nand_version;
|
||||
|
||||
@ -592,15 +596,29 @@ enum {
|
||||
INTFC_CTLR_READY = BIT(31),
|
||||
};
|
||||
|
||||
static inline bool brcmnand_non_mmio_ops(struct brcmnand_controller *ctrl)
|
||||
{
|
||||
#if IS_ENABLED(CONFIG_MTD_NAND_BRCMNAND_BCMA)
|
||||
return static_branch_unlikely(&brcmnand_soc_has_ops_key);
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
static inline u32 nand_readreg(struct brcmnand_controller *ctrl, u32 offs)
|
||||
{
|
||||
if (brcmnand_non_mmio_ops(ctrl))
|
||||
return brcmnand_soc_read(ctrl->soc, offs);
|
||||
return brcmnand_readl(ctrl->nand_base + offs);
|
||||
}
|
||||
|
||||
static inline void nand_writereg(struct brcmnand_controller *ctrl, u32 offs,
|
||||
u32 val)
|
||||
{
|
||||
brcmnand_writel(val, ctrl->nand_base + offs);
|
||||
if (brcmnand_non_mmio_ops(ctrl))
|
||||
brcmnand_soc_write(ctrl->soc, val, offs);
|
||||
else
|
||||
brcmnand_writel(val, ctrl->nand_base + offs);
|
||||
}
|
||||
|
||||
static int brcmnand_revision_init(struct brcmnand_controller *ctrl)
|
||||
@ -766,13 +784,18 @@ static inline void brcmnand_rmw_reg(struct brcmnand_controller *ctrl,
|
||||
|
||||
static inline u32 brcmnand_read_fc(struct brcmnand_controller *ctrl, int word)
|
||||
{
|
||||
if (brcmnand_non_mmio_ops(ctrl))
|
||||
return brcmnand_soc_read(ctrl->soc, BRCMNAND_NON_MMIO_FC_ADDR);
|
||||
return __raw_readl(ctrl->nand_fc + word * 4);
|
||||
}
|
||||
|
||||
static inline void brcmnand_write_fc(struct brcmnand_controller *ctrl,
|
||||
int word, u32 val)
|
||||
{
|
||||
__raw_writel(val, ctrl->nand_fc + word * 4);
|
||||
if (brcmnand_non_mmio_ops(ctrl))
|
||||
brcmnand_soc_write(ctrl->soc, val, BRCMNAND_NON_MMIO_FC_ADDR);
|
||||
else
|
||||
__raw_writel(val, ctrl->nand_fc + word * 4);
|
||||
}
|
||||
|
||||
static inline void edu_writel(struct brcmnand_controller *ctrl,
|
||||
@ -897,6 +920,12 @@ static void brcmnand_wr_corr_thresh(struct brcmnand_host *host, u8 val)
|
||||
|
||||
static inline int brcmnand_cmd_shift(struct brcmnand_controller *ctrl)
|
||||
{
|
||||
/* Kludge for the BCMA-based NAND controller which does not actually
|
||||
* shift the command
|
||||
*/
|
||||
if (ctrl->nand_version == 0x0304 && brcmnand_non_mmio_ops(ctrl))
|
||||
return 0;
|
||||
|
||||
if (ctrl->nand_version < 0x0602)
|
||||
return 24;
|
||||
return 0;
|
||||
@ -1592,7 +1621,7 @@ static bool brcmstb_nand_wait_for_completion(struct nand_chip *chip)
|
||||
bool err = false;
|
||||
int sts;
|
||||
|
||||
if (mtd->oops_panic_write) {
|
||||
if (mtd->oops_panic_write || ctrl->irq < 0) {
|
||||
/* switch to interrupt polling and PIO mode */
|
||||
disable_ctrl_irqs(ctrl);
|
||||
sts = bcmnand_ctrl_poll_status(ctrl, NAND_CTRL_RDY,
|
||||
@ -2750,33 +2779,27 @@ static const struct nand_controller_ops brcmnand_controller_ops = {
|
||||
.attach_chip = brcmnand_attach_chip,
|
||||
};
|
||||
|
||||
static int brcmnand_init_cs(struct brcmnand_host *host, struct device_node *dn)
|
||||
static int brcmnand_init_cs(struct brcmnand_host *host,
|
||||
const char * const *part_probe_types)
|
||||
{
|
||||
struct brcmnand_controller *ctrl = host->ctrl;
|
||||
struct platform_device *pdev = host->pdev;
|
||||
struct device *dev = ctrl->dev;
|
||||
struct mtd_info *mtd;
|
||||
struct nand_chip *chip;
|
||||
int ret;
|
||||
u16 cfg_offs;
|
||||
|
||||
ret = of_property_read_u32(dn, "reg", &host->cs);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "can't get chip-select\n");
|
||||
return -ENXIO;
|
||||
}
|
||||
|
||||
mtd = nand_to_mtd(&host->chip);
|
||||
chip = &host->chip;
|
||||
|
||||
nand_set_flash_node(chip, dn);
|
||||
nand_set_controller_data(chip, host);
|
||||
mtd->name = devm_kasprintf(&pdev->dev, GFP_KERNEL, "brcmnand.%d",
|
||||
mtd->name = devm_kasprintf(dev, GFP_KERNEL, "brcmnand.%d",
|
||||
host->cs);
|
||||
if (!mtd->name)
|
||||
return -ENOMEM;
|
||||
|
||||
mtd->owner = THIS_MODULE;
|
||||
mtd->dev.parent = &pdev->dev;
|
||||
mtd->dev.parent = dev;
|
||||
|
||||
chip->legacy.cmd_ctrl = brcmnand_cmd_ctrl;
|
||||
chip->legacy.cmdfunc = brcmnand_cmdfunc;
|
||||
@ -2810,7 +2833,7 @@ static int brcmnand_init_cs(struct brcmnand_host *host, struct device_node *dn)
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = mtd_device_register(mtd, NULL, 0);
|
||||
ret = mtd_device_parse_register(mtd, part_probe_types, NULL, NULL, 0);
|
||||
if (ret)
|
||||
nand_cleanup(chip);
|
||||
|
||||
@ -2914,7 +2937,7 @@ const struct dev_pm_ops brcmnand_pm_ops = {
|
||||
};
|
||||
EXPORT_SYMBOL_GPL(brcmnand_pm_ops);
|
||||
|
||||
static const struct of_device_id brcmnand_of_match[] = {
|
||||
static const struct of_device_id __maybe_unused brcmnand_of_match[] = {
|
||||
{ .compatible = "brcm,brcmnand-v2.1" },
|
||||
{ .compatible = "brcm,brcmnand-v2.2" },
|
||||
{ .compatible = "brcm,brcmnand-v4.0" },
|
||||
@ -2979,17 +3002,15 @@ static int brcmnand_edu_setup(struct platform_device *pdev)
|
||||
|
||||
int brcmnand_probe(struct platform_device *pdev, struct brcmnand_soc *soc)
|
||||
{
|
||||
struct brcmnand_platform_data *pd = dev_get_platdata(&pdev->dev);
|
||||
struct device *dev = &pdev->dev;
|
||||
struct device_node *dn = dev->of_node, *child;
|
||||
struct brcmnand_controller *ctrl;
|
||||
struct brcmnand_host *host;
|
||||
struct resource *res;
|
||||
int ret;
|
||||
|
||||
/* We only support device-tree instantiation */
|
||||
if (!dn)
|
||||
return -ENODEV;
|
||||
|
||||
if (!of_match_node(brcmnand_of_match, dn))
|
||||
if (dn && !of_match_node(brcmnand_of_match, dn))
|
||||
return -ENODEV;
|
||||
|
||||
ctrl = devm_kzalloc(dev, sizeof(*ctrl), GFP_KERNEL);
|
||||
@ -2998,6 +3019,13 @@ int brcmnand_probe(struct platform_device *pdev, struct brcmnand_soc *soc)
|
||||
|
||||
dev_set_drvdata(dev, ctrl);
|
||||
ctrl->dev = dev;
|
||||
ctrl->soc = soc;
|
||||
|
||||
/* Enable the static key if the soc provides I/O operations indicating
|
||||
* that a non-memory mapped IO access path must be used
|
||||
*/
|
||||
if (brcmnand_soc_has_ops(ctrl->soc))
|
||||
static_branch_enable(&brcmnand_soc_has_ops_key);
|
||||
|
||||
init_completion(&ctrl->done);
|
||||
init_completion(&ctrl->dma_done);
|
||||
@ -3009,7 +3037,7 @@ int brcmnand_probe(struct platform_device *pdev, struct brcmnand_soc *soc)
|
||||
/* NAND register range */
|
||||
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
ctrl->nand_base = devm_ioremap_resource(dev, res);
|
||||
if (IS_ERR(ctrl->nand_base))
|
||||
if (IS_ERR(ctrl->nand_base) && !brcmnand_soc_has_ops(soc))
|
||||
return PTR_ERR(ctrl->nand_base);
|
||||
|
||||
/* Enable clock before using NAND registers */
|
||||
@ -3126,40 +3154,33 @@ int brcmnand_probe(struct platform_device *pdev, struct brcmnand_soc *soc)
|
||||
}
|
||||
|
||||
/* IRQ */
|
||||
ctrl->irq = platform_get_irq(pdev, 0);
|
||||
if ((int)ctrl->irq < 0) {
|
||||
dev_err(dev, "no IRQ defined\n");
|
||||
ret = -ENODEV;
|
||||
goto err;
|
||||
}
|
||||
ctrl->irq = platform_get_irq_optional(pdev, 0);
|
||||
if (ctrl->irq > 0) {
|
||||
/*
|
||||
* Some SoCs integrate this controller (e.g., its interrupt bits) in
|
||||
* interesting ways
|
||||
*/
|
||||
if (soc) {
|
||||
ret = devm_request_irq(dev, ctrl->irq, brcmnand_irq, 0,
|
||||
DRV_NAME, ctrl);
|
||||
|
||||
/*
|
||||
* Some SoCs integrate this controller (e.g., its interrupt bits) in
|
||||
* interesting ways
|
||||
*/
|
||||
if (soc) {
|
||||
ctrl->soc = soc;
|
||||
|
||||
ret = devm_request_irq(dev, ctrl->irq, brcmnand_irq, 0,
|
||||
DRV_NAME, ctrl);
|
||||
|
||||
/* Enable interrupt */
|
||||
ctrl->soc->ctlrdy_ack(ctrl->soc);
|
||||
ctrl->soc->ctlrdy_set_enabled(ctrl->soc, true);
|
||||
} else {
|
||||
/* Use standard interrupt infrastructure */
|
||||
ret = devm_request_irq(dev, ctrl->irq, brcmnand_ctlrdy_irq, 0,
|
||||
DRV_NAME, ctrl);
|
||||
}
|
||||
if (ret < 0) {
|
||||
dev_err(dev, "can't allocate IRQ %d: error %d\n",
|
||||
ctrl->irq, ret);
|
||||
goto err;
|
||||
/* Enable interrupt */
|
||||
ctrl->soc->ctlrdy_ack(ctrl->soc);
|
||||
ctrl->soc->ctlrdy_set_enabled(ctrl->soc, true);
|
||||
} else {
|
||||
/* Use standard interrupt infrastructure */
|
||||
ret = devm_request_irq(dev, ctrl->irq, brcmnand_ctlrdy_irq, 0,
|
||||
DRV_NAME, ctrl);
|
||||
}
|
||||
if (ret < 0) {
|
||||
dev_err(dev, "can't allocate IRQ %d: error %d\n",
|
||||
ctrl->irq, ret);
|
||||
goto err;
|
||||
}
|
||||
}
|
||||
|
||||
for_each_available_child_of_node(dn, child) {
|
||||
if (of_device_is_compatible(child, "brcm,nandcs")) {
|
||||
struct brcmnand_host *host;
|
||||
|
||||
host = devm_kzalloc(dev, sizeof(*host), GFP_KERNEL);
|
||||
if (!host) {
|
||||
@ -3170,7 +3191,16 @@ int brcmnand_probe(struct platform_device *pdev, struct brcmnand_soc *soc)
|
||||
host->pdev = pdev;
|
||||
host->ctrl = ctrl;
|
||||
|
||||
ret = brcmnand_init_cs(host, child);
|
||||
ret = of_property_read_u32(child, "reg", &host->cs);
|
||||
if (ret) {
|
||||
dev_err(dev, "can't get chip-select\n");
|
||||
devm_kfree(dev, host);
|
||||
continue;
|
||||
}
|
||||
|
||||
nand_set_flash_node(&host->chip, child);
|
||||
|
||||
ret = brcmnand_init_cs(host, NULL);
|
||||
if (ret) {
|
||||
devm_kfree(dev, host);
|
||||
continue; /* Try all chip-selects */
|
||||
@ -3180,6 +3210,32 @@ int brcmnand_probe(struct platform_device *pdev, struct brcmnand_soc *soc)
|
||||
}
|
||||
}
|
||||
|
||||
if (!list_empty(&ctrl->host_list))
|
||||
return 0;
|
||||
|
||||
if (!pd) {
|
||||
ret = -ENODEV;
|
||||
goto err;
|
||||
}
|
||||
|
||||
/* If we got there we must have been probing via platform data */
|
||||
host = devm_kzalloc(dev, sizeof(*host), GFP_KERNEL);
|
||||
if (!host) {
|
||||
ret = -ENOMEM;
|
||||
goto err;
|
||||
}
|
||||
host->pdev = pdev;
|
||||
host->ctrl = ctrl;
|
||||
host->cs = pd->chip_select;
|
||||
host->chip.ecc.size = pd->ecc_stepsize;
|
||||
host->chip.ecc.strength = pd->ecc_strength;
|
||||
|
||||
ret = brcmnand_init_cs(host, pd->part_probe_types);
|
||||
if (ret)
|
||||
goto err;
|
||||
|
||||
list_add_tail(&host->node, &ctrl->host_list);
|
||||
|
||||
/* No chip-selects could initialize properly */
|
||||
if (list_empty(&ctrl->host_list)) {
|
||||
ret = -ENODEV;
|
||||
|
@ -11,12 +11,25 @@
|
||||
|
||||
struct platform_device;
|
||||
struct dev_pm_ops;
|
||||
struct brcmnand_io_ops;
|
||||
|
||||
/* Special register offset constant to intercept a non-MMIO access
|
||||
* to the flash cache register space. This is intentionally large
|
||||
* not to overlap with an existing offset.
|
||||
*/
|
||||
#define BRCMNAND_NON_MMIO_FC_ADDR 0xffffffff
|
||||
|
||||
struct brcmnand_soc {
|
||||
bool (*ctlrdy_ack)(struct brcmnand_soc *soc);
|
||||
void (*ctlrdy_set_enabled)(struct brcmnand_soc *soc, bool en);
|
||||
void (*prepare_data_bus)(struct brcmnand_soc *soc, bool prepare,
|
||||
bool is_param);
|
||||
const struct brcmnand_io_ops *ops;
|
||||
};
|
||||
|
||||
struct brcmnand_io_ops {
|
||||
u32 (*read_reg)(struct brcmnand_soc *soc, u32 offset);
|
||||
void (*write_reg)(struct brcmnand_soc *soc, u32 val, u32 offset);
|
||||
};
|
||||
|
||||
static inline void brcmnand_soc_data_bus_prepare(struct brcmnand_soc *soc,
|
||||
@ -58,6 +71,22 @@ static inline void brcmnand_writel(u32 val, void __iomem *addr)
|
||||
writel_relaxed(val, addr);
|
||||
}
|
||||
|
||||
static inline bool brcmnand_soc_has_ops(struct brcmnand_soc *soc)
|
||||
{
|
||||
return soc && soc->ops && soc->ops->read_reg && soc->ops->write_reg;
|
||||
}
|
||||
|
||||
static inline u32 brcmnand_soc_read(struct brcmnand_soc *soc, u32 offset)
|
||||
{
|
||||
return soc->ops->read_reg(soc, offset);
|
||||
}
|
||||
|
||||
static inline void brcmnand_soc_write(struct brcmnand_soc *soc, u32 val,
|
||||
u32 offset)
|
||||
{
|
||||
soc->ops->write_reg(soc, val, offset);
|
||||
}
|
||||
|
||||
int brcmnand_probe(struct platform_device *pdev, struct brcmnand_soc *soc);
|
||||
int brcmnand_remove(struct platform_device *pdev);
|
||||
|
||||
|
@ -644,10 +644,11 @@ static int bch_set_geometry(struct gpmi_nand_data *this)
|
||||
* RDN_DELAY = ----------------------- {3}
|
||||
* RP
|
||||
*/
|
||||
static void gpmi_nfc_compute_timings(struct gpmi_nand_data *this,
|
||||
const struct nand_sdr_timings *sdr)
|
||||
static int gpmi_nfc_compute_timings(struct gpmi_nand_data *this,
|
||||
const struct nand_sdr_timings *sdr)
|
||||
{
|
||||
struct gpmi_nfc_hardware_timing *hw = &this->hw;
|
||||
struct resources *r = &this->resources;
|
||||
unsigned int dll_threshold_ps = this->devdata->max_chain_delay;
|
||||
unsigned int period_ps, reference_period_ps;
|
||||
unsigned int data_setup_cycles, data_hold_cycles, addr_setup_cycles;
|
||||
@ -656,21 +657,33 @@ static void gpmi_nfc_compute_timings(struct gpmi_nand_data *this,
|
||||
int sample_delay_ps, sample_delay_factor;
|
||||
u16 busy_timeout_cycles;
|
||||
u8 wrn_dly_sel;
|
||||
unsigned long clk_rate, min_rate;
|
||||
|
||||
if (sdr->tRC_min >= 30000) {
|
||||
/* ONFI non-EDO modes [0-3] */
|
||||
hw->clk_rate = 22000000;
|
||||
min_rate = 0;
|
||||
wrn_dly_sel = BV_GPMI_CTRL1_WRN_DLY_SEL_4_TO_8NS;
|
||||
} else if (sdr->tRC_min >= 25000) {
|
||||
/* ONFI EDO mode 4 */
|
||||
hw->clk_rate = 80000000;
|
||||
min_rate = 22000000;
|
||||
wrn_dly_sel = BV_GPMI_CTRL1_WRN_DLY_SEL_NO_DELAY;
|
||||
} else {
|
||||
/* ONFI EDO mode 5 */
|
||||
hw->clk_rate = 100000000;
|
||||
min_rate = 80000000;
|
||||
wrn_dly_sel = BV_GPMI_CTRL1_WRN_DLY_SEL_NO_DELAY;
|
||||
}
|
||||
|
||||
clk_rate = clk_round_rate(r->clock[0], hw->clk_rate);
|
||||
if (clk_rate <= min_rate) {
|
||||
dev_err(this->dev, "clock setting: expected %ld, got %ld\n",
|
||||
hw->clk_rate, clk_rate);
|
||||
return -ENOTSUPP;
|
||||
}
|
||||
|
||||
hw->clk_rate = clk_rate;
|
||||
/* SDR core timings are given in picoseconds */
|
||||
period_ps = div_u64((u64)NSEC_PER_SEC * 1000, hw->clk_rate);
|
||||
|
||||
@ -711,6 +724,7 @@ static void gpmi_nfc_compute_timings(struct gpmi_nand_data *this,
|
||||
hw->ctrl1n |= BF_GPMI_CTRL1_RDN_DELAY(sample_delay_factor) |
|
||||
BM_GPMI_CTRL1_DLL_ENABLE |
|
||||
(use_half_period ? BM_GPMI_CTRL1_HALF_PERIOD : 0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int gpmi_nfc_apply_timings(struct gpmi_nand_data *this)
|
||||
@ -766,14 +780,15 @@ static int gpmi_setup_interface(struct nand_chip *chip, int chipnr,
|
||||
{
|
||||
struct gpmi_nand_data *this = nand_get_controller_data(chip);
|
||||
const struct nand_sdr_timings *sdr;
|
||||
int ret;
|
||||
|
||||
/* Retrieve required NAND timings */
|
||||
sdr = nand_get_sdr_timings(conf);
|
||||
if (IS_ERR(sdr))
|
||||
return PTR_ERR(sdr);
|
||||
|
||||
/* Only MX6 GPMI controller can reach EDO timings */
|
||||
if (sdr->tRC_min <= 25000 && !GPMI_IS_MX6(this))
|
||||
/* Only MX28/MX6 GPMI controller can reach EDO timings */
|
||||
if (sdr->tRC_min <= 25000 && !GPMI_IS_MX28(this) && !GPMI_IS_MX6(this))
|
||||
return -ENOTSUPP;
|
||||
|
||||
/* Stop here if this call was just a check */
|
||||
@ -781,7 +796,9 @@ static int gpmi_setup_interface(struct nand_chip *chip, int chipnr,
|
||||
return 0;
|
||||
|
||||
/* Do the actual derivation of the controller timings */
|
||||
gpmi_nfc_compute_timings(this, sdr);
|
||||
ret = gpmi_nfc_compute_timings(this, sdr);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
this->hw.must_apply_timings = true;
|
||||
|
||||
|
@ -567,7 +567,7 @@ static struct platform_driver ingenic_nand_driver = {
|
||||
.remove = ingenic_nand_remove,
|
||||
.driver = {
|
||||
.name = DRV_NAME,
|
||||
.of_match_table = of_match_ptr(ingenic_nand_dt_match),
|
||||
.of_match_table = ingenic_nand_dt_match,
|
||||
},
|
||||
};
|
||||
module_platform_driver(ingenic_nand_driver);
|
||||
|
@ -260,7 +260,7 @@ static struct platform_driver jz4780_bch_driver = {
|
||||
.probe = jz4780_bch_probe,
|
||||
.driver = {
|
||||
.name = "jz4780-bch",
|
||||
.of_match_table = of_match_ptr(jz4780_bch_dt_match),
|
||||
.of_match_table = jz4780_bch_dt_match,
|
||||
},
|
||||
};
|
||||
module_platform_driver(jz4780_bch_driver);
|
||||
|
@ -579,7 +579,7 @@ static struct platform_driver mtk_ecc_driver = {
|
||||
.probe = mtk_ecc_probe,
|
||||
.driver = {
|
||||
.name = "mtk-ecc",
|
||||
.of_match_table = of_match_ptr(mtk_ecc_dt_match),
|
||||
.of_match_table = mtk_ecc_dt_match,
|
||||
#ifdef CONFIG_PM_SLEEP
|
||||
.pm = &mtk_ecc_pm_ops,
|
||||
#endif
|
||||
|
@ -321,7 +321,7 @@ static int nand_isbad_bbm(struct nand_chip *chip, loff_t ofs)
|
||||
if (nand_region_is_secured(chip, ofs, mtd->erasesize))
|
||||
return -EIO;
|
||||
|
||||
if (WARN_ONCE(mtd_expert_analysis_mode, mtd_expert_analysis_warning))
|
||||
if (mtd_check_expert_analysis_mode())
|
||||
return 0;
|
||||
|
||||
if (chip->legacy.block_bad)
|
||||
@ -338,16 +338,19 @@ static int nand_isbad_bbm(struct nand_chip *chip, loff_t ofs)
|
||||
*
|
||||
* Return: -EBUSY if the chip has been suspended, 0 otherwise
|
||||
*/
|
||||
static int nand_get_device(struct nand_chip *chip)
|
||||
static void nand_get_device(struct nand_chip *chip)
|
||||
{
|
||||
mutex_lock(&chip->lock);
|
||||
if (chip->suspended) {
|
||||
/* Wait until the device is resumed. */
|
||||
while (1) {
|
||||
mutex_lock(&chip->lock);
|
||||
if (!chip->suspended) {
|
||||
mutex_lock(&chip->controller->lock);
|
||||
return;
|
||||
}
|
||||
mutex_unlock(&chip->lock);
|
||||
return -EBUSY;
|
||||
}
|
||||
mutex_lock(&chip->controller->lock);
|
||||
|
||||
return 0;
|
||||
wait_event(chip->resume_wq, !chip->suspended);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -576,9 +579,7 @@ static int nand_block_markbad_lowlevel(struct nand_chip *chip, loff_t ofs)
|
||||
nand_erase_nand(chip, &einfo, 0);
|
||||
|
||||
/* Write bad block marker to OOB */
|
||||
ret = nand_get_device(chip);
|
||||
if (ret)
|
||||
return ret;
|
||||
nand_get_device(chip);
|
||||
|
||||
ret = nand_markbad_bbm(chip, ofs);
|
||||
nand_release_device(chip);
|
||||
@ -3826,9 +3827,7 @@ static int nand_read_oob(struct mtd_info *mtd, loff_t from,
|
||||
ops->mode != MTD_OPS_RAW)
|
||||
return -ENOTSUPP;
|
||||
|
||||
ret = nand_get_device(chip);
|
||||
if (ret)
|
||||
return ret;
|
||||
nand_get_device(chip);
|
||||
|
||||
if (!ops->datbuf)
|
||||
ret = nand_do_read_oob(chip, from, ops);
|
||||
@ -4415,13 +4414,11 @@ static int nand_write_oob(struct mtd_info *mtd, loff_t to,
|
||||
struct mtd_oob_ops *ops)
|
||||
{
|
||||
struct nand_chip *chip = mtd_to_nand(mtd);
|
||||
int ret;
|
||||
int ret = 0;
|
||||
|
||||
ops->retlen = 0;
|
||||
|
||||
ret = nand_get_device(chip);
|
||||
if (ret)
|
||||
return ret;
|
||||
nand_get_device(chip);
|
||||
|
||||
switch (ops->mode) {
|
||||
case MTD_OPS_PLACE_OOB:
|
||||
@ -4481,9 +4478,7 @@ int nand_erase_nand(struct nand_chip *chip, struct erase_info *instr,
|
||||
return -EIO;
|
||||
|
||||
/* Grab the lock and see if the device is available */
|
||||
ret = nand_get_device(chip);
|
||||
if (ret)
|
||||
return ret;
|
||||
nand_get_device(chip);
|
||||
|
||||
/* Shift to get first page */
|
||||
page = (int)(instr->addr >> chip->page_shift);
|
||||
@ -4570,7 +4565,7 @@ static void nand_sync(struct mtd_info *mtd)
|
||||
pr_debug("%s: called\n", __func__);
|
||||
|
||||
/* Grab the lock and see if the device is available */
|
||||
WARN_ON(nand_get_device(chip));
|
||||
nand_get_device(chip);
|
||||
/* Release it and go back */
|
||||
nand_release_device(chip);
|
||||
}
|
||||
@ -4587,9 +4582,7 @@ static int nand_block_isbad(struct mtd_info *mtd, loff_t offs)
|
||||
int ret;
|
||||
|
||||
/* Select the NAND device */
|
||||
ret = nand_get_device(chip);
|
||||
if (ret)
|
||||
return ret;
|
||||
nand_get_device(chip);
|
||||
|
||||
nand_select_target(chip, chipnr);
|
||||
|
||||
@ -4660,6 +4653,8 @@ static void nand_resume(struct mtd_info *mtd)
|
||||
__func__);
|
||||
}
|
||||
mutex_unlock(&chip->lock);
|
||||
|
||||
wake_up_all(&chip->resume_wq);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -5274,25 +5269,24 @@ static void of_get_nand_ecc_legacy_user_config(struct nand_chip *chip)
|
||||
user_conf->placement = of_get_rawnand_ecc_placement_legacy(dn);
|
||||
}
|
||||
|
||||
static int of_get_nand_bus_width(struct device_node *np)
|
||||
static int of_get_nand_bus_width(struct nand_chip *chip)
|
||||
{
|
||||
struct device_node *dn = nand_get_flash_node(chip);
|
||||
u32 val;
|
||||
int ret;
|
||||
|
||||
if (of_property_read_u32(np, "nand-bus-width", &val))
|
||||
return 8;
|
||||
ret = of_property_read_u32(dn, "nand-bus-width", &val);
|
||||
if (ret == -EINVAL)
|
||||
/* Buswidth defaults to 8 if the property does not exist .*/
|
||||
return 0;
|
||||
else if (ret)
|
||||
return ret;
|
||||
|
||||
switch (val) {
|
||||
case 8:
|
||||
case 16:
|
||||
return val;
|
||||
default:
|
||||
return -EIO;
|
||||
}
|
||||
}
|
||||
|
||||
static bool of_get_nand_on_flash_bbt(struct device_node *np)
|
||||
{
|
||||
return of_property_read_bool(np, "nand-on-flash-bbt");
|
||||
if (val == 16)
|
||||
chip->options |= NAND_BUSWIDTH_16;
|
||||
else if (val != 8)
|
||||
return -EINVAL;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int of_get_nand_secure_regions(struct nand_chip *chip)
|
||||
@ -5368,17 +5362,19 @@ static int rawnand_dt_init(struct nand_chip *chip)
|
||||
{
|
||||
struct nand_device *nand = mtd_to_nanddev(nand_to_mtd(chip));
|
||||
struct device_node *dn = nand_get_flash_node(chip);
|
||||
int ret;
|
||||
|
||||
if (!dn)
|
||||
return 0;
|
||||
|
||||
if (of_get_nand_bus_width(dn) == 16)
|
||||
chip->options |= NAND_BUSWIDTH_16;
|
||||
ret = of_get_nand_bus_width(chip);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (of_property_read_bool(dn, "nand-is-boot-medium"))
|
||||
chip->options |= NAND_IS_BOOT_MEDIUM;
|
||||
|
||||
if (of_get_nand_on_flash_bbt(dn))
|
||||
if (of_property_read_bool(dn, "nand-on-flash-bbt"))
|
||||
chip->bbt_options |= NAND_BBT_USE_FLASH;
|
||||
|
||||
of_get_nand_ecc_user_config(nand);
|
||||
@ -5437,6 +5433,7 @@ static int nand_scan_ident(struct nand_chip *chip, unsigned int maxchips,
|
||||
chip->cur_cs = -1;
|
||||
|
||||
mutex_init(&chip->lock);
|
||||
init_waitqueue_head(&chip->resume_wq);
|
||||
|
||||
/* Enforce the right timings for reset/detection */
|
||||
chip->current_interface_config = nand_get_reset_interface_config();
|
||||
|
@ -1455,7 +1455,7 @@ int nand_isbad_bbt(struct nand_chip *this, loff_t offs, int allowbbt)
|
||||
pr_debug("nand_isbad_bbt(): bbt info for offs 0x%08x: (block %d) 0x%02x\n",
|
||||
(unsigned int)offs, block, res);
|
||||
|
||||
if (WARN_ONCE(mtd_expert_analysis_mode, mtd_expert_analysis_warning))
|
||||
if (mtd_check_expert_analysis_mode())
|
||||
return 0;
|
||||
|
||||
switch (res) {
|
||||
|
@ -201,6 +201,9 @@ MODULE_PARM_DESC(bch, "Enable BCH ecc and set how many bits should "
|
||||
/* Calculate the OOB offset in flash RAM image by (row, column) address */
|
||||
#define NS_RAW_OFFSET_OOB(ns) (NS_RAW_OFFSET(ns) + ns->geom.pgsz)
|
||||
|
||||
/* Calculate the byte shift in the next page to access */
|
||||
#define NS_PAGE_BYTE_SHIFT(ns) ((ns)->regs.column + (ns)->regs.off)
|
||||
|
||||
/* After a command is input, the simulator goes to one of the following states */
|
||||
#define STATE_CMD_READ0 0x00000001 /* read data from the beginning of page */
|
||||
#define STATE_CMD_READ1 0x00000002 /* read data from the second half of page */
|
||||
@ -979,15 +982,8 @@ static int ns_read_error(unsigned int page_no)
|
||||
|
||||
static int ns_setup_wear_reporting(struct mtd_info *mtd)
|
||||
{
|
||||
size_t mem;
|
||||
|
||||
wear_eb_count = div_u64(mtd->size, mtd->erasesize);
|
||||
mem = wear_eb_count * sizeof(unsigned long);
|
||||
if (mem / sizeof(unsigned long) != wear_eb_count) {
|
||||
NS_ERR("Too many erase blocks for wear reporting\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
erase_block_wear = kzalloc(mem, GFP_KERNEL);
|
||||
erase_block_wear = kcalloc(wear_eb_count, sizeof(unsigned long), GFP_KERNEL);
|
||||
if (!erase_block_wear) {
|
||||
NS_ERR("Too many erase blocks for wear reporting\n");
|
||||
return -ENOMEM;
|
||||
@ -1389,7 +1385,7 @@ static inline union ns_mem *NS_GET_PAGE(struct nandsim *ns)
|
||||
*/
|
||||
static inline u_char *NS_PAGE_BYTE_OFF(struct nandsim *ns)
|
||||
{
|
||||
return NS_GET_PAGE(ns)->byte + ns->regs.column + ns->regs.off;
|
||||
return NS_GET_PAGE(ns)->byte + NS_PAGE_BYTE_SHIFT(ns);
|
||||
}
|
||||
|
||||
static int ns_do_read_error(struct nandsim *ns, int num)
|
||||
@ -1415,7 +1411,7 @@ static void ns_do_bit_flips(struct nandsim *ns, int num)
|
||||
ns->buf.byte[pos / 8] ^= (1 << (pos % 8));
|
||||
NS_WARN("read_page: flipping bit %d in page %d "
|
||||
"reading from %d ecc: corrected=%u failed=%u\n",
|
||||
pos, ns->regs.row, ns->regs.column + ns->regs.off,
|
||||
pos, ns->regs.row, NS_PAGE_BYTE_SHIFT(ns),
|
||||
nsmtd->ecc_stats.corrected, nsmtd->ecc_stats.failed);
|
||||
}
|
||||
}
|
||||
@ -1437,7 +1433,7 @@ static void ns_read_page(struct nandsim *ns, int num)
|
||||
ssize_t tx;
|
||||
|
||||
NS_DBG("read_page: page %d written, reading from %d\n",
|
||||
ns->regs.row, ns->regs.column + ns->regs.off);
|
||||
ns->regs.row, NS_PAGE_BYTE_SHIFT(ns));
|
||||
if (ns_do_read_error(ns, num))
|
||||
return;
|
||||
pos = (loff_t)NS_RAW_OFFSET(ns) + ns->regs.off;
|
||||
@ -1458,7 +1454,7 @@ static void ns_read_page(struct nandsim *ns, int num)
|
||||
memset(ns->buf.byte, 0xFF, num);
|
||||
} else {
|
||||
NS_DBG("read_page: page %d allocated, reading from %d\n",
|
||||
ns->regs.row, ns->regs.column + ns->regs.off);
|
||||
ns->regs.row, NS_PAGE_BYTE_SHIFT(ns));
|
||||
if (ns_do_read_error(ns, num))
|
||||
return;
|
||||
memcpy(ns->buf.byte, NS_PAGE_BYTE_OFF(ns), num);
|
||||
@ -1509,7 +1505,7 @@ static int ns_prog_page(struct nandsim *ns, int num)
|
||||
int all;
|
||||
|
||||
NS_DBG("prog_page: writing page %d\n", ns->regs.row);
|
||||
pg_off = ns->file_buf + ns->regs.column + ns->regs.off;
|
||||
pg_off = ns->file_buf + NS_PAGE_BYTE_SHIFT(ns);
|
||||
off = (loff_t)NS_RAW_OFFSET(ns) + ns->regs.off;
|
||||
if (!test_bit(ns->regs.row, ns->pages_written)) {
|
||||
all = 1;
|
||||
@ -1598,7 +1594,7 @@ static int ns_do_state_action(struct nandsim *ns, uint32_t action)
|
||||
NS_ERR("do_state_action: column number is too large\n");
|
||||
break;
|
||||
}
|
||||
num = ns->geom.pgszoob - ns->regs.off - ns->regs.column;
|
||||
num = ns->geom.pgszoob - NS_PAGE_BYTE_SHIFT(ns);
|
||||
ns_read_page(ns, num);
|
||||
|
||||
NS_DBG("do_state_action: (ACTION_CPY:) copy %d bytes to int buf, raw offset %d\n",
|
||||
@ -1666,7 +1662,7 @@ static int ns_do_state_action(struct nandsim *ns, uint32_t action)
|
||||
return -1;
|
||||
}
|
||||
|
||||
num = ns->geom.pgszoob - ns->regs.off - ns->regs.column;
|
||||
num = ns->geom.pgszoob - NS_PAGE_BYTE_SHIFT(ns);
|
||||
if (num != ns->regs.count) {
|
||||
NS_ERR("do_state_action: too few bytes were input (%d instead of %d)\n",
|
||||
ns->regs.count, num);
|
||||
@ -1738,14 +1734,6 @@ static void ns_switch_state(struct nandsim *ns)
|
||||
"state: %s, nxstate: %s\n",
|
||||
ns_get_state_name(ns->state),
|
||||
ns_get_state_name(ns->nxstate));
|
||||
|
||||
/* See, whether we need to do some action */
|
||||
if ((ns->state & ACTION_MASK) &&
|
||||
ns_do_state_action(ns, ns->state) < 0) {
|
||||
ns_switch_to_ready_state(ns, NS_STATUS_FAILED(ns));
|
||||
return;
|
||||
}
|
||||
|
||||
} else {
|
||||
/*
|
||||
* We don't yet know which operation we perform.
|
||||
@ -1762,12 +1750,13 @@ static void ns_switch_state(struct nandsim *ns)
|
||||
|
||||
if (ns_find_operation(ns, 0))
|
||||
return;
|
||||
}
|
||||
|
||||
if ((ns->state & ACTION_MASK) &&
|
||||
ns_do_state_action(ns, ns->state) < 0) {
|
||||
ns_switch_to_ready_state(ns, NS_STATUS_FAILED(ns));
|
||||
return;
|
||||
}
|
||||
/* See, whether we need to do some action */
|
||||
if ((ns->state & ACTION_MASK) &&
|
||||
ns_do_state_action(ns, ns->state) < 0) {
|
||||
ns_switch_to_ready_state(ns, NS_STATUS_FAILED(ns));
|
||||
return;
|
||||
}
|
||||
|
||||
/* For 16x devices column means the page offset in words */
|
||||
@ -1817,7 +1806,7 @@ static void ns_switch_state(struct nandsim *ns)
|
||||
switch (NS_STATE(ns->state)) {
|
||||
case STATE_DATAIN:
|
||||
case STATE_DATAOUT:
|
||||
ns->regs.num = ns->geom.pgszoob - ns->regs.off - ns->regs.column;
|
||||
ns->regs.num = ns->geom.pgszoob - NS_PAGE_BYTE_SHIFT(ns);
|
||||
break;
|
||||
|
||||
case STATE_DATAOUT_ID:
|
||||
|
@ -2298,7 +2298,7 @@ static struct platform_driver omap_nand_driver = {
|
||||
.remove = omap_nand_remove,
|
||||
.driver = {
|
||||
.name = DRIVER_NAME,
|
||||
.of_match_table = of_match_ptr(omap_nand_ids),
|
||||
.of_match_table = omap_nand_ids,
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -282,7 +282,7 @@ static void elm_start_processing(struct elm_info *info,
|
||||
static void elm_error_correction(struct elm_info *info,
|
||||
struct elm_errorvec *err_vec)
|
||||
{
|
||||
int i, j, errors = 0;
|
||||
int i, j;
|
||||
int offset;
|
||||
u32 reg_val;
|
||||
|
||||
@ -312,8 +312,6 @@ static void elm_error_correction(struct elm_info *info,
|
||||
/* Update error location register */
|
||||
offset += 4;
|
||||
}
|
||||
|
||||
errors += err_vec[i].error_count;
|
||||
} else {
|
||||
err_vec[i].error_uncorrectable = true;
|
||||
}
|
||||
|
@ -1062,7 +1062,7 @@ static int pl35x_nand_chip_init(struct pl35x_nandc *nfc,
|
||||
chip->controller = &nfc->controller;
|
||||
mtd = nand_to_mtd(chip);
|
||||
mtd->dev.parent = nfc->dev;
|
||||
nand_set_flash_node(chip, nfc->dev->of_node);
|
||||
nand_set_flash_node(chip, np);
|
||||
if (!mtd->name) {
|
||||
mtd->name = devm_kasprintf(nfc->dev, GFP_KERNEL,
|
||||
"%s", PL35X_NANDC_DRIVER_NAME);
|
||||
|
@ -1412,7 +1412,7 @@ MODULE_DEVICE_TABLE(of, rnandc_id_table);
|
||||
static struct platform_driver rnandc_driver = {
|
||||
.driver = {
|
||||
.name = "renesas-nandc",
|
||||
.of_match_table = of_match_ptr(rnandc_id_table),
|
||||
.of_match_table = rnandc_id_table,
|
||||
},
|
||||
.probe = rnandc_probe,
|
||||
.remove = rnandc_remove,
|
||||
|
@ -1403,7 +1403,6 @@ static int rk_nfc_probe(struct platform_device *pdev)
|
||||
|
||||
irq = platform_get_irq(pdev, 0);
|
||||
if (irq < 0) {
|
||||
dev_err(dev, "no NFC irq resource\n");
|
||||
ret = -EINVAL;
|
||||
goto clk_disable;
|
||||
}
|
||||
|
@ -1220,7 +1220,7 @@ static struct platform_driver flctl_driver = {
|
||||
.remove = flctl_remove,
|
||||
.driver = {
|
||||
.name = "sh_flctl",
|
||||
.of_match_table = of_match_ptr(of_flctl_match),
|
||||
.of_match_table = of_flctl_match,
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -9,6 +9,7 @@
|
||||
#include <linux/dmaengine.h>
|
||||
#include <linux/dma-mapping.h>
|
||||
#include <linux/errno.h>
|
||||
#include <linux/gpio/consumer.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/iopoll.h>
|
||||
#include <linux/mfd/syscon.h>
|
||||
@ -231,6 +232,7 @@ struct stm32_fmc2_timings {
|
||||
|
||||
struct stm32_fmc2_nand {
|
||||
struct nand_chip chip;
|
||||
struct gpio_desc *wp_gpio;
|
||||
struct stm32_fmc2_timings timings;
|
||||
int ncs;
|
||||
int cs_used[FMC2_MAX_CE];
|
||||
@ -1747,6 +1749,18 @@ static const struct nand_controller_ops stm32_fmc2_nfc_controller_ops = {
|
||||
.setup_interface = stm32_fmc2_nfc_setup_interface,
|
||||
};
|
||||
|
||||
static void stm32_fmc2_nfc_wp_enable(struct stm32_fmc2_nand *nand)
|
||||
{
|
||||
if (nand->wp_gpio)
|
||||
gpiod_set_value(nand->wp_gpio, 1);
|
||||
}
|
||||
|
||||
static void stm32_fmc2_nfc_wp_disable(struct stm32_fmc2_nand *nand)
|
||||
{
|
||||
if (nand->wp_gpio)
|
||||
gpiod_set_value(nand->wp_gpio, 0);
|
||||
}
|
||||
|
||||
static int stm32_fmc2_nfc_parse_child(struct stm32_fmc2_nfc *nfc,
|
||||
struct device_node *dn)
|
||||
{
|
||||
@ -1785,6 +1799,18 @@ static int stm32_fmc2_nfc_parse_child(struct stm32_fmc2_nfc *nfc,
|
||||
nand->cs_used[i] = cs;
|
||||
}
|
||||
|
||||
nand->wp_gpio = devm_gpiod_get_from_of_node(nfc->dev, dn,
|
||||
"wp-gpios", 0,
|
||||
GPIOD_OUT_HIGH, "wp");
|
||||
if (IS_ERR(nand->wp_gpio)) {
|
||||
ret = PTR_ERR(nand->wp_gpio);
|
||||
if (ret != -ENOENT)
|
||||
return dev_err_probe(nfc->dev, ret,
|
||||
"failed to request WP GPIO\n");
|
||||
|
||||
nand->wp_gpio = NULL;
|
||||
}
|
||||
|
||||
nand_set_flash_node(&nand->chip, dn);
|
||||
|
||||
return 0;
|
||||
@ -1956,10 +1982,12 @@ static int stm32_fmc2_nfc_probe(struct platform_device *pdev)
|
||||
chip->options |= NAND_BUSWIDTH_AUTO | NAND_NO_SUBPAGE_WRITE |
|
||||
NAND_USES_DMA;
|
||||
|
||||
stm32_fmc2_nfc_wp_disable(nand);
|
||||
|
||||
/* Scan to find existence of the device */
|
||||
ret = nand_scan(chip, nand->ncs);
|
||||
if (ret)
|
||||
goto err_release_dma;
|
||||
goto err_wp_enable;
|
||||
|
||||
ret = mtd_device_register(mtd, NULL, 0);
|
||||
if (ret)
|
||||
@ -1972,6 +2000,9 @@ static int stm32_fmc2_nfc_probe(struct platform_device *pdev)
|
||||
err_nand_cleanup:
|
||||
nand_cleanup(chip);
|
||||
|
||||
err_wp_enable:
|
||||
stm32_fmc2_nfc_wp_enable(nand);
|
||||
|
||||
err_release_dma:
|
||||
if (nfc->dma_ecc_ch)
|
||||
dma_release_channel(nfc->dma_ecc_ch);
|
||||
@ -2012,15 +2043,20 @@ static int stm32_fmc2_nfc_remove(struct platform_device *pdev)
|
||||
|
||||
clk_disable_unprepare(nfc->clk);
|
||||
|
||||
stm32_fmc2_nfc_wp_enable(nand);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int __maybe_unused stm32_fmc2_nfc_suspend(struct device *dev)
|
||||
{
|
||||
struct stm32_fmc2_nfc *nfc = dev_get_drvdata(dev);
|
||||
struct stm32_fmc2_nand *nand = &nfc->nand;
|
||||
|
||||
clk_disable_unprepare(nfc->clk);
|
||||
|
||||
stm32_fmc2_nfc_wp_enable(nand);
|
||||
|
||||
pinctrl_pm_select_sleep_state(dev);
|
||||
|
||||
return 0;
|
||||
@ -2042,6 +2078,8 @@ static int __maybe_unused stm32_fmc2_nfc_resume(struct device *dev)
|
||||
|
||||
stm32_fmc2_nfc_init(nfc);
|
||||
|
||||
stm32_fmc2_nfc_wp_disable(nand);
|
||||
|
||||
for (chip_cs = 0; chip_cs < FMC2_MAX_CE; chip_cs++) {
|
||||
if (!(nfc->cs_assigned & BIT(chip_cs)))
|
||||
continue;
|
||||
|
@ -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),
|
||||
|
@ -115,7 +115,7 @@ config MTD_AFS_PARTS
|
||||
|
||||
config MTD_PARSER_TRX
|
||||
tristate "Parser for TRX format partitions"
|
||||
depends on MTD && (BCM47XX || ARCH_BCM_5301X || ARCH_MEDIATEK || COMPILE_TEST)
|
||||
depends on MTD && (BCM47XX || ARCH_BCM_5301X || ARCH_MEDIATEK || RALINK || COMPILE_TEST)
|
||||
help
|
||||
TRX is a firmware format used by Broadcom on their devices. It
|
||||
may contain up to 3/4 partitions (depending on the version).
|
||||
|
@ -16,12 +16,12 @@
|
||||
* is to unlock the whole flash array on startup. Therefore, we have to support
|
||||
* exactly this operation.
|
||||
*/
|
||||
static int atmel_at25fs_lock(struct spi_nor *nor, loff_t ofs, uint64_t len)
|
||||
static int at25fs_nor_lock(struct spi_nor *nor, loff_t ofs, uint64_t len)
|
||||
{
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
|
||||
static int atmel_at25fs_unlock(struct spi_nor *nor, loff_t ofs, uint64_t len)
|
||||
static int at25fs_nor_unlock(struct spi_nor *nor, loff_t ofs, uint64_t len)
|
||||
{
|
||||
int ret;
|
||||
|
||||
@ -37,28 +37,28 @@ static int atmel_at25fs_unlock(struct spi_nor *nor, loff_t ofs, uint64_t len)
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int atmel_at25fs_is_locked(struct spi_nor *nor, loff_t ofs, uint64_t len)
|
||||
static int at25fs_nor_is_locked(struct spi_nor *nor, loff_t ofs, uint64_t len)
|
||||
{
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
|
||||
static const struct spi_nor_locking_ops atmel_at25fs_locking_ops = {
|
||||
.lock = atmel_at25fs_lock,
|
||||
.unlock = atmel_at25fs_unlock,
|
||||
.is_locked = atmel_at25fs_is_locked,
|
||||
static const struct spi_nor_locking_ops at25fs_nor_locking_ops = {
|
||||
.lock = at25fs_nor_lock,
|
||||
.unlock = at25fs_nor_unlock,
|
||||
.is_locked = at25fs_nor_is_locked,
|
||||
};
|
||||
|
||||
static void atmel_at25fs_late_init(struct spi_nor *nor)
|
||||
static void at25fs_nor_late_init(struct spi_nor *nor)
|
||||
{
|
||||
nor->params->locking_ops = &atmel_at25fs_locking_ops;
|
||||
nor->params->locking_ops = &at25fs_nor_locking_ops;
|
||||
}
|
||||
|
||||
static const struct spi_nor_fixups atmel_at25fs_fixups = {
|
||||
.late_init = atmel_at25fs_late_init,
|
||||
static const struct spi_nor_fixups at25fs_nor_fixups = {
|
||||
.late_init = at25fs_nor_late_init,
|
||||
};
|
||||
|
||||
/**
|
||||
* atmel_set_global_protection - Do a Global Protect or Unprotect command
|
||||
* atmel_nor_set_global_protection - Do a Global Protect or Unprotect command
|
||||
* @nor: pointer to 'struct spi_nor'
|
||||
* @ofs: offset in bytes
|
||||
* @len: len in bytes
|
||||
@ -66,8 +66,8 @@ static const struct spi_nor_fixups atmel_at25fs_fixups = {
|
||||
*
|
||||
* Return: 0 on success, -error otherwise.
|
||||
*/
|
||||
static int atmel_set_global_protection(struct spi_nor *nor, loff_t ofs,
|
||||
uint64_t len, bool is_protect)
|
||||
static int atmel_nor_set_global_protection(struct spi_nor *nor, loff_t ofs,
|
||||
uint64_t len, bool is_protect)
|
||||
{
|
||||
int ret;
|
||||
u8 sr;
|
||||
@ -116,17 +116,20 @@ static int atmel_set_global_protection(struct spi_nor *nor, loff_t ofs,
|
||||
return spi_nor_write_sr(nor, nor->bouncebuf, 1);
|
||||
}
|
||||
|
||||
static int atmel_global_protect(struct spi_nor *nor, loff_t ofs, uint64_t len)
|
||||
static int atmel_nor_global_protect(struct spi_nor *nor, loff_t ofs,
|
||||
uint64_t len)
|
||||
{
|
||||
return atmel_set_global_protection(nor, ofs, len, true);
|
||||
return atmel_nor_set_global_protection(nor, ofs, len, true);
|
||||
}
|
||||
|
||||
static int atmel_global_unprotect(struct spi_nor *nor, loff_t ofs, uint64_t len)
|
||||
static int atmel_nor_global_unprotect(struct spi_nor *nor, loff_t ofs,
|
||||
uint64_t len)
|
||||
{
|
||||
return atmel_set_global_protection(nor, ofs, len, false);
|
||||
return atmel_nor_set_global_protection(nor, ofs, len, false);
|
||||
}
|
||||
|
||||
static int atmel_is_global_protected(struct spi_nor *nor, loff_t ofs, uint64_t len)
|
||||
static int atmel_nor_is_global_protected(struct spi_nor *nor, loff_t ofs,
|
||||
uint64_t len)
|
||||
{
|
||||
int ret;
|
||||
|
||||
@ -140,47 +143,47 @@ static int atmel_is_global_protected(struct spi_nor *nor, loff_t ofs, uint64_t l
|
||||
return ((nor->bouncebuf[0] & ATMEL_SR_GLOBAL_PROTECT_MASK) == ATMEL_SR_GLOBAL_PROTECT_MASK);
|
||||
}
|
||||
|
||||
static const struct spi_nor_locking_ops atmel_global_protection_ops = {
|
||||
.lock = atmel_global_protect,
|
||||
.unlock = atmel_global_unprotect,
|
||||
.is_locked = atmel_is_global_protected,
|
||||
static const struct spi_nor_locking_ops atmel_nor_global_protection_ops = {
|
||||
.lock = atmel_nor_global_protect,
|
||||
.unlock = atmel_nor_global_unprotect,
|
||||
.is_locked = atmel_nor_is_global_protected,
|
||||
};
|
||||
|
||||
static void atmel_global_protection_late_init(struct spi_nor *nor)
|
||||
static void atmel_nor_global_protection_late_init(struct spi_nor *nor)
|
||||
{
|
||||
nor->params->locking_ops = &atmel_global_protection_ops;
|
||||
nor->params->locking_ops = &atmel_nor_global_protection_ops;
|
||||
}
|
||||
|
||||
static const struct spi_nor_fixups atmel_global_protection_fixups = {
|
||||
.late_init = atmel_global_protection_late_init,
|
||||
static const struct spi_nor_fixups atmel_nor_global_protection_fixups = {
|
||||
.late_init = atmel_nor_global_protection_late_init,
|
||||
};
|
||||
|
||||
static const struct flash_info atmel_parts[] = {
|
||||
static const struct flash_info atmel_nor_parts[] = {
|
||||
/* Atmel -- some are (confusingly) marketed as "DataFlash" */
|
||||
{ "at25fs010", INFO(0x1f6601, 0, 32 * 1024, 4)
|
||||
FLAGS(SPI_NOR_HAS_LOCK)
|
||||
NO_SFDP_FLAGS(SECT_4K)
|
||||
.fixups = &atmel_at25fs_fixups },
|
||||
.fixups = &at25fs_nor_fixups },
|
||||
{ "at25fs040", INFO(0x1f6604, 0, 64 * 1024, 8)
|
||||
FLAGS(SPI_NOR_HAS_LOCK)
|
||||
NO_SFDP_FLAGS(SECT_4K)
|
||||
.fixups = &atmel_at25fs_fixups },
|
||||
.fixups = &at25fs_nor_fixups },
|
||||
{ "at25df041a", INFO(0x1f4401, 0, 64 * 1024, 8)
|
||||
FLAGS(SPI_NOR_HAS_LOCK | SPI_NOR_SWP_IS_VOLATILE)
|
||||
NO_SFDP_FLAGS(SECT_4K)
|
||||
.fixups = &atmel_global_protection_fixups },
|
||||
.fixups = &atmel_nor_global_protection_fixups },
|
||||
{ "at25df321", INFO(0x1f4700, 0, 64 * 1024, 64)
|
||||
FLAGS(SPI_NOR_HAS_LOCK | SPI_NOR_SWP_IS_VOLATILE)
|
||||
NO_SFDP_FLAGS(SECT_4K)
|
||||
.fixups = &atmel_global_protection_fixups },
|
||||
.fixups = &atmel_nor_global_protection_fixups },
|
||||
{ "at25df321a", INFO(0x1f4701, 0, 64 * 1024, 64)
|
||||
FLAGS(SPI_NOR_HAS_LOCK | SPI_NOR_SWP_IS_VOLATILE)
|
||||
NO_SFDP_FLAGS(SECT_4K)
|
||||
.fixups = &atmel_global_protection_fixups },
|
||||
.fixups = &atmel_nor_global_protection_fixups },
|
||||
{ "at25df641", INFO(0x1f4800, 0, 64 * 1024, 128)
|
||||
FLAGS(SPI_NOR_HAS_LOCK | SPI_NOR_SWP_IS_VOLATILE)
|
||||
NO_SFDP_FLAGS(SECT_4K)
|
||||
.fixups = &atmel_global_protection_fixups },
|
||||
.fixups = &atmel_nor_global_protection_fixups },
|
||||
{ "at25sl321", INFO(0x1f4216, 0, 64 * 1024, 64)
|
||||
NO_SFDP_FLAGS(SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ) },
|
||||
{ "at26f004", INFO(0x1f0400, 0, 64 * 1024, 8)
|
||||
@ -188,21 +191,21 @@ static const struct flash_info atmel_parts[] = {
|
||||
{ "at26df081a", INFO(0x1f4501, 0, 64 * 1024, 16)
|
||||
FLAGS(SPI_NOR_HAS_LOCK | SPI_NOR_SWP_IS_VOLATILE)
|
||||
NO_SFDP_FLAGS(SECT_4K)
|
||||
.fixups = &atmel_global_protection_fixups },
|
||||
.fixups = &atmel_nor_global_protection_fixups },
|
||||
{ "at26df161a", INFO(0x1f4601, 0, 64 * 1024, 32)
|
||||
FLAGS(SPI_NOR_HAS_LOCK | SPI_NOR_SWP_IS_VOLATILE)
|
||||
NO_SFDP_FLAGS(SECT_4K)
|
||||
.fixups = &atmel_global_protection_fixups },
|
||||
.fixups = &atmel_nor_global_protection_fixups },
|
||||
{ "at26df321", INFO(0x1f4700, 0, 64 * 1024, 64)
|
||||
FLAGS(SPI_NOR_HAS_LOCK | SPI_NOR_SWP_IS_VOLATILE)
|
||||
NO_SFDP_FLAGS(SECT_4K)
|
||||
.fixups = &atmel_global_protection_fixups },
|
||||
.fixups = &atmel_nor_global_protection_fixups },
|
||||
{ "at45db081d", INFO(0x1f2500, 0, 64 * 1024, 16)
|
||||
NO_SFDP_FLAGS(SECT_4K) },
|
||||
};
|
||||
|
||||
const struct spi_nor_manufacturer spi_nor_atmel = {
|
||||
.name = "atmel",
|
||||
.parts = atmel_parts,
|
||||
.nparts = ARRAY_SIZE(atmel_parts),
|
||||
.parts = atmel_nor_parts,
|
||||
.nparts = ARRAY_SIZE(atmel_nor_parts),
|
||||
};
|
||||
|
@ -8,7 +8,7 @@
|
||||
|
||||
#include "core.h"
|
||||
|
||||
static const struct flash_info catalyst_parts[] = {
|
||||
static const struct flash_info catalyst_nor_parts[] = {
|
||||
/* Catalyst / On Semiconductor -- non-JEDEC */
|
||||
{ "cat25c11", CAT25_INFO(16, 8, 16, 1) },
|
||||
{ "cat25c03", CAT25_INFO(32, 8, 16, 2) },
|
||||
@ -19,6 +19,6 @@ static const struct flash_info catalyst_parts[] = {
|
||||
|
||||
const struct spi_nor_manufacturer spi_nor_catalyst = {
|
||||
.name = "catalyst",
|
||||
.parts = catalyst_parts,
|
||||
.nparts = ARRAY_SIZE(catalyst_parts),
|
||||
.parts = catalyst_nor_parts,
|
||||
.nparts = ARRAY_SIZE(catalyst_nor_parts),
|
||||
};
|
||||
|
@ -769,6 +769,7 @@ static int aspeed_smc_setup_flash(struct aspeed_smc_controller *controller,
|
||||
struct device_node *child;
|
||||
unsigned int cs;
|
||||
int ret = -ENODEV;
|
||||
bool found_one = false;
|
||||
|
||||
for_each_available_child_of_node(np, child) {
|
||||
struct aspeed_smc_chip *chip;
|
||||
@ -827,8 +828,17 @@ static int aspeed_smc_setup_flash(struct aspeed_smc_controller *controller,
|
||||
* by of property.
|
||||
*/
|
||||
ret = spi_nor_scan(nor, NULL, &hwcaps);
|
||||
if (ret)
|
||||
break;
|
||||
/*
|
||||
* If we fail to scan the device it might not be present or
|
||||
* broken. Don't fail the whole controller if others work.
|
||||
*/
|
||||
if (ret) {
|
||||
if (found_one)
|
||||
ret = 0;
|
||||
|
||||
devm_kfree(controller->dev, chip);
|
||||
continue;
|
||||
}
|
||||
|
||||
ret = aspeed_smc_chip_setup_finish(chip);
|
||||
if (ret)
|
||||
@ -839,6 +849,7 @@ static int aspeed_smc_setup_flash(struct aspeed_smc_controller *controller,
|
||||
break;
|
||||
|
||||
controller->chips[cs] = chip;
|
||||
found_one = true;
|
||||
}
|
||||
|
||||
if (ret) {
|
||||
|
@ -157,8 +157,8 @@ static int spi_nor_spimem_exec_op(struct spi_nor *nor, struct spi_mem_op *op)
|
||||
return spi_mem_exec_op(nor->spimem, op);
|
||||
}
|
||||
|
||||
static int spi_nor_controller_ops_read_reg(struct spi_nor *nor, u8 opcode,
|
||||
u8 *buf, size_t len)
|
||||
int spi_nor_controller_ops_read_reg(struct spi_nor *nor, u8 opcode,
|
||||
u8 *buf, size_t len)
|
||||
{
|
||||
if (spi_nor_protocol_is_dtr(nor->reg_proto))
|
||||
return -EOPNOTSUPP;
|
||||
@ -166,8 +166,8 @@ static int spi_nor_controller_ops_read_reg(struct spi_nor *nor, u8 opcode,
|
||||
return nor->controller_ops->read_reg(nor, opcode, buf, len);
|
||||
}
|
||||
|
||||
static int spi_nor_controller_ops_write_reg(struct spi_nor *nor, u8 opcode,
|
||||
const u8 *buf, size_t len)
|
||||
int spi_nor_controller_ops_write_reg(struct spi_nor *nor, u8 opcode,
|
||||
const u8 *buf, size_t len)
|
||||
{
|
||||
if (spi_nor_protocol_is_dtr(nor->reg_proto))
|
||||
return -EOPNOTSUPP;
|
||||
@ -412,50 +412,6 @@ int spi_nor_read_sr(struct spi_nor *nor, u8 *sr)
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* spi_nor_read_fsr() - Read the Flag Status Register.
|
||||
* @nor: pointer to 'struct spi_nor'
|
||||
* @fsr: pointer to a DMA-able buffer where the value of the
|
||||
* Flag Status Register will be written. Should be at least 2
|
||||
* bytes.
|
||||
*
|
||||
* Return: 0 on success, -errno otherwise.
|
||||
*/
|
||||
static int spi_nor_read_fsr(struct spi_nor *nor, u8 *fsr)
|
||||
{
|
||||
int ret;
|
||||
|
||||
if (nor->spimem) {
|
||||
struct spi_mem_op op =
|
||||
SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_RDFSR, 0),
|
||||
SPI_MEM_OP_NO_ADDR,
|
||||
SPI_MEM_OP_NO_DUMMY,
|
||||
SPI_MEM_OP_DATA_IN(1, fsr, 0));
|
||||
|
||||
if (nor->reg_proto == SNOR_PROTO_8_8_8_DTR) {
|
||||
op.addr.nbytes = nor->params->rdsr_addr_nbytes;
|
||||
op.dummy.nbytes = nor->params->rdsr_dummy;
|
||||
/*
|
||||
* We don't want to read only one byte in DTR mode. So,
|
||||
* read 2 and then discard the second byte.
|
||||
*/
|
||||
op.data.nbytes = 2;
|
||||
}
|
||||
|
||||
spi_nor_spimem_setup_op(nor, &op, nor->reg_proto);
|
||||
|
||||
ret = spi_mem_exec_op(nor->spimem, &op);
|
||||
} else {
|
||||
ret = spi_nor_controller_ops_read_reg(nor, SPINOR_OP_RDFSR, fsr,
|
||||
1);
|
||||
}
|
||||
|
||||
if (ret)
|
||||
dev_dbg(nor->dev, "error %d reading FSR\n", ret);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* spi_nor_read_cr() - Read the Configuration Register using the
|
||||
* SPINOR_OP_RDCR (35h) command.
|
||||
@ -598,84 +554,6 @@ int spi_nor_write_ear(struct spi_nor *nor, u8 ear)
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* spi_nor_xread_sr() - Read the Status Register on S3AN flashes.
|
||||
* @nor: pointer to 'struct spi_nor'.
|
||||
* @sr: pointer to a DMA-able buffer where the value of the
|
||||
* Status Register will be written.
|
||||
*
|
||||
* Return: 0 on success, -errno otherwise.
|
||||
*/
|
||||
int spi_nor_xread_sr(struct spi_nor *nor, u8 *sr)
|
||||
{
|
||||
int ret;
|
||||
|
||||
if (nor->spimem) {
|
||||
struct spi_mem_op op =
|
||||
SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_XRDSR, 0),
|
||||
SPI_MEM_OP_NO_ADDR,
|
||||
SPI_MEM_OP_NO_DUMMY,
|
||||
SPI_MEM_OP_DATA_IN(1, sr, 0));
|
||||
|
||||
spi_nor_spimem_setup_op(nor, &op, nor->reg_proto);
|
||||
|
||||
ret = spi_mem_exec_op(nor->spimem, &op);
|
||||
} else {
|
||||
ret = spi_nor_controller_ops_read_reg(nor, SPINOR_OP_XRDSR, sr,
|
||||
1);
|
||||
}
|
||||
|
||||
if (ret)
|
||||
dev_dbg(nor->dev, "error %d reading XRDSR\n", ret);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* spi_nor_xsr_ready() - Query the Status Register of the S3AN flash to see if
|
||||
* the flash is ready for new commands.
|
||||
* @nor: pointer to 'struct spi_nor'.
|
||||
*
|
||||
* Return: 1 if ready, 0 if not ready, -errno on errors.
|
||||
*/
|
||||
static int spi_nor_xsr_ready(struct spi_nor *nor)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = spi_nor_xread_sr(nor, nor->bouncebuf);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return !!(nor->bouncebuf[0] & XSR_RDY);
|
||||
}
|
||||
|
||||
/**
|
||||
* spi_nor_clear_sr() - Clear the Status Register.
|
||||
* @nor: pointer to 'struct spi_nor'.
|
||||
*/
|
||||
static void spi_nor_clear_sr(struct spi_nor *nor)
|
||||
{
|
||||
int ret;
|
||||
|
||||
if (nor->spimem) {
|
||||
struct spi_mem_op op =
|
||||
SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_CLSR, 0),
|
||||
SPI_MEM_OP_NO_ADDR,
|
||||
SPI_MEM_OP_NO_DUMMY,
|
||||
SPI_MEM_OP_NO_DATA);
|
||||
|
||||
spi_nor_spimem_setup_op(nor, &op, nor->reg_proto);
|
||||
|
||||
ret = spi_mem_exec_op(nor->spimem, &op);
|
||||
} else {
|
||||
ret = spi_nor_controller_ops_write_reg(nor, SPINOR_OP_CLSR,
|
||||
NULL, 0);
|
||||
}
|
||||
|
||||
if (ret)
|
||||
dev_dbg(nor->dev, "error %d clearing SR\n", ret);
|
||||
}
|
||||
|
||||
/**
|
||||
* spi_nor_sr_ready() - Query the Status Register to see if the flash is ready
|
||||
* for new commands.
|
||||
@ -683,105 +561,15 @@ static void spi_nor_clear_sr(struct spi_nor *nor)
|
||||
*
|
||||
* Return: 1 if ready, 0 if not ready, -errno on errors.
|
||||
*/
|
||||
static int spi_nor_sr_ready(struct spi_nor *nor)
|
||||
{
|
||||
int ret = spi_nor_read_sr(nor, nor->bouncebuf);
|
||||
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (nor->flags & SNOR_F_USE_CLSR &&
|
||||
nor->bouncebuf[0] & (SR_E_ERR | SR_P_ERR)) {
|
||||
if (nor->bouncebuf[0] & SR_E_ERR)
|
||||
dev_err(nor->dev, "Erase Error occurred\n");
|
||||
else
|
||||
dev_err(nor->dev, "Programming Error occurred\n");
|
||||
|
||||
spi_nor_clear_sr(nor);
|
||||
|
||||
/*
|
||||
* WEL bit remains set to one when an erase or page program
|
||||
* error occurs. Issue a Write Disable command to protect
|
||||
* against inadvertent writes that can possibly corrupt the
|
||||
* contents of the memory.
|
||||
*/
|
||||
ret = spi_nor_write_disable(nor);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
return !(nor->bouncebuf[0] & SR_WIP);
|
||||
}
|
||||
|
||||
/**
|
||||
* spi_nor_clear_fsr() - Clear the Flag Status Register.
|
||||
* @nor: pointer to 'struct spi_nor'.
|
||||
*/
|
||||
static void spi_nor_clear_fsr(struct spi_nor *nor)
|
||||
int spi_nor_sr_ready(struct spi_nor *nor)
|
||||
{
|
||||
int ret;
|
||||
|
||||
if (nor->spimem) {
|
||||
struct spi_mem_op op =
|
||||
SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_CLFSR, 0),
|
||||
SPI_MEM_OP_NO_ADDR,
|
||||
SPI_MEM_OP_NO_DUMMY,
|
||||
SPI_MEM_OP_NO_DATA);
|
||||
|
||||
spi_nor_spimem_setup_op(nor, &op, nor->reg_proto);
|
||||
|
||||
ret = spi_mem_exec_op(nor->spimem, &op);
|
||||
} else {
|
||||
ret = spi_nor_controller_ops_write_reg(nor, SPINOR_OP_CLFSR,
|
||||
NULL, 0);
|
||||
}
|
||||
|
||||
if (ret)
|
||||
dev_dbg(nor->dev, "error %d clearing FSR\n", ret);
|
||||
}
|
||||
|
||||
/**
|
||||
* spi_nor_fsr_ready() - Query the Flag Status Register to see if the flash is
|
||||
* ready for new commands.
|
||||
* @nor: pointer to 'struct spi_nor'.
|
||||
*
|
||||
* Return: 1 if ready, 0 if not ready, -errno on errors.
|
||||
*/
|
||||
static int spi_nor_fsr_ready(struct spi_nor *nor)
|
||||
{
|
||||
int ret = spi_nor_read_fsr(nor, nor->bouncebuf);
|
||||
|
||||
ret = spi_nor_read_sr(nor, nor->bouncebuf);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (nor->bouncebuf[0] & (FSR_E_ERR | FSR_P_ERR)) {
|
||||
if (nor->bouncebuf[0] & FSR_E_ERR)
|
||||
dev_err(nor->dev, "Erase operation failed.\n");
|
||||
else
|
||||
dev_err(nor->dev, "Program operation failed.\n");
|
||||
|
||||
if (nor->bouncebuf[0] & FSR_PT_ERR)
|
||||
dev_err(nor->dev,
|
||||
"Attempted to modify a protected sector.\n");
|
||||
|
||||
spi_nor_clear_fsr(nor);
|
||||
|
||||
/*
|
||||
* WEL bit remains set to one when an erase or page program
|
||||
* error occurs. Issue a Write Disable command to protect
|
||||
* against inadvertent writes that can possibly corrupt the
|
||||
* contents of the memory.
|
||||
*/
|
||||
ret = spi_nor_write_disable(nor);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
return !!(nor->bouncebuf[0] & FSR_READY);
|
||||
return !(nor->bouncebuf[0] & SR_WIP);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -792,18 +580,11 @@ static int spi_nor_fsr_ready(struct spi_nor *nor)
|
||||
*/
|
||||
static int spi_nor_ready(struct spi_nor *nor)
|
||||
{
|
||||
int sr, fsr;
|
||||
/* Flashes might override the standard routine. */
|
||||
if (nor->params->ready)
|
||||
return nor->params->ready(nor);
|
||||
|
||||
if (nor->flags & SNOR_F_READY_XSR_RDY)
|
||||
sr = spi_nor_xsr_ready(nor);
|
||||
else
|
||||
sr = spi_nor_sr_ready(nor);
|
||||
if (sr < 0)
|
||||
return sr;
|
||||
fsr = nor->flags & SNOR_F_USE_FSR ? spi_nor_fsr_ready(nor) : 1;
|
||||
if (fsr < 0)
|
||||
return fsr;
|
||||
return sr && fsr;
|
||||
return spi_nor_sr_ready(nor);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -2532,11 +2313,12 @@ static int spi_nor_setup(struct spi_nor *nor,
|
||||
{
|
||||
int ret;
|
||||
|
||||
if (nor->params->setup) {
|
||||
if (nor->params->setup)
|
||||
ret = nor->params->setup(nor, hwcaps);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
else
|
||||
ret = spi_nor_default_setup(nor, hwcaps);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return spi_nor_set_addr_width(nor);
|
||||
}
|
||||
@ -2666,20 +2448,6 @@ static void spi_nor_init_flags(struct spi_nor *nor)
|
||||
|
||||
if (flags & NO_CHIP_ERASE)
|
||||
nor->flags |= SNOR_F_NO_OP_CHIP_ERASE;
|
||||
|
||||
if (flags & USE_CLSR)
|
||||
nor->flags |= SNOR_F_USE_CLSR;
|
||||
|
||||
if (flags & USE_FSR)
|
||||
nor->flags |= SNOR_F_USE_FSR;
|
||||
|
||||
/*
|
||||
* Make sure the XSR_RDY flag is set before calling
|
||||
* spi_nor_wait_till_ready(). Xilinx S3AN share MFR
|
||||
* with Atmel SPI NOR.
|
||||
*/
|
||||
if (flags & SPI_NOR_XSR_RDY)
|
||||
nor->flags |= SNOR_F_READY_XSR_RDY;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -2786,7 +2554,6 @@ static void spi_nor_init_default_params(struct spi_nor *nor)
|
||||
|
||||
params->quad_enable = spi_nor_sr2_bit1_quad_enable;
|
||||
params->set_4byte_addr_mode = spansion_set_4byte_addr_mode;
|
||||
params->setup = spi_nor_default_setup;
|
||||
params->otp.org = &info->otp_org;
|
||||
|
||||
/* Default to 16-bit Write Status (01h) Command */
|
||||
@ -3181,10 +2948,11 @@ static void spi_nor_set_mtd_info(struct spi_nor *nor)
|
||||
mtd->flags = MTD_CAP_NORFLASH;
|
||||
if (nor->info->flags & SPI_NOR_NO_ERASE)
|
||||
mtd->flags |= MTD_NO_ERASE;
|
||||
else
|
||||
mtd->_erase = spi_nor_erase;
|
||||
mtd->writesize = nor->params->writesize;
|
||||
mtd->writebufsize = nor->params->page_size;
|
||||
mtd->size = nor->params->size;
|
||||
mtd->_erase = spi_nor_erase;
|
||||
mtd->_read = spi_nor_read;
|
||||
/* Might be already set by some SST flashes. */
|
||||
if (!mtd->_write)
|
||||
|
@ -12,23 +12,20 @@
|
||||
#define SPI_NOR_MAX_ID_LEN 6
|
||||
|
||||
enum spi_nor_option_flags {
|
||||
SNOR_F_USE_FSR = BIT(0),
|
||||
SNOR_F_HAS_SR_TB = BIT(1),
|
||||
SNOR_F_NO_OP_CHIP_ERASE = BIT(2),
|
||||
SNOR_F_READY_XSR_RDY = BIT(3),
|
||||
SNOR_F_USE_CLSR = BIT(4),
|
||||
SNOR_F_BROKEN_RESET = BIT(5),
|
||||
SNOR_F_4B_OPCODES = BIT(6),
|
||||
SNOR_F_HAS_4BAIT = BIT(7),
|
||||
SNOR_F_HAS_LOCK = BIT(8),
|
||||
SNOR_F_HAS_16BIT_SR = BIT(9),
|
||||
SNOR_F_NO_READ_CR = BIT(10),
|
||||
SNOR_F_HAS_SR_TB_BIT6 = BIT(11),
|
||||
SNOR_F_HAS_4BIT_BP = BIT(12),
|
||||
SNOR_F_HAS_SR_BP3_BIT6 = BIT(13),
|
||||
SNOR_F_IO_MODE_EN_VOLATILE = BIT(14),
|
||||
SNOR_F_SOFT_RESET = BIT(15),
|
||||
SNOR_F_SWP_IS_VOLATILE = BIT(16),
|
||||
SNOR_F_HAS_SR_TB = BIT(0),
|
||||
SNOR_F_NO_OP_CHIP_ERASE = BIT(1),
|
||||
SNOR_F_BROKEN_RESET = BIT(2),
|
||||
SNOR_F_4B_OPCODES = BIT(3),
|
||||
SNOR_F_HAS_4BAIT = BIT(4),
|
||||
SNOR_F_HAS_LOCK = BIT(5),
|
||||
SNOR_F_HAS_16BIT_SR = BIT(6),
|
||||
SNOR_F_NO_READ_CR = BIT(7),
|
||||
SNOR_F_HAS_SR_TB_BIT6 = BIT(8),
|
||||
SNOR_F_HAS_4BIT_BP = BIT(9),
|
||||
SNOR_F_HAS_SR_BP3_BIT6 = BIT(10),
|
||||
SNOR_F_IO_MODE_EN_VOLATILE = BIT(11),
|
||||
SNOR_F_SOFT_RESET = BIT(12),
|
||||
SNOR_F_SWP_IS_VOLATILE = BIT(13),
|
||||
};
|
||||
|
||||
struct spi_nor_read_command {
|
||||
@ -257,10 +254,13 @@ struct spi_nor_otp {
|
||||
* @convert_addr: converts an absolute address into something the flash
|
||||
* will understand. Particularly useful when pagesize is
|
||||
* not a power-of-2.
|
||||
* @setup: configures the SPI NOR memory. Useful for SPI NOR
|
||||
* flashes that have peculiarities to the SPI NOR standard
|
||||
* e.g. different opcodes, specific address calculation,
|
||||
* page size, etc.
|
||||
* @setup: (optional) configures the SPI NOR memory. Useful for
|
||||
* SPI NOR flashes that have peculiarities to the SPI NOR
|
||||
* standard e.g. different opcodes, specific address
|
||||
* calculation, page size, etc.
|
||||
* @ready: (optional) flashes might use a different mechanism
|
||||
* than reading the status register to indicate they
|
||||
* are ready for a new command
|
||||
* @locking_ops: SPI NOR locking methods.
|
||||
*/
|
||||
struct spi_nor_flash_parameter {
|
||||
@ -282,6 +282,7 @@ struct spi_nor_flash_parameter {
|
||||
int (*set_4byte_addr_mode)(struct spi_nor *nor, bool enable);
|
||||
u32 (*convert_addr)(struct spi_nor *nor, u32 addr);
|
||||
int (*setup)(struct spi_nor *nor, const struct spi_nor_hwcaps *hwcaps);
|
||||
int (*ready)(struct spi_nor *nor);
|
||||
|
||||
const struct spi_nor_locking_ops *locking_ops;
|
||||
};
|
||||
@ -345,10 +346,6 @@ struct spi_nor_fixups {
|
||||
* SPI_NOR_NO_ERASE: no erase command needed.
|
||||
* NO_CHIP_ERASE: chip does not support chip erase.
|
||||
* SPI_NOR_NO_FR: can't do fastread.
|
||||
* USE_CLSR: use CLSR command.
|
||||
* USE_FSR: use flag status register
|
||||
* SPI_NOR_XSR_RDY: S3AN flashes have specific opcode to read the
|
||||
* status register.
|
||||
*
|
||||
* @no_sfdp_flags: flags that indicate support that can be discovered via SFDP.
|
||||
* Used when SFDP tables are not defined in the flash. These
|
||||
@ -399,9 +396,6 @@ struct flash_info {
|
||||
#define SPI_NOR_NO_ERASE BIT(6)
|
||||
#define NO_CHIP_ERASE BIT(7)
|
||||
#define SPI_NOR_NO_FR BIT(8)
|
||||
#define USE_CLSR BIT(9)
|
||||
#define USE_FSR BIT(10)
|
||||
#define SPI_NOR_XSR_RDY BIT(11)
|
||||
|
||||
u8 no_sfdp_flags;
|
||||
#define SPI_NOR_SKIP_SFDP BIT(0)
|
||||
@ -458,19 +452,6 @@ struct flash_info {
|
||||
.addr_width = (_addr_width), \
|
||||
.flags = SPI_NOR_NO_ERASE | SPI_NOR_NO_FR, \
|
||||
|
||||
#define S3AN_INFO(_jedec_id, _n_sectors, _page_size) \
|
||||
.id = { \
|
||||
((_jedec_id) >> 16) & 0xff, \
|
||||
((_jedec_id) >> 8) & 0xff, \
|
||||
(_jedec_id) & 0xff \
|
||||
}, \
|
||||
.id_len = 3, \
|
||||
.sector_size = (8*_page_size), \
|
||||
.n_sectors = (_n_sectors), \
|
||||
.page_size = _page_size, \
|
||||
.addr_width = 3, \
|
||||
.flags = SPI_NOR_NO_FR | SPI_NOR_XSR_RDY,
|
||||
|
||||
#define OTP_INFO(_len, _n_regions, _base, _offset) \
|
||||
.otp_org = { \
|
||||
.len = (_len), \
|
||||
@ -554,12 +535,12 @@ int spi_nor_sr1_bit6_quad_enable(struct spi_nor *nor);
|
||||
int spi_nor_sr2_bit1_quad_enable(struct spi_nor *nor);
|
||||
int spi_nor_sr2_bit7_quad_enable(struct spi_nor *nor);
|
||||
int spi_nor_read_sr(struct spi_nor *nor, u8 *sr);
|
||||
int spi_nor_sr_ready(struct spi_nor *nor);
|
||||
int spi_nor_read_cr(struct spi_nor *nor, u8 *cr);
|
||||
int spi_nor_write_sr(struct spi_nor *nor, const u8 *sr, size_t len);
|
||||
int spi_nor_write_sr_and_check(struct spi_nor *nor, u8 sr1);
|
||||
int spi_nor_write_16bit_cr_and_check(struct spi_nor *nor, u8 cr);
|
||||
|
||||
int spi_nor_xread_sr(struct spi_nor *nor, u8 *sr);
|
||||
ssize_t spi_nor_read_data(struct spi_nor *nor, loff_t from, size_t len,
|
||||
u8 *buf);
|
||||
ssize_t spi_nor_write_data(struct spi_nor *nor, loff_t to, size_t len,
|
||||
@ -599,6 +580,11 @@ void spi_nor_try_unlock_all(struct spi_nor *nor);
|
||||
void spi_nor_set_mtd_locking_ops(struct spi_nor *nor);
|
||||
void spi_nor_set_mtd_otp_ops(struct spi_nor *nor);
|
||||
|
||||
int spi_nor_controller_ops_read_reg(struct spi_nor *nor, u8 opcode,
|
||||
u8 *buf, size_t len);
|
||||
int spi_nor_controller_ops_write_reg(struct spi_nor *nor, u8 opcode,
|
||||
const u8 *buf, size_t len);
|
||||
|
||||
static inline struct spi_nor *mtd_to_spi_nor(struct mtd_info *mtd)
|
||||
{
|
||||
return container_of(mtd, struct spi_nor, mtd);
|
||||
|
@ -8,7 +8,7 @@
|
||||
|
||||
#include "core.h"
|
||||
|
||||
static const struct flash_info eon_parts[] = {
|
||||
static const struct flash_info eon_nor_parts[] = {
|
||||
/* EON -- en25xxx */
|
||||
{ "en25f32", INFO(0x1c3116, 0, 64 * 1024, 64)
|
||||
NO_SFDP_FLAGS(SECT_4K) },
|
||||
@ -32,6 +32,6 @@ static const struct flash_info eon_parts[] = {
|
||||
|
||||
const struct spi_nor_manufacturer spi_nor_eon = {
|
||||
.name = "eon",
|
||||
.parts = eon_parts,
|
||||
.nparts = ARRAY_SIZE(eon_parts),
|
||||
.parts = eon_nor_parts,
|
||||
.nparts = ARRAY_SIZE(eon_nor_parts),
|
||||
};
|
||||
|
@ -8,7 +8,7 @@
|
||||
|
||||
#include "core.h"
|
||||
|
||||
static const struct flash_info esmt_parts[] = {
|
||||
static const struct flash_info esmt_nor_parts[] = {
|
||||
/* ESMT */
|
||||
{ "f25l32pa", INFO(0x8c2016, 0, 64 * 1024, 64)
|
||||
FLAGS(SPI_NOR_HAS_LOCK | SPI_NOR_SWP_IS_VOLATILE)
|
||||
@ -23,6 +23,6 @@ static const struct flash_info esmt_parts[] = {
|
||||
|
||||
const struct spi_nor_manufacturer spi_nor_esmt = {
|
||||
.name = "esmt",
|
||||
.parts = esmt_parts,
|
||||
.nparts = ARRAY_SIZE(esmt_parts),
|
||||
.parts = esmt_nor_parts,
|
||||
.nparts = ARRAY_SIZE(esmt_nor_parts),
|
||||
};
|
||||
|
@ -8,7 +8,7 @@
|
||||
|
||||
#include "core.h"
|
||||
|
||||
static const struct flash_info everspin_parts[] = {
|
||||
static const struct flash_info everspin_nor_parts[] = {
|
||||
/* Everspin */
|
||||
{ "mr25h128", CAT25_INFO(16 * 1024, 1, 256, 2) },
|
||||
{ "mr25h256", CAT25_INFO(32 * 1024, 1, 256, 2) },
|
||||
@ -18,6 +18,6 @@ static const struct flash_info everspin_parts[] = {
|
||||
|
||||
const struct spi_nor_manufacturer spi_nor_everspin = {
|
||||
.name = "everspin",
|
||||
.parts = everspin_parts,
|
||||
.nparts = ARRAY_SIZE(everspin_parts),
|
||||
.parts = everspin_nor_parts,
|
||||
.nparts = ARRAY_SIZE(everspin_nor_parts),
|
||||
};
|
||||
|
@ -8,7 +8,7 @@
|
||||
|
||||
#include "core.h"
|
||||
|
||||
static const struct flash_info fujitsu_parts[] = {
|
||||
static const struct flash_info fujitsu_nor_parts[] = {
|
||||
/* Fujitsu */
|
||||
{ "mb85rs1mt", INFO(0x047f27, 0, 128 * 1024, 1)
|
||||
FLAGS(SPI_NOR_NO_ERASE) },
|
||||
@ -16,6 +16,6 @@ static const struct flash_info fujitsu_parts[] = {
|
||||
|
||||
const struct spi_nor_manufacturer spi_nor_fujitsu = {
|
||||
.name = "fujitsu",
|
||||
.parts = fujitsu_parts,
|
||||
.nparts = ARRAY_SIZE(fujitsu_parts),
|
||||
.parts = fujitsu_nor_parts,
|
||||
.nparts = ARRAY_SIZE(fujitsu_nor_parts),
|
||||
};
|
||||
|
@ -23,7 +23,7 @@ static const struct spi_nor_fixups gd25q256_fixups = {
|
||||
.default_init = gd25q256_default_init,
|
||||
};
|
||||
|
||||
static const struct flash_info gigadevice_parts[] = {
|
||||
static const struct flash_info gigadevice_nor_parts[] = {
|
||||
{ "gd25q16", INFO(0xc84015, 0, 64 * 1024, 32)
|
||||
FLAGS(SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB)
|
||||
NO_SFDP_FLAGS(SECT_4K | SPI_NOR_DUAL_READ |
|
||||
@ -61,6 +61,6 @@ static const struct flash_info gigadevice_parts[] = {
|
||||
|
||||
const struct spi_nor_manufacturer spi_nor_gigadevice = {
|
||||
.name = "gigadevice",
|
||||
.parts = gigadevice_parts,
|
||||
.nparts = ARRAY_SIZE(gigadevice_parts),
|
||||
.parts = gigadevice_nor_parts,
|
||||
.nparts = ARRAY_SIZE(gigadevice_nor_parts),
|
||||
};
|
||||
|
@ -8,7 +8,7 @@
|
||||
|
||||
#include "core.h"
|
||||
|
||||
static const struct flash_info intel_parts[] = {
|
||||
static const struct flash_info intel_nor_parts[] = {
|
||||
/* Intel/Numonyx -- xxxs33b */
|
||||
{ "160s33b", INFO(0x898911, 0, 64 * 1024, 32)
|
||||
FLAGS(SPI_NOR_HAS_LOCK | SPI_NOR_SWP_IS_VOLATILE) },
|
||||
@ -20,6 +20,6 @@ static const struct flash_info intel_parts[] = {
|
||||
|
||||
const struct spi_nor_manufacturer spi_nor_intel = {
|
||||
.name = "intel",
|
||||
.parts = intel_parts,
|
||||
.nparts = ARRAY_SIZE(intel_parts),
|
||||
.parts = intel_nor_parts,
|
||||
.nparts = ARRAY_SIZE(intel_nor_parts),
|
||||
};
|
||||
|
@ -29,7 +29,7 @@ static const struct spi_nor_fixups is25lp256_fixups = {
|
||||
.post_bfpt = is25lp256_post_bfpt_fixups,
|
||||
};
|
||||
|
||||
static const struct flash_info issi_parts[] = {
|
||||
static const struct flash_info issi_nor_parts[] = {
|
||||
/* ISSI */
|
||||
{ "is25cd512", INFO(0x7f9d20, 0, 32 * 1024, 2)
|
||||
NO_SFDP_FLAGS(SECT_4K) },
|
||||
@ -69,18 +69,18 @@ static const struct flash_info issi_parts[] = {
|
||||
NO_SFDP_FLAGS(SECT_4K) },
|
||||
};
|
||||
|
||||
static void issi_default_init(struct spi_nor *nor)
|
||||
static void issi_nor_default_init(struct spi_nor *nor)
|
||||
{
|
||||
nor->params->quad_enable = spi_nor_sr1_bit6_quad_enable;
|
||||
}
|
||||
|
||||
static const struct spi_nor_fixups issi_fixups = {
|
||||
.default_init = issi_default_init,
|
||||
.default_init = issi_nor_default_init,
|
||||
};
|
||||
|
||||
const struct spi_nor_manufacturer spi_nor_issi = {
|
||||
.name = "issi",
|
||||
.parts = issi_parts,
|
||||
.nparts = ARRAY_SIZE(issi_parts),
|
||||
.parts = issi_nor_parts,
|
||||
.nparts = ARRAY_SIZE(issi_nor_parts),
|
||||
.fixups = &issi_fixups,
|
||||
};
|
||||
|
@ -32,7 +32,7 @@ static const struct spi_nor_fixups mx25l25635_fixups = {
|
||||
.post_bfpt = mx25l25635_post_bfpt_fixups,
|
||||
};
|
||||
|
||||
static const struct flash_info macronix_parts[] = {
|
||||
static const struct flash_info macronix_nor_parts[] = {
|
||||
/* Macronix */
|
||||
{ "mx25l512e", INFO(0xc22010, 0, 64 * 1024, 1)
|
||||
NO_SFDP_FLAGS(SECT_4K) },
|
||||
@ -102,19 +102,19 @@ static const struct flash_info macronix_parts[] = {
|
||||
FIXUP_FLAGS(SPI_NOR_4B_OPCODES) },
|
||||
};
|
||||
|
||||
static void macronix_default_init(struct spi_nor *nor)
|
||||
static void macronix_nor_default_init(struct spi_nor *nor)
|
||||
{
|
||||
nor->params->quad_enable = spi_nor_sr1_bit6_quad_enable;
|
||||
nor->params->set_4byte_addr_mode = spi_nor_set_4byte_addr_mode;
|
||||
}
|
||||
|
||||
static const struct spi_nor_fixups macronix_fixups = {
|
||||
.default_init = macronix_default_init,
|
||||
static const struct spi_nor_fixups macronix_nor_fixups = {
|
||||
.default_init = macronix_nor_default_init,
|
||||
};
|
||||
|
||||
const struct spi_nor_manufacturer spi_nor_macronix = {
|
||||
.name = "macronix",
|
||||
.parts = macronix_parts,
|
||||
.nparts = ARRAY_SIZE(macronix_parts),
|
||||
.fixups = ¯onix_fixups,
|
||||
.parts = macronix_nor_parts,
|
||||
.nparts = ARRAY_SIZE(macronix_nor_parts),
|
||||
.fixups = ¯onix_nor_fixups,
|
||||
};
|
||||
|
@ -8,6 +8,11 @@
|
||||
|
||||
#include "core.h"
|
||||
|
||||
/* flash_info mfr_flag. Used to read proprietary FSR register. */
|
||||
#define USE_FSR BIT(0)
|
||||
|
||||
#define SPINOR_OP_RDFSR 0x70 /* Read flag status register */
|
||||
#define SPINOR_OP_CLFSR 0x50 /* Clear flag status register */
|
||||
#define SPINOR_OP_MT_DTR_RD 0xfd /* Fast Read opcode in DTR mode */
|
||||
#define SPINOR_OP_MT_RD_ANY_REG 0x85 /* Read volatile register */
|
||||
#define SPINOR_OP_MT_WR_ANY_REG 0x81 /* Write volatile register */
|
||||
@ -17,7 +22,13 @@
|
||||
#define SPINOR_MT_OCT_DTR 0xe7 /* Enable Octal DTR. */
|
||||
#define SPINOR_MT_EXSPI 0xff /* Enable Extended SPI (default) */
|
||||
|
||||
static int spi_nor_micron_octal_dtr_enable(struct spi_nor *nor, bool enable)
|
||||
/* Flag Status Register bits */
|
||||
#define FSR_READY BIT(7) /* Device status, 0 = Busy, 1 = Ready */
|
||||
#define FSR_E_ERR BIT(5) /* Erase operation status */
|
||||
#define FSR_P_ERR BIT(4) /* Program operation status */
|
||||
#define FSR_PT_ERR BIT(1) /* Protection error bit */
|
||||
|
||||
static int micron_st_nor_octal_dtr_enable(struct spi_nor *nor, bool enable)
|
||||
{
|
||||
struct spi_mem_op op;
|
||||
u8 *buf = nor->bouncebuf;
|
||||
@ -102,7 +113,7 @@ static int spi_nor_micron_octal_dtr_enable(struct spi_nor *nor, bool enable)
|
||||
|
||||
static void mt35xu512aba_default_init(struct spi_nor *nor)
|
||||
{
|
||||
nor->params->octal_dtr_enable = spi_nor_micron_octal_dtr_enable;
|
||||
nor->params->octal_dtr_enable = micron_st_nor_octal_dtr_enable;
|
||||
}
|
||||
|
||||
static void mt35xu512aba_post_sfdp_fixup(struct spi_nor *nor)
|
||||
@ -130,20 +141,22 @@ static const struct spi_nor_fixups mt35xu512aba_fixups = {
|
||||
.post_sfdp = mt35xu512aba_post_sfdp_fixup,
|
||||
};
|
||||
|
||||
static const struct flash_info micron_parts[] = {
|
||||
static const struct flash_info micron_nor_parts[] = {
|
||||
{ "mt35xu512aba", INFO(0x2c5b1a, 0, 128 * 1024, 512)
|
||||
FLAGS(USE_FSR)
|
||||
NO_SFDP_FLAGS(SECT_4K | SPI_NOR_OCTAL_READ |
|
||||
SPI_NOR_OCTAL_DTR_READ | SPI_NOR_OCTAL_DTR_PP)
|
||||
FIXUP_FLAGS(SPI_NOR_4B_OPCODES | SPI_NOR_IO_MODE_EN_VOLATILE)
|
||||
.fixups = &mt35xu512aba_fixups},
|
||||
MFR_FLAGS(USE_FSR)
|
||||
.fixups = &mt35xu512aba_fixups
|
||||
},
|
||||
{ "mt35xu02g", INFO(0x2c5b1c, 0, 128 * 1024, 2048)
|
||||
FLAGS(USE_FSR)
|
||||
NO_SFDP_FLAGS(SECT_4K | SPI_NOR_OCTAL_READ)
|
||||
FIXUP_FLAGS(SPI_NOR_4B_OPCODES) },
|
||||
FIXUP_FLAGS(SPI_NOR_4B_OPCODES)
|
||||
MFR_FLAGS(USE_FSR)
|
||||
},
|
||||
};
|
||||
|
||||
static const struct flash_info st_parts[] = {
|
||||
static const struct flash_info st_nor_parts[] = {
|
||||
{ "n25q016a", INFO(0x20bb15, 0, 64 * 1024, 32)
|
||||
NO_SFDP_FLAGS(SECT_4K | SPI_NOR_QUAD_READ) },
|
||||
{ "n25q032", INFO(0x20ba16, 0, 64 * 1024, 64)
|
||||
@ -156,57 +169,79 @@ static const struct flash_info st_parts[] = {
|
||||
NO_SFDP_FLAGS(SECT_4K | SPI_NOR_QUAD_READ) },
|
||||
{ "n25q128a11", INFO(0x20bb18, 0, 64 * 1024, 256)
|
||||
FLAGS(SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_4BIT_BP |
|
||||
SPI_NOR_BP3_SR_BIT6 | USE_FSR)
|
||||
NO_SFDP_FLAGS(SECT_4K | SPI_NOR_QUAD_READ) },
|
||||
SPI_NOR_BP3_SR_BIT6)
|
||||
NO_SFDP_FLAGS(SECT_4K | SPI_NOR_QUAD_READ)
|
||||
MFR_FLAGS(USE_FSR)
|
||||
},
|
||||
{ "n25q128a13", INFO(0x20ba18, 0, 64 * 1024, 256)
|
||||
FLAGS(SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_4BIT_BP |
|
||||
SPI_NOR_BP3_SR_BIT6 | USE_FSR)
|
||||
NO_SFDP_FLAGS(SECT_4K | SPI_NOR_QUAD_READ) },
|
||||
SPI_NOR_BP3_SR_BIT6)
|
||||
NO_SFDP_FLAGS(SECT_4K | SPI_NOR_QUAD_READ)
|
||||
MFR_FLAGS(USE_FSR)
|
||||
},
|
||||
{ "mt25ql256a", INFO6(0x20ba19, 0x104400, 64 * 1024, 512)
|
||||
FLAGS(USE_FSR)
|
||||
NO_SFDP_FLAGS(SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ)
|
||||
FIXUP_FLAGS(SPI_NOR_4B_OPCODES) },
|
||||
FIXUP_FLAGS(SPI_NOR_4B_OPCODES)
|
||||
MFR_FLAGS(USE_FSR)
|
||||
},
|
||||
{ "n25q256a", INFO(0x20ba19, 0, 64 * 1024, 512)
|
||||
FLAGS(USE_FSR)
|
||||
NO_SFDP_FLAGS(SECT_4K | SPI_NOR_DUAL_READ |
|
||||
SPI_NOR_QUAD_READ) },
|
||||
SPI_NOR_QUAD_READ)
|
||||
MFR_FLAGS(USE_FSR)
|
||||
},
|
||||
{ "mt25qu256a", INFO6(0x20bb19, 0x104400, 64 * 1024, 512)
|
||||
FLAGS(USE_FSR)
|
||||
NO_SFDP_FLAGS(SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ)
|
||||
FIXUP_FLAGS(SPI_NOR_4B_OPCODES) },
|
||||
FIXUP_FLAGS(SPI_NOR_4B_OPCODES)
|
||||
MFR_FLAGS(USE_FSR)
|
||||
},
|
||||
{ "n25q256ax1", INFO(0x20bb19, 0, 64 * 1024, 512)
|
||||
FLAGS(USE_FSR)
|
||||
NO_SFDP_FLAGS(SECT_4K | SPI_NOR_QUAD_READ) },
|
||||
NO_SFDP_FLAGS(SECT_4K | SPI_NOR_QUAD_READ)
|
||||
MFR_FLAGS(USE_FSR)
|
||||
},
|
||||
{ "mt25ql512a", INFO6(0x20ba20, 0x104400, 64 * 1024, 1024)
|
||||
FLAGS(USE_FSR)
|
||||
NO_SFDP_FLAGS(SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ)
|
||||
FIXUP_FLAGS(SPI_NOR_4B_OPCODES) },
|
||||
FIXUP_FLAGS(SPI_NOR_4B_OPCODES)
|
||||
MFR_FLAGS(USE_FSR)
|
||||
},
|
||||
{ "n25q512ax3", INFO(0x20ba20, 0, 64 * 1024, 1024)
|
||||
FLAGS(SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_4BIT_BP |
|
||||
SPI_NOR_BP3_SR_BIT6 | USE_FSR)
|
||||
NO_SFDP_FLAGS(SECT_4K | SPI_NOR_QUAD_READ) },
|
||||
SPI_NOR_BP3_SR_BIT6)
|
||||
NO_SFDP_FLAGS(SECT_4K | SPI_NOR_QUAD_READ)
|
||||
MFR_FLAGS(USE_FSR)
|
||||
},
|
||||
{ "mt25qu512a", INFO6(0x20bb20, 0x104400, 64 * 1024, 1024)
|
||||
FLAGS(USE_FSR)
|
||||
NO_SFDP_FLAGS(SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ)
|
||||
FIXUP_FLAGS(SPI_NOR_4B_OPCODES) },
|
||||
FIXUP_FLAGS(SPI_NOR_4B_OPCODES)
|
||||
MFR_FLAGS(USE_FSR)
|
||||
},
|
||||
{ "n25q512a", INFO(0x20bb20, 0, 64 * 1024, 1024)
|
||||
FLAGS(SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_4BIT_BP |
|
||||
SPI_NOR_BP3_SR_BIT6 | USE_FSR)
|
||||
NO_SFDP_FLAGS(SECT_4K | SPI_NOR_QUAD_READ) },
|
||||
SPI_NOR_BP3_SR_BIT6)
|
||||
NO_SFDP_FLAGS(SECT_4K | SPI_NOR_QUAD_READ)
|
||||
MFR_FLAGS(USE_FSR)
|
||||
},
|
||||
{ "n25q00", INFO(0x20ba21, 0, 64 * 1024, 2048)
|
||||
FLAGS(SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_4BIT_BP |
|
||||
SPI_NOR_BP3_SR_BIT6 | NO_CHIP_ERASE | USE_FSR)
|
||||
NO_SFDP_FLAGS(SECT_4K | SPI_NOR_QUAD_READ) },
|
||||
SPI_NOR_BP3_SR_BIT6 | NO_CHIP_ERASE)
|
||||
NO_SFDP_FLAGS(SECT_4K | SPI_NOR_QUAD_READ)
|
||||
MFR_FLAGS(USE_FSR)
|
||||
},
|
||||
{ "n25q00a", INFO(0x20bb21, 0, 64 * 1024, 2048)
|
||||
FLAGS(NO_CHIP_ERASE | USE_FSR)
|
||||
NO_SFDP_FLAGS(SECT_4K | SPI_NOR_QUAD_READ) },
|
||||
FLAGS(NO_CHIP_ERASE)
|
||||
NO_SFDP_FLAGS(SECT_4K | SPI_NOR_QUAD_READ)
|
||||
MFR_FLAGS(USE_FSR)
|
||||
},
|
||||
{ "mt25ql02g", INFO(0x20ba22, 0, 64 * 1024, 4096)
|
||||
FLAGS(NO_CHIP_ERASE | USE_FSR)
|
||||
NO_SFDP_FLAGS(SECT_4K | SPI_NOR_QUAD_READ) },
|
||||
FLAGS(NO_CHIP_ERASE)
|
||||
NO_SFDP_FLAGS(SECT_4K | SPI_NOR_QUAD_READ)
|
||||
MFR_FLAGS(USE_FSR)
|
||||
},
|
||||
{ "mt25qu02g", INFO(0x20bb22, 0, 64 * 1024, 4096)
|
||||
FLAGS(NO_CHIP_ERASE | USE_FSR)
|
||||
FLAGS(NO_CHIP_ERASE)
|
||||
NO_SFDP_FLAGS(SECT_4K | SPI_NOR_DUAL_READ |
|
||||
SPI_NOR_QUAD_READ) },
|
||||
SPI_NOR_QUAD_READ)
|
||||
MFR_FLAGS(USE_FSR)
|
||||
},
|
||||
|
||||
{ "m25p05", INFO(0x202010, 0, 32 * 1024, 2) },
|
||||
{ "m25p10", INFO(0x202011, 0, 32 * 1024, 4) },
|
||||
@ -250,15 +285,15 @@ static const struct flash_info st_parts[] = {
|
||||
};
|
||||
|
||||
/**
|
||||
* st_micron_set_4byte_addr_mode() - Set 4-byte address mode for ST and Micron
|
||||
* flashes.
|
||||
* micron_st_nor_set_4byte_addr_mode() - Set 4-byte address mode for ST and
|
||||
* Micron flashes.
|
||||
* @nor: pointer to 'struct spi_nor'.
|
||||
* @enable: true to enter the 4-byte address mode, false to exit the 4-byte
|
||||
* address mode.
|
||||
*
|
||||
* Return: 0 on success, -errno otherwise.
|
||||
*/
|
||||
static int st_micron_set_4byte_addr_mode(struct spi_nor *nor, bool enable)
|
||||
static int micron_st_nor_set_4byte_addr_mode(struct spi_nor *nor, bool enable)
|
||||
{
|
||||
int ret;
|
||||
|
||||
@ -273,28 +308,154 @@ static int st_micron_set_4byte_addr_mode(struct spi_nor *nor, bool enable)
|
||||
return spi_nor_write_disable(nor);
|
||||
}
|
||||
|
||||
static void micron_st_default_init(struct spi_nor *nor)
|
||||
/**
|
||||
* micron_st_nor_read_fsr() - Read the Flag Status Register.
|
||||
* @nor: pointer to 'struct spi_nor'
|
||||
* @fsr: pointer to a DMA-able buffer where the value of the
|
||||
* Flag Status Register will be written. Should be at least 2
|
||||
* bytes.
|
||||
*
|
||||
* Return: 0 on success, -errno otherwise.
|
||||
*/
|
||||
static int micron_st_nor_read_fsr(struct spi_nor *nor, u8 *fsr)
|
||||
{
|
||||
int ret;
|
||||
|
||||
if (nor->spimem) {
|
||||
struct spi_mem_op op =
|
||||
SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_RDFSR, 0),
|
||||
SPI_MEM_OP_NO_ADDR,
|
||||
SPI_MEM_OP_NO_DUMMY,
|
||||
SPI_MEM_OP_DATA_IN(1, fsr, 0));
|
||||
|
||||
if (nor->reg_proto == SNOR_PROTO_8_8_8_DTR) {
|
||||
op.addr.nbytes = nor->params->rdsr_addr_nbytes;
|
||||
op.dummy.nbytes = nor->params->rdsr_dummy;
|
||||
/*
|
||||
* We don't want to read only one byte in DTR mode. So,
|
||||
* read 2 and then discard the second byte.
|
||||
*/
|
||||
op.data.nbytes = 2;
|
||||
}
|
||||
|
||||
spi_nor_spimem_setup_op(nor, &op, nor->reg_proto);
|
||||
|
||||
ret = spi_mem_exec_op(nor->spimem, &op);
|
||||
} else {
|
||||
ret = spi_nor_controller_ops_read_reg(nor, SPINOR_OP_RDFSR, fsr,
|
||||
1);
|
||||
}
|
||||
|
||||
if (ret)
|
||||
dev_dbg(nor->dev, "error %d reading FSR\n", ret);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* micron_st_nor_clear_fsr() - Clear the Flag Status Register.
|
||||
* @nor: pointer to 'struct spi_nor'.
|
||||
*/
|
||||
static void micron_st_nor_clear_fsr(struct spi_nor *nor)
|
||||
{
|
||||
int ret;
|
||||
|
||||
if (nor->spimem) {
|
||||
struct spi_mem_op op =
|
||||
SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_CLFSR, 0),
|
||||
SPI_MEM_OP_NO_ADDR,
|
||||
SPI_MEM_OP_NO_DUMMY,
|
||||
SPI_MEM_OP_NO_DATA);
|
||||
|
||||
spi_nor_spimem_setup_op(nor, &op, nor->reg_proto);
|
||||
|
||||
ret = spi_mem_exec_op(nor->spimem, &op);
|
||||
} else {
|
||||
ret = spi_nor_controller_ops_write_reg(nor, SPINOR_OP_CLFSR,
|
||||
NULL, 0);
|
||||
}
|
||||
|
||||
if (ret)
|
||||
dev_dbg(nor->dev, "error %d clearing FSR\n", ret);
|
||||
}
|
||||
|
||||
/**
|
||||
* micron_st_nor_ready() - Query the Status Register as well as the Flag Status
|
||||
* Register to see if the flash is ready for new commands. If there are any
|
||||
* errors in the FSR clear them.
|
||||
* @nor: pointer to 'struct spi_nor'.
|
||||
*
|
||||
* Return: 1 if ready, 0 if not ready, -errno on errors.
|
||||
*/
|
||||
static int micron_st_nor_ready(struct spi_nor *nor)
|
||||
{
|
||||
int sr_ready, ret;
|
||||
|
||||
sr_ready = spi_nor_sr_ready(nor);
|
||||
if (sr_ready < 0)
|
||||
return sr_ready;
|
||||
|
||||
ret = micron_st_nor_read_fsr(nor, nor->bouncebuf);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (nor->bouncebuf[0] & (FSR_E_ERR | FSR_P_ERR)) {
|
||||
if (nor->bouncebuf[0] & FSR_E_ERR)
|
||||
dev_err(nor->dev, "Erase operation failed.\n");
|
||||
else
|
||||
dev_err(nor->dev, "Program operation failed.\n");
|
||||
|
||||
if (nor->bouncebuf[0] & FSR_PT_ERR)
|
||||
dev_err(nor->dev,
|
||||
"Attempted to modify a protected sector.\n");
|
||||
|
||||
micron_st_nor_clear_fsr(nor);
|
||||
|
||||
/*
|
||||
* WEL bit remains set to one when an erase or page program
|
||||
* error occurs. Issue a Write Disable command to protect
|
||||
* against inadvertent writes that can possibly corrupt the
|
||||
* contents of the memory.
|
||||
*/
|
||||
ret = spi_nor_write_disable(nor);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
return sr_ready && !!(nor->bouncebuf[0] & FSR_READY);
|
||||
}
|
||||
|
||||
static void micron_st_nor_default_init(struct spi_nor *nor)
|
||||
{
|
||||
nor->flags |= SNOR_F_HAS_LOCK;
|
||||
nor->flags &= ~SNOR_F_HAS_16BIT_SR;
|
||||
nor->params->quad_enable = NULL;
|
||||
nor->params->set_4byte_addr_mode = st_micron_set_4byte_addr_mode;
|
||||
nor->params->set_4byte_addr_mode = micron_st_nor_set_4byte_addr_mode;
|
||||
}
|
||||
|
||||
static const struct spi_nor_fixups micron_st_fixups = {
|
||||
.default_init = micron_st_default_init,
|
||||
static void micron_st_nor_late_init(struct spi_nor *nor)
|
||||
{
|
||||
if (nor->info->mfr_flags & USE_FSR)
|
||||
nor->params->ready = micron_st_nor_ready;
|
||||
}
|
||||
|
||||
static const struct spi_nor_fixups micron_st_nor_fixups = {
|
||||
.default_init = micron_st_nor_default_init,
|
||||
.late_init = micron_st_nor_late_init,
|
||||
};
|
||||
|
||||
const struct spi_nor_manufacturer spi_nor_micron = {
|
||||
.name = "micron",
|
||||
.parts = micron_parts,
|
||||
.nparts = ARRAY_SIZE(micron_parts),
|
||||
.fixups = µn_st_fixups,
|
||||
.parts = micron_nor_parts,
|
||||
.nparts = ARRAY_SIZE(micron_nor_parts),
|
||||
.fixups = µn_st_nor_fixups,
|
||||
};
|
||||
|
||||
const struct spi_nor_manufacturer spi_nor_st = {
|
||||
.name = "st",
|
||||
.parts = st_parts,
|
||||
.nparts = ARRAY_SIZE(st_parts),
|
||||
.fixups = µn_st_fixups,
|
||||
.parts = st_nor_parts,
|
||||
.nparts = ARRAY_SIZE(st_nor_parts),
|
||||
.fixups = µn_st_nor_fixups,
|
||||
};
|
||||
|
@ -8,6 +8,10 @@
|
||||
|
||||
#include "core.h"
|
||||
|
||||
/* flash_info mfr_flag. Used to clear sticky prorietary SR bits. */
|
||||
#define USE_CLSR BIT(0)
|
||||
|
||||
#define SPINOR_OP_CLSR 0x30 /* Clear status register 1 */
|
||||
#define SPINOR_OP_RD_ANY_REG 0x65 /* Read any register */
|
||||
#define SPINOR_OP_WR_ANY_REG 0x71 /* Write any register */
|
||||
#define SPINOR_REG_CYPRESS_CFR2V 0x00800003
|
||||
@ -20,7 +24,7 @@
|
||||
#define SPINOR_OP_CYPRESS_RD_FAST 0xee
|
||||
|
||||
/**
|
||||
* spi_nor_cypress_octal_dtr_enable() - Enable octal DTR on Cypress flashes.
|
||||
* cypress_nor_octal_dtr_enable() - Enable octal DTR on Cypress flashes.
|
||||
* @nor: pointer to a 'struct spi_nor'
|
||||
* @enable: whether to enable or disable Octal DTR
|
||||
*
|
||||
@ -29,7 +33,7 @@
|
||||
*
|
||||
* Return: 0 on success, -errno otherwise.
|
||||
*/
|
||||
static int spi_nor_cypress_octal_dtr_enable(struct spi_nor *nor, bool enable)
|
||||
static int cypress_nor_octal_dtr_enable(struct spi_nor *nor, bool enable)
|
||||
{
|
||||
struct spi_mem_op op;
|
||||
u8 *buf = nor->bouncebuf;
|
||||
@ -116,7 +120,7 @@ static int spi_nor_cypress_octal_dtr_enable(struct spi_nor *nor, bool enable)
|
||||
|
||||
static void s28hs512t_default_init(struct spi_nor *nor)
|
||||
{
|
||||
nor->params->octal_dtr_enable = spi_nor_cypress_octal_dtr_enable;
|
||||
nor->params->octal_dtr_enable = cypress_nor_octal_dtr_enable;
|
||||
nor->params->writesize = 16;
|
||||
}
|
||||
|
||||
@ -183,9 +187,9 @@ static const struct spi_nor_fixups s28hs512t_fixups = {
|
||||
};
|
||||
|
||||
static int
|
||||
s25fs_s_post_bfpt_fixups(struct spi_nor *nor,
|
||||
const struct sfdp_parameter_header *bfpt_header,
|
||||
const struct sfdp_bfpt *bfpt)
|
||||
s25fs_s_nor_post_bfpt_fixups(struct spi_nor *nor,
|
||||
const struct sfdp_parameter_header *bfpt_header,
|
||||
const struct sfdp_bfpt *bfpt)
|
||||
{
|
||||
/*
|
||||
* The S25FS-S chip family reports 512-byte pages in BFPT but
|
||||
@ -198,11 +202,11 @@ s25fs_s_post_bfpt_fixups(struct spi_nor *nor,
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct spi_nor_fixups s25fs_s_fixups = {
|
||||
.post_bfpt = s25fs_s_post_bfpt_fixups,
|
||||
static const struct spi_nor_fixups s25fs_s_nor_fixups = {
|
||||
.post_bfpt = s25fs_s_nor_post_bfpt_fixups,
|
||||
};
|
||||
|
||||
static const struct flash_info spansion_parts[] = {
|
||||
static const struct flash_info spansion_nor_parts[] = {
|
||||
/* Spansion/Cypress -- single (large) sector size only, at least
|
||||
* for the chips listed here (without boot sectors).
|
||||
*/
|
||||
@ -211,43 +215,53 @@ static const struct flash_info spansion_parts[] = {
|
||||
{ "s25sl064p", INFO(0x010216, 0x4d00, 64 * 1024, 128)
|
||||
NO_SFDP_FLAGS(SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ) },
|
||||
{ "s25fl128s0", INFO6(0x012018, 0x4d0080, 256 * 1024, 64)
|
||||
FLAGS(USE_CLSR)
|
||||
NO_SFDP_FLAGS(SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ) },
|
||||
NO_SFDP_FLAGS(SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ)
|
||||
MFR_FLAGS(USE_CLSR)
|
||||
},
|
||||
{ "s25fl128s1", INFO6(0x012018, 0x4d0180, 64 * 1024, 256)
|
||||
FLAGS(USE_CLSR)
|
||||
NO_SFDP_FLAGS(SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ) },
|
||||
NO_SFDP_FLAGS(SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ)
|
||||
MFR_FLAGS(USE_CLSR)
|
||||
},
|
||||
{ "s25fl256s0", INFO6(0x010219, 0x4d0080, 256 * 1024, 128)
|
||||
FLAGS(USE_CLSR)
|
||||
NO_SFDP_FLAGS(SPI_NOR_SKIP_SFDP | SPI_NOR_DUAL_READ |
|
||||
SPI_NOR_QUAD_READ) },
|
||||
SPI_NOR_QUAD_READ)
|
||||
MFR_FLAGS(USE_CLSR)
|
||||
},
|
||||
{ "s25fl256s1", INFO6(0x010219, 0x4d0180, 64 * 1024, 512)
|
||||
FLAGS(USE_CLSR)
|
||||
NO_SFDP_FLAGS(SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ) },
|
||||
NO_SFDP_FLAGS(SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ)
|
||||
MFR_FLAGS(USE_CLSR)
|
||||
},
|
||||
{ "s25fl512s", INFO6(0x010220, 0x4d0080, 256 * 1024, 256)
|
||||
FLAGS(SPI_NOR_HAS_LOCK | USE_CLSR)
|
||||
NO_SFDP_FLAGS(SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ) },
|
||||
FLAGS(SPI_NOR_HAS_LOCK)
|
||||
NO_SFDP_FLAGS(SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ)
|
||||
MFR_FLAGS(USE_CLSR)
|
||||
},
|
||||
{ "s25fs128s1", INFO6(0x012018, 0x4d0181, 64 * 1024, 256)
|
||||
FLAGS(USE_CLSR)
|
||||
NO_SFDP_FLAGS(SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ)
|
||||
.fixups = &s25fs_s_fixups, },
|
||||
MFR_FLAGS(USE_CLSR)
|
||||
.fixups = &s25fs_s_nor_fixups, },
|
||||
{ "s25fs256s0", INFO6(0x010219, 0x4d0081, 256 * 1024, 128)
|
||||
FLAGS(USE_CLSR)
|
||||
NO_SFDP_FLAGS(SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ) },
|
||||
{ "s25fs256s1", INFO6(0x010219, 0x4d0181, 64 * 1024, 512)
|
||||
FLAGS(USE_CLSR)
|
||||
NO_SFDP_FLAGS(SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ) },
|
||||
{ "s25fs512s", INFO6(0x010220, 0x4d0081, 256 * 1024, 256)
|
||||
FLAGS(USE_CLSR)
|
||||
NO_SFDP_FLAGS(SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ)
|
||||
.fixups = &s25fs_s_fixups, },
|
||||
MFR_FLAGS(USE_CLSR)
|
||||
},
|
||||
{ "s25fs256s1", INFO6(0x010219, 0x4d0181, 64 * 1024, 512)
|
||||
NO_SFDP_FLAGS(SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ)
|
||||
MFR_FLAGS(USE_CLSR)
|
||||
},
|
||||
{ "s25fs512s", INFO6(0x010220, 0x4d0081, 256 * 1024, 256)
|
||||
NO_SFDP_FLAGS(SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ)
|
||||
MFR_FLAGS(USE_CLSR)
|
||||
.fixups = &s25fs_s_nor_fixups, },
|
||||
{ "s25sl12800", INFO(0x012018, 0x0300, 256 * 1024, 64) },
|
||||
{ "s25sl12801", INFO(0x012018, 0x0301, 64 * 1024, 256) },
|
||||
{ "s25fl129p0", INFO(0x012018, 0x4d00, 256 * 1024, 64)
|
||||
FLAGS(USE_CLSR)
|
||||
NO_SFDP_FLAGS(SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ) },
|
||||
NO_SFDP_FLAGS(SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ)
|
||||
MFR_FLAGS(USE_CLSR)
|
||||
},
|
||||
{ "s25fl129p1", INFO(0x012018, 0x4d01, 64 * 1024, 256)
|
||||
FLAGS(USE_CLSR)
|
||||
NO_SFDP_FLAGS(SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ) },
|
||||
NO_SFDP_FLAGS(SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ)
|
||||
MFR_FLAGS(USE_CLSR)
|
||||
},
|
||||
{ "s25sl004a", INFO(0x010212, 0, 64 * 1024, 8) },
|
||||
{ "s25sl008a", INFO(0x010213, 0, 64 * 1024, 16) },
|
||||
{ "s25sl016a", INFO(0x010214, 0, 64 * 1024, 32) },
|
||||
@ -294,24 +308,92 @@ static const struct flash_info spansion_parts[] = {
|
||||
},
|
||||
};
|
||||
|
||||
static void spansion_late_init(struct spi_nor *nor)
|
||||
/**
|
||||
* spansion_nor_clear_sr() - Clear the Status Register.
|
||||
* @nor: pointer to 'struct spi_nor'.
|
||||
*/
|
||||
static void spansion_nor_clear_sr(struct spi_nor *nor)
|
||||
{
|
||||
if (nor->params->size <= SZ_16M)
|
||||
return;
|
||||
int ret;
|
||||
|
||||
nor->flags |= SNOR_F_4B_OPCODES;
|
||||
/* No small sector erase for 4-byte command set */
|
||||
nor->erase_opcode = SPINOR_OP_SE;
|
||||
nor->mtd.erasesize = nor->info->sector_size;
|
||||
if (nor->spimem) {
|
||||
struct spi_mem_op op =
|
||||
SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_CLSR, 0),
|
||||
SPI_MEM_OP_NO_ADDR,
|
||||
SPI_MEM_OP_NO_DUMMY,
|
||||
SPI_MEM_OP_NO_DATA);
|
||||
|
||||
spi_nor_spimem_setup_op(nor, &op, nor->reg_proto);
|
||||
|
||||
ret = spi_mem_exec_op(nor->spimem, &op);
|
||||
} else {
|
||||
ret = spi_nor_controller_ops_write_reg(nor, SPINOR_OP_CLSR,
|
||||
NULL, 0);
|
||||
}
|
||||
|
||||
if (ret)
|
||||
dev_dbg(nor->dev, "error %d clearing SR\n", ret);
|
||||
}
|
||||
|
||||
static const struct spi_nor_fixups spansion_fixups = {
|
||||
.late_init = spansion_late_init,
|
||||
/**
|
||||
* spansion_nor_sr_ready_and_clear() - Query the Status Register to see if the
|
||||
* flash is ready for new commands and clear it if there are any errors.
|
||||
* @nor: pointer to 'struct spi_nor'.
|
||||
*
|
||||
* Return: 1 if ready, 0 if not ready, -errno on errors.
|
||||
*/
|
||||
static int spansion_nor_sr_ready_and_clear(struct spi_nor *nor)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = spi_nor_read_sr(nor, nor->bouncebuf);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (nor->bouncebuf[0] & (SR_E_ERR | SR_P_ERR)) {
|
||||
if (nor->bouncebuf[0] & SR_E_ERR)
|
||||
dev_err(nor->dev, "Erase Error occurred\n");
|
||||
else
|
||||
dev_err(nor->dev, "Programming Error occurred\n");
|
||||
|
||||
spansion_nor_clear_sr(nor);
|
||||
|
||||
/*
|
||||
* WEL bit remains set to one when an erase or page program
|
||||
* error occurs. Issue a Write Disable command to protect
|
||||
* against inadvertent writes that can possibly corrupt the
|
||||
* contents of the memory.
|
||||
*/
|
||||
ret = spi_nor_write_disable(nor);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
return !(nor->bouncebuf[0] & SR_WIP);
|
||||
}
|
||||
|
||||
static void spansion_nor_late_init(struct spi_nor *nor)
|
||||
{
|
||||
if (nor->params->size > SZ_16M) {
|
||||
nor->flags |= SNOR_F_4B_OPCODES;
|
||||
/* No small sector erase for 4-byte command set */
|
||||
nor->erase_opcode = SPINOR_OP_SE;
|
||||
nor->mtd.erasesize = nor->info->sector_size;
|
||||
}
|
||||
|
||||
if (nor->info->mfr_flags & USE_CLSR)
|
||||
nor->params->ready = spansion_nor_sr_ready_and_clear;
|
||||
}
|
||||
|
||||
static const struct spi_nor_fixups spansion_nor_fixups = {
|
||||
.late_init = spansion_nor_late_init,
|
||||
};
|
||||
|
||||
const struct spi_nor_manufacturer spi_nor_spansion = {
|
||||
.name = "spansion",
|
||||
.parts = spansion_parts,
|
||||
.nparts = ARRAY_SIZE(spansion_parts),
|
||||
.fixups = &spansion_fixups,
|
||||
.parts = spansion_nor_parts,
|
||||
.nparts = ARRAY_SIZE(spansion_nor_parts),
|
||||
.fixups = &spansion_nor_fixups,
|
||||
};
|
||||
|
@ -13,12 +13,12 @@
|
||||
|
||||
#define SST26VF_CR_BPNV BIT(3)
|
||||
|
||||
static int sst26vf_lock(struct spi_nor *nor, loff_t ofs, uint64_t len)
|
||||
static int sst26vf_nor_lock(struct spi_nor *nor, loff_t ofs, uint64_t len)
|
||||
{
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
|
||||
static int sst26vf_unlock(struct spi_nor *nor, loff_t ofs, uint64_t len)
|
||||
static int sst26vf_nor_unlock(struct spi_nor *nor, loff_t ofs, uint64_t len)
|
||||
{
|
||||
int ret;
|
||||
|
||||
@ -38,27 +38,27 @@ static int sst26vf_unlock(struct spi_nor *nor, loff_t ofs, uint64_t len)
|
||||
return spi_nor_global_block_unlock(nor);
|
||||
}
|
||||
|
||||
static int sst26vf_is_locked(struct spi_nor *nor, loff_t ofs, uint64_t len)
|
||||
static int sst26vf_nor_is_locked(struct spi_nor *nor, loff_t ofs, uint64_t len)
|
||||
{
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
|
||||
static const struct spi_nor_locking_ops sst26vf_locking_ops = {
|
||||
.lock = sst26vf_lock,
|
||||
.unlock = sst26vf_unlock,
|
||||
.is_locked = sst26vf_is_locked,
|
||||
static const struct spi_nor_locking_ops sst26vf_nor_locking_ops = {
|
||||
.lock = sst26vf_nor_lock,
|
||||
.unlock = sst26vf_nor_unlock,
|
||||
.is_locked = sst26vf_nor_is_locked,
|
||||
};
|
||||
|
||||
static void sst26vf_late_init(struct spi_nor *nor)
|
||||
static void sst26vf_nor_late_init(struct spi_nor *nor)
|
||||
{
|
||||
nor->params->locking_ops = &sst26vf_locking_ops;
|
||||
nor->params->locking_ops = &sst26vf_nor_locking_ops;
|
||||
}
|
||||
|
||||
static const struct spi_nor_fixups sst26vf_fixups = {
|
||||
.late_init = sst26vf_late_init,
|
||||
static const struct spi_nor_fixups sst26vf_nor_fixups = {
|
||||
.late_init = sst26vf_nor_late_init,
|
||||
};
|
||||
|
||||
static const struct flash_info sst_parts[] = {
|
||||
static const struct flash_info sst_nor_parts[] = {
|
||||
/* SST -- large erase sizes are "overlays", "sectors" are 4K */
|
||||
{ "sst25vf040b", INFO(0xbf258d, 0, 64 * 1024, 8)
|
||||
FLAGS(SPI_NOR_HAS_LOCK | SPI_NOR_SWP_IS_VOLATILE)
|
||||
@ -114,11 +114,11 @@ static const struct flash_info sst_parts[] = {
|
||||
{ "sst26vf064b", INFO(0xbf2643, 0, 64 * 1024, 128)
|
||||
FLAGS(SPI_NOR_HAS_LOCK | SPI_NOR_SWP_IS_VOLATILE)
|
||||
NO_SFDP_FLAGS(SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ)
|
||||
.fixups = &sst26vf_fixups },
|
||||
.fixups = &sst26vf_nor_fixups },
|
||||
};
|
||||
|
||||
static int sst_write(struct mtd_info *mtd, loff_t to, size_t len,
|
||||
size_t *retlen, const u_char *buf)
|
||||
static int sst_nor_write(struct mtd_info *mtd, loff_t to, size_t len,
|
||||
size_t *retlen, const u_char *buf)
|
||||
{
|
||||
struct spi_nor *nor = mtd_to_spi_nor(mtd);
|
||||
size_t actual = 0;
|
||||
@ -203,19 +203,19 @@ static int sst_write(struct mtd_info *mtd, loff_t to, size_t len,
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void sst_late_init(struct spi_nor *nor)
|
||||
static void sst_nor_late_init(struct spi_nor *nor)
|
||||
{
|
||||
if (nor->info->mfr_flags & SST_WRITE)
|
||||
nor->mtd._write = sst_write;
|
||||
nor->mtd._write = sst_nor_write;
|
||||
}
|
||||
|
||||
static const struct spi_nor_fixups sst_fixups = {
|
||||
.late_init = sst_late_init,
|
||||
static const struct spi_nor_fixups sst_nor_fixups = {
|
||||
.late_init = sst_nor_late_init,
|
||||
};
|
||||
|
||||
const struct spi_nor_manufacturer spi_nor_sst = {
|
||||
.name = "sst",
|
||||
.parts = sst_parts,
|
||||
.nparts = ARRAY_SIZE(sst_parts),
|
||||
.fixups = &sst_fixups,
|
||||
.parts = sst_nor_parts,
|
||||
.nparts = ARRAY_SIZE(sst_nor_parts),
|
||||
.fixups = &sst_nor_fixups,
|
||||
};
|
||||
|
@ -32,7 +32,7 @@ static const struct spi_nor_fixups w25q256_fixups = {
|
||||
.post_bfpt = w25q256_post_bfpt_fixups,
|
||||
};
|
||||
|
||||
static const struct flash_info winbond_parts[] = {
|
||||
static const struct flash_info winbond_nor_parts[] = {
|
||||
/* Winbond -- w25x "blocks" are 64K, "sectors" are 4KiB */
|
||||
{ "w25x05", INFO(0xef3010, 0, 64 * 1024, 1)
|
||||
NO_SFDP_FLAGS(SECT_4K) },
|
||||
@ -130,14 +130,15 @@ static const struct flash_info winbond_parts[] = {
|
||||
};
|
||||
|
||||
/**
|
||||
* winbond_set_4byte_addr_mode() - Set 4-byte address mode for Winbond flashes.
|
||||
* winbond_nor_set_4byte_addr_mode() - Set 4-byte address mode for Winbond
|
||||
* flashes.
|
||||
* @nor: pointer to 'struct spi_nor'.
|
||||
* @enable: true to enter the 4-byte address mode, false to exit the 4-byte
|
||||
* address mode.
|
||||
*
|
||||
* Return: 0 on success, -errno otherwise.
|
||||
*/
|
||||
static int winbond_set_4byte_addr_mode(struct spi_nor *nor, bool enable)
|
||||
static int winbond_nor_set_4byte_addr_mode(struct spi_nor *nor, bool enable)
|
||||
{
|
||||
int ret;
|
||||
|
||||
@ -161,7 +162,7 @@ static int winbond_set_4byte_addr_mode(struct spi_nor *nor, bool enable)
|
||||
return spi_nor_write_disable(nor);
|
||||
}
|
||||
|
||||
static const struct spi_nor_otp_ops winbond_otp_ops = {
|
||||
static const struct spi_nor_otp_ops winbond_nor_otp_ops = {
|
||||
.read = spi_nor_otp_read_secr,
|
||||
.write = spi_nor_otp_write_secr,
|
||||
.erase = spi_nor_otp_erase_secr,
|
||||
@ -169,25 +170,25 @@ static const struct spi_nor_otp_ops winbond_otp_ops = {
|
||||
.is_locked = spi_nor_otp_is_locked_sr2,
|
||||
};
|
||||
|
||||
static void winbond_default_init(struct spi_nor *nor)
|
||||
static void winbond_nor_default_init(struct spi_nor *nor)
|
||||
{
|
||||
nor->params->set_4byte_addr_mode = winbond_set_4byte_addr_mode;
|
||||
nor->params->set_4byte_addr_mode = winbond_nor_set_4byte_addr_mode;
|
||||
}
|
||||
|
||||
static void winbond_late_init(struct spi_nor *nor)
|
||||
static void winbond_nor_late_init(struct spi_nor *nor)
|
||||
{
|
||||
if (nor->params->otp.org->n_regions)
|
||||
nor->params->otp.ops = &winbond_otp_ops;
|
||||
nor->params->otp.ops = &winbond_nor_otp_ops;
|
||||
}
|
||||
|
||||
static const struct spi_nor_fixups winbond_fixups = {
|
||||
.default_init = winbond_default_init,
|
||||
.late_init = winbond_late_init,
|
||||
static const struct spi_nor_fixups winbond_nor_fixups = {
|
||||
.default_init = winbond_nor_default_init,
|
||||
.late_init = winbond_nor_late_init,
|
||||
};
|
||||
|
||||
const struct spi_nor_manufacturer spi_nor_winbond = {
|
||||
.name = "winbond",
|
||||
.parts = winbond_parts,
|
||||
.nparts = ARRAY_SIZE(winbond_parts),
|
||||
.fixups = &winbond_fixups,
|
||||
.parts = winbond_nor_parts,
|
||||
.nparts = ARRAY_SIZE(winbond_nor_parts),
|
||||
.fixups = &winbond_nor_fixups,
|
||||
};
|
||||
|
@ -8,7 +8,28 @@
|
||||
|
||||
#include "core.h"
|
||||
|
||||
static const struct flash_info xilinx_parts[] = {
|
||||
#define XILINX_OP_SE 0x50 /* Sector erase */
|
||||
#define XILINX_OP_PP 0x82 /* Page program */
|
||||
#define XILINX_OP_RDSR 0xd7 /* Read status register */
|
||||
|
||||
#define XSR_PAGESIZE BIT(0) /* Page size in Po2 or Linear */
|
||||
#define XSR_RDY BIT(7) /* Ready */
|
||||
|
||||
#define S3AN_INFO(_jedec_id, _n_sectors, _page_size) \
|
||||
.id = { \
|
||||
((_jedec_id) >> 16) & 0xff, \
|
||||
((_jedec_id) >> 8) & 0xff, \
|
||||
(_jedec_id) & 0xff \
|
||||
}, \
|
||||
.id_len = 3, \
|
||||
.sector_size = (8 * (_page_size)), \
|
||||
.n_sectors = (_n_sectors), \
|
||||
.page_size = (_page_size), \
|
||||
.addr_width = 3, \
|
||||
.flags = SPI_NOR_NO_FR
|
||||
|
||||
/* Xilinx S3AN share MFR with Atmel SPI NOR */
|
||||
static const struct flash_info xilinx_nor_parts[] = {
|
||||
/* Xilinx S3AN Internal Flash */
|
||||
{ "3S50AN", S3AN_INFO(0x1f2200, 64, 264) },
|
||||
{ "3S200AN", S3AN_INFO(0x1f2400, 256, 264) },
|
||||
@ -26,7 +47,7 @@ static const struct flash_info xilinx_parts[] = {
|
||||
* Addr can safely be unsigned int, the biggest S3AN device is smaller than
|
||||
* 4 MiB.
|
||||
*/
|
||||
static u32 s3an_convert_addr(struct spi_nor *nor, u32 addr)
|
||||
static u32 s3an_nor_convert_addr(struct spi_nor *nor, u32 addr)
|
||||
{
|
||||
u32 page_size = nor->params->page_size;
|
||||
u32 offset, page;
|
||||
@ -38,18 +59,69 @@ static u32 s3an_convert_addr(struct spi_nor *nor, u32 addr)
|
||||
return page | offset;
|
||||
}
|
||||
|
||||
/**
|
||||
* xilinx_nor_read_sr() - Read the Status Register on S3AN flashes.
|
||||
* @nor: pointer to 'struct spi_nor'.
|
||||
* @sr: pointer to a DMA-able buffer where the value of the
|
||||
* Status Register will be written.
|
||||
*
|
||||
* Return: 0 on success, -errno otherwise.
|
||||
*/
|
||||
static int xilinx_nor_read_sr(struct spi_nor *nor, u8 *sr)
|
||||
{
|
||||
int ret;
|
||||
|
||||
if (nor->spimem) {
|
||||
struct spi_mem_op op =
|
||||
SPI_MEM_OP(SPI_MEM_OP_CMD(XILINX_OP_RDSR, 0),
|
||||
SPI_MEM_OP_NO_ADDR,
|
||||
SPI_MEM_OP_NO_DUMMY,
|
||||
SPI_MEM_OP_DATA_IN(1, sr, 0));
|
||||
|
||||
spi_nor_spimem_setup_op(nor, &op, nor->reg_proto);
|
||||
|
||||
ret = spi_mem_exec_op(nor->spimem, &op);
|
||||
} else {
|
||||
ret = spi_nor_controller_ops_read_reg(nor, XILINX_OP_RDSR, sr,
|
||||
1);
|
||||
}
|
||||
|
||||
if (ret)
|
||||
dev_dbg(nor->dev, "error %d reading SR\n", ret);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* xilinx_nor_sr_ready() - Query the Status Register of the S3AN flash to see
|
||||
* if the flash is ready for new commands.
|
||||
* @nor: pointer to 'struct spi_nor'.
|
||||
*
|
||||
* Return: 1 if ready, 0 if not ready, -errno on errors.
|
||||
*/
|
||||
static int xilinx_nor_sr_ready(struct spi_nor *nor)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = xilinx_nor_read_sr(nor, nor->bouncebuf);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return !!(nor->bouncebuf[0] & XSR_RDY);
|
||||
}
|
||||
|
||||
static int xilinx_nor_setup(struct spi_nor *nor,
|
||||
const struct spi_nor_hwcaps *hwcaps)
|
||||
{
|
||||
u32 page_size;
|
||||
int ret;
|
||||
|
||||
ret = spi_nor_xread_sr(nor, nor->bouncebuf);
|
||||
ret = xilinx_nor_read_sr(nor, nor->bouncebuf);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
nor->erase_opcode = SPINOR_OP_XSE;
|
||||
nor->program_opcode = SPINOR_OP_XPP;
|
||||
nor->erase_opcode = XILINX_OP_SE;
|
||||
nor->program_opcode = XILINX_OP_PP;
|
||||
nor->read_opcode = SPINOR_OP_READ;
|
||||
nor->flags |= SNOR_F_NO_OP_CHIP_ERASE;
|
||||
|
||||
@ -73,25 +145,26 @@ static int xilinx_nor_setup(struct spi_nor *nor,
|
||||
nor->mtd.erasesize = 8 * page_size;
|
||||
} else {
|
||||
/* Flash in Default addressing mode */
|
||||
nor->params->convert_addr = s3an_convert_addr;
|
||||
nor->params->convert_addr = s3an_nor_convert_addr;
|
||||
nor->mtd.erasesize = nor->info->sector_size;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void xilinx_late_init(struct spi_nor *nor)
|
||||
static void xilinx_nor_late_init(struct spi_nor *nor)
|
||||
{
|
||||
nor->params->setup = xilinx_nor_setup;
|
||||
nor->params->ready = xilinx_nor_sr_ready;
|
||||
}
|
||||
|
||||
static const struct spi_nor_fixups xilinx_fixups = {
|
||||
.late_init = xilinx_late_init,
|
||||
static const struct spi_nor_fixups xilinx_nor_fixups = {
|
||||
.late_init = xilinx_nor_late_init,
|
||||
};
|
||||
|
||||
const struct spi_nor_manufacturer spi_nor_xilinx = {
|
||||
.name = "xilinx",
|
||||
.parts = xilinx_parts,
|
||||
.nparts = ARRAY_SIZE(xilinx_parts),
|
||||
.fixups = &xilinx_fixups,
|
||||
.parts = xilinx_nor_parts,
|
||||
.nparts = ARRAY_SIZE(xilinx_nor_parts),
|
||||
.fixups = &xilinx_nor_fixups,
|
||||
};
|
||||
|
@ -8,7 +8,7 @@
|
||||
|
||||
#include "core.h"
|
||||
|
||||
static const struct flash_info xmc_parts[] = {
|
||||
static const struct flash_info xmc_nor_parts[] = {
|
||||
/* XMC (Wuhan Xinxin Semiconductor Manufacturing Corp.) */
|
||||
{ "XM25QH64A", INFO(0x207017, 0, 64 * 1024, 128)
|
||||
NO_SFDP_FLAGS(SECT_4K | SPI_NOR_DUAL_READ |
|
||||
@ -20,6 +20,6 @@ static const struct flash_info xmc_parts[] = {
|
||||
|
||||
const struct spi_nor_manufacturer spi_nor_xmc = {
|
||||
.name = "xmc",
|
||||
.parts = xmc_parts,
|
||||
.nparts = ARRAY_SIZE(xmc_parts),
|
||||
.parts = xmc_nor_parts,
|
||||
.nparts = ARRAY_SIZE(xmc_nor_parts),
|
||||
};
|
||||
|
@ -160,14 +160,13 @@ static inline void stop_timing(void)
|
||||
|
||||
static long calc_speed(void)
|
||||
{
|
||||
uint64_t k;
|
||||
long ms;
|
||||
uint64_t k, us;
|
||||
|
||||
ms = ktime_ms_delta(finish, start);
|
||||
if (ms == 0)
|
||||
us = ktime_us_delta(finish, start);
|
||||
if (us == 0)
|
||||
return 0;
|
||||
k = (uint64_t)goodebcnt * (mtd->erasesize / 1024) * 1000;
|
||||
do_div(k, ms);
|
||||
k = (uint64_t)goodebcnt * (mtd->erasesize / 1024) * 1000000;
|
||||
do_div(k, us);
|
||||
return k;
|
||||
}
|
||||
|
||||
|
@ -929,6 +929,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 @@ static int mxic_spi_mem_exec_op(struct spi_mem *mem,
|
||||
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;
|
||||
|
@ -3,6 +3,7 @@
|
||||
#define LINUX_BCMA_DRIVER_CC_H_
|
||||
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/platform_data/brcmnand.h>
|
||||
#include <linux/gpio.h>
|
||||
|
||||
/** ChipCommon core registers. **/
|
||||
@ -599,6 +600,10 @@ struct bcma_sflash {
|
||||
|
||||
#ifdef CONFIG_BCMA_NFLASH
|
||||
struct bcma_nflash {
|
||||
/* Must be the fist member for the brcmnand driver to
|
||||
* de-reference that structure.
|
||||
*/
|
||||
struct brcmnand_platform_data brcmnand_info;
|
||||
bool present;
|
||||
bool boot; /* This is the flash the SoC boots from */
|
||||
};
|
||||
|
@ -188,9 +188,6 @@ struct module; /* only needed for owner field in mtd_info */
|
||||
*/
|
||||
struct mtd_debug_info {
|
||||
struct dentry *dfs_dir;
|
||||
|
||||
const char *partname;
|
||||
const char *partid;
|
||||
};
|
||||
|
||||
/**
|
||||
@ -711,7 +708,11 @@ static inline int mtd_is_bitflip_or_eccerr(int err) {
|
||||
|
||||
unsigned mtd_mmap_capabilities(struct mtd_info *mtd);
|
||||
|
||||
extern char *mtd_expert_analysis_warning;
|
||||
extern bool mtd_expert_analysis_mode;
|
||||
#ifdef CONFIG_DEBUG_FS
|
||||
bool mtd_check_expert_analysis_mode(void);
|
||||
#else
|
||||
static inline bool mtd_check_expert_analysis_mode(void) { return false; }
|
||||
#endif
|
||||
|
||||
|
||||
#endif /* __MTD_MTD_H__ */
|
||||
|
49
include/linux/mtd/nand-ecc-mxic.h
Normal file
49
include/linux/mtd/nand-ecc-mxic.h
Normal file
@ -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,
|
||||
|
@ -1240,6 +1240,7 @@ struct nand_secure_region {
|
||||
* @lock: Lock protecting the suspended field. Also used to serialize accesses
|
||||
* to the NAND device
|
||||
* @suspended: Set to 1 when the device is suspended, 0 when it's not
|
||||
* @resume_wq: wait queue to sleep if rawnand is in suspended state.
|
||||
* @cur_cs: Currently selected target. -1 means no target selected, otherwise we
|
||||
* should always have cur_cs >= 0 && cur_cs < nanddev_ntargets().
|
||||
* NAND Controller drivers should not modify this value, but they're
|
||||
@ -1294,6 +1295,7 @@ struct nand_chip {
|
||||
/* Internals */
|
||||
struct mutex lock;
|
||||
unsigned int suspended : 1;
|
||||
wait_queue_head_t resume_wq;
|
||||
int cur_cs;
|
||||
int read_retries;
|
||||
struct nand_secure_region *secure_regions;
|
||||
|
@ -47,8 +47,6 @@
|
||||
#define SPINOR_OP_RDID 0x9f /* Read JEDEC ID */
|
||||
#define SPINOR_OP_RDSFDP 0x5a /* Read SFDP */
|
||||
#define SPINOR_OP_RDCR 0x35 /* Read configuration register */
|
||||
#define SPINOR_OP_RDFSR 0x70 /* Read flag status register */
|
||||
#define SPINOR_OP_CLFSR 0x50 /* Clear flag status register */
|
||||
#define SPINOR_OP_RDEAR 0xc8 /* Read Extended Address Register */
|
||||
#define SPINOR_OP_WREAR 0xc5 /* Write Extended Address Register */
|
||||
#define SPINOR_OP_SRSTEN 0x66 /* Software Reset Enable */
|
||||
@ -86,22 +84,12 @@
|
||||
#define SPINOR_OP_BP 0x02 /* Byte program */
|
||||
#define SPINOR_OP_AAI_WP 0xad /* Auto address increment word program */
|
||||
|
||||
/* Used for S3AN flashes only */
|
||||
#define SPINOR_OP_XSE 0x50 /* Sector erase */
|
||||
#define SPINOR_OP_XPP 0x82 /* Page program */
|
||||
#define SPINOR_OP_XRDSR 0xd7 /* Read status register */
|
||||
|
||||
#define XSR_PAGESIZE BIT(0) /* Page size in Po2 or Linear */
|
||||
#define XSR_RDY BIT(7) /* Ready */
|
||||
|
||||
|
||||
/* Used for Macronix and Winbond flashes. */
|
||||
#define SPINOR_OP_EN4B 0xb7 /* Enter 4-byte mode */
|
||||
#define SPINOR_OP_EX4B 0xe9 /* Exit 4-byte mode */
|
||||
|
||||
/* Used for Spansion flashes only. */
|
||||
#define SPINOR_OP_BRWR 0x17 /* Bank register write */
|
||||
#define SPINOR_OP_CLSR 0x30 /* Clear status register 1 */
|
||||
|
||||
/* Used for Micron flashes only. */
|
||||
#define SPINOR_OP_RD_EVCR 0x65 /* Read EVCR register */
|
||||
@ -135,12 +123,6 @@
|
||||
/* Enhanced Volatile Configuration Register bits */
|
||||
#define EVCR_QUAD_EN_MICRON BIT(7) /* Micron Quad I/O */
|
||||
|
||||
/* Flag Status Register bits */
|
||||
#define FSR_READY BIT(7) /* Device status, 0 = Busy, 1 = Ready */
|
||||
#define FSR_E_ERR BIT(5) /* Erase operation status */
|
||||
#define FSR_P_ERR BIT(4) /* Program operation status */
|
||||
#define FSR_PT_ERR BIT(1) /* Protection error bit */
|
||||
|
||||
/* Status Register 2 bits. */
|
||||
#define SR2_QUAD_EN_BIT1 BIT(1)
|
||||
#define SR2_LB1 BIT(3) /* Security Register Lock Bit 1 */
|
||||
|
@ -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;
|
||||
};
|
||||
|
||||
/**
|
||||
|
12
include/linux/platform_data/brcmnand.h
Normal file
12
include/linux/platform_data/brcmnand.h
Normal file
@ -0,0 +1,12 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0-only */
|
||||
#ifndef BRCMNAND_PLAT_DATA_H
|
||||
#define BRCMNAND_PLAT_DATA_H
|
||||
|
||||
struct brcmnand_platform_data {
|
||||
int chip_select;
|
||||
const char * const *part_probe_types;
|
||||
unsigned int ecc_stepsize;
|
||||
unsigned int ecc_strength;
|
||||
};
|
||||
|
||||
#endif /* BRCMNAND_PLAT_DATA_H */
|
@ -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);
|
||||
|
@ -24,6 +24,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,
|
||||
@ -413,6 +414,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_gpiods: Array of GPIO descs to use as chip select lines; one per CS
|
||||
@ -632,6 +634,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 */
|
||||
struct gpio_desc **cs_gpiods;
|
||||
|
Loading…
Reference in New Issue
Block a user