mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2024-10-31 16:38:12 +00:00
MTD core changes:
* Call of_platform_populate() for MTD partitions * Check devicetree alias for index * mtdoops: - Add a timestamp to the mtdoops header. - Create a header structure for the saved mtdoops. - Fix the size of the header read buffer. * mtdblock: Warn if opened on NAND * Bindings: - reserved-memory: Support MTD/block device - jedec,spi-nor: remove unneeded properties - Extend fixed-partitions binding - Add Sercomm (Suzhou) Corporation vendor prefix MTD driver changes: * st_spi_fsm: add missing clk_disable_unprepare() in stfsm_remove() * phram: - Allow cached mappings - Allow probing via reserved-memory * maps: ixp4xx: Drop driver * bcm47xxpart: Print correct offset on read error CFI driver changes: * Rename chip_ready variables * Add S29GL064N ID definition * Use chip_ready() for write on S29GL064N * Move and rename chip_check/chip_ready/chip_good_for_write NAND core changes: * Print offset instead of page number for bad blocks Raw NAND controller drivers: * Cadence: Fix possible null-ptr-deref in cadence_nand_dt_probe() * CS553X: simplify the return expression of cs553x_write_ctrl_byte() * Davinci: Remove redundant unsigned comparison to zero * Denali: Use managed device resources * GPMI: - Add large oob bch setting support - Rename the variable ecc_chunk_size - Uninline the gpmi_check_ecc function - Add strict ecc strength check - Refactor BCH geometry settings function * Intel: Fix possible null-ptr-deref in ebu_nand_probe() * MPC5121: Check before clk_disable_unprepare() not needed * Mtk: - MTD_NAND_ECC_MEDIATEK should depend on ARCH_MEDIATEK - Also parse the default nand-ecc-engine property if available - Make mtk_ecc.c a separated module * OMAP ELM: - Convert the bindings to yaml - Describe the bindings for AM64 ELM - Add support for its compatible * Renesas: Use runtime PM instead of the raw clock API and update the bindings accordingly * Rockchip: Check before clk_disable_unprepare() not needed * TMIO: Check return value after calling platform_get_resource() Raw NAND chip driver: * Kioxia: Add support for TH58NVG3S0HBAI4 and TC58NVG0S3HTA00 SPI-NAND chip drivers: * Gigadevice: - Add support for: - GD5FxGM7xExxG - GD5F{2,4}GQ5xExxG - GD5F1GQ5RExxG - GD5FxGQ4xExxG - Fix Quad IO for GD5F1GQ5UExxG * XTX: Add support for XT26G0xA SPI NOR core changes: * Read back written SR value to make sure the write was done correctly. * Introduce a common function for Read ID that manufacturer drivers can use to verify the Octal DTR switch worked correctly. * Add helpers for read/write any register commands so manufacturer drivers don't open code it every time. * Clarify rdsr dummy cycles documentation. * Add debugfs entry to expose internal flash parameters and state. SPI NOR manufacturer drivers changes: * Add support for Winbond W25Q512NW-IM, and Eon EN25QH256A. * Move spi_nor_write_ear() to Winbond module since only Winbond flashes use it. * Rework Micron and Cypress Octal DTR enable methods to improve readability. * Use the common Read ID function to verify switch to Octal DTR mode for Micron and Cypress flashes. * Skip polling status on volatile register writes for Micron and Cypress flashes since the operation is instant. -----BEGIN PGP SIGNATURE----- iQEzBAABCgAdFiEE9HuaYnbmDhq/XIDIJWrqGEe9VoQFAmKHhXEACgkQJWrqGEe9 VoQ4FQgAwTWnv7Eps8ngxlOE1WW1Kx+XUFCYN7mUKmDycgRKWWqF0OUu3pU6T3Si XoOixajzsPC7AJtXbbGKUkH3UtqapCZ/OVxMJzY1S1zKVgS72ChvPth+rNOA5H6f wkwCRYBCm51hYAlOQeRoFtwLEpOP17KTZw9Jn/u5RVwbQedQnzPj5sb6PddejjF4 9nlk2auJHzENBEEBG3WLRNgvGep8mNNFarzWB4iWrMcG0w1EgBksbIdshoBmu7vj nKcDJbyA81DqX+8d56BfAFih8H2yCXZmhUzMK+zqnNkd1NuPnDDnrQYFKmKm2hAg 3bIMIzi+8dl/m0TY6BHPHj8oIYpjYA== =P95J -----END PGP SIGNATURE----- Merge tag 'mtd/for-5.19' of git://git.kernel.org/pub/scm/linux/kernel/git/mtd/linux Pull mtd updates from Miquel Raynal: "MTD core changes: - Call of_platform_populate() for MTD partitions - Check devicetree alias for index - mtdoops: - Add a timestamp to the mtdoops header. - Create a header structure for the saved mtdoops. - Fix the size of the header read buffer. - mtdblock: Warn if opened on NAND - Bindings: - reserved-memory: Support MTD/block device - jedec,spi-nor: remove unneeded properties - Extend fixed-partitions binding - Add Sercomm (Suzhou) Corporation vendor prefix MTD driver changes: - st_spi_fsm: add missing clk_disable_unprepare() in stfsm_remove() - phram: - Allow cached mappings - Allow probing via reserved-memory - maps: ixp4xx: Drop driver - bcm47xxpart: Print correct offset on read error CFI driver changes: - Rename chip_ready variables - Add S29GL064N ID definition - Use chip_ready() for write on S29GL064N - Move and rename chip_check/chip_ready/chip_good_for_write NAND core changes: - Print offset instead of page number for bad blocks Raw NAND controller drivers: - Cadence: Fix possible null-ptr-deref in cadence_nand_dt_probe() - CS553X: simplify the return expression of cs553x_write_ctrl_byte() - Davinci: Remove redundant unsigned comparison to zero - Denali: Use managed device resources - GPMI: - Add large oob bch setting support - Rename the variable ecc_chunk_size - Uninline the gpmi_check_ecc function - Add strict ecc strength check - Refactor BCH geometry settings function - Intel: Fix possible null-ptr-deref in ebu_nand_probe() - MPC5121: Check before clk_disable_unprepare() not needed - Mtk: - MTD_NAND_ECC_MEDIATEK should depend on ARCH_MEDIATEK - Also parse the default nand-ecc-engine property if available - Make mtk_ecc.c a separated module - OMAP ELM: - Convert the bindings to yaml - Describe the bindings for AM64 ELM - Add support for its compatible - Renesas: Use runtime PM instead of the raw clock API and update the bindings accordingly - Rockchip: Check before clk_disable_unprepare() not needed - TMIO: Check return value after calling platform_get_resource() Raw NAND chip driver: - Kioxia: Add support for TH58NVG3S0HBAI4 and TC58NVG0S3HTA00 SPI-NAND chip drivers: - Gigadevice: - Add support for: - GD5FxGM7xExxG - GD5F{2,4}GQ5xExxG - GD5F1GQ5RExxG - GD5FxGQ4xExxG - Fix Quad IO for GD5F1GQ5UExxG - XTX: Add support for XT26G0xA SPI NOR core changes: - Read back written SR value to make sure the write was done correctly. - Introduce a common function for Read ID that manufacturer drivers can use to verify the Octal DTR switch worked correctly. - Add helpers for read/write any register commands so manufacturer drivers don't open code it every time. - Clarify rdsr dummy cycles documentation. - Add debugfs entry to expose internal flash parameters and state. SPI NOR manufacturer drivers changes: - Add support for Winbond W25Q512NW-IM, and Eon EN25QH256A. - Move spi_nor_write_ear() to Winbond module since only Winbond flashes use it. - Rework Micron and Cypress Octal DTR enable methods to improve readability. - Use the common Read ID function to verify switch to Octal DTR mode for Micron and Cypress flashes. - Skip polling status on volatile register writes for Micron and Cypress flashes since the operation is instant" * tag 'mtd/for-5.19' of git://git.kernel.org/pub/scm/linux/kernel/git/mtd/linux: (68 commits) mtd: st_spi_fsm: add missing clk_disable_unprepare() in stfsm_remove() dt-bindings: mtd: partitions: Extend fixed-partitions binding dt-bindings: Add Sercomm (Suzhou) Corporation vendor prefix mtd: phram: Allow cached mappings mtd: call of_platform_populate() for MTD partitions mtd: rawnand: renesas: Use runtime PM instead of the raw clock API dt-bindings: mtd: renesas: Fix the NAND controller description mtd: rawnand: mpc5121: Check before clk_disable_unprepare() not needed mtd: rawnand: rockchip: Check before clk_disable_unprepare() not needed mtd: nand: MTD_NAND_ECC_MEDIATEK should depend on ARCH_MEDIATEK mtd: rawnand: cs553x: simplify the return expression of cs553x_write_ctrl_byte() mtd: rawnand: kioxia: Add support for TH58NVG3S0HBAI4 mtd: spi-nor: debugfs: fix format specifier mtd: spi-nor: support eon en25qh256a variant mtd: spi-nor: winbond: add support for W25Q512NW-IM mtd: spi-nor: expose internal parameters via debugfs mtd: spi-nor: export spi_nor_hwcaps_pp2cmd() mtd: spi-nor: move spi_nor_write_ear() to winbond module mtd: spi-nor: amend the rdsr dummy cycles documentation mtd: cfi_cmdset_0002: Rename chip_ready variables ...
This commit is contained in:
commit
d335371940
57 changed files with 1749 additions and 839 deletions
|
@ -1,16 +0,0 @@
|
|||
Error location module
|
||||
|
||||
Required properties:
|
||||
- compatible: Must be "ti,am3352-elm"
|
||||
- reg: physical base address and size of the registers map.
|
||||
- interrupts: Interrupt number for the elm.
|
||||
|
||||
Optional properties:
|
||||
- ti,hwmods: Name of the hwmod associated to the elm
|
||||
|
||||
Example:
|
||||
elm: elm@0 {
|
||||
compatible = "ti,am3352-elm";
|
||||
reg = <0x48080000 0x2000>;
|
||||
interrupts = <4>;
|
||||
};
|
|
@ -50,10 +50,6 @@ properties:
|
|||
minItems: 1
|
||||
maxItems: 2
|
||||
|
||||
spi-max-frequency: true
|
||||
spi-rx-bus-width: true
|
||||
spi-tx-bus-width: true
|
||||
|
||||
m25p,fast-read:
|
||||
type: boolean
|
||||
description:
|
||||
|
@ -74,8 +70,6 @@ properties:
|
|||
be used on such systems, to denote the absence of a reliable reset
|
||||
mechanism.
|
||||
|
||||
label: true
|
||||
|
||||
partitions:
|
||||
type: object
|
||||
|
||||
|
@ -99,8 +93,6 @@ examples:
|
|||
#size-cells = <0>;
|
||||
|
||||
flash@0 {
|
||||
#address-cells = <1>;
|
||||
#size-cells = <1>;
|
||||
compatible = "spansion,m25p80", "jedec,spi-nor";
|
||||
reg = <0>;
|
||||
spi-max-frequency = <40000000>;
|
||||
|
|
|
@ -19,7 +19,11 @@ maintainers:
|
|||
|
||||
properties:
|
||||
compatible:
|
||||
const: fixed-partitions
|
||||
oneOf:
|
||||
- const: fixed-partitions
|
||||
- items:
|
||||
- const: sercomm,sc-partitions
|
||||
- const: fixed-partitions
|
||||
|
||||
"#address-cells": true
|
||||
|
||||
|
@ -27,7 +31,24 @@ properties:
|
|||
|
||||
patternProperties:
|
||||
"@[0-9a-f]+$":
|
||||
$ref: "partition.yaml#"
|
||||
allOf:
|
||||
- $ref: "partition.yaml#"
|
||||
- if:
|
||||
properties:
|
||||
compatible:
|
||||
contains:
|
||||
const: sercomm,sc-partitions
|
||||
then:
|
||||
properties:
|
||||
sercomm,scpart-id:
|
||||
description: Partition id in Sercomm partition map. Mtd
|
||||
parser uses this id to find a record in the partition map
|
||||
containing offset and size of the current partition. The
|
||||
values from partition map overrides partition offset and
|
||||
size defined in reg property of the dts. Frequently these
|
||||
values are the same, but may differ if device has bad
|
||||
eraseblocks on a flash.
|
||||
$ref: /schemas/types.yaml#/definitions/uint32
|
||||
|
||||
required:
|
||||
- "#address-cells"
|
||||
|
@ -52,6 +73,7 @@ examples:
|
|||
reg = <0x0100000 0x200000>;
|
||||
};
|
||||
};
|
||||
|
||||
- |
|
||||
partitions {
|
||||
compatible = "fixed-partitions";
|
||||
|
@ -64,6 +86,7 @@ examples:
|
|||
reg = <0x00000000 0x1 0x00000000>;
|
||||
};
|
||||
};
|
||||
|
||||
- |
|
||||
partitions {
|
||||
compatible = "fixed-partitions";
|
||||
|
@ -82,6 +105,7 @@ examples:
|
|||
reg = <0x2 0x00000000 0x1 0x00000000>;
|
||||
};
|
||||
};
|
||||
|
||||
- |
|
||||
partitions {
|
||||
compatible = "fixed-partitions";
|
||||
|
@ -119,3 +143,30 @@ examples:
|
|||
};
|
||||
};
|
||||
};
|
||||
|
||||
- |
|
||||
partitions {
|
||||
compatible = "sercomm,sc-partitions", "fixed-partitions";
|
||||
#address-cells = <1>;
|
||||
#size-cells = <1>;
|
||||
|
||||
partition@0 {
|
||||
label = "u-boot";
|
||||
reg = <0x0 0x100000>;
|
||||
sercomm,scpart-id = <0>;
|
||||
read-only;
|
||||
};
|
||||
|
||||
partition@100000 {
|
||||
label = "dynamic partition map";
|
||||
reg = <0x100000 0x100000>;
|
||||
sercomm,scpart-id = <1>;
|
||||
};
|
||||
|
||||
partition@200000 {
|
||||
label = "Factory";
|
||||
reg = <0x200000 0x100000>;
|
||||
sercomm,scpart-id = <2>;
|
||||
read-only;
|
||||
};
|
||||
};
|
||||
|
|
|
@ -36,11 +36,15 @@ properties:
|
|||
- const: hclk
|
||||
- const: eclk
|
||||
|
||||
power-domains:
|
||||
maxItems: 1
|
||||
|
||||
required:
|
||||
- compatible
|
||||
- reg
|
||||
- clocks
|
||||
- clock-names
|
||||
- power-domains
|
||||
- interrupts
|
||||
|
||||
unevaluatedProperties: false
|
||||
|
@ -56,6 +60,7 @@ examples:
|
|||
interrupts = <GIC_SPI 58 IRQ_TYPE_LEVEL_HIGH>;
|
||||
clocks = <&sysctrl R9A06G032_HCLK_NAND>, <&sysctrl R9A06G032_CLK_NAND>;
|
||||
clock-names = "hclk", "eclk";
|
||||
power-domains = <&sysctrl>;
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
};
|
||||
|
|
72
Documentation/devicetree/bindings/mtd/ti,elm.yaml
Normal file
72
Documentation/devicetree/bindings/mtd/ti,elm.yaml
Normal file
|
@ -0,0 +1,72 @@
|
|||
# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
|
||||
%YAML 1.2
|
||||
---
|
||||
$id: http://devicetree.org/schemas/mtd/ti,elm.yaml#
|
||||
$schema: http://devicetree.org/meta-schemas/core.yaml#
|
||||
|
||||
title: Texas Instruments Error Location Module (ELM).
|
||||
|
||||
maintainers:
|
||||
- Roger Quadros <rogerq@kernel.org>
|
||||
|
||||
description:
|
||||
ELM module is used together with GPMC and NAND Flash to detect
|
||||
errors and the location of the error based on BCH algorithms
|
||||
so they can be corrected if possible.
|
||||
|
||||
properties:
|
||||
compatible:
|
||||
enum:
|
||||
- ti,am3352-elm
|
||||
- ti,am64-elm
|
||||
|
||||
reg:
|
||||
maxItems: 1
|
||||
|
||||
interrupts:
|
||||
maxItems: 1
|
||||
|
||||
clocks:
|
||||
maxItems: 1
|
||||
description: Functional clock.
|
||||
|
||||
clock-names:
|
||||
items:
|
||||
- const: fck
|
||||
|
||||
power-domains:
|
||||
maxItems: 1
|
||||
|
||||
ti,hwmods:
|
||||
description:
|
||||
Name of the HWMOD associated with ELM. This is for legacy
|
||||
platforms only.
|
||||
$ref: /schemas/types.yaml#/definitions/string
|
||||
deprecated: true
|
||||
|
||||
required:
|
||||
- compatible
|
||||
- reg
|
||||
- interrupts
|
||||
|
||||
allOf:
|
||||
- if:
|
||||
properties:
|
||||
compatible:
|
||||
contains:
|
||||
const: ti,am64-elm
|
||||
then:
|
||||
required:
|
||||
- clocks
|
||||
- clock-names
|
||||
- power-domains
|
||||
|
||||
additionalProperties: false
|
||||
|
||||
examples:
|
||||
- |
|
||||
elm: ecc@0 {
|
||||
compatible = "ti,am3352-elm";
|
||||
reg = <0x0 0x2000>;
|
||||
interrupts = <4>;
|
||||
};
|
47
Documentation/devicetree/bindings/reserved-memory/phram.yaml
Normal file
47
Documentation/devicetree/bindings/reserved-memory/phram.yaml
Normal file
|
@ -0,0 +1,47 @@
|
|||
# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
|
||||
%YAML 1.2
|
||||
---
|
||||
$id: http://devicetree.org/schemas/reserved-memory/phram.yaml#
|
||||
$schema: http://devicetree.org/meta-schemas/core.yaml#
|
||||
|
||||
title: MTD/block device in RAM
|
||||
|
||||
description: |
|
||||
Specifies that the reserved memory region can be used as an MTD or block
|
||||
device.
|
||||
|
||||
The "phram" node is named after the "MTD in PHysical RAM" driver which
|
||||
provides an implementation of this functionality in Linux.
|
||||
|
||||
maintainers:
|
||||
- Vincent Whitchurch <vincent.whitchurch@axis.com>
|
||||
|
||||
allOf:
|
||||
- $ref: "reserved-memory.yaml"
|
||||
- $ref: "/schemas/mtd/mtd.yaml"
|
||||
|
||||
properties:
|
||||
compatible:
|
||||
const: phram
|
||||
|
||||
reg:
|
||||
description: region of memory that can be used as an MTD/block device
|
||||
|
||||
required:
|
||||
- compatible
|
||||
- reg
|
||||
|
||||
unevaluatedProperties: false
|
||||
|
||||
examples:
|
||||
- |
|
||||
reserved-memory {
|
||||
#address-cells = <1>;
|
||||
#size-cells = <1>;
|
||||
|
||||
phram: flash@12340000 {
|
||||
compatible = "phram";
|
||||
label = "rootfs";
|
||||
reg = <0x12340000 0x00800000>;
|
||||
};
|
||||
};
|
|
@ -1082,6 +1082,8 @@ patternProperties:
|
|||
description: Sensirion AG
|
||||
"^sensortek,.*":
|
||||
description: Sensortek Technology Corporation
|
||||
"^sercomm,.*":
|
||||
description: Sercomm (Suzhou) Corporation
|
||||
"^sff,.*":
|
||||
description: Small Form Factor Committee
|
||||
"^sgd,.*":
|
||||
|
|
|
@ -48,6 +48,7 @@
|
|||
#define SST49LF040B 0x0050
|
||||
#define SST49LF008A 0x005a
|
||||
#define AT49BV6416 0x00d6
|
||||
#define S29GL064N_MN12 0x0c01
|
||||
|
||||
/*
|
||||
* Status Register bit description. Used by flash devices that don't
|
||||
|
@ -59,6 +60,10 @@
|
|||
#define CFI_SR_WBASB BIT(3)
|
||||
#define CFI_SR_SLSB BIT(1)
|
||||
|
||||
enum cfi_quirks {
|
||||
CFI_QUIRK_DQ_TRUE_DATA = BIT(0),
|
||||
};
|
||||
|
||||
static int cfi_amdstd_read (struct mtd_info *, loff_t, size_t, size_t *, u_char *);
|
||||
static int cfi_amdstd_write_words(struct mtd_info *, loff_t, size_t, size_t *, const u_char *);
|
||||
#if !FORCE_WORD_WRITE
|
||||
|
@ -436,6 +441,15 @@ static void fixup_s29ns512p_sectors(struct mtd_info *mtd)
|
|||
mtd->name);
|
||||
}
|
||||
|
||||
static void fixup_quirks(struct mtd_info *mtd)
|
||||
{
|
||||
struct map_info *map = mtd->priv;
|
||||
struct cfi_private *cfi = map->fldrv_priv;
|
||||
|
||||
if (cfi->mfr == CFI_MFR_AMD && cfi->id == S29GL064N_MN12)
|
||||
cfi->quirks |= CFI_QUIRK_DQ_TRUE_DATA;
|
||||
}
|
||||
|
||||
/* Used to fix CFI-Tables of chips without Extended Query Tables */
|
||||
static struct cfi_fixup cfi_nopri_fixup_table[] = {
|
||||
{ CFI_MFR_SST, 0x234a, fixup_sst39vf }, /* SST39VF1602 */
|
||||
|
@ -462,7 +476,7 @@ static struct cfi_fixup cfi_fixup_table[] = {
|
|||
{ CFI_MFR_AMD, 0x0056, fixup_use_secsi },
|
||||
{ CFI_MFR_AMD, 0x005C, fixup_use_secsi },
|
||||
{ CFI_MFR_AMD, 0x005F, fixup_use_secsi },
|
||||
{ CFI_MFR_AMD, 0x0c01, fixup_s29gl064n_sectors },
|
||||
{ CFI_MFR_AMD, S29GL064N_MN12, fixup_s29gl064n_sectors },
|
||||
{ CFI_MFR_AMD, 0x1301, fixup_s29gl064n_sectors },
|
||||
{ CFI_MFR_AMD, 0x1a00, fixup_s29gl032n_sectors },
|
||||
{ CFI_MFR_AMD, 0x1a01, fixup_s29gl032n_sectors },
|
||||
|
@ -474,6 +488,7 @@ static struct cfi_fixup cfi_fixup_table[] = {
|
|||
#if !FORCE_WORD_WRITE
|
||||
{ CFI_MFR_ANY, CFI_ID_ANY, fixup_use_write_buffers },
|
||||
#endif
|
||||
{ CFI_MFR_ANY, CFI_ID_ANY, fixup_quirks },
|
||||
{ 0, 0, NULL }
|
||||
};
|
||||
static struct cfi_fixup jedec_fixup_table[] = {
|
||||
|
@ -801,47 +816,11 @@ static struct mtd_info *cfi_amdstd_setup(struct mtd_info *mtd)
|
|||
return NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
* Return true if the chip is ready.
|
||||
*
|
||||
* Ready is one of: read mode, query mode, erase-suspend-read mode (in any
|
||||
* non-suspended sector) and is indicated by no toggle bits toggling.
|
||||
*
|
||||
* Note that anything more complicated than checking if no bits are toggling
|
||||
* (including checking DQ5 for an error status) is tricky to get working
|
||||
* correctly and is therefore not done (particularly with interleaved chips
|
||||
* as each chip must be checked independently of the others).
|
||||
*/
|
||||
static int __xipram chip_ready(struct map_info *map, struct flchip *chip,
|
||||
unsigned long addr)
|
||||
{
|
||||
struct cfi_private *cfi = map->fldrv_priv;
|
||||
map_word d, t;
|
||||
|
||||
if (cfi_use_status_reg(cfi)) {
|
||||
map_word ready = CMD(CFI_SR_DRB);
|
||||
/*
|
||||
* For chips that support status register, check device
|
||||
* ready bit
|
||||
*/
|
||||
cfi_send_gen_cmd(0x70, cfi->addr_unlock1, chip->start, map, cfi,
|
||||
cfi->device_type, NULL);
|
||||
d = map_read(map, addr);
|
||||
|
||||
return map_word_andequal(map, d, ready, ready);
|
||||
}
|
||||
|
||||
d = map_read(map, addr);
|
||||
t = map_read(map, addr);
|
||||
|
||||
return map_word_equal(map, d, t);
|
||||
}
|
||||
|
||||
/*
|
||||
* Return true if the chip is ready and has the correct value.
|
||||
*
|
||||
* Ready is one of: read mode, query mode, erase-suspend-read mode (in any
|
||||
* non-suspended sector) and it is indicated by no bits toggling.
|
||||
* non-suspended sector) and is indicated by no toggle bits toggling.
|
||||
*
|
||||
* Error are indicated by toggling bits or bits held with the wrong value,
|
||||
* or with bits toggling.
|
||||
|
@ -850,17 +829,16 @@ static int __xipram chip_ready(struct map_info *map, struct flchip *chip,
|
|||
* (including checking DQ5 for an error status) is tricky to get working
|
||||
* correctly and is therefore not done (particularly with interleaved chips
|
||||
* as each chip must be checked independently of the others).
|
||||
*
|
||||
*/
|
||||
static int __xipram chip_good(struct map_info *map, struct flchip *chip,
|
||||
unsigned long addr, map_word expected)
|
||||
static int __xipram chip_ready(struct map_info *map, struct flchip *chip,
|
||||
unsigned long addr, map_word *expected)
|
||||
{
|
||||
struct cfi_private *cfi = map->fldrv_priv;
|
||||
map_word oldd, curd;
|
||||
int ret;
|
||||
|
||||
if (cfi_use_status_reg(cfi)) {
|
||||
map_word ready = CMD(CFI_SR_DRB);
|
||||
|
||||
/*
|
||||
* For chips that support status register, check device
|
||||
* ready bit
|
||||
|
@ -875,8 +853,24 @@ static int __xipram chip_good(struct map_info *map, struct flchip *chip,
|
|||
oldd = map_read(map, addr);
|
||||
curd = map_read(map, addr);
|
||||
|
||||
return map_word_equal(map, oldd, curd) &&
|
||||
map_word_equal(map, curd, expected);
|
||||
ret = map_word_equal(map, oldd, curd);
|
||||
|
||||
if (!ret || !expected)
|
||||
return ret;
|
||||
|
||||
return map_word_equal(map, curd, *expected);
|
||||
}
|
||||
|
||||
static int __xipram chip_good(struct map_info *map, struct flchip *chip,
|
||||
unsigned long addr, map_word *expected)
|
||||
{
|
||||
struct cfi_private *cfi = map->fldrv_priv;
|
||||
map_word *datum = expected;
|
||||
|
||||
if (cfi->quirks & CFI_QUIRK_DQ_TRUE_DATA)
|
||||
datum = NULL;
|
||||
|
||||
return chip_ready(map, chip, addr, datum);
|
||||
}
|
||||
|
||||
static int get_chip(struct map_info *map, struct flchip *chip, unsigned long adr, int mode)
|
||||
|
@ -893,7 +887,7 @@ static int get_chip(struct map_info *map, struct flchip *chip, unsigned long adr
|
|||
|
||||
case FL_STATUS:
|
||||
for (;;) {
|
||||
if (chip_ready(map, chip, adr))
|
||||
if (chip_ready(map, chip, adr, NULL))
|
||||
break;
|
||||
|
||||
if (time_after(jiffies, timeo)) {
|
||||
|
@ -932,7 +926,7 @@ static int get_chip(struct map_info *map, struct flchip *chip, unsigned long adr
|
|||
chip->state = FL_ERASE_SUSPENDING;
|
||||
chip->erase_suspended = 1;
|
||||
for (;;) {
|
||||
if (chip_ready(map, chip, adr))
|
||||
if (chip_ready(map, chip, adr, NULL))
|
||||
break;
|
||||
|
||||
if (time_after(jiffies, timeo)) {
|
||||
|
@ -1463,7 +1457,7 @@ static int do_otp_lock(struct map_info *map, struct flchip *chip, loff_t adr,
|
|||
/* wait for chip to become ready */
|
||||
timeo = jiffies + msecs_to_jiffies(2);
|
||||
for (;;) {
|
||||
if (chip_ready(map, chip, adr))
|
||||
if (chip_ready(map, chip, adr, NULL))
|
||||
break;
|
||||
|
||||
if (time_after(jiffies, timeo)) {
|
||||
|
@ -1699,7 +1693,7 @@ static int __xipram do_write_oneword_once(struct map_info *map,
|
|||
* "chip_good" to avoid the failure due to scheduling.
|
||||
*/
|
||||
if (time_after(jiffies, timeo) &&
|
||||
!chip_good(map, chip, adr, datum)) {
|
||||
!chip_good(map, chip, adr, &datum)) {
|
||||
xip_enable(map, chip, adr);
|
||||
printk(KERN_WARNING "MTD %s(): software timeout\n", __func__);
|
||||
xip_disable(map, chip, adr);
|
||||
|
@ -1707,7 +1701,7 @@ static int __xipram do_write_oneword_once(struct map_info *map,
|
|||
break;
|
||||
}
|
||||
|
||||
if (chip_good(map, chip, adr, datum)) {
|
||||
if (chip_good(map, chip, adr, &datum)) {
|
||||
if (cfi_check_err_status(map, chip, adr))
|
||||
ret = -EIO;
|
||||
break;
|
||||
|
@ -1979,14 +1973,14 @@ static int __xipram do_write_buffer_wait(struct map_info *map,
|
|||
* "chip_good" to avoid the failure due to scheduling.
|
||||
*/
|
||||
if (time_after(jiffies, timeo) &&
|
||||
!chip_good(map, chip, adr, datum)) {
|
||||
!chip_good(map, chip, adr, &datum)) {
|
||||
pr_err("MTD %s(): software timeout, address:0x%.8lx.\n",
|
||||
__func__, adr);
|
||||
ret = -EIO;
|
||||
break;
|
||||
}
|
||||
|
||||
if (chip_good(map, chip, adr, datum)) {
|
||||
if (chip_good(map, chip, adr, &datum)) {
|
||||
if (cfi_check_err_status(map, chip, adr))
|
||||
ret = -EIO;
|
||||
break;
|
||||
|
@ -2195,7 +2189,7 @@ static int cfi_amdstd_panic_wait(struct map_info *map, struct flchip *chip,
|
|||
* If the driver thinks the chip is idle, and no toggle bits
|
||||
* are changing, then the chip is actually idle for sure.
|
||||
*/
|
||||
if (chip->state == FL_READY && chip_ready(map, chip, adr))
|
||||
if (chip->state == FL_READY && chip_ready(map, chip, adr, NULL))
|
||||
return 0;
|
||||
|
||||
/*
|
||||
|
@ -2212,7 +2206,7 @@ static int cfi_amdstd_panic_wait(struct map_info *map, struct flchip *chip,
|
|||
|
||||
/* wait for the chip to become ready */
|
||||
for (i = 0; i < jiffies_to_usecs(timeo); i++) {
|
||||
if (chip_ready(map, chip, adr))
|
||||
if (chip_ready(map, chip, adr, NULL))
|
||||
return 0;
|
||||
|
||||
udelay(1);
|
||||
|
@ -2276,13 +2270,13 @@ static int do_panic_write_oneword(struct map_info *map, struct flchip *chip,
|
|||
map_write(map, datum, adr);
|
||||
|
||||
for (i = 0; i < jiffies_to_usecs(uWriteTimeout); i++) {
|
||||
if (chip_ready(map, chip, adr))
|
||||
if (chip_ready(map, chip, adr, NULL))
|
||||
break;
|
||||
|
||||
udelay(1);
|
||||
}
|
||||
|
||||
if (!chip_good(map, chip, adr, datum) ||
|
||||
if (!chip_ready(map, chip, adr, &datum) ||
|
||||
cfi_check_err_status(map, chip, adr)) {
|
||||
/* reset on all failures. */
|
||||
map_write(map, CMD(0xF0), chip->start);
|
||||
|
@ -2424,6 +2418,7 @@ static int __xipram do_erase_chip(struct map_info *map, struct flchip *chip)
|
|||
DECLARE_WAITQUEUE(wait, current);
|
||||
int ret;
|
||||
int retry_cnt = 0;
|
||||
map_word datum = map_word_ff(map);
|
||||
|
||||
adr = cfi->addr_unlock1;
|
||||
|
||||
|
@ -2478,7 +2473,7 @@ static int __xipram do_erase_chip(struct map_info *map, struct flchip *chip)
|
|||
chip->erase_suspended = 0;
|
||||
}
|
||||
|
||||
if (chip_good(map, chip, adr, map_word_ff(map))) {
|
||||
if (chip_ready(map, chip, adr, &datum)) {
|
||||
if (cfi_check_err_status(map, chip, adr))
|
||||
ret = -EIO;
|
||||
break;
|
||||
|
@ -2523,6 +2518,7 @@ static int __xipram do_erase_oneblock(struct map_info *map, struct flchip *chip,
|
|||
DECLARE_WAITQUEUE(wait, current);
|
||||
int ret;
|
||||
int retry_cnt = 0;
|
||||
map_word datum = map_word_ff(map);
|
||||
|
||||
adr += chip->start;
|
||||
|
||||
|
@ -2577,7 +2573,7 @@ static int __xipram do_erase_oneblock(struct map_info *map, struct flchip *chip,
|
|||
chip->erase_suspended = 0;
|
||||
}
|
||||
|
||||
if (chip_good(map, chip, adr, map_word_ff(map))) {
|
||||
if (chip_ready(map, chip, adr, &datum)) {
|
||||
if (cfi_check_err_status(map, chip, adr))
|
||||
ret = -EIO;
|
||||
break;
|
||||
|
@ -2771,7 +2767,7 @@ static int __maybe_unused do_ppb_xxlock(struct map_info *map,
|
|||
*/
|
||||
timeo = jiffies + msecs_to_jiffies(2000); /* 2s max (un)locking */
|
||||
for (;;) {
|
||||
if (chip_ready(map, chip, adr))
|
||||
if (chip_ready(map, chip, adr, NULL))
|
||||
break;
|
||||
|
||||
if (time_after(jiffies, timeo)) {
|
||||
|
|
|
@ -27,10 +27,14 @@
|
|||
#include <linux/slab.h>
|
||||
#include <linux/mtd/mtd.h>
|
||||
#include <asm/div64.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/of_address.h>
|
||||
#include <linux/of.h>
|
||||
|
||||
struct phram_mtd_list {
|
||||
struct mtd_info mtd;
|
||||
struct list_head list;
|
||||
bool cached;
|
||||
};
|
||||
|
||||
static LIST_HEAD(phram_list);
|
||||
|
@ -77,20 +81,51 @@ static int phram_write(struct mtd_info *mtd, loff_t to, size_t len,
|
|||
return 0;
|
||||
}
|
||||
|
||||
static int phram_map(struct phram_mtd_list *phram, phys_addr_t start, size_t len)
|
||||
{
|
||||
void *addr = NULL;
|
||||
|
||||
if (phram->cached)
|
||||
addr = memremap(start, len, MEMREMAP_WB);
|
||||
else
|
||||
addr = (void __force *)ioremap(start, len);
|
||||
if (!addr)
|
||||
return -EIO;
|
||||
|
||||
phram->mtd.priv = addr;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void phram_unmap(struct phram_mtd_list *phram)
|
||||
{
|
||||
void *addr = phram->mtd.priv;
|
||||
|
||||
if (phram->cached) {
|
||||
memunmap(addr);
|
||||
return;
|
||||
}
|
||||
|
||||
iounmap((void __iomem *)addr);
|
||||
}
|
||||
|
||||
static void unregister_devices(void)
|
||||
{
|
||||
struct phram_mtd_list *this, *safe;
|
||||
|
||||
list_for_each_entry_safe(this, safe, &phram_list, list) {
|
||||
mtd_device_unregister(&this->mtd);
|
||||
iounmap(this->mtd.priv);
|
||||
phram_unmap(this);
|
||||
kfree(this->mtd.name);
|
||||
kfree(this);
|
||||
}
|
||||
}
|
||||
|
||||
static int register_device(char *name, phys_addr_t start, size_t len, uint32_t erasesize)
|
||||
static int register_device(struct platform_device *pdev, const char *name,
|
||||
phys_addr_t start, size_t len, uint32_t erasesize)
|
||||
{
|
||||
struct device_node *np = pdev ? pdev->dev.of_node : NULL;
|
||||
bool cached = np ? !of_property_read_bool(np, "no-map") : false;
|
||||
struct phram_mtd_list *new;
|
||||
int ret = -ENOMEM;
|
||||
|
||||
|
@ -98,9 +133,10 @@ static int register_device(char *name, phys_addr_t start, size_t len, uint32_t e
|
|||
if (!new)
|
||||
goto out0;
|
||||
|
||||
ret = -EIO;
|
||||
new->mtd.priv = ioremap(start, len);
|
||||
if (!new->mtd.priv) {
|
||||
new->cached = cached;
|
||||
|
||||
ret = phram_map(new, start, len);
|
||||
if (ret) {
|
||||
pr_err("ioremap failed\n");
|
||||
goto out1;
|
||||
}
|
||||
|
@ -119,17 +155,23 @@ static int register_device(char *name, phys_addr_t start, size_t len, uint32_t e
|
|||
new->mtd.erasesize = erasesize;
|
||||
new->mtd.writesize = 1;
|
||||
|
||||
mtd_set_of_node(&new->mtd, np);
|
||||
|
||||
ret = -EAGAIN;
|
||||
if (mtd_device_register(&new->mtd, NULL, 0)) {
|
||||
pr_err("Failed to register new device\n");
|
||||
goto out2;
|
||||
}
|
||||
|
||||
list_add_tail(&new->list, &phram_list);
|
||||
if (pdev)
|
||||
platform_set_drvdata(pdev, new);
|
||||
else
|
||||
list_add_tail(&new->list, &phram_list);
|
||||
|
||||
return 0;
|
||||
|
||||
out2:
|
||||
iounmap(new->mtd.priv);
|
||||
phram_unmap(new);
|
||||
out1:
|
||||
kfree(new);
|
||||
out0:
|
||||
|
@ -278,7 +320,7 @@ static int phram_setup(const char *val)
|
|||
goto error;
|
||||
}
|
||||
|
||||
ret = register_device(name, start, len, (uint32_t)erasesize);
|
||||
ret = register_device(NULL, name, start, len, (uint32_t)erasesize);
|
||||
if (ret)
|
||||
goto error;
|
||||
|
||||
|
@ -325,10 +367,54 @@ static int phram_param_call(const char *val, const struct kernel_param *kp)
|
|||
module_param_call(phram, phram_param_call, NULL, NULL, 0200);
|
||||
MODULE_PARM_DESC(phram, "Memory region to map. \"phram=<name>,<start>,<length>[,<erasesize>]\"");
|
||||
|
||||
#ifdef CONFIG_OF
|
||||
static const struct of_device_id phram_of_match[] = {
|
||||
{ .compatible = "phram" },
|
||||
{}
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, phram_of_match);
|
||||
#endif
|
||||
|
||||
static int phram_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct resource *res;
|
||||
|
||||
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
if (!res)
|
||||
return -ENOMEM;
|
||||
|
||||
/* mtd_set_of_node() reads name from "label" */
|
||||
return register_device(pdev, NULL, res->start, resource_size(res),
|
||||
PAGE_SIZE);
|
||||
}
|
||||
|
||||
static int phram_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct phram_mtd_list *phram = platform_get_drvdata(pdev);
|
||||
|
||||
mtd_device_unregister(&phram->mtd);
|
||||
phram_unmap(phram);
|
||||
kfree(phram);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct platform_driver phram_driver = {
|
||||
.probe = phram_probe,
|
||||
.remove = phram_remove,
|
||||
.driver = {
|
||||
.name = "phram",
|
||||
.of_match_table = of_match_ptr(phram_of_match),
|
||||
},
|
||||
};
|
||||
|
||||
static int __init init_phram(void)
|
||||
{
|
||||
int ret = 0;
|
||||
int ret;
|
||||
|
||||
ret = platform_driver_register(&phram_driver);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
#ifndef MODULE
|
||||
if (phram_paramline[0])
|
||||
|
@ -336,12 +422,16 @@ static int __init init_phram(void)
|
|||
phram_init_called = 1;
|
||||
#endif
|
||||
|
||||
if (ret)
|
||||
platform_driver_unregister(&phram_driver);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void __exit cleanup_phram(void)
|
||||
{
|
||||
unregister_devices();
|
||||
platform_driver_unregister(&phram_driver);
|
||||
}
|
||||
|
||||
module_init(init_phram);
|
||||
|
|
|
@ -2126,6 +2126,8 @@ static int stfsm_remove(struct platform_device *pdev)
|
|||
{
|
||||
struct stfsm *fsm = platform_get_drvdata(pdev);
|
||||
|
||||
clk_disable_unprepare(fsm->clk);
|
||||
|
||||
return mtd_device_unregister(&fsm->mtd);
|
||||
}
|
||||
|
||||
|
|
|
@ -300,15 +300,6 @@ config MTD_DC21285
|
|||
21285 bridge used with Intel's StrongARM processors. More info at
|
||||
<https://www.intel.com/design/bridge/docs/21285_documentation.htm>.
|
||||
|
||||
config MTD_IXP4XX
|
||||
tristate "CFI Flash device mapped on Intel IXP4xx based systems"
|
||||
depends on MTD_CFI && MTD_COMPLEX_MAPPINGS && ARCH_IXP4XX && MTD_CFI_ADV_OPTIONS
|
||||
help
|
||||
This enables MTD access to flash devices on platforms based
|
||||
on Intel's IXP4xx family of network processors such as the
|
||||
IXDP425 and Coyote. If you have an IXP4xx based board and
|
||||
would like to use the flash chips on it, say 'Y'.
|
||||
|
||||
config MTD_IMPA7
|
||||
tristate "JEDEC Flash device mapped on impA7"
|
||||
depends on ARM && MTD_JEDECPROBE
|
||||
|
|
|
@ -39,7 +39,6 @@ obj-$(CONFIG_MTD_IMPA7) += impa7.o
|
|||
obj-$(CONFIG_MTD_UCLINUX) += uclinux.o
|
||||
obj-$(CONFIG_MTD_NETtel) += nettel.o
|
||||
obj-$(CONFIG_MTD_SCB2_FLASH) += scb2_flash.o
|
||||
obj-$(CONFIG_MTD_IXP4XX) += ixp4xx.o
|
||||
obj-$(CONFIG_MTD_PLATRAM) += plat-ram.o
|
||||
obj-$(CONFIG_MTD_INTEL_VR_NOR) += intel_vr_nor.o
|
||||
obj-$(CONFIG_MTD_VMU) += vmu-flash.o
|
||||
|
|
|
@ -1,262 +0,0 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* drivers/mtd/maps/ixp4xx.c
|
||||
*
|
||||
* MTD Map file for IXP4XX based systems. Please do not make per-board
|
||||
* changes in here. If your board needs special setup, do it in your
|
||||
* platform level code in arch/arm/mach-ixp4xx/board-setup.c
|
||||
*
|
||||
* Original Author: Intel Corporation
|
||||
* Maintainer: Deepak Saxena <dsaxena@mvista.com>
|
||||
*
|
||||
* Copyright (C) 2002 Intel Corporation
|
||||
* Copyright (C) 2003-2004 MontaVista Software, Inc.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/err.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/string.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/ioport.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/platform_device.h>
|
||||
|
||||
#include <linux/mtd/mtd.h>
|
||||
#include <linux/mtd/map.h>
|
||||
#include <linux/mtd/partitions.h>
|
||||
|
||||
#include <asm/io.h>
|
||||
#include <asm/mach/flash.h>
|
||||
|
||||
#include <linux/reboot.h>
|
||||
|
||||
/*
|
||||
* Read/write a 16 bit word from flash address 'addr'.
|
||||
*
|
||||
* When the cpu is in little-endian mode it swizzles the address lines
|
||||
* ('address coherency') so we need to undo the swizzling to ensure commands
|
||||
* and the like end up on the correct flash address.
|
||||
*
|
||||
* To further complicate matters, due to the way the expansion bus controller
|
||||
* handles 32 bit reads, the byte stream ABCD is stored on the flash as:
|
||||
* D15 D0
|
||||
* +---+---+
|
||||
* | A | B | 0
|
||||
* +---+---+
|
||||
* | C | D | 2
|
||||
* +---+---+
|
||||
* This means that on LE systems each 16 bit word must be swapped. Note that
|
||||
* this requires CONFIG_MTD_CFI_BE_BYTE_SWAP to be enabled to 'unswap' the CFI
|
||||
* data and other flash commands which are always in D7-D0.
|
||||
*/
|
||||
#ifndef __ARMEB__
|
||||
#ifndef CONFIG_MTD_CFI_BE_BYTE_SWAP
|
||||
# error CONFIG_MTD_CFI_BE_BYTE_SWAP required
|
||||
#endif
|
||||
|
||||
static inline u16 flash_read16(void __iomem *addr)
|
||||
{
|
||||
return be16_to_cpu(__raw_readw((void __iomem *)((unsigned long)addr ^ 0x2)));
|
||||
}
|
||||
|
||||
static inline void flash_write16(u16 d, void __iomem *addr)
|
||||
{
|
||||
__raw_writew(cpu_to_be16(d), (void __iomem *)((unsigned long)addr ^ 0x2));
|
||||
}
|
||||
|
||||
#define BYTE0(h) ((h) & 0xFF)
|
||||
#define BYTE1(h) (((h) >> 8) & 0xFF)
|
||||
|
||||
#else
|
||||
|
||||
static inline u16 flash_read16(const void __iomem *addr)
|
||||
{
|
||||
return __raw_readw(addr);
|
||||
}
|
||||
|
||||
static inline void flash_write16(u16 d, void __iomem *addr)
|
||||
{
|
||||
__raw_writew(d, addr);
|
||||
}
|
||||
|
||||
#define BYTE0(h) (((h) >> 8) & 0xFF)
|
||||
#define BYTE1(h) ((h) & 0xFF)
|
||||
#endif
|
||||
|
||||
static map_word ixp4xx_read16(struct map_info *map, unsigned long ofs)
|
||||
{
|
||||
map_word val;
|
||||
val.x[0] = flash_read16(map->virt + ofs);
|
||||
return val;
|
||||
}
|
||||
|
||||
/*
|
||||
* The IXP4xx expansion bus only allows 16-bit wide acceses
|
||||
* when attached to a 16-bit wide device (such as the 28F128J3A),
|
||||
* so we can't just memcpy_fromio().
|
||||
*/
|
||||
static void ixp4xx_copy_from(struct map_info *map, void *to,
|
||||
unsigned long from, ssize_t len)
|
||||
{
|
||||
u8 *dest = (u8 *) to;
|
||||
void __iomem *src = map->virt + from;
|
||||
|
||||
if (len <= 0)
|
||||
return;
|
||||
|
||||
if (from & 1) {
|
||||
*dest++ = BYTE1(flash_read16(src-1));
|
||||
src++;
|
||||
--len;
|
||||
}
|
||||
|
||||
while (len >= 2) {
|
||||
u16 data = flash_read16(src);
|
||||
*dest++ = BYTE0(data);
|
||||
*dest++ = BYTE1(data);
|
||||
src += 2;
|
||||
len -= 2;
|
||||
}
|
||||
|
||||
if (len > 0)
|
||||
*dest++ = BYTE0(flash_read16(src));
|
||||
}
|
||||
|
||||
/*
|
||||
* Unaligned writes are ignored, causing the 8-bit
|
||||
* probe to fail and proceed to the 16-bit probe (which succeeds).
|
||||
*/
|
||||
static void ixp4xx_probe_write16(struct map_info *map, map_word d, unsigned long adr)
|
||||
{
|
||||
if (!(adr & 1))
|
||||
flash_write16(d.x[0], map->virt + adr);
|
||||
}
|
||||
|
||||
/*
|
||||
* Fast write16 function without the probing check above
|
||||
*/
|
||||
static void ixp4xx_write16(struct map_info *map, map_word d, unsigned long adr)
|
||||
{
|
||||
flash_write16(d.x[0], map->virt + adr);
|
||||
}
|
||||
|
||||
struct ixp4xx_flash_info {
|
||||
struct mtd_info *mtd;
|
||||
struct map_info map;
|
||||
struct resource *res;
|
||||
};
|
||||
|
||||
static const char * const probes[] = { "RedBoot", "cmdlinepart", NULL };
|
||||
|
||||
static int ixp4xx_flash_remove(struct platform_device *dev)
|
||||
{
|
||||
struct flash_platform_data *plat = dev_get_platdata(&dev->dev);
|
||||
struct ixp4xx_flash_info *info = platform_get_drvdata(dev);
|
||||
|
||||
if(!info)
|
||||
return 0;
|
||||
|
||||
if (info->mtd) {
|
||||
mtd_device_unregister(info->mtd);
|
||||
map_destroy(info->mtd);
|
||||
}
|
||||
|
||||
if (plat->exit)
|
||||
plat->exit();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ixp4xx_flash_probe(struct platform_device *dev)
|
||||
{
|
||||
struct flash_platform_data *plat = dev_get_platdata(&dev->dev);
|
||||
struct ixp4xx_flash_info *info;
|
||||
struct mtd_part_parser_data ppdata = {
|
||||
.origin = dev->resource->start,
|
||||
};
|
||||
int err = -1;
|
||||
|
||||
if (!plat)
|
||||
return -ENODEV;
|
||||
|
||||
if (plat->init) {
|
||||
err = plat->init();
|
||||
if (err)
|
||||
return err;
|
||||
}
|
||||
|
||||
info = devm_kzalloc(&dev->dev, sizeof(struct ixp4xx_flash_info),
|
||||
GFP_KERNEL);
|
||||
if(!info) {
|
||||
err = -ENOMEM;
|
||||
goto Error;
|
||||
}
|
||||
|
||||
platform_set_drvdata(dev, info);
|
||||
|
||||
/*
|
||||
* Tell the MTD layer we're not 1:1 mapped so that it does
|
||||
* not attempt to do a direct access on us.
|
||||
*/
|
||||
info->map.phys = NO_XIP;
|
||||
info->map.size = resource_size(dev->resource);
|
||||
|
||||
/*
|
||||
* We only support 16-bit accesses for now. If and when
|
||||
* any board use 8-bit access, we'll fixup the driver to
|
||||
* handle that.
|
||||
*/
|
||||
info->map.bankwidth = 2;
|
||||
info->map.name = dev_name(&dev->dev);
|
||||
info->map.read = ixp4xx_read16;
|
||||
info->map.write = ixp4xx_probe_write16;
|
||||
info->map.copy_from = ixp4xx_copy_from;
|
||||
|
||||
info->map.virt = devm_ioremap_resource(&dev->dev, dev->resource);
|
||||
if (IS_ERR(info->map.virt)) {
|
||||
err = PTR_ERR(info->map.virt);
|
||||
goto Error;
|
||||
}
|
||||
|
||||
info->mtd = do_map_probe(plat->map_name, &info->map);
|
||||
if (!info->mtd) {
|
||||
printk(KERN_ERR "IXP4XXFlash: map_probe failed\n");
|
||||
err = -ENXIO;
|
||||
goto Error;
|
||||
}
|
||||
info->mtd->dev.parent = &dev->dev;
|
||||
|
||||
/* Use the fast version */
|
||||
info->map.write = ixp4xx_write16;
|
||||
|
||||
err = mtd_device_parse_register(info->mtd, probes, &ppdata,
|
||||
plat->parts, plat->nr_parts);
|
||||
if (err) {
|
||||
printk(KERN_ERR "Could not parse partitions\n");
|
||||
goto Error;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
Error:
|
||||
ixp4xx_flash_remove(dev);
|
||||
return err;
|
||||
}
|
||||
|
||||
static struct platform_driver ixp4xx_flash_driver = {
|
||||
.probe = ixp4xx_flash_probe,
|
||||
.remove = ixp4xx_flash_remove,
|
||||
.driver = {
|
||||
.name = "IXP4XX-Flash",
|
||||
},
|
||||
};
|
||||
|
||||
module_platform_driver(ixp4xx_flash_driver);
|
||||
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_DESCRIPTION("MTD map driver for Intel IXP4xx systems");
|
||||
MODULE_AUTHOR("Deepak Saxena");
|
||||
MODULE_ALIAS("platform:IXP4XX-Flash");
|
|
@ -257,6 +257,10 @@ static int mtdblock_open(struct mtd_blktrans_dev *mbd)
|
|||
return 0;
|
||||
}
|
||||
|
||||
if (mtd_type_is_nand(mbd->mtd))
|
||||
pr_warn("%s: MTD device '%s' is NAND, please consider using UBI block devices instead.\n",
|
||||
mbd->tr->name, mbd->mtd->name);
|
||||
|
||||
/* OK, it's not open. Create cache info for it */
|
||||
mtdblk->count = 1;
|
||||
mutex_init(&mtdblk->cache_mutex);
|
||||
|
@ -322,10 +326,6 @@ static void mtdblock_add_mtd(struct mtd_blktrans_ops *tr, struct mtd_info *mtd)
|
|||
if (!(mtd->flags & MTD_WRITEABLE))
|
||||
dev->mbd.readonly = 1;
|
||||
|
||||
if (mtd_type_is_nand(mtd))
|
||||
pr_warn("%s: MTD device '%s' is NAND, please consider using UBI block devices instead.\n",
|
||||
tr->name, mtd->name);
|
||||
|
||||
if (add_mtd_blktrans_dev(&dev->mbd))
|
||||
kfree(dev);
|
||||
}
|
||||
|
|
|
@ -557,9 +557,10 @@ static int mtd_nvmem_add(struct mtd_info *mtd)
|
|||
|
||||
int add_mtd_device(struct mtd_info *mtd)
|
||||
{
|
||||
struct device_node *np = mtd_get_of_node(mtd);
|
||||
struct mtd_info *master = mtd_get_master(mtd);
|
||||
struct mtd_notifier *not;
|
||||
int i, error;
|
||||
int i, error, ofidx;
|
||||
|
||||
/*
|
||||
* May occur, for instance, on buggy drivers which call
|
||||
|
@ -598,7 +599,13 @@ int add_mtd_device(struct mtd_info *mtd)
|
|||
|
||||
mutex_lock(&mtd_table_mutex);
|
||||
|
||||
i = idr_alloc(&mtd_idr, mtd, 0, 0, GFP_KERNEL);
|
||||
ofidx = -1;
|
||||
if (np)
|
||||
ofidx = of_alias_get_id(np, "mtd");
|
||||
if (ofidx >= 0)
|
||||
i = idr_alloc(&mtd_idr, mtd, ofidx, ofidx + 1, GFP_KERNEL);
|
||||
else
|
||||
i = idr_alloc(&mtd_idr, mtd, 0, 0, GFP_KERNEL);
|
||||
if (i < 0) {
|
||||
error = i;
|
||||
goto fail_locked;
|
||||
|
|
|
@ -16,15 +16,13 @@
|
|||
#include <linux/wait.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/timekeeping.h>
|
||||
#include <linux/mtd/mtd.h>
|
||||
#include <linux/kmsg_dump.h>
|
||||
|
||||
/* Maximum MTD partition size */
|
||||
#define MTDOOPS_MAX_MTD_SIZE (8 * 1024 * 1024)
|
||||
|
||||
#define MTDOOPS_KERNMSG_MAGIC 0x5d005d00
|
||||
#define MTDOOPS_HEADER_SIZE 8
|
||||
|
||||
static unsigned long record_size = 4096;
|
||||
module_param(record_size, ulong, 0400);
|
||||
MODULE_PARM_DESC(record_size,
|
||||
|
@ -40,6 +38,15 @@ module_param(dump_oops, int, 0600);
|
|||
MODULE_PARM_DESC(dump_oops,
|
||||
"set to 1 to dump oopses, 0 to only dump panics (default 1)");
|
||||
|
||||
#define MTDOOPS_KERNMSG_MAGIC_v1 0x5d005d00 /* Original */
|
||||
#define MTDOOPS_KERNMSG_MAGIC_v2 0x5d005e00 /* Adds the timestamp */
|
||||
|
||||
struct mtdoops_hdr {
|
||||
u32 seq;
|
||||
u32 magic;
|
||||
ktime_t timestamp;
|
||||
} __packed;
|
||||
|
||||
static struct mtdoops_context {
|
||||
struct kmsg_dumper dump;
|
||||
|
||||
|
@ -178,16 +185,17 @@ static void mtdoops_write(struct mtdoops_context *cxt, int panic)
|
|||
{
|
||||
struct mtd_info *mtd = cxt->mtd;
|
||||
size_t retlen;
|
||||
u32 *hdr;
|
||||
struct mtdoops_hdr *hdr;
|
||||
int ret;
|
||||
|
||||
if (test_and_set_bit(0, &cxt->oops_buf_busy))
|
||||
return;
|
||||
|
||||
/* Add mtdoops header to the buffer */
|
||||
hdr = cxt->oops_buf;
|
||||
hdr[0] = cxt->nextcount;
|
||||
hdr[1] = MTDOOPS_KERNMSG_MAGIC;
|
||||
hdr = (struct mtdoops_hdr *)cxt->oops_buf;
|
||||
hdr->seq = cxt->nextcount;
|
||||
hdr->magic = MTDOOPS_KERNMSG_MAGIC_v2;
|
||||
hdr->timestamp = ktime_get_real();
|
||||
|
||||
if (panic) {
|
||||
ret = mtd_panic_write(mtd, cxt->nextpage * record_size,
|
||||
|
@ -222,8 +230,9 @@ static void mtdoops_workfunc_write(struct work_struct *work)
|
|||
static void find_next_position(struct mtdoops_context *cxt)
|
||||
{
|
||||
struct mtd_info *mtd = cxt->mtd;
|
||||
struct mtdoops_hdr hdr;
|
||||
int ret, page, maxpos = 0;
|
||||
u32 count[2], maxcount = 0xffffffff;
|
||||
u32 maxcount = 0xffffffff;
|
||||
size_t retlen;
|
||||
|
||||
for (page = 0; page < cxt->oops_pages; page++) {
|
||||
|
@ -231,32 +240,33 @@ static void find_next_position(struct mtdoops_context *cxt)
|
|||
continue;
|
||||
/* Assume the page is used */
|
||||
mark_page_used(cxt, page);
|
||||
ret = mtd_read(mtd, page * record_size, MTDOOPS_HEADER_SIZE,
|
||||
&retlen, (u_char *)&count[0]);
|
||||
if (retlen != MTDOOPS_HEADER_SIZE ||
|
||||
ret = mtd_read(mtd, page * record_size, sizeof(hdr),
|
||||
&retlen, (u_char *)&hdr);
|
||||
if (retlen != sizeof(hdr) ||
|
||||
(ret < 0 && !mtd_is_bitflip(ret))) {
|
||||
printk(KERN_ERR "mtdoops: read failure at %ld (%td of %d read), err %d\n",
|
||||
page * record_size, retlen,
|
||||
MTDOOPS_HEADER_SIZE, ret);
|
||||
printk(KERN_ERR "mtdoops: read failure at %ld (%zu of %zu read), err %d\n",
|
||||
page * record_size, retlen, sizeof(hdr), ret);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (count[0] == 0xffffffff && count[1] == 0xffffffff)
|
||||
if (hdr.seq == 0xffffffff && hdr.magic == 0xffffffff)
|
||||
mark_page_unused(cxt, page);
|
||||
if (count[0] == 0xffffffff || count[1] != MTDOOPS_KERNMSG_MAGIC)
|
||||
if (hdr.seq == 0xffffffff ||
|
||||
(hdr.magic != MTDOOPS_KERNMSG_MAGIC_v1 &&
|
||||
hdr.magic != MTDOOPS_KERNMSG_MAGIC_v2))
|
||||
continue;
|
||||
if (maxcount == 0xffffffff) {
|
||||
maxcount = count[0];
|
||||
maxcount = hdr.seq;
|
||||
maxpos = page;
|
||||
} else if (count[0] < 0x40000000 && maxcount > 0xc0000000) {
|
||||
maxcount = count[0];
|
||||
} else if (hdr.seq < 0x40000000 && maxcount > 0xc0000000) {
|
||||
maxcount = hdr.seq;
|
||||
maxpos = page;
|
||||
} else if (count[0] > maxcount && count[0] < 0xc0000000) {
|
||||
maxcount = count[0];
|
||||
} else if (hdr.seq > maxcount && hdr.seq < 0xc0000000) {
|
||||
maxcount = hdr.seq;
|
||||
maxpos = page;
|
||||
} else if (count[0] > maxcount && count[0] > 0xc0000000
|
||||
} else if (hdr.seq > maxcount && hdr.seq > 0xc0000000
|
||||
&& maxcount > 0x80000000) {
|
||||
maxcount = count[0];
|
||||
maxcount = hdr.seq;
|
||||
maxpos = page;
|
||||
}
|
||||
}
|
||||
|
@ -287,8 +297,9 @@ static void mtdoops_do_dump(struct kmsg_dumper *dumper,
|
|||
|
||||
if (test_and_set_bit(0, &cxt->oops_buf_busy))
|
||||
return;
|
||||
kmsg_dump_get_buffer(&iter, true, cxt->oops_buf + MTDOOPS_HEADER_SIZE,
|
||||
record_size - MTDOOPS_HEADER_SIZE, NULL);
|
||||
kmsg_dump_get_buffer(&iter, true,
|
||||
cxt->oops_buf + sizeof(struct mtdoops_hdr),
|
||||
record_size - sizeof(struct mtdoops_hdr), NULL);
|
||||
clear_bit(0, &cxt->oops_buf_busy);
|
||||
|
||||
if (reason != KMSG_DUMP_OOPS) {
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
#include <linux/mtd/partitions.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/of_platform.h>
|
||||
|
||||
#include "mtdcore.h"
|
||||
|
||||
|
@ -577,10 +578,16 @@ static int mtd_part_of_parse(struct mtd_info *master,
|
|||
struct mtd_part_parser *parser;
|
||||
struct device_node *np;
|
||||
struct property *prop;
|
||||
struct device *dev;
|
||||
const char *compat;
|
||||
const char *fixed = "fixed-partitions";
|
||||
int ret, err = 0;
|
||||
|
||||
dev = &master->dev;
|
||||
/* Use parent device (controller) if the top level MTD is not registered */
|
||||
if (!IS_ENABLED(CONFIG_MTD_PARTITIONED_MASTER) && !mtd_is_partition(master))
|
||||
dev = master->dev.parent;
|
||||
|
||||
np = mtd_get_of_node(master);
|
||||
if (mtd_is_partition(master))
|
||||
of_node_get(np);
|
||||
|
@ -593,6 +600,7 @@ static int mtd_part_of_parse(struct mtd_info *master,
|
|||
continue;
|
||||
ret = mtd_part_do_parse(parser, master, pparts, NULL);
|
||||
if (ret > 0) {
|
||||
of_platform_populate(np, NULL, NULL, dev);
|
||||
of_node_put(np);
|
||||
return ret;
|
||||
}
|
||||
|
@ -600,6 +608,7 @@ static int mtd_part_of_parse(struct mtd_info *master,
|
|||
if (ret < 0 && !err)
|
||||
err = ret;
|
||||
}
|
||||
of_platform_populate(np, NULL, NULL, dev);
|
||||
of_node_put(np);
|
||||
|
||||
/*
|
||||
|
|
|
@ -53,6 +53,14 @@ config MTD_NAND_ECC_MXIC
|
|||
help
|
||||
This enables support for the hardware ECC engine from Macronix.
|
||||
|
||||
config MTD_NAND_ECC_MEDIATEK
|
||||
tristate "Mediatek hardware ECC engine"
|
||||
depends on HAS_IOMEM
|
||||
depends on ARCH_MEDIATEK || COMPILE_TEST
|
||||
select MTD_NAND_ECC
|
||||
help
|
||||
This enables support for the hardware ECC engine from Mediatek.
|
||||
|
||||
endmenu
|
||||
|
||||
endmenu
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
nandcore-objs := core.o bbt.o
|
||||
obj-$(CONFIG_MTD_NAND_CORE) += nandcore.o
|
||||
obj-$(CONFIG_MTD_NAND_ECC_MEDIATEK) += ecc-mtk.o
|
||||
|
||||
obj-y += onenand/
|
||||
obj-y += raw/
|
||||
|
|
|
@ -15,8 +15,7 @@
|
|||
#include <linux/of.h>
|
||||
#include <linux/of_platform.h>
|
||||
#include <linux/mutex.h>
|
||||
|
||||
#include "mtk_ecc.h"
|
||||
#include <linux/mtd/nand-ecc-mtk.h>
|
||||
|
||||
#define ECC_IDLE_MASK BIT(0)
|
||||
#define ECC_IRQ_EN BIT(0)
|
||||
|
@ -280,7 +279,10 @@ struct mtk_ecc *of_mtk_ecc_get(struct device_node *of_node)
|
|||
struct mtk_ecc *ecc = NULL;
|
||||
struct device_node *np;
|
||||
|
||||
np = of_parse_phandle(of_node, "ecc-engine", 0);
|
||||
np = of_parse_phandle(of_node, "nand-ecc-engine", 0);
|
||||
/* for backward compatibility */
|
||||
if (!np)
|
||||
np = of_parse_phandle(of_node, "ecc-engine", 0);
|
||||
if (np) {
|
||||
ecc = mtk_ecc_get(np);
|
||||
of_node_put(np);
|
|
@ -374,6 +374,7 @@ config MTD_NAND_QCOM
|
|||
|
||||
config MTD_NAND_MTK
|
||||
tristate "MTK NAND controller"
|
||||
depends on MTD_NAND_ECC_MEDIATEK
|
||||
depends on ARCH_MEDIATEK || COMPILE_TEST
|
||||
depends on HAS_IOMEM
|
||||
help
|
||||
|
|
|
@ -48,7 +48,7 @@ obj-$(CONFIG_MTD_NAND_SUNXI) += sunxi_nand.o
|
|||
obj-$(CONFIG_MTD_NAND_HISI504) += hisi504_nand.o
|
||||
obj-$(CONFIG_MTD_NAND_BRCMNAND) += brcmnand/
|
||||
obj-$(CONFIG_MTD_NAND_QCOM) += qcom_nandc.o
|
||||
obj-$(CONFIG_MTD_NAND_MTK) += mtk_ecc.o mtk_nand.o
|
||||
obj-$(CONFIG_MTD_NAND_MTK) += mtk_nand.o
|
||||
obj-$(CONFIG_MTD_NAND_MXIC) += mxic_nand.o
|
||||
obj-$(CONFIG_MTD_NAND_TEGRA) += tegra_nand.o
|
||||
obj-$(CONFIG_MTD_NAND_STM32_FMC2) += stm32_fmc2_nand.o
|
||||
|
|
|
@ -2983,11 +2983,10 @@ static int cadence_nand_dt_probe(struct platform_device *ofdev)
|
|||
if (IS_ERR(cdns_ctrl->reg))
|
||||
return PTR_ERR(cdns_ctrl->reg);
|
||||
|
||||
res = platform_get_resource(ofdev, IORESOURCE_MEM, 1);
|
||||
cdns_ctrl->io.dma = res->start;
|
||||
cdns_ctrl->io.virt = devm_ioremap_resource(&ofdev->dev, res);
|
||||
cdns_ctrl->io.virt = devm_platform_get_and_ioremap_resource(ofdev, 1, &res);
|
||||
if (IS_ERR(cdns_ctrl->io.virt))
|
||||
return PTR_ERR(cdns_ctrl->io.virt);
|
||||
cdns_ctrl->io.dma = res->start;
|
||||
|
||||
dt->clk = devm_clk_get(cdns_ctrl->dev, "nf_clk");
|
||||
if (IS_ERR(dt->clk))
|
||||
|
|
|
@ -104,17 +104,12 @@ static int cs553x_write_ctrl_byte(struct cs553x_nand_controller *cs553x,
|
|||
u32 ctl, u8 data)
|
||||
{
|
||||
u8 status;
|
||||
int ret;
|
||||
|
||||
writeb(ctl, cs553x->mmio + MM_NAND_CTL);
|
||||
writeb(data, cs553x->mmio + MM_NAND_IO);
|
||||
ret = readb_poll_timeout_atomic(cs553x->mmio + MM_NAND_STS, status,
|
||||
return readb_poll_timeout_atomic(cs553x->mmio + MM_NAND_STS, status,
|
||||
!(status & CS_NAND_CTLR_BUSY), 1,
|
||||
100000);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void cs553x_data_in(struct cs553x_nand_controller *cs553x, void *buf,
|
||||
|
|
|
@ -727,7 +727,7 @@ static int nand_davinci_probe(struct platform_device *pdev)
|
|||
return -ENODEV;
|
||||
|
||||
/* which external chipselect will we be managing? */
|
||||
if (pdata->core_chipsel < 0 || pdata->core_chipsel > 3)
|
||||
if (pdata->core_chipsel > 3)
|
||||
return -ENODEV;
|
||||
|
||||
info = devm_kzalloc(&pdev->dev, sizeof(*info), GFP_KERNEL);
|
||||
|
|
|
@ -74,22 +74,21 @@ static int denali_pci_probe(struct pci_dev *dev, const struct pci_device_id *id)
|
|||
return ret;
|
||||
}
|
||||
|
||||
denali->reg = ioremap(csr_base, csr_len);
|
||||
denali->reg = devm_ioremap(denali->dev, csr_base, csr_len);
|
||||
if (!denali->reg) {
|
||||
dev_err(&dev->dev, "Spectra: Unable to remap memory region\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
denali->host = ioremap(mem_base, mem_len);
|
||||
denali->host = devm_ioremap(denali->dev, mem_base, mem_len);
|
||||
if (!denali->host) {
|
||||
dev_err(&dev->dev, "Spectra: ioremap failed!");
|
||||
ret = -ENOMEM;
|
||||
goto out_unmap_reg;
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
ret = denali_init(denali);
|
||||
if (ret)
|
||||
goto out_unmap_host;
|
||||
return ret;
|
||||
|
||||
nsels = denali->nbanks;
|
||||
|
||||
|
@ -117,10 +116,6 @@ static int denali_pci_probe(struct pci_dev *dev, const struct pci_device_id *id)
|
|||
|
||||
out_remove_denali:
|
||||
denali_remove(denali);
|
||||
out_unmap_host:
|
||||
iounmap(denali->host);
|
||||
out_unmap_reg:
|
||||
iounmap(denali->reg);
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
@ -129,8 +124,6 @@ static void denali_pci_remove(struct pci_dev *dev)
|
|||
struct denali_controller *denali = pci_get_drvdata(dev);
|
||||
|
||||
denali_remove(denali);
|
||||
iounmap(denali->reg);
|
||||
iounmap(denali->host);
|
||||
}
|
||||
|
||||
static struct pci_driver denali_pci_driver = {
|
||||
|
|
|
@ -218,7 +218,8 @@ static void gpmi_dump_info(struct gpmi_nand_data *this)
|
|||
"ECC Strength : %u\n"
|
||||
"Page Size in Bytes : %u\n"
|
||||
"Metadata Size in Bytes : %u\n"
|
||||
"ECC Chunk Size in Bytes: %u\n"
|
||||
"ECC0 Chunk Size in Bytes: %u\n"
|
||||
"ECCn Chunk Size in Bytes: %u\n"
|
||||
"ECC Chunk Count : %u\n"
|
||||
"Payload Size in Bytes : %u\n"
|
||||
"Auxiliary Size in Bytes: %u\n"
|
||||
|
@ -229,7 +230,8 @@ static void gpmi_dump_info(struct gpmi_nand_data *this)
|
|||
geo->ecc_strength,
|
||||
geo->page_size,
|
||||
geo->metadata_size,
|
||||
geo->ecc_chunk_size,
|
||||
geo->ecc0_chunk_size,
|
||||
geo->eccn_chunk_size,
|
||||
geo->ecc_chunk_count,
|
||||
geo->payload_size,
|
||||
geo->auxiliary_size,
|
||||
|
@ -238,9 +240,15 @@ static void gpmi_dump_info(struct gpmi_nand_data *this)
|
|||
geo->block_mark_bit_offset);
|
||||
}
|
||||
|
||||
static inline bool gpmi_check_ecc(struct gpmi_nand_data *this)
|
||||
static bool gpmi_check_ecc(struct gpmi_nand_data *this)
|
||||
{
|
||||
struct nand_chip *chip = &this->nand;
|
||||
struct bch_geometry *geo = &this->bch_geometry;
|
||||
struct nand_device *nand = &chip->base;
|
||||
struct nand_ecc_props *conf = &nand->ecc.ctx.conf;
|
||||
|
||||
conf->step_size = geo->eccn_chunk_size;
|
||||
conf->strength = geo->ecc_strength;
|
||||
|
||||
/* Do the sanity check. */
|
||||
if (GPMI_IS_MXS(this)) {
|
||||
|
@ -248,7 +256,47 @@ static inline bool gpmi_check_ecc(struct gpmi_nand_data *this)
|
|||
if (geo->gf_len == 14)
|
||||
return false;
|
||||
}
|
||||
return geo->ecc_strength <= this->devdata->bch_max_ecc_strength;
|
||||
|
||||
if (geo->ecc_strength > this->devdata->bch_max_ecc_strength)
|
||||
return false;
|
||||
|
||||
if (!nand_ecc_is_strong_enough(nand))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/* check if bbm locates in data chunk rather than ecc chunk */
|
||||
static bool bbm_in_data_chunk(struct gpmi_nand_data *this,
|
||||
unsigned int *chunk_num)
|
||||
{
|
||||
struct bch_geometry *geo = &this->bch_geometry;
|
||||
struct nand_chip *chip = &this->nand;
|
||||
struct mtd_info *mtd = nand_to_mtd(chip);
|
||||
unsigned int i, j;
|
||||
|
||||
if (geo->ecc0_chunk_size != geo->eccn_chunk_size) {
|
||||
dev_err(this->dev,
|
||||
"The size of ecc0_chunk must equal to eccn_chunk\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
i = (mtd->writesize * 8 - geo->metadata_size * 8) /
|
||||
(geo->gf_len * geo->ecc_strength +
|
||||
geo->eccn_chunk_size * 8);
|
||||
|
||||
j = (mtd->writesize * 8 - geo->metadata_size * 8) -
|
||||
(geo->gf_len * geo->ecc_strength +
|
||||
geo->eccn_chunk_size * 8) * i;
|
||||
|
||||
if (j < geo->eccn_chunk_size * 8) {
|
||||
*chunk_num = i+1;
|
||||
dev_dbg(this->dev, "Set ecc to %d and bbm in chunk %d\n",
|
||||
geo->ecc_strength, *chunk_num);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -280,13 +328,14 @@ static int set_geometry_by_ecc_info(struct gpmi_nand_data *this,
|
|||
nanddev_get_ecc_requirements(&chip->base)->step_size);
|
||||
return -EINVAL;
|
||||
}
|
||||
geo->ecc_chunk_size = ecc_step;
|
||||
geo->ecc0_chunk_size = ecc_step;
|
||||
geo->eccn_chunk_size = ecc_step;
|
||||
geo->ecc_strength = round_up(ecc_strength, 2);
|
||||
if (!gpmi_check_ecc(this))
|
||||
return -EINVAL;
|
||||
|
||||
/* Keep the C >= O */
|
||||
if (geo->ecc_chunk_size < mtd->oobsize) {
|
||||
if (geo->eccn_chunk_size < mtd->oobsize) {
|
||||
dev_err(this->dev,
|
||||
"unsupported nand chip. ecc size: %d, oob size : %d\n",
|
||||
ecc_step, mtd->oobsize);
|
||||
|
@ -296,7 +345,7 @@ static int set_geometry_by_ecc_info(struct gpmi_nand_data *this,
|
|||
/* The default value, see comment in the legacy_set_geometry(). */
|
||||
geo->metadata_size = 10;
|
||||
|
||||
geo->ecc_chunk_count = mtd->writesize / geo->ecc_chunk_size;
|
||||
geo->ecc_chunk_count = mtd->writesize / geo->eccn_chunk_size;
|
||||
|
||||
/*
|
||||
* Now, the NAND chip with 2K page(data chunk is 512byte) shows below:
|
||||
|
@ -399,6 +448,134 @@ static inline int get_ecc_strength(struct gpmi_nand_data *this)
|
|||
return round_down(ecc_strength, 2);
|
||||
}
|
||||
|
||||
static int set_geometry_for_large_oob(struct gpmi_nand_data *this)
|
||||
{
|
||||
struct bch_geometry *geo = &this->bch_geometry;
|
||||
struct nand_chip *chip = &this->nand;
|
||||
struct mtd_info *mtd = nand_to_mtd(chip);
|
||||
const struct nand_ecc_props *requirements =
|
||||
nanddev_get_ecc_requirements(&chip->base);
|
||||
unsigned int block_mark_bit_offset;
|
||||
unsigned int max_ecc;
|
||||
unsigned int bbm_chunk;
|
||||
unsigned int i;
|
||||
|
||||
/* sanity check for the minimum ecc nand required */
|
||||
if (!(requirements->strength > 0 &&
|
||||
requirements->step_size > 0))
|
||||
return -EINVAL;
|
||||
geo->ecc_strength = requirements->strength;
|
||||
|
||||
/* check if platform can support this nand */
|
||||
if (!gpmi_check_ecc(this)) {
|
||||
dev_err(this->dev,
|
||||
"unsupported NAND chip, minimum ecc required %d\n",
|
||||
geo->ecc_strength);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* calculate the maximum ecc platform can support*/
|
||||
geo->metadata_size = 10;
|
||||
geo->gf_len = 14;
|
||||
geo->ecc0_chunk_size = 1024;
|
||||
geo->eccn_chunk_size = 1024;
|
||||
geo->ecc_chunk_count = mtd->writesize / geo->eccn_chunk_size;
|
||||
max_ecc = min(get_ecc_strength(this),
|
||||
this->devdata->bch_max_ecc_strength);
|
||||
|
||||
/*
|
||||
* search a supported ecc strength that makes bbm
|
||||
* located in data chunk
|
||||
*/
|
||||
geo->ecc_strength = max_ecc;
|
||||
while (!(geo->ecc_strength < requirements->strength)) {
|
||||
if (bbm_in_data_chunk(this, &bbm_chunk))
|
||||
goto geo_setting;
|
||||
geo->ecc_strength -= 2;
|
||||
}
|
||||
|
||||
/* if none of them works, keep using the minimum ecc */
|
||||
/* nand required but changing ecc page layout */
|
||||
geo->ecc_strength = requirements->strength;
|
||||
/* add extra ecc for meta data */
|
||||
geo->ecc0_chunk_size = 0;
|
||||
geo->ecc_chunk_count = (mtd->writesize / geo->eccn_chunk_size) + 1;
|
||||
geo->ecc_for_meta = 1;
|
||||
/* check if oob can afford this extra ecc chunk */
|
||||
if (mtd->oobsize * 8 < geo->metadata_size * 8 +
|
||||
geo->gf_len * geo->ecc_strength * geo->ecc_chunk_count) {
|
||||
dev_err(this->dev, "unsupported NAND chip with new layout\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* calculate in which chunk bbm located */
|
||||
bbm_chunk = (mtd->writesize * 8 - geo->metadata_size * 8 -
|
||||
geo->gf_len * geo->ecc_strength) /
|
||||
(geo->gf_len * geo->ecc_strength +
|
||||
geo->eccn_chunk_size * 8) + 1;
|
||||
|
||||
geo_setting:
|
||||
|
||||
geo->page_size = mtd->writesize + geo->metadata_size +
|
||||
(geo->gf_len * geo->ecc_strength * geo->ecc_chunk_count) / 8;
|
||||
geo->payload_size = mtd->writesize;
|
||||
|
||||
/*
|
||||
* The auxiliary buffer contains the metadata and the ECC status. The
|
||||
* metadata is padded to the nearest 32-bit boundary. The ECC status
|
||||
* contains one byte for every ECC chunk, and is also padded to the
|
||||
* nearest 32-bit boundary.
|
||||
*/
|
||||
geo->auxiliary_status_offset = ALIGN(geo->metadata_size, 4);
|
||||
geo->auxiliary_size = ALIGN(geo->metadata_size, 4)
|
||||
+ ALIGN(geo->ecc_chunk_count, 4);
|
||||
|
||||
if (!this->swap_block_mark)
|
||||
return 0;
|
||||
|
||||
/* calculate the number of ecc chunk behind the bbm */
|
||||
i = (mtd->writesize / geo->eccn_chunk_size) - bbm_chunk + 1;
|
||||
|
||||
block_mark_bit_offset = mtd->writesize * 8 -
|
||||
(geo->ecc_strength * geo->gf_len * (geo->ecc_chunk_count - i)
|
||||
+ geo->metadata_size * 8);
|
||||
|
||||
geo->block_mark_byte_offset = block_mark_bit_offset / 8;
|
||||
geo->block_mark_bit_offset = block_mark_bit_offset % 8;
|
||||
|
||||
dev_dbg(this->dev, "BCH Geometry :\n"
|
||||
"GF length : %u\n"
|
||||
"ECC Strength : %u\n"
|
||||
"Page Size in Bytes : %u\n"
|
||||
"Metadata Size in Bytes : %u\n"
|
||||
"ECC0 Chunk Size in Bytes: %u\n"
|
||||
"ECCn Chunk Size in Bytes: %u\n"
|
||||
"ECC Chunk Count : %u\n"
|
||||
"Payload Size in Bytes : %u\n"
|
||||
"Auxiliary Size in Bytes: %u\n"
|
||||
"Auxiliary Status Offset: %u\n"
|
||||
"Block Mark Byte Offset : %u\n"
|
||||
"Block Mark Bit Offset : %u\n"
|
||||
"Block Mark in chunk : %u\n"
|
||||
"Ecc for Meta data : %u\n",
|
||||
geo->gf_len,
|
||||
geo->ecc_strength,
|
||||
geo->page_size,
|
||||
geo->metadata_size,
|
||||
geo->ecc0_chunk_size,
|
||||
geo->eccn_chunk_size,
|
||||
geo->ecc_chunk_count,
|
||||
geo->payload_size,
|
||||
geo->auxiliary_size,
|
||||
geo->auxiliary_status_offset,
|
||||
geo->block_mark_byte_offset,
|
||||
geo->block_mark_bit_offset,
|
||||
bbm_chunk,
|
||||
geo->ecc_for_meta);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int legacy_set_geometry(struct gpmi_nand_data *this)
|
||||
{
|
||||
struct bch_geometry *geo = &this->bch_geometry;
|
||||
|
@ -418,13 +595,15 @@ static int legacy_set_geometry(struct gpmi_nand_data *this)
|
|||
geo->gf_len = 13;
|
||||
|
||||
/* The default for chunk size. */
|
||||
geo->ecc_chunk_size = 512;
|
||||
while (geo->ecc_chunk_size < mtd->oobsize) {
|
||||
geo->ecc_chunk_size *= 2; /* keep C >= O */
|
||||
geo->ecc0_chunk_size = 512;
|
||||
geo->eccn_chunk_size = 512;
|
||||
while (geo->eccn_chunk_size < mtd->oobsize) {
|
||||
geo->ecc0_chunk_size *= 2; /* keep C >= O */
|
||||
geo->eccn_chunk_size *= 2; /* keep C >= O */
|
||||
geo->gf_len = 14;
|
||||
}
|
||||
|
||||
geo->ecc_chunk_count = mtd->writesize / geo->ecc_chunk_size;
|
||||
geo->ecc_chunk_count = mtd->writesize / geo->eccn_chunk_size;
|
||||
|
||||
/* We use the same ECC strength for all chunks. */
|
||||
geo->ecc_strength = get_ecc_strength(this);
|
||||
|
@ -514,24 +693,40 @@ static int legacy_set_geometry(struct gpmi_nand_data *this)
|
|||
static int common_nfc_set_geometry(struct gpmi_nand_data *this)
|
||||
{
|
||||
struct nand_chip *chip = &this->nand;
|
||||
struct mtd_info *mtd = nand_to_mtd(&this->nand);
|
||||
const struct nand_ecc_props *requirements =
|
||||
nanddev_get_ecc_requirements(&chip->base);
|
||||
bool use_minimun_ecc;
|
||||
int err;
|
||||
|
||||
if (chip->ecc.strength > 0 && chip->ecc.size > 0)
|
||||
return set_geometry_by_ecc_info(this, chip->ecc.strength,
|
||||
chip->ecc.size);
|
||||
use_minimun_ecc = of_property_read_bool(this->dev->of_node,
|
||||
"fsl,use-minimum-ecc");
|
||||
|
||||
if ((of_property_read_bool(this->dev->of_node, "fsl,use-minimum-ecc"))
|
||||
|| legacy_set_geometry(this)) {
|
||||
if (!(requirements->strength > 0 && requirements->step_size > 0))
|
||||
return -EINVAL;
|
||||
|
||||
return set_geometry_by_ecc_info(this,
|
||||
requirements->strength,
|
||||
requirements->step_size);
|
||||
/* use legacy bch geometry settings by default*/
|
||||
if ((!use_minimun_ecc && mtd->oobsize < 1024) ||
|
||||
!(requirements->strength > 0 && requirements->step_size > 0)) {
|
||||
dev_dbg(this->dev, "use legacy bch geometry\n");
|
||||
err = legacy_set_geometry(this);
|
||||
if (!err)
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 0;
|
||||
/* for large oob nand */
|
||||
if (mtd->oobsize > 1024) {
|
||||
dev_dbg(this->dev, "use large oob bch geometry\n");
|
||||
err = set_geometry_for_large_oob(this);
|
||||
if (!err)
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* otherwise use the minimum ecc nand chip required */
|
||||
dev_dbg(this->dev, "use minimum ecc bch geometry\n");
|
||||
err = set_geometry_by_ecc_info(this, requirements->strength,
|
||||
requirements->step_size);
|
||||
if (err)
|
||||
dev_err(this->dev, "none of the bch geometry setting works\n");
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
/* Configures the geometry for BCH. */
|
||||
|
@ -843,7 +1038,7 @@ static int gpmi_raw_len_to_len(struct gpmi_nand_data *this, int raw_len)
|
|||
* we are passed in exec_op. Calculate the data length from it.
|
||||
*/
|
||||
if (this->bch)
|
||||
return ALIGN_DOWN(raw_len, this->bch_geometry.ecc_chunk_size);
|
||||
return ALIGN_DOWN(raw_len, this->bch_geometry.eccn_chunk_size);
|
||||
else
|
||||
return raw_len;
|
||||
}
|
||||
|
@ -1235,7 +1430,7 @@ static int gpmi_count_bitflips(struct nand_chip *chip, void *buf, int first,
|
|||
|
||||
/* Read ECC bytes into our internal raw_buffer */
|
||||
offset = nfc_geo->metadata_size * 8;
|
||||
offset += ((8 * nfc_geo->ecc_chunk_size) + eccbits) * (i + 1);
|
||||
offset += ((8 * nfc_geo->eccn_chunk_size) + eccbits) * (i + 1);
|
||||
offset -= eccbits;
|
||||
bitoffset = offset % 8;
|
||||
eccbytes = DIV_ROUND_UP(offset + eccbits, 8);
|
||||
|
@ -1272,16 +1467,16 @@ static int gpmi_count_bitflips(struct nand_chip *chip, void *buf, int first,
|
|||
if (i == 0) {
|
||||
/* The first block includes metadata */
|
||||
flips = nand_check_erased_ecc_chunk(
|
||||
buf + i * nfc_geo->ecc_chunk_size,
|
||||
nfc_geo->ecc_chunk_size,
|
||||
buf + i * nfc_geo->eccn_chunk_size,
|
||||
nfc_geo->eccn_chunk_size,
|
||||
eccbuf, eccbytes,
|
||||
this->auxiliary_virt,
|
||||
nfc_geo->metadata_size,
|
||||
nfc_geo->ecc_strength);
|
||||
} else {
|
||||
flips = nand_check_erased_ecc_chunk(
|
||||
buf + i * nfc_geo->ecc_chunk_size,
|
||||
nfc_geo->ecc_chunk_size,
|
||||
buf + i * nfc_geo->eccn_chunk_size,
|
||||
nfc_geo->eccn_chunk_size,
|
||||
eccbuf, eccbytes,
|
||||
NULL, 0,
|
||||
nfc_geo->ecc_strength);
|
||||
|
@ -1310,20 +1505,21 @@ static void gpmi_bch_layout_std(struct gpmi_nand_data *this)
|
|||
struct bch_geometry *geo = &this->bch_geometry;
|
||||
unsigned int ecc_strength = geo->ecc_strength >> 1;
|
||||
unsigned int gf_len = geo->gf_len;
|
||||
unsigned int block_size = geo->ecc_chunk_size;
|
||||
unsigned int block0_size = geo->ecc0_chunk_size;
|
||||
unsigned int blockn_size = geo->eccn_chunk_size;
|
||||
|
||||
this->bch_flashlayout0 =
|
||||
BF_BCH_FLASH0LAYOUT0_NBLOCKS(geo->ecc_chunk_count - 1) |
|
||||
BF_BCH_FLASH0LAYOUT0_META_SIZE(geo->metadata_size) |
|
||||
BF_BCH_FLASH0LAYOUT0_ECC0(ecc_strength, this) |
|
||||
BF_BCH_FLASH0LAYOUT0_GF(gf_len, this) |
|
||||
BF_BCH_FLASH0LAYOUT0_DATA0_SIZE(block_size, this);
|
||||
BF_BCH_FLASH0LAYOUT0_DATA0_SIZE(block0_size, this);
|
||||
|
||||
this->bch_flashlayout1 =
|
||||
BF_BCH_FLASH0LAYOUT1_PAGE_SIZE(geo->page_size) |
|
||||
BF_BCH_FLASH0LAYOUT1_ECCN(ecc_strength, this) |
|
||||
BF_BCH_FLASH0LAYOUT1_GF(gf_len, this) |
|
||||
BF_BCH_FLASH0LAYOUT1_DATAN_SIZE(block_size, this);
|
||||
BF_BCH_FLASH0LAYOUT1_DATAN_SIZE(blockn_size, this);
|
||||
}
|
||||
|
||||
static int gpmi_ecc_read_page(struct nand_chip *chip, uint8_t *buf,
|
||||
|
@ -1406,29 +1602,49 @@ static int gpmi_ecc_read_subpage(struct nand_chip *chip, uint32_t offs,
|
|||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* if there is an ECC dedicate for meta:
|
||||
* - need to add an extra ECC size when calculating col and page_size,
|
||||
* if the meta size is NOT zero.
|
||||
* - ecc0_chunk size need to set to the same size as other chunks,
|
||||
* if the meta size is zero.
|
||||
*/
|
||||
|
||||
meta = geo->metadata_size;
|
||||
if (first) {
|
||||
col = meta + (size + ecc_parity_size) * first;
|
||||
if (geo->ecc_for_meta)
|
||||
col = meta + ecc_parity_size
|
||||
+ (size + ecc_parity_size) * first;
|
||||
else
|
||||
col = meta + (size + ecc_parity_size) * first;
|
||||
|
||||
meta = 0;
|
||||
buf = buf + first * size;
|
||||
}
|
||||
|
||||
ecc_parity_size = geo->gf_len * geo->ecc_strength / 8;
|
||||
|
||||
n = last - first + 1;
|
||||
page_size = meta + (size + ecc_parity_size) * n;
|
||||
|
||||
if (geo->ecc_for_meta && meta)
|
||||
page_size = meta + ecc_parity_size
|
||||
+ (size + ecc_parity_size) * n;
|
||||
else
|
||||
page_size = meta + (size + ecc_parity_size) * n;
|
||||
|
||||
ecc_strength = geo->ecc_strength >> 1;
|
||||
|
||||
this->bch_flashlayout0 = BF_BCH_FLASH0LAYOUT0_NBLOCKS(n - 1) |
|
||||
this->bch_flashlayout0 = BF_BCH_FLASH0LAYOUT0_NBLOCKS(
|
||||
(geo->ecc_for_meta ? n : n - 1)) |
|
||||
BF_BCH_FLASH0LAYOUT0_META_SIZE(meta) |
|
||||
BF_BCH_FLASH0LAYOUT0_ECC0(ecc_strength, this) |
|
||||
BF_BCH_FLASH0LAYOUT0_GF(geo->gf_len, this) |
|
||||
BF_BCH_FLASH0LAYOUT0_DATA0_SIZE(geo->ecc_chunk_size, this);
|
||||
BF_BCH_FLASH0LAYOUT0_DATA0_SIZE((geo->ecc_for_meta ?
|
||||
0 : geo->ecc0_chunk_size), this);
|
||||
|
||||
this->bch_flashlayout1 = BF_BCH_FLASH0LAYOUT1_PAGE_SIZE(page_size) |
|
||||
BF_BCH_FLASH0LAYOUT1_ECCN(ecc_strength, this) |
|
||||
BF_BCH_FLASH0LAYOUT1_GF(geo->gf_len, this) |
|
||||
BF_BCH_FLASH0LAYOUT1_DATAN_SIZE(geo->ecc_chunk_size, this);
|
||||
BF_BCH_FLASH0LAYOUT1_DATAN_SIZE(geo->eccn_chunk_size, this);
|
||||
|
||||
this->bch = true;
|
||||
|
||||
|
@ -1597,7 +1813,7 @@ static int gpmi_ecc_read_page_raw(struct nand_chip *chip, uint8_t *buf,
|
|||
struct mtd_info *mtd = nand_to_mtd(chip);
|
||||
struct gpmi_nand_data *this = nand_get_controller_data(chip);
|
||||
struct bch_geometry *nfc_geo = &this->bch_geometry;
|
||||
int eccsize = nfc_geo->ecc_chunk_size;
|
||||
int eccsize = nfc_geo->eccn_chunk_size;
|
||||
int eccbits = nfc_geo->ecc_strength * nfc_geo->gf_len;
|
||||
u8 *tmp_buf = this->raw_buffer;
|
||||
size_t src_bit_off;
|
||||
|
@ -1682,7 +1898,7 @@ static int gpmi_ecc_write_page_raw(struct nand_chip *chip, const uint8_t *buf,
|
|||
struct mtd_info *mtd = nand_to_mtd(chip);
|
||||
struct gpmi_nand_data *this = nand_get_controller_data(chip);
|
||||
struct bch_geometry *nfc_geo = &this->bch_geometry;
|
||||
int eccsize = nfc_geo->ecc_chunk_size;
|
||||
int eccsize = nfc_geo->eccn_chunk_size;
|
||||
int eccbits = nfc_geo->ecc_strength * nfc_geo->gf_len;
|
||||
u8 *tmp_buf = this->raw_buffer;
|
||||
uint8_t *oob = chip->oob_poi;
|
||||
|
@ -2056,7 +2272,7 @@ static int gpmi_init_last(struct gpmi_nand_data *this)
|
|||
ecc->read_oob_raw = gpmi_ecc_read_oob_raw;
|
||||
ecc->write_oob_raw = gpmi_ecc_write_oob_raw;
|
||||
ecc->engine_type = NAND_ECC_ENGINE_TYPE_ON_HOST;
|
||||
ecc->size = bch_geo->ecc_chunk_size;
|
||||
ecc->size = bch_geo->eccn_chunk_size;
|
||||
ecc->strength = bch_geo->ecc_strength;
|
||||
mtd_set_ooblayout(mtd, &gpmi_ooblayout_ops);
|
||||
|
||||
|
|
|
@ -30,9 +30,9 @@ struct resources {
|
|||
* @page_size: The size, in bytes, of a physical page, including
|
||||
* both data and OOB.
|
||||
* @metadata_size: The size, in bytes, of the metadata.
|
||||
* @ecc_chunk_size: The size, in bytes, of a single ECC chunk. Note
|
||||
* the first chunk in the page includes both data and
|
||||
* metadata, so it's a bit larger than this value.
|
||||
* @ecc0_chunk_size: The size, in bytes, of a first ECC chunk.
|
||||
* @eccn_chunk_size: The size, in bytes, of a single ECC chunk after
|
||||
* the first chunk in the page.
|
||||
* @ecc_chunk_count: The number of ECC chunks in the page,
|
||||
* @payload_size: The size, in bytes, of the payload buffer.
|
||||
* @auxiliary_size: The size, in bytes, of the auxiliary buffer.
|
||||
|
@ -42,19 +42,23 @@ struct resources {
|
|||
* which the underlying physical block mark appears.
|
||||
* @block_mark_bit_offset: The bit offset into the ECC-based page view at
|
||||
* which the underlying physical block mark appears.
|
||||
* @ecc_for_meta: The flag to indicate if there is a dedicate ecc
|
||||
* for meta.
|
||||
*/
|
||||
struct bch_geometry {
|
||||
unsigned int gf_len;
|
||||
unsigned int ecc_strength;
|
||||
unsigned int page_size;
|
||||
unsigned int metadata_size;
|
||||
unsigned int ecc_chunk_size;
|
||||
unsigned int ecc0_chunk_size;
|
||||
unsigned int eccn_chunk_size;
|
||||
unsigned int ecc_chunk_count;
|
||||
unsigned int payload_size;
|
||||
unsigned int auxiliary_size;
|
||||
unsigned int auxiliary_status_offset;
|
||||
unsigned int block_mark_byte_offset;
|
||||
unsigned int block_mark_bit_offset;
|
||||
unsigned int ecc_for_meta; /* ECC for meta data */
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
@ -619,9 +619,9 @@ static int ebu_nand_probe(struct platform_device *pdev)
|
|||
resname = devm_kasprintf(dev, GFP_KERNEL, "nand_cs%d", cs);
|
||||
res = platform_get_resource_byname(pdev, IORESOURCE_MEM, resname);
|
||||
ebu_host->cs[cs].chipaddr = devm_ioremap_resource(dev, res);
|
||||
ebu_host->cs[cs].nand_pa = res->start;
|
||||
if (IS_ERR(ebu_host->cs[cs].chipaddr))
|
||||
return PTR_ERR(ebu_host->cs[cs].chipaddr);
|
||||
ebu_host->cs[cs].nand_pa = res->start;
|
||||
|
||||
ebu_host->clk = devm_clk_get(dev, NULL);
|
||||
if (IS_ERR(ebu_host->clk))
|
||||
|
|
|
@ -595,8 +595,7 @@ static void mpc5121_nfc_free(struct device *dev, struct mtd_info *mtd)
|
|||
struct nand_chip *chip = mtd_to_nand(mtd);
|
||||
struct mpc5121_nfc_prv *prv = nand_get_controller_data(chip);
|
||||
|
||||
if (prv->clk)
|
||||
clk_disable_unprepare(prv->clk);
|
||||
clk_disable_unprepare(prv->clk);
|
||||
|
||||
if (prv->csreg)
|
||||
iounmap(prv->csreg);
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
#include <linux/iopoll.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/of_device.h>
|
||||
#include "mtk_ecc.h"
|
||||
#include <linux/mtd/nand-ecc-mtk.h>
|
||||
|
||||
/* NAND controller register definition */
|
||||
#define NFI_CNFG (0x00)
|
||||
|
|
|
@ -4502,11 +4502,13 @@ int nand_erase_nand(struct nand_chip *chip, struct erase_info *instr,
|
|||
len = instr->len;
|
||||
|
||||
while (len) {
|
||||
loff_t ofs = (loff_t)page << chip->page_shift;
|
||||
|
||||
/* Check if we have a bad block, we do not erase bad blocks! */
|
||||
if (nand_block_checkbad(chip, ((loff_t) page) <<
|
||||
chip->page_shift, allowbbt)) {
|
||||
pr_warn("%s: attempt to erase a bad block at page 0x%08x\n",
|
||||
__func__, page);
|
||||
pr_warn("%s: attempt to erase a bad block at 0x%08llx\n",
|
||||
__func__, (unsigned long long)ofs);
|
||||
ret = -EIO;
|
||||
goto erase_exit;
|
||||
}
|
||||
|
@ -4524,8 +4526,7 @@ int nand_erase_nand(struct nand_chip *chip, struct erase_info *instr,
|
|||
if (ret) {
|
||||
pr_debug("%s: failed erase, page 0x%08x\n",
|
||||
__func__, page);
|
||||
instr->fail_addr =
|
||||
((loff_t)page << chip->page_shift);
|
||||
instr->fail_addr = ofs;
|
||||
goto erase_exit;
|
||||
}
|
||||
|
||||
|
|
|
@ -29,6 +29,9 @@ struct nand_flash_dev nand_flash_ids[] = {
|
|||
{"TC58NVG0S3E 1G 3.3V 8-bit",
|
||||
{ .id = {0x98, 0xd1, 0x90, 0x15, 0x76, 0x14, 0x01, 0x00} },
|
||||
SZ_2K, SZ_128, SZ_128K, 0, 8, 64, NAND_ECC_INFO(1, SZ_512), },
|
||||
{"TC58NVG0S3HTA00 1G 3.3V 8-bit",
|
||||
{ .id = {0x98, 0xf1, 0x80, 0x15} },
|
||||
SZ_2K, SZ_128, SZ_128K, 0, 4, 128, NAND_ECC_INFO(8, SZ_512), },
|
||||
{"TC58NVG2S0F 4G 3.3V 8-bit",
|
||||
{ .id = {0x98, 0xdc, 0x90, 0x26, 0x76, 0x15, 0x01, 0x08} },
|
||||
SZ_4K, SZ_512, SZ_256K, 0, 8, 224, NAND_ECC_INFO(4, SZ_512) },
|
||||
|
@ -58,6 +61,9 @@ struct nand_flash_dev nand_flash_ids[] = {
|
|||
{"TH58NVG2S3HBAI4 4G 3.3V 8-bit",
|
||||
{ .id = {0x98, 0xdc, 0x91, 0x15, 0x76} },
|
||||
SZ_2K, SZ_512, SZ_128K, 0, 5, 128, NAND_ECC_INFO(8, SZ_512) },
|
||||
{"TH58NVG3S0HBAI4 8G 3.3V 8-bit",
|
||||
{ .id = {0x98, 0xd3, 0x91, 0x26, 0x76} },
|
||||
SZ_4K, SZ_1K, SZ_256K, 0, 5, 256, NAND_ECC_INFO(8, SZ_512)},
|
||||
|
||||
LEGACY_ID_NAND("NAND 4MiB 5V 8-bit", 0x6B, 4, SZ_8K, SP_OPTIONS),
|
||||
LEGACY_ID_NAND("NAND 4MiB 3,3V 8-bit", 0xE3, 4, SZ_8K, SP_OPTIONS),
|
||||
|
|
|
@ -287,8 +287,10 @@ static int toshiba_nand_init(struct nand_chip *chip)
|
|||
if (!strncmp("TC58NVG0S3E", chip->parameters.model,
|
||||
sizeof("TC58NVG0S3E") - 1))
|
||||
tc58nvg0s3e_init(chip);
|
||||
if (!strncmp("TH58NVG2S3HBAI4", chip->parameters.model,
|
||||
sizeof("TH58NVG2S3HBAI4") - 1))
|
||||
if ((!strncmp("TH58NVG2S3HBAI4", chip->parameters.model,
|
||||
sizeof("TH58NVG2S3HBAI4") - 1)) ||
|
||||
(!strncmp("TH58NVG3S0HBAI4", chip->parameters.model,
|
||||
sizeof("TH58NVG3S0HBAI4") - 1)))
|
||||
th58nvg2s3hbai4_init(chip);
|
||||
|
||||
return 0;
|
||||
|
|
|
@ -548,6 +548,7 @@ static SIMPLE_DEV_PM_OPS(elm_pm_ops, elm_suspend, elm_resume);
|
|||
#ifdef CONFIG_OF
|
||||
static const struct of_device_id elm_of_match[] = {
|
||||
{ .compatible = "ti,am3352-elm" },
|
||||
{ .compatible = "ti,am64-elm" },
|
||||
{},
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, elm_of_match);
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
#include <linux/mtd/rawnand.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/pm_runtime.h>
|
||||
#include <linux/slab.h>
|
||||
|
||||
#define COMMAND_REG 0x00
|
||||
|
@ -216,8 +217,7 @@ struct rnandc {
|
|||
struct nand_controller controller;
|
||||
struct device *dev;
|
||||
void __iomem *regs;
|
||||
struct clk *hclk;
|
||||
struct clk *eclk;
|
||||
unsigned long ext_clk_rate;
|
||||
unsigned long assigned_cs;
|
||||
struct list_head chips;
|
||||
struct nand_chip *selected_chip;
|
||||
|
@ -891,7 +891,7 @@ static int rnandc_setup_interface(struct nand_chip *chip, int chipnr,
|
|||
{
|
||||
struct rnand_chip *rnand = to_rnand(chip);
|
||||
struct rnandc *rnandc = to_rnandc(chip->controller);
|
||||
unsigned int period_ns = 1000000000 / clk_get_rate(rnandc->eclk);
|
||||
unsigned int period_ns = 1000000000 / rnandc->ext_clk_rate;
|
||||
const struct nand_sdr_timings *sdr;
|
||||
unsigned int cyc, cle, ale, bef_dly, ca_to_data;
|
||||
|
||||
|
@ -1319,6 +1319,7 @@ static int rnandc_chips_init(struct rnandc *rnandc)
|
|||
static int rnandc_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct rnandc *rnandc;
|
||||
struct clk *eclk;
|
||||
int irq, ret;
|
||||
|
||||
rnandc = devm_kzalloc(&pdev->dev, sizeof(*rnandc), GFP_KERNEL);
|
||||
|
@ -1335,29 +1336,26 @@ static int rnandc_probe(struct platform_device *pdev)
|
|||
if (IS_ERR(rnandc->regs))
|
||||
return PTR_ERR(rnandc->regs);
|
||||
|
||||
/* APB clock */
|
||||
rnandc->hclk = devm_clk_get(&pdev->dev, "hclk");
|
||||
if (IS_ERR(rnandc->hclk))
|
||||
return PTR_ERR(rnandc->hclk);
|
||||
|
||||
/* External NAND bus clock */
|
||||
rnandc->eclk = devm_clk_get(&pdev->dev, "eclk");
|
||||
if (IS_ERR(rnandc->eclk))
|
||||
return PTR_ERR(rnandc->eclk);
|
||||
|
||||
ret = clk_prepare_enable(rnandc->hclk);
|
||||
if (ret)
|
||||
devm_pm_runtime_enable(&pdev->dev);
|
||||
ret = pm_runtime_resume_and_get(&pdev->dev);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = clk_prepare_enable(rnandc->eclk);
|
||||
if (ret)
|
||||
goto disable_hclk;
|
||||
/* The external NAND bus clock rate is needed for computing timings */
|
||||
eclk = clk_get(&pdev->dev, "eclk");
|
||||
if (IS_ERR(eclk)) {
|
||||
ret = PTR_ERR(eclk);
|
||||
goto dis_runtime_pm;
|
||||
}
|
||||
|
||||
rnandc->ext_clk_rate = clk_get_rate(eclk);
|
||||
clk_put(eclk);
|
||||
|
||||
rnandc_dis_interrupts(rnandc);
|
||||
irq = platform_get_irq_optional(pdev, 0);
|
||||
if (irq == -EPROBE_DEFER) {
|
||||
ret = irq;
|
||||
goto disable_eclk;
|
||||
goto dis_runtime_pm;
|
||||
} else if (irq < 0) {
|
||||
dev_info(&pdev->dev, "No IRQ found, fallback to polling\n");
|
||||
rnandc->use_polling = true;
|
||||
|
@ -1365,12 +1363,12 @@ static int rnandc_probe(struct platform_device *pdev)
|
|||
ret = devm_request_irq(&pdev->dev, irq, rnandc_irq_handler, 0,
|
||||
"renesas-nand-controller", rnandc);
|
||||
if (ret < 0)
|
||||
goto disable_eclk;
|
||||
goto dis_runtime_pm;
|
||||
}
|
||||
|
||||
ret = dma_set_mask(&pdev->dev, DMA_BIT_MASK(32));
|
||||
if (ret)
|
||||
goto disable_eclk;
|
||||
goto dis_runtime_pm;
|
||||
|
||||
rnandc_clear_fifo(rnandc);
|
||||
|
||||
|
@ -1378,14 +1376,12 @@ static int rnandc_probe(struct platform_device *pdev)
|
|||
|
||||
ret = rnandc_chips_init(rnandc);
|
||||
if (ret)
|
||||
goto disable_eclk;
|
||||
goto dis_runtime_pm;
|
||||
|
||||
return 0;
|
||||
|
||||
disable_eclk:
|
||||
clk_disable_unprepare(rnandc->eclk);
|
||||
disable_hclk:
|
||||
clk_disable_unprepare(rnandc->hclk);
|
||||
dis_runtime_pm:
|
||||
pm_runtime_put(&pdev->dev);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
@ -1396,8 +1392,7 @@ static int rnandc_remove(struct platform_device *pdev)
|
|||
|
||||
rnandc_chips_cleanup(rnandc);
|
||||
|
||||
clk_disable_unprepare(rnandc->eclk);
|
||||
clk_disable_unprepare(rnandc->hclk);
|
||||
pm_runtime_put(&pdev->dev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
|
@ -911,8 +911,7 @@ static int rk_nfc_enable_clks(struct device *dev, struct rk_nfc *nfc)
|
|||
ret = clk_prepare_enable(nfc->ahb_clk);
|
||||
if (ret) {
|
||||
dev_err(dev, "failed to enable ahb clk\n");
|
||||
if (!IS_ERR(nfc->nfc_clk))
|
||||
clk_disable_unprepare(nfc->nfc_clk);
|
||||
clk_disable_unprepare(nfc->nfc_clk);
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
@ -921,8 +920,7 @@ static int rk_nfc_enable_clks(struct device *dev, struct rk_nfc *nfc)
|
|||
|
||||
static void rk_nfc_disable_clks(struct rk_nfc *nfc)
|
||||
{
|
||||
if (!IS_ERR(nfc->nfc_clk))
|
||||
clk_disable_unprepare(nfc->nfc_clk);
|
||||
clk_disable_unprepare(nfc->nfc_clk);
|
||||
clk_disable_unprepare(nfc->ahb_clk);
|
||||
}
|
||||
|
||||
|
|
|
@ -390,6 +390,9 @@ static int tmio_probe(struct platform_device *dev)
|
|||
if (data == NULL)
|
||||
dev_warn(&dev->dev, "NULL platform data!\n");
|
||||
|
||||
if (!ccr || !fcr)
|
||||
return -EINVAL;
|
||||
|
||||
tmio = devm_kzalloc(&dev->dev, sizeof(*tmio), GFP_KERNEL);
|
||||
if (!tmio)
|
||||
return -ENOMEM;
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
# SPDX-License-Identifier: GPL-2.0
|
||||
spinand-objs := core.o gigadevice.o macronix.o micron.o paragon.o toshiba.o winbond.o
|
||||
spinand-objs := core.o gigadevice.o macronix.o micron.o paragon.o toshiba.o winbond.o xtx.o
|
||||
obj-$(CONFIG_MTD_SPI_NAND) += spinand.o
|
||||
|
|
|
@ -933,6 +933,7 @@ static const struct spinand_manufacturer *spinand_manufacturers[] = {
|
|||
¶gon_spinand_manufacturer,
|
||||
&toshiba_spinand_manufacturer,
|
||||
&winbond_spinand_manufacturer,
|
||||
&xtx_spinand_manufacturer,
|
||||
};
|
||||
|
||||
static int spinand_manufacturer_match(struct spinand_device *spinand,
|
||||
|
|
|
@ -39,6 +39,22 @@ static SPINAND_OP_VARIANTS(read_cache_variants_f,
|
|||
SPINAND_PAGE_READ_FROM_CACHE_OP_3A(true, 0, 1, NULL, 0),
|
||||
SPINAND_PAGE_READ_FROM_CACHE_OP_3A(false, 0, 0, NULL, 0));
|
||||
|
||||
static SPINAND_OP_VARIANTS(read_cache_variants_1gq5,
|
||||
SPINAND_PAGE_READ_FROM_CACHE_QUADIO_OP(0, 2, NULL, 0),
|
||||
SPINAND_PAGE_READ_FROM_CACHE_X4_OP(0, 1, NULL, 0),
|
||||
SPINAND_PAGE_READ_FROM_CACHE_DUALIO_OP(0, 1, NULL, 0),
|
||||
SPINAND_PAGE_READ_FROM_CACHE_X2_OP(0, 1, NULL, 0),
|
||||
SPINAND_PAGE_READ_FROM_CACHE_OP(true, 0, 1, NULL, 0),
|
||||
SPINAND_PAGE_READ_FROM_CACHE_OP(false, 0, 1, NULL, 0));
|
||||
|
||||
static SPINAND_OP_VARIANTS(read_cache_variants_2gq5,
|
||||
SPINAND_PAGE_READ_FROM_CACHE_QUADIO_OP(0, 4, NULL, 0),
|
||||
SPINAND_PAGE_READ_FROM_CACHE_X4_OP(0, 1, NULL, 0),
|
||||
SPINAND_PAGE_READ_FROM_CACHE_DUALIO_OP(0, 2, NULL, 0),
|
||||
SPINAND_PAGE_READ_FROM_CACHE_X2_OP(0, 1, NULL, 0),
|
||||
SPINAND_PAGE_READ_FROM_CACHE_OP(true, 0, 1, NULL, 0),
|
||||
SPINAND_PAGE_READ_FROM_CACHE_OP(false, 0, 1, NULL, 0));
|
||||
|
||||
static SPINAND_OP_VARIANTS(write_cache_variants,
|
||||
SPINAND_PROG_LOAD_X4(true, 0, NULL, 0),
|
||||
SPINAND_PROG_LOAD(true, 0, NULL, 0));
|
||||
|
@ -325,6 +341,36 @@ static const struct spinand_info gigadevice_spinand_table[] = {
|
|||
SPINAND_HAS_QE_BIT,
|
||||
SPINAND_ECCINFO(&gd5fxgqx_variant2_ooblayout,
|
||||
gd5fxgq4uexxg_ecc_get_status)),
|
||||
SPINAND_INFO("GD5F1GQ4RExxG",
|
||||
SPINAND_ID(SPINAND_READID_METHOD_OPCODE_ADDR, 0xc1),
|
||||
NAND_MEMORG(1, 2048, 128, 64, 1024, 20, 1, 1, 1),
|
||||
NAND_ECCREQ(8, 512),
|
||||
SPINAND_INFO_OP_VARIANTS(&read_cache_variants,
|
||||
&write_cache_variants,
|
||||
&update_cache_variants),
|
||||
SPINAND_HAS_QE_BIT,
|
||||
SPINAND_ECCINFO(&gd5fxgqx_variant2_ooblayout,
|
||||
gd5fxgq4uexxg_ecc_get_status)),
|
||||
SPINAND_INFO("GD5F2GQ4UExxG",
|
||||
SPINAND_ID(SPINAND_READID_METHOD_OPCODE_ADDR, 0xd2),
|
||||
NAND_MEMORG(1, 2048, 128, 64, 2048, 40, 1, 1, 1),
|
||||
NAND_ECCREQ(8, 512),
|
||||
SPINAND_INFO_OP_VARIANTS(&read_cache_variants,
|
||||
&write_cache_variants,
|
||||
&update_cache_variants),
|
||||
SPINAND_HAS_QE_BIT,
|
||||
SPINAND_ECCINFO(&gd5fxgqx_variant2_ooblayout,
|
||||
gd5fxgq4uexxg_ecc_get_status)),
|
||||
SPINAND_INFO("GD5F2GQ4RExxG",
|
||||
SPINAND_ID(SPINAND_READID_METHOD_OPCODE_ADDR, 0xc2),
|
||||
NAND_MEMORG(1, 2048, 128, 64, 2048, 40, 1, 1, 1),
|
||||
NAND_ECCREQ(8, 512),
|
||||
SPINAND_INFO_OP_VARIANTS(&read_cache_variants,
|
||||
&write_cache_variants,
|
||||
&update_cache_variants),
|
||||
SPINAND_HAS_QE_BIT,
|
||||
SPINAND_ECCINFO(&gd5fxgqx_variant2_ooblayout,
|
||||
gd5fxgq4uexxg_ecc_get_status)),
|
||||
SPINAND_INFO("GD5F1GQ4UFxxG",
|
||||
SPINAND_ID(SPINAND_READID_METHOD_OPCODE, 0xb1, 0x48),
|
||||
NAND_MEMORG(1, 2048, 128, 64, 1024, 20, 1, 1, 1),
|
||||
|
@ -339,12 +385,122 @@ static const struct spinand_info gigadevice_spinand_table[] = {
|
|||
SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x51),
|
||||
NAND_MEMORG(1, 2048, 128, 64, 1024, 20, 1, 1, 1),
|
||||
NAND_ECCREQ(4, 512),
|
||||
SPINAND_INFO_OP_VARIANTS(&read_cache_variants,
|
||||
SPINAND_INFO_OP_VARIANTS(&read_cache_variants_1gq5,
|
||||
&write_cache_variants,
|
||||
&update_cache_variants),
|
||||
SPINAND_HAS_QE_BIT,
|
||||
SPINAND_ECCINFO(&gd5fxgqx_variant2_ooblayout,
|
||||
gd5fxgq5xexxg_ecc_get_status)),
|
||||
SPINAND_INFO("GD5F1GQ5RExxG",
|
||||
SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x41),
|
||||
NAND_MEMORG(1, 2048, 128, 64, 1024, 20, 1, 1, 1),
|
||||
NAND_ECCREQ(4, 512),
|
||||
SPINAND_INFO_OP_VARIANTS(&read_cache_variants_1gq5,
|
||||
&write_cache_variants,
|
||||
&update_cache_variants),
|
||||
SPINAND_HAS_QE_BIT,
|
||||
SPINAND_ECCINFO(&gd5fxgqx_variant2_ooblayout,
|
||||
gd5fxgq5xexxg_ecc_get_status)),
|
||||
SPINAND_INFO("GD5F2GQ5UExxG",
|
||||
SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x52),
|
||||
NAND_MEMORG(1, 2048, 128, 64, 2048, 40, 1, 1, 1),
|
||||
NAND_ECCREQ(4, 512),
|
||||
SPINAND_INFO_OP_VARIANTS(&read_cache_variants_2gq5,
|
||||
&write_cache_variants,
|
||||
&update_cache_variants),
|
||||
SPINAND_HAS_QE_BIT,
|
||||
SPINAND_ECCINFO(&gd5fxgqx_variant2_ooblayout,
|
||||
gd5fxgq5xexxg_ecc_get_status)),
|
||||
SPINAND_INFO("GD5F2GQ5RExxG",
|
||||
SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x42),
|
||||
NAND_MEMORG(1, 2048, 128, 64, 2048, 40, 1, 1, 1),
|
||||
NAND_ECCREQ(4, 512),
|
||||
SPINAND_INFO_OP_VARIANTS(&read_cache_variants_2gq5,
|
||||
&write_cache_variants,
|
||||
&update_cache_variants),
|
||||
SPINAND_HAS_QE_BIT,
|
||||
SPINAND_ECCINFO(&gd5fxgqx_variant2_ooblayout,
|
||||
gd5fxgq5xexxg_ecc_get_status)),
|
||||
SPINAND_INFO("GD5F4GQ6UExxG",
|
||||
SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x55),
|
||||
NAND_MEMORG(1, 2048, 128, 64, 2048, 40, 1, 2, 1),
|
||||
NAND_ECCREQ(4, 512),
|
||||
SPINAND_INFO_OP_VARIANTS(&read_cache_variants_2gq5,
|
||||
&write_cache_variants,
|
||||
&update_cache_variants),
|
||||
SPINAND_HAS_QE_BIT,
|
||||
SPINAND_ECCINFO(&gd5fxgqx_variant2_ooblayout,
|
||||
gd5fxgq5xexxg_ecc_get_status)),
|
||||
SPINAND_INFO("GD5F4GQ6RExxG",
|
||||
SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x45),
|
||||
NAND_MEMORG(1, 2048, 128, 64, 2048, 40, 1, 2, 1),
|
||||
NAND_ECCREQ(4, 512),
|
||||
SPINAND_INFO_OP_VARIANTS(&read_cache_variants_2gq5,
|
||||
&write_cache_variants,
|
||||
&update_cache_variants),
|
||||
SPINAND_HAS_QE_BIT,
|
||||
SPINAND_ECCINFO(&gd5fxgqx_variant2_ooblayout,
|
||||
gd5fxgq5xexxg_ecc_get_status)),
|
||||
SPINAND_INFO("GD5F1GM7UExxG",
|
||||
SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x91),
|
||||
NAND_MEMORG(1, 2048, 128, 64, 1024, 20, 1, 1, 1),
|
||||
NAND_ECCREQ(8, 512),
|
||||
SPINAND_INFO_OP_VARIANTS(&read_cache_variants_1gq5,
|
||||
&write_cache_variants,
|
||||
&update_cache_variants),
|
||||
SPINAND_HAS_QE_BIT,
|
||||
SPINAND_ECCINFO(&gd5fxgqx_variant2_ooblayout,
|
||||
gd5fxgq4uexxg_ecc_get_status)),
|
||||
SPINAND_INFO("GD5F1GM7RExxG",
|
||||
SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x81),
|
||||
NAND_MEMORG(1, 2048, 128, 64, 1024, 20, 1, 1, 1),
|
||||
NAND_ECCREQ(8, 512),
|
||||
SPINAND_INFO_OP_VARIANTS(&read_cache_variants_1gq5,
|
||||
&write_cache_variants,
|
||||
&update_cache_variants),
|
||||
SPINAND_HAS_QE_BIT,
|
||||
SPINAND_ECCINFO(&gd5fxgqx_variant2_ooblayout,
|
||||
gd5fxgq4uexxg_ecc_get_status)),
|
||||
SPINAND_INFO("GD5F2GM7UExxG",
|
||||
SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x92),
|
||||
NAND_MEMORG(1, 2048, 128, 64, 2048, 40, 1, 1, 1),
|
||||
NAND_ECCREQ(8, 512),
|
||||
SPINAND_INFO_OP_VARIANTS(&read_cache_variants_1gq5,
|
||||
&write_cache_variants,
|
||||
&update_cache_variants),
|
||||
SPINAND_HAS_QE_BIT,
|
||||
SPINAND_ECCINFO(&gd5fxgqx_variant2_ooblayout,
|
||||
gd5fxgq4uexxg_ecc_get_status)),
|
||||
SPINAND_INFO("GD5F2GM7RExxG",
|
||||
SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x82),
|
||||
NAND_MEMORG(1, 2048, 128, 64, 2048, 40, 1, 1, 1),
|
||||
NAND_ECCREQ(8, 512),
|
||||
SPINAND_INFO_OP_VARIANTS(&read_cache_variants_1gq5,
|
||||
&write_cache_variants,
|
||||
&update_cache_variants),
|
||||
SPINAND_HAS_QE_BIT,
|
||||
SPINAND_ECCINFO(&gd5fxgqx_variant2_ooblayout,
|
||||
gd5fxgq4uexxg_ecc_get_status)),
|
||||
SPINAND_INFO("GD5F4GM8UExxG",
|
||||
SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x95),
|
||||
NAND_MEMORG(1, 2048, 128, 64, 4096, 80, 1, 1, 1),
|
||||
NAND_ECCREQ(8, 512),
|
||||
SPINAND_INFO_OP_VARIANTS(&read_cache_variants_1gq5,
|
||||
&write_cache_variants,
|
||||
&update_cache_variants),
|
||||
SPINAND_HAS_QE_BIT,
|
||||
SPINAND_ECCINFO(&gd5fxgqx_variant2_ooblayout,
|
||||
gd5fxgq4uexxg_ecc_get_status)),
|
||||
SPINAND_INFO("GD5F4GM8RExxG",
|
||||
SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x85),
|
||||
NAND_MEMORG(1, 2048, 128, 64, 4096, 80, 1, 1, 1),
|
||||
NAND_ECCREQ(8, 512),
|
||||
SPINAND_INFO_OP_VARIANTS(&read_cache_variants_1gq5,
|
||||
&write_cache_variants,
|
||||
&update_cache_variants),
|
||||
SPINAND_HAS_QE_BIT,
|
||||
SPINAND_ECCINFO(&gd5fxgqx_variant2_ooblayout,
|
||||
gd5fxgq4uexxg_ecc_get_status)),
|
||||
};
|
||||
|
||||
static const struct spinand_manufacturer_ops gigadevice_spinand_manuf_ops = {
|
||||
|
|
129
drivers/mtd/nand/spi/xtx.c
Normal file
129
drivers/mtd/nand/spi/xtx.c
Normal file
|
@ -0,0 +1,129 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Author:
|
||||
* Felix Matouschek <felix@matouschek.org>
|
||||
*/
|
||||
|
||||
#include <linux/device.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/mtd/spinand.h>
|
||||
|
||||
#define SPINAND_MFR_XTX 0x0B
|
||||
|
||||
#define XT26G0XA_STATUS_ECC_MASK GENMASK(5, 2)
|
||||
#define XT26G0XA_STATUS_ECC_NO_DETECTED (0 << 2)
|
||||
#define XT26G0XA_STATUS_ECC_8_CORRECTED (3 << 4)
|
||||
#define XT26G0XA_STATUS_ECC_UNCOR_ERROR (2 << 4)
|
||||
|
||||
static SPINAND_OP_VARIANTS(read_cache_variants,
|
||||
SPINAND_PAGE_READ_FROM_CACHE_QUADIO_OP(0, 1, NULL, 0),
|
||||
SPINAND_PAGE_READ_FROM_CACHE_X4_OP(0, 1, NULL, 0),
|
||||
SPINAND_PAGE_READ_FROM_CACHE_DUALIO_OP(0, 1, NULL, 0),
|
||||
SPINAND_PAGE_READ_FROM_CACHE_X2_OP(0, 1, NULL, 0),
|
||||
SPINAND_PAGE_READ_FROM_CACHE_OP(true, 0, 1, NULL, 0),
|
||||
SPINAND_PAGE_READ_FROM_CACHE_OP(false, 0, 1, NULL, 0));
|
||||
|
||||
static SPINAND_OP_VARIANTS(write_cache_variants,
|
||||
SPINAND_PROG_LOAD_X4(true, 0, NULL, 0),
|
||||
SPINAND_PROG_LOAD(true, 0, NULL, 0));
|
||||
|
||||
static SPINAND_OP_VARIANTS(update_cache_variants,
|
||||
SPINAND_PROG_LOAD_X4(false, 0, NULL, 0),
|
||||
SPINAND_PROG_LOAD(false, 0, NULL, 0));
|
||||
|
||||
static int xt26g0xa_ooblayout_ecc(struct mtd_info *mtd, int section,
|
||||
struct mtd_oob_region *region)
|
||||
{
|
||||
if (section)
|
||||
return -ERANGE;
|
||||
|
||||
region->offset = 48;
|
||||
region->length = 16;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int xt26g0xa_ooblayout_free(struct mtd_info *mtd, int section,
|
||||
struct mtd_oob_region *region)
|
||||
{
|
||||
if (section)
|
||||
return -ERANGE;
|
||||
|
||||
region->offset = 1;
|
||||
region->length = 47;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct mtd_ooblayout_ops xt26g0xa_ooblayout = {
|
||||
.ecc = xt26g0xa_ooblayout_ecc,
|
||||
.free = xt26g0xa_ooblayout_free,
|
||||
};
|
||||
|
||||
static int xt26g0xa_ecc_get_status(struct spinand_device *spinand,
|
||||
u8 status)
|
||||
{
|
||||
status = status & XT26G0XA_STATUS_ECC_MASK;
|
||||
|
||||
switch (status) {
|
||||
case XT26G0XA_STATUS_ECC_NO_DETECTED:
|
||||
return 0;
|
||||
case XT26G0XA_STATUS_ECC_8_CORRECTED:
|
||||
return 8;
|
||||
case XT26G0XA_STATUS_ECC_UNCOR_ERROR:
|
||||
return -EBADMSG;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
/* At this point values greater than (2 << 4) are invalid */
|
||||
if (status > XT26G0XA_STATUS_ECC_UNCOR_ERROR)
|
||||
return -EINVAL;
|
||||
|
||||
/* (1 << 2) through (7 << 2) are 1-7 corrected errors */
|
||||
return status >> 2;
|
||||
}
|
||||
|
||||
static const struct spinand_info xtx_spinand_table[] = {
|
||||
SPINAND_INFO("XT26G01A",
|
||||
SPINAND_ID(SPINAND_READID_METHOD_OPCODE_ADDR, 0xE1),
|
||||
NAND_MEMORG(1, 2048, 64, 64, 1024, 20, 1, 1, 1),
|
||||
NAND_ECCREQ(8, 512),
|
||||
SPINAND_INFO_OP_VARIANTS(&read_cache_variants,
|
||||
&write_cache_variants,
|
||||
&update_cache_variants),
|
||||
SPINAND_HAS_QE_BIT,
|
||||
SPINAND_ECCINFO(&xt26g0xa_ooblayout,
|
||||
xt26g0xa_ecc_get_status)),
|
||||
SPINAND_INFO("XT26G02A",
|
||||
SPINAND_ID(SPINAND_READID_METHOD_OPCODE_ADDR, 0xE2),
|
||||
NAND_MEMORG(1, 2048, 64, 64, 2048, 40, 1, 1, 1),
|
||||
NAND_ECCREQ(8, 512),
|
||||
SPINAND_INFO_OP_VARIANTS(&read_cache_variants,
|
||||
&write_cache_variants,
|
||||
&update_cache_variants),
|
||||
SPINAND_HAS_QE_BIT,
|
||||
SPINAND_ECCINFO(&xt26g0xa_ooblayout,
|
||||
xt26g0xa_ecc_get_status)),
|
||||
SPINAND_INFO("XT26G04A",
|
||||
SPINAND_ID(SPINAND_READID_METHOD_OPCODE_ADDR, 0xE3),
|
||||
NAND_MEMORG(1, 2048, 64, 128, 2048, 40, 1, 1, 1),
|
||||
NAND_ECCREQ(8, 512),
|
||||
SPINAND_INFO_OP_VARIANTS(&read_cache_variants,
|
||||
&write_cache_variants,
|
||||
&update_cache_variants),
|
||||
SPINAND_HAS_QE_BIT,
|
||||
SPINAND_ECCINFO(&xt26g0xa_ooblayout,
|
||||
xt26g0xa_ecc_get_status)),
|
||||
};
|
||||
|
||||
static const struct spinand_manufacturer_ops xtx_spinand_manuf_ops = {
|
||||
};
|
||||
|
||||
const struct spinand_manufacturer xtx_spinand_manufacturer = {
|
||||
.id = SPINAND_MFR_XTX,
|
||||
.name = "XTX",
|
||||
.chips = xtx_spinand_table,
|
||||
.nchips = ARRAY_SIZE(xtx_spinand_table),
|
||||
.ops = &xtx_spinand_manuf_ops,
|
||||
};
|
|
@ -237,7 +237,7 @@ static int bcm47xxpart_parse(struct mtd_info *master,
|
|||
(uint8_t *)buf);
|
||||
if (err && !mtd_is_bitflip(err)) {
|
||||
pr_err("mtd_read error while parsing (offset: 0x%X): %d\n",
|
||||
offset, err);
|
||||
offset + 0x8000, err);
|
||||
continue;
|
||||
}
|
||||
|
||||
|
|
|
@ -17,6 +17,7 @@ spi-nor-objs += sst.o
|
|||
spi-nor-objs += winbond.o
|
||||
spi-nor-objs += xilinx.o
|
||||
spi-nor-objs += xmc.o
|
||||
spi-nor-$(CONFIG_DEBUG_FS) += debugfs.o
|
||||
obj-$(CONFIG_MTD_SPI_NOR) += spi-nor.o
|
||||
|
||||
obj-$(CONFIG_MTD_SPI_NOR) += controllers/
|
||||
|
|
|
@ -307,6 +307,52 @@ ssize_t spi_nor_write_data(struct spi_nor *nor, loff_t to, size_t len,
|
|||
return nor->controller_ops->write(nor, to, len, buf);
|
||||
}
|
||||
|
||||
/**
|
||||
* spi_nor_read_any_reg() - read any register from flash memory, nonvolatile or
|
||||
* volatile.
|
||||
* @nor: pointer to 'struct spi_nor'.
|
||||
* @op: SPI memory operation. op->data.buf must be DMA-able.
|
||||
* @proto: SPI protocol to use for the register operation.
|
||||
*
|
||||
* Return: zero on success, -errno otherwise
|
||||
*/
|
||||
int spi_nor_read_any_reg(struct spi_nor *nor, struct spi_mem_op *op,
|
||||
enum spi_nor_protocol proto)
|
||||
{
|
||||
if (!nor->spimem)
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
spi_nor_spimem_setup_op(nor, op, proto);
|
||||
return spi_nor_spimem_exec_op(nor, op);
|
||||
}
|
||||
|
||||
/**
|
||||
* spi_nor_write_any_volatile_reg() - write any volatile register to flash
|
||||
* memory.
|
||||
* @nor: pointer to 'struct spi_nor'
|
||||
* @op: SPI memory operation. op->data.buf must be DMA-able.
|
||||
* @proto: SPI protocol to use for the register operation.
|
||||
*
|
||||
* Writing volatile registers are instant according to some manufacturers
|
||||
* (Cypress, Micron) and do not need any status polling.
|
||||
*
|
||||
* Return: zero on success, -errno otherwise
|
||||
*/
|
||||
int spi_nor_write_any_volatile_reg(struct spi_nor *nor, struct spi_mem_op *op,
|
||||
enum spi_nor_protocol proto)
|
||||
{
|
||||
int ret;
|
||||
|
||||
if (!nor->spimem)
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
ret = spi_nor_write_enable(nor);
|
||||
if (ret)
|
||||
return ret;
|
||||
spi_nor_spimem_setup_op(nor, op, proto);
|
||||
return spi_nor_spimem_exec_op(nor, op);
|
||||
}
|
||||
|
||||
/**
|
||||
* spi_nor_write_enable() - Set write enable latch with Write Enable command.
|
||||
* @nor: pointer to 'struct spi_nor'.
|
||||
|
@ -318,11 +364,7 @@ int spi_nor_write_enable(struct spi_nor *nor)
|
|||
int ret;
|
||||
|
||||
if (nor->spimem) {
|
||||
struct spi_mem_op op =
|
||||
SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_WREN, 0),
|
||||
SPI_MEM_OP_NO_ADDR,
|
||||
SPI_MEM_OP_NO_DUMMY,
|
||||
SPI_MEM_OP_NO_DATA);
|
||||
struct spi_mem_op op = SPI_NOR_WREN_OP;
|
||||
|
||||
spi_nor_spimem_setup_op(nor, &op, nor->reg_proto);
|
||||
|
||||
|
@ -349,11 +391,7 @@ int spi_nor_write_disable(struct spi_nor *nor)
|
|||
int ret;
|
||||
|
||||
if (nor->spimem) {
|
||||
struct spi_mem_op op =
|
||||
SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_WRDI, 0),
|
||||
SPI_MEM_OP_NO_ADDR,
|
||||
SPI_MEM_OP_NO_DUMMY,
|
||||
SPI_MEM_OP_NO_DATA);
|
||||
struct spi_mem_op op = SPI_NOR_WRDI_OP;
|
||||
|
||||
spi_nor_spimem_setup_op(nor, &op, nor->reg_proto);
|
||||
|
||||
|
@ -369,6 +407,37 @@ int spi_nor_write_disable(struct spi_nor *nor)
|
|||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* spi_nor_read_id() - Read the JEDEC ID.
|
||||
* @nor: pointer to 'struct spi_nor'.
|
||||
* @naddr: number of address bytes to send. Can be zero if the operation
|
||||
* does not need to send an address.
|
||||
* @ndummy: number of dummy bytes to send after an opcode or address. Can
|
||||
* be zero if the operation does not require dummy bytes.
|
||||
* @id: pointer to a DMA-able buffer where the value of the JEDEC ID
|
||||
* will be written.
|
||||
* @proto: the SPI protocol for register operation.
|
||||
*
|
||||
* Return: 0 on success, -errno otherwise.
|
||||
*/
|
||||
int spi_nor_read_id(struct spi_nor *nor, u8 naddr, u8 ndummy, u8 *id,
|
||||
enum spi_nor_protocol proto)
|
||||
{
|
||||
int ret;
|
||||
|
||||
if (nor->spimem) {
|
||||
struct spi_mem_op op =
|
||||
SPI_NOR_READID_OP(naddr, ndummy, id, SPI_NOR_MAX_ID_LEN);
|
||||
|
||||
spi_nor_spimem_setup_op(nor, &op, proto);
|
||||
ret = spi_mem_exec_op(nor->spimem, &op);
|
||||
} else {
|
||||
ret = nor->controller_ops->read_reg(nor, SPINOR_OP_RDID, id,
|
||||
SPI_NOR_MAX_ID_LEN);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* spi_nor_read_sr() - Read the Status Register.
|
||||
* @nor: pointer to 'struct spi_nor'.
|
||||
|
@ -382,11 +451,7 @@ int spi_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(SPINOR_OP_RDSR, 0),
|
||||
SPI_MEM_OP_NO_ADDR,
|
||||
SPI_MEM_OP_NO_DUMMY,
|
||||
SPI_MEM_OP_DATA_IN(1, sr, 0));
|
||||
struct spi_mem_op op = SPI_NOR_RDSR_OP(sr);
|
||||
|
||||
if (nor->reg_proto == SNOR_PROTO_8_8_8_DTR) {
|
||||
op.addr.nbytes = nor->params->rdsr_addr_nbytes;
|
||||
|
@ -426,11 +491,7 @@ int spi_nor_read_cr(struct spi_nor *nor, u8 *cr)
|
|||
int ret;
|
||||
|
||||
if (nor->spimem) {
|
||||
struct spi_mem_op op =
|
||||
SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_RDCR, 0),
|
||||
SPI_MEM_OP_NO_ADDR,
|
||||
SPI_MEM_OP_NO_DUMMY,
|
||||
SPI_MEM_OP_DATA_IN(1, cr, 0));
|
||||
struct spi_mem_op op = SPI_NOR_RDCR_OP(cr);
|
||||
|
||||
spi_nor_spimem_setup_op(nor, &op, nor->reg_proto);
|
||||
|
||||
|
@ -459,14 +520,7 @@ int spi_nor_set_4byte_addr_mode(struct spi_nor *nor, bool enable)
|
|||
int ret;
|
||||
|
||||
if (nor->spimem) {
|
||||
struct spi_mem_op op =
|
||||
SPI_MEM_OP(SPI_MEM_OP_CMD(enable ?
|
||||
SPINOR_OP_EN4B :
|
||||
SPINOR_OP_EX4B,
|
||||
0),
|
||||
SPI_MEM_OP_NO_ADDR,
|
||||
SPI_MEM_OP_NO_DUMMY,
|
||||
SPI_MEM_OP_NO_DATA);
|
||||
struct spi_mem_op op = SPI_NOR_EN4B_EX4B_OP(enable);
|
||||
|
||||
spi_nor_spimem_setup_op(nor, &op, nor->reg_proto);
|
||||
|
||||
|
@ -500,11 +554,7 @@ static int spansion_set_4byte_addr_mode(struct spi_nor *nor, bool enable)
|
|||
nor->bouncebuf[0] = enable << 7;
|
||||
|
||||
if (nor->spimem) {
|
||||
struct spi_mem_op op =
|
||||
SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_BRWR, 0),
|
||||
SPI_MEM_OP_NO_ADDR,
|
||||
SPI_MEM_OP_NO_DUMMY,
|
||||
SPI_MEM_OP_DATA_OUT(1, nor->bouncebuf, 0));
|
||||
struct spi_mem_op op = SPI_NOR_BRWR_OP(nor->bouncebuf);
|
||||
|
||||
spi_nor_spimem_setup_op(nor, &op, nor->reg_proto);
|
||||
|
||||
|
@ -520,40 +570,6 @@ static int spansion_set_4byte_addr_mode(struct spi_nor *nor, bool enable)
|
|||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* spi_nor_write_ear() - Write Extended Address Register.
|
||||
* @nor: pointer to 'struct spi_nor'.
|
||||
* @ear: value to write to the Extended Address Register.
|
||||
*
|
||||
* Return: 0 on success, -errno otherwise.
|
||||
*/
|
||||
int spi_nor_write_ear(struct spi_nor *nor, u8 ear)
|
||||
{
|
||||
int ret;
|
||||
|
||||
nor->bouncebuf[0] = ear;
|
||||
|
||||
if (nor->spimem) {
|
||||
struct spi_mem_op op =
|
||||
SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_WREAR, 0),
|
||||
SPI_MEM_OP_NO_ADDR,
|
||||
SPI_MEM_OP_NO_DUMMY,
|
||||
SPI_MEM_OP_DATA_OUT(1, nor->bouncebuf, 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_write_reg(nor, SPINOR_OP_WREAR,
|
||||
nor->bouncebuf, 1);
|
||||
}
|
||||
|
||||
if (ret)
|
||||
dev_dbg(nor->dev, "error %d writing EAR\n", ret);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* spi_nor_sr_ready() - Query the Status Register to see if the flash is ready
|
||||
* for new commands.
|
||||
|
@ -649,11 +665,7 @@ int spi_nor_global_block_unlock(struct spi_nor *nor)
|
|||
return ret;
|
||||
|
||||
if (nor->spimem) {
|
||||
struct spi_mem_op op =
|
||||
SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_GBULK, 0),
|
||||
SPI_MEM_OP_NO_ADDR,
|
||||
SPI_MEM_OP_NO_DUMMY,
|
||||
SPI_MEM_OP_NO_DATA);
|
||||
struct spi_mem_op op = SPI_NOR_GBULK_OP;
|
||||
|
||||
spi_nor_spimem_setup_op(nor, &op, nor->reg_proto);
|
||||
|
||||
|
@ -688,11 +700,7 @@ int spi_nor_write_sr(struct spi_nor *nor, const u8 *sr, size_t len)
|
|||
return ret;
|
||||
|
||||
if (nor->spimem) {
|
||||
struct spi_mem_op op =
|
||||
SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_WRSR, 0),
|
||||
SPI_MEM_OP_NO_ADDR,
|
||||
SPI_MEM_OP_NO_DUMMY,
|
||||
SPI_MEM_OP_DATA_OUT(len, sr, 0));
|
||||
struct spi_mem_op op = SPI_NOR_WRSR_OP(sr, len);
|
||||
|
||||
spi_nor_spimem_setup_op(nor, &op, nor->reg_proto);
|
||||
|
||||
|
@ -788,6 +796,15 @@ static int spi_nor_write_16bit_sr_and_check(struct spi_nor *nor, u8 sr1)
|
|||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = spi_nor_read_sr(nor, sr_cr);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (sr1 != sr_cr[0]) {
|
||||
dev_dbg(nor->dev, "SR: Read back test failed\n");
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
if (nor->flags & SNOR_F_NO_READ_CR)
|
||||
return 0;
|
||||
|
||||
|
@ -892,11 +909,7 @@ static int spi_nor_write_sr2(struct spi_nor *nor, const u8 *sr2)
|
|||
return ret;
|
||||
|
||||
if (nor->spimem) {
|
||||
struct spi_mem_op op =
|
||||
SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_WRSR2, 0),
|
||||
SPI_MEM_OP_NO_ADDR,
|
||||
SPI_MEM_OP_NO_DUMMY,
|
||||
SPI_MEM_OP_DATA_OUT(1, sr2, 0));
|
||||
struct spi_mem_op op = SPI_NOR_WRSR2_OP(sr2);
|
||||
|
||||
spi_nor_spimem_setup_op(nor, &op, nor->reg_proto);
|
||||
|
||||
|
@ -928,11 +941,7 @@ static int spi_nor_read_sr2(struct spi_nor *nor, u8 *sr2)
|
|||
int ret;
|
||||
|
||||
if (nor->spimem) {
|
||||
struct spi_mem_op op =
|
||||
SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_RDSR2, 0),
|
||||
SPI_MEM_OP_NO_ADDR,
|
||||
SPI_MEM_OP_NO_DUMMY,
|
||||
SPI_MEM_OP_DATA_IN(1, sr2, 0));
|
||||
struct spi_mem_op op = SPI_NOR_RDSR2_OP(sr2);
|
||||
|
||||
spi_nor_spimem_setup_op(nor, &op, nor->reg_proto);
|
||||
|
||||
|
@ -961,11 +970,7 @@ static int spi_nor_erase_chip(struct spi_nor *nor)
|
|||
dev_dbg(nor->dev, " %lldKiB\n", (long long)(nor->mtd.size >> 10));
|
||||
|
||||
if (nor->spimem) {
|
||||
struct spi_mem_op op =
|
||||
SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_CHIP_ERASE, 0),
|
||||
SPI_MEM_OP_NO_ADDR,
|
||||
SPI_MEM_OP_NO_DUMMY,
|
||||
SPI_MEM_OP_NO_DATA);
|
||||
struct spi_mem_op op = SPI_NOR_CHIP_ERASE_OP;
|
||||
|
||||
spi_nor_spimem_setup_op(nor, &op, nor->write_proto);
|
||||
|
||||
|
@ -1107,10 +1112,8 @@ int spi_nor_erase_sector(struct spi_nor *nor, u32 addr)
|
|||
|
||||
if (nor->spimem) {
|
||||
struct spi_mem_op op =
|
||||
SPI_MEM_OP(SPI_MEM_OP_CMD(nor->erase_opcode, 0),
|
||||
SPI_MEM_OP_ADDR(nor->addr_width, addr, 0),
|
||||
SPI_MEM_OP_NO_DUMMY,
|
||||
SPI_MEM_OP_NO_DATA);
|
||||
SPI_NOR_SECTOR_ERASE_OP(nor->erase_opcode,
|
||||
nor->addr_width, addr);
|
||||
|
||||
spi_nor_spimem_setup_op(nor, &op, nor->write_proto);
|
||||
|
||||
|
@ -1629,58 +1632,45 @@ static const struct spi_nor_manufacturer *manufacturers[] = {
|
|||
&spi_nor_xmc,
|
||||
};
|
||||
|
||||
static const struct flash_info *
|
||||
spi_nor_search_part_by_id(const struct flash_info *parts, unsigned int nparts,
|
||||
const u8 *id)
|
||||
static const struct flash_info *spi_nor_match_id(struct spi_nor *nor,
|
||||
const u8 *id)
|
||||
{
|
||||
unsigned int i;
|
||||
const struct flash_info *part;
|
||||
unsigned int i, j;
|
||||
|
||||
for (i = 0; i < nparts; i++) {
|
||||
if (parts[i].id_len &&
|
||||
!memcmp(parts[i].id, id, parts[i].id_len))
|
||||
return &parts[i];
|
||||
for (i = 0; i < ARRAY_SIZE(manufacturers); i++) {
|
||||
for (j = 0; j < manufacturers[i]->nparts; j++) {
|
||||
part = &manufacturers[i]->parts[j];
|
||||
if (part->id_len &&
|
||||
!memcmp(part->id, id, part->id_len)) {
|
||||
nor->manufacturer = manufacturers[i];
|
||||
return part;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static const struct flash_info *spi_nor_read_id(struct spi_nor *nor)
|
||||
static const struct flash_info *spi_nor_detect(struct spi_nor *nor)
|
||||
{
|
||||
const struct flash_info *info;
|
||||
u8 *id = nor->bouncebuf;
|
||||
unsigned int i;
|
||||
int ret;
|
||||
|
||||
if (nor->spimem) {
|
||||
struct spi_mem_op op =
|
||||
SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_RDID, 1),
|
||||
SPI_MEM_OP_NO_ADDR,
|
||||
SPI_MEM_OP_NO_DUMMY,
|
||||
SPI_MEM_OP_DATA_IN(SPI_NOR_MAX_ID_LEN, id, 1));
|
||||
|
||||
ret = spi_mem_exec_op(nor->spimem, &op);
|
||||
} else {
|
||||
ret = nor->controller_ops->read_reg(nor, SPINOR_OP_RDID, id,
|
||||
SPI_NOR_MAX_ID_LEN);
|
||||
}
|
||||
ret = spi_nor_read_id(nor, 0, 0, id, nor->reg_proto);
|
||||
if (ret) {
|
||||
dev_dbg(nor->dev, "error %d reading JEDEC ID\n", ret);
|
||||
return ERR_PTR(ret);
|
||||
}
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(manufacturers); i++) {
|
||||
info = spi_nor_search_part_by_id(manufacturers[i]->parts,
|
||||
manufacturers[i]->nparts,
|
||||
id);
|
||||
if (info) {
|
||||
nor->manufacturer = manufacturers[i];
|
||||
return info;
|
||||
}
|
||||
info = spi_nor_match_id(nor, id);
|
||||
if (!info) {
|
||||
dev_err(nor->dev, "unrecognized JEDEC id bytes: %*ph\n",
|
||||
SPI_NOR_MAX_ID_LEN, id);
|
||||
return ERR_PTR(-ENODEV);
|
||||
}
|
||||
|
||||
dev_err(nor->dev, "unrecognized JEDEC id bytes: %*ph\n",
|
||||
SPI_NOR_MAX_ID_LEN, id);
|
||||
return ERR_PTR(-ENODEV);
|
||||
return info;
|
||||
}
|
||||
|
||||
static int spi_nor_read(struct mtd_info *mtd, loff_t from, size_t len,
|
||||
|
@ -1860,7 +1850,7 @@ int spi_nor_hwcaps_read2cmd(u32 hwcaps)
|
|||
ARRAY_SIZE(hwcaps_read2cmd));
|
||||
}
|
||||
|
||||
static int spi_nor_hwcaps_pp2cmd(u32 hwcaps)
|
||||
int spi_nor_hwcaps_pp2cmd(u32 hwcaps)
|
||||
{
|
||||
static const int hwcaps_pp2cmd[][2] = {
|
||||
{ SNOR_HWCAPS_PP, SNOR_CMD_PP },
|
||||
|
@ -1919,10 +1909,7 @@ static int spi_nor_spimem_check_op(struct spi_nor *nor,
|
|||
static int spi_nor_spimem_check_readop(struct spi_nor *nor,
|
||||
const struct spi_nor_read_command *read)
|
||||
{
|
||||
struct spi_mem_op op = SPI_MEM_OP(SPI_MEM_OP_CMD(read->opcode, 0),
|
||||
SPI_MEM_OP_ADDR(3, 0, 0),
|
||||
SPI_MEM_OP_DUMMY(1, 0),
|
||||
SPI_MEM_OP_DATA_IN(2, NULL, 0));
|
||||
struct spi_mem_op op = SPI_NOR_READ_OP(read->opcode);
|
||||
|
||||
spi_nor_spimem_setup_op(nor, &op, read->proto);
|
||||
|
||||
|
@ -1945,10 +1932,7 @@ static int spi_nor_spimem_check_readop(struct spi_nor *nor,
|
|||
static int spi_nor_spimem_check_pp(struct spi_nor *nor,
|
||||
const struct spi_nor_pp_command *pp)
|
||||
{
|
||||
struct spi_mem_op op = SPI_MEM_OP(SPI_MEM_OP_CMD(pp->opcode, 0),
|
||||
SPI_MEM_OP_ADDR(3, 0, 0),
|
||||
SPI_MEM_OP_NO_DUMMY,
|
||||
SPI_MEM_OP_DATA_OUT(2, NULL, 0));
|
||||
struct spi_mem_op op = SPI_NOR_PP_OP(pp->opcode);
|
||||
|
||||
spi_nor_spimem_setup_op(nor, &op, pp->proto);
|
||||
|
||||
|
@ -2772,10 +2756,7 @@ static void spi_nor_soft_reset(struct spi_nor *nor)
|
|||
struct spi_mem_op op;
|
||||
int ret;
|
||||
|
||||
op = (struct spi_mem_op)SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_SRSTEN, 0),
|
||||
SPI_MEM_OP_NO_DUMMY,
|
||||
SPI_MEM_OP_NO_ADDR,
|
||||
SPI_MEM_OP_NO_DATA);
|
||||
op = (struct spi_mem_op)SPINOR_SRSTEN_OP;
|
||||
|
||||
spi_nor_spimem_setup_op(nor, &op, nor->reg_proto);
|
||||
|
||||
|
@ -2785,10 +2766,7 @@ static void spi_nor_soft_reset(struct spi_nor *nor)
|
|||
return;
|
||||
}
|
||||
|
||||
op = (struct spi_mem_op)SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_SRST, 0),
|
||||
SPI_MEM_OP_NO_DUMMY,
|
||||
SPI_MEM_OP_NO_ADDR,
|
||||
SPI_MEM_OP_NO_DATA);
|
||||
op = (struct spi_mem_op)SPINOR_SRST_OP;
|
||||
|
||||
spi_nor_spimem_setup_op(nor, &op, nor->reg_proto);
|
||||
|
||||
|
@ -2876,8 +2854,8 @@ void spi_nor_restore(struct spi_nor *nor)
|
|||
}
|
||||
EXPORT_SYMBOL_GPL(spi_nor_restore);
|
||||
|
||||
static const struct flash_info *spi_nor_match_id(struct spi_nor *nor,
|
||||
const char *name)
|
||||
static const struct flash_info *spi_nor_match_name(struct spi_nor *nor,
|
||||
const char *name)
|
||||
{
|
||||
unsigned int i, j;
|
||||
|
||||
|
@ -2899,12 +2877,10 @@ static const struct flash_info *spi_nor_get_flash_info(struct spi_nor *nor,
|
|||
const struct flash_info *info = NULL;
|
||||
|
||||
if (name)
|
||||
info = spi_nor_match_id(nor, name);
|
||||
info = spi_nor_match_name(nor, name);
|
||||
/* Try to auto-detect if chip name wasn't specified or not found */
|
||||
if (!info)
|
||||
info = spi_nor_read_id(nor);
|
||||
if (IS_ERR_OR_NULL(info))
|
||||
return ERR_PTR(-ENOENT);
|
||||
return spi_nor_detect(nor);
|
||||
|
||||
/*
|
||||
* If caller has specified name of flash model that can normally be
|
||||
|
@ -2913,7 +2889,7 @@ static const struct flash_info *spi_nor_get_flash_info(struct spi_nor *nor,
|
|||
if (name && info->id_len) {
|
||||
const struct flash_info *jinfo;
|
||||
|
||||
jinfo = spi_nor_read_id(nor);
|
||||
jinfo = spi_nor_detect(nor);
|
||||
if (IS_ERR(jinfo)) {
|
||||
return jinfo;
|
||||
} else if (jinfo != info) {
|
||||
|
@ -3156,6 +3132,8 @@ static int spi_nor_probe(struct spi_mem *spimem)
|
|||
if (ret)
|
||||
return ret;
|
||||
|
||||
spi_nor_debugfs_register(nor);
|
||||
|
||||
/*
|
||||
* None of the existing parts have > 512B pages, but let's play safe
|
||||
* and add this logic so that if anyone ever adds support for such
|
||||
|
|
|
@ -11,6 +11,110 @@
|
|||
|
||||
#define SPI_NOR_MAX_ID_LEN 6
|
||||
|
||||
/* Standard SPI NOR flash operations. */
|
||||
#define SPI_NOR_READID_OP(naddr, ndummy, buf, len) \
|
||||
SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_RDID, 0), \
|
||||
SPI_MEM_OP_ADDR(naddr, 0, 0), \
|
||||
SPI_MEM_OP_DUMMY(ndummy, 0), \
|
||||
SPI_MEM_OP_DATA_IN(len, buf, 0))
|
||||
|
||||
#define SPI_NOR_WREN_OP \
|
||||
SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_WREN, 0), \
|
||||
SPI_MEM_OP_NO_ADDR, \
|
||||
SPI_MEM_OP_NO_DUMMY, \
|
||||
SPI_MEM_OP_NO_DATA)
|
||||
|
||||
#define SPI_NOR_WRDI_OP \
|
||||
SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_WRDI, 0), \
|
||||
SPI_MEM_OP_NO_ADDR, \
|
||||
SPI_MEM_OP_NO_DUMMY, \
|
||||
SPI_MEM_OP_NO_DATA)
|
||||
|
||||
#define SPI_NOR_RDSR_OP(buf) \
|
||||
SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_RDSR, 0), \
|
||||
SPI_MEM_OP_NO_ADDR, \
|
||||
SPI_MEM_OP_NO_DUMMY, \
|
||||
SPI_MEM_OP_DATA_IN(1, buf, 0))
|
||||
|
||||
#define SPI_NOR_WRSR_OP(buf, len) \
|
||||
SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_WRSR, 0), \
|
||||
SPI_MEM_OP_NO_ADDR, \
|
||||
SPI_MEM_OP_NO_DUMMY, \
|
||||
SPI_MEM_OP_DATA_OUT(len, buf, 0))
|
||||
|
||||
#define SPI_NOR_RDSR2_OP(buf) \
|
||||
SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_RDSR2, 0), \
|
||||
SPI_MEM_OP_NO_ADDR, \
|
||||
SPI_MEM_OP_NO_DUMMY, \
|
||||
SPI_MEM_OP_DATA_OUT(1, buf, 0))
|
||||
|
||||
#define SPI_NOR_WRSR2_OP(buf) \
|
||||
SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_WRSR2, 0), \
|
||||
SPI_MEM_OP_NO_ADDR, \
|
||||
SPI_MEM_OP_NO_DUMMY, \
|
||||
SPI_MEM_OP_DATA_OUT(1, buf, 0))
|
||||
|
||||
#define SPI_NOR_RDCR_OP(buf) \
|
||||
SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_RDCR, 0), \
|
||||
SPI_MEM_OP_NO_ADDR, \
|
||||
SPI_MEM_OP_NO_DUMMY, \
|
||||
SPI_MEM_OP_DATA_IN(1, buf, 0))
|
||||
|
||||
#define SPI_NOR_EN4B_EX4B_OP(enable) \
|
||||
SPI_MEM_OP(SPI_MEM_OP_CMD(enable ? SPINOR_OP_EN4B : SPINOR_OP_EX4B, 0), \
|
||||
SPI_MEM_OP_NO_ADDR, \
|
||||
SPI_MEM_OP_NO_DUMMY, \
|
||||
SPI_MEM_OP_NO_DATA)
|
||||
|
||||
#define SPI_NOR_BRWR_OP(buf) \
|
||||
SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_BRWR, 0), \
|
||||
SPI_MEM_OP_NO_ADDR, \
|
||||
SPI_MEM_OP_NO_DUMMY, \
|
||||
SPI_MEM_OP_DATA_OUT(1, buf, 0))
|
||||
|
||||
#define SPI_NOR_GBULK_OP \
|
||||
SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_GBULK, 0), \
|
||||
SPI_MEM_OP_NO_ADDR, \
|
||||
SPI_MEM_OP_NO_DUMMY, \
|
||||
SPI_MEM_OP_NO_DATA)
|
||||
|
||||
#define SPI_NOR_CHIP_ERASE_OP \
|
||||
SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_CHIP_ERASE, 0), \
|
||||
SPI_MEM_OP_NO_ADDR, \
|
||||
SPI_MEM_OP_NO_DUMMY, \
|
||||
SPI_MEM_OP_NO_DATA)
|
||||
|
||||
#define SPI_NOR_SECTOR_ERASE_OP(opcode, addr_width, addr) \
|
||||
SPI_MEM_OP(SPI_MEM_OP_CMD(opcode, 0), \
|
||||
SPI_MEM_OP_ADDR(addr_width, addr, 0), \
|
||||
SPI_MEM_OP_NO_DUMMY, \
|
||||
SPI_MEM_OP_NO_DATA)
|
||||
|
||||
#define SPI_NOR_READ_OP(opcode) \
|
||||
SPI_MEM_OP(SPI_MEM_OP_CMD(opcode, 0), \
|
||||
SPI_MEM_OP_ADDR(3, 0, 0), \
|
||||
SPI_MEM_OP_DUMMY(1, 0), \
|
||||
SPI_MEM_OP_DATA_IN(2, NULL, 0))
|
||||
|
||||
#define SPI_NOR_PP_OP(opcode) \
|
||||
SPI_MEM_OP(SPI_MEM_OP_CMD(opcode, 0), \
|
||||
SPI_MEM_OP_ADDR(3, 0, 0), \
|
||||
SPI_MEM_OP_NO_DUMMY, \
|
||||
SPI_MEM_OP_DATA_OUT(2, NULL, 0))
|
||||
|
||||
#define SPINOR_SRSTEN_OP \
|
||||
SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_SRSTEN, 0), \
|
||||
SPI_MEM_OP_NO_DUMMY, \
|
||||
SPI_MEM_OP_NO_ADDR, \
|
||||
SPI_MEM_OP_NO_DATA)
|
||||
|
||||
#define SPINOR_SRST_OP \
|
||||
SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_SRST, 0), \
|
||||
SPI_MEM_OP_NO_DUMMY, \
|
||||
SPI_MEM_OP_NO_ADDR, \
|
||||
SPI_MEM_OP_NO_DATA)
|
||||
|
||||
/* Keep these in sync with the list in debugfs.c */
|
||||
enum spi_nor_option_flags {
|
||||
SNOR_F_HAS_SR_TB = BIT(0),
|
||||
SNOR_F_NO_OP_CHIP_ERASE = BIT(1),
|
||||
|
@ -236,9 +340,10 @@ struct spi_nor_otp {
|
|||
* @writesize Minimal writable flash unit size. Defaults to 1. Set to
|
||||
* ECC unit size for ECC-ed flashes.
|
||||
* @page_size: the page size of the SPI NOR flash memory.
|
||||
* @rdsr_dummy: dummy cycles needed for Read Status Register command.
|
||||
* @rdsr_dummy: dummy cycles needed for Read Status Register command
|
||||
* in octal DTR mode.
|
||||
* @rdsr_addr_nbytes: dummy address bytes needed for Read Status Register
|
||||
* command.
|
||||
* command in octal DTR mode.
|
||||
* @hwcaps: describes the read and page program hardware
|
||||
* capabilities.
|
||||
* @reads: read capabilities ordered by priority: the higher index
|
||||
|
@ -526,7 +631,6 @@ void spi_nor_spimem_setup_op(const struct spi_nor *nor,
|
|||
int spi_nor_write_enable(struct spi_nor *nor);
|
||||
int spi_nor_write_disable(struct spi_nor *nor);
|
||||
int spi_nor_set_4byte_addr_mode(struct spi_nor *nor, bool enable);
|
||||
int spi_nor_write_ear(struct spi_nor *nor, u8 ear);
|
||||
int spi_nor_wait_till_ready(struct spi_nor *nor);
|
||||
int spi_nor_global_block_unlock(struct spi_nor *nor);
|
||||
int spi_nor_lock_and_prep(struct spi_nor *nor);
|
||||
|
@ -534,6 +638,8 @@ void spi_nor_unlock_and_unprep(struct spi_nor *nor);
|
|||
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_id(struct spi_nor *nor, u8 naddr, u8 ndummy, u8 *id,
|
||||
enum spi_nor_protocol reg_proto);
|
||||
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);
|
||||
|
@ -545,6 +651,10 @@ 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,
|
||||
const u8 *buf);
|
||||
int spi_nor_read_any_reg(struct spi_nor *nor, struct spi_mem_op *op,
|
||||
enum spi_nor_protocol proto);
|
||||
int spi_nor_write_any_volatile_reg(struct spi_nor *nor, struct spi_mem_op *op,
|
||||
enum spi_nor_protocol proto);
|
||||
int spi_nor_erase_sector(struct spi_nor *nor, u32 addr);
|
||||
|
||||
int spi_nor_otp_read_secr(struct spi_nor *nor, loff_t addr, size_t len, u8 *buf);
|
||||
|
@ -555,6 +665,7 @@ int spi_nor_otp_lock_sr2(struct spi_nor *nor, unsigned int region);
|
|||
int spi_nor_otp_is_locked_sr2(struct spi_nor *nor, unsigned int region);
|
||||
|
||||
int spi_nor_hwcaps_read2cmd(u32 hwcaps);
|
||||
int spi_nor_hwcaps_pp2cmd(u32 hwcaps);
|
||||
u8 spi_nor_convert_3to4_read(u8 opcode);
|
||||
void spi_nor_set_read_settings(struct spi_nor_read_command *read,
|
||||
u8 num_mode_clocks,
|
||||
|
@ -590,4 +701,10 @@ static inline struct spi_nor *mtd_to_spi_nor(struct mtd_info *mtd)
|
|||
return container_of(mtd, struct spi_nor, mtd);
|
||||
}
|
||||
|
||||
#ifdef CONFIG_DEBUG_FS
|
||||
void spi_nor_debugfs_register(struct spi_nor *nor);
|
||||
#else
|
||||
static inline void spi_nor_debugfs_register(struct spi_nor *nor) {}
|
||||
#endif
|
||||
|
||||
#endif /* __LINUX_MTD_SPI_NOR_INTERNAL_H */
|
||||
|
|
249
drivers/mtd/spi-nor/debugfs.c
Normal file
249
drivers/mtd/spi-nor/debugfs.c
Normal file
|
@ -0,0 +1,249 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
#include <linux/mtd/spi-nor.h>
|
||||
#include <linux/spi/spi.h>
|
||||
#include <linux/spi/spi-mem.h>
|
||||
#include <linux/debugfs.h>
|
||||
|
||||
#include "core.h"
|
||||
|
||||
#define SPI_NOR_DEBUGFS_ROOT "spi-nor"
|
||||
|
||||
#define SNOR_F_NAME(name) [ilog2(SNOR_F_##name)] = #name
|
||||
static const char *const snor_f_names[] = {
|
||||
SNOR_F_NAME(HAS_SR_TB),
|
||||
SNOR_F_NAME(NO_OP_CHIP_ERASE),
|
||||
SNOR_F_NAME(BROKEN_RESET),
|
||||
SNOR_F_NAME(4B_OPCODES),
|
||||
SNOR_F_NAME(HAS_4BAIT),
|
||||
SNOR_F_NAME(HAS_LOCK),
|
||||
SNOR_F_NAME(HAS_16BIT_SR),
|
||||
SNOR_F_NAME(NO_READ_CR),
|
||||
SNOR_F_NAME(HAS_SR_TB_BIT6),
|
||||
SNOR_F_NAME(HAS_4BIT_BP),
|
||||
SNOR_F_NAME(HAS_SR_BP3_BIT6),
|
||||
SNOR_F_NAME(IO_MODE_EN_VOLATILE),
|
||||
SNOR_F_NAME(SOFT_RESET),
|
||||
SNOR_F_NAME(SWP_IS_VOLATILE),
|
||||
};
|
||||
#undef SNOR_F_NAME
|
||||
|
||||
static const char *spi_nor_protocol_name(enum spi_nor_protocol proto)
|
||||
{
|
||||
switch (proto) {
|
||||
case SNOR_PROTO_1_1_1: return "1S-1S-1S";
|
||||
case SNOR_PROTO_1_1_2: return "1S-1S-2S";
|
||||
case SNOR_PROTO_1_1_4: return "1S-1S-4S";
|
||||
case SNOR_PROTO_1_1_8: return "1S-1S-8S";
|
||||
case SNOR_PROTO_1_2_2: return "1S-2S-2S";
|
||||
case SNOR_PROTO_1_4_4: return "1S-4S-4S";
|
||||
case SNOR_PROTO_1_8_8: return "1S-8S-8S";
|
||||
case SNOR_PROTO_2_2_2: return "2S-2S-2S";
|
||||
case SNOR_PROTO_4_4_4: return "4S-4S-4S";
|
||||
case SNOR_PROTO_8_8_8: return "8S-8S-8S";
|
||||
case SNOR_PROTO_1_1_1_DTR: return "1D-1D-1D";
|
||||
case SNOR_PROTO_1_2_2_DTR: return "1D-2D-2D";
|
||||
case SNOR_PROTO_1_4_4_DTR: return "1D-4D-4D";
|
||||
case SNOR_PROTO_1_8_8_DTR: return "1D-8D-8D";
|
||||
case SNOR_PROTO_8_8_8_DTR: return "8D-8D-8D";
|
||||
}
|
||||
|
||||
return "<unknown>";
|
||||
}
|
||||
|
||||
static void spi_nor_print_flags(struct seq_file *s, unsigned long flags,
|
||||
const char *const *names, int names_len)
|
||||
{
|
||||
bool sep = false;
|
||||
int i;
|
||||
|
||||
for (i = 0; i < sizeof(flags) * BITS_PER_BYTE; i++) {
|
||||
if (!(flags & BIT(i)))
|
||||
continue;
|
||||
if (sep)
|
||||
seq_puts(s, " | ");
|
||||
sep = true;
|
||||
if (i < names_len && names[i])
|
||||
seq_puts(s, names[i]);
|
||||
else
|
||||
seq_printf(s, "1<<%d", i);
|
||||
}
|
||||
}
|
||||
|
||||
static int spi_nor_params_show(struct seq_file *s, void *data)
|
||||
{
|
||||
struct spi_nor *nor = s->private;
|
||||
struct spi_nor_flash_parameter *params = nor->params;
|
||||
struct spi_nor_erase_map *erase_map = ¶ms->erase_map;
|
||||
struct spi_nor_erase_region *region;
|
||||
const struct flash_info *info = nor->info;
|
||||
char buf[16], *str;
|
||||
int i;
|
||||
|
||||
seq_printf(s, "name\t\t%s\n", info->name);
|
||||
seq_printf(s, "id\t\t%*ph\n", info->id_len, info->id);
|
||||
string_get_size(params->size, 1, STRING_UNITS_2, buf, sizeof(buf));
|
||||
seq_printf(s, "size\t\t%s\n", buf);
|
||||
seq_printf(s, "write size\t%u\n", params->writesize);
|
||||
seq_printf(s, "page size\t%u\n", params->page_size);
|
||||
seq_printf(s, "address width\t%u\n", nor->addr_width);
|
||||
|
||||
seq_puts(s, "flags\t\t");
|
||||
spi_nor_print_flags(s, nor->flags, snor_f_names, sizeof(snor_f_names));
|
||||
seq_puts(s, "\n");
|
||||
|
||||
seq_puts(s, "\nopcodes\n");
|
||||
seq_printf(s, " read\t\t0x%02x\n", nor->read_opcode);
|
||||
seq_printf(s, " dummy cycles\t%u\n", nor->read_dummy);
|
||||
seq_printf(s, " erase\t\t0x%02x\n", nor->erase_opcode);
|
||||
seq_printf(s, " program\t0x%02x\n", nor->program_opcode);
|
||||
|
||||
switch (nor->cmd_ext_type) {
|
||||
case SPI_NOR_EXT_NONE:
|
||||
str = "none";
|
||||
break;
|
||||
case SPI_NOR_EXT_REPEAT:
|
||||
str = "repeat";
|
||||
break;
|
||||
case SPI_NOR_EXT_INVERT:
|
||||
str = "invert";
|
||||
break;
|
||||
default:
|
||||
str = "<unknown>";
|
||||
break;
|
||||
}
|
||||
seq_printf(s, " 8D extension\t%s\n", str);
|
||||
|
||||
seq_puts(s, "\nprotocols\n");
|
||||
seq_printf(s, " read\t\t%s\n",
|
||||
spi_nor_protocol_name(nor->read_proto));
|
||||
seq_printf(s, " write\t\t%s\n",
|
||||
spi_nor_protocol_name(nor->write_proto));
|
||||
seq_printf(s, " register\t%s\n",
|
||||
spi_nor_protocol_name(nor->reg_proto));
|
||||
|
||||
seq_puts(s, "\nerase commands\n");
|
||||
for (i = 0; i < SNOR_ERASE_TYPE_MAX; i++) {
|
||||
struct spi_nor_erase_type *et = &erase_map->erase_type[i];
|
||||
|
||||
if (et->size) {
|
||||
string_get_size(et->size, 1, STRING_UNITS_2, buf,
|
||||
sizeof(buf));
|
||||
seq_printf(s, " %02x (%s) [%d]\n", et->opcode, buf, i);
|
||||
}
|
||||
}
|
||||
|
||||
if (!(nor->flags & SNOR_F_NO_OP_CHIP_ERASE)) {
|
||||
string_get_size(params->size, 1, STRING_UNITS_2, buf, sizeof(buf));
|
||||
seq_printf(s, " %02x (%s)\n", SPINOR_OP_CHIP_ERASE, buf);
|
||||
}
|
||||
|
||||
seq_puts(s, "\nsector map\n");
|
||||
seq_puts(s, " region (in hex) | erase mask | flags\n");
|
||||
seq_puts(s, " ------------------+------------+----------\n");
|
||||
for (region = erase_map->regions;
|
||||
region;
|
||||
region = spi_nor_region_next(region)) {
|
||||
u64 start = region->offset & ~SNOR_ERASE_FLAGS_MASK;
|
||||
u64 flags = region->offset & SNOR_ERASE_FLAGS_MASK;
|
||||
u64 end = start + region->size - 1;
|
||||
|
||||
seq_printf(s, " %08llx-%08llx | [%c%c%c%c] | %s\n",
|
||||
start, end,
|
||||
flags & BIT(0) ? '0' : ' ',
|
||||
flags & BIT(1) ? '1' : ' ',
|
||||
flags & BIT(2) ? '2' : ' ',
|
||||
flags & BIT(3) ? '3' : ' ',
|
||||
flags & SNOR_OVERLAID_REGION ? "overlaid" : "");
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
DEFINE_SHOW_ATTRIBUTE(spi_nor_params);
|
||||
|
||||
static void spi_nor_print_read_cmd(struct seq_file *s, u32 cap,
|
||||
struct spi_nor_read_command *cmd)
|
||||
{
|
||||
seq_printf(s, " %s%s\n", spi_nor_protocol_name(cmd->proto),
|
||||
cap == SNOR_HWCAPS_READ_FAST ? " (fast read)" : "");
|
||||
seq_printf(s, " opcode\t0x%02x\n", cmd->opcode);
|
||||
seq_printf(s, " mode cycles\t%u\n", cmd->num_mode_clocks);
|
||||
seq_printf(s, " dummy cycles\t%u\n", cmd->num_wait_states);
|
||||
}
|
||||
|
||||
static void spi_nor_print_pp_cmd(struct seq_file *s,
|
||||
struct spi_nor_pp_command *cmd)
|
||||
{
|
||||
seq_printf(s, " %s\n", spi_nor_protocol_name(cmd->proto));
|
||||
seq_printf(s, " opcode\t0x%02x\n", cmd->opcode);
|
||||
}
|
||||
|
||||
static int spi_nor_capabilities_show(struct seq_file *s, void *data)
|
||||
{
|
||||
struct spi_nor *nor = s->private;
|
||||
struct spi_nor_flash_parameter *params = nor->params;
|
||||
u32 hwcaps = params->hwcaps.mask;
|
||||
int i, cmd;
|
||||
|
||||
seq_puts(s, "Supported read modes by the flash\n");
|
||||
for (i = 0; i < sizeof(hwcaps) * BITS_PER_BYTE; i++) {
|
||||
if (!(hwcaps & BIT(i)))
|
||||
continue;
|
||||
|
||||
cmd = spi_nor_hwcaps_read2cmd(BIT(i));
|
||||
if (cmd < 0)
|
||||
continue;
|
||||
|
||||
spi_nor_print_read_cmd(s, BIT(i), ¶ms->reads[cmd]);
|
||||
hwcaps &= ~BIT(i);
|
||||
}
|
||||
|
||||
seq_puts(s, "\nSupported page program modes by the flash\n");
|
||||
for (i = 0; i < sizeof(hwcaps) * BITS_PER_BYTE; i++) {
|
||||
if (!(hwcaps & BIT(i)))
|
||||
continue;
|
||||
|
||||
cmd = spi_nor_hwcaps_pp2cmd(BIT(i));
|
||||
if (cmd < 0)
|
||||
continue;
|
||||
|
||||
spi_nor_print_pp_cmd(s, ¶ms->page_programs[cmd]);
|
||||
hwcaps &= ~BIT(i);
|
||||
}
|
||||
|
||||
if (hwcaps)
|
||||
seq_printf(s, "\nunknown hwcaps 0x%x\n", hwcaps);
|
||||
|
||||
return 0;
|
||||
}
|
||||
DEFINE_SHOW_ATTRIBUTE(spi_nor_capabilities);
|
||||
|
||||
static void spi_nor_debugfs_unregister(void *data)
|
||||
{
|
||||
struct spi_nor *nor = data;
|
||||
|
||||
debugfs_remove(nor->debugfs_root);
|
||||
nor->debugfs_root = NULL;
|
||||
}
|
||||
|
||||
void spi_nor_debugfs_register(struct spi_nor *nor)
|
||||
{
|
||||
struct dentry *rootdir, *d;
|
||||
int ret;
|
||||
|
||||
/* Create rootdir once. Will never be deleted again. */
|
||||
rootdir = debugfs_lookup(SPI_NOR_DEBUGFS_ROOT, NULL);
|
||||
if (!rootdir)
|
||||
rootdir = debugfs_create_dir(SPI_NOR_DEBUGFS_ROOT, NULL);
|
||||
|
||||
ret = devm_add_action(nor->dev, spi_nor_debugfs_unregister, nor);
|
||||
if (ret)
|
||||
return;
|
||||
|
||||
d = debugfs_create_dir(dev_name(nor->dev), rootdir);
|
||||
nor->debugfs_root = d;
|
||||
|
||||
debugfs_create_file("params", 0444, d, nor, &spi_nor_params_fops);
|
||||
debugfs_create_file("capabilities", 0444, d, nor,
|
||||
&spi_nor_capabilities_fops);
|
||||
}
|
|
@ -25,7 +25,8 @@ static const struct flash_info eon_nor_parts[] = {
|
|||
{ "en25qh64", INFO(0x1c7017, 0, 64 * 1024, 128)
|
||||
NO_SFDP_FLAGS(SECT_4K | SPI_NOR_DUAL_READ) },
|
||||
{ "en25qh128", INFO(0x1c7018, 0, 64 * 1024, 256) },
|
||||
{ "en25qh256", INFO(0x1c7019, 0, 64 * 1024, 512) },
|
||||
{ "en25qh256", INFO(0x1c7019, 0, 64 * 1024, 512)
|
||||
PARSE_SFDP },
|
||||
{ "en25s64", INFO(0x1c3817, 0, 64 * 1024, 128)
|
||||
NO_SFDP_FLAGS(SECT_4K) },
|
||||
};
|
||||
|
|
|
@ -28,82 +28,52 @@
|
|||
#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)
|
||||
/* Micron ST SPI NOR flash operations. */
|
||||
#define MICRON_ST_NOR_WR_ANY_REG_OP(naddr, addr, ndata, buf) \
|
||||
SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_MT_WR_ANY_REG, 0), \
|
||||
SPI_MEM_OP_ADDR(naddr, addr, 0), \
|
||||
SPI_MEM_OP_NO_DUMMY, \
|
||||
SPI_MEM_OP_DATA_OUT(ndata, buf, 0))
|
||||
|
||||
#define MICRON_ST_RDFSR_OP(buf) \
|
||||
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, buf, 0))
|
||||
|
||||
#define MICRON_ST_CLFSR_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)
|
||||
|
||||
static int micron_st_nor_octal_dtr_en(struct spi_nor *nor)
|
||||
{
|
||||
struct spi_mem_op op;
|
||||
u8 *buf = nor->bouncebuf;
|
||||
int ret;
|
||||
|
||||
if (enable) {
|
||||
/* Use 20 dummy cycles for memory array reads. */
|
||||
ret = spi_nor_write_enable(nor);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
*buf = 20;
|
||||
op = (struct spi_mem_op)
|
||||
SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_MT_WR_ANY_REG, 1),
|
||||
SPI_MEM_OP_ADDR(3, SPINOR_REG_MT_CFR1V, 1),
|
||||
SPI_MEM_OP_NO_DUMMY,
|
||||
SPI_MEM_OP_DATA_OUT(1, buf, 1));
|
||||
|
||||
ret = spi_mem_exec_op(nor->spimem, &op);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = spi_nor_wait_till_ready(nor);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = spi_nor_write_enable(nor);
|
||||
/* Use 20 dummy cycles for memory array reads. */
|
||||
*buf = 20;
|
||||
op = (struct spi_mem_op)
|
||||
MICRON_ST_NOR_WR_ANY_REG_OP(3, SPINOR_REG_MT_CFR1V, 1, buf);
|
||||
ret = spi_nor_write_any_volatile_reg(nor, &op, nor->reg_proto);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (enable) {
|
||||
buf[0] = SPINOR_MT_OCT_DTR;
|
||||
} else {
|
||||
/*
|
||||
* The register is 1-byte wide, but 1-byte transactions are not
|
||||
* allowed in 8D-8D-8D mode. The next register is the dummy
|
||||
* cycle configuration register. Since the transaction needs to
|
||||
* be at least 2 bytes wide, set the next register to its
|
||||
* default value. This also makes sense because the value was
|
||||
* changed when enabling 8D-8D-8D mode, it should be reset when
|
||||
* disabling.
|
||||
*/
|
||||
buf[0] = SPINOR_MT_EXSPI;
|
||||
buf[1] = SPINOR_REG_MT_CFR1V_DEF;
|
||||
}
|
||||
|
||||
buf[0] = SPINOR_MT_OCT_DTR;
|
||||
op = (struct spi_mem_op)
|
||||
SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_MT_WR_ANY_REG, 1),
|
||||
SPI_MEM_OP_ADDR(enable ? 3 : 4,
|
||||
SPINOR_REG_MT_CFR0V, 1),
|
||||
SPI_MEM_OP_NO_DUMMY,
|
||||
SPI_MEM_OP_DATA_OUT(enable ? 1 : 2, buf, 1));
|
||||
|
||||
if (!enable)
|
||||
spi_nor_spimem_setup_op(nor, &op, SNOR_PROTO_8_8_8_DTR);
|
||||
|
||||
ret = spi_mem_exec_op(nor->spimem, &op);
|
||||
MICRON_ST_NOR_WR_ANY_REG_OP(3, SPINOR_REG_MT_CFR0V, 1, buf);
|
||||
ret = spi_nor_write_any_volatile_reg(nor, &op, nor->reg_proto);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* Read flash ID to make sure the switch was successful. */
|
||||
op = (struct spi_mem_op)
|
||||
SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_RDID, 1),
|
||||
SPI_MEM_OP_NO_ADDR,
|
||||
SPI_MEM_OP_DUMMY(enable ? 8 : 0, 1),
|
||||
SPI_MEM_OP_DATA_IN(round_up(nor->info->id_len, 2),
|
||||
buf, 1));
|
||||
|
||||
if (enable)
|
||||
spi_nor_spimem_setup_op(nor, &op, SNOR_PROTO_8_8_8_DTR);
|
||||
|
||||
ret = spi_mem_exec_op(nor->spimem, &op);
|
||||
if (ret)
|
||||
ret = spi_nor_read_id(nor, 0, 8, buf, SNOR_PROTO_8_8_8_DTR);
|
||||
if (ret) {
|
||||
dev_dbg(nor->dev, "error %d reading JEDEC ID after enabling 8D-8D-8D mode\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (memcmp(buf, nor->info->id, nor->info->id_len))
|
||||
return -EINVAL;
|
||||
|
@ -111,6 +81,47 @@ static int micron_st_nor_octal_dtr_enable(struct spi_nor *nor, bool enable)
|
|||
return 0;
|
||||
}
|
||||
|
||||
static int micron_st_nor_octal_dtr_dis(struct spi_nor *nor)
|
||||
{
|
||||
struct spi_mem_op op;
|
||||
u8 *buf = nor->bouncebuf;
|
||||
int ret;
|
||||
|
||||
/*
|
||||
* The register is 1-byte wide, but 1-byte transactions are not allowed
|
||||
* in 8D-8D-8D mode. The next register is the dummy cycle configuration
|
||||
* register. Since the transaction needs to be at least 2 bytes wide,
|
||||
* set the next register to its default value. This also makes sense
|
||||
* because the value was changed when enabling 8D-8D-8D mode, it should
|
||||
* be reset when disabling.
|
||||
*/
|
||||
buf[0] = SPINOR_MT_EXSPI;
|
||||
buf[1] = SPINOR_REG_MT_CFR1V_DEF;
|
||||
op = (struct spi_mem_op)
|
||||
MICRON_ST_NOR_WR_ANY_REG_OP(4, SPINOR_REG_MT_CFR0V, 2, buf);
|
||||
ret = spi_nor_write_any_volatile_reg(nor, &op, SNOR_PROTO_8_8_8_DTR);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* Read flash ID to make sure the switch was successful. */
|
||||
ret = spi_nor_read_id(nor, 0, 0, buf, SNOR_PROTO_1_1_1);
|
||||
if (ret) {
|
||||
dev_dbg(nor->dev, "error %d reading JEDEC ID after disabling 8D-8D-8D mode\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (memcmp(buf, nor->info->id, nor->info->id_len))
|
||||
return -EINVAL;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int micron_st_nor_octal_dtr_enable(struct spi_nor *nor, bool enable)
|
||||
{
|
||||
return enable ? micron_st_nor_octal_dtr_en(nor) :
|
||||
micron_st_nor_octal_dtr_dis(nor);
|
||||
}
|
||||
|
||||
static void mt35xu512aba_default_init(struct spi_nor *nor)
|
||||
{
|
||||
nor->params->octal_dtr_enable = micron_st_nor_octal_dtr_enable;
|
||||
|
@ -322,11 +333,7 @@ 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));
|
||||
struct spi_mem_op op = MICRON_ST_RDFSR_OP(fsr);
|
||||
|
||||
if (nor->reg_proto == SNOR_PROTO_8_8_8_DTR) {
|
||||
op.addr.nbytes = nor->params->rdsr_addr_nbytes;
|
||||
|
@ -361,11 +368,7 @@ 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);
|
||||
struct spi_mem_op op = MICRON_ST_CLFSR_OP;
|
||||
|
||||
spi_nor_spimem_setup_op(nor, &op, nor->reg_proto);
|
||||
|
||||
|
|
|
@ -23,6 +23,96 @@
|
|||
#define SPINOR_REG_CYPRESS_CFR5V_OCT_DTR_DS 0
|
||||
#define SPINOR_OP_CYPRESS_RD_FAST 0xee
|
||||
|
||||
/* Cypress SPI NOR flash operations. */
|
||||
#define CYPRESS_NOR_WR_ANY_REG_OP(naddr, addr, ndata, buf) \
|
||||
SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_WR_ANY_REG, 0), \
|
||||
SPI_MEM_OP_ADDR(naddr, addr, 0), \
|
||||
SPI_MEM_OP_NO_DUMMY, \
|
||||
SPI_MEM_OP_DATA_OUT(ndata, buf, 0))
|
||||
|
||||
#define CYPRESS_NOR_RD_ANY_REG_OP(naddr, addr, buf) \
|
||||
SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_RD_ANY_REG, 0), \
|
||||
SPI_MEM_OP_ADDR(naddr, addr, 0), \
|
||||
SPI_MEM_OP_NO_DUMMY, \
|
||||
SPI_MEM_OP_DATA_IN(1, buf, 0))
|
||||
|
||||
#define SPANSION_CLSR_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)
|
||||
|
||||
static int cypress_nor_octal_dtr_en(struct spi_nor *nor)
|
||||
{
|
||||
struct spi_mem_op op;
|
||||
u8 *buf = nor->bouncebuf;
|
||||
int ret;
|
||||
|
||||
/* Use 24 dummy cycles for memory array reads. */
|
||||
*buf = SPINOR_REG_CYPRESS_CFR2V_MEMLAT_11_24;
|
||||
op = (struct spi_mem_op)
|
||||
CYPRESS_NOR_WR_ANY_REG_OP(3, SPINOR_REG_CYPRESS_CFR2V, 1, buf);
|
||||
|
||||
ret = spi_nor_write_any_volatile_reg(nor, &op, nor->reg_proto);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
nor->read_dummy = 24;
|
||||
|
||||
/* Set the octal and DTR enable bits. */
|
||||
buf[0] = SPINOR_REG_CYPRESS_CFR5V_OCT_DTR_EN;
|
||||
op = (struct spi_mem_op)
|
||||
CYPRESS_NOR_WR_ANY_REG_OP(3, SPINOR_REG_CYPRESS_CFR5V, 1, buf);
|
||||
|
||||
ret = spi_nor_write_any_volatile_reg(nor, &op, nor->reg_proto);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* Read flash ID to make sure the switch was successful. */
|
||||
ret = spi_nor_read_id(nor, 4, 3, buf, SNOR_PROTO_8_8_8_DTR);
|
||||
if (ret) {
|
||||
dev_dbg(nor->dev, "error %d reading JEDEC ID after enabling 8D-8D-8D mode\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (memcmp(buf, nor->info->id, nor->info->id_len))
|
||||
return -EINVAL;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int cypress_nor_octal_dtr_dis(struct spi_nor *nor)
|
||||
{
|
||||
struct spi_mem_op op;
|
||||
u8 *buf = nor->bouncebuf;
|
||||
int ret;
|
||||
|
||||
/*
|
||||
* The register is 1-byte wide, but 1-byte transactions are not allowed
|
||||
* in 8D-8D-8D mode. Since there is no register at the next location,
|
||||
* just initialize the value to 0 and let the transaction go on.
|
||||
*/
|
||||
buf[0] = SPINOR_REG_CYPRESS_CFR5V_OCT_DTR_DS;
|
||||
buf[1] = 0;
|
||||
op = (struct spi_mem_op)
|
||||
CYPRESS_NOR_WR_ANY_REG_OP(4, SPINOR_REG_CYPRESS_CFR5V, 2, buf);
|
||||
ret = spi_nor_write_any_volatile_reg(nor, &op, SNOR_PROTO_8_8_8_DTR);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* Read flash ID to make sure the switch was successful. */
|
||||
ret = spi_nor_read_id(nor, 0, 0, buf, SNOR_PROTO_1_1_1);
|
||||
if (ret) {
|
||||
dev_dbg(nor->dev, "error %d reading JEDEC ID after disabling 8D-8D-8D mode\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (memcmp(buf, nor->info->id, nor->info->id_len))
|
||||
return -EINVAL;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* cypress_nor_octal_dtr_enable() - Enable octal DTR on Cypress flashes.
|
||||
* @nor: pointer to a 'struct spi_nor'
|
||||
|
@ -35,87 +125,8 @@
|
|||
*/
|
||||
static int cypress_nor_octal_dtr_enable(struct spi_nor *nor, bool enable)
|
||||
{
|
||||
struct spi_mem_op op;
|
||||
u8 *buf = nor->bouncebuf;
|
||||
int ret;
|
||||
|
||||
if (enable) {
|
||||
/* Use 24 dummy cycles for memory array reads. */
|
||||
ret = spi_nor_write_enable(nor);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
*buf = SPINOR_REG_CYPRESS_CFR2V_MEMLAT_11_24;
|
||||
op = (struct spi_mem_op)
|
||||
SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_WR_ANY_REG, 1),
|
||||
SPI_MEM_OP_ADDR(3, SPINOR_REG_CYPRESS_CFR2V,
|
||||
1),
|
||||
SPI_MEM_OP_NO_DUMMY,
|
||||
SPI_MEM_OP_DATA_OUT(1, buf, 1));
|
||||
|
||||
ret = spi_mem_exec_op(nor->spimem, &op);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = spi_nor_wait_till_ready(nor);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
nor->read_dummy = 24;
|
||||
}
|
||||
|
||||
/* Set/unset the octal and DTR enable bits. */
|
||||
ret = spi_nor_write_enable(nor);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (enable) {
|
||||
buf[0] = SPINOR_REG_CYPRESS_CFR5V_OCT_DTR_EN;
|
||||
} else {
|
||||
/*
|
||||
* The register is 1-byte wide, but 1-byte transactions are not
|
||||
* allowed in 8D-8D-8D mode. Since there is no register at the
|
||||
* next location, just initialize the value to 0 and let the
|
||||
* transaction go on.
|
||||
*/
|
||||
buf[0] = SPINOR_REG_CYPRESS_CFR5V_OCT_DTR_DS;
|
||||
buf[1] = 0;
|
||||
}
|
||||
|
||||
op = (struct spi_mem_op)
|
||||
SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_WR_ANY_REG, 1),
|
||||
SPI_MEM_OP_ADDR(enable ? 3 : 4,
|
||||
SPINOR_REG_CYPRESS_CFR5V,
|
||||
1),
|
||||
SPI_MEM_OP_NO_DUMMY,
|
||||
SPI_MEM_OP_DATA_OUT(enable ? 1 : 2, buf, 1));
|
||||
|
||||
if (!enable)
|
||||
spi_nor_spimem_setup_op(nor, &op, SNOR_PROTO_8_8_8_DTR);
|
||||
|
||||
ret = spi_mem_exec_op(nor->spimem, &op);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* Read flash ID to make sure the switch was successful. */
|
||||
op = (struct spi_mem_op)
|
||||
SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_RDID, 1),
|
||||
SPI_MEM_OP_ADDR(enable ? 4 : 0, 0, 1),
|
||||
SPI_MEM_OP_DUMMY(enable ? 3 : 0, 1),
|
||||
SPI_MEM_OP_DATA_IN(round_up(nor->info->id_len, 2),
|
||||
buf, 1));
|
||||
|
||||
if (enable)
|
||||
spi_nor_spimem_setup_op(nor, &op, SNOR_PROTO_8_8_8_DTR);
|
||||
|
||||
ret = spi_mem_exec_op(nor->spimem, &op);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (memcmp(buf, nor->info->id, nor->info->id_len))
|
||||
return -EINVAL;
|
||||
|
||||
return 0;
|
||||
return enable ? cypress_nor_octal_dtr_en(nor) :
|
||||
cypress_nor_octal_dtr_dis(nor);
|
||||
}
|
||||
|
||||
static void s28hs512t_default_init(struct spi_nor *nor)
|
||||
|
@ -162,12 +173,12 @@ static int s28hs512t_post_bfpt_fixup(struct spi_nor *nor,
|
|||
* CFR3V[4] and set the correct size.
|
||||
*/
|
||||
struct spi_mem_op op =
|
||||
SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_RD_ANY_REG, 1),
|
||||
SPI_MEM_OP_ADDR(3, SPINOR_REG_CYPRESS_CFR3V, 1),
|
||||
SPI_MEM_OP_NO_DUMMY,
|
||||
SPI_MEM_OP_DATA_IN(1, nor->bouncebuf, 1));
|
||||
CYPRESS_NOR_RD_ANY_REG_OP(3, SPINOR_REG_CYPRESS_CFR3V,
|
||||
nor->bouncebuf);
|
||||
int ret;
|
||||
|
||||
spi_nor_spimem_setup_op(nor, &op, nor->reg_proto);
|
||||
|
||||
ret = spi_mem_exec_op(nor->spimem, &op);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
@ -317,11 +328,7 @@ static void spansion_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);
|
||||
struct spi_mem_op op = SPANSION_CLSR_OP;
|
||||
|
||||
spi_nor_spimem_setup_op(nor, &op, nor->reg_proto);
|
||||
|
||||
|
|
|
@ -8,6 +8,15 @@
|
|||
|
||||
#include "core.h"
|
||||
|
||||
#define WINBOND_NOR_OP_RDEAR 0xc8 /* Read Extended Address Register */
|
||||
#define WINBOND_NOR_OP_WREAR 0xc5 /* Write Extended Address Register */
|
||||
|
||||
#define WINBOND_NOR_WREAR_OP(buf) \
|
||||
SPI_MEM_OP(SPI_MEM_OP_CMD(WINBOND_NOR_OP_WREAR, 0), \
|
||||
SPI_MEM_OP_NO_ADDR, \
|
||||
SPI_MEM_OP_NO_DUMMY, \
|
||||
SPI_MEM_OP_DATA_OUT(1, buf, 0))
|
||||
|
||||
static int
|
||||
w25q256_post_bfpt_fixups(struct spi_nor *nor,
|
||||
const struct sfdp_parameter_header *bfpt_header,
|
||||
|
@ -124,11 +133,45 @@ static const struct flash_info winbond_nor_parts[] = {
|
|||
{ "w25m512jv", INFO(0xef7119, 0, 64 * 1024, 1024)
|
||||
NO_SFDP_FLAGS(SECT_4K | SPI_NOR_QUAD_READ |
|
||||
SPI_NOR_DUAL_READ) },
|
||||
{ "w25q512nwm", INFO(0xef8020, 0, 64 * 1024, 1024)
|
||||
PARSE_SFDP
|
||||
OTP_INFO(256, 3, 0x1000, 0x1000) },
|
||||
{ "w25q512jvq", INFO(0xef4020, 0, 64 * 1024, 1024)
|
||||
NO_SFDP_FLAGS(SECT_4K | SPI_NOR_DUAL_READ |
|
||||
SPI_NOR_QUAD_READ) },
|
||||
};
|
||||
|
||||
/**
|
||||
* winbond_nor_write_ear() - Write Extended Address Register.
|
||||
* @nor: pointer to 'struct spi_nor'.
|
||||
* @ear: value to write to the Extended Address Register.
|
||||
*
|
||||
* Return: 0 on success, -errno otherwise.
|
||||
*/
|
||||
static int winbond_nor_write_ear(struct spi_nor *nor, u8 ear)
|
||||
{
|
||||
int ret;
|
||||
|
||||
nor->bouncebuf[0] = ear;
|
||||
|
||||
if (nor->spimem) {
|
||||
struct spi_mem_op op = WINBOND_NOR_WREAR_OP(nor->bouncebuf);
|
||||
|
||||
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,
|
||||
WINBOND_NOR_OP_WREAR,
|
||||
nor->bouncebuf, 1);
|
||||
}
|
||||
|
||||
if (ret)
|
||||
dev_dbg(nor->dev, "error %d writing EAR\n", ret);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* winbond_nor_set_4byte_addr_mode() - Set 4-byte address mode for Winbond
|
||||
* flashes.
|
||||
|
@ -155,7 +198,7 @@ static int winbond_nor_set_4byte_addr_mode(struct spi_nor *nor, bool enable)
|
|||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = spi_nor_write_ear(nor, 0);
|
||||
ret = winbond_nor_write_ear(nor, 0);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
|
|
|
@ -15,6 +15,12 @@
|
|||
#define XSR_PAGESIZE BIT(0) /* Page size in Po2 or Linear */
|
||||
#define XSR_RDY BIT(7) /* Ready */
|
||||
|
||||
#define XILINX_RDSR_OP(buf) \
|
||||
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, buf, 0))
|
||||
|
||||
#define S3AN_INFO(_jedec_id, _n_sectors, _page_size) \
|
||||
.id = { \
|
||||
((_jedec_id) >> 16) & 0xff, \
|
||||
|
@ -72,11 +78,7 @@ 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));
|
||||
struct spi_mem_op op = XILINX_RDSR_OP(sr);
|
||||
|
||||
spi_nor_spimem_setup_op(nor, &op, nor->reg_proto);
|
||||
|
||||
|
|
|
@ -509,6 +509,7 @@ EXPORT_SYMBOL_GPL(of_platform_default_populate);
|
|||
|
||||
#ifndef CONFIG_PPC
|
||||
static const struct of_device_id reserved_mem_matches[] = {
|
||||
{ .compatible = "phram" },
|
||||
{ .compatible = "qcom,rmtfs-mem" },
|
||||
{ .compatible = "qcom,cmd-db" },
|
||||
{ .compatible = "qcom,smem" },
|
||||
|
|
|
@ -286,6 +286,7 @@ struct cfi_private {
|
|||
map_word sector_erase_cmd;
|
||||
unsigned long chipshift; /* Because they're of the same type */
|
||||
const char *im_name; /* inter_module name for cmdset_setup */
|
||||
unsigned long quirks;
|
||||
struct flchip chips[]; /* per-chip data structure for each chip */
|
||||
};
|
||||
|
||||
|
|
|
@ -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_RDEAR 0xc8 /* Read Extended Address Register */
|
||||
#define SPINOR_OP_WREAR 0xc5 /* Write Extended Address Register */
|
||||
#define SPINOR_OP_SRSTEN 0x66 /* Software Reset Enable */
|
||||
#define SPINOR_OP_SRST 0x99 /* Software Reset */
|
||||
#define SPINOR_OP_GBULK 0x98 /* Global Block Unlock */
|
||||
|
@ -365,6 +363,7 @@ struct spi_nor_flash_parameter;
|
|||
* @write_proto: the SPI protocol for write operations
|
||||
* @reg_proto: the SPI protocol for read_reg/write_reg/erase operations
|
||||
* @sfdp: the SFDP data of the flash
|
||||
* @debugfs_root: pointer to the debugfs directory
|
||||
* @controller_ops: SPI NOR controller driver specific operations.
|
||||
* @params: [FLASH-SPECIFIC] SPI NOR flash parameters and settings.
|
||||
* The structure includes legacy flash parameters and
|
||||
|
@ -394,6 +393,7 @@ struct spi_nor {
|
|||
u32 flags;
|
||||
enum spi_nor_cmd_ext cmd_ext_type;
|
||||
struct sfdp *sfdp;
|
||||
struct dentry *debugfs_root;
|
||||
|
||||
const struct spi_nor_controller_ops *controller_ops;
|
||||
|
||||
|
|
|
@ -266,6 +266,7 @@ extern const struct spinand_manufacturer micron_spinand_manufacturer;
|
|||
extern const struct spinand_manufacturer paragon_spinand_manufacturer;
|
||||
extern const struct spinand_manufacturer toshiba_spinand_manufacturer;
|
||||
extern const struct spinand_manufacturer winbond_spinand_manufacturer;
|
||||
extern const struct spinand_manufacturer xtx_spinand_manufacturer;
|
||||
|
||||
/**
|
||||
* struct spinand_op_variants - SPI NAND operation variants
|
||||
|
|
Loading…
Reference in a new issue