mirror of
https://github.com/torvalds/linux.git
synced 2024-11-23 12:42:02 +00:00
MTD merge for 3.7
- Disable broken mtdchar mmap() on MMU systems - Additional ECC tests for NAND flash, and some test cleanups - New NAND and SPI chip support - Fixes/cleanup for SH FLCTL NAND controller driver - Improved hardware support for GPMI NAND controller - Conversions to device-tree support for various drivers - Removal of obsolete drivers (sbc8xxx, bcmring, etc.) - New LPC32xx drivers for MLC and SLC NAND - Further cleanup of NAND OOB/ECC handling - UAPI cleanup merge from David Howells (just moving files, since MTD headers were sorted out long ago to separate user-visible from kernel bits) -----BEGIN PGP SIGNATURE----- Version: GnuPG v1.4.12 (GNU/Linux) iEYEABECAAYFAlB0OosACgkQdwG7hYl686OSPACeKLrlHmyG8KXgAqcGZwAj1RM+ X9YAoI2Kd6Sz8v6sLbJidnxUBr/oJVa8 =/kFV -----END PGP SIGNATURE----- Merge tag 'for-linus-20121009' of git://git.infradead.org/mtd-2.6 Pull MTD updates from David Woodhouse: - Disable broken mtdchar mmap() on MMU systems - Additional ECC tests for NAND flash, and some test cleanups - New NAND and SPI chip support - Fixes/cleanup for SH FLCTL NAND controller driver - Improved hardware support for GPMI NAND controller - Conversions to device-tree support for various drivers - Removal of obsolete drivers (sbc8xxx, bcmring, etc.) - New LPC32xx drivers for MLC and SLC NAND - Further cleanup of NAND OOB/ECC handling - UAPI cleanup merge from David Howells (just moving files, since MTD headers were sorted out long ago to separate user-visible from kernel bits) * tag 'for-linus-20121009' of git://git.infradead.org/mtd-2.6: (168 commits) mtd: Disable mtdchar mmap on MMU systems UAPI: (Scripted) Disintegrate include/mtd mtd: nand: detect Samsung K9GBG08U0A, K9GAG08U0F ID mtd: nand: decode Hynix MLC, 6-byte ID length mtd: nand: increase max OOB size to 640 mtd: nand: add generic READ ID length calculation functions mtd: nand: split simple ID decode into its own function mtd: nand: split extended ID decoding into its own function mtd: nand: split BB marker options decoding into its own function mtd: nand: remove redundant ID read mtd: nand: remove unnecessary variable mtd: docg4: add missing HAS_IOMEM dependency mtd: gpmi: initialize the timing registers only one time mtd: gpmi: add EDO feature for imx6q mtd: gpmi: do not set the default values for the extra clocks mtd: gpmi: simplify the DLL setting code mtd: gpmi: add a new field for HW_GPMI_CTRL1 mtd: gpmi: do not get the clock frequency in gpmi_begin() mtd: gpmi: add a new field for HW_GPMI_TIMING1 mtd: add helpers to get the supportted ONFI timing mode ...
This commit is contained in:
commit
10f39f04b2
@ -1216,8 +1216,6 @@ in this page</entry>
|
||||
#define NAND_BBT_LASTBLOCK 0x00000010
|
||||
/* The bbt is at the given page, else we must scan for the bbt */
|
||||
#define NAND_BBT_ABSPAGE 0x00000020
|
||||
/* The bbt is at the given page, else we must scan for the bbt */
|
||||
#define NAND_BBT_SEARCH 0x00000040
|
||||
/* bbt is stored per chip on multichip devices */
|
||||
#define NAND_BBT_PERCHIP 0x00000080
|
||||
/* bbt has a version counter at offset veroffs */
|
||||
|
51
Documentation/devicetree/bindings/arm/davinci/nand.txt
Normal file
51
Documentation/devicetree/bindings/arm/davinci/nand.txt
Normal file
@ -0,0 +1,51 @@
|
||||
* Texas Instruments Davinci NAND
|
||||
|
||||
This file provides information, what the device node for the
|
||||
davinci nand interface contain.
|
||||
|
||||
Required properties:
|
||||
- compatible: "ti,davinci-nand";
|
||||
- reg : contain 2 offset/length values:
|
||||
- offset and length for the access window
|
||||
- offset and length for accessing the aemif control registers
|
||||
- ti,davinci-chipselect: Indicates on the davinci_nand driver which
|
||||
chipselect is used for accessing the nand.
|
||||
|
||||
Recommended properties :
|
||||
- ti,davinci-mask-ale: mask for ale
|
||||
- ti,davinci-mask-cle: mask for cle
|
||||
- ti,davinci-mask-chipsel: mask for chipselect
|
||||
- ti,davinci-ecc-mode: ECC mode valid values for davinci driver:
|
||||
- "none"
|
||||
- "soft"
|
||||
- "hw"
|
||||
- ti,davinci-ecc-bits: used ECC bits, currently supported 1 or 4.
|
||||
- ti,davinci-nand-buswidth: buswidth 8 or 16
|
||||
- ti,davinci-nand-use-bbt: use flash based bad block table support.
|
||||
|
||||
Example (enbw_cmc board):
|
||||
aemif@60000000 {
|
||||
compatible = "ti,davinci-aemif";
|
||||
#address-cells = <2>;
|
||||
#size-cells = <1>;
|
||||
reg = <0x68000000 0x80000>;
|
||||
ranges = <2 0 0x60000000 0x02000000
|
||||
3 0 0x62000000 0x02000000
|
||||
4 0 0x64000000 0x02000000
|
||||
5 0 0x66000000 0x02000000
|
||||
6 0 0x68000000 0x02000000>;
|
||||
nand@3,0 {
|
||||
compatible = "ti,davinci-nand";
|
||||
reg = <3 0x0 0x807ff
|
||||
6 0x0 0x8000>;
|
||||
#address-cells = <1>;
|
||||
#size-cells = <1>;
|
||||
ti,davinci-chipselect = <1>;
|
||||
ti,davinci-mask-ale = <0>;
|
||||
ti,davinci-mask-cle = <0>;
|
||||
ti,davinci-mask-chipsel = <0>;
|
||||
ti,davinci-ecc-mode = "hw";
|
||||
ti,davinci-ecc-bits = <4>;
|
||||
ti,davinci-nand-use-bbt;
|
||||
};
|
||||
};
|
@ -3,7 +3,9 @@ Atmel NAND flash
|
||||
Required properties:
|
||||
- compatible : "atmel,at91rm9200-nand".
|
||||
- reg : should specify localbus address and size used for the chip,
|
||||
and if availlable the ECC.
|
||||
and hardware ECC controller if available.
|
||||
If the hardware ECC is PMECC, it should contain address and size for
|
||||
PMECC, PMECC Error Location controller and ROM which has lookup tables.
|
||||
- atmel,nand-addr-offset : offset for the address latch.
|
||||
- atmel,nand-cmd-offset : offset for the command latch.
|
||||
- #address-cells, #size-cells : Must be present if the device has sub-nodes
|
||||
@ -16,6 +18,15 @@ Optional properties:
|
||||
- nand-ecc-mode : String, operation mode of the NAND ecc mode, soft by default.
|
||||
Supported values are: "none", "soft", "hw", "hw_syndrome", "hw_oob_first",
|
||||
"soft_bch".
|
||||
- atmel,has-pmecc : boolean to enable Programmable Multibit ECC hardware.
|
||||
Only supported by at91sam9x5 or later sam9 product.
|
||||
- atmel,pmecc-cap : error correct capability for Programmable Multibit ECC
|
||||
Controller. Supported values are: 2, 4, 8, 12, 24.
|
||||
- atmel,pmecc-sector-size : sector size for ECC computation. Supported values
|
||||
are: 512, 1024.
|
||||
- atmel,pmecc-lookup-table-offset : includes two offsets of lookup table in ROM
|
||||
for different sector size. First one is for sector size 512, the next is for
|
||||
sector size 1024.
|
||||
- nand-bus-width : 8 or 16 bus width if not present 8
|
||||
- nand-on-flash-bbt: boolean to enable on flash bbt option if not present false
|
||||
|
||||
@ -39,3 +50,30 @@ nand0: nand@40000000,0 {
|
||||
...
|
||||
};
|
||||
};
|
||||
|
||||
/* for PMECC supported chips */
|
||||
nand0: nand@40000000 {
|
||||
compatible = "atmel,at91rm9200-nand";
|
||||
#address-cells = <1>;
|
||||
#size-cells = <1>;
|
||||
reg = < 0x40000000 0x10000000 /* bus addr & size */
|
||||
0xffffe000 0x00000600 /* PMECC addr & size */
|
||||
0xffffe600 0x00000200 /* PMECC ERRLOC addr & size */
|
||||
0x00100000 0x00100000 /* ROM addr & size */
|
||||
>;
|
||||
atmel,nand-addr-offset = <21>; /* ale */
|
||||
atmel,nand-cmd-offset = <22>; /* cle */
|
||||
nand-on-flash-bbt;
|
||||
nand-ecc-mode = "hw";
|
||||
atmel,has-pmecc; /* enable PMECC */
|
||||
atmel,pmecc-cap = <2>;
|
||||
atmel,pmecc-sector-size = <512>;
|
||||
atmel,pmecc-lookup-table-offset = <0x8000 0x10000>;
|
||||
gpios = <&pioD 5 0 /* rdy */
|
||||
&pioD 4 0 /* nce */
|
||||
0 /* cd */
|
||||
>;
|
||||
partition@0 {
|
||||
...
|
||||
};
|
||||
};
|
||||
|
@ -12,6 +12,10 @@ Required properties:
|
||||
- interrupt-names : The interrupt names "gpmi-dma", "bch";
|
||||
- fsl,gpmi-dma-channel : Should contain the dma channel it uses.
|
||||
|
||||
Optional properties:
|
||||
- nand-on-flash-bbt: boolean to enable on flash bbt option if not
|
||||
present false
|
||||
|
||||
The device tree may optionally contain sub-nodes describing partitions of the
|
||||
address space. See partition.txt for more detail.
|
||||
|
||||
|
50
Documentation/devicetree/bindings/mtd/lpc32xx-mlc.txt
Normal file
50
Documentation/devicetree/bindings/mtd/lpc32xx-mlc.txt
Normal file
@ -0,0 +1,50 @@
|
||||
NXP LPC32xx SoC NAND MLC controller
|
||||
|
||||
Required properties:
|
||||
- compatible: "nxp,lpc3220-mlc"
|
||||
- reg: Address and size of the controller
|
||||
- interrupts: The NAND interrupt specification
|
||||
- gpios: GPIO specification for NAND write protect
|
||||
|
||||
The following required properties are very controller specific. See the LPC32xx
|
||||
User Manual 7.5.14 MLC NAND Timing Register (the values here are specified in
|
||||
Hz, to make them independent of actual clock speed and to provide for good
|
||||
accuracy:)
|
||||
- nxp,tcea_delay: TCEA_DELAY
|
||||
- nxp,busy_delay: BUSY_DELAY
|
||||
- nxp,nand_ta: NAND_TA
|
||||
- nxp,rd_high: RD_HIGH
|
||||
- nxp,rd_low: RD_LOW
|
||||
- nxp,wr_high: WR_HIGH
|
||||
- nxp,wr_low: WR_LOW
|
||||
|
||||
Optional subnodes:
|
||||
- Partitions, see Documentation/devicetree/bindings/mtd/partition.txt
|
||||
|
||||
Example:
|
||||
|
||||
mlc: flash@200A8000 {
|
||||
compatible = "nxp,lpc3220-mlc";
|
||||
reg = <0x200A8000 0x11000>;
|
||||
interrupts = <11 0>;
|
||||
#address-cells = <1>;
|
||||
#size-cells = <1>;
|
||||
|
||||
nxp,tcea-delay = <333333333>;
|
||||
nxp,busy-delay = <10000000>;
|
||||
nxp,nand-ta = <18181818>;
|
||||
nxp,rd-high = <31250000>;
|
||||
nxp,rd-low = <45454545>;
|
||||
nxp,wr-high = <40000000>;
|
||||
nxp,wr-low = <83333333>;
|
||||
gpios = <&gpio 5 19 1>; /* GPO_P3 19, active low */
|
||||
|
||||
mtd0@00000000 {
|
||||
label = "boot";
|
||||
reg = <0x00000000 0x00064000>;
|
||||
read-only;
|
||||
};
|
||||
|
||||
...
|
||||
|
||||
};
|
52
Documentation/devicetree/bindings/mtd/lpc32xx-slc.txt
Normal file
52
Documentation/devicetree/bindings/mtd/lpc32xx-slc.txt
Normal file
@ -0,0 +1,52 @@
|
||||
NXP LPC32xx SoC NAND SLC controller
|
||||
|
||||
Required properties:
|
||||
- compatible: "nxp,lpc3220-slc"
|
||||
- reg: Address and size of the controller
|
||||
- nand-on-flash-bbt: Use bad block table on flash
|
||||
- gpios: GPIO specification for NAND write protect
|
||||
|
||||
The following required properties are very controller specific. See the LPC32xx
|
||||
User Manual:
|
||||
- nxp,wdr-clks: Delay before Ready signal is tested on write (W_RDY)
|
||||
- nxp,rdr-clks: Delay before Ready signal is tested on read (R_RDY)
|
||||
(The following values are specified in Hz, to make them independent of actual
|
||||
clock speed:)
|
||||
- nxp,wwidth: Write pulse width (W_WIDTH)
|
||||
- nxp,whold: Write hold time (W_HOLD)
|
||||
- nxp,wsetup: Write setup time (W_SETUP)
|
||||
- nxp,rwidth: Read pulse width (R_WIDTH)
|
||||
- nxp,rhold: Read hold time (R_HOLD)
|
||||
- nxp,rsetup: Read setup time (R_SETUP)
|
||||
|
||||
Optional subnodes:
|
||||
- Partitions, see Documentation/devicetree/bindings/mtd/partition.txt
|
||||
|
||||
Example:
|
||||
|
||||
slc: flash@20020000 {
|
||||
compatible = "nxp,lpc3220-slc";
|
||||
reg = <0x20020000 0x1000>;
|
||||
#address-cells = <1>;
|
||||
#size-cells = <1>;
|
||||
|
||||
nxp,wdr-clks = <14>;
|
||||
nxp,wwidth = <40000000>;
|
||||
nxp,whold = <100000000>;
|
||||
nxp,wsetup = <100000000>;
|
||||
nxp,rdr-clks = <14>;
|
||||
nxp,rwidth = <40000000>;
|
||||
nxp,rhold = <66666666>;
|
||||
nxp,rsetup = <100000000>;
|
||||
nand-on-flash-bbt;
|
||||
gpios = <&gpio 5 19 1>; /* GPO_P3 19, active low */
|
||||
|
||||
mtd0@00000000 {
|
||||
label = "phy3250-boot";
|
||||
reg = <0x00000000 0x00064000>;
|
||||
read-only;
|
||||
};
|
||||
|
||||
...
|
||||
|
||||
};
|
@ -16,6 +16,13 @@ file systems on embedded devices.
|
||||
- #address-cells, #size-cells : Must be present if the device has
|
||||
sub-nodes representing partitions (see below). In this case
|
||||
both #address-cells and #size-cells must be equal to 1.
|
||||
- no-unaligned-direct-access: boolean to disable the default direct
|
||||
mapping of the flash.
|
||||
On some platforms (e.g. MPC5200) a direct 1:1 mapping may cause
|
||||
problems with JFFS2 usage, as the local bus (LPB) doesn't support
|
||||
unaligned accesses as implemented in the JFFS2 code via memcpy().
|
||||
By defining "no-unaligned-direct-access", the flash will not be
|
||||
exposed directly to the MTD users (e.g. JFFS2) any more.
|
||||
|
||||
For JEDEC compatible devices, the following additional properties
|
||||
are defined:
|
||||
|
@ -407,6 +407,13 @@
|
||||
status = "disabled";
|
||||
};
|
||||
|
||||
nand@83fdb000 {
|
||||
compatible = "fsl,imx51-nand";
|
||||
reg = <0x83fdb000 0x1000 0xcfff0000 0x10000>;
|
||||
interrupts = <8>;
|
||||
status = "disabled";
|
||||
};
|
||||
|
||||
ssi3: ssi@83fe8000 {
|
||||
compatible = "fsl,imx51-ssi", "fsl,imx21-ssi";
|
||||
reg = <0x83fe8000 0x4000>;
|
||||
|
@ -518,6 +518,13 @@
|
||||
status = "disabled";
|
||||
};
|
||||
|
||||
nand@63fdb000 {
|
||||
compatible = "fsl,imx53-nand";
|
||||
reg = <0x63fdb000 0x1000 0xf7ff0000 0x10000>;
|
||||
interrupts = <8>;
|
||||
status = "disabled";
|
||||
};
|
||||
|
||||
ssi3: ssi@63fe8000 {
|
||||
compatible = "fsl,imx53-ssi", "fsl,imx21-ssi";
|
||||
reg = <0x63fe8000 0x4000>;
|
||||
|
@ -49,7 +49,6 @@ CONFIG_MTD_COMPLEX_MAPPINGS=y
|
||||
CONFIG_MTD_PLATRAM=m
|
||||
CONFIG_MTD_DATAFLASH=y
|
||||
CONFIG_MTD_NAND=y
|
||||
CONFIG_MTD_NAND_VERIFY_WRITE=y
|
||||
CONFIG_MTD_NAND_ATMEL=y
|
||||
CONFIG_BLK_DEV_LOOP=y
|
||||
CONFIG_BLK_DEV_RAM=y
|
||||
|
@ -97,7 +97,6 @@ CONFIG_MTD_BLOCK=y
|
||||
CONFIG_MTD_ROM=y
|
||||
CONFIG_MTD_COMPLEX_MAPPINGS=y
|
||||
CONFIG_MTD_NAND=y
|
||||
CONFIG_MTD_NAND_VERIFY_WRITE=y
|
||||
CONFIG_MTD_NAND_SHARPSL=y
|
||||
CONFIG_BLK_DEV_LOOP=y
|
||||
CONFIG_IDE=y
|
||||
|
@ -61,7 +61,6 @@ CONFIG_MTD_CFI_STAA=y
|
||||
CONFIG_MTD_ROM=y
|
||||
CONFIG_MTD_PHYSMAP=y
|
||||
CONFIG_MTD_NAND=y
|
||||
CONFIG_MTD_NAND_VERIFY_WRITE=y
|
||||
CONFIG_BLK_DEV_NBD=y
|
||||
CONFIG_EEPROM_LEGACY=y
|
||||
CONFIG_SCSI=y
|
||||
|
@ -102,7 +102,6 @@ CONFIG_MTD_CFI_STAA=y
|
||||
CONFIG_MTD_RAM=y
|
||||
CONFIG_MTD_ROM=y
|
||||
CONFIG_MTD_NAND=y
|
||||
CONFIG_MTD_NAND_VERIFY_WRITE=y
|
||||
CONFIG_MTD_NAND_S3C2410=y
|
||||
CONFIG_MTD_NAND_PLATFORM=y
|
||||
CONFIG_MTD_LPDDR=y
|
||||
|
@ -49,7 +49,6 @@ CONFIG_MTD_CFI_INTELEXT=y
|
||||
CONFIG_MTD_CFI_AMDSTD=y
|
||||
CONFIG_MTD_PHYSMAP=y
|
||||
CONFIG_MTD_NAND=y
|
||||
CONFIG_MTD_NAND_VERIFY_WRITE=y
|
||||
CONFIG_MTD_NAND_ORION=y
|
||||
CONFIG_BLK_DEV_LOOP=y
|
||||
# CONFIG_SCSI_PROC_FS is not set
|
||||
|
@ -57,7 +57,6 @@ CONFIG_MTD_CHAR=y
|
||||
CONFIG_MTD_BLOCK=y
|
||||
CONFIG_MTD_NAND=y
|
||||
CONFIG_MTD_NAND_ECC_SMC=y
|
||||
CONFIG_MTD_NAND_VERIFY_WRITE=y
|
||||
CONFIG_MTD_NAND_NOMADIK=y
|
||||
CONFIG_MTD_ONENAND=y
|
||||
CONFIG_MTD_ONENAND_VERIFY_WRITE=y
|
||||
|
@ -72,7 +72,6 @@ CONFIG_MTD_CFI_INTELEXT=y
|
||||
CONFIG_MTD_CFI_AMDSTD=y
|
||||
CONFIG_MTD_PHYSMAP=y
|
||||
CONFIG_MTD_NAND=y
|
||||
CONFIG_MTD_NAND_VERIFY_WRITE=y
|
||||
CONFIG_MTD_NAND_PLATFORM=y
|
||||
CONFIG_MTD_NAND_ORION=y
|
||||
CONFIG_BLK_DEV_LOOP=y
|
||||
|
@ -36,7 +36,6 @@ CONFIG_MTD_CONCAT=y
|
||||
CONFIG_MTD_CHAR=y
|
||||
CONFIG_MTD_BLOCK=y
|
||||
CONFIG_MTD_NAND=y
|
||||
CONFIG_MTD_NAND_VERIFY_WRITE=y
|
||||
CONFIG_MTD_NAND_PXA3xx=y
|
||||
CONFIG_MTD_NAND_PXA3xx_BUILTIN=y
|
||||
CONFIG_MTD_ONENAND=y
|
||||
|
@ -94,7 +94,6 @@ CONFIG_MTD_BLOCK=y
|
||||
CONFIG_MTD_ROM=y
|
||||
CONFIG_MTD_COMPLEX_MAPPINGS=y
|
||||
CONFIG_MTD_NAND=y
|
||||
CONFIG_MTD_NAND_VERIFY_WRITE=y
|
||||
CONFIG_MTD_NAND_SHARPSL=y
|
||||
CONFIG_BLK_DEV_LOOP=y
|
||||
CONFIG_IDE=y
|
||||
|
@ -23,6 +23,8 @@
|
||||
#include <linux/string.h>
|
||||
#include <linux/mm.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/ioport.h>
|
||||
#include <linux/platform_device.h>
|
||||
|
||||
#include <mach/hardware.h>
|
||||
#include <asm/sizes.h>
|
||||
@ -62,9 +64,26 @@ void __init autcpu12_map_io(void)
|
||||
iotable_init(autcpu12_io_desc, ARRAY_SIZE(autcpu12_io_desc));
|
||||
}
|
||||
|
||||
static struct resource autcpu12_nvram_resource[] __initdata = {
|
||||
DEFINE_RES_MEM_NAMED(AUTCPU12_PHYS_NVRAM, SZ_128K, "SRAM"),
|
||||
};
|
||||
|
||||
static struct platform_device autcpu12_nvram_pdev __initdata = {
|
||||
.name = "autcpu12_nvram",
|
||||
.id = -1,
|
||||
.resource = autcpu12_nvram_resource,
|
||||
.num_resources = ARRAY_SIZE(autcpu12_nvram_resource),
|
||||
};
|
||||
|
||||
static void __init autcpu12_init(void)
|
||||
{
|
||||
platform_device_register(&autcpu12_nvram_pdev);
|
||||
}
|
||||
|
||||
MACHINE_START(AUTCPU12, "autronix autcpu12")
|
||||
/* Maintainer: Thomas Gleixner */
|
||||
.atag_offset = 0x20000,
|
||||
.init_machine = autcpu12_init,
|
||||
.map_io = autcpu12_map_io,
|
||||
.init_irq = clps711x_init_irq,
|
||||
.timer = &clps711x_timer,
|
||||
|
@ -369,6 +369,7 @@ int __init mx51_clocks_init(unsigned long rate_ckil, unsigned long rate_osc,
|
||||
clk_register_clkdev(clk[ssi1_ipg_gate], NULL, "83fcc000.ssi");
|
||||
clk_register_clkdev(clk[ssi2_ipg_gate], NULL, "70014000.ssi");
|
||||
clk_register_clkdev(clk[ssi3_ipg_gate], NULL, "83fe8000.ssi");
|
||||
clk_register_clkdev(clk[nfc_gate], NULL, "83fdb000.nand");
|
||||
|
||||
/* set the usboh3 parent to pll2_sw */
|
||||
clk_set_parent(clk[usboh3_sel], clk[pll2_sw]);
|
||||
@ -461,6 +462,7 @@ int __init mx53_clocks_init(unsigned long rate_ckil, unsigned long rate_osc,
|
||||
clk_register_clkdev(clk[ssi1_ipg_gate], NULL, "63fcc000.ssi");
|
||||
clk_register_clkdev(clk[ssi2_ipg_gate], NULL, "50014000.ssi");
|
||||
clk_register_clkdev(clk[ssi3_ipg_gate], NULL, "63fd0000.ssi");
|
||||
clk_register_clkdev(clk[nfc_gate], NULL, "63fdb000.nand");
|
||||
clk_register_clkdev(clk[can1_ipg_gate], "ipg", "53fc8000.can");
|
||||
clk_register_clkdev(clk[can1_serial_gate], "per", "53fc8000.can");
|
||||
clk_register_clkdev(clk[can2_ipg_gate], "ipg", "53fcc000.can");
|
||||
|
@ -63,10 +63,6 @@ struct platform_device *__init imx_add_mxc_nand(
|
||||
/* AXI has to come first, that's how the mxc_nand driver expect it */
|
||||
struct resource res[] = {
|
||||
{
|
||||
.start = data->axibase,
|
||||
.end = data->axibase + SZ_16K - 1,
|
||||
.flags = IORESOURCE_MEM,
|
||||
}, {
|
||||
.start = data->iobase,
|
||||
.end = data->iobase + data->iosize - 1,
|
||||
.flags = IORESOURCE_MEM,
|
||||
@ -74,10 +70,13 @@ struct platform_device *__init imx_add_mxc_nand(
|
||||
.start = data->irq,
|
||||
.end = data->irq,
|
||||
.flags = IORESOURCE_IRQ,
|
||||
}, {
|
||||
.start = data->axibase,
|
||||
.end = data->axibase + SZ_16K - 1,
|
||||
.flags = IORESOURCE_MEM,
|
||||
},
|
||||
};
|
||||
return imx_add_platform_device("mxc_nand", data->id,
|
||||
res + !data->axibase,
|
||||
ARRAY_SIZE(res) - !data->axibase,
|
||||
res, ARRAY_SIZE(res) - !data->axibase,
|
||||
pdata, sizeof(*pdata));
|
||||
}
|
||||
|
@ -57,7 +57,6 @@ CONFIG_MTD_PLATRAM=y
|
||||
CONFIG_MTD_PHRAM=y
|
||||
CONFIG_MTD_BLOCK2MTD=y
|
||||
CONFIG_MTD_NAND=y
|
||||
CONFIG_MTD_NAND_VERIFY_WRITE=y
|
||||
CONFIG_MTD_NAND_PLATFORM=y
|
||||
CONFIG_BLK_DEV_LOOP=y
|
||||
CONFIG_BLK_DEV_RAM=y
|
||||
|
@ -119,7 +119,6 @@ CONFIG_MTD_CHAR=y
|
||||
CONFIG_MTD_BLOCK=y
|
||||
CONFIG_MTD_BLOCK2MTD=y
|
||||
CONFIG_MTD_NAND=y
|
||||
CONFIG_MTD_NAND_VERIFY_WRITE=y
|
||||
CONFIG_MTD_NAND_PLATFORM=y
|
||||
CONFIG_ATA=y
|
||||
# CONFIG_ATA_VERBOSE_ERROR is not set
|
||||
|
@ -38,7 +38,6 @@ CONFIG_MTD_CFI=y
|
||||
CONFIG_MTD_CFI_AMDSTD=y
|
||||
CONFIG_MTD_PHYSMAP_OF=y
|
||||
CONFIG_MTD_NAND=y
|
||||
CONFIG_MTD_NAND_VERIFY_WRITE=y
|
||||
CONFIG_MTD_NAND_FSL_ELBC=y
|
||||
CONFIG_PROC_DEVICETREE=y
|
||||
CONFIG_BLK_DEV_LOOP=y
|
||||
|
@ -37,7 +37,6 @@ CONFIG_MTD_CFI=y
|
||||
CONFIG_MTD_CFI_AMDSTD=y
|
||||
CONFIG_MTD_PHYSMAP_OF=y
|
||||
CONFIG_MTD_NAND=y
|
||||
CONFIG_MTD_NAND_VERIFY_WRITE=y
|
||||
CONFIG_PROC_DEVICETREE=y
|
||||
CONFIG_BLK_DEV_LOOP=y
|
||||
CONFIG_BLK_DEV_RAM=y
|
||||
|
@ -50,7 +50,6 @@ CONFIG_MTD_CFI=y
|
||||
CONFIG_MTD_CFI_AMDSTD=y
|
||||
CONFIG_MTD_PHYSMAP_OF=y
|
||||
CONFIG_MTD_NAND=y
|
||||
CONFIG_MTD_NAND_VERIFY_WRITE=y
|
||||
CONFIG_MTD_NAND_FSL_ELBC=y
|
||||
CONFIG_PROC_DEVICETREE=y
|
||||
CONFIG_BLK_DEV_LOOP=y
|
||||
|
@ -148,6 +148,13 @@ config MTD_BCM63XX_PARTS
|
||||
This provides partions parsing for BCM63xx devices with CFE
|
||||
bootloaders.
|
||||
|
||||
config MTD_BCM47XX_PARTS
|
||||
tristate "BCM47XX partitioning support"
|
||||
depends on BCM47XX
|
||||
help
|
||||
This provides partitions parser for devices based on BCM47xx
|
||||
boards.
|
||||
|
||||
comment "User Modules And Translation Layers"
|
||||
|
||||
config MTD_CHAR
|
||||
|
@ -12,6 +12,7 @@ obj-$(CONFIG_MTD_CMDLINE_PARTS) += cmdlinepart.o
|
||||
obj-$(CONFIG_MTD_AFS_PARTS) += afs.o
|
||||
obj-$(CONFIG_MTD_AR7_PARTS) += ar7part.o
|
||||
obj-$(CONFIG_MTD_BCM63XX_PARTS) += bcm63xxpart.o
|
||||
obj-$(CONFIG_MTD_BCM47XX_PARTS) += bcm47xxpart.o
|
||||
|
||||
# 'Users' - code which presents functionality to userspace.
|
||||
obj-$(CONFIG_MTD_CHAR) += mtdchar.o
|
||||
|
202
drivers/mtd/bcm47xxpart.c
Normal file
202
drivers/mtd/bcm47xxpart.c
Normal file
@ -0,0 +1,202 @@
|
||||
/*
|
||||
* BCM47XX MTD partitioning
|
||||
*
|
||||
* Copyright © 2012 Rafał Miłecki <zajec5@gmail.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/mtd/mtd.h>
|
||||
#include <linux/mtd/partitions.h>
|
||||
#include <asm/mach-bcm47xx/nvram.h>
|
||||
|
||||
/* 10 parts were found on sflash on Netgear WNDR4500 */
|
||||
#define BCM47XXPART_MAX_PARTS 12
|
||||
|
||||
/*
|
||||
* Amount of bytes we read when analyzing each block of flash memory.
|
||||
* Set it big enough to allow detecting partition and reading important data.
|
||||
*/
|
||||
#define BCM47XXPART_BYTES_TO_READ 0x404
|
||||
|
||||
/* Magics */
|
||||
#define BOARD_DATA_MAGIC 0x5246504D /* MPFR */
|
||||
#define POT_MAGIC1 0x54544f50 /* POTT */
|
||||
#define POT_MAGIC2 0x504f /* OP */
|
||||
#define ML_MAGIC1 0x39685a42
|
||||
#define ML_MAGIC2 0x26594131
|
||||
#define TRX_MAGIC 0x30524448
|
||||
|
||||
struct trx_header {
|
||||
uint32_t magic;
|
||||
uint32_t length;
|
||||
uint32_t crc32;
|
||||
uint16_t flags;
|
||||
uint16_t version;
|
||||
uint32_t offset[3];
|
||||
} __packed;
|
||||
|
||||
static void bcm47xxpart_add_part(struct mtd_partition *part, char *name,
|
||||
u64 offset, uint32_t mask_flags)
|
||||
{
|
||||
part->name = name;
|
||||
part->offset = offset;
|
||||
part->mask_flags = mask_flags;
|
||||
}
|
||||
|
||||
static int bcm47xxpart_parse(struct mtd_info *master,
|
||||
struct mtd_partition **pparts,
|
||||
struct mtd_part_parser_data *data)
|
||||
{
|
||||
struct mtd_partition *parts;
|
||||
uint8_t i, curr_part = 0;
|
||||
uint32_t *buf;
|
||||
size_t bytes_read;
|
||||
uint32_t offset;
|
||||
uint32_t blocksize = 0x10000;
|
||||
struct trx_header *trx;
|
||||
|
||||
/* Alloc */
|
||||
parts = kzalloc(sizeof(struct mtd_partition) * BCM47XXPART_MAX_PARTS,
|
||||
GFP_KERNEL);
|
||||
buf = kzalloc(BCM47XXPART_BYTES_TO_READ, GFP_KERNEL);
|
||||
|
||||
/* Parse block by block looking for magics */
|
||||
for (offset = 0; offset <= master->size - blocksize;
|
||||
offset += blocksize) {
|
||||
/* Nothing more in higher memory */
|
||||
if (offset >= 0x2000000)
|
||||
break;
|
||||
|
||||
if (curr_part > BCM47XXPART_MAX_PARTS) {
|
||||
pr_warn("Reached maximum number of partitions, scanning stopped!\n");
|
||||
break;
|
||||
}
|
||||
|
||||
/* Read beginning of the block */
|
||||
if (mtd_read(master, offset, BCM47XXPART_BYTES_TO_READ,
|
||||
&bytes_read, (uint8_t *)buf) < 0) {
|
||||
pr_err("mtd_read error while parsing (offset: 0x%X)!\n",
|
||||
offset);
|
||||
continue;
|
||||
}
|
||||
|
||||
/* CFE has small NVRAM at 0x400 */
|
||||
if (buf[0x400 / 4] == NVRAM_HEADER) {
|
||||
bcm47xxpart_add_part(&parts[curr_part++], "boot",
|
||||
offset, MTD_WRITEABLE);
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Standard NVRAM */
|
||||
if (buf[0x000 / 4] == NVRAM_HEADER) {
|
||||
bcm47xxpart_add_part(&parts[curr_part++], "nvram",
|
||||
offset, 0);
|
||||
continue;
|
||||
}
|
||||
|
||||
/*
|
||||
* board_data starts with board_id which differs across boards,
|
||||
* but we can use 'MPFR' (hopefully) magic at 0x100
|
||||
*/
|
||||
if (buf[0x100 / 4] == BOARD_DATA_MAGIC) {
|
||||
bcm47xxpart_add_part(&parts[curr_part++], "board_data",
|
||||
offset, MTD_WRITEABLE);
|
||||
continue;
|
||||
}
|
||||
|
||||
/* POT(TOP) */
|
||||
if (buf[0x000 / 4] == POT_MAGIC1 &&
|
||||
(buf[0x004 / 4] & 0xFFFF) == POT_MAGIC2) {
|
||||
bcm47xxpart_add_part(&parts[curr_part++], "POT", offset,
|
||||
MTD_WRITEABLE);
|
||||
continue;
|
||||
}
|
||||
|
||||
/* ML */
|
||||
if (buf[0x010 / 4] == ML_MAGIC1 &&
|
||||
buf[0x014 / 4] == ML_MAGIC2) {
|
||||
bcm47xxpart_add_part(&parts[curr_part++], "ML", offset,
|
||||
MTD_WRITEABLE);
|
||||
continue;
|
||||
}
|
||||
|
||||
/* TRX */
|
||||
if (buf[0x000 / 4] == TRX_MAGIC) {
|
||||
trx = (struct trx_header *)buf;
|
||||
|
||||
i = 0;
|
||||
/* We have LZMA loader if offset[2] points to sth */
|
||||
if (trx->offset[2]) {
|
||||
bcm47xxpart_add_part(&parts[curr_part++],
|
||||
"loader",
|
||||
offset + trx->offset[i],
|
||||
0);
|
||||
i++;
|
||||
}
|
||||
|
||||
bcm47xxpart_add_part(&parts[curr_part++], "linux",
|
||||
offset + trx->offset[i], 0);
|
||||
i++;
|
||||
|
||||
/*
|
||||
* Pure rootfs size is known and can be calculated as:
|
||||
* trx->length - trx->offset[i]. We don't fill it as
|
||||
* we want to have jffs2 (overlay) in the same mtd.
|
||||
*/
|
||||
bcm47xxpart_add_part(&parts[curr_part++], "rootfs",
|
||||
offset + trx->offset[i], 0);
|
||||
i++;
|
||||
|
||||
/*
|
||||
* We have whole TRX scanned, skip to the next part. Use
|
||||
* roundown (not roundup), as the loop will increase
|
||||
* offset in next step.
|
||||
*/
|
||||
offset = rounddown(offset + trx->length, blocksize);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
kfree(buf);
|
||||
|
||||
/*
|
||||
* Assume that partitions end at the beginning of the one they are
|
||||
* followed by.
|
||||
*/
|
||||
for (i = 0; i < curr_part - 1; i++)
|
||||
parts[i].size = parts[i + 1].offset - parts[i].offset;
|
||||
if (curr_part > 0)
|
||||
parts[curr_part - 1].size =
|
||||
master->size - parts[curr_part - 1].offset;
|
||||
|
||||
*pparts = parts;
|
||||
return curr_part;
|
||||
};
|
||||
|
||||
static struct mtd_part_parser bcm47xxpart_mtd_parser = {
|
||||
.owner = THIS_MODULE,
|
||||
.parse_fn = bcm47xxpart_parse,
|
||||
.name = "bcm47xxpart",
|
||||
};
|
||||
|
||||
static int __init bcm47xxpart_init(void)
|
||||
{
|
||||
return register_mtd_parser(&bcm47xxpart_mtd_parser);
|
||||
}
|
||||
|
||||
static void __exit bcm47xxpart_exit(void)
|
||||
{
|
||||
deregister_mtd_parser(&bcm47xxpart_mtd_parser);
|
||||
}
|
||||
|
||||
module_init(bcm47xxpart_init);
|
||||
module_exit(bcm47xxpart_exit);
|
||||
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_DESCRIPTION("MTD partitioning for BCM47XX flash memories");
|
@ -43,9 +43,6 @@ choice
|
||||
prompt "Flash cmd/query data swapping"
|
||||
depends on MTD_CFI_ADV_OPTIONS
|
||||
default MTD_CFI_NOSWAP
|
||||
|
||||
config MTD_CFI_NOSWAP
|
||||
bool "NO"
|
||||
---help---
|
||||
This option defines the way in which the CPU attempts to arrange
|
||||
data bits when writing the 'magic' commands to the chips. Saying
|
||||
@ -55,12 +52,8 @@ config MTD_CFI_NOSWAP
|
||||
Specific arrangements are possible with the BIG_ENDIAN_BYTE and
|
||||
LITTLE_ENDIAN_BYTE, if the bytes are reversed.
|
||||
|
||||
If you have a LART, on which the data (and address) lines were
|
||||
connected in a fashion which ensured that the nets were as short
|
||||
as possible, resulting in a bit-shuffling which seems utterly
|
||||
random to the untrained eye, you need the LART_ENDIAN_BYTE option.
|
||||
|
||||
Yes, there really exists something sicker than PDP-endian :)
|
||||
config MTD_CFI_NOSWAP
|
||||
bool "NO"
|
||||
|
||||
config MTD_CFI_BE_BYTE_SWAP
|
||||
bool "BIG_ENDIAN_BYTE"
|
||||
|
@ -2043,7 +2043,7 @@ static int __xipram do_xxlock_oneblock(struct map_info *map, struct flchip *chip
|
||||
{
|
||||
struct cfi_private *cfi = map->fldrv_priv;
|
||||
struct cfi_pri_intelext *extp = cfi->cmdset_priv;
|
||||
int udelay;
|
||||
int mdelay;
|
||||
int ret;
|
||||
|
||||
adr += chip->start;
|
||||
@ -2072,9 +2072,17 @@ static int __xipram do_xxlock_oneblock(struct map_info *map, struct flchip *chip
|
||||
* If Instant Individual Block Locking supported then no need
|
||||
* to delay.
|
||||
*/
|
||||
udelay = (!extp || !(extp->FeatureSupport & (1 << 5))) ? 1000000/HZ : 0;
|
||||
/*
|
||||
* Unlocking may take up to 1.4 seconds on some Intel flashes. So
|
||||
* lets use a max of 1.5 seconds (1500ms) as timeout.
|
||||
*
|
||||
* See "Clear Block Lock-Bits Time" on page 40 in
|
||||
* "3 Volt Intel StrataFlash Memory" 28F128J3,28F640J3,28F320J3 manual
|
||||
* from February 2003
|
||||
*/
|
||||
mdelay = (!extp || !(extp->FeatureSupport & (1 << 5))) ? 1500 : 0;
|
||||
|
||||
ret = WAIT_TIMEOUT(map, chip, adr, udelay, udelay * 100);
|
||||
ret = WAIT_TIMEOUT(map, chip, adr, mdelay, mdelay * 1000);
|
||||
if (ret) {
|
||||
map_write(map, CMD(0x70), adr);
|
||||
chip->state = FL_STATUS;
|
||||
|
@ -431,6 +431,68 @@ static void cfi_fixup_major_minor(struct cfi_private *cfi,
|
||||
}
|
||||
}
|
||||
|
||||
static int is_m29ew(struct cfi_private *cfi)
|
||||
{
|
||||
if (cfi->mfr == CFI_MFR_INTEL &&
|
||||
((cfi->device_type == CFI_DEVICETYPE_X8 && (cfi->id & 0xff) == 0x7e) ||
|
||||
(cfi->device_type == CFI_DEVICETYPE_X16 && cfi->id == 0x227e)))
|
||||
return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* From TN-13-07: Patching the Linux Kernel and U-Boot for M29 Flash, page 20:
|
||||
* Some revisions of the M29EW suffer from erase suspend hang ups. In
|
||||
* particular, it can occur when the sequence
|
||||
* Erase Confirm -> Suspend -> Program -> Resume
|
||||
* causes a lockup due to internal timing issues. The consequence is that the
|
||||
* erase cannot be resumed without inserting a dummy command after programming
|
||||
* and prior to resuming. [...] The work-around is to issue a dummy write cycle
|
||||
* that writes an F0 command code before the RESUME command.
|
||||
*/
|
||||
static void cfi_fixup_m29ew_erase_suspend(struct map_info *map,
|
||||
unsigned long adr)
|
||||
{
|
||||
struct cfi_private *cfi = map->fldrv_priv;
|
||||
/* before resume, insert a dummy 0xF0 cycle for Micron M29EW devices */
|
||||
if (is_m29ew(cfi))
|
||||
map_write(map, CMD(0xF0), adr);
|
||||
}
|
||||
|
||||
/*
|
||||
* From TN-13-07: Patching the Linux Kernel and U-Boot for M29 Flash, page 22:
|
||||
*
|
||||
* Some revisions of the M29EW (for example, A1 and A2 step revisions)
|
||||
* are affected by a problem that could cause a hang up when an ERASE SUSPEND
|
||||
* command is issued after an ERASE RESUME operation without waiting for a
|
||||
* minimum delay. The result is that once the ERASE seems to be completed
|
||||
* (no bits are toggling), the contents of the Flash memory block on which
|
||||
* the erase was ongoing could be inconsistent with the expected values
|
||||
* (typically, the array value is stuck to the 0xC0, 0xC4, 0x80, or 0x84
|
||||
* values), causing a consequent failure of the ERASE operation.
|
||||
* The occurrence of this issue could be high, especially when file system
|
||||
* operations on the Flash are intensive. As a result, it is recommended
|
||||
* that a patch be applied. Intensive file system operations can cause many
|
||||
* calls to the garbage routine to free Flash space (also by erasing physical
|
||||
* Flash blocks) and as a result, many consecutive SUSPEND and RESUME
|
||||
* commands can occur. The problem disappears when a delay is inserted after
|
||||
* the RESUME command by using the udelay() function available in Linux.
|
||||
* The DELAY value must be tuned based on the customer's platform.
|
||||
* The maximum value that fixes the problem in all cases is 500us.
|
||||
* But, in our experience, a delay of 30 µs to 50 µs is sufficient
|
||||
* in most cases.
|
||||
* We have chosen 500µs because this latency is acceptable.
|
||||
*/
|
||||
static void cfi_fixup_m29ew_delay_after_resume(struct cfi_private *cfi)
|
||||
{
|
||||
/*
|
||||
* Resolving the Delay After Resume Issue see Micron TN-13-07
|
||||
* Worst case delay must be 500µs but 30-50µs should be ok as well
|
||||
*/
|
||||
if (is_m29ew(cfi))
|
||||
cfi_udelay(500);
|
||||
}
|
||||
|
||||
struct mtd_info *cfi_cmdset_0002(struct map_info *map, int primary)
|
||||
{
|
||||
struct cfi_private *cfi = map->fldrv_priv;
|
||||
@ -776,7 +838,10 @@ static void put_chip(struct map_info *map, struct flchip *chip, unsigned long ad
|
||||
|
||||
switch(chip->oldstate) {
|
||||
case FL_ERASING:
|
||||
cfi_fixup_m29ew_erase_suspend(map,
|
||||
chip->in_progress_block_addr);
|
||||
map_write(map, cfi->sector_erase_cmd, chip->in_progress_block_addr);
|
||||
cfi_fixup_m29ew_delay_after_resume(cfi);
|
||||
chip->oldstate = FL_READY;
|
||||
chip->state = FL_ERASING;
|
||||
break;
|
||||
@ -916,6 +981,8 @@ static void __xipram xip_udelay(struct map_info *map, struct flchip *chip,
|
||||
/* Disallow XIP again */
|
||||
local_irq_disable();
|
||||
|
||||
/* Correct Erase Suspend Hangups for M29EW */
|
||||
cfi_fixup_m29ew_erase_suspend(map, adr);
|
||||
/* Resume the write or erase operation */
|
||||
map_write(map, cfi->sector_erase_cmd, adr);
|
||||
chip->state = oldstate;
|
||||
|
@ -39,11 +39,10 @@
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/slab.h>
|
||||
|
||||
#include <linux/mtd/mtd.h>
|
||||
#include <linux/mtd/partitions.h>
|
||||
#include <linux/bootmem.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/err.h>
|
||||
|
||||
/* error message prefix */
|
||||
#define ERRP "mtd: "
|
||||
@ -72,7 +71,7 @@ static struct cmdline_mtd_partition *partitions;
|
||||
|
||||
/* the command line passed to mtdpart_setup() */
|
||||
static char *cmdline;
|
||||
static int cmdline_parsed = 0;
|
||||
static int cmdline_parsed;
|
||||
|
||||
/*
|
||||
* Parse one partition definition for an MTD. Since there can be many
|
||||
@ -83,15 +82,14 @@ static int cmdline_parsed = 0;
|
||||
* syntax has been verified ok.
|
||||
*/
|
||||
static struct mtd_partition * newpart(char *s,
|
||||
char **retptr,
|
||||
int *num_parts,
|
||||
int this_part,
|
||||
unsigned char **extra_mem_ptr,
|
||||
int extra_mem_size)
|
||||
char **retptr,
|
||||
int *num_parts,
|
||||
int this_part,
|
||||
unsigned char **extra_mem_ptr,
|
||||
int extra_mem_size)
|
||||
{
|
||||
struct mtd_partition *parts;
|
||||
unsigned long size;
|
||||
unsigned long offset = OFFSET_CONTINUOUS;
|
||||
unsigned long size, offset = OFFSET_CONTINUOUS;
|
||||
char *name;
|
||||
int name_len;
|
||||
unsigned char *extra_mem;
|
||||
@ -99,124 +97,106 @@ static struct mtd_partition * newpart(char *s,
|
||||
unsigned int mask_flags;
|
||||
|
||||
/* fetch the partition size */
|
||||
if (*s == '-')
|
||||
{ /* assign all remaining space to this partition */
|
||||
if (*s == '-') {
|
||||
/* assign all remaining space to this partition */
|
||||
size = SIZE_REMAINING;
|
||||
s++;
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
size = memparse(s, &s);
|
||||
if (size < PAGE_SIZE)
|
||||
{
|
||||
if (size < PAGE_SIZE) {
|
||||
printk(KERN_ERR ERRP "partition size too small (%lx)\n", size);
|
||||
return NULL;
|
||||
return ERR_PTR(-EINVAL);
|
||||
}
|
||||
}
|
||||
|
||||
/* fetch partition name and flags */
|
||||
mask_flags = 0; /* this is going to be a regular partition */
|
||||
delim = 0;
|
||||
/* check for offset */
|
||||
if (*s == '@')
|
||||
{
|
||||
s++;
|
||||
offset = memparse(s, &s);
|
||||
}
|
||||
/* now look for name */
|
||||
if (*s == '(')
|
||||
{
|
||||
delim = ')';
|
||||
|
||||
/* check for offset */
|
||||
if (*s == '@') {
|
||||
s++;
|
||||
offset = memparse(s, &s);
|
||||
}
|
||||
|
||||
if (delim)
|
||||
{
|
||||
/* now look for name */
|
||||
if (*s == '(')
|
||||
delim = ')';
|
||||
|
||||
if (delim) {
|
||||
char *p;
|
||||
|
||||
name = ++s;
|
||||
name = ++s;
|
||||
p = strchr(name, delim);
|
||||
if (!p)
|
||||
{
|
||||
if (!p) {
|
||||
printk(KERN_ERR ERRP "no closing %c found in partition name\n", delim);
|
||||
return NULL;
|
||||
return ERR_PTR(-EINVAL);
|
||||
}
|
||||
name_len = p - name;
|
||||
s = p + 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
name = NULL;
|
||||
} else {
|
||||
name = NULL;
|
||||
name_len = 13; /* Partition_000 */
|
||||
}
|
||||
|
||||
/* record name length for memory allocation later */
|
||||
extra_mem_size += name_len + 1;
|
||||
|
||||
/* test for options */
|
||||
if (strncmp(s, "ro", 2) == 0)
|
||||
{
|
||||
/* test for options */
|
||||
if (strncmp(s, "ro", 2) == 0) {
|
||||
mask_flags |= MTD_WRITEABLE;
|
||||
s += 2;
|
||||
}
|
||||
}
|
||||
|
||||
/* if lk is found do NOT unlock the MTD partition*/
|
||||
if (strncmp(s, "lk", 2) == 0)
|
||||
{
|
||||
/* if lk is found do NOT unlock the MTD partition*/
|
||||
if (strncmp(s, "lk", 2) == 0) {
|
||||
mask_flags |= MTD_POWERUP_LOCK;
|
||||
s += 2;
|
||||
}
|
||||
}
|
||||
|
||||
/* test if more partitions are following */
|
||||
if (*s == ',')
|
||||
{
|
||||
if (size == SIZE_REMAINING)
|
||||
{
|
||||
if (*s == ',') {
|
||||
if (size == SIZE_REMAINING) {
|
||||
printk(KERN_ERR ERRP "no partitions allowed after a fill-up partition\n");
|
||||
return NULL;
|
||||
return ERR_PTR(-EINVAL);
|
||||
}
|
||||
/* more partitions follow, parse them */
|
||||
parts = newpart(s + 1, &s, num_parts, this_part + 1,
|
||||
&extra_mem, extra_mem_size);
|
||||
if (!parts)
|
||||
return NULL;
|
||||
}
|
||||
else
|
||||
{ /* this is the last partition: allocate space for all */
|
||||
if (IS_ERR(parts))
|
||||
return parts;
|
||||
} else {
|
||||
/* this is the last partition: allocate space for all */
|
||||
int alloc_size;
|
||||
|
||||
*num_parts = this_part + 1;
|
||||
alloc_size = *num_parts * sizeof(struct mtd_partition) +
|
||||
extra_mem_size;
|
||||
|
||||
parts = kzalloc(alloc_size, GFP_KERNEL);
|
||||
if (!parts)
|
||||
return NULL;
|
||||
return ERR_PTR(-ENOMEM);
|
||||
extra_mem = (unsigned char *)(parts + *num_parts);
|
||||
}
|
||||
|
||||
/* enter this partition (offset will be calculated later if it is zero at this point) */
|
||||
parts[this_part].size = size;
|
||||
parts[this_part].offset = offset;
|
||||
parts[this_part].mask_flags = mask_flags;
|
||||
if (name)
|
||||
{
|
||||
strlcpy(extra_mem, name, name_len + 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
sprintf(extra_mem, "Partition_%03d", this_part);
|
||||
}
|
||||
parts[this_part].name = extra_mem;
|
||||
extra_mem += name_len + 1;
|
||||
|
||||
dbg(("partition %d: name <%s>, offset %llx, size %llx, mask flags %x\n",
|
||||
this_part,
|
||||
parts[this_part].name,
|
||||
parts[this_part].offset,
|
||||
parts[this_part].size,
|
||||
parts[this_part].mask_flags));
|
||||
this_part, parts[this_part].name, parts[this_part].offset,
|
||||
parts[this_part].size, parts[this_part].mask_flags));
|
||||
|
||||
/* return (updated) pointer to extra_mem memory */
|
||||
if (extra_mem_ptr)
|
||||
*extra_mem_ptr = extra_mem;
|
||||
*extra_mem_ptr = extra_mem;
|
||||
|
||||
/* return (updated) pointer command line string */
|
||||
*retptr = s;
|
||||
@ -236,16 +216,16 @@ static int mtdpart_setup_real(char *s)
|
||||
{
|
||||
struct cmdline_mtd_partition *this_mtd;
|
||||
struct mtd_partition *parts;
|
||||
int mtd_id_len;
|
||||
int num_parts;
|
||||
int mtd_id_len, num_parts;
|
||||
char *p, *mtd_id;
|
||||
|
||||
mtd_id = s;
|
||||
mtd_id = s;
|
||||
|
||||
/* fetch <mtd-id> */
|
||||
if (!(p = strchr(s, ':')))
|
||||
{
|
||||
p = strchr(s, ':');
|
||||
if (!p) {
|
||||
printk(KERN_ERR ERRP "no mtd-id\n");
|
||||
return 0;
|
||||
return -EINVAL;
|
||||
}
|
||||
mtd_id_len = p - mtd_id;
|
||||
|
||||
@ -262,8 +242,7 @@ static int mtdpart_setup_real(char *s)
|
||||
(unsigned char**)&this_mtd, /* out: extra mem */
|
||||
mtd_id_len + 1 + sizeof(*this_mtd) +
|
||||
sizeof(void*)-1 /*alignment*/);
|
||||
if(!parts)
|
||||
{
|
||||
if (IS_ERR(parts)) {
|
||||
/*
|
||||
* An error occurred. We're either:
|
||||
* a) out of memory, or
|
||||
@ -271,12 +250,12 @@ static int mtdpart_setup_real(char *s)
|
||||
* Either way, this mtd is hosed and we're
|
||||
* unlikely to succeed in parsing any more
|
||||
*/
|
||||
return 0;
|
||||
return PTR_ERR(parts);
|
||||
}
|
||||
|
||||
/* align this_mtd */
|
||||
this_mtd = (struct cmdline_mtd_partition *)
|
||||
ALIGN((unsigned long)this_mtd, sizeof(void*));
|
||||
ALIGN((unsigned long)this_mtd, sizeof(void *));
|
||||
/* enter results */
|
||||
this_mtd->parts = parts;
|
||||
this_mtd->num_parts = num_parts;
|
||||
@ -296,14 +275,14 @@ static int mtdpart_setup_real(char *s)
|
||||
break;
|
||||
|
||||
/* does another spec follow? */
|
||||
if (*s != ';')
|
||||
{
|
||||
if (*s != ';') {
|
||||
printk(KERN_ERR ERRP "bad character after partition (%c)\n", *s);
|
||||
return 0;
|
||||
return -EINVAL;
|
||||
}
|
||||
s++;
|
||||
}
|
||||
return 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
@ -318,44 +297,58 @@ static int parse_cmdline_partitions(struct mtd_info *master,
|
||||
struct mtd_part_parser_data *data)
|
||||
{
|
||||
unsigned long offset;
|
||||
int i;
|
||||
int i, err;
|
||||
struct cmdline_mtd_partition *part;
|
||||
const char *mtd_id = master->name;
|
||||
|
||||
/* parse command line */
|
||||
if (!cmdline_parsed)
|
||||
mtdpart_setup_real(cmdline);
|
||||
if (!cmdline_parsed) {
|
||||
err = mtdpart_setup_real(cmdline);
|
||||
if (err)
|
||||
return err;
|
||||
}
|
||||
|
||||
for(part = partitions; part; part = part->next)
|
||||
{
|
||||
if ((!mtd_id) || (!strcmp(part->mtd_id, mtd_id)))
|
||||
{
|
||||
for(i = 0, offset = 0; i < part->num_parts; i++)
|
||||
{
|
||||
for (part = partitions; part; part = part->next) {
|
||||
if ((!mtd_id) || (!strcmp(part->mtd_id, mtd_id))) {
|
||||
for (i = 0, offset = 0; i < part->num_parts; i++) {
|
||||
if (part->parts[i].offset == OFFSET_CONTINUOUS)
|
||||
part->parts[i].offset = offset;
|
||||
part->parts[i].offset = offset;
|
||||
else
|
||||
offset = part->parts[i].offset;
|
||||
offset = part->parts[i].offset;
|
||||
|
||||
if (part->parts[i].size == SIZE_REMAINING)
|
||||
part->parts[i].size = master->size - offset;
|
||||
if (offset + part->parts[i].size > master->size)
|
||||
{
|
||||
part->parts[i].size = master->size - offset;
|
||||
|
||||
if (part->parts[i].size == 0) {
|
||||
printk(KERN_WARNING ERRP
|
||||
"%s: skipping zero sized partition\n",
|
||||
part->mtd_id);
|
||||
part->num_parts--;
|
||||
memmove(&part->parts[i],
|
||||
&part->parts[i + 1],
|
||||
sizeof(*part->parts) * (part->num_parts - i));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (offset + part->parts[i].size > master->size) {
|
||||
printk(KERN_WARNING ERRP
|
||||
"%s: partitioning exceeds flash size, truncating\n",
|
||||
part->mtd_id);
|
||||
part->parts[i].size = master->size - offset;
|
||||
part->num_parts = i;
|
||||
}
|
||||
offset += part->parts[i].size;
|
||||
}
|
||||
|
||||
*pparts = kmemdup(part->parts,
|
||||
sizeof(*part->parts) * part->num_parts,
|
||||
GFP_KERNEL);
|
||||
if (!*pparts)
|
||||
return -ENOMEM;
|
||||
|
||||
return part->num_parts;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -97,7 +97,7 @@ config MTD_M25P80
|
||||
doesn't support the JEDEC ID instruction.
|
||||
|
||||
config M25PXX_USE_FAST_READ
|
||||
bool "Use FAST_READ OPCode allowing SPI CLK <= 50MHz"
|
||||
bool "Use FAST_READ OPCode allowing SPI CLK >= 50MHz"
|
||||
depends on MTD_M25P80
|
||||
default y
|
||||
help
|
||||
@ -120,6 +120,14 @@ config MTD_SST25L
|
||||
Set up your spi devices with the right board-specific platform data,
|
||||
if you want to specify device partitioning.
|
||||
|
||||
config MTD_BCM47XXSFLASH
|
||||
tristate "R/O support for serial flash on BCMA bus"
|
||||
depends on BCMA_SFLASH
|
||||
help
|
||||
BCMA bus can have various flash memories attached, they are
|
||||
registered by bcma as platform devices. This enables driver for
|
||||
serial flash memories (only read-only mode is implemented).
|
||||
|
||||
config MTD_SLRAM
|
||||
tristate "Uncached system RAM"
|
||||
help
|
||||
|
@ -19,5 +19,6 @@ obj-$(CONFIG_MTD_DATAFLASH) += mtd_dataflash.o
|
||||
obj-$(CONFIG_MTD_M25P80) += m25p80.o
|
||||
obj-$(CONFIG_MTD_SPEAR_SMI) += spear_smi.o
|
||||
obj-$(CONFIG_MTD_SST25L) += sst25l.o
|
||||
obj-$(CONFIG_MTD_BCM47XXSFLASH) += bcm47xxsflash.o
|
||||
|
||||
CFLAGS_docg3.o += -I$(src)
|
105
drivers/mtd/devices/bcm47xxsflash.c
Normal file
105
drivers/mtd/devices/bcm47xxsflash.c
Normal file
@ -0,0 +1,105 @@
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/mtd/mtd.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/bcma/bcma.h>
|
||||
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_DESCRIPTION("Serial flash driver for BCMA bus");
|
||||
|
||||
static const char *probes[] = { "bcm47xxpart", NULL };
|
||||
|
||||
static int bcm47xxsflash_read(struct mtd_info *mtd, loff_t from, size_t len,
|
||||
size_t *retlen, u_char *buf)
|
||||
{
|
||||
struct bcma_sflash *sflash = mtd->priv;
|
||||
|
||||
/* Check address range */
|
||||
if ((from + len) > mtd->size)
|
||||
return -EINVAL;
|
||||
|
||||
memcpy_fromio(buf, (void __iomem *)KSEG0ADDR(sflash->window + from),
|
||||
len);
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
static void bcm47xxsflash_fill_mtd(struct bcma_sflash *sflash,
|
||||
struct mtd_info *mtd)
|
||||
{
|
||||
mtd->priv = sflash;
|
||||
mtd->name = "bcm47xxsflash";
|
||||
mtd->owner = THIS_MODULE;
|
||||
mtd->type = MTD_ROM;
|
||||
mtd->size = sflash->size;
|
||||
mtd->_read = bcm47xxsflash_read;
|
||||
|
||||
/* TODO: implement writing support and verify/change following code */
|
||||
mtd->flags = MTD_CAP_ROM;
|
||||
mtd->writebufsize = mtd->writesize = 1;
|
||||
}
|
||||
|
||||
static int bcm47xxsflash_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct bcma_sflash *sflash = dev_get_platdata(&pdev->dev);
|
||||
int err;
|
||||
|
||||
sflash->mtd = kzalloc(sizeof(struct mtd_info), GFP_KERNEL);
|
||||
if (!sflash->mtd) {
|
||||
err = -ENOMEM;
|
||||
goto out;
|
||||
}
|
||||
bcm47xxsflash_fill_mtd(sflash, sflash->mtd);
|
||||
|
||||
err = mtd_device_parse_register(sflash->mtd, probes, NULL, NULL, 0);
|
||||
if (err) {
|
||||
pr_err("Failed to register MTD device: %d\n", err);
|
||||
goto err_dev_reg;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
err_dev_reg:
|
||||
kfree(sflash->mtd);
|
||||
out:
|
||||
return err;
|
||||
}
|
||||
|
||||
static int __devexit bcm47xxsflash_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct bcma_sflash *sflash = dev_get_platdata(&pdev->dev);
|
||||
|
||||
mtd_device_unregister(sflash->mtd);
|
||||
kfree(sflash->mtd);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct platform_driver bcma_sflash_driver = {
|
||||
.remove = __devexit_p(bcm47xxsflash_remove),
|
||||
.driver = {
|
||||
.name = "bcma_sflash",
|
||||
.owner = THIS_MODULE,
|
||||
},
|
||||
};
|
||||
|
||||
static int __init bcm47xxsflash_init(void)
|
||||
{
|
||||
int err;
|
||||
|
||||
err = platform_driver_probe(&bcma_sflash_driver, bcm47xxsflash_probe);
|
||||
if (err)
|
||||
pr_err("Failed to register BCMA serial flash driver: %d\n",
|
||||
err);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static void __exit bcm47xxsflash_exit(void)
|
||||
{
|
||||
platform_driver_unregister(&bcma_sflash_driver);
|
||||
}
|
||||
|
||||
module_init(bcm47xxsflash_init);
|
||||
module_exit(bcm47xxsflash_exit);
|
@ -659,23 +659,15 @@ static int doc_read(struct mtd_info *mtd, loff_t from, size_t len,
|
||||
#ifdef ECC_DEBUG
|
||||
printk("%s(%d): Millennium Plus ECC error (from=0x%x:\n",
|
||||
__FILE__, __LINE__, (int)from);
|
||||
printk(" syndrome= %02x:%02x:%02x:%02x:%02x:"
|
||||
"%02x\n",
|
||||
syndrome[0], syndrome[1], syndrome[2],
|
||||
syndrome[3], syndrome[4], syndrome[5]);
|
||||
printk(" eccbuf= %02x:%02x:%02x:%02x:%02x:"
|
||||
"%02x\n",
|
||||
eccbuf[0], eccbuf[1], eccbuf[2],
|
||||
eccbuf[3], eccbuf[4], eccbuf[5]);
|
||||
printk(" syndrome= %*phC\n", 6, syndrome);
|
||||
printk(" eccbuf= %*phC\n", 6, eccbuf);
|
||||
#endif
|
||||
ret = -EIO;
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef PSYCHO_DEBUG
|
||||
printk("ECC DATA at %lx: %2.2X %2.2X %2.2X %2.2X %2.2X %2.2X\n",
|
||||
(long)from, eccbuf[0], eccbuf[1], eccbuf[2], eccbuf[3],
|
||||
eccbuf[4], eccbuf[5]);
|
||||
printk("ECC DATA at %lx: %*ph\n", (long)from, 6, eccbuf);
|
||||
#endif
|
||||
/* disable the ECC engine */
|
||||
WriteDOC(DOC_ECC_DIS, docptr , Mplus_ECCConf);
|
||||
|
@ -919,19 +919,13 @@ static int doc_read_oob(struct mtd_info *mtd, loff_t from,
|
||||
eccconf1 = doc_register_readb(docg3, DOC_ECCCONF1);
|
||||
|
||||
if (nboob >= DOC_LAYOUT_OOB_SIZE) {
|
||||
doc_dbg("OOB - INFO: %02x:%02x:%02x:%02x:%02x:%02x:%02x\n",
|
||||
oobbuf[0], oobbuf[1], oobbuf[2], oobbuf[3],
|
||||
oobbuf[4], oobbuf[5], oobbuf[6]);
|
||||
doc_dbg("OOB - INFO: %*phC\n", 7, oobbuf);
|
||||
doc_dbg("OOB - HAMMING: %02x\n", oobbuf[7]);
|
||||
doc_dbg("OOB - BCH_ECC: %02x:%02x:%02x:%02x:%02x:%02x:%02x\n",
|
||||
oobbuf[8], oobbuf[9], oobbuf[10], oobbuf[11],
|
||||
oobbuf[12], oobbuf[13], oobbuf[14]);
|
||||
doc_dbg("OOB - BCH_ECC: %*phC\n", 7, oobbuf + 8);
|
||||
doc_dbg("OOB - UNUSED: %02x\n", oobbuf[15]);
|
||||
}
|
||||
doc_dbg("ECC checks: ECCConf1=%x\n", eccconf1);
|
||||
doc_dbg("ECC HW_ECC: %02x:%02x:%02x:%02x:%02x:%02x:%02x\n",
|
||||
hwecc[0], hwecc[1], hwecc[2], hwecc[3], hwecc[4],
|
||||
hwecc[5], hwecc[6]);
|
||||
doc_dbg("ECC HW_ECC: %*phC\n", 7, hwecc);
|
||||
|
||||
ret = -EIO;
|
||||
if (is_prot_seq_error(docg3))
|
||||
|
@ -633,11 +633,14 @@ static const struct spi_device_id m25p_ids[] = {
|
||||
{ "at26df161a", INFO(0x1f4601, 0, 64 * 1024, 32, SECT_4K) },
|
||||
{ "at26df321", INFO(0x1f4700, 0, 64 * 1024, 64, SECT_4K) },
|
||||
|
||||
{ "at45db081d", INFO(0x1f2500, 0, 64 * 1024, 16, SECT_4K) },
|
||||
|
||||
/* EON -- en25xxx */
|
||||
{ "en25f32", INFO(0x1c3116, 0, 64 * 1024, 64, SECT_4K) },
|
||||
{ "en25p32", INFO(0x1c2016, 0, 64 * 1024, 64, 0) },
|
||||
{ "en25q32b", INFO(0x1c3016, 0, 64 * 1024, 64, 0) },
|
||||
{ "en25p64", INFO(0x1c2017, 0, 64 * 1024, 128, 0) },
|
||||
{ "en25q64", INFO(0x1c3017, 0, 64 * 1024, 128, SECT_4K) },
|
||||
|
||||
/* Everspin */
|
||||
{ "mr25h256", CAT25_INFO( 32 * 1024, 1, 256, 2) },
|
||||
@ -646,6 +649,7 @@ static const struct spi_device_id m25p_ids[] = {
|
||||
{ "160s33b", INFO(0x898911, 0, 64 * 1024, 32, 0) },
|
||||
{ "320s33b", INFO(0x898912, 0, 64 * 1024, 64, 0) },
|
||||
{ "640s33b", INFO(0x898913, 0, 64 * 1024, 128, 0) },
|
||||
{ "n25q064", INFO(0x20ba17, 0, 64 * 1024, 128, 0) },
|
||||
|
||||
/* Macronix */
|
||||
{ "mx25l2005a", INFO(0xc22012, 0, 64 * 1024, 4, SECT_4K) },
|
||||
@ -659,15 +663,15 @@ static const struct spi_device_id m25p_ids[] = {
|
||||
{ "mx25l25635e", INFO(0xc22019, 0, 64 * 1024, 512, 0) },
|
||||
{ "mx25l25655e", INFO(0xc22619, 0, 64 * 1024, 512, 0) },
|
||||
|
||||
/* Micron */
|
||||
{ "n25q128", INFO(0x20ba18, 0, 64 * 1024, 256, 0) },
|
||||
{ "n25q256a", INFO(0x20ba19, 0, 64 * 1024, 512, SECT_4K) },
|
||||
|
||||
/* Spansion -- single (large) sector size only, at least
|
||||
* for the chips listed here (without boot sectors).
|
||||
*/
|
||||
{ "s25sl004a", INFO(0x010212, 0, 64 * 1024, 8, 0) },
|
||||
{ "s25sl008a", INFO(0x010213, 0, 64 * 1024, 16, 0) },
|
||||
{ "s25sl016a", INFO(0x010214, 0, 64 * 1024, 32, 0) },
|
||||
{ "s25sl032a", INFO(0x010215, 0, 64 * 1024, 64, 0) },
|
||||
{ "s25sl032p", INFO(0x010215, 0x4d00, 64 * 1024, 64, SECT_4K) },
|
||||
{ "s25sl064a", INFO(0x010216, 0, 64 * 1024, 128, 0) },
|
||||
{ "s25sl032p", INFO(0x010215, 0x4d00, 64 * 1024, 64, 0) },
|
||||
{ "s25sl064p", INFO(0x010216, 0x4d00, 64 * 1024, 128, 0) },
|
||||
{ "s25fl256s0", INFO(0x010219, 0x4d00, 256 * 1024, 128, 0) },
|
||||
{ "s25fl256s1", INFO(0x010219, 0x4d01, 64 * 1024, 512, 0) },
|
||||
{ "s25fl512s", INFO(0x010220, 0x4d00, 256 * 1024, 256, 0) },
|
||||
@ -676,6 +680,11 @@ static const struct spi_device_id m25p_ids[] = {
|
||||
{ "s25sl12801", INFO(0x012018, 0x0301, 64 * 1024, 256, 0) },
|
||||
{ "s25fl129p0", INFO(0x012018, 0x4d00, 256 * 1024, 64, 0) },
|
||||
{ "s25fl129p1", INFO(0x012018, 0x4d01, 64 * 1024, 256, 0) },
|
||||
{ "s25sl004a", INFO(0x010212, 0, 64 * 1024, 8, 0) },
|
||||
{ "s25sl008a", INFO(0x010213, 0, 64 * 1024, 16, 0) },
|
||||
{ "s25sl016a", INFO(0x010214, 0, 64 * 1024, 32, 0) },
|
||||
{ "s25sl032a", INFO(0x010215, 0, 64 * 1024, 64, 0) },
|
||||
{ "s25sl064a", INFO(0x010216, 0, 64 * 1024, 128, 0) },
|
||||
{ "s25fl016k", INFO(0xef4015, 0, 64 * 1024, 32, SECT_4K) },
|
||||
{ "s25fl064k", INFO(0xef4017, 0, 64 * 1024, 128, SECT_4K) },
|
||||
|
||||
@ -699,6 +708,7 @@ static const struct spi_device_id m25p_ids[] = {
|
||||
{ "m25p32", INFO(0x202016, 0, 64 * 1024, 64, 0) },
|
||||
{ "m25p64", INFO(0x202017, 0, 64 * 1024, 128, 0) },
|
||||
{ "m25p128", INFO(0x202018, 0, 256 * 1024, 64, 0) },
|
||||
{ "n25q032", INFO(0x20ba16, 0, 64 * 1024, 64, 0) },
|
||||
|
||||
{ "m25p05-nonjedec", INFO(0, 0, 32 * 1024, 2, 0) },
|
||||
{ "m25p10-nonjedec", INFO(0, 0, 32 * 1024, 4, 0) },
|
||||
@ -714,6 +724,7 @@ static const struct spi_device_id m25p_ids[] = {
|
||||
{ "m45pe80", INFO(0x204014, 0, 64 * 1024, 16, 0) },
|
||||
{ "m45pe16", INFO(0x204015, 0, 64 * 1024, 32, 0) },
|
||||
|
||||
{ "m25pe20", INFO(0x208012, 0, 64 * 1024, 4, 0) },
|
||||
{ "m25pe80", INFO(0x208014, 0, 64 * 1024, 16, 0) },
|
||||
{ "m25pe16", INFO(0x208015, 0, 64 * 1024, 32, SECT_4K) },
|
||||
|
||||
@ -730,6 +741,7 @@ static const struct spi_device_id m25p_ids[] = {
|
||||
{ "w25x16", INFO(0xef3015, 0, 64 * 1024, 32, SECT_4K) },
|
||||
{ "w25x32", INFO(0xef3016, 0, 64 * 1024, 64, SECT_4K) },
|
||||
{ "w25q32", INFO(0xef4016, 0, 64 * 1024, 64, SECT_4K) },
|
||||
{ "w25q32dw", INFO(0xef6016, 0, 64 * 1024, 64, SECT_4K) },
|
||||
{ "w25x64", INFO(0xef3017, 0, 64 * 1024, 128, SECT_4K) },
|
||||
{ "w25q64", INFO(0xef4017, 0, 64 * 1024, 128, SECT_4K) },
|
||||
{ "w25q80", INFO(0xef5014, 0, 64 * 1024, 16, SECT_4K) },
|
||||
|
@ -26,6 +26,7 @@
|
||||
#include <linux/module.h>
|
||||
#include <linux/param.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/pm.h>
|
||||
#include <linux/mtd/mtd.h>
|
||||
#include <linux/mtd/partitions.h>
|
||||
#include <linux/mtd/spear_smi.h>
|
||||
@ -240,8 +241,8 @@ static int spear_smi_read_sr(struct spear_smi *dev, u32 bank)
|
||||
/* copy dev->status (lower 16 bits) in order to release lock */
|
||||
if (ret > 0)
|
||||
ret = dev->status & 0xffff;
|
||||
else
|
||||
ret = -EIO;
|
||||
else if (ret == 0)
|
||||
ret = -ETIMEDOUT;
|
||||
|
||||
/* restore the ctrl regs state */
|
||||
writel(ctrlreg1, dev->io_base + SMI_CR1);
|
||||
@ -269,16 +270,19 @@ static int spear_smi_wait_till_ready(struct spear_smi *dev, u32 bank,
|
||||
finish = jiffies + timeout;
|
||||
do {
|
||||
status = spear_smi_read_sr(dev, bank);
|
||||
if (status < 0)
|
||||
continue; /* try till timeout */
|
||||
else if (!(status & SR_WIP))
|
||||
if (status < 0) {
|
||||
if (status == -ETIMEDOUT)
|
||||
continue; /* try till finish */
|
||||
return status;
|
||||
} else if (!(status & SR_WIP)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
cond_resched();
|
||||
} while (!time_after_eq(jiffies, finish));
|
||||
|
||||
dev_err(&dev->pdev->dev, "smi controller is busy, timeout\n");
|
||||
return status;
|
||||
return -EBUSY;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -335,6 +339,9 @@ static void spear_smi_hw_init(struct spear_smi *dev)
|
||||
val = HOLD1 | BANK_EN | DSEL_TIME | (prescale << 8);
|
||||
|
||||
mutex_lock(&dev->lock);
|
||||
/* clear all interrupt conditions */
|
||||
writel(0, dev->io_base + SMI_SR);
|
||||
|
||||
writel(val, dev->io_base + SMI_CR1);
|
||||
mutex_unlock(&dev->lock);
|
||||
}
|
||||
@ -391,11 +398,11 @@ static int spear_smi_write_enable(struct spear_smi *dev, u32 bank)
|
||||
writel(ctrlreg1, dev->io_base + SMI_CR1);
|
||||
writel(0, dev->io_base + SMI_CR2);
|
||||
|
||||
if (ret <= 0) {
|
||||
if (ret == 0) {
|
||||
ret = -EIO;
|
||||
dev_err(&dev->pdev->dev,
|
||||
"smi controller failed on write enable\n");
|
||||
} else {
|
||||
} else if (ret > 0) {
|
||||
/* check whether write mode status is set for required bank */
|
||||
if (dev->status & (1 << (bank + WM_SHIFT)))
|
||||
ret = 0;
|
||||
@ -462,10 +469,10 @@ static int spear_smi_erase_sector(struct spear_smi *dev,
|
||||
ret = wait_event_interruptible_timeout(dev->cmd_complete,
|
||||
dev->status & TFF, SMI_CMD_TIMEOUT);
|
||||
|
||||
if (ret <= 0) {
|
||||
if (ret == 0) {
|
||||
ret = -EIO;
|
||||
dev_err(&dev->pdev->dev, "sector erase failed\n");
|
||||
} else
|
||||
} else if (ret > 0)
|
||||
ret = 0; /* success */
|
||||
|
||||
/* restore ctrl regs */
|
||||
@ -820,7 +827,7 @@ static int spear_smi_setup_banks(struct platform_device *pdev,
|
||||
if (!flash_info)
|
||||
return -ENODEV;
|
||||
|
||||
flash = kzalloc(sizeof(*flash), GFP_ATOMIC);
|
||||
flash = devm_kzalloc(&pdev->dev, sizeof(*flash), GFP_ATOMIC);
|
||||
if (!flash)
|
||||
return -ENOMEM;
|
||||
flash->bank = bank;
|
||||
@ -831,15 +838,13 @@ static int spear_smi_setup_banks(struct platform_device *pdev,
|
||||
flash_index = spear_smi_probe_flash(dev, bank);
|
||||
if (flash_index < 0) {
|
||||
dev_info(&dev->pdev->dev, "smi-nor%d not found\n", bank);
|
||||
ret = flash_index;
|
||||
goto err_probe;
|
||||
return flash_index;
|
||||
}
|
||||
/* map the memory for nor flash chip */
|
||||
flash->base_addr = ioremap(flash_info->mem_base, flash_info->size);
|
||||
if (!flash->base_addr) {
|
||||
ret = -EIO;
|
||||
goto err_probe;
|
||||
}
|
||||
flash->base_addr = devm_ioremap(&pdev->dev, flash_info->mem_base,
|
||||
flash_info->size);
|
||||
if (!flash->base_addr)
|
||||
return -EIO;
|
||||
|
||||
dev->flash[bank] = flash;
|
||||
flash->mtd.priv = dev;
|
||||
@ -881,17 +886,10 @@ static int spear_smi_setup_banks(struct platform_device *pdev,
|
||||
count);
|
||||
if (ret) {
|
||||
dev_err(&dev->pdev->dev, "Err MTD partition=%d\n", ret);
|
||||
goto err_map;
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
err_map:
|
||||
iounmap(flash->base_addr);
|
||||
|
||||
err_probe:
|
||||
kfree(flash);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -928,20 +926,13 @@ static int __devinit spear_smi_probe(struct platform_device *pdev)
|
||||
}
|
||||
} else {
|
||||
pdata = dev_get_platdata(&pdev->dev);
|
||||
if (pdata < 0) {
|
||||
if (!pdata) {
|
||||
ret = -ENODEV;
|
||||
dev_err(&pdev->dev, "no platform data\n");
|
||||
goto err;
|
||||
}
|
||||
}
|
||||
|
||||
smi_base = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
if (!smi_base) {
|
||||
ret = -ENODEV;
|
||||
dev_err(&pdev->dev, "invalid smi base address\n");
|
||||
goto err;
|
||||
}
|
||||
|
||||
irq = platform_get_irq(pdev, 0);
|
||||
if (irq < 0) {
|
||||
ret = -ENODEV;
|
||||
@ -949,32 +940,26 @@ static int __devinit spear_smi_probe(struct platform_device *pdev)
|
||||
goto err;
|
||||
}
|
||||
|
||||
dev = kzalloc(sizeof(*dev), GFP_ATOMIC);
|
||||
dev = devm_kzalloc(&pdev->dev, sizeof(*dev), GFP_ATOMIC);
|
||||
if (!dev) {
|
||||
ret = -ENOMEM;
|
||||
dev_err(&pdev->dev, "mem alloc fail\n");
|
||||
goto err;
|
||||
}
|
||||
|
||||
smi_base = request_mem_region(smi_base->start, resource_size(smi_base),
|
||||
pdev->name);
|
||||
if (!smi_base) {
|
||||
ret = -EBUSY;
|
||||
dev_err(&pdev->dev, "request mem region fail\n");
|
||||
goto err_mem;
|
||||
}
|
||||
smi_base = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
|
||||
dev->io_base = ioremap(smi_base->start, resource_size(smi_base));
|
||||
dev->io_base = devm_request_and_ioremap(&pdev->dev, smi_base);
|
||||
if (!dev->io_base) {
|
||||
ret = -EIO;
|
||||
dev_err(&pdev->dev, "ioremap fail\n");
|
||||
goto err_ioremap;
|
||||
dev_err(&pdev->dev, "devm_request_and_ioremap fail\n");
|
||||
goto err;
|
||||
}
|
||||
|
||||
dev->pdev = pdev;
|
||||
dev->clk_rate = pdata->clk_rate;
|
||||
|
||||
if (dev->clk_rate < 0 || dev->clk_rate > SMI_MAX_CLOCK_FREQ)
|
||||
if (dev->clk_rate > SMI_MAX_CLOCK_FREQ)
|
||||
dev->clk_rate = SMI_MAX_CLOCK_FREQ;
|
||||
|
||||
dev->num_flashes = pdata->num_flashes;
|
||||
@ -984,17 +969,18 @@ static int __devinit spear_smi_probe(struct platform_device *pdev)
|
||||
dev->num_flashes = MAX_NUM_FLASH_CHIP;
|
||||
}
|
||||
|
||||
dev->clk = clk_get(&pdev->dev, NULL);
|
||||
dev->clk = devm_clk_get(&pdev->dev, NULL);
|
||||
if (IS_ERR(dev->clk)) {
|
||||
ret = PTR_ERR(dev->clk);
|
||||
goto err_clk;
|
||||
goto err;
|
||||
}
|
||||
|
||||
ret = clk_prepare_enable(dev->clk);
|
||||
if (ret)
|
||||
goto err_clk_prepare_enable;
|
||||
goto err;
|
||||
|
||||
ret = request_irq(irq, spear_smi_int_handler, 0, pdev->name, dev);
|
||||
ret = devm_request_irq(&pdev->dev, irq, spear_smi_int_handler, 0,
|
||||
pdev->name, dev);
|
||||
if (ret) {
|
||||
dev_err(&dev->pdev->dev, "SMI IRQ allocation failed\n");
|
||||
goto err_irq;
|
||||
@ -1017,18 +1003,9 @@ static int __devinit spear_smi_probe(struct platform_device *pdev)
|
||||
return 0;
|
||||
|
||||
err_bank_setup:
|
||||
free_irq(irq, dev);
|
||||
platform_set_drvdata(pdev, NULL);
|
||||
err_irq:
|
||||
clk_disable_unprepare(dev->clk);
|
||||
err_clk_prepare_enable:
|
||||
clk_put(dev->clk);
|
||||
err_clk:
|
||||
iounmap(dev->io_base);
|
||||
err_ioremap:
|
||||
release_mem_region(smi_base->start, resource_size(smi_base));
|
||||
err_mem:
|
||||
kfree(dev);
|
||||
err:
|
||||
return ret;
|
||||
}
|
||||
@ -1042,11 +1019,8 @@ err:
|
||||
static int __devexit spear_smi_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct spear_smi *dev;
|
||||
struct spear_smi_plat_data *pdata;
|
||||
struct spear_snor_flash *flash;
|
||||
struct resource *smi_base;
|
||||
int ret;
|
||||
int i, irq;
|
||||
int ret, i;
|
||||
|
||||
dev = platform_get_drvdata(pdev);
|
||||
if (!dev) {
|
||||
@ -1054,8 +1028,6 @@ static int __devexit spear_smi_remove(struct platform_device *pdev)
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
pdata = dev_get_platdata(&pdev->dev);
|
||||
|
||||
/* clean up for all nor flash */
|
||||
for (i = 0; i < dev->num_flashes; i++) {
|
||||
flash = dev->flash[i];
|
||||
@ -1066,49 +1038,41 @@ static int __devexit spear_smi_remove(struct platform_device *pdev)
|
||||
ret = mtd_device_unregister(&flash->mtd);
|
||||
if (ret)
|
||||
dev_err(&pdev->dev, "error removing mtd\n");
|
||||
|
||||
iounmap(flash->base_addr);
|
||||
kfree(flash);
|
||||
}
|
||||
|
||||
irq = platform_get_irq(pdev, 0);
|
||||
free_irq(irq, dev);
|
||||
|
||||
clk_disable_unprepare(dev->clk);
|
||||
clk_put(dev->clk);
|
||||
iounmap(dev->io_base);
|
||||
kfree(dev);
|
||||
|
||||
smi_base = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
release_mem_region(smi_base->start, resource_size(smi_base));
|
||||
platform_set_drvdata(pdev, NULL);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int spear_smi_suspend(struct platform_device *pdev, pm_message_t state)
|
||||
#ifdef CONFIG_PM
|
||||
static int spear_smi_suspend(struct device *dev)
|
||||
{
|
||||
struct spear_smi *dev = platform_get_drvdata(pdev);
|
||||
struct spear_smi *sdev = dev_get_drvdata(dev);
|
||||
|
||||
if (dev && dev->clk)
|
||||
clk_disable_unprepare(dev->clk);
|
||||
if (sdev && sdev->clk)
|
||||
clk_disable_unprepare(sdev->clk);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int spear_smi_resume(struct platform_device *pdev)
|
||||
static int spear_smi_resume(struct device *dev)
|
||||
{
|
||||
struct spear_smi *dev = platform_get_drvdata(pdev);
|
||||
struct spear_smi *sdev = dev_get_drvdata(dev);
|
||||
int ret = -EPERM;
|
||||
|
||||
if (dev && dev->clk)
|
||||
ret = clk_prepare_enable(dev->clk);
|
||||
if (sdev && sdev->clk)
|
||||
ret = clk_prepare_enable(sdev->clk);
|
||||
|
||||
if (!ret)
|
||||
spear_smi_hw_init(dev);
|
||||
spear_smi_hw_init(sdev);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static SIMPLE_DEV_PM_OPS(spear_smi_pm_ops, spear_smi_suspend, spear_smi_resume);
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_OF
|
||||
static const struct of_device_id spear_smi_id_table[] = {
|
||||
{ .compatible = "st,spear600-smi" },
|
||||
@ -1123,11 +1087,12 @@ static struct platform_driver spear_smi_driver = {
|
||||
.bus = &platform_bus_type,
|
||||
.owner = THIS_MODULE,
|
||||
.of_match_table = of_match_ptr(spear_smi_id_table),
|
||||
#ifdef CONFIG_PM
|
||||
.pm = &spear_smi_pm_ops,
|
||||
#endif
|
||||
},
|
||||
.probe = spear_smi_probe,
|
||||
.remove = __devexit_p(spear_smi_remove),
|
||||
.suspend = spear_smi_suspend,
|
||||
.resume = spear_smi_resume,
|
||||
};
|
||||
|
||||
static int spear_smi_init(void)
|
||||
|
@ -373,7 +373,7 @@ config MTD_FORTUNET
|
||||
have such a board, say 'Y'.
|
||||
|
||||
config MTD_AUTCPU12
|
||||
tristate "NV-RAM mapping AUTCPU12 board"
|
||||
bool "NV-RAM mapping AUTCPU12 board"
|
||||
depends on ARCH_AUTCPU12
|
||||
help
|
||||
This enables access to the NV-RAM on autronix autcpu12 board.
|
||||
@ -443,22 +443,10 @@ config MTD_GPIO_ADDR
|
||||
|
||||
config MTD_UCLINUX
|
||||
bool "Generic uClinux RAM/ROM filesystem support"
|
||||
depends on MTD_RAM=y && !MMU
|
||||
depends on MTD_RAM=y && (!MMU || COLDFIRE)
|
||||
help
|
||||
Map driver to support image based filesystems for uClinux.
|
||||
|
||||
config MTD_WRSBC8260
|
||||
tristate "Map driver for WindRiver PowerQUICC II MPC82xx board"
|
||||
depends on (SBC82xx || SBC8560)
|
||||
select MTD_MAP_BANK_WIDTH_4
|
||||
select MTD_MAP_BANK_WIDTH_1
|
||||
select MTD_CFI_I1
|
||||
select MTD_CFI_I4
|
||||
help
|
||||
Map driver for WindRiver PowerQUICC II MPC82xx board. Drives
|
||||
all three flash regions on CS0, CS1 and CS6 if they are configured
|
||||
correctly by the boot loader.
|
||||
|
||||
config MTD_DMV182
|
||||
tristate "Map driver for Dy-4 SVME/DMV-182 board."
|
||||
depends on DMV182
|
||||
|
@ -47,7 +47,6 @@ obj-$(CONFIG_MTD_SCB2_FLASH) += scb2_flash.o
|
||||
obj-$(CONFIG_MTD_H720X) += h720x-flash.o
|
||||
obj-$(CONFIG_MTD_IXP4XX) += ixp4xx.o
|
||||
obj-$(CONFIG_MTD_IXP2000) += ixp2000.o
|
||||
obj-$(CONFIG_MTD_WRSBC8260) += wr_sbc82xx_flash.o
|
||||
obj-$(CONFIG_MTD_DMV182) += dmv182.o
|
||||
obj-$(CONFIG_MTD_PLATRAM) += plat-ram.o
|
||||
obj-$(CONFIG_MTD_INTEL_VR_NOR) += intel_vr_nor.o
|
||||
|
@ -15,43 +15,54 @@
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*
|
||||
*/
|
||||
#include <linux/sizes.h>
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/ioport.h>
|
||||
#include <linux/init.h>
|
||||
#include <asm/io.h>
|
||||
#include <asm/sizes.h>
|
||||
#include <mach/hardware.h>
|
||||
#include <mach/autcpu12.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/platform_device.h>
|
||||
|
||||
#include <linux/mtd/mtd.h>
|
||||
#include <linux/mtd/map.h>
|
||||
#include <linux/mtd/partitions.h>
|
||||
|
||||
|
||||
static struct mtd_info *sram_mtd;
|
||||
|
||||
struct map_info autcpu12_sram_map = {
|
||||
.name = "SRAM",
|
||||
.size = 32768,
|
||||
.bankwidth = 4,
|
||||
.phys = 0x12000000,
|
||||
struct autcpu12_nvram_priv {
|
||||
struct mtd_info *mtd;
|
||||
struct map_info map;
|
||||
};
|
||||
|
||||
static int __init init_autcpu12_sram (void)
|
||||
static int __devinit autcpu12_nvram_probe(struct platform_device *pdev)
|
||||
{
|
||||
int err, save0, save1;
|
||||
map_word tmp, save0, save1;
|
||||
struct resource *res;
|
||||
struct autcpu12_nvram_priv *priv;
|
||||
|
||||
autcpu12_sram_map.virt = ioremap(0x12000000, SZ_128K);
|
||||
if (!autcpu12_sram_map.virt) {
|
||||
printk("Failed to ioremap autcpu12 NV-RAM space\n");
|
||||
err = -EIO;
|
||||
goto out;
|
||||
priv = devm_kzalloc(&pdev->dev,
|
||||
sizeof(struct autcpu12_nvram_priv), GFP_KERNEL);
|
||||
if (!priv)
|
||||
return -ENOMEM;
|
||||
|
||||
platform_set_drvdata(pdev, priv);
|
||||
|
||||
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
if (!res) {
|
||||
dev_err(&pdev->dev, "failed to get memory resource\n");
|
||||
return -ENOENT;
|
||||
}
|
||||
simple_map_init(&autcpu_sram_map);
|
||||
|
||||
priv->map.bankwidth = 4;
|
||||
priv->map.phys = res->start;
|
||||
priv->map.size = resource_size(res);
|
||||
priv->map.virt = devm_request_and_ioremap(&pdev->dev, res);
|
||||
strcpy((char *)priv->map.name, res->name);
|
||||
if (!priv->map.virt) {
|
||||
dev_err(&pdev->dev, "failed to remap mem resource\n");
|
||||
return -EBUSY;
|
||||
}
|
||||
|
||||
simple_map_init(&priv->map);
|
||||
|
||||
/*
|
||||
* Check for 32K/128K
|
||||
@ -61,65 +72,59 @@ static int __init init_autcpu12_sram (void)
|
||||
* Read and check result on ofs 0x0
|
||||
* Restore contents
|
||||
*/
|
||||
save0 = map_read32(&autcpu12_sram_map,0);
|
||||
save1 = map_read32(&autcpu12_sram_map,0x10000);
|
||||
map_write32(&autcpu12_sram_map,~save0,0x10000);
|
||||
/* if we find this pattern on 0x0, we have 32K size
|
||||
* restore contents and exit
|
||||
*/
|
||||
if ( map_read32(&autcpu12_sram_map,0) != save0) {
|
||||
map_write32(&autcpu12_sram_map,save0,0x0);
|
||||
goto map;
|
||||
}
|
||||
/* We have a 128K found, restore 0x10000 and set size
|
||||
* to 128K
|
||||
*/
|
||||
map_write32(&autcpu12_sram_map,save1,0x10000);
|
||||
autcpu12_sram_map.size = SZ_128K;
|
||||
save0 = map_read(&priv->map, 0);
|
||||
save1 = map_read(&priv->map, 0x10000);
|
||||
tmp.x[0] = ~save0.x[0];
|
||||
map_write(&priv->map, tmp, 0x10000);
|
||||
tmp = map_read(&priv->map, 0);
|
||||
/* if we find this pattern on 0x0, we have 32K size */
|
||||
if (!map_word_equal(&priv->map, tmp, save0)) {
|
||||
map_write(&priv->map, save0, 0x0);
|
||||
priv->map.size = SZ_32K;
|
||||
} else
|
||||
map_write(&priv->map, save1, 0x10000);
|
||||
|
||||
map:
|
||||
sram_mtd = do_map_probe("map_ram", &autcpu12_sram_map);
|
||||
if (!sram_mtd) {
|
||||
printk("NV-RAM probe failed\n");
|
||||
err = -ENXIO;
|
||||
goto out_ioremap;
|
||||
priv->mtd = do_map_probe("map_ram", &priv->map);
|
||||
if (!priv->mtd) {
|
||||
dev_err(&pdev->dev, "probing failed\n");
|
||||
return -ENXIO;
|
||||
}
|
||||
|
||||
sram_mtd->owner = THIS_MODULE;
|
||||
sram_mtd->erasesize = 16;
|
||||
|
||||
if (mtd_device_register(sram_mtd, NULL, 0)) {
|
||||
printk("NV-RAM device addition failed\n");
|
||||
err = -ENOMEM;
|
||||
goto out_probe;
|
||||
priv->mtd->owner = THIS_MODULE;
|
||||
priv->mtd->erasesize = 16;
|
||||
priv->mtd->dev.parent = &pdev->dev;
|
||||
if (!mtd_device_register(priv->mtd, NULL, 0)) {
|
||||
dev_info(&pdev->dev,
|
||||
"NV-RAM device size %ldKiB registered on AUTCPU12\n",
|
||||
priv->map.size / SZ_1K);
|
||||
return 0;
|
||||
}
|
||||
|
||||
printk("NV-RAM device size %ldKiB registered on AUTCPU12\n",autcpu12_sram_map.size/SZ_1K);
|
||||
map_destroy(priv->mtd);
|
||||
dev_err(&pdev->dev, "NV-RAM device addition failed\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
static int __devexit autcpu12_nvram_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct autcpu12_nvram_priv *priv = platform_get_drvdata(pdev);
|
||||
|
||||
mtd_device_unregister(priv->mtd);
|
||||
map_destroy(priv->mtd);
|
||||
|
||||
return 0;
|
||||
|
||||
out_probe:
|
||||
map_destroy(sram_mtd);
|
||||
sram_mtd = 0;
|
||||
|
||||
out_ioremap:
|
||||
iounmap((void *)autcpu12_sram_map.virt);
|
||||
out:
|
||||
return err;
|
||||
}
|
||||
|
||||
static void __exit cleanup_autcpu12_maps(void)
|
||||
{
|
||||
if (sram_mtd) {
|
||||
mtd_device_unregister(sram_mtd);
|
||||
map_destroy(sram_mtd);
|
||||
iounmap((void *)autcpu12_sram_map.virt);
|
||||
}
|
||||
}
|
||||
|
||||
module_init(init_autcpu12_sram);
|
||||
module_exit(cleanup_autcpu12_maps);
|
||||
static struct platform_driver autcpu12_nvram_driver = {
|
||||
.driver = {
|
||||
.name = "autcpu12_nvram",
|
||||
.owner = THIS_MODULE,
|
||||
},
|
||||
.probe = autcpu12_nvram_probe,
|
||||
.remove = __devexit_p(autcpu12_nvram_remove),
|
||||
};
|
||||
module_platform_driver(autcpu12_nvram_driver);
|
||||
|
||||
MODULE_AUTHOR("Thomas Gleixner");
|
||||
MODULE_DESCRIPTION("autcpu12 NV-RAM map driver");
|
||||
MODULE_DESCRIPTION("autcpu12 NVRAM map driver");
|
||||
MODULE_LICENSE("GPL");
|
||||
|
@ -43,26 +43,14 @@ static map_word mtd_pci_read8(struct map_info *_map, unsigned long ofs)
|
||||
struct map_pci_info *map = (struct map_pci_info *)_map;
|
||||
map_word val;
|
||||
val.x[0]= readb(map->base + map->translate(map, ofs));
|
||||
// printk("read8 : %08lx => %02x\n", ofs, val.x[0]);
|
||||
return val;
|
||||
}
|
||||
|
||||
#if 0
|
||||
static map_word mtd_pci_read16(struct map_info *_map, unsigned long ofs)
|
||||
{
|
||||
struct map_pci_info *map = (struct map_pci_info *)_map;
|
||||
map_word val;
|
||||
val.x[0] = readw(map->base + map->translate(map, ofs));
|
||||
// printk("read16: %08lx => %04x\n", ofs, val.x[0]);
|
||||
return val;
|
||||
}
|
||||
#endif
|
||||
static map_word mtd_pci_read32(struct map_info *_map, unsigned long ofs)
|
||||
{
|
||||
struct map_pci_info *map = (struct map_pci_info *)_map;
|
||||
map_word val;
|
||||
val.x[0] = readl(map->base + map->translate(map, ofs));
|
||||
// printk("read32: %08lx => %08x\n", ofs, val.x[0]);
|
||||
return val;
|
||||
}
|
||||
|
||||
@ -75,22 +63,12 @@ static void mtd_pci_copyfrom(struct map_info *_map, void *to, unsigned long from
|
||||
static void mtd_pci_write8(struct map_info *_map, map_word val, unsigned long ofs)
|
||||
{
|
||||
struct map_pci_info *map = (struct map_pci_info *)_map;
|
||||
// printk("write8 : %08lx <= %02x\n", ofs, val.x[0]);
|
||||
writeb(val.x[0], map->base + map->translate(map, ofs));
|
||||
}
|
||||
|
||||
#if 0
|
||||
static void mtd_pci_write16(struct map_info *_map, map_word val, unsigned long ofs)
|
||||
{
|
||||
struct map_pci_info *map = (struct map_pci_info *)_map;
|
||||
// printk("write16: %08lx <= %04x\n", ofs, val.x[0]);
|
||||
writew(val.x[0], map->base + map->translate(map, ofs));
|
||||
}
|
||||
#endif
|
||||
static void mtd_pci_write32(struct map_info *_map, map_word val, unsigned long ofs)
|
||||
{
|
||||
struct map_pci_info *map = (struct map_pci_info *)_map;
|
||||
// printk("write32: %08lx <= %08x\n", ofs, val.x[0]);
|
||||
writel(val.x[0], map->base + map->translate(map, ofs));
|
||||
}
|
||||
|
||||
@ -358,4 +336,3 @@ MODULE_LICENSE("GPL");
|
||||
MODULE_AUTHOR("Russell King <rmk@arm.linux.org.uk>");
|
||||
MODULE_DESCRIPTION("Generic PCI map driver");
|
||||
MODULE_DEVICE_TABLE(pci, mtd_pci_ids);
|
||||
|
||||
|
@ -169,6 +169,7 @@ static int __devinit of_flash_probe(struct platform_device *dev)
|
||||
struct mtd_info **mtd_list = NULL;
|
||||
resource_size_t res_size;
|
||||
struct mtd_part_parser_data ppdata;
|
||||
bool map_indirect;
|
||||
|
||||
match = of_match_device(of_flash_match, &dev->dev);
|
||||
if (!match)
|
||||
@ -192,6 +193,8 @@ static int __devinit of_flash_probe(struct platform_device *dev)
|
||||
}
|
||||
count /= reg_tuple_size;
|
||||
|
||||
map_indirect = of_property_read_bool(dp, "no-unaligned-direct-access");
|
||||
|
||||
err = -ENOMEM;
|
||||
info = kzalloc(sizeof(struct of_flash) +
|
||||
sizeof(struct of_flash_list) * count, GFP_KERNEL);
|
||||
@ -247,6 +250,17 @@ static int __devinit of_flash_probe(struct platform_device *dev)
|
||||
|
||||
simple_map_init(&info->list[i].map);
|
||||
|
||||
/*
|
||||
* On some platforms (e.g. MPC5200) a direct 1:1 mapping
|
||||
* may cause problems with JFFS2 usage, as the local bus (LPB)
|
||||
* doesn't support unaligned accesses as implemented in the
|
||||
* JFFS2 code via memcpy(). By setting NO_XIP, the
|
||||
* flash will not be exposed directly to the MTD users
|
||||
* (e.g. JFFS2) any more.
|
||||
*/
|
||||
if (map_indirect)
|
||||
info->list[i].map.phys = NO_XIP;
|
||||
|
||||
if (probe_type) {
|
||||
info->list[i].mtd = do_map_probe(probe_type,
|
||||
&info->list[i].map);
|
||||
|
@ -100,8 +100,6 @@ static int rbtx4939_flash_probe(struct platform_device *dev)
|
||||
goto err_out;
|
||||
}
|
||||
info->mtd->owner = THIS_MODULE;
|
||||
if (err)
|
||||
goto err_out;
|
||||
err = mtd_device_parse_register(info->mtd, NULL, NULL, pdata->parts,
|
||||
pdata->nr_parts);
|
||||
|
||||
|
@ -67,10 +67,16 @@ static int __init uclinux_mtd_init(void)
|
||||
printk("uclinux[mtd]: RAM probe address=0x%x size=0x%x\n",
|
||||
(int) mapp->phys, (int) mapp->size);
|
||||
|
||||
mapp->virt = ioremap_nocache(mapp->phys, mapp->size);
|
||||
/*
|
||||
* The filesystem is guaranteed to be in direct mapped memory. It is
|
||||
* directly following the kernels own bss region. Following the same
|
||||
* mechanism used by architectures setting up traditional initrds we
|
||||
* use phys_to_virt to get the virtual address of its start.
|
||||
*/
|
||||
mapp->virt = phys_to_virt(mapp->phys);
|
||||
|
||||
if (mapp->virt == 0) {
|
||||
printk("uclinux[mtd]: ioremap_nocache() failed\n");
|
||||
printk("uclinux[mtd]: no virtual mapping?\n");
|
||||
return(-EIO);
|
||||
}
|
||||
|
||||
@ -79,7 +85,6 @@ static int __init uclinux_mtd_init(void)
|
||||
mtd = do_map_probe("map_ram", mapp);
|
||||
if (!mtd) {
|
||||
printk("uclinux[mtd]: failed to find a mapping?\n");
|
||||
iounmap(mapp->virt);
|
||||
return(-ENXIO);
|
||||
}
|
||||
|
||||
@ -102,10 +107,8 @@ static void __exit uclinux_mtd_cleanup(void)
|
||||
map_destroy(uclinux_ram_mtdinfo);
|
||||
uclinux_ram_mtdinfo = NULL;
|
||||
}
|
||||
if (uclinux_ram_map.virt) {
|
||||
iounmap((void *) uclinux_ram_map.virt);
|
||||
if (uclinux_ram_map.virt)
|
||||
uclinux_ram_map.virt = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/****************************************************************************/
|
||||
|
@ -1,174 +0,0 @@
|
||||
/*
|
||||
* Map for flash chips on Wind River PowerQUICC II SBC82xx board.
|
||||
*
|
||||
* Copyright (C) 2004 Red Hat, Inc.
|
||||
*
|
||||
* Author: David Woodhouse <dwmw2@infradead.org>
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/slab.h>
|
||||
#include <asm/io.h>
|
||||
#include <linux/mtd/mtd.h>
|
||||
#include <linux/mtd/map.h>
|
||||
#include <linux/mtd/partitions.h>
|
||||
|
||||
#include <asm/immap_cpm2.h>
|
||||
|
||||
static struct mtd_info *sbcmtd[3];
|
||||
|
||||
struct map_info sbc82xx_flash_map[3] = {
|
||||
{.name = "Boot flash"},
|
||||
{.name = "Alternate boot flash"},
|
||||
{.name = "User flash"}
|
||||
};
|
||||
|
||||
static struct mtd_partition smallflash_parts[] = {
|
||||
{
|
||||
.name = "space",
|
||||
.size = 0x100000,
|
||||
.offset = 0,
|
||||
}, {
|
||||
.name = "bootloader",
|
||||
.size = MTDPART_SIZ_FULL,
|
||||
.offset = MTDPART_OFS_APPEND,
|
||||
}
|
||||
};
|
||||
|
||||
static struct mtd_partition bigflash_parts[] = {
|
||||
{
|
||||
.name = "bootloader",
|
||||
.size = 0x00100000,
|
||||
.offset = 0,
|
||||
}, {
|
||||
.name = "file system",
|
||||
.size = 0x01f00000,
|
||||
.offset = MTDPART_OFS_APPEND,
|
||||
}, {
|
||||
.name = "boot config",
|
||||
.size = 0x00100000,
|
||||
.offset = MTDPART_OFS_APPEND,
|
||||
}, {
|
||||
.name = "space",
|
||||
.size = 0x01f00000,
|
||||
.offset = MTDPART_OFS_APPEND,
|
||||
}
|
||||
};
|
||||
|
||||
static const char *part_probes[] __initconst = {"cmdlinepart", "RedBoot", NULL};
|
||||
|
||||
#define init_sbc82xx_one_flash(map, br, or) \
|
||||
do { \
|
||||
(map).phys = (br & 1) ? (br & 0xffff8000) : 0; \
|
||||
(map).size = (br & 1) ? (~(or & 0xffff8000) + 1) : 0; \
|
||||
switch (br & 0x00001800) { \
|
||||
case 0x00000000: \
|
||||
case 0x00000800: (map).bankwidth = 1; break; \
|
||||
case 0x00001000: (map).bankwidth = 2; break; \
|
||||
case 0x00001800: (map).bankwidth = 4; break; \
|
||||
} \
|
||||
} while (0);
|
||||
|
||||
static int __init init_sbc82xx_flash(void)
|
||||
{
|
||||
volatile memctl_cpm2_t *mc = &cpm2_immr->im_memctl;
|
||||
int bigflash;
|
||||
int i;
|
||||
|
||||
#ifdef CONFIG_SBC8560
|
||||
mc = ioremap(0xff700000 + 0x5000, sizeof(memctl_cpm2_t));
|
||||
#else
|
||||
mc = &cpm2_immr->im_memctl;
|
||||
#endif
|
||||
|
||||
bigflash = 1;
|
||||
if ((mc->memc_br0 & 0x00001800) == 0x00001800)
|
||||
bigflash = 0;
|
||||
|
||||
init_sbc82xx_one_flash(sbc82xx_flash_map[0], mc->memc_br0, mc->memc_or0);
|
||||
init_sbc82xx_one_flash(sbc82xx_flash_map[1], mc->memc_br6, mc->memc_or6);
|
||||
init_sbc82xx_one_flash(sbc82xx_flash_map[2], mc->memc_br1, mc->memc_or1);
|
||||
|
||||
#ifdef CONFIG_SBC8560
|
||||
iounmap((void *) mc);
|
||||
#endif
|
||||
|
||||
for (i=0; i<3; i++) {
|
||||
int8_t flashcs[3] = { 0, 6, 1 };
|
||||
int nr_parts;
|
||||
struct mtd_partition *defparts;
|
||||
|
||||
printk(KERN_NOTICE "PowerQUICC II %s (%ld MiB on CS%d",
|
||||
sbc82xx_flash_map[i].name,
|
||||
(sbc82xx_flash_map[i].size >> 20),
|
||||
flashcs[i]);
|
||||
if (!sbc82xx_flash_map[i].phys) {
|
||||
/* We know it can't be at zero. */
|
||||
printk("): disabled by bootloader.\n");
|
||||
continue;
|
||||
}
|
||||
printk(" at %08lx)\n", sbc82xx_flash_map[i].phys);
|
||||
|
||||
sbc82xx_flash_map[i].virt = ioremap(sbc82xx_flash_map[i].phys,
|
||||
sbc82xx_flash_map[i].size);
|
||||
|
||||
if (!sbc82xx_flash_map[i].virt) {
|
||||
printk("Failed to ioremap\n");
|
||||
continue;
|
||||
}
|
||||
|
||||
simple_map_init(&sbc82xx_flash_map[i]);
|
||||
|
||||
sbcmtd[i] = do_map_probe("cfi_probe", &sbc82xx_flash_map[i]);
|
||||
|
||||
if (!sbcmtd[i])
|
||||
continue;
|
||||
|
||||
sbcmtd[i]->owner = THIS_MODULE;
|
||||
|
||||
/* No partitioning detected. Use default */
|
||||
if (i == 2) {
|
||||
defparts = NULL;
|
||||
nr_parts = 0;
|
||||
} else if (i == bigflash) {
|
||||
defparts = bigflash_parts;
|
||||
nr_parts = ARRAY_SIZE(bigflash_parts);
|
||||
} else {
|
||||
defparts = smallflash_parts;
|
||||
nr_parts = ARRAY_SIZE(smallflash_parts);
|
||||
}
|
||||
|
||||
mtd_device_parse_register(sbcmtd[i], part_probes, NULL,
|
||||
defparts, nr_parts);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void __exit cleanup_sbc82xx_flash(void)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i=0; i<3; i++) {
|
||||
if (!sbcmtd[i])
|
||||
continue;
|
||||
|
||||
mtd_device_unregister(sbcmtd[i]);
|
||||
|
||||
map_destroy(sbcmtd[i]);
|
||||
|
||||
iounmap((void *)sbc82xx_flash_map[i].virt);
|
||||
sbc82xx_flash_map[i].virt = 0;
|
||||
}
|
||||
}
|
||||
|
||||
module_init(init_sbc82xx_flash);
|
||||
module_exit(cleanup_sbc82xx_flash);
|
||||
|
||||
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_AUTHOR("David Woodhouse <dwmw2@infradead.org>");
|
||||
MODULE_DESCRIPTION("Flash map driver for WindRiver PowerQUICC II");
|
@ -1162,7 +1162,11 @@ static int mtdchar_mmap(struct file *file, struct vm_area_struct *vma)
|
||||
resource_size_t start, off;
|
||||
unsigned long len, vma_len;
|
||||
|
||||
if (mtd->type == MTD_RAM || mtd->type == MTD_ROM) {
|
||||
/* This is broken because it assumes the MTD device is map-based
|
||||
and that mtd->priv is a valid struct map_info. It should be
|
||||
replaced with something that uses the mtd_get_unmapped_area()
|
||||
operation properly. */
|
||||
if (0 /*mtd->type == MTD_RAM || mtd->type == MTD_ROM*/) {
|
||||
off = get_vm_offset(vma);
|
||||
start = map->phys;
|
||||
len = PAGE_ALIGN((start & ~PAGE_MASK) + map->size);
|
||||
|
@ -858,6 +858,27 @@ int mtd_panic_write(struct mtd_info *mtd, loff_t to, size_t len, size_t *retlen,
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(mtd_panic_write);
|
||||
|
||||
int mtd_read_oob(struct mtd_info *mtd, loff_t from, struct mtd_oob_ops *ops)
|
||||
{
|
||||
int ret_code;
|
||||
ops->retlen = ops->oobretlen = 0;
|
||||
if (!mtd->_read_oob)
|
||||
return -EOPNOTSUPP;
|
||||
/*
|
||||
* In cases where ops->datbuf != NULL, mtd->_read_oob() has semantics
|
||||
* similar to mtd->_read(), returning a non-negative integer
|
||||
* representing max bitflips. In other cases, mtd->_read_oob() may
|
||||
* return -EUCLEAN. In all cases, perform similar logic to mtd_read().
|
||||
*/
|
||||
ret_code = mtd->_read_oob(mtd, from, ops);
|
||||
if (unlikely(ret_code < 0))
|
||||
return ret_code;
|
||||
if (mtd->ecc_strength == 0)
|
||||
return 0; /* device lacks ecc */
|
||||
return ret_code >= mtd->bitflip_threshold ? -EUCLEAN : 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(mtd_read_oob);
|
||||
|
||||
/*
|
||||
* Method to access the protection register area, present in some flash
|
||||
* devices. The user data is one time programmable but the factory data is read
|
||||
|
@ -169,14 +169,7 @@ static void mtdoops_workfunc_erase(struct work_struct *work)
|
||||
cxt->nextpage = 0;
|
||||
}
|
||||
|
||||
while (1) {
|
||||
ret = mtd_block_isbad(mtd, cxt->nextpage * record_size);
|
||||
if (!ret)
|
||||
break;
|
||||
if (ret < 0) {
|
||||
printk(KERN_ERR "mtdoops: block_isbad failed, aborting\n");
|
||||
return;
|
||||
}
|
||||
while ((ret = mtd_block_isbad(mtd, cxt->nextpage * record_size)) > 0) {
|
||||
badblock:
|
||||
printk(KERN_WARNING "mtdoops: bad block at %08lx\n",
|
||||
cxt->nextpage * record_size);
|
||||
@ -190,6 +183,11 @@ badblock:
|
||||
}
|
||||
}
|
||||
|
||||
if (ret < 0) {
|
||||
printk(KERN_ERR "mtdoops: mtd_block_isbad failed, aborting\n");
|
||||
return;
|
||||
}
|
||||
|
||||
for (j = 0, ret = -1; (j < 3) && (ret < 0); j++)
|
||||
ret = mtdoops_erase_block(cxt, cxt->nextpage * record_size);
|
||||
|
||||
|
@ -711,6 +711,8 @@ static const char *default_mtd_part_types[] = {
|
||||
* partition parsers, specified in @types. However, if @types is %NULL, then
|
||||
* the default list of parsers is used. The default list contains only the
|
||||
* "cmdlinepart" and "ofpart" parsers ATM.
|
||||
* Note: If there are more then one parser in @types, the kernel only takes the
|
||||
* partitions parsed out by the first parser.
|
||||
*
|
||||
* This function may return:
|
||||
* o a negative error code in case of failure
|
||||
@ -735,11 +737,12 @@ int parse_mtd_partitions(struct mtd_info *master, const char **types,
|
||||
if (!parser)
|
||||
continue;
|
||||
ret = (*parser->parse_fn)(master, pparts, data);
|
||||
put_partition_parser(parser);
|
||||
if (ret > 0) {
|
||||
printk(KERN_NOTICE "%d %s partitions found on MTD device %s\n",
|
||||
ret, parser->name, master->name);
|
||||
break;
|
||||
}
|
||||
put_partition_parser(parser);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
@ -22,15 +22,6 @@ menuconfig MTD_NAND
|
||||
|
||||
if MTD_NAND
|
||||
|
||||
config MTD_NAND_VERIFY_WRITE
|
||||
bool "Verify NAND page writes"
|
||||
help
|
||||
This adds an extra check when data is written to the flash. The
|
||||
NAND flash device internally checks only bits transitioning
|
||||
from 1 to 0. There is a rare possibility that even though the
|
||||
device thinks the write was successful, a bit could have been
|
||||
flipped accidentally due to device wear or something else.
|
||||
|
||||
config MTD_NAND_BCH
|
||||
tristate
|
||||
select BCH
|
||||
@ -267,22 +258,6 @@ config MTD_NAND_S3C2410_CLKSTOP
|
||||
when the is NAND chip selected or released, but will save
|
||||
approximately 5mA of power when there is nothing happening.
|
||||
|
||||
config MTD_NAND_BCM_UMI
|
||||
tristate "NAND Flash support for BCM Reference Boards"
|
||||
depends on ARCH_BCMRING
|
||||
help
|
||||
This enables the NAND flash controller on the BCM UMI block.
|
||||
|
||||
No board specific support is done by this driver, each board
|
||||
must advertise a platform_device for the driver to attach.
|
||||
|
||||
config MTD_NAND_BCM_UMI_HWCS
|
||||
bool "BCM UMI NAND Hardware CS"
|
||||
depends on MTD_NAND_BCM_UMI
|
||||
help
|
||||
Enable the use of the BCM UMI block's internal CS using NAND.
|
||||
This should only be used if you know the external NAND CS can toggle.
|
||||
|
||||
config MTD_NAND_DISKONCHIP
|
||||
tristate "DiskOnChip 2000, Millennium and Millennium Plus (NAND reimplementation) (EXPERIMENTAL)"
|
||||
depends on EXPERIMENTAL
|
||||
@ -356,7 +331,7 @@ config MTD_NAND_DISKONCHIP_BBTWRITE
|
||||
|
||||
config MTD_NAND_DOCG4
|
||||
tristate "Support for DiskOnChip G4 (EXPERIMENTAL)"
|
||||
depends on EXPERIMENTAL
|
||||
depends on EXPERIMENTAL && HAS_IOMEM
|
||||
select BCH
|
||||
select BITREVERSE
|
||||
help
|
||||
@ -414,6 +389,28 @@ config MTD_NAND_PXA3xx
|
||||
This enables the driver for the NAND flash device found on
|
||||
PXA3xx processors
|
||||
|
||||
config MTD_NAND_SLC_LPC32XX
|
||||
tristate "NXP LPC32xx SLC Controller"
|
||||
depends on ARCH_LPC32XX
|
||||
help
|
||||
Enables support for NXP's LPC32XX SLC (i.e. for Single Level Cell
|
||||
chips) NAND controller. This is the default for the PHYTEC 3250
|
||||
reference board which contains a NAND256R3A2CZA6 chip.
|
||||
|
||||
Please check the actual NAND chip connected and its support
|
||||
by the SLC NAND controller.
|
||||
|
||||
config MTD_NAND_MLC_LPC32XX
|
||||
tristate "NXP LPC32xx MLC Controller"
|
||||
depends on ARCH_LPC32XX
|
||||
help
|
||||
Uses the LPC32XX MLC (i.e. for Multi Level Cell chips) NAND
|
||||
controller. This is the default for the WORK92105 controller
|
||||
board.
|
||||
|
||||
Please check the actual NAND chip connected and its support
|
||||
by the MLC NAND controller.
|
||||
|
||||
config MTD_NAND_CM_X270
|
||||
tristate "Support for NAND Flash on CM-X270 modules"
|
||||
depends on MACH_ARMCORE
|
||||
@ -439,10 +436,10 @@ config MTD_NAND_NANDSIM
|
||||
MTD nand layer.
|
||||
|
||||
config MTD_NAND_GPMI_NAND
|
||||
bool "GPMI NAND Flash Controller driver"
|
||||
tristate "GPMI NAND Flash Controller driver"
|
||||
depends on MTD_NAND && MXS_DMA
|
||||
help
|
||||
Enables NAND Flash support for IMX23 or IMX28.
|
||||
Enables NAND Flash support for IMX23, IMX28 or IMX6.
|
||||
The GPMI controller is very powerful, with the help of BCH
|
||||
module, it can do the hardware ECC. The GPMI supports several
|
||||
NAND flashs at the same time. The GPMI may conflicts with other
|
||||
@ -510,7 +507,7 @@ config MTD_NAND_MPC5121_NFC
|
||||
|
||||
config MTD_NAND_MXC
|
||||
tristate "MXC NAND support"
|
||||
depends on IMX_HAVE_PLATFORM_MXC_NAND
|
||||
depends on ARCH_MXC
|
||||
help
|
||||
This enables the driver for the NAND flash controller on the
|
||||
MXC processors.
|
||||
@ -567,4 +564,12 @@ config MTD_NAND_FSMC
|
||||
Enables support for NAND Flash chips on the ST Microelectronics
|
||||
Flexible Static Memory Controller (FSMC)
|
||||
|
||||
config MTD_NAND_XWAY
|
||||
tristate "Support for NAND on Lantiq XWAY SoC"
|
||||
depends on LANTIQ && SOC_TYPE_XWAY
|
||||
select MTD_NAND_PLATFORM
|
||||
help
|
||||
Enables support for NAND Flash chips on Lantiq XWAY SoCs. NAND is attached
|
||||
to the External Bus Unit (EBU).
|
||||
|
||||
endif # MTD_NAND
|
||||
|
@ -40,16 +40,18 @@ obj-$(CONFIG_MTD_NAND_ORION) += orion_nand.o
|
||||
obj-$(CONFIG_MTD_NAND_FSL_ELBC) += fsl_elbc_nand.o
|
||||
obj-$(CONFIG_MTD_NAND_FSL_IFC) += fsl_ifc_nand.o
|
||||
obj-$(CONFIG_MTD_NAND_FSL_UPM) += fsl_upm.o
|
||||
obj-$(CONFIG_MTD_NAND_SLC_LPC32XX) += lpc32xx_slc.o
|
||||
obj-$(CONFIG_MTD_NAND_MLC_LPC32XX) += lpc32xx_mlc.o
|
||||
obj-$(CONFIG_MTD_NAND_SH_FLCTL) += sh_flctl.o
|
||||
obj-$(CONFIG_MTD_NAND_MXC) += mxc_nand.o
|
||||
obj-$(CONFIG_MTD_NAND_SOCRATES) += socrates_nand.o
|
||||
obj-$(CONFIG_MTD_NAND_TXX9NDFMC) += txx9ndfmc.o
|
||||
obj-$(CONFIG_MTD_NAND_NUC900) += nuc900_nand.o
|
||||
obj-$(CONFIG_MTD_NAND_NOMADIK) += nomadik_nand.o
|
||||
obj-$(CONFIG_MTD_NAND_BCM_UMI) += bcm_umi_nand.o nand_bcm_umi.o
|
||||
obj-$(CONFIG_MTD_NAND_MPC5121_NFC) += mpc5121_nfc.o
|
||||
obj-$(CONFIG_MTD_NAND_RICOH) += r852.o
|
||||
obj-$(CONFIG_MTD_NAND_JZ4740) += jz4740_nand.o
|
||||
obj-$(CONFIG_MTD_NAND_GPMI_NAND) += gpmi-nand/
|
||||
obj-$(CONFIG_MTD_NAND_XWAY) += xway_nand.o
|
||||
|
||||
nand-objs := nand_base.o nand_bbt.o
|
||||
|
@ -107,18 +107,6 @@ static void ams_delta_read_buf(struct mtd_info *mtd, u_char *buf, int len)
|
||||
buf[i] = ams_delta_read_byte(mtd);
|
||||
}
|
||||
|
||||
static int ams_delta_verify_buf(struct mtd_info *mtd, const u_char *buf,
|
||||
int len)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i=0; i<len; i++)
|
||||
if (buf[i] != ams_delta_read_byte(mtd))
|
||||
return -EFAULT;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Command control function
|
||||
*
|
||||
@ -237,7 +225,6 @@ static int __devinit ams_delta_init(struct platform_device *pdev)
|
||||
this->read_byte = ams_delta_read_byte;
|
||||
this->write_buf = ams_delta_write_buf;
|
||||
this->read_buf = ams_delta_read_buf;
|
||||
this->verify_buf = ams_delta_verify_buf;
|
||||
this->cmd_ctrl = ams_delta_hwcontrol;
|
||||
if (gpio_request(AMS_DELTA_GPIO_PIN_NAND_RB, "nand_rdy") == 0) {
|
||||
this->dev_ready = ams_delta_nand_ready;
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -3,7 +3,7 @@
|
||||
* Based on AT91SAM9260 datasheet revision B.
|
||||
*
|
||||
* Copyright (C) 2007 Andrew Victor
|
||||
* Copyright (C) 2007 Atmel Corporation.
|
||||
* Copyright (C) 2007 - 2012 Atmel Corporation.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the
|
||||
@ -36,4 +36,116 @@
|
||||
#define ATMEL_ECC_NPR 0x10 /* NParity register */
|
||||
#define ATMEL_ECC_NPARITY (0xffff << 0) /* NParity */
|
||||
|
||||
/* PMECC Register Definitions */
|
||||
#define ATMEL_PMECC_CFG 0x000 /* Configuration Register */
|
||||
#define PMECC_CFG_BCH_ERR2 (0 << 0)
|
||||
#define PMECC_CFG_BCH_ERR4 (1 << 0)
|
||||
#define PMECC_CFG_BCH_ERR8 (2 << 0)
|
||||
#define PMECC_CFG_BCH_ERR12 (3 << 0)
|
||||
#define PMECC_CFG_BCH_ERR24 (4 << 0)
|
||||
|
||||
#define PMECC_CFG_SECTOR512 (0 << 4)
|
||||
#define PMECC_CFG_SECTOR1024 (1 << 4)
|
||||
|
||||
#define PMECC_CFG_PAGE_1SECTOR (0 << 8)
|
||||
#define PMECC_CFG_PAGE_2SECTORS (1 << 8)
|
||||
#define PMECC_CFG_PAGE_4SECTORS (2 << 8)
|
||||
#define PMECC_CFG_PAGE_8SECTORS (3 << 8)
|
||||
|
||||
#define PMECC_CFG_READ_OP (0 << 12)
|
||||
#define PMECC_CFG_WRITE_OP (1 << 12)
|
||||
|
||||
#define PMECC_CFG_SPARE_ENABLE (1 << 16)
|
||||
#define PMECC_CFG_SPARE_DISABLE (0 << 16)
|
||||
|
||||
#define PMECC_CFG_AUTO_ENABLE (1 << 20)
|
||||
#define PMECC_CFG_AUTO_DISABLE (0 << 20)
|
||||
|
||||
#define ATMEL_PMECC_SAREA 0x004 /* Spare area size */
|
||||
#define ATMEL_PMECC_SADDR 0x008 /* PMECC starting address */
|
||||
#define ATMEL_PMECC_EADDR 0x00c /* PMECC ending address */
|
||||
#define ATMEL_PMECC_CLK 0x010 /* PMECC clock control */
|
||||
#define PMECC_CLK_133MHZ (2 << 0)
|
||||
|
||||
#define ATMEL_PMECC_CTRL 0x014 /* PMECC control register */
|
||||
#define PMECC_CTRL_RST (1 << 0)
|
||||
#define PMECC_CTRL_DATA (1 << 1)
|
||||
#define PMECC_CTRL_USER (1 << 2)
|
||||
#define PMECC_CTRL_ENABLE (1 << 4)
|
||||
#define PMECC_CTRL_DISABLE (1 << 5)
|
||||
|
||||
#define ATMEL_PMECC_SR 0x018 /* PMECC status register */
|
||||
#define PMECC_SR_BUSY (1 << 0)
|
||||
#define PMECC_SR_ENABLE (1 << 4)
|
||||
|
||||
#define ATMEL_PMECC_IER 0x01c /* PMECC interrupt enable */
|
||||
#define PMECC_IER_ENABLE (1 << 0)
|
||||
#define ATMEL_PMECC_IDR 0x020 /* PMECC interrupt disable */
|
||||
#define PMECC_IER_DISABLE (1 << 0)
|
||||
#define ATMEL_PMECC_IMR 0x024 /* PMECC interrupt mask */
|
||||
#define PMECC_IER_MASK (1 << 0)
|
||||
#define ATMEL_PMECC_ISR 0x028 /* PMECC interrupt status */
|
||||
#define ATMEL_PMECC_ECCx 0x040 /* PMECC ECC x */
|
||||
#define ATMEL_PMECC_REMx 0x240 /* PMECC REM x */
|
||||
|
||||
/* PMERRLOC Register Definitions */
|
||||
#define ATMEL_PMERRLOC_ELCFG 0x000 /* Error location config */
|
||||
#define PMERRLOC_ELCFG_SECTOR_512 (0 << 0)
|
||||
#define PMERRLOC_ELCFG_SECTOR_1024 (1 << 0)
|
||||
#define PMERRLOC_ELCFG_NUM_ERRORS(n) ((n) << 16)
|
||||
|
||||
#define ATMEL_PMERRLOC_ELPRIM 0x004 /* Error location primitive */
|
||||
#define ATMEL_PMERRLOC_ELEN 0x008 /* Error location enable */
|
||||
#define ATMEL_PMERRLOC_ELDIS 0x00c /* Error location disable */
|
||||
#define PMERRLOC_DISABLE (1 << 0)
|
||||
|
||||
#define ATMEL_PMERRLOC_ELSR 0x010 /* Error location status */
|
||||
#define PMERRLOC_ELSR_BUSY (1 << 0)
|
||||
#define ATMEL_PMERRLOC_ELIER 0x014 /* Error location int enable */
|
||||
#define ATMEL_PMERRLOC_ELIDR 0x018 /* Error location int disable */
|
||||
#define ATMEL_PMERRLOC_ELIMR 0x01c /* Error location int mask */
|
||||
#define ATMEL_PMERRLOC_ELISR 0x020 /* Error location int status */
|
||||
#define PMERRLOC_ERR_NUM_MASK (0x1f << 8)
|
||||
#define PMERRLOC_CALC_DONE (1 << 0)
|
||||
#define ATMEL_PMERRLOC_SIGMAx 0x028 /* Error location SIGMA x */
|
||||
#define ATMEL_PMERRLOC_ELx 0x08c /* Error location x */
|
||||
|
||||
/* Register access macros for PMECC */
|
||||
#define pmecc_readl_relaxed(addr, reg) \
|
||||
readl_relaxed((addr) + ATMEL_PMECC_##reg)
|
||||
|
||||
#define pmecc_writel(addr, reg, value) \
|
||||
writel((value), (addr) + ATMEL_PMECC_##reg)
|
||||
|
||||
#define pmecc_readb_ecc_relaxed(addr, sector, n) \
|
||||
readb_relaxed((addr) + ATMEL_PMECC_ECCx + ((sector) * 0x40) + (n))
|
||||
|
||||
#define pmecc_readl_rem_relaxed(addr, sector, n) \
|
||||
readl_relaxed((addr) + ATMEL_PMECC_REMx + ((sector) * 0x40) + ((n) * 4))
|
||||
|
||||
#define pmerrloc_readl_relaxed(addr, reg) \
|
||||
readl_relaxed((addr) + ATMEL_PMERRLOC_##reg)
|
||||
|
||||
#define pmerrloc_writel(addr, reg, value) \
|
||||
writel((value), (addr) + ATMEL_PMERRLOC_##reg)
|
||||
|
||||
#define pmerrloc_writel_sigma_relaxed(addr, n, value) \
|
||||
writel_relaxed((value), (addr) + ATMEL_PMERRLOC_SIGMAx + ((n) * 4))
|
||||
|
||||
#define pmerrloc_readl_sigma_relaxed(addr, n) \
|
||||
readl_relaxed((addr) + ATMEL_PMERRLOC_SIGMAx + ((n) * 4))
|
||||
|
||||
#define pmerrloc_readl_el_relaxed(addr, n) \
|
||||
readl_relaxed((addr) + ATMEL_PMERRLOC_ELx + ((n) * 4))
|
||||
|
||||
/* Galois field dimension */
|
||||
#define PMECC_GF_DIMENSION_13 13
|
||||
#define PMECC_GF_DIMENSION_14 14
|
||||
|
||||
#define PMECC_LOOKUP_TABLE_SIZE_512 0x2000
|
||||
#define PMECC_LOOKUP_TABLE_SIZE_1024 0x4000
|
||||
|
||||
/* Time out value for reading PMECC status register */
|
||||
#define PMECC_MAX_TIMEOUT_MS 100
|
||||
|
||||
#endif
|
||||
|
@ -140,28 +140,6 @@ static void au_read_buf(struct mtd_info *mtd, u_char *buf, int len)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* au_verify_buf - Verify chip data against buffer
|
||||
* @mtd: MTD device structure
|
||||
* @buf: buffer containing the data to compare
|
||||
* @len: number of bytes to compare
|
||||
*
|
||||
* verify function for 8bit buswidth
|
||||
*/
|
||||
static int au_verify_buf(struct mtd_info *mtd, const u_char *buf, int len)
|
||||
{
|
||||
int i;
|
||||
struct nand_chip *this = mtd->priv;
|
||||
|
||||
for (i = 0; i < len; i++) {
|
||||
if (buf[i] != readb(this->IO_ADDR_R))
|
||||
return -EFAULT;
|
||||
au_sync();
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* au_write_buf16 - write buffer to chip
|
||||
* @mtd: MTD device structure
|
||||
@ -205,29 +183,6 @@ static void au_read_buf16(struct mtd_info *mtd, u_char *buf, int len)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* au_verify_buf16 - Verify chip data against buffer
|
||||
* @mtd: MTD device structure
|
||||
* @buf: buffer containing the data to compare
|
||||
* @len: number of bytes to compare
|
||||
*
|
||||
* verify function for 16bit buswidth
|
||||
*/
|
||||
static int au_verify_buf16(struct mtd_info *mtd, const u_char *buf, int len)
|
||||
{
|
||||
int i;
|
||||
struct nand_chip *this = mtd->priv;
|
||||
u16 *p = (u16 *) buf;
|
||||
len >>= 1;
|
||||
|
||||
for (i = 0; i < len; i++) {
|
||||
if (p[i] != readw(this->IO_ADDR_R))
|
||||
return -EFAULT;
|
||||
au_sync();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Select the chip by setting nCE to low */
|
||||
#define NAND_CTL_SETNCE 1
|
||||
/* Deselect the chip by setting nCE to high */
|
||||
@ -516,7 +471,6 @@ static int __devinit au1550nd_probe(struct platform_device *pdev)
|
||||
this->read_word = au_read_word;
|
||||
this->write_buf = (pd->devwidth) ? au_write_buf16 : au_write_buf;
|
||||
this->read_buf = (pd->devwidth) ? au_read_buf16 : au_read_buf;
|
||||
this->verify_buf = (pd->devwidth) ? au_verify_buf16 : au_verify_buf;
|
||||
|
||||
ret = nand_scan(&ctx->info, 1);
|
||||
if (ret) {
|
||||
|
@ -1,217 +0,0 @@
|
||||
/*****************************************************************************
|
||||
* Copyright 2004 - 2009 Broadcom Corporation. All rights reserved.
|
||||
*
|
||||
* Unless you and Broadcom execute a separate written software license
|
||||
* agreement governing use of this software, this software is licensed to you
|
||||
* under the terms of the GNU General Public License version 2, available at
|
||||
* http://www.broadcom.com/licenses/GPLv2.php (the "GPL").
|
||||
*
|
||||
* Notwithstanding the above, under no circumstances may you combine this
|
||||
* software in any way with any other Broadcom software provided under a
|
||||
* license other than the GPL, without Broadcom's express prior written
|
||||
* consent.
|
||||
*****************************************************************************/
|
||||
|
||||
/* ---- Include Files ---------------------------------------------------- */
|
||||
#include "nand_bcm_umi.h"
|
||||
|
||||
/* ---- External Variable Declarations ----------------------------------- */
|
||||
/* ---- External Function Prototypes ------------------------------------- */
|
||||
/* ---- Public Variables ------------------------------------------------- */
|
||||
/* ---- Private Constants and Types -------------------------------------- */
|
||||
|
||||
/* ---- Private Function Prototypes -------------------------------------- */
|
||||
static int bcm_umi_bch_read_page_hwecc(struct mtd_info *mtd,
|
||||
struct nand_chip *chip, uint8_t *buf, int oob_required, int page);
|
||||
static void bcm_umi_bch_write_page_hwecc(struct mtd_info *mtd,
|
||||
struct nand_chip *chip, const uint8_t *buf, int oob_required);
|
||||
|
||||
/* ---- Private Variables ------------------------------------------------ */
|
||||
|
||||
/*
|
||||
** nand_hw_eccoob
|
||||
** New oob placement block for use with hardware ecc generation.
|
||||
*/
|
||||
static struct nand_ecclayout nand_hw_eccoob_512 = {
|
||||
/* Reserve 5 for BI indicator */
|
||||
.oobfree = {
|
||||
#if (NAND_ECC_NUM_BYTES > 3)
|
||||
{.offset = 0, .length = 2}
|
||||
#else
|
||||
{.offset = 0, .length = 5},
|
||||
{.offset = 6, .length = 7}
|
||||
#endif
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
** We treat the OOB for a 2K page as if it were 4 512 byte oobs,
|
||||
** except the BI is at byte 0.
|
||||
*/
|
||||
static struct nand_ecclayout nand_hw_eccoob_2048 = {
|
||||
/* Reserve 0 as BI indicator */
|
||||
.oobfree = {
|
||||
#if (NAND_ECC_NUM_BYTES > 10)
|
||||
{.offset = 1, .length = 2},
|
||||
#elif (NAND_ECC_NUM_BYTES > 7)
|
||||
{.offset = 1, .length = 5},
|
||||
{.offset = 16, .length = 6},
|
||||
{.offset = 32, .length = 6},
|
||||
{.offset = 48, .length = 6}
|
||||
#else
|
||||
{.offset = 1, .length = 8},
|
||||
{.offset = 16, .length = 9},
|
||||
{.offset = 32, .length = 9},
|
||||
{.offset = 48, .length = 9}
|
||||
#endif
|
||||
}
|
||||
};
|
||||
|
||||
/* We treat the OOB for a 4K page as if it were 8 512 byte oobs,
|
||||
* except the BI is at byte 0. */
|
||||
static struct nand_ecclayout nand_hw_eccoob_4096 = {
|
||||
/* Reserve 0 as BI indicator */
|
||||
.oobfree = {
|
||||
#if (NAND_ECC_NUM_BYTES > 10)
|
||||
{.offset = 1, .length = 2},
|
||||
{.offset = 16, .length = 3},
|
||||
{.offset = 32, .length = 3},
|
||||
{.offset = 48, .length = 3},
|
||||
{.offset = 64, .length = 3},
|
||||
{.offset = 80, .length = 3},
|
||||
{.offset = 96, .length = 3},
|
||||
{.offset = 112, .length = 3}
|
||||
#else
|
||||
{.offset = 1, .length = 5},
|
||||
{.offset = 16, .length = 6},
|
||||
{.offset = 32, .length = 6},
|
||||
{.offset = 48, .length = 6},
|
||||
{.offset = 64, .length = 6},
|
||||
{.offset = 80, .length = 6},
|
||||
{.offset = 96, .length = 6},
|
||||
{.offset = 112, .length = 6}
|
||||
#endif
|
||||
}
|
||||
};
|
||||
|
||||
/* ---- Private Functions ------------------------------------------------ */
|
||||
/* ==== Public Functions ================================================= */
|
||||
|
||||
/****************************************************************************
|
||||
*
|
||||
* bcm_umi_bch_read_page_hwecc - hardware ecc based page read function
|
||||
* @mtd: mtd info structure
|
||||
* @chip: nand chip info structure
|
||||
* @buf: buffer to store read data
|
||||
* @oob_required: caller expects OOB data read to chip->oob_poi
|
||||
*
|
||||
***************************************************************************/
|
||||
static int bcm_umi_bch_read_page_hwecc(struct mtd_info *mtd,
|
||||
struct nand_chip *chip, uint8_t * buf,
|
||||
int oob_required, int page)
|
||||
{
|
||||
int sectorIdx = 0;
|
||||
int eccsize = chip->ecc.size;
|
||||
int eccsteps = chip->ecc.steps;
|
||||
uint8_t *datap = buf;
|
||||
uint8_t eccCalc[NAND_ECC_NUM_BYTES];
|
||||
int sectorOobSize = mtd->oobsize / eccsteps;
|
||||
int stat;
|
||||
unsigned int max_bitflips = 0;
|
||||
|
||||
for (sectorIdx = 0; sectorIdx < eccsteps;
|
||||
sectorIdx++, datap += eccsize) {
|
||||
if (sectorIdx > 0) {
|
||||
/* Seek to page location within sector */
|
||||
chip->cmdfunc(mtd, NAND_CMD_RNDOUT, sectorIdx * eccsize,
|
||||
-1);
|
||||
}
|
||||
|
||||
/* Enable hardware ECC before reading the buf */
|
||||
nand_bcm_umi_bch_enable_read_hwecc();
|
||||
|
||||
/* Read in data */
|
||||
bcm_umi_nand_read_buf(mtd, datap, eccsize);
|
||||
|
||||
/* Pause hardware ECC after reading the buf */
|
||||
nand_bcm_umi_bch_pause_read_ecc_calc();
|
||||
|
||||
/* Read the OOB ECC */
|
||||
chip->cmdfunc(mtd, NAND_CMD_RNDOUT,
|
||||
mtd->writesize + sectorIdx * sectorOobSize, -1);
|
||||
nand_bcm_umi_bch_read_oobEcc(mtd->writesize, eccCalc,
|
||||
NAND_ECC_NUM_BYTES,
|
||||
chip->oob_poi +
|
||||
sectorIdx * sectorOobSize);
|
||||
|
||||
/* Correct any ECC detected errors */
|
||||
stat =
|
||||
nand_bcm_umi_bch_correct_page(datap, eccCalc,
|
||||
NAND_ECC_NUM_BYTES);
|
||||
|
||||
/* Update Stats */
|
||||
if (stat < 0) {
|
||||
#if defined(NAND_BCM_UMI_DEBUG)
|
||||
printk(KERN_WARNING "%s uncorr_err sectorIdx=%d\n",
|
||||
__func__, sectorIdx);
|
||||
printk(KERN_WARNING
|
||||
"%s data %02x %02x %02x %02x "
|
||||
"%02x %02x %02x %02x\n",
|
||||
__func__, datap[0], datap[1], datap[2], datap[3],
|
||||
datap[4], datap[5], datap[6], datap[7]);
|
||||
printk(KERN_WARNING
|
||||
"%s ecc %02x %02x %02x %02x "
|
||||
"%02x %02x %02x %02x %02x %02x "
|
||||
"%02x %02x %02x\n",
|
||||
__func__, eccCalc[0], eccCalc[1], eccCalc[2],
|
||||
eccCalc[3], eccCalc[4], eccCalc[5], eccCalc[6],
|
||||
eccCalc[7], eccCalc[8], eccCalc[9], eccCalc[10],
|
||||
eccCalc[11], eccCalc[12]);
|
||||
BUG();
|
||||
#endif
|
||||
mtd->ecc_stats.failed++;
|
||||
} else {
|
||||
#if defined(NAND_BCM_UMI_DEBUG)
|
||||
if (stat > 0) {
|
||||
printk(KERN_INFO
|
||||
"%s %d correctable_errors detected\n",
|
||||
__func__, stat);
|
||||
}
|
||||
#endif
|
||||
mtd->ecc_stats.corrected += stat;
|
||||
max_bitflips = max_t(unsigned int, max_bitflips, stat);
|
||||
}
|
||||
}
|
||||
return max_bitflips;
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
*
|
||||
* bcm_umi_bch_write_page_hwecc - hardware ecc based page write function
|
||||
* @mtd: mtd info structure
|
||||
* @chip: nand chip info structure
|
||||
* @buf: data buffer
|
||||
* @oob_required: must write chip->oob_poi to OOB
|
||||
*
|
||||
***************************************************************************/
|
||||
static void bcm_umi_bch_write_page_hwecc(struct mtd_info *mtd,
|
||||
struct nand_chip *chip, const uint8_t *buf, int oob_required)
|
||||
{
|
||||
int sectorIdx = 0;
|
||||
int eccsize = chip->ecc.size;
|
||||
int eccsteps = chip->ecc.steps;
|
||||
const uint8_t *datap = buf;
|
||||
uint8_t *oobp = chip->oob_poi;
|
||||
int sectorOobSize = mtd->oobsize / eccsteps;
|
||||
|
||||
for (sectorIdx = 0; sectorIdx < eccsteps;
|
||||
sectorIdx++, datap += eccsize, oobp += sectorOobSize) {
|
||||
/* Enable hardware ECC before writing the buf */
|
||||
nand_bcm_umi_bch_enable_write_hwecc();
|
||||
bcm_umi_nand_write_buf(mtd, datap, eccsize);
|
||||
nand_bcm_umi_bch_write_oobEcc(mtd->writesize, oobp,
|
||||
NAND_ECC_NUM_BYTES);
|
||||
}
|
||||
|
||||
bcm_umi_nand_write_buf(mtd, chip->oob_poi, mtd->oobsize);
|
||||
}
|
@ -1,555 +0,0 @@
|
||||
/*****************************************************************************
|
||||
* Copyright 2004 - 2009 Broadcom Corporation. All rights reserved.
|
||||
*
|
||||
* Unless you and Broadcom execute a separate written software license
|
||||
* agreement governing use of this software, this software is licensed to you
|
||||
* under the terms of the GNU General Public License version 2, available at
|
||||
* http://www.broadcom.com/licenses/GPLv2.php (the "GPL").
|
||||
*
|
||||
* Notwithstanding the above, under no circumstances may you combine this
|
||||
* software in any way with any other Broadcom software provided under a
|
||||
* license other than the GPL, without Broadcom's express prior written
|
||||
* consent.
|
||||
*****************************************************************************/
|
||||
|
||||
/* ---- Include Files ---------------------------------------------------- */
|
||||
#include <linux/module.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/string.h>
|
||||
#include <linux/ioport.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/mtd/mtd.h>
|
||||
#include <linux/mtd/nand.h>
|
||||
#include <linux/mtd/nand_ecc.h>
|
||||
#include <linux/mtd/partitions.h>
|
||||
|
||||
#include <asm/mach-types.h>
|
||||
|
||||
#include <mach/reg_nand.h>
|
||||
#include <mach/reg_umi.h>
|
||||
|
||||
#include "nand_bcm_umi.h"
|
||||
|
||||
#include <mach/memory_settings.h>
|
||||
|
||||
#define USE_DMA 1
|
||||
#include <mach/dma.h>
|
||||
#include <linux/dma-mapping.h>
|
||||
#include <linux/completion.h>
|
||||
|
||||
/* ---- External Variable Declarations ----------------------------------- */
|
||||
/* ---- External Function Prototypes ------------------------------------- */
|
||||
/* ---- Public Variables ------------------------------------------------- */
|
||||
/* ---- Private Constants and Types -------------------------------------- */
|
||||
static const __devinitconst char gBanner[] = KERN_INFO \
|
||||
"BCM UMI MTD NAND Driver: 1.00\n";
|
||||
|
||||
#if NAND_ECC_BCH
|
||||
static uint8_t scan_ff_pattern[] = { 0xff };
|
||||
|
||||
static struct nand_bbt_descr largepage_bbt = {
|
||||
.options = 0,
|
||||
.offs = 0,
|
||||
.len = 1,
|
||||
.pattern = scan_ff_pattern
|
||||
};
|
||||
#endif
|
||||
|
||||
/*
|
||||
** Preallocate a buffer to avoid having to do this every dma operation.
|
||||
** This is the size of the preallocated coherent DMA buffer.
|
||||
*/
|
||||
#if USE_DMA
|
||||
#define DMA_MIN_BUFLEN 512
|
||||
#define DMA_MAX_BUFLEN PAGE_SIZE
|
||||
#define USE_DIRECT_IO(len) (((len) < DMA_MIN_BUFLEN) || \
|
||||
((len) > DMA_MAX_BUFLEN))
|
||||
|
||||
/*
|
||||
* The current NAND data space goes from 0x80001900 to 0x80001FFF,
|
||||
* which is only 0x700 = 1792 bytes long. This is too small for 2K, 4K page
|
||||
* size NAND flash. Need to break the DMA down to multiple 1Ks.
|
||||
*
|
||||
* Need to make sure REG_NAND_DATA_PADDR + DMA_MAX_LEN < 0x80002000
|
||||
*/
|
||||
#define DMA_MAX_LEN 1024
|
||||
|
||||
#else /* !USE_DMA */
|
||||
#define DMA_MIN_BUFLEN 0
|
||||
#define DMA_MAX_BUFLEN 0
|
||||
#define USE_DIRECT_IO(len) 1
|
||||
#endif
|
||||
/* ---- Private Function Prototypes -------------------------------------- */
|
||||
static void bcm_umi_nand_read_buf(struct mtd_info *mtd, u_char * buf, int len);
|
||||
static void bcm_umi_nand_write_buf(struct mtd_info *mtd, const u_char * buf,
|
||||
int len);
|
||||
|
||||
/* ---- Private Variables ------------------------------------------------ */
|
||||
static struct mtd_info *board_mtd;
|
||||
static void __iomem *bcm_umi_io_base;
|
||||
static void *virtPtr;
|
||||
static dma_addr_t physPtr;
|
||||
static struct completion nand_comp;
|
||||
|
||||
/* ---- Private Functions ------------------------------------------------ */
|
||||
#if NAND_ECC_BCH
|
||||
#include "bcm_umi_bch.c"
|
||||
#else
|
||||
#include "bcm_umi_hamming.c"
|
||||
#endif
|
||||
|
||||
#if USE_DMA
|
||||
|
||||
/* Handler called when the DMA finishes. */
|
||||
static void nand_dma_handler(DMA_Device_t dev, int reason, void *userData)
|
||||
{
|
||||
complete(&nand_comp);
|
||||
}
|
||||
|
||||
static int nand_dma_init(void)
|
||||
{
|
||||
int rc;
|
||||
|
||||
rc = dma_set_device_handler(DMA_DEVICE_NAND_MEM_TO_MEM,
|
||||
nand_dma_handler, NULL);
|
||||
if (rc != 0) {
|
||||
printk(KERN_ERR "dma_set_device_handler failed: %d\n", rc);
|
||||
return rc;
|
||||
}
|
||||
|
||||
virtPtr =
|
||||
dma_alloc_coherent(NULL, DMA_MAX_BUFLEN, &physPtr, GFP_KERNEL);
|
||||
if (virtPtr == NULL) {
|
||||
printk(KERN_ERR "NAND - Failed to allocate memory for DMA buffer\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void nand_dma_term(void)
|
||||
{
|
||||
if (virtPtr != NULL)
|
||||
dma_free_coherent(NULL, DMA_MAX_BUFLEN, virtPtr, physPtr);
|
||||
}
|
||||
|
||||
static void nand_dma_read(void *buf, int len)
|
||||
{
|
||||
int offset = 0;
|
||||
int tmp_len = 0;
|
||||
int len_left = len;
|
||||
DMA_Handle_t hndl;
|
||||
|
||||
if (virtPtr == NULL)
|
||||
panic("nand_dma_read: virtPtr == NULL\n");
|
||||
|
||||
if ((void *)physPtr == NULL)
|
||||
panic("nand_dma_read: physPtr == NULL\n");
|
||||
|
||||
hndl = dma_request_channel(DMA_DEVICE_NAND_MEM_TO_MEM);
|
||||
if (hndl < 0) {
|
||||
printk(KERN_ERR
|
||||
"nand_dma_read: unable to allocate dma channel: %d\n",
|
||||
(int)hndl);
|
||||
panic("\n");
|
||||
}
|
||||
|
||||
while (len_left > 0) {
|
||||
if (len_left > DMA_MAX_LEN) {
|
||||
tmp_len = DMA_MAX_LEN;
|
||||
len_left -= DMA_MAX_LEN;
|
||||
} else {
|
||||
tmp_len = len_left;
|
||||
len_left = 0;
|
||||
}
|
||||
|
||||
init_completion(&nand_comp);
|
||||
dma_transfer_mem_to_mem(hndl, REG_NAND_DATA_PADDR,
|
||||
physPtr + offset, tmp_len);
|
||||
wait_for_completion(&nand_comp);
|
||||
|
||||
offset += tmp_len;
|
||||
}
|
||||
|
||||
dma_free_channel(hndl);
|
||||
|
||||
if (buf != NULL)
|
||||
memcpy(buf, virtPtr, len);
|
||||
}
|
||||
|
||||
static void nand_dma_write(const void *buf, int len)
|
||||
{
|
||||
int offset = 0;
|
||||
int tmp_len = 0;
|
||||
int len_left = len;
|
||||
DMA_Handle_t hndl;
|
||||
|
||||
if (buf == NULL)
|
||||
panic("nand_dma_write: buf == NULL\n");
|
||||
|
||||
if (virtPtr == NULL)
|
||||
panic("nand_dma_write: virtPtr == NULL\n");
|
||||
|
||||
if ((void *)physPtr == NULL)
|
||||
panic("nand_dma_write: physPtr == NULL\n");
|
||||
|
||||
memcpy(virtPtr, buf, len);
|
||||
|
||||
|
||||
hndl = dma_request_channel(DMA_DEVICE_NAND_MEM_TO_MEM);
|
||||
if (hndl < 0) {
|
||||
printk(KERN_ERR
|
||||
"nand_dma_write: unable to allocate dma channel: %d\n",
|
||||
(int)hndl);
|
||||
panic("\n");
|
||||
}
|
||||
|
||||
while (len_left > 0) {
|
||||
if (len_left > DMA_MAX_LEN) {
|
||||
tmp_len = DMA_MAX_LEN;
|
||||
len_left -= DMA_MAX_LEN;
|
||||
} else {
|
||||
tmp_len = len_left;
|
||||
len_left = 0;
|
||||
}
|
||||
|
||||
init_completion(&nand_comp);
|
||||
dma_transfer_mem_to_mem(hndl, physPtr + offset,
|
||||
REG_NAND_DATA_PADDR, tmp_len);
|
||||
wait_for_completion(&nand_comp);
|
||||
|
||||
offset += tmp_len;
|
||||
}
|
||||
|
||||
dma_free_channel(hndl);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
static int nand_dev_ready(struct mtd_info *mtd)
|
||||
{
|
||||
return nand_bcm_umi_dev_ready();
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
*
|
||||
* bcm_umi_nand_inithw
|
||||
*
|
||||
* This routine does the necessary hardware (board-specific)
|
||||
* initializations. This includes setting up the timings, etc.
|
||||
*
|
||||
***************************************************************************/
|
||||
int bcm_umi_nand_inithw(void)
|
||||
{
|
||||
/* Configure nand timing parameters */
|
||||
writel(readl(®_UMI_NAND_TCR) & ~0x7ffff, ®_UMI_NAND_TCR);
|
||||
writel(readl(®_UMI_NAND_TCR) | HW_CFG_NAND_TCR, ®_UMI_NAND_TCR);
|
||||
|
||||
#if !defined(CONFIG_MTD_NAND_BCM_UMI_HWCS)
|
||||
/* enable software control of CS */
|
||||
writel(readl(®_UMI_NAND_TCR) | REG_UMI_NAND_TCR_CS_SWCTRL, ®_UMI_NAND_TCR);
|
||||
#endif
|
||||
|
||||
/* keep NAND chip select asserted */
|
||||
writel(readl(®_UMI_NAND_RCSR) | REG_UMI_NAND_RCSR_CS_ASSERTED, ®_UMI_NAND_RCSR);
|
||||
|
||||
writel(readl(®_UMI_NAND_TCR) & ~REG_UMI_NAND_TCR_WORD16, ®_UMI_NAND_TCR);
|
||||
/* enable writes to flash */
|
||||
writel(readl(®_UMI_MMD_ICR) | REG_UMI_MMD_ICR_FLASH_WP, ®_UMI_MMD_ICR);
|
||||
|
||||
writel(NAND_CMD_RESET, bcm_umi_io_base + REG_NAND_CMD_OFFSET);
|
||||
nand_bcm_umi_wait_till_ready();
|
||||
|
||||
#if NAND_ECC_BCH
|
||||
nand_bcm_umi_bch_config_ecc(NAND_ECC_NUM_BYTES);
|
||||
#endif
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Used to turn latch the proper register for access. */
|
||||
static void bcm_umi_nand_hwcontrol(struct mtd_info *mtd, int cmd,
|
||||
unsigned int ctrl)
|
||||
{
|
||||
/* send command to hardware */
|
||||
struct nand_chip *chip = mtd->priv;
|
||||
if (ctrl & NAND_CTRL_CHANGE) {
|
||||
if (ctrl & NAND_CLE) {
|
||||
chip->IO_ADDR_W = bcm_umi_io_base + REG_NAND_CMD_OFFSET;
|
||||
goto CMD;
|
||||
}
|
||||
if (ctrl & NAND_ALE) {
|
||||
chip->IO_ADDR_W =
|
||||
bcm_umi_io_base + REG_NAND_ADDR_OFFSET;
|
||||
goto CMD;
|
||||
}
|
||||
chip->IO_ADDR_W = bcm_umi_io_base + REG_NAND_DATA8_OFFSET;
|
||||
}
|
||||
|
||||
CMD:
|
||||
/* Send command to chip directly */
|
||||
if (cmd != NAND_CMD_NONE)
|
||||
writeb(cmd, chip->IO_ADDR_W);
|
||||
}
|
||||
|
||||
static void bcm_umi_nand_write_buf(struct mtd_info *mtd, const u_char * buf,
|
||||
int len)
|
||||
{
|
||||
if (USE_DIRECT_IO(len)) {
|
||||
/* Do it the old way if the buffer is small or too large.
|
||||
* Probably quicker than starting and checking dma. */
|
||||
int i;
|
||||
struct nand_chip *this = mtd->priv;
|
||||
|
||||
for (i = 0; i < len; i++)
|
||||
writeb(buf[i], this->IO_ADDR_W);
|
||||
}
|
||||
#if USE_DMA
|
||||
else
|
||||
nand_dma_write(buf, len);
|
||||
#endif
|
||||
}
|
||||
|
||||
static void bcm_umi_nand_read_buf(struct mtd_info *mtd, u_char * buf, int len)
|
||||
{
|
||||
if (USE_DIRECT_IO(len)) {
|
||||
int i;
|
||||
struct nand_chip *this = mtd->priv;
|
||||
|
||||
for (i = 0; i < len; i++)
|
||||
buf[i] = readb(this->IO_ADDR_R);
|
||||
}
|
||||
#if USE_DMA
|
||||
else
|
||||
nand_dma_read(buf, len);
|
||||
#endif
|
||||
}
|
||||
|
||||
static uint8_t readbackbuf[NAND_MAX_PAGESIZE];
|
||||
static int bcm_umi_nand_verify_buf(struct mtd_info *mtd, const u_char * buf,
|
||||
int len)
|
||||
{
|
||||
/*
|
||||
* Try to readback page with ECC correction. This is necessary
|
||||
* for MLC parts which may have permanently stuck bits.
|
||||
*/
|
||||
struct nand_chip *chip = mtd->priv;
|
||||
int ret = chip->ecc.read_page(mtd, chip, readbackbuf, 0, 0);
|
||||
if (ret < 0)
|
||||
return -EFAULT;
|
||||
else {
|
||||
if (memcmp(readbackbuf, buf, len) == 0)
|
||||
return 0;
|
||||
|
||||
return -EFAULT;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int __devinit bcm_umi_nand_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct nand_chip *this;
|
||||
struct resource *r;
|
||||
int err = 0;
|
||||
|
||||
printk(gBanner);
|
||||
|
||||
/* Allocate memory for MTD device structure and private data */
|
||||
board_mtd =
|
||||
kmalloc(sizeof(struct mtd_info) + sizeof(struct nand_chip),
|
||||
GFP_KERNEL);
|
||||
if (!board_mtd) {
|
||||
printk(KERN_WARNING
|
||||
"Unable to allocate NAND MTD device structure.\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
|
||||
if (!r) {
|
||||
err = -ENXIO;
|
||||
goto out_free;
|
||||
}
|
||||
|
||||
/* map physical address */
|
||||
bcm_umi_io_base = ioremap(r->start, resource_size(r));
|
||||
|
||||
if (!bcm_umi_io_base) {
|
||||
printk(KERN_ERR "ioremap to access BCM UMI NAND chip failed\n");
|
||||
err = -EIO;
|
||||
goto out_free;
|
||||
}
|
||||
|
||||
/* Get pointer to private data */
|
||||
this = (struct nand_chip *)(&board_mtd[1]);
|
||||
|
||||
/* Initialize structures */
|
||||
memset((char *)board_mtd, 0, sizeof(struct mtd_info));
|
||||
memset((char *)this, 0, sizeof(struct nand_chip));
|
||||
|
||||
/* Link the private data with the MTD structure */
|
||||
board_mtd->priv = this;
|
||||
|
||||
/* Initialize the NAND hardware. */
|
||||
if (bcm_umi_nand_inithw() < 0) {
|
||||
printk(KERN_ERR "BCM UMI NAND chip could not be initialized\n");
|
||||
err = -EIO;
|
||||
goto out_unmap;
|
||||
}
|
||||
|
||||
/* Set address of NAND IO lines */
|
||||
this->IO_ADDR_W = bcm_umi_io_base + REG_NAND_DATA8_OFFSET;
|
||||
this->IO_ADDR_R = bcm_umi_io_base + REG_NAND_DATA8_OFFSET;
|
||||
|
||||
/* Set command delay time, see datasheet for correct value */
|
||||
this->chip_delay = 0;
|
||||
/* Assign the device ready function, if available */
|
||||
this->dev_ready = nand_dev_ready;
|
||||
this->options = 0;
|
||||
|
||||
this->write_buf = bcm_umi_nand_write_buf;
|
||||
this->read_buf = bcm_umi_nand_read_buf;
|
||||
this->verify_buf = bcm_umi_nand_verify_buf;
|
||||
|
||||
this->cmd_ctrl = bcm_umi_nand_hwcontrol;
|
||||
this->ecc.mode = NAND_ECC_HW;
|
||||
this->ecc.size = 512;
|
||||
this->ecc.bytes = NAND_ECC_NUM_BYTES;
|
||||
#if NAND_ECC_BCH
|
||||
this->ecc.read_page = bcm_umi_bch_read_page_hwecc;
|
||||
this->ecc.write_page = bcm_umi_bch_write_page_hwecc;
|
||||
#else
|
||||
this->ecc.correct = nand_correct_data512;
|
||||
this->ecc.calculate = bcm_umi_hamming_get_hw_ecc;
|
||||
this->ecc.hwctl = bcm_umi_hamming_enable_hwecc;
|
||||
#endif
|
||||
|
||||
#if USE_DMA
|
||||
err = nand_dma_init();
|
||||
if (err != 0)
|
||||
goto out_unmap;
|
||||
#endif
|
||||
|
||||
/* Figure out the size of the device that we have.
|
||||
* We need to do this to figure out which ECC
|
||||
* layout we'll be using.
|
||||
*/
|
||||
|
||||
err = nand_scan_ident(board_mtd, 1, NULL);
|
||||
if (err) {
|
||||
printk(KERN_ERR "nand_scan failed: %d\n", err);
|
||||
goto out_unmap;
|
||||
}
|
||||
|
||||
/* Now that we know the nand size, we can setup the ECC layout */
|
||||
|
||||
switch (board_mtd->writesize) { /* writesize is the pagesize */
|
||||
case 4096:
|
||||
this->ecc.layout = &nand_hw_eccoob_4096;
|
||||
break;
|
||||
case 2048:
|
||||
this->ecc.layout = &nand_hw_eccoob_2048;
|
||||
break;
|
||||
case 512:
|
||||
this->ecc.layout = &nand_hw_eccoob_512;
|
||||
break;
|
||||
default:
|
||||
{
|
||||
printk(KERN_ERR "NAND - Unrecognized pagesize: %d\n",
|
||||
board_mtd->writesize);
|
||||
err = -EINVAL;
|
||||
goto out_unmap;
|
||||
}
|
||||
}
|
||||
|
||||
#if NAND_ECC_BCH
|
||||
if (board_mtd->writesize > 512) {
|
||||
if (this->bbt_options & NAND_BBT_USE_FLASH)
|
||||
largepage_bbt.options = NAND_BBT_SCAN2NDPAGE;
|
||||
this->badblock_pattern = &largepage_bbt;
|
||||
}
|
||||
|
||||
this->ecc.strength = 8;
|
||||
|
||||
#endif
|
||||
|
||||
/* Now finish off the scan, now that ecc.layout has been initialized. */
|
||||
|
||||
err = nand_scan_tail(board_mtd);
|
||||
if (err) {
|
||||
printk(KERN_ERR "nand_scan failed: %d\n", err);
|
||||
goto out_unmap;
|
||||
}
|
||||
|
||||
/* Register the partitions */
|
||||
board_mtd->name = "bcm_umi-nand";
|
||||
mtd_device_parse_register(board_mtd, NULL, NULL, NULL, 0);
|
||||
|
||||
/* Return happy */
|
||||
return 0;
|
||||
out_unmap:
|
||||
iounmap(bcm_umi_io_base);
|
||||
out_free:
|
||||
kfree(board_mtd);
|
||||
return err;
|
||||
}
|
||||
|
||||
static int bcm_umi_nand_remove(struct platform_device *pdev)
|
||||
{
|
||||
#if USE_DMA
|
||||
nand_dma_term();
|
||||
#endif
|
||||
|
||||
/* Release resources, unregister device */
|
||||
nand_release(board_mtd);
|
||||
|
||||
/* unmap physical address */
|
||||
iounmap(bcm_umi_io_base);
|
||||
|
||||
/* Free the MTD device structure */
|
||||
kfree(board_mtd);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM
|
||||
static int bcm_umi_nand_suspend(struct platform_device *pdev,
|
||||
pm_message_t state)
|
||||
{
|
||||
printk(KERN_ERR "MTD NAND suspend is being called\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int bcm_umi_nand_resume(struct platform_device *pdev)
|
||||
{
|
||||
printk(KERN_ERR "MTD NAND resume is being called\n");
|
||||
return 0;
|
||||
}
|
||||
#else
|
||||
#define bcm_umi_nand_suspend NULL
|
||||
#define bcm_umi_nand_resume NULL
|
||||
#endif
|
||||
|
||||
static struct platform_driver nand_driver = {
|
||||
.driver = {
|
||||
.name = "bcm-nand",
|
||||
.owner = THIS_MODULE,
|
||||
},
|
||||
.probe = bcm_umi_nand_probe,
|
||||
.remove = bcm_umi_nand_remove,
|
||||
.suspend = bcm_umi_nand_suspend,
|
||||
.resume = bcm_umi_nand_resume,
|
||||
};
|
||||
|
||||
module_platform_driver(nand_driver);
|
||||
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_AUTHOR("Broadcom");
|
||||
MODULE_DESCRIPTION("BCM UMI MTD NAND driver");
|
@ -566,11 +566,13 @@ static int bf5xx_nand_read_page_raw(struct mtd_info *mtd, struct nand_chip *chip
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void bf5xx_nand_write_page_raw(struct mtd_info *mtd, struct nand_chip *chip,
|
||||
const uint8_t *buf, int oob_required)
|
||||
static int bf5xx_nand_write_page_raw(struct mtd_info *mtd,
|
||||
struct nand_chip *chip, const uint8_t *buf, int oob_required)
|
||||
{
|
||||
bf5xx_nand_write_buf(mtd, buf, mtd->writesize);
|
||||
bf5xx_nand_write_buf(mtd, chip->oob_poi, mtd->oobsize);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -377,7 +377,7 @@ static int cafe_nand_read_oob(struct mtd_info *mtd, struct nand_chip *chip,
|
||||
* @buf: buffer to store read data
|
||||
* @oob_required: caller expects OOB data read to chip->oob_poi
|
||||
*
|
||||
* The hw generator calculates the error syndrome automatically. Therefor
|
||||
* The hw generator calculates the error syndrome automatically. Therefore
|
||||
* we need a special oob layout and handling.
|
||||
*/
|
||||
static int cafe_nand_read_page(struct mtd_info *mtd, struct nand_chip *chip,
|
||||
@ -520,7 +520,7 @@ static struct nand_bbt_descr cafe_bbt_mirror_descr_512 = {
|
||||
};
|
||||
|
||||
|
||||
static void cafe_nand_write_page_lowlevel(struct mtd_info *mtd,
|
||||
static int cafe_nand_write_page_lowlevel(struct mtd_info *mtd,
|
||||
struct nand_chip *chip,
|
||||
const uint8_t *buf, int oob_required)
|
||||
{
|
||||
@ -531,6 +531,8 @@ static void cafe_nand_write_page_lowlevel(struct mtd_info *mtd,
|
||||
|
||||
/* Set up ECC autogeneration */
|
||||
cafe->ctl2 |= (1<<30);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int cafe_nand_write_page(struct mtd_info *mtd, struct nand_chip *chip,
|
||||
@ -542,9 +544,12 @@ static int cafe_nand_write_page(struct mtd_info *mtd, struct nand_chip *chip,
|
||||
chip->cmdfunc(mtd, NAND_CMD_SEQIN, 0x00, page);
|
||||
|
||||
if (unlikely(raw))
|
||||
chip->ecc.write_page_raw(mtd, chip, buf, oob_required);
|
||||
status = chip->ecc.write_page_raw(mtd, chip, buf, oob_required);
|
||||
else
|
||||
chip->ecc.write_page(mtd, chip, buf, oob_required);
|
||||
status = chip->ecc.write_page(mtd, chip, buf, oob_required);
|
||||
|
||||
if (status < 0)
|
||||
return status;
|
||||
|
||||
/*
|
||||
* Cached progamming disabled for now, Not sure if its worth the
|
||||
@ -571,13 +576,6 @@ static int cafe_nand_write_page(struct mtd_info *mtd, struct nand_chip *chip,
|
||||
status = chip->waitfunc(mtd, chip);
|
||||
}
|
||||
|
||||
#ifdef CONFIG_MTD_NAND_VERIFY_WRITE
|
||||
/* Send command to read back the data */
|
||||
chip->cmdfunc(mtd, NAND_CMD_READ0, 0, page);
|
||||
|
||||
if (chip->verify_buf(mtd, buf, mtd->writesize))
|
||||
return -EIO;
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -76,18 +76,6 @@ static void cmx270_read_buf(struct mtd_info *mtd, u_char *buf, int len)
|
||||
*buf++ = readl(this->IO_ADDR_R) >> 16;
|
||||
}
|
||||
|
||||
static int cmx270_verify_buf(struct mtd_info *mtd, const u_char *buf, int len)
|
||||
{
|
||||
int i;
|
||||
struct nand_chip *this = mtd->priv;
|
||||
|
||||
for (i=0; i<len; i++)
|
||||
if (buf[i] != (u_char)(readl(this->IO_ADDR_R) >> 16))
|
||||
return -EFAULT;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline void nand_cs_on(void)
|
||||
{
|
||||
gpio_set_value(GPIO_NAND_CS, 0);
|
||||
@ -209,7 +197,6 @@ static int __init cmx270_init(void)
|
||||
this->read_byte = cmx270_read_byte;
|
||||
this->read_buf = cmx270_read_buf;
|
||||
this->write_buf = cmx270_write_buf;
|
||||
this->verify_buf = cmx270_verify_buf;
|
||||
|
||||
/* Scan to find existence of the device */
|
||||
if (nand_scan (cmx270_nand_mtd, 1)) {
|
||||
|
@ -33,6 +33,7 @@
|
||||
#include <linux/mtd/nand.h>
|
||||
#include <linux/mtd/partitions.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/of_device.h>
|
||||
|
||||
#include <linux/platform_data/mtd-davinci.h>
|
||||
#include <linux/platform_data/mtd-davinci-aemif.h>
|
||||
@ -518,9 +519,75 @@ static struct nand_ecclayout hwecc4_2048 __initconst = {
|
||||
},
|
||||
};
|
||||
|
||||
#if defined(CONFIG_OF)
|
||||
static const struct of_device_id davinci_nand_of_match[] = {
|
||||
{.compatible = "ti,davinci-nand", },
|
||||
{},
|
||||
}
|
||||
MODULE_DEVICE_TABLE(of, davinci_nand_of_match);
|
||||
|
||||
static struct davinci_nand_pdata
|
||||
*nand_davinci_get_pdata(struct platform_device *pdev)
|
||||
{
|
||||
if (!pdev->dev.platform_data && pdev->dev.of_node) {
|
||||
struct davinci_nand_pdata *pdata;
|
||||
const char *mode;
|
||||
u32 prop;
|
||||
int len;
|
||||
|
||||
pdata = devm_kzalloc(&pdev->dev,
|
||||
sizeof(struct davinci_nand_pdata),
|
||||
GFP_KERNEL);
|
||||
pdev->dev.platform_data = pdata;
|
||||
if (!pdata)
|
||||
return NULL;
|
||||
if (!of_property_read_u32(pdev->dev.of_node,
|
||||
"ti,davinci-chipselect", &prop))
|
||||
pdev->id = prop;
|
||||
if (!of_property_read_u32(pdev->dev.of_node,
|
||||
"ti,davinci-mask-ale", &prop))
|
||||
pdata->mask_ale = prop;
|
||||
if (!of_property_read_u32(pdev->dev.of_node,
|
||||
"ti,davinci-mask-cle", &prop))
|
||||
pdata->mask_cle = prop;
|
||||
if (!of_property_read_u32(pdev->dev.of_node,
|
||||
"ti,davinci-mask-chipsel", &prop))
|
||||
pdata->mask_chipsel = prop;
|
||||
if (!of_property_read_string(pdev->dev.of_node,
|
||||
"ti,davinci-ecc-mode", &mode)) {
|
||||
if (!strncmp("none", mode, 4))
|
||||
pdata->ecc_mode = NAND_ECC_NONE;
|
||||
if (!strncmp("soft", mode, 4))
|
||||
pdata->ecc_mode = NAND_ECC_SOFT;
|
||||
if (!strncmp("hw", mode, 2))
|
||||
pdata->ecc_mode = NAND_ECC_HW;
|
||||
}
|
||||
if (!of_property_read_u32(pdev->dev.of_node,
|
||||
"ti,davinci-ecc-bits", &prop))
|
||||
pdata->ecc_bits = prop;
|
||||
if (!of_property_read_u32(pdev->dev.of_node,
|
||||
"ti,davinci-nand-buswidth", &prop))
|
||||
if (prop == 16)
|
||||
pdata->options |= NAND_BUSWIDTH_16;
|
||||
if (of_find_property(pdev->dev.of_node,
|
||||
"ti,davinci-nand-use-bbt", &len))
|
||||
pdata->bbt_options = NAND_BBT_USE_FLASH;
|
||||
}
|
||||
|
||||
return pdev->dev.platform_data;
|
||||
}
|
||||
#else
|
||||
#define davinci_nand_of_match NULL
|
||||
static struct davinci_nand_pdata
|
||||
*nand_davinci_get_pdata(struct platform_device *pdev)
|
||||
{
|
||||
return pdev->dev.platform_data;
|
||||
}
|
||||
#endif
|
||||
|
||||
static int __init nand_davinci_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct davinci_nand_pdata *pdata = pdev->dev.platform_data;
|
||||
struct davinci_nand_pdata *pdata;
|
||||
struct davinci_nand_info *info;
|
||||
struct resource *res1;
|
||||
struct resource *res2;
|
||||
@ -530,6 +597,7 @@ static int __init nand_davinci_probe(struct platform_device *pdev)
|
||||
uint32_t val;
|
||||
nand_ecc_modes_t ecc_mode;
|
||||
|
||||
pdata = nand_davinci_get_pdata(pdev);
|
||||
/* insist on board-specific configuration */
|
||||
if (!pdata)
|
||||
return -ENODEV;
|
||||
@ -656,7 +724,7 @@ static int __init nand_davinci_probe(struct platform_device *pdev)
|
||||
goto err_clk;
|
||||
}
|
||||
|
||||
ret = clk_enable(info->clk);
|
||||
ret = clk_prepare_enable(info->clk);
|
||||
if (ret < 0) {
|
||||
dev_dbg(&pdev->dev, "unable to enable AEMIF clock, err %d\n",
|
||||
ret);
|
||||
@ -767,7 +835,7 @@ syndrome_done:
|
||||
|
||||
err_scan:
|
||||
err_timing:
|
||||
clk_disable(info->clk);
|
||||
clk_disable_unprepare(info->clk);
|
||||
|
||||
err_clk_enable:
|
||||
clk_put(info->clk);
|
||||
@ -804,7 +872,7 @@ static int __exit nand_davinci_remove(struct platform_device *pdev)
|
||||
|
||||
nand_release(&info->mtd);
|
||||
|
||||
clk_disable(info->clk);
|
||||
clk_disable_unprepare(info->clk);
|
||||
clk_put(info->clk);
|
||||
|
||||
kfree(info);
|
||||
@ -816,6 +884,8 @@ static struct platform_driver nand_davinci_driver = {
|
||||
.remove = __exit_p(nand_davinci_remove),
|
||||
.driver = {
|
||||
.name = "davinci_nand",
|
||||
.owner = THIS_MODULE,
|
||||
.of_match_table = davinci_nand_of_match,
|
||||
},
|
||||
};
|
||||
MODULE_ALIAS("platform:davinci_nand");
|
||||
|
@ -1028,7 +1028,7 @@ static void denali_setup_dma(struct denali_nand_info *denali, int op)
|
||||
|
||||
/* writes a page. user specifies type, and this function handles the
|
||||
* configuration details. */
|
||||
static void write_page(struct mtd_info *mtd, struct nand_chip *chip,
|
||||
static int write_page(struct mtd_info *mtd, struct nand_chip *chip,
|
||||
const uint8_t *buf, bool raw_xfer)
|
||||
{
|
||||
struct denali_nand_info *denali = mtd_to_denali(mtd);
|
||||
@ -1078,6 +1078,8 @@ static void write_page(struct mtd_info *mtd, struct nand_chip *chip,
|
||||
|
||||
denali_enable_dma(denali, false);
|
||||
dma_sync_single_for_cpu(denali->dev, addr, size, DMA_TO_DEVICE);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* NAND core entry points */
|
||||
@ -1086,24 +1088,24 @@ static void write_page(struct mtd_info *mtd, struct nand_chip *chip,
|
||||
* writing a page with ECC or without is similar, all the work is done
|
||||
* by write_page above.
|
||||
* */
|
||||
static void denali_write_page(struct mtd_info *mtd, struct nand_chip *chip,
|
||||
static int denali_write_page(struct mtd_info *mtd, struct nand_chip *chip,
|
||||
const uint8_t *buf, int oob_required)
|
||||
{
|
||||
/* for regular page writes, we let HW handle all the ECC
|
||||
* data written to the device. */
|
||||
write_page(mtd, chip, buf, false);
|
||||
return write_page(mtd, chip, buf, false);
|
||||
}
|
||||
|
||||
/* This is the callback that the NAND core calls to write a page without ECC.
|
||||
* raw access is similar to ECC page writes, so all the work is done in the
|
||||
* write_page() function above.
|
||||
*/
|
||||
static void denali_write_page_raw(struct mtd_info *mtd, struct nand_chip *chip,
|
||||
static int denali_write_page_raw(struct mtd_info *mtd, struct nand_chip *chip,
|
||||
const uint8_t *buf, int oob_required)
|
||||
{
|
||||
/* for raw page writes, we want to disable ECC and simply write
|
||||
whatever data is in the buffer. */
|
||||
write_page(mtd, chip, buf, true);
|
||||
return write_page(mtd, chip, buf, true);
|
||||
}
|
||||
|
||||
static int denali_write_oob(struct mtd_info *mtd, struct nand_chip *chip,
|
||||
|
@ -376,19 +376,6 @@ static void doc2000_readbuf_dword(struct mtd_info *mtd, u_char *buf, int len)
|
||||
}
|
||||
}
|
||||
|
||||
static int doc2000_verifybuf(struct mtd_info *mtd, const u_char *buf, int len)
|
||||
{
|
||||
struct nand_chip *this = mtd->priv;
|
||||
struct doc_priv *doc = this->priv;
|
||||
void __iomem *docptr = doc->virtadr;
|
||||
int i;
|
||||
|
||||
for (i = 0; i < len; i++)
|
||||
if (buf[i] != ReadDOC(docptr, 2k_CDSN_IO))
|
||||
return -EFAULT;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static uint16_t __init doc200x_ident_chip(struct mtd_info *mtd, int nr)
|
||||
{
|
||||
struct nand_chip *this = mtd->priv;
|
||||
@ -526,26 +513,6 @@ static void doc2001_readbuf(struct mtd_info *mtd, u_char *buf, int len)
|
||||
buf[i] = ReadDOC(docptr, LastDataRead);
|
||||
}
|
||||
|
||||
static int doc2001_verifybuf(struct mtd_info *mtd, const u_char *buf, int len)
|
||||
{
|
||||
struct nand_chip *this = mtd->priv;
|
||||
struct doc_priv *doc = this->priv;
|
||||
void __iomem *docptr = doc->virtadr;
|
||||
int i;
|
||||
|
||||
/* Start read pipeline */
|
||||
ReadDOC(docptr, ReadPipeInit);
|
||||
|
||||
for (i = 0; i < len - 1; i++)
|
||||
if (buf[i] != ReadDOC(docptr, Mil_CDSN_IO)) {
|
||||
ReadDOC(docptr, LastDataRead);
|
||||
return i;
|
||||
}
|
||||
if (buf[i] != ReadDOC(docptr, LastDataRead))
|
||||
return i;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static u_char doc2001plus_read_byte(struct mtd_info *mtd)
|
||||
{
|
||||
struct nand_chip *this = mtd->priv;
|
||||
@ -610,33 +577,6 @@ static void doc2001plus_readbuf(struct mtd_info *mtd, u_char *buf, int len)
|
||||
printk("\n");
|
||||
}
|
||||
|
||||
static int doc2001plus_verifybuf(struct mtd_info *mtd, const u_char *buf, int len)
|
||||
{
|
||||
struct nand_chip *this = mtd->priv;
|
||||
struct doc_priv *doc = this->priv;
|
||||
void __iomem *docptr = doc->virtadr;
|
||||
int i;
|
||||
|
||||
if (debug)
|
||||
printk("verifybuf of %d bytes: ", len);
|
||||
|
||||
/* Start read pipeline */
|
||||
ReadDOC(docptr, Mplus_ReadPipeInit);
|
||||
ReadDOC(docptr, Mplus_ReadPipeInit);
|
||||
|
||||
for (i = 0; i < len - 2; i++)
|
||||
if (buf[i] != ReadDOC(docptr, Mil_CDSN_IO)) {
|
||||
ReadDOC(docptr, Mplus_LastDataRead);
|
||||
ReadDOC(docptr, Mplus_LastDataRead);
|
||||
return i;
|
||||
}
|
||||
if (buf[len - 2] != ReadDOC(docptr, Mplus_LastDataRead))
|
||||
return len - 2;
|
||||
if (buf[len - 1] != ReadDOC(docptr, Mplus_LastDataRead))
|
||||
return len - 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void doc2001plus_select_chip(struct mtd_info *mtd, int chip)
|
||||
{
|
||||
struct nand_chip *this = mtd->priv;
|
||||
@ -1432,7 +1372,6 @@ static inline int __init doc2000_init(struct mtd_info *mtd)
|
||||
this->read_byte = doc2000_read_byte;
|
||||
this->write_buf = doc2000_writebuf;
|
||||
this->read_buf = doc2000_readbuf;
|
||||
this->verify_buf = doc2000_verifybuf;
|
||||
this->scan_bbt = nftl_scan_bbt;
|
||||
|
||||
doc->CDSNControl = CDSN_CTRL_FLASH_IO | CDSN_CTRL_ECC_IO;
|
||||
@ -1449,7 +1388,6 @@ static inline int __init doc2001_init(struct mtd_info *mtd)
|
||||
this->read_byte = doc2001_read_byte;
|
||||
this->write_buf = doc2001_writebuf;
|
||||
this->read_buf = doc2001_readbuf;
|
||||
this->verify_buf = doc2001_verifybuf;
|
||||
|
||||
ReadDOC(doc->virtadr, ChipID);
|
||||
ReadDOC(doc->virtadr, ChipID);
|
||||
@ -1480,7 +1418,6 @@ static inline int __init doc2001plus_init(struct mtd_info *mtd)
|
||||
this->read_byte = doc2001plus_read_byte;
|
||||
this->write_buf = doc2001plus_writebuf;
|
||||
this->read_buf = doc2001plus_readbuf;
|
||||
this->verify_buf = doc2001plus_verifybuf;
|
||||
this->scan_bbt = inftl_scan_bbt;
|
||||
this->cmd_ctrl = NULL;
|
||||
this->select_chip = doc2001plus_select_chip;
|
||||
|
@ -378,9 +378,9 @@ static int correct_data(struct mtd_info *mtd, uint8_t *buf, int page)
|
||||
* bit flips(s) are not reported in stats.
|
||||
*/
|
||||
|
||||
if (doc->oob_buf[15]) {
|
||||
if (nand->oob_poi[15]) {
|
||||
int bit, numsetbits = 0;
|
||||
unsigned long written_flag = doc->oob_buf[15];
|
||||
unsigned long written_flag = nand->oob_poi[15];
|
||||
for_each_set_bit(bit, &written_flag, 8)
|
||||
numsetbits++;
|
||||
if (numsetbits > 4) { /* assume blank */
|
||||
@ -428,7 +428,7 @@ static int correct_data(struct mtd_info *mtd, uint8_t *buf, int page)
|
||||
/* if error within oob area preceeding ecc bytes... */
|
||||
if (errpos[i] > DOCG4_PAGE_SIZE * 8)
|
||||
change_bit(errpos[i] - DOCG4_PAGE_SIZE * 8,
|
||||
(unsigned long *)doc->oob_buf);
|
||||
(unsigned long *)nand->oob_poi);
|
||||
|
||||
else /* error in page data */
|
||||
change_bit(errpos[i], (unsigned long *)buf);
|
||||
@ -748,18 +748,12 @@ static int read_page(struct mtd_info *mtd, struct nand_chip *nand,
|
||||
|
||||
docg4_read_buf(mtd, buf, DOCG4_PAGE_SIZE); /* read the page data */
|
||||
|
||||
/*
|
||||
* Diskonchips read oob immediately after a page read. Mtd
|
||||
* infrastructure issues a separate command for reading oob after the
|
||||
* page is read. So we save the oob bytes in a local buffer and just
|
||||
* copy it if the next command reads oob from the same page.
|
||||
*/
|
||||
|
||||
/* this device always reads oob after page data */
|
||||
/* first 14 oob bytes read from I/O reg */
|
||||
docg4_read_buf(mtd, doc->oob_buf, 14);
|
||||
docg4_read_buf(mtd, nand->oob_poi, 14);
|
||||
|
||||
/* last 2 read from another reg */
|
||||
buf16 = (uint16_t *)(doc->oob_buf + 14);
|
||||
buf16 = (uint16_t *)(nand->oob_poi + 14);
|
||||
*buf16 = readw(docptr + DOCG4_MYSTERY_REG);
|
||||
|
||||
write_nop(docptr);
|
||||
@ -782,6 +776,8 @@ static int read_page(struct mtd_info *mtd, struct nand_chip *nand,
|
||||
}
|
||||
|
||||
writew(0, docptr + DOC_DATAEND);
|
||||
if (bits_corrected == -EBADMSG) /* uncorrectable errors */
|
||||
return 0;
|
||||
return bits_corrected;
|
||||
}
|
||||
|
||||
@ -807,21 +803,6 @@ static int docg4_read_oob(struct mtd_info *mtd, struct nand_chip *nand,
|
||||
|
||||
dev_dbg(doc->dev, "%s: page %x\n", __func__, page);
|
||||
|
||||
/*
|
||||
* Oob bytes are read as part of a normal page read. If the previous
|
||||
* nand command was a read of the page whose oob is now being read, just
|
||||
* copy the oob bytes that we saved in a local buffer and avoid a
|
||||
* separate oob read.
|
||||
*/
|
||||
if (doc->last_command.command == NAND_CMD_READ0 &&
|
||||
doc->last_command.page == page) {
|
||||
memcpy(nand->oob_poi, doc->oob_buf, 16);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Separate read of oob data only.
|
||||
*/
|
||||
docg4_command(mtd, NAND_CMD_READ0, nand->ecc.size, page);
|
||||
|
||||
writew(DOC_ECCCONF0_READ_MODE | DOCG4_OOB_SIZE, docptr + DOC_ECCCONF0);
|
||||
@ -898,7 +879,7 @@ static void docg4_erase_block(struct mtd_info *mtd, int page)
|
||||
write_nop(docptr);
|
||||
}
|
||||
|
||||
static void write_page(struct mtd_info *mtd, struct nand_chip *nand,
|
||||
static int write_page(struct mtd_info *mtd, struct nand_chip *nand,
|
||||
const uint8_t *buf, bool use_ecc)
|
||||
{
|
||||
struct docg4_priv *doc = nand->priv;
|
||||
@ -950,15 +931,17 @@ static void write_page(struct mtd_info *mtd, struct nand_chip *nand,
|
||||
write_nop(docptr);
|
||||
writew(0, docptr + DOC_DATAEND);
|
||||
write_nop(docptr);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void docg4_write_page_raw(struct mtd_info *mtd, struct nand_chip *nand,
|
||||
static int docg4_write_page_raw(struct mtd_info *mtd, struct nand_chip *nand,
|
||||
const uint8_t *buf, int oob_required)
|
||||
{
|
||||
return write_page(mtd, nand, buf, false);
|
||||
}
|
||||
|
||||
static void docg4_write_page(struct mtd_info *mtd, struct nand_chip *nand,
|
||||
static int docg4_write_page(struct mtd_info *mtd, struct nand_chip *nand,
|
||||
const uint8_t *buf, int oob_required)
|
||||
{
|
||||
return write_page(mtd, nand, buf, true);
|
||||
|
@ -614,41 +614,6 @@ static void fsl_elbc_read_buf(struct mtd_info *mtd, u8 *buf, int len)
|
||||
len, avail);
|
||||
}
|
||||
|
||||
/*
|
||||
* Verify buffer against the FCM Controller Data Buffer
|
||||
*/
|
||||
static int fsl_elbc_verify_buf(struct mtd_info *mtd, const u_char *buf, int len)
|
||||
{
|
||||
struct nand_chip *chip = mtd->priv;
|
||||
struct fsl_elbc_mtd *priv = chip->priv;
|
||||
struct fsl_elbc_fcm_ctrl *elbc_fcm_ctrl = priv->ctrl->nand;
|
||||
int i;
|
||||
|
||||
if (len < 0) {
|
||||
dev_err(priv->dev, "write_buf of %d bytes", len);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if ((unsigned int)len >
|
||||
elbc_fcm_ctrl->read_bytes - elbc_fcm_ctrl->index) {
|
||||
dev_err(priv->dev,
|
||||
"verify_buf beyond end of buffer "
|
||||
"(%d requested, %u available)\n",
|
||||
len, elbc_fcm_ctrl->read_bytes - elbc_fcm_ctrl->index);
|
||||
|
||||
elbc_fcm_ctrl->index = elbc_fcm_ctrl->read_bytes;
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
for (i = 0; i < len; i++)
|
||||
if (in_8(&elbc_fcm_ctrl->addr[elbc_fcm_ctrl->index + i])
|
||||
!= buf[i])
|
||||
break;
|
||||
|
||||
elbc_fcm_ctrl->index += len;
|
||||
return i == len && elbc_fcm_ctrl->status == LTESR_CC ? 0 : -EIO;
|
||||
}
|
||||
|
||||
/* This function is called after Program and Erase Operations to
|
||||
* check for success or failure.
|
||||
*/
|
||||
@ -766,11 +731,13 @@ static int fsl_elbc_read_page(struct mtd_info *mtd, struct nand_chip *chip,
|
||||
/* ECC will be calculated automatically, and errors will be detected in
|
||||
* waitfunc.
|
||||
*/
|
||||
static void fsl_elbc_write_page(struct mtd_info *mtd, struct nand_chip *chip,
|
||||
static int fsl_elbc_write_page(struct mtd_info *mtd, struct nand_chip *chip,
|
||||
const uint8_t *buf, int oob_required)
|
||||
{
|
||||
fsl_elbc_write_buf(mtd, buf, mtd->writesize);
|
||||
fsl_elbc_write_buf(mtd, chip->oob_poi, mtd->oobsize);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int fsl_elbc_chip_init(struct fsl_elbc_mtd *priv)
|
||||
@ -796,7 +763,6 @@ static int fsl_elbc_chip_init(struct fsl_elbc_mtd *priv)
|
||||
chip->read_byte = fsl_elbc_read_byte;
|
||||
chip->write_buf = fsl_elbc_write_buf;
|
||||
chip->read_buf = fsl_elbc_read_buf;
|
||||
chip->verify_buf = fsl_elbc_verify_buf;
|
||||
chip->select_chip = fsl_elbc_select_chip;
|
||||
chip->cmdfunc = fsl_elbc_cmdfunc;
|
||||
chip->waitfunc = fsl_elbc_wait;
|
||||
@ -805,7 +771,6 @@ static int fsl_elbc_chip_init(struct fsl_elbc_mtd *priv)
|
||||
chip->bbt_md = &bbt_mirror_descr;
|
||||
|
||||
/* set up nand options */
|
||||
chip->options = NAND_NO_READRDY;
|
||||
chip->bbt_options = NAND_BBT_USE_FLASH;
|
||||
|
||||
chip->controller = &elbc_fcm_ctrl->controller;
|
||||
@ -916,7 +881,8 @@ static int __devinit fsl_elbc_nand_probe(struct platform_device *pdev)
|
||||
elbc_fcm_ctrl->chips[bank] = priv;
|
||||
priv->bank = bank;
|
||||
priv->ctrl = fsl_lbc_ctrl_dev;
|
||||
priv->dev = dev;
|
||||
priv->dev = &pdev->dev;
|
||||
dev_set_drvdata(priv->dev, priv);
|
||||
|
||||
priv->vbase = ioremap(res.start, resource_size(&res));
|
||||
if (!priv->vbase) {
|
||||
@ -963,11 +929,10 @@ err:
|
||||
|
||||
static int fsl_elbc_nand_remove(struct platform_device *pdev)
|
||||
{
|
||||
int i;
|
||||
struct fsl_elbc_fcm_ctrl *elbc_fcm_ctrl = fsl_lbc_ctrl_dev->nand;
|
||||
for (i = 0; i < MAX_BANKS; i++)
|
||||
if (elbc_fcm_ctrl->chips[i])
|
||||
fsl_elbc_chip_remove(elbc_fcm_ctrl->chips[i]);
|
||||
struct fsl_elbc_mtd *priv = dev_get_drvdata(&pdev->dev);
|
||||
|
||||
fsl_elbc_chip_remove(priv);
|
||||
|
||||
mutex_lock(&fsl_elbc_nand_mutex);
|
||||
elbc_fcm_ctrl->counter--;
|
||||
|
@ -194,7 +194,7 @@ static int is_blank(struct mtd_info *mtd, unsigned int bufnum)
|
||||
struct nand_chip *chip = mtd->priv;
|
||||
struct fsl_ifc_mtd *priv = chip->priv;
|
||||
u8 __iomem *addr = priv->vbase + bufnum * (mtd->writesize * 2);
|
||||
u32 __iomem *mainarea = (u32 *)addr;
|
||||
u32 __iomem *mainarea = (u32 __iomem *)addr;
|
||||
u8 __iomem *oob = addr + mtd->writesize;
|
||||
int i;
|
||||
|
||||
@ -592,8 +592,8 @@ static uint8_t fsl_ifc_read_byte16(struct mtd_info *mtd)
|
||||
* next byte.
|
||||
*/
|
||||
if (ifc_nand_ctrl->index < ifc_nand_ctrl->read_bytes) {
|
||||
data = in_be16((uint16_t *)&ifc_nand_ctrl->
|
||||
addr[ifc_nand_ctrl->index]);
|
||||
data = in_be16((uint16_t __iomem *)&ifc_nand_ctrl->
|
||||
addr[ifc_nand_ctrl->index]);
|
||||
ifc_nand_ctrl->index += 2;
|
||||
return (uint8_t) data;
|
||||
}
|
||||
@ -627,46 +627,6 @@ static void fsl_ifc_read_buf(struct mtd_info *mtd, u8 *buf, int len)
|
||||
__func__, len, avail);
|
||||
}
|
||||
|
||||
/*
|
||||
* Verify buffer against the IFC Controller Data Buffer
|
||||
*/
|
||||
static int fsl_ifc_verify_buf(struct mtd_info *mtd,
|
||||
const u_char *buf, int len)
|
||||
{
|
||||
struct nand_chip *chip = mtd->priv;
|
||||
struct fsl_ifc_mtd *priv = chip->priv;
|
||||
struct fsl_ifc_ctrl *ctrl = priv->ctrl;
|
||||
struct fsl_ifc_nand_ctrl *nctrl = ifc_nand_ctrl;
|
||||
int i;
|
||||
|
||||
if (len < 0) {
|
||||
dev_err(priv->dev, "%s: write_buf of %d bytes", __func__, len);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if ((unsigned int)len > nctrl->read_bytes - nctrl->index) {
|
||||
dev_err(priv->dev,
|
||||
"%s: beyond end of buffer (%d requested, %u available)\n",
|
||||
__func__, len, nctrl->read_bytes - nctrl->index);
|
||||
|
||||
nctrl->index = nctrl->read_bytes;
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
for (i = 0; i < len; i++)
|
||||
if (in_8(&nctrl->addr[nctrl->index + i]) != buf[i])
|
||||
break;
|
||||
|
||||
nctrl->index += len;
|
||||
|
||||
if (i != len)
|
||||
return -EIO;
|
||||
if (ctrl->nand_stat != IFC_NAND_EVTER_STAT_OPC)
|
||||
return -EIO;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* This function is called after Program and Erase Operations to
|
||||
* check for success or failure.
|
||||
@ -722,11 +682,13 @@ static int fsl_ifc_read_page(struct mtd_info *mtd, struct nand_chip *chip,
|
||||
/* ECC will be calculated automatically, and errors will be detected in
|
||||
* waitfunc.
|
||||
*/
|
||||
static void fsl_ifc_write_page(struct mtd_info *mtd, struct nand_chip *chip,
|
||||
static int fsl_ifc_write_page(struct mtd_info *mtd, struct nand_chip *chip,
|
||||
const uint8_t *buf, int oob_required)
|
||||
{
|
||||
fsl_ifc_write_buf(mtd, buf, mtd->writesize);
|
||||
fsl_ifc_write_buf(mtd, chip->oob_poi, mtd->oobsize);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int fsl_ifc_chip_init_tail(struct mtd_info *mtd)
|
||||
@ -844,7 +806,6 @@ static int fsl_ifc_chip_init(struct fsl_ifc_mtd *priv)
|
||||
|
||||
chip->write_buf = fsl_ifc_write_buf;
|
||||
chip->read_buf = fsl_ifc_read_buf;
|
||||
chip->verify_buf = fsl_ifc_verify_buf;
|
||||
chip->select_chip = fsl_ifc_select_chip;
|
||||
chip->cmdfunc = fsl_ifc_cmdfunc;
|
||||
chip->waitfunc = fsl_ifc_wait;
|
||||
@ -855,7 +816,6 @@ static int fsl_ifc_chip_init(struct fsl_ifc_mtd *priv)
|
||||
out_be32(&ifc->ifc_nand.ncfgr, 0x0);
|
||||
|
||||
/* set up nand options */
|
||||
chip->options = NAND_NO_READRDY;
|
||||
chip->bbt_options = NAND_BBT_USE_FLASH;
|
||||
|
||||
|
||||
|
@ -100,23 +100,6 @@ static void gpio_nand_readbuf(struct mtd_info *mtd, u_char *buf, int len)
|
||||
readsb(this->IO_ADDR_R, buf, len);
|
||||
}
|
||||
|
||||
static int gpio_nand_verifybuf(struct mtd_info *mtd, const u_char *buf, int len)
|
||||
{
|
||||
struct nand_chip *this = mtd->priv;
|
||||
unsigned char read, *p = (unsigned char *) buf;
|
||||
int i, err = 0;
|
||||
|
||||
for (i = 0; i < len; i++) {
|
||||
read = readb(this->IO_ADDR_R);
|
||||
if (read != p[i]) {
|
||||
pr_debug("%s: err at %d (read %04x vs %04x)\n",
|
||||
__func__, i, read, p[i]);
|
||||
err = -EFAULT;
|
||||
}
|
||||
}
|
||||
return err;
|
||||
}
|
||||
|
||||
static void gpio_nand_writebuf16(struct mtd_info *mtd, const u_char *buf,
|
||||
int len)
|
||||
{
|
||||
@ -148,26 +131,6 @@ static void gpio_nand_readbuf16(struct mtd_info *mtd, u_char *buf, int len)
|
||||
}
|
||||
}
|
||||
|
||||
static int gpio_nand_verifybuf16(struct mtd_info *mtd, const u_char *buf,
|
||||
int len)
|
||||
{
|
||||
struct nand_chip *this = mtd->priv;
|
||||
unsigned short read, *p = (unsigned short *) buf;
|
||||
int i, err = 0;
|
||||
len >>= 1;
|
||||
|
||||
for (i = 0; i < len; i++) {
|
||||
read = readw(this->IO_ADDR_R);
|
||||
if (read != p[i]) {
|
||||
pr_debug("%s: err at %d (read %04x vs %04x)\n",
|
||||
__func__, i, read, p[i]);
|
||||
err = -EFAULT;
|
||||
}
|
||||
}
|
||||
return err;
|
||||
}
|
||||
|
||||
|
||||
static int gpio_nand_devready(struct mtd_info *mtd)
|
||||
{
|
||||
struct gpiomtd *gpiomtd = gpio_nand_getpriv(mtd);
|
||||
@ -391,11 +354,9 @@ static int __devinit gpio_nand_probe(struct platform_device *dev)
|
||||
if (this->options & NAND_BUSWIDTH_16) {
|
||||
this->read_buf = gpio_nand_readbuf16;
|
||||
this->write_buf = gpio_nand_writebuf16;
|
||||
this->verify_buf = gpio_nand_verifybuf16;
|
||||
} else {
|
||||
this->read_buf = gpio_nand_readbuf;
|
||||
this->write_buf = gpio_nand_writebuf;
|
||||
this->verify_buf = gpio_nand_verifybuf;
|
||||
}
|
||||
|
||||
/* set the mtd private data for the nand driver */
|
||||
@ -456,20 +417,7 @@ static struct platform_driver gpio_nand_driver = {
|
||||
},
|
||||
};
|
||||
|
||||
static int __init gpio_nand_init(void)
|
||||
{
|
||||
printk(KERN_INFO "GPIO NAND driver, © 2004 Simtec Electronics\n");
|
||||
|
||||
return platform_driver_register(&gpio_nand_driver);
|
||||
}
|
||||
|
||||
static void __exit gpio_nand_exit(void)
|
||||
{
|
||||
platform_driver_unregister(&gpio_nand_driver);
|
||||
}
|
||||
|
||||
module_init(gpio_nand_init);
|
||||
module_exit(gpio_nand_exit);
|
||||
module_platform_driver(gpio_nand_driver);
|
||||
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_AUTHOR("Ben Dooks <ben@simtec.co.uk>");
|
||||
|
@ -26,7 +26,7 @@
|
||||
#include "gpmi-regs.h"
|
||||
#include "bch-regs.h"
|
||||
|
||||
struct timing_threshod timing_default_threshold = {
|
||||
static struct timing_threshod timing_default_threshold = {
|
||||
.max_data_setup_cycles = (BM_GPMI_TIMING0_DATA_SETUP >>
|
||||
BP_GPMI_TIMING0_DATA_SETUP),
|
||||
.internal_data_setup_in_ns = 0,
|
||||
@ -124,12 +124,42 @@ error:
|
||||
return -ETIMEDOUT;
|
||||
}
|
||||
|
||||
static int __gpmi_enable_clk(struct gpmi_nand_data *this, bool v)
|
||||
{
|
||||
struct clk *clk;
|
||||
int ret;
|
||||
int i;
|
||||
|
||||
for (i = 0; i < GPMI_CLK_MAX; i++) {
|
||||
clk = this->resources.clock[i];
|
||||
if (!clk)
|
||||
break;
|
||||
|
||||
if (v) {
|
||||
ret = clk_prepare_enable(clk);
|
||||
if (ret)
|
||||
goto err_clk;
|
||||
} else {
|
||||
clk_disable_unprepare(clk);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
|
||||
err_clk:
|
||||
for (; i > 0; i--)
|
||||
clk_disable_unprepare(this->resources.clock[i - 1]);
|
||||
return ret;
|
||||
}
|
||||
|
||||
#define gpmi_enable_clk(x) __gpmi_enable_clk(x, true)
|
||||
#define gpmi_disable_clk(x) __gpmi_enable_clk(x, false)
|
||||
|
||||
int gpmi_init(struct gpmi_nand_data *this)
|
||||
{
|
||||
struct resources *r = &this->resources;
|
||||
int ret;
|
||||
|
||||
ret = clk_prepare_enable(r->clock);
|
||||
ret = gpmi_enable_clk(this);
|
||||
if (ret)
|
||||
goto err_out;
|
||||
ret = gpmi_reset_block(r->gpmi_regs, false);
|
||||
@ -149,7 +179,7 @@ int gpmi_init(struct gpmi_nand_data *this)
|
||||
/* Select BCH ECC. */
|
||||
writel(BM_GPMI_CTRL1_BCH_MODE, r->gpmi_regs + HW_GPMI_CTRL1_SET);
|
||||
|
||||
clk_disable_unprepare(r->clock);
|
||||
gpmi_disable_clk(this);
|
||||
return 0;
|
||||
err_out:
|
||||
return ret;
|
||||
@ -205,7 +235,7 @@ int bch_set_geometry(struct gpmi_nand_data *this)
|
||||
ecc_strength = bch_geo->ecc_strength >> 1;
|
||||
page_size = bch_geo->page_size;
|
||||
|
||||
ret = clk_prepare_enable(r->clock);
|
||||
ret = gpmi_enable_clk(this);
|
||||
if (ret)
|
||||
goto err_out;
|
||||
|
||||
@ -240,7 +270,7 @@ int bch_set_geometry(struct gpmi_nand_data *this)
|
||||
writel(BM_BCH_CTRL_COMPLETE_IRQ_EN,
|
||||
r->bch_regs + HW_BCH_CTRL_SET);
|
||||
|
||||
clk_disable_unprepare(r->clock);
|
||||
gpmi_disable_clk(this);
|
||||
return 0;
|
||||
err_out:
|
||||
return ret;
|
||||
@ -263,6 +293,7 @@ static int gpmi_nfc_compute_hardware_timing(struct gpmi_nand_data *this,
|
||||
struct gpmi_nfc_hardware_timing *hw)
|
||||
{
|
||||
struct timing_threshod *nfc = &timing_default_threshold;
|
||||
struct resources *r = &this->resources;
|
||||
struct nand_chip *nand = &this->nand;
|
||||
struct nand_timing target = this->timing;
|
||||
bool improved_timing_is_available;
|
||||
@ -302,8 +333,9 @@ static int gpmi_nfc_compute_hardware_timing(struct gpmi_nand_data *this,
|
||||
(target.tRHOH_in_ns >= 0) ;
|
||||
|
||||
/* Inspect the clock. */
|
||||
nfc->clock_frequency_in_hz = clk_get_rate(r->clock[0]);
|
||||
clock_frequency_in_hz = nfc->clock_frequency_in_hz;
|
||||
clock_period_in_ns = 1000000000 / clock_frequency_in_hz;
|
||||
clock_period_in_ns = NSEC_PER_SEC / clock_frequency_in_hz;
|
||||
|
||||
/*
|
||||
* The NFC quantizes setup and hold parameters in terms of clock cycles.
|
||||
@ -698,17 +730,230 @@ return_results:
|
||||
hw->address_setup_in_cycles = address_setup_in_cycles;
|
||||
hw->use_half_periods = dll_use_half_periods;
|
||||
hw->sample_delay_factor = sample_delay_factor;
|
||||
hw->device_busy_timeout = GPMI_DEFAULT_BUSY_TIMEOUT;
|
||||
hw->wrn_dly_sel = BV_GPMI_CTRL1_WRN_DLY_SEL_4_TO_8NS;
|
||||
|
||||
/* Return success. */
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* <1> Firstly, we should know what's the GPMI-clock means.
|
||||
* The GPMI-clock is the internal clock in the gpmi nand controller.
|
||||
* If you set 100MHz to gpmi nand controller, the GPMI-clock's period
|
||||
* is 10ns. Mark the GPMI-clock's period as GPMI-clock-period.
|
||||
*
|
||||
* <2> Secondly, we should know what's the frequency on the nand chip pins.
|
||||
* The frequency on the nand chip pins is derived from the GPMI-clock.
|
||||
* We can get it from the following equation:
|
||||
*
|
||||
* F = G / (DS + DH)
|
||||
*
|
||||
* F : the frequency on the nand chip pins.
|
||||
* G : the GPMI clock, such as 100MHz.
|
||||
* DS : GPMI_HW_GPMI_TIMING0:DATA_SETUP
|
||||
* DH : GPMI_HW_GPMI_TIMING0:DATA_HOLD
|
||||
*
|
||||
* <3> Thirdly, when the frequency on the nand chip pins is above 33MHz,
|
||||
* the nand EDO(extended Data Out) timing could be applied.
|
||||
* The GPMI implements a feedback read strobe to sample the read data.
|
||||
* The feedback read strobe can be delayed to support the nand EDO timing
|
||||
* where the read strobe may deasserts before the read data is valid, and
|
||||
* read data is valid for some time after read strobe.
|
||||
*
|
||||
* The following figure illustrates some aspects of a NAND Flash read:
|
||||
*
|
||||
* |<---tREA---->|
|
||||
* | |
|
||||
* | | |
|
||||
* |<--tRP-->| |
|
||||
* | | |
|
||||
* __ ___|__________________________________
|
||||
* RDN \________/ |
|
||||
* |
|
||||
* /---------\
|
||||
* Read Data --------------< >---------
|
||||
* \---------/
|
||||
* | |
|
||||
* |<-D->|
|
||||
* FeedbackRDN ________ ____________
|
||||
* \___________/
|
||||
*
|
||||
* D stands for delay, set in the HW_GPMI_CTRL1:RDN_DELAY.
|
||||
*
|
||||
*
|
||||
* <4> Now, we begin to describe how to compute the right RDN_DELAY.
|
||||
*
|
||||
* 4.1) From the aspect of the nand chip pins:
|
||||
* Delay = (tREA + C - tRP) {1}
|
||||
*
|
||||
* tREA : the maximum read access time. From the ONFI nand standards,
|
||||
* we know that tREA is 16ns in mode 5, tREA is 20ns is mode 4.
|
||||
* Please check it in : www.onfi.org
|
||||
* C : a constant for adjust the delay. default is 4.
|
||||
* tRP : the read pulse width.
|
||||
* Specified by the HW_GPMI_TIMING0:DATA_SETUP:
|
||||
* tRP = (GPMI-clock-period) * DATA_SETUP
|
||||
*
|
||||
* 4.2) From the aspect of the GPMI nand controller:
|
||||
* Delay = RDN_DELAY * 0.125 * RP {2}
|
||||
*
|
||||
* RP : the DLL reference period.
|
||||
* if (GPMI-clock-period > DLL_THRETHOLD)
|
||||
* RP = GPMI-clock-period / 2;
|
||||
* else
|
||||
* RP = GPMI-clock-period;
|
||||
*
|
||||
* Set the HW_GPMI_CTRL1:HALF_PERIOD if GPMI-clock-period
|
||||
* is greater DLL_THRETHOLD. In other SOCs, the DLL_THRETHOLD
|
||||
* is 16ns, but in mx6q, we use 12ns.
|
||||
*
|
||||
* 4.3) since {1} equals {2}, we get:
|
||||
*
|
||||
* (tREA + 4 - tRP) * 8
|
||||
* RDN_DELAY = --------------------- {3}
|
||||
* RP
|
||||
*
|
||||
* 4.4) We only support the fastest asynchronous mode of ONFI nand.
|
||||
* For some ONFI nand, the mode 4 is the fastest mode;
|
||||
* while for some ONFI nand, the mode 5 is the fastest mode.
|
||||
* So we only support the mode 4 and mode 5. It is no need to
|
||||
* support other modes.
|
||||
*/
|
||||
static void gpmi_compute_edo_timing(struct gpmi_nand_data *this,
|
||||
struct gpmi_nfc_hardware_timing *hw)
|
||||
{
|
||||
struct resources *r = &this->resources;
|
||||
unsigned long rate = clk_get_rate(r->clock[0]);
|
||||
int mode = this->timing_mode;
|
||||
int dll_threshold = 16; /* in ns */
|
||||
unsigned long delay;
|
||||
unsigned long clk_period;
|
||||
int t_rea;
|
||||
int c = 4;
|
||||
int t_rp;
|
||||
int rp;
|
||||
|
||||
/*
|
||||
* [1] for GPMI_HW_GPMI_TIMING0:
|
||||
* The async mode requires 40MHz for mode 4, 50MHz for mode 5.
|
||||
* The GPMI can support 100MHz at most. So if we want to
|
||||
* get the 40MHz or 50MHz, we have to set DS=1, DH=1.
|
||||
* Set the ADDRESS_SETUP to 0 in mode 4.
|
||||
*/
|
||||
hw->data_setup_in_cycles = 1;
|
||||
hw->data_hold_in_cycles = 1;
|
||||
hw->address_setup_in_cycles = ((mode == 5) ? 1 : 0);
|
||||
|
||||
/* [2] for GPMI_HW_GPMI_TIMING1 */
|
||||
hw->device_busy_timeout = 0x9000;
|
||||
|
||||
/* [3] for GPMI_HW_GPMI_CTRL1 */
|
||||
hw->wrn_dly_sel = BV_GPMI_CTRL1_WRN_DLY_SEL_NO_DELAY;
|
||||
|
||||
if (GPMI_IS_MX6Q(this))
|
||||
dll_threshold = 12;
|
||||
|
||||
/*
|
||||
* Enlarge 10 times for the numerator and denominator in {3}.
|
||||
* This make us to get more accurate result.
|
||||
*/
|
||||
clk_period = NSEC_PER_SEC / (rate / 10);
|
||||
dll_threshold *= 10;
|
||||
t_rea = ((mode == 5) ? 16 : 20) * 10;
|
||||
c *= 10;
|
||||
|
||||
t_rp = clk_period * 1; /* DATA_SETUP is 1 */
|
||||
|
||||
if (clk_period > dll_threshold) {
|
||||
hw->use_half_periods = 1;
|
||||
rp = clk_period / 2;
|
||||
} else {
|
||||
hw->use_half_periods = 0;
|
||||
rp = clk_period;
|
||||
}
|
||||
|
||||
/*
|
||||
* Multiply the numerator with 10, we could do a round off:
|
||||
* 7.8 round up to 8; 7.4 round down to 7.
|
||||
*/
|
||||
delay = (((t_rea + c - t_rp) * 8) * 10) / rp;
|
||||
delay = (delay + 5) / 10;
|
||||
|
||||
hw->sample_delay_factor = delay;
|
||||
}
|
||||
|
||||
static int enable_edo_mode(struct gpmi_nand_data *this, int mode)
|
||||
{
|
||||
struct resources *r = &this->resources;
|
||||
struct nand_chip *nand = &this->nand;
|
||||
struct mtd_info *mtd = &this->mtd;
|
||||
uint8_t feature[ONFI_SUBFEATURE_PARAM_LEN] = {};
|
||||
unsigned long rate;
|
||||
int ret;
|
||||
|
||||
nand->select_chip(mtd, 0);
|
||||
|
||||
/* [1] send SET FEATURE commond to NAND */
|
||||
feature[0] = mode;
|
||||
ret = nand->onfi_set_features(mtd, nand,
|
||||
ONFI_FEATURE_ADDR_TIMING_MODE, feature);
|
||||
if (ret)
|
||||
goto err_out;
|
||||
|
||||
/* [2] send GET FEATURE command to double-check the timing mode */
|
||||
memset(feature, 0, ONFI_SUBFEATURE_PARAM_LEN);
|
||||
ret = nand->onfi_get_features(mtd, nand,
|
||||
ONFI_FEATURE_ADDR_TIMING_MODE, feature);
|
||||
if (ret || feature[0] != mode)
|
||||
goto err_out;
|
||||
|
||||
nand->select_chip(mtd, -1);
|
||||
|
||||
/* [3] set the main IO clock, 100MHz for mode 5, 80MHz for mode 4. */
|
||||
rate = (mode == 5) ? 100000000 : 80000000;
|
||||
clk_set_rate(r->clock[0], rate);
|
||||
|
||||
/* Let the gpmi_begin() re-compute the timing again. */
|
||||
this->flags &= ~GPMI_TIMING_INIT_OK;
|
||||
|
||||
this->flags |= GPMI_ASYNC_EDO_ENABLED;
|
||||
this->timing_mode = mode;
|
||||
dev_info(this->dev, "enable the asynchronous EDO mode %d\n", mode);
|
||||
return 0;
|
||||
|
||||
err_out:
|
||||
nand->select_chip(mtd, -1);
|
||||
dev_err(this->dev, "mode:%d ,failed in set feature.\n", mode);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
int gpmi_extra_init(struct gpmi_nand_data *this)
|
||||
{
|
||||
struct nand_chip *chip = &this->nand;
|
||||
|
||||
/* Enable the asynchronous EDO feature. */
|
||||
if (GPMI_IS_MX6Q(this) && chip->onfi_version) {
|
||||
int mode = onfi_get_async_timing_mode(chip);
|
||||
|
||||
/* We only support the timing mode 4 and mode 5. */
|
||||
if (mode & ONFI_TIMING_MODE_5)
|
||||
mode = 5;
|
||||
else if (mode & ONFI_TIMING_MODE_4)
|
||||
mode = 4;
|
||||
else
|
||||
return 0;
|
||||
|
||||
return enable_edo_mode(this, mode);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Begin the I/O */
|
||||
void gpmi_begin(struct gpmi_nand_data *this)
|
||||
{
|
||||
struct resources *r = &this->resources;
|
||||
struct timing_threshod *nfc = &timing_default_threshold;
|
||||
unsigned char *gpmi_regs = r->gpmi_regs;
|
||||
void __iomem *gpmi_regs = r->gpmi_regs;
|
||||
unsigned int clock_period_in_ns;
|
||||
uint32_t reg;
|
||||
unsigned int dll_wait_time_in_us;
|
||||
@ -716,60 +961,66 @@ void gpmi_begin(struct gpmi_nand_data *this)
|
||||
int ret;
|
||||
|
||||
/* Enable the clock. */
|
||||
ret = clk_prepare_enable(r->clock);
|
||||
ret = gpmi_enable_clk(this);
|
||||
if (ret) {
|
||||
pr_err("We failed in enable the clk\n");
|
||||
goto err_out;
|
||||
}
|
||||
|
||||
/* set ready/busy timeout */
|
||||
writel(0x500 << BP_GPMI_TIMING1_BUSY_TIMEOUT,
|
||||
gpmi_regs + HW_GPMI_TIMING1);
|
||||
/* Only initialize the timing once */
|
||||
if (this->flags & GPMI_TIMING_INIT_OK)
|
||||
return;
|
||||
this->flags |= GPMI_TIMING_INIT_OK;
|
||||
|
||||
/* Get the timing information we need. */
|
||||
nfc->clock_frequency_in_hz = clk_get_rate(r->clock);
|
||||
clock_period_in_ns = 1000000000 / nfc->clock_frequency_in_hz;
|
||||
if (this->flags & GPMI_ASYNC_EDO_ENABLED)
|
||||
gpmi_compute_edo_timing(this, &hw);
|
||||
else
|
||||
gpmi_nfc_compute_hardware_timing(this, &hw);
|
||||
|
||||
gpmi_nfc_compute_hardware_timing(this, &hw);
|
||||
|
||||
/* Set up all the simple timing parameters. */
|
||||
/* [1] Set HW_GPMI_TIMING0 */
|
||||
reg = BF_GPMI_TIMING0_ADDRESS_SETUP(hw.address_setup_in_cycles) |
|
||||
BF_GPMI_TIMING0_DATA_HOLD(hw.data_hold_in_cycles) |
|
||||
BF_GPMI_TIMING0_DATA_SETUP(hw.data_setup_in_cycles) ;
|
||||
|
||||
writel(reg, gpmi_regs + HW_GPMI_TIMING0);
|
||||
|
||||
/*
|
||||
* DLL_ENABLE must be set to 0 when setting RDN_DELAY or HALF_PERIOD.
|
||||
*/
|
||||
/* [2] Set HW_GPMI_TIMING1 */
|
||||
writel(BF_GPMI_TIMING1_BUSY_TIMEOUT(hw.device_busy_timeout),
|
||||
gpmi_regs + HW_GPMI_TIMING1);
|
||||
|
||||
/* [3] The following code is to set the HW_GPMI_CTRL1. */
|
||||
|
||||
/* Set the WRN_DLY_SEL */
|
||||
writel(BM_GPMI_CTRL1_WRN_DLY_SEL, gpmi_regs + HW_GPMI_CTRL1_CLR);
|
||||
writel(BF_GPMI_CTRL1_WRN_DLY_SEL(hw.wrn_dly_sel),
|
||||
gpmi_regs + HW_GPMI_CTRL1_SET);
|
||||
|
||||
/* DLL_ENABLE must be set to 0 when setting RDN_DELAY or HALF_PERIOD. */
|
||||
writel(BM_GPMI_CTRL1_DLL_ENABLE, gpmi_regs + HW_GPMI_CTRL1_CLR);
|
||||
|
||||
/* Clear out the DLL control fields. */
|
||||
writel(BM_GPMI_CTRL1_RDN_DELAY, gpmi_regs + HW_GPMI_CTRL1_CLR);
|
||||
writel(BM_GPMI_CTRL1_HALF_PERIOD, gpmi_regs + HW_GPMI_CTRL1_CLR);
|
||||
reg = BM_GPMI_CTRL1_RDN_DELAY | BM_GPMI_CTRL1_HALF_PERIOD;
|
||||
writel(reg, gpmi_regs + HW_GPMI_CTRL1_CLR);
|
||||
|
||||
/* If no sample delay is called for, return immediately. */
|
||||
if (!hw.sample_delay_factor)
|
||||
return;
|
||||
|
||||
/* Configure the HALF_PERIOD flag. */
|
||||
if (hw.use_half_periods)
|
||||
writel(BM_GPMI_CTRL1_HALF_PERIOD,
|
||||
gpmi_regs + HW_GPMI_CTRL1_SET);
|
||||
/* Set RDN_DELAY or HALF_PERIOD. */
|
||||
reg = ((hw.use_half_periods) ? BM_GPMI_CTRL1_HALF_PERIOD : 0)
|
||||
| BF_GPMI_CTRL1_RDN_DELAY(hw.sample_delay_factor);
|
||||
|
||||
/* Set the delay factor. */
|
||||
writel(BF_GPMI_CTRL1_RDN_DELAY(hw.sample_delay_factor),
|
||||
gpmi_regs + HW_GPMI_CTRL1_SET);
|
||||
writel(reg, gpmi_regs + HW_GPMI_CTRL1_SET);
|
||||
|
||||
/* Enable the DLL. */
|
||||
/* At last, we enable the DLL. */
|
||||
writel(BM_GPMI_CTRL1_DLL_ENABLE, gpmi_regs + HW_GPMI_CTRL1_SET);
|
||||
|
||||
/*
|
||||
* After we enable the GPMI DLL, we have to wait 64 clock cycles before
|
||||
* we can use the GPMI.
|
||||
*
|
||||
* Calculate the amount of time we need to wait, in microseconds.
|
||||
* we can use the GPMI. Calculate the amount of time we need to wait,
|
||||
* in microseconds.
|
||||
*/
|
||||
clock_period_in_ns = NSEC_PER_SEC / clk_get_rate(r->clock[0]);
|
||||
dll_wait_time_in_us = (clock_period_in_ns * 64) / 1000;
|
||||
|
||||
if (!dll_wait_time_in_us)
|
||||
@ -784,8 +1035,7 @@ err_out:
|
||||
|
||||
void gpmi_end(struct gpmi_nand_data *this)
|
||||
{
|
||||
struct resources *r = &this->resources;
|
||||
clk_disable_unprepare(r->clock);
|
||||
gpmi_disable_clk(this);
|
||||
}
|
||||
|
||||
/* Clears a BCH interrupt. */
|
||||
|
@ -18,6 +18,9 @@
|
||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
|
||||
|
||||
#include <linux/clk.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/interrupt.h>
|
||||
@ -27,6 +30,7 @@
|
||||
#include <linux/pinctrl/consumer.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/of_device.h>
|
||||
#include <linux/of_mtd.h>
|
||||
#include "gpmi-nand.h"
|
||||
|
||||
/* add our owner bbt descriptor */
|
||||
@ -113,7 +117,7 @@ int common_nfc_set_geometry(struct gpmi_nand_data *this)
|
||||
/* We use the same ECC strength for all chunks. */
|
||||
geo->ecc_strength = get_ecc_strength(this);
|
||||
if (!geo->ecc_strength) {
|
||||
pr_err("We get a wrong ECC strength.\n");
|
||||
pr_err("wrong ECC strength.\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
@ -316,7 +320,7 @@ acquire_register_block(struct gpmi_nand_data *this, const char *res_name)
|
||||
struct platform_device *pdev = this->pdev;
|
||||
struct resources *res = &this->resources;
|
||||
struct resource *r;
|
||||
void *p;
|
||||
void __iomem *p;
|
||||
|
||||
r = platform_get_resource_byname(pdev, IORESOURCE_MEM, res_name);
|
||||
if (!r) {
|
||||
@ -423,8 +427,8 @@ static int __devinit acquire_dma_channels(struct gpmi_nand_data *this)
|
||||
struct platform_device *pdev = this->pdev;
|
||||
struct resource *r_dma;
|
||||
struct device_node *dn;
|
||||
int dma_channel;
|
||||
unsigned int ret;
|
||||
u32 dma_channel;
|
||||
int ret;
|
||||
struct dma_chan *dma_chan;
|
||||
dma_cap_mask_t mask;
|
||||
|
||||
@ -464,9 +468,73 @@ acquire_err:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
static void gpmi_put_clks(struct gpmi_nand_data *this)
|
||||
{
|
||||
struct resources *r = &this->resources;
|
||||
struct clk *clk;
|
||||
int i;
|
||||
|
||||
for (i = 0; i < GPMI_CLK_MAX; i++) {
|
||||
clk = r->clock[i];
|
||||
if (clk) {
|
||||
clk_put(clk);
|
||||
r->clock[i] = NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static char *extra_clks_for_mx6q[GPMI_CLK_MAX] = {
|
||||
"gpmi_apb", "gpmi_bch", "gpmi_bch_apb", "per1_bch",
|
||||
};
|
||||
|
||||
static int __devinit gpmi_get_clks(struct gpmi_nand_data *this)
|
||||
{
|
||||
struct resources *r = &this->resources;
|
||||
char **extra_clks = NULL;
|
||||
struct clk *clk;
|
||||
int i;
|
||||
|
||||
/* The main clock is stored in the first. */
|
||||
r->clock[0] = clk_get(this->dev, "gpmi_io");
|
||||
if (IS_ERR(r->clock[0]))
|
||||
goto err_clock;
|
||||
|
||||
/* Get extra clocks */
|
||||
if (GPMI_IS_MX6Q(this))
|
||||
extra_clks = extra_clks_for_mx6q;
|
||||
if (!extra_clks)
|
||||
return 0;
|
||||
|
||||
for (i = 1; i < GPMI_CLK_MAX; i++) {
|
||||
if (extra_clks[i - 1] == NULL)
|
||||
break;
|
||||
|
||||
clk = clk_get(this->dev, extra_clks[i - 1]);
|
||||
if (IS_ERR(clk))
|
||||
goto err_clock;
|
||||
|
||||
r->clock[i] = clk;
|
||||
}
|
||||
|
||||
if (GPMI_IS_MX6Q(this))
|
||||
/*
|
||||
* Set the default value for the gpmi clock in mx6q:
|
||||
*
|
||||
* If you want to use the ONFI nand which is in the
|
||||
* Synchronous Mode, you should change the clock as you need.
|
||||
*/
|
||||
clk_set_rate(r->clock[0], 22000000);
|
||||
|
||||
return 0;
|
||||
|
||||
err_clock:
|
||||
dev_dbg(this->dev, "failed in finding the clocks.\n");
|
||||
gpmi_put_clks(this);
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
static int __devinit acquire_resources(struct gpmi_nand_data *this)
|
||||
{
|
||||
struct resources *res = &this->resources;
|
||||
struct pinctrl *pinctrl;
|
||||
int ret;
|
||||
|
||||
@ -492,12 +560,9 @@ static int __devinit acquire_resources(struct gpmi_nand_data *this)
|
||||
goto exit_pin;
|
||||
}
|
||||
|
||||
res->clock = clk_get(&this->pdev->dev, NULL);
|
||||
if (IS_ERR(res->clock)) {
|
||||
pr_err("can not get the clock\n");
|
||||
ret = -ENOENT;
|
||||
ret = gpmi_get_clks(this);
|
||||
if (ret)
|
||||
goto exit_clock;
|
||||
}
|
||||
return 0;
|
||||
|
||||
exit_clock:
|
||||
@ -512,9 +577,7 @@ exit_regs:
|
||||
|
||||
static void release_resources(struct gpmi_nand_data *this)
|
||||
{
|
||||
struct resources *r = &this->resources;
|
||||
|
||||
clk_put(r->clock);
|
||||
gpmi_put_clks(this);
|
||||
release_register_block(this);
|
||||
release_bch_irq(this);
|
||||
release_dma_channels(this);
|
||||
@ -667,12 +730,12 @@ static int gpmi_alloc_dma_buffer(struct gpmi_nand_data *this)
|
||||
struct device *dev = this->dev;
|
||||
|
||||
/* [1] Allocate a command buffer. PAGE_SIZE is enough. */
|
||||
this->cmd_buffer = kzalloc(PAGE_SIZE, GFP_DMA);
|
||||
this->cmd_buffer = kzalloc(PAGE_SIZE, GFP_DMA | GFP_KERNEL);
|
||||
if (this->cmd_buffer == NULL)
|
||||
goto error_alloc;
|
||||
|
||||
/* [2] Allocate a read/write data buffer. PAGE_SIZE is enough. */
|
||||
this->data_buffer_dma = kzalloc(PAGE_SIZE, GFP_DMA);
|
||||
this->data_buffer_dma = kzalloc(PAGE_SIZE, GFP_DMA | GFP_KERNEL);
|
||||
if (this->data_buffer_dma == NULL)
|
||||
goto error_alloc;
|
||||
|
||||
@ -930,7 +993,7 @@ exit_nfc:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void gpmi_ecc_write_page(struct mtd_info *mtd, struct nand_chip *chip,
|
||||
static int gpmi_ecc_write_page(struct mtd_info *mtd, struct nand_chip *chip,
|
||||
const uint8_t *buf, int oob_required)
|
||||
{
|
||||
struct gpmi_nand_data *this = chip->priv;
|
||||
@ -972,7 +1035,7 @@ static void gpmi_ecc_write_page(struct mtd_info *mtd, struct nand_chip *chip,
|
||||
&payload_virt, &payload_phys);
|
||||
if (ret) {
|
||||
pr_err("Inadequate payload DMA buffer\n");
|
||||
return;
|
||||
return 0;
|
||||
}
|
||||
|
||||
ret = send_page_prepare(this,
|
||||
@ -1002,6 +1065,8 @@ exit_auxiliary:
|
||||
nfc_geo->payload_size,
|
||||
payload_virt, payload_phys);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
@ -1064,6 +1129,9 @@ exit_auxiliary:
|
||||
* ECC-based or raw view of the page is implicit in which function it calls
|
||||
* (there is a similar pair of ECC-based/raw functions for writing).
|
||||
*
|
||||
* FIXME: The following paragraph is incorrect, now that there exist
|
||||
* ecc.read_oob_raw and ecc.write_oob_raw functions.
|
||||
*
|
||||
* Since MTD assumes the OOB is not covered by ECC, there is no pair of
|
||||
* ECC-based/raw functions for reading or or writing the OOB. The fact that the
|
||||
* caller wants an ECC-based or raw view of the page is not propagated down to
|
||||
@ -1190,7 +1258,6 @@ static int mx23_check_transcription_stamp(struct gpmi_nand_data *this)
|
||||
unsigned int search_area_size_in_strides;
|
||||
unsigned int stride;
|
||||
unsigned int page;
|
||||
loff_t byte;
|
||||
uint8_t *buffer = chip->buffers->databuf;
|
||||
int saved_chip_number;
|
||||
int found_an_ncb_fingerprint = false;
|
||||
@ -1207,9 +1274,8 @@ static int mx23_check_transcription_stamp(struct gpmi_nand_data *this)
|
||||
dev_dbg(dev, "Scanning for an NCB fingerprint...\n");
|
||||
|
||||
for (stride = 0; stride < search_area_size_in_strides; stride++) {
|
||||
/* Compute the page and byte addresses. */
|
||||
/* Compute the page addresses. */
|
||||
page = stride * rom_geo->stride_size_in_pages;
|
||||
byte = page * mtd->writesize;
|
||||
|
||||
dev_dbg(dev, "Looking for a fingerprint in page 0x%x\n", page);
|
||||
|
||||
@ -1251,7 +1317,6 @@ static int mx23_write_transcription_stamp(struct gpmi_nand_data *this)
|
||||
unsigned int block;
|
||||
unsigned int stride;
|
||||
unsigned int page;
|
||||
loff_t byte;
|
||||
uint8_t *buffer = chip->buffers->databuf;
|
||||
int saved_chip_number;
|
||||
int status;
|
||||
@ -1300,9 +1365,8 @@ static int mx23_write_transcription_stamp(struct gpmi_nand_data *this)
|
||||
/* Loop through the first search area, writing NCB fingerprints. */
|
||||
dev_dbg(dev, "Writing NCB fingerprints...\n");
|
||||
for (stride = 0; stride < search_area_size_in_strides; stride++) {
|
||||
/* Compute the page and byte addresses. */
|
||||
/* Compute the page addresses. */
|
||||
page = stride * rom_geo->stride_size_in_pages;
|
||||
byte = page * mtd->writesize;
|
||||
|
||||
/* Write the first page of the current stride. */
|
||||
dev_dbg(dev, "Writing an NCB fingerprint in page 0x%x\n", page);
|
||||
@ -1436,6 +1500,7 @@ static int gpmi_pre_bbt_scan(struct gpmi_nand_data *this)
|
||||
/* Adjust the ECC strength according to the chip. */
|
||||
this->nand.ecc.strength = this->bch_geometry.ecc_strength;
|
||||
this->mtd.ecc_strength = this->bch_geometry.ecc_strength;
|
||||
this->mtd.bitflip_threshold = this->bch_geometry.ecc_strength;
|
||||
|
||||
/* NAND boot init, depends on the gpmi_set_geometry(). */
|
||||
return nand_boot_init(this);
|
||||
@ -1452,11 +1517,19 @@ static int gpmi_scan_bbt(struct mtd_info *mtd)
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/*
|
||||
* Can we enable the extra features? such as EDO or Sync mode.
|
||||
*
|
||||
* We do not check the return value now. That's means if we fail in
|
||||
* enable the extra features, we still can run in the normal way.
|
||||
*/
|
||||
gpmi_extra_init(this);
|
||||
|
||||
/* use the default BBT implementation */
|
||||
return nand_default_bbt(mtd);
|
||||
}
|
||||
|
||||
void gpmi_nfc_exit(struct gpmi_nand_data *this)
|
||||
static void gpmi_nfc_exit(struct gpmi_nand_data *this)
|
||||
{
|
||||
nand_release(&this->mtd);
|
||||
gpmi_free_dma_buffer(this);
|
||||
@ -1497,6 +1570,8 @@ static int __devinit gpmi_nfc_init(struct gpmi_nand_data *this)
|
||||
chip->ecc.size = 1;
|
||||
chip->ecc.strength = 8;
|
||||
chip->ecc.layout = &gpmi_hw_ecclayout;
|
||||
if (of_get_nand_on_flash_bbt(this->dev->of_node))
|
||||
chip->bbt_options |= NAND_BBT_USE_FLASH | NAND_BBT_NO_OOB;
|
||||
|
||||
/* Allocate a temporary DMA buffer for reading ID in the nand_scan() */
|
||||
this->bch_geometry.payload_size = 1024;
|
||||
@ -1579,6 +1654,8 @@ static int __devinit gpmi_nand_probe(struct platform_device *pdev)
|
||||
if (ret)
|
||||
goto exit_nfc_init;
|
||||
|
||||
dev_info(this->dev, "driver registered.\n");
|
||||
|
||||
return 0;
|
||||
|
||||
exit_nfc_init:
|
||||
@ -1586,10 +1663,12 @@ exit_nfc_init:
|
||||
exit_acquire_resources:
|
||||
platform_set_drvdata(pdev, NULL);
|
||||
kfree(this);
|
||||
dev_err(this->dev, "driver registration failed: %d\n", ret);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int __exit gpmi_nand_remove(struct platform_device *pdev)
|
||||
static int __devexit gpmi_nand_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct gpmi_nand_data *this = platform_get_drvdata(pdev);
|
||||
|
||||
@ -1606,29 +1685,10 @@ static struct platform_driver gpmi_nand_driver = {
|
||||
.of_match_table = gpmi_nand_id_table,
|
||||
},
|
||||
.probe = gpmi_nand_probe,
|
||||
.remove = __exit_p(gpmi_nand_remove),
|
||||
.remove = __devexit_p(gpmi_nand_remove),
|
||||
.id_table = gpmi_ids,
|
||||
};
|
||||
|
||||
static int __init gpmi_nand_init(void)
|
||||
{
|
||||
int err;
|
||||
|
||||
err = platform_driver_register(&gpmi_nand_driver);
|
||||
if (err == 0)
|
||||
printk(KERN_INFO "GPMI NAND driver registered. (IMX)\n");
|
||||
else
|
||||
pr_err("i.MX GPMI NAND driver registration failed\n");
|
||||
return err;
|
||||
}
|
||||
|
||||
static void __exit gpmi_nand_exit(void)
|
||||
{
|
||||
platform_driver_unregister(&gpmi_nand_driver);
|
||||
}
|
||||
|
||||
module_init(gpmi_nand_init);
|
||||
module_exit(gpmi_nand_exit);
|
||||
module_platform_driver(gpmi_nand_driver);
|
||||
|
||||
MODULE_AUTHOR("Freescale Semiconductor, Inc.");
|
||||
MODULE_DESCRIPTION("i.MX GPMI NAND Flash Controller Driver");
|
||||
|
@ -22,14 +22,15 @@
|
||||
#include <linux/dma-mapping.h>
|
||||
#include <linux/fsl/mxs-dma.h>
|
||||
|
||||
#define GPMI_CLK_MAX 5 /* MX6Q needs five clocks */
|
||||
struct resources {
|
||||
void *gpmi_regs;
|
||||
void *bch_regs;
|
||||
void __iomem *gpmi_regs;
|
||||
void __iomem *bch_regs;
|
||||
unsigned int bch_low_interrupt;
|
||||
unsigned int bch_high_interrupt;
|
||||
unsigned int dma_low_channel;
|
||||
unsigned int dma_high_channel;
|
||||
struct clk *clock;
|
||||
struct clk *clock[GPMI_CLK_MAX];
|
||||
};
|
||||
|
||||
/**
|
||||
@ -121,6 +122,11 @@ struct nand_timing {
|
||||
};
|
||||
|
||||
struct gpmi_nand_data {
|
||||
/* flags */
|
||||
#define GPMI_ASYNC_EDO_ENABLED (1 << 0)
|
||||
#define GPMI_TIMING_INIT_OK (1 << 1)
|
||||
int flags;
|
||||
|
||||
/* System Interface */
|
||||
struct device *dev;
|
||||
struct platform_device *pdev;
|
||||
@ -131,6 +137,7 @@ struct gpmi_nand_data {
|
||||
|
||||
/* Flash Hardware */
|
||||
struct nand_timing timing;
|
||||
int timing_mode;
|
||||
|
||||
/* BCH */
|
||||
struct bch_geometry bch_geometry;
|
||||
@ -188,16 +195,28 @@ struct gpmi_nand_data {
|
||||
* @data_setup_in_cycles: The data setup time, in cycles.
|
||||
* @data_hold_in_cycles: The data hold time, in cycles.
|
||||
* @address_setup_in_cycles: The address setup time, in cycles.
|
||||
* @device_busy_timeout: The timeout waiting for NAND Ready/Busy,
|
||||
* this value is the number of cycles multiplied
|
||||
* by 4096.
|
||||
* @use_half_periods: Indicates the clock is running slowly, so the
|
||||
* NFC DLL should use half-periods.
|
||||
* @sample_delay_factor: The sample delay factor.
|
||||
* @wrn_dly_sel: The delay on the GPMI write strobe.
|
||||
*/
|
||||
struct gpmi_nfc_hardware_timing {
|
||||
/* for HW_GPMI_TIMING0 */
|
||||
uint8_t data_setup_in_cycles;
|
||||
uint8_t data_hold_in_cycles;
|
||||
uint8_t address_setup_in_cycles;
|
||||
|
||||
/* for HW_GPMI_TIMING1 */
|
||||
uint16_t device_busy_timeout;
|
||||
#define GPMI_DEFAULT_BUSY_TIMEOUT 0x500 /* default busy timeout value.*/
|
||||
|
||||
/* for HW_GPMI_CTRL1 */
|
||||
bool use_half_periods;
|
||||
uint8_t sample_delay_factor;
|
||||
uint8_t wrn_dly_sel;
|
||||
};
|
||||
|
||||
/**
|
||||
@ -246,6 +265,7 @@ extern int start_dma_with_bch_irq(struct gpmi_nand_data *,
|
||||
|
||||
/* GPMI-NAND helper function library */
|
||||
extern int gpmi_init(struct gpmi_nand_data *);
|
||||
extern int gpmi_extra_init(struct gpmi_nand_data *);
|
||||
extern void gpmi_clear_bch(struct gpmi_nand_data *);
|
||||
extern void gpmi_dump_info(struct gpmi_nand_data *);
|
||||
extern int bch_set_geometry(struct gpmi_nand_data *);
|
||||
|
@ -108,6 +108,15 @@
|
||||
#define HW_GPMI_CTRL1_CLR 0x00000068
|
||||
#define HW_GPMI_CTRL1_TOG 0x0000006c
|
||||
|
||||
#define BP_GPMI_CTRL1_WRN_DLY_SEL 22
|
||||
#define BM_GPMI_CTRL1_WRN_DLY_SEL (0x3 << BP_GPMI_CTRL1_WRN_DLY_SEL)
|
||||
#define BF_GPMI_CTRL1_WRN_DLY_SEL(v) \
|
||||
(((v) << BP_GPMI_CTRL1_WRN_DLY_SEL) & BM_GPMI_CTRL1_WRN_DLY_SEL)
|
||||
#define BV_GPMI_CTRL1_WRN_DLY_SEL_4_TO_8NS 0x0
|
||||
#define BV_GPMI_CTRL1_WRN_DLY_SEL_6_TO_10NS 0x1
|
||||
#define BV_GPMI_CTRL1_WRN_DLY_SEL_7_TO_12NS 0x2
|
||||
#define BV_GPMI_CTRL1_WRN_DLY_SEL_NO_DELAY 0x3
|
||||
|
||||
#define BM_GPMI_CTRL1_BCH_MODE (1 << 18)
|
||||
|
||||
#define BP_GPMI_CTRL1_DLL_ENABLE 17
|
||||
@ -154,6 +163,9 @@
|
||||
|
||||
#define HW_GPMI_TIMING1 0x00000080
|
||||
#define BP_GPMI_TIMING1_BUSY_TIMEOUT 16
|
||||
#define BM_GPMI_TIMING1_BUSY_TIMEOUT (0xffff << BP_GPMI_TIMING1_BUSY_TIMEOUT)
|
||||
#define BF_GPMI_TIMING1_BUSY_TIMEOUT(v) \
|
||||
(((v) << BP_GPMI_TIMING1_BUSY_TIMEOUT) & BM_GPMI_TIMING1_BUSY_TIMEOUT)
|
||||
|
||||
#define HW_GPMI_TIMING2 0x00000090
|
||||
#define HW_GPMI_DATA 0x000000a0
|
||||
|
924
drivers/mtd/nand/lpc32xx_mlc.c
Normal file
924
drivers/mtd/nand/lpc32xx_mlc.c
Normal file
@ -0,0 +1,924 @@
|
||||
/*
|
||||
* Driver for NAND MLC Controller in LPC32xx
|
||||
*
|
||||
* Author: Roland Stigge <stigge@antcom.de>
|
||||
*
|
||||
* Copyright © 2011 WORK Microwave GmbH
|
||||
* Copyright © 2011, 2012 Roland Stigge
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
*
|
||||
* NAND Flash Controller Operation:
|
||||
* - Read: Auto Decode
|
||||
* - Write: Auto Encode
|
||||
* - Tested Page Sizes: 2048, 4096
|
||||
*/
|
||||
|
||||
#include <linux/slab.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/mtd/mtd.h>
|
||||
#include <linux/mtd/nand.h>
|
||||
#include <linux/mtd/partitions.h>
|
||||
#include <linux/clk.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/completion.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/of_mtd.h>
|
||||
#include <linux/of_gpio.h>
|
||||
#include <linux/mtd/lpc32xx_mlc.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/mm.h>
|
||||
#include <linux/dma-mapping.h>
|
||||
#include <linux/dmaengine.h>
|
||||
#include <linux/mtd/nand_ecc.h>
|
||||
|
||||
#define DRV_NAME "lpc32xx_mlc"
|
||||
|
||||
/**********************************************************************
|
||||
* MLC NAND controller register offsets
|
||||
**********************************************************************/
|
||||
|
||||
#define MLC_BUFF(x) (x + 0x00000)
|
||||
#define MLC_DATA(x) (x + 0x08000)
|
||||
#define MLC_CMD(x) (x + 0x10000)
|
||||
#define MLC_ADDR(x) (x + 0x10004)
|
||||
#define MLC_ECC_ENC_REG(x) (x + 0x10008)
|
||||
#define MLC_ECC_DEC_REG(x) (x + 0x1000C)
|
||||
#define MLC_ECC_AUTO_ENC_REG(x) (x + 0x10010)
|
||||
#define MLC_ECC_AUTO_DEC_REG(x) (x + 0x10014)
|
||||
#define MLC_RPR(x) (x + 0x10018)
|
||||
#define MLC_WPR(x) (x + 0x1001C)
|
||||
#define MLC_RUBP(x) (x + 0x10020)
|
||||
#define MLC_ROBP(x) (x + 0x10024)
|
||||
#define MLC_SW_WP_ADD_LOW(x) (x + 0x10028)
|
||||
#define MLC_SW_WP_ADD_HIG(x) (x + 0x1002C)
|
||||
#define MLC_ICR(x) (x + 0x10030)
|
||||
#define MLC_TIME_REG(x) (x + 0x10034)
|
||||
#define MLC_IRQ_MR(x) (x + 0x10038)
|
||||
#define MLC_IRQ_SR(x) (x + 0x1003C)
|
||||
#define MLC_LOCK_PR(x) (x + 0x10044)
|
||||
#define MLC_ISR(x) (x + 0x10048)
|
||||
#define MLC_CEH(x) (x + 0x1004C)
|
||||
|
||||
/**********************************************************************
|
||||
* MLC_CMD bit definitions
|
||||
**********************************************************************/
|
||||
#define MLCCMD_RESET 0xFF
|
||||
|
||||
/**********************************************************************
|
||||
* MLC_ICR bit definitions
|
||||
**********************************************************************/
|
||||
#define MLCICR_WPROT (1 << 3)
|
||||
#define MLCICR_LARGEBLOCK (1 << 2)
|
||||
#define MLCICR_LONGADDR (1 << 1)
|
||||
#define MLCICR_16BIT (1 << 0) /* unsupported by LPC32x0! */
|
||||
|
||||
/**********************************************************************
|
||||
* MLC_TIME_REG bit definitions
|
||||
**********************************************************************/
|
||||
#define MLCTIMEREG_TCEA_DELAY(n) (((n) & 0x03) << 24)
|
||||
#define MLCTIMEREG_BUSY_DELAY(n) (((n) & 0x1F) << 19)
|
||||
#define MLCTIMEREG_NAND_TA(n) (((n) & 0x07) << 16)
|
||||
#define MLCTIMEREG_RD_HIGH(n) (((n) & 0x0F) << 12)
|
||||
#define MLCTIMEREG_RD_LOW(n) (((n) & 0x0F) << 8)
|
||||
#define MLCTIMEREG_WR_HIGH(n) (((n) & 0x0F) << 4)
|
||||
#define MLCTIMEREG_WR_LOW(n) (((n) & 0x0F) << 0)
|
||||
|
||||
/**********************************************************************
|
||||
* MLC_IRQ_MR and MLC_IRQ_SR bit definitions
|
||||
**********************************************************************/
|
||||
#define MLCIRQ_NAND_READY (1 << 5)
|
||||
#define MLCIRQ_CONTROLLER_READY (1 << 4)
|
||||
#define MLCIRQ_DECODE_FAILURE (1 << 3)
|
||||
#define MLCIRQ_DECODE_ERROR (1 << 2)
|
||||
#define MLCIRQ_ECC_READY (1 << 1)
|
||||
#define MLCIRQ_WRPROT_FAULT (1 << 0)
|
||||
|
||||
/**********************************************************************
|
||||
* MLC_LOCK_PR bit definitions
|
||||
**********************************************************************/
|
||||
#define MLCLOCKPR_MAGIC 0xA25E
|
||||
|
||||
/**********************************************************************
|
||||
* MLC_ISR bit definitions
|
||||
**********************************************************************/
|
||||
#define MLCISR_DECODER_FAILURE (1 << 6)
|
||||
#define MLCISR_ERRORS ((1 << 4) | (1 << 5))
|
||||
#define MLCISR_ERRORS_DETECTED (1 << 3)
|
||||
#define MLCISR_ECC_READY (1 << 2)
|
||||
#define MLCISR_CONTROLLER_READY (1 << 1)
|
||||
#define MLCISR_NAND_READY (1 << 0)
|
||||
|
||||
/**********************************************************************
|
||||
* MLC_CEH bit definitions
|
||||
**********************************************************************/
|
||||
#define MLCCEH_NORMAL (1 << 0)
|
||||
|
||||
struct lpc32xx_nand_cfg_mlc {
|
||||
uint32_t tcea_delay;
|
||||
uint32_t busy_delay;
|
||||
uint32_t nand_ta;
|
||||
uint32_t rd_high;
|
||||
uint32_t rd_low;
|
||||
uint32_t wr_high;
|
||||
uint32_t wr_low;
|
||||
int wp_gpio;
|
||||
struct mtd_partition *parts;
|
||||
unsigned num_parts;
|
||||
};
|
||||
|
||||
static struct nand_ecclayout lpc32xx_nand_oob = {
|
||||
.eccbytes = 40,
|
||||
.eccpos = { 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
|
||||
22, 23, 24, 25, 26, 27, 28, 29, 30, 31,
|
||||
38, 39, 40, 41, 42, 43, 44, 45, 46, 47,
|
||||
54, 55, 56, 57, 58, 59, 60, 61, 62, 63 },
|
||||
.oobfree = {
|
||||
{ .offset = 0,
|
||||
.length = 6, },
|
||||
{ .offset = 16,
|
||||
.length = 6, },
|
||||
{ .offset = 32,
|
||||
.length = 6, },
|
||||
{ .offset = 48,
|
||||
.length = 6, },
|
||||
},
|
||||
};
|
||||
|
||||
static struct nand_bbt_descr lpc32xx_nand_bbt = {
|
||||
.options = NAND_BBT_ABSPAGE | NAND_BBT_2BIT | NAND_BBT_NO_OOB |
|
||||
NAND_BBT_WRITE,
|
||||
.pages = { 524224, 0, 0, 0, 0, 0, 0, 0 },
|
||||
};
|
||||
|
||||
static struct nand_bbt_descr lpc32xx_nand_bbt_mirror = {
|
||||
.options = NAND_BBT_ABSPAGE | NAND_BBT_2BIT | NAND_BBT_NO_OOB |
|
||||
NAND_BBT_WRITE,
|
||||
.pages = { 524160, 0, 0, 0, 0, 0, 0, 0 },
|
||||
};
|
||||
|
||||
struct lpc32xx_nand_host {
|
||||
struct nand_chip nand_chip;
|
||||
struct lpc32xx_mlc_platform_data *pdata;
|
||||
struct clk *clk;
|
||||
struct mtd_info mtd;
|
||||
void __iomem *io_base;
|
||||
int irq;
|
||||
struct lpc32xx_nand_cfg_mlc *ncfg;
|
||||
struct completion comp_nand;
|
||||
struct completion comp_controller;
|
||||
uint32_t llptr;
|
||||
/*
|
||||
* Physical addresses of ECC buffer, DMA data buffers, OOB data buffer
|
||||
*/
|
||||
dma_addr_t oob_buf_phy;
|
||||
/*
|
||||
* Virtual addresses of ECC buffer, DMA data buffers, OOB data buffer
|
||||
*/
|
||||
uint8_t *oob_buf;
|
||||
/* Physical address of DMA base address */
|
||||
dma_addr_t io_base_phy;
|
||||
|
||||
struct completion comp_dma;
|
||||
struct dma_chan *dma_chan;
|
||||
struct dma_slave_config dma_slave_config;
|
||||
struct scatterlist sgl;
|
||||
uint8_t *dma_buf;
|
||||
uint8_t *dummy_buf;
|
||||
int mlcsubpages; /* number of 512bytes-subpages */
|
||||
};
|
||||
|
||||
/*
|
||||
* Activate/Deactivate DMA Operation:
|
||||
*
|
||||
* Using the PL080 DMA Controller for transferring the 512 byte subpages
|
||||
* instead of doing readl() / writel() in a loop slows it down significantly.
|
||||
* Measurements via getnstimeofday() upon 512 byte subpage reads reveal:
|
||||
*
|
||||
* - readl() of 128 x 32 bits in a loop: ~20us
|
||||
* - DMA read of 512 bytes (32 bit, 4...128 words bursts): ~60us
|
||||
* - DMA read of 512 bytes (32 bit, no bursts): ~100us
|
||||
*
|
||||
* This applies to the transfer itself. In the DMA case: only the
|
||||
* wait_for_completion() (DMA setup _not_ included).
|
||||
*
|
||||
* Note that the 512 bytes subpage transfer is done directly from/to a
|
||||
* FIFO/buffer inside the NAND controller. Most of the time (~400-800us for a
|
||||
* 2048 bytes page) is spent waiting for the NAND IRQ, anyway. (The NAND
|
||||
* controller transferring data between its internal buffer to/from the NAND
|
||||
* chip.)
|
||||
*
|
||||
* Therefore, using the PL080 DMA is disabled by default, for now.
|
||||
*
|
||||
*/
|
||||
static int use_dma;
|
||||
|
||||
static void lpc32xx_nand_setup(struct lpc32xx_nand_host *host)
|
||||
{
|
||||
uint32_t clkrate, tmp;
|
||||
|
||||
/* Reset MLC controller */
|
||||
writel(MLCCMD_RESET, MLC_CMD(host->io_base));
|
||||
udelay(1000);
|
||||
|
||||
/* Get base clock for MLC block */
|
||||
clkrate = clk_get_rate(host->clk);
|
||||
if (clkrate == 0)
|
||||
clkrate = 104000000;
|
||||
|
||||
/* Unlock MLC_ICR
|
||||
* (among others, will be locked again automatically) */
|
||||
writew(MLCLOCKPR_MAGIC, MLC_LOCK_PR(host->io_base));
|
||||
|
||||
/* Configure MLC Controller: Large Block, 5 Byte Address */
|
||||
tmp = MLCICR_LARGEBLOCK | MLCICR_LONGADDR;
|
||||
writel(tmp, MLC_ICR(host->io_base));
|
||||
|
||||
/* Unlock MLC_TIME_REG
|
||||
* (among others, will be locked again automatically) */
|
||||
writew(MLCLOCKPR_MAGIC, MLC_LOCK_PR(host->io_base));
|
||||
|
||||
/* Compute clock setup values, see LPC and NAND manual */
|
||||
tmp = 0;
|
||||
tmp |= MLCTIMEREG_TCEA_DELAY(clkrate / host->ncfg->tcea_delay + 1);
|
||||
tmp |= MLCTIMEREG_BUSY_DELAY(clkrate / host->ncfg->busy_delay + 1);
|
||||
tmp |= MLCTIMEREG_NAND_TA(clkrate / host->ncfg->nand_ta + 1);
|
||||
tmp |= MLCTIMEREG_RD_HIGH(clkrate / host->ncfg->rd_high + 1);
|
||||
tmp |= MLCTIMEREG_RD_LOW(clkrate / host->ncfg->rd_low);
|
||||
tmp |= MLCTIMEREG_WR_HIGH(clkrate / host->ncfg->wr_high + 1);
|
||||
tmp |= MLCTIMEREG_WR_LOW(clkrate / host->ncfg->wr_low);
|
||||
writel(tmp, MLC_TIME_REG(host->io_base));
|
||||
|
||||
/* Enable IRQ for CONTROLLER_READY and NAND_READY */
|
||||
writeb(MLCIRQ_CONTROLLER_READY | MLCIRQ_NAND_READY,
|
||||
MLC_IRQ_MR(host->io_base));
|
||||
|
||||
/* Normal nCE operation: nCE controlled by controller */
|
||||
writel(MLCCEH_NORMAL, MLC_CEH(host->io_base));
|
||||
}
|
||||
|
||||
/*
|
||||
* Hardware specific access to control lines
|
||||
*/
|
||||
static void lpc32xx_nand_cmd_ctrl(struct mtd_info *mtd, int cmd,
|
||||
unsigned int ctrl)
|
||||
{
|
||||
struct nand_chip *nand_chip = mtd->priv;
|
||||
struct lpc32xx_nand_host *host = nand_chip->priv;
|
||||
|
||||
if (cmd != NAND_CMD_NONE) {
|
||||
if (ctrl & NAND_CLE)
|
||||
writel(cmd, MLC_CMD(host->io_base));
|
||||
else
|
||||
writel(cmd, MLC_ADDR(host->io_base));
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Read Device Ready (NAND device _and_ controller ready)
|
||||
*/
|
||||
static int lpc32xx_nand_device_ready(struct mtd_info *mtd)
|
||||
{
|
||||
struct nand_chip *nand_chip = mtd->priv;
|
||||
struct lpc32xx_nand_host *host = nand_chip->priv;
|
||||
|
||||
if ((readb(MLC_ISR(host->io_base)) &
|
||||
(MLCISR_CONTROLLER_READY | MLCISR_NAND_READY)) ==
|
||||
(MLCISR_CONTROLLER_READY | MLCISR_NAND_READY))
|
||||
return 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static irqreturn_t lpc3xxx_nand_irq(int irq, struct lpc32xx_nand_host *host)
|
||||
{
|
||||
uint8_t sr;
|
||||
|
||||
/* Clear interrupt flag by reading status */
|
||||
sr = readb(MLC_IRQ_SR(host->io_base));
|
||||
if (sr & MLCIRQ_NAND_READY)
|
||||
complete(&host->comp_nand);
|
||||
if (sr & MLCIRQ_CONTROLLER_READY)
|
||||
complete(&host->comp_controller);
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static int lpc32xx_waitfunc_nand(struct mtd_info *mtd, struct nand_chip *chip)
|
||||
{
|
||||
struct lpc32xx_nand_host *host = chip->priv;
|
||||
|
||||
if (readb(MLC_ISR(host->io_base)) & MLCISR_NAND_READY)
|
||||
goto exit;
|
||||
|
||||
wait_for_completion(&host->comp_nand);
|
||||
|
||||
while (!(readb(MLC_ISR(host->io_base)) & MLCISR_NAND_READY)) {
|
||||
/* Seems to be delayed sometimes by controller */
|
||||
dev_dbg(&mtd->dev, "Warning: NAND not ready.\n");
|
||||
cpu_relax();
|
||||
}
|
||||
|
||||
exit:
|
||||
return NAND_STATUS_READY;
|
||||
}
|
||||
|
||||
static int lpc32xx_waitfunc_controller(struct mtd_info *mtd,
|
||||
struct nand_chip *chip)
|
||||
{
|
||||
struct lpc32xx_nand_host *host = chip->priv;
|
||||
|
||||
if (readb(MLC_ISR(host->io_base)) & MLCISR_CONTROLLER_READY)
|
||||
goto exit;
|
||||
|
||||
wait_for_completion(&host->comp_controller);
|
||||
|
||||
while (!(readb(MLC_ISR(host->io_base)) &
|
||||
MLCISR_CONTROLLER_READY)) {
|
||||
dev_dbg(&mtd->dev, "Warning: Controller not ready.\n");
|
||||
cpu_relax();
|
||||
}
|
||||
|
||||
exit:
|
||||
return NAND_STATUS_READY;
|
||||
}
|
||||
|
||||
static int lpc32xx_waitfunc(struct mtd_info *mtd, struct nand_chip *chip)
|
||||
{
|
||||
lpc32xx_waitfunc_nand(mtd, chip);
|
||||
lpc32xx_waitfunc_controller(mtd, chip);
|
||||
|
||||
return NAND_STATUS_READY;
|
||||
}
|
||||
|
||||
/*
|
||||
* Enable NAND write protect
|
||||
*/
|
||||
static void lpc32xx_wp_enable(struct lpc32xx_nand_host *host)
|
||||
{
|
||||
if (gpio_is_valid(host->ncfg->wp_gpio))
|
||||
gpio_set_value(host->ncfg->wp_gpio, 0);
|
||||
}
|
||||
|
||||
/*
|
||||
* Disable NAND write protect
|
||||
*/
|
||||
static void lpc32xx_wp_disable(struct lpc32xx_nand_host *host)
|
||||
{
|
||||
if (gpio_is_valid(host->ncfg->wp_gpio))
|
||||
gpio_set_value(host->ncfg->wp_gpio, 1);
|
||||
}
|
||||
|
||||
static void lpc32xx_dma_complete_func(void *completion)
|
||||
{
|
||||
complete(completion);
|
||||
}
|
||||
|
||||
static int lpc32xx_xmit_dma(struct mtd_info *mtd, void *mem, int len,
|
||||
enum dma_transfer_direction dir)
|
||||
{
|
||||
struct nand_chip *chip = mtd->priv;
|
||||
struct lpc32xx_nand_host *host = chip->priv;
|
||||
struct dma_async_tx_descriptor *desc;
|
||||
int flags = DMA_CTRL_ACK | DMA_PREP_INTERRUPT;
|
||||
int res;
|
||||
|
||||
sg_init_one(&host->sgl, mem, len);
|
||||
|
||||
res = dma_map_sg(host->dma_chan->device->dev, &host->sgl, 1,
|
||||
DMA_BIDIRECTIONAL);
|
||||
if (res != 1) {
|
||||
dev_err(mtd->dev.parent, "Failed to map sg list\n");
|
||||
return -ENXIO;
|
||||
}
|
||||
desc = dmaengine_prep_slave_sg(host->dma_chan, &host->sgl, 1, dir,
|
||||
flags);
|
||||
if (!desc) {
|
||||
dev_err(mtd->dev.parent, "Failed to prepare slave sg\n");
|
||||
goto out1;
|
||||
}
|
||||
|
||||
init_completion(&host->comp_dma);
|
||||
desc->callback = lpc32xx_dma_complete_func;
|
||||
desc->callback_param = &host->comp_dma;
|
||||
|
||||
dmaengine_submit(desc);
|
||||
dma_async_issue_pending(host->dma_chan);
|
||||
|
||||
wait_for_completion_timeout(&host->comp_dma, msecs_to_jiffies(1000));
|
||||
|
||||
dma_unmap_sg(host->dma_chan->device->dev, &host->sgl, 1,
|
||||
DMA_BIDIRECTIONAL);
|
||||
return 0;
|
||||
out1:
|
||||
dma_unmap_sg(host->dma_chan->device->dev, &host->sgl, 1,
|
||||
DMA_BIDIRECTIONAL);
|
||||
return -ENXIO;
|
||||
}
|
||||
|
||||
static int lpc32xx_read_page(struct mtd_info *mtd, struct nand_chip *chip,
|
||||
uint8_t *buf, int oob_required, int page)
|
||||
{
|
||||
struct lpc32xx_nand_host *host = chip->priv;
|
||||
int i, j;
|
||||
uint8_t *oobbuf = chip->oob_poi;
|
||||
uint32_t mlc_isr;
|
||||
int res;
|
||||
uint8_t *dma_buf;
|
||||
bool dma_mapped;
|
||||
|
||||
if ((void *)buf <= high_memory) {
|
||||
dma_buf = buf;
|
||||
dma_mapped = true;
|
||||
} else {
|
||||
dma_buf = host->dma_buf;
|
||||
dma_mapped = false;
|
||||
}
|
||||
|
||||
/* Writing Command and Address */
|
||||
chip->cmdfunc(mtd, NAND_CMD_READ0, 0, page);
|
||||
|
||||
/* For all sub-pages */
|
||||
for (i = 0; i < host->mlcsubpages; i++) {
|
||||
/* Start Auto Decode Command */
|
||||
writeb(0x00, MLC_ECC_AUTO_DEC_REG(host->io_base));
|
||||
|
||||
/* Wait for Controller Ready */
|
||||
lpc32xx_waitfunc_controller(mtd, chip);
|
||||
|
||||
/* Check ECC Error status */
|
||||
mlc_isr = readl(MLC_ISR(host->io_base));
|
||||
if (mlc_isr & MLCISR_DECODER_FAILURE) {
|
||||
mtd->ecc_stats.failed++;
|
||||
dev_warn(&mtd->dev, "%s: DECODER_FAILURE\n", __func__);
|
||||
} else if (mlc_isr & MLCISR_ERRORS_DETECTED) {
|
||||
mtd->ecc_stats.corrected += ((mlc_isr >> 4) & 0x3) + 1;
|
||||
}
|
||||
|
||||
/* Read 512 + 16 Bytes */
|
||||
if (use_dma) {
|
||||
res = lpc32xx_xmit_dma(mtd, dma_buf + i * 512, 512,
|
||||
DMA_DEV_TO_MEM);
|
||||
if (res)
|
||||
return res;
|
||||
} else {
|
||||
for (j = 0; j < (512 >> 2); j++) {
|
||||
*((uint32_t *)(buf)) =
|
||||
readl(MLC_BUFF(host->io_base));
|
||||
buf += 4;
|
||||
}
|
||||
}
|
||||
for (j = 0; j < (16 >> 2); j++) {
|
||||
*((uint32_t *)(oobbuf)) =
|
||||
readl(MLC_BUFF(host->io_base));
|
||||
oobbuf += 4;
|
||||
}
|
||||
}
|
||||
|
||||
if (use_dma && !dma_mapped)
|
||||
memcpy(buf, dma_buf, mtd->writesize);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int lpc32xx_write_page_lowlevel(struct mtd_info *mtd,
|
||||
struct nand_chip *chip,
|
||||
const uint8_t *buf, int oob_required)
|
||||
{
|
||||
struct lpc32xx_nand_host *host = chip->priv;
|
||||
const uint8_t *oobbuf = chip->oob_poi;
|
||||
uint8_t *dma_buf = (uint8_t *)buf;
|
||||
int res;
|
||||
int i, j;
|
||||
|
||||
if (use_dma && (void *)buf >= high_memory) {
|
||||
dma_buf = host->dma_buf;
|
||||
memcpy(dma_buf, buf, mtd->writesize);
|
||||
}
|
||||
|
||||
for (i = 0; i < host->mlcsubpages; i++) {
|
||||
/* Start Encode */
|
||||
writeb(0x00, MLC_ECC_ENC_REG(host->io_base));
|
||||
|
||||
/* Write 512 + 6 Bytes to Buffer */
|
||||
if (use_dma) {
|
||||
res = lpc32xx_xmit_dma(mtd, dma_buf + i * 512, 512,
|
||||
DMA_MEM_TO_DEV);
|
||||
if (res)
|
||||
return res;
|
||||
} else {
|
||||
for (j = 0; j < (512 >> 2); j++) {
|
||||
writel(*((uint32_t *)(buf)),
|
||||
MLC_BUFF(host->io_base));
|
||||
buf += 4;
|
||||
}
|
||||
}
|
||||
writel(*((uint32_t *)(oobbuf)), MLC_BUFF(host->io_base));
|
||||
oobbuf += 4;
|
||||
writew(*((uint16_t *)(oobbuf)), MLC_BUFF(host->io_base));
|
||||
oobbuf += 12;
|
||||
|
||||
/* Auto Encode w/ Bit 8 = 0 (see LPC MLC Controller manual) */
|
||||
writeb(0x00, MLC_ECC_AUTO_ENC_REG(host->io_base));
|
||||
|
||||
/* Wait for Controller Ready */
|
||||
lpc32xx_waitfunc_controller(mtd, chip);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int lpc32xx_write_page(struct mtd_info *mtd, struct nand_chip *chip,
|
||||
const uint8_t *buf, int oob_required, int page,
|
||||
int cached, int raw)
|
||||
{
|
||||
int res;
|
||||
|
||||
chip->cmdfunc(mtd, NAND_CMD_SEQIN, 0x00, page);
|
||||
res = lpc32xx_write_page_lowlevel(mtd, chip, buf, oob_required);
|
||||
chip->cmdfunc(mtd, NAND_CMD_PAGEPROG, -1, -1);
|
||||
lpc32xx_waitfunc(mtd, chip);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
static int lpc32xx_read_oob(struct mtd_info *mtd, struct nand_chip *chip,
|
||||
int page)
|
||||
{
|
||||
struct lpc32xx_nand_host *host = chip->priv;
|
||||
|
||||
/* Read whole page - necessary with MLC controller! */
|
||||
lpc32xx_read_page(mtd, chip, host->dummy_buf, 1, page);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int lpc32xx_write_oob(struct mtd_info *mtd, struct nand_chip *chip,
|
||||
int page)
|
||||
{
|
||||
/* None, write_oob conflicts with the automatic LPC MLC ECC decoder! */
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Prepares MLC for transfers with H/W ECC enabled: always enabled anyway */
|
||||
static void lpc32xx_ecc_enable(struct mtd_info *mtd, int mode)
|
||||
{
|
||||
/* Always enabled! */
|
||||
}
|
||||
|
||||
static int lpc32xx_dma_setup(struct lpc32xx_nand_host *host)
|
||||
{
|
||||
struct mtd_info *mtd = &host->mtd;
|
||||
dma_cap_mask_t mask;
|
||||
|
||||
if (!host->pdata || !host->pdata->dma_filter) {
|
||||
dev_err(mtd->dev.parent, "no DMA platform data\n");
|
||||
return -ENOENT;
|
||||
}
|
||||
|
||||
dma_cap_zero(mask);
|
||||
dma_cap_set(DMA_SLAVE, mask);
|
||||
host->dma_chan = dma_request_channel(mask, host->pdata->dma_filter,
|
||||
"nand-mlc");
|
||||
if (!host->dma_chan) {
|
||||
dev_err(mtd->dev.parent, "Failed to request DMA channel\n");
|
||||
return -EBUSY;
|
||||
}
|
||||
|
||||
/*
|
||||
* Set direction to a sensible value even if the dmaengine driver
|
||||
* should ignore it. With the default (DMA_MEM_TO_MEM), the amba-pl08x
|
||||
* driver criticizes it as "alien transfer direction".
|
||||
*/
|
||||
host->dma_slave_config.direction = DMA_DEV_TO_MEM;
|
||||
host->dma_slave_config.src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
|
||||
host->dma_slave_config.dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
|
||||
host->dma_slave_config.src_maxburst = 128;
|
||||
host->dma_slave_config.dst_maxburst = 128;
|
||||
/* DMA controller does flow control: */
|
||||
host->dma_slave_config.device_fc = false;
|
||||
host->dma_slave_config.src_addr = MLC_BUFF(host->io_base_phy);
|
||||
host->dma_slave_config.dst_addr = MLC_BUFF(host->io_base_phy);
|
||||
if (dmaengine_slave_config(host->dma_chan, &host->dma_slave_config)) {
|
||||
dev_err(mtd->dev.parent, "Failed to setup DMA slave\n");
|
||||
goto out1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
out1:
|
||||
dma_release_channel(host->dma_chan);
|
||||
return -ENXIO;
|
||||
}
|
||||
|
||||
static struct lpc32xx_nand_cfg_mlc *lpc32xx_parse_dt(struct device *dev)
|
||||
{
|
||||
struct lpc32xx_nand_cfg_mlc *ncfg;
|
||||
struct device_node *np = dev->of_node;
|
||||
|
||||
ncfg = devm_kzalloc(dev, sizeof(*ncfg), GFP_KERNEL);
|
||||
if (!ncfg) {
|
||||
dev_err(dev, "could not allocate memory for platform data\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
of_property_read_u32(np, "nxp,tcea-delay", &ncfg->tcea_delay);
|
||||
of_property_read_u32(np, "nxp,busy-delay", &ncfg->busy_delay);
|
||||
of_property_read_u32(np, "nxp,nand-ta", &ncfg->nand_ta);
|
||||
of_property_read_u32(np, "nxp,rd-high", &ncfg->rd_high);
|
||||
of_property_read_u32(np, "nxp,rd-low", &ncfg->rd_low);
|
||||
of_property_read_u32(np, "nxp,wr-high", &ncfg->wr_high);
|
||||
of_property_read_u32(np, "nxp,wr-low", &ncfg->wr_low);
|
||||
|
||||
if (!ncfg->tcea_delay || !ncfg->busy_delay || !ncfg->nand_ta ||
|
||||
!ncfg->rd_high || !ncfg->rd_low || !ncfg->wr_high ||
|
||||
!ncfg->wr_low) {
|
||||
dev_err(dev, "chip parameters not specified correctly\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ncfg->wp_gpio = of_get_named_gpio(np, "gpios", 0);
|
||||
|
||||
return ncfg;
|
||||
}
|
||||
|
||||
/*
|
||||
* Probe for NAND controller
|
||||
*/
|
||||
static int __devinit lpc32xx_nand_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct lpc32xx_nand_host *host;
|
||||
struct mtd_info *mtd;
|
||||
struct nand_chip *nand_chip;
|
||||
struct resource *rc;
|
||||
int res;
|
||||
struct mtd_part_parser_data ppdata = {};
|
||||
|
||||
/* Allocate memory for the device structure (and zero it) */
|
||||
host = devm_kzalloc(&pdev->dev, sizeof(*host), GFP_KERNEL);
|
||||
if (!host) {
|
||||
dev_err(&pdev->dev, "failed to allocate device structure.\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
rc = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
if (rc == NULL) {
|
||||
dev_err(&pdev->dev, "No memory resource found for device!\r\n");
|
||||
return -ENXIO;
|
||||
}
|
||||
|
||||
host->io_base = devm_request_and_ioremap(&pdev->dev, rc);
|
||||
if (host->io_base == NULL) {
|
||||
dev_err(&pdev->dev, "ioremap failed\n");
|
||||
return -EIO;
|
||||
}
|
||||
host->io_base_phy = rc->start;
|
||||
|
||||
mtd = &host->mtd;
|
||||
nand_chip = &host->nand_chip;
|
||||
if (pdev->dev.of_node)
|
||||
host->ncfg = lpc32xx_parse_dt(&pdev->dev);
|
||||
if (!host->ncfg) {
|
||||
dev_err(&pdev->dev,
|
||||
"Missing or bad NAND config from device tree\n");
|
||||
return -ENOENT;
|
||||
}
|
||||
if (host->ncfg->wp_gpio == -EPROBE_DEFER)
|
||||
return -EPROBE_DEFER;
|
||||
if (gpio_is_valid(host->ncfg->wp_gpio) &&
|
||||
gpio_request(host->ncfg->wp_gpio, "NAND WP")) {
|
||||
dev_err(&pdev->dev, "GPIO not available\n");
|
||||
return -EBUSY;
|
||||
}
|
||||
lpc32xx_wp_disable(host);
|
||||
|
||||
host->pdata = pdev->dev.platform_data;
|
||||
|
||||
nand_chip->priv = host; /* link the private data structures */
|
||||
mtd->priv = nand_chip;
|
||||
mtd->owner = THIS_MODULE;
|
||||
mtd->dev.parent = &pdev->dev;
|
||||
|
||||
/* Get NAND clock */
|
||||
host->clk = clk_get(&pdev->dev, NULL);
|
||||
if (IS_ERR(host->clk)) {
|
||||
dev_err(&pdev->dev, "Clock initialization failure\n");
|
||||
res = -ENOENT;
|
||||
goto err_exit1;
|
||||
}
|
||||
clk_enable(host->clk);
|
||||
|
||||
nand_chip->cmd_ctrl = lpc32xx_nand_cmd_ctrl;
|
||||
nand_chip->dev_ready = lpc32xx_nand_device_ready;
|
||||
nand_chip->chip_delay = 25; /* us */
|
||||
nand_chip->IO_ADDR_R = MLC_DATA(host->io_base);
|
||||
nand_chip->IO_ADDR_W = MLC_DATA(host->io_base);
|
||||
|
||||
/* Init NAND controller */
|
||||
lpc32xx_nand_setup(host);
|
||||
|
||||
platform_set_drvdata(pdev, host);
|
||||
|
||||
/* Initialize function pointers */
|
||||
nand_chip->ecc.hwctl = lpc32xx_ecc_enable;
|
||||
nand_chip->ecc.read_page_raw = lpc32xx_read_page;
|
||||
nand_chip->ecc.read_page = lpc32xx_read_page;
|
||||
nand_chip->ecc.write_page_raw = lpc32xx_write_page_lowlevel;
|
||||
nand_chip->ecc.write_page = lpc32xx_write_page_lowlevel;
|
||||
nand_chip->ecc.write_oob = lpc32xx_write_oob;
|
||||
nand_chip->ecc.read_oob = lpc32xx_read_oob;
|
||||
nand_chip->ecc.strength = 4;
|
||||
nand_chip->write_page = lpc32xx_write_page;
|
||||
nand_chip->waitfunc = lpc32xx_waitfunc;
|
||||
|
||||
nand_chip->bbt_options = NAND_BBT_USE_FLASH | NAND_BBT_NO_OOB;
|
||||
nand_chip->bbt_td = &lpc32xx_nand_bbt;
|
||||
nand_chip->bbt_md = &lpc32xx_nand_bbt_mirror;
|
||||
|
||||
/* bitflip_threshold's default is defined as ecc_strength anyway.
|
||||
* Unfortunately, it is set only later at add_mtd_device(). Meanwhile
|
||||
* being 0, it causes bad block table scanning errors in
|
||||
* nand_scan_tail(), so preparing it here. */
|
||||
mtd->bitflip_threshold = nand_chip->ecc.strength;
|
||||
|
||||
if (use_dma) {
|
||||
res = lpc32xx_dma_setup(host);
|
||||
if (res) {
|
||||
res = -EIO;
|
||||
goto err_exit2;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Scan to find existance of the device and
|
||||
* Get the type of NAND device SMALL block or LARGE block
|
||||
*/
|
||||
if (nand_scan_ident(mtd, 1, NULL)) {
|
||||
res = -ENXIO;
|
||||
goto err_exit3;
|
||||
}
|
||||
|
||||
host->dma_buf = devm_kzalloc(&pdev->dev, mtd->writesize, GFP_KERNEL);
|
||||
if (!host->dma_buf) {
|
||||
dev_err(&pdev->dev, "Error allocating dma_buf memory\n");
|
||||
res = -ENOMEM;
|
||||
goto err_exit3;
|
||||
}
|
||||
|
||||
host->dummy_buf = devm_kzalloc(&pdev->dev, mtd->writesize, GFP_KERNEL);
|
||||
if (!host->dummy_buf) {
|
||||
dev_err(&pdev->dev, "Error allocating dummy_buf memory\n");
|
||||
res = -ENOMEM;
|
||||
goto err_exit3;
|
||||
}
|
||||
|
||||
nand_chip->ecc.mode = NAND_ECC_HW;
|
||||
nand_chip->ecc.size = mtd->writesize;
|
||||
nand_chip->ecc.layout = &lpc32xx_nand_oob;
|
||||
host->mlcsubpages = mtd->writesize / 512;
|
||||
|
||||
/* initially clear interrupt status */
|
||||
readb(MLC_IRQ_SR(host->io_base));
|
||||
|
||||
init_completion(&host->comp_nand);
|
||||
init_completion(&host->comp_controller);
|
||||
|
||||
host->irq = platform_get_irq(pdev, 0);
|
||||
if ((host->irq < 0) || (host->irq >= NR_IRQS)) {
|
||||
dev_err(&pdev->dev, "failed to get platform irq\n");
|
||||
res = -EINVAL;
|
||||
goto err_exit3;
|
||||
}
|
||||
|
||||
if (request_irq(host->irq, (irq_handler_t)&lpc3xxx_nand_irq,
|
||||
IRQF_TRIGGER_HIGH, DRV_NAME, host)) {
|
||||
dev_err(&pdev->dev, "Error requesting NAND IRQ\n");
|
||||
res = -ENXIO;
|
||||
goto err_exit3;
|
||||
}
|
||||
|
||||
/*
|
||||
* Fills out all the uninitialized function pointers with the defaults
|
||||
* And scans for a bad block table if appropriate.
|
||||
*/
|
||||
if (nand_scan_tail(mtd)) {
|
||||
res = -ENXIO;
|
||||
goto err_exit4;
|
||||
}
|
||||
|
||||
mtd->name = DRV_NAME;
|
||||
|
||||
ppdata.of_node = pdev->dev.of_node;
|
||||
res = mtd_device_parse_register(mtd, NULL, &ppdata, host->ncfg->parts,
|
||||
host->ncfg->num_parts);
|
||||
if (!res)
|
||||
return res;
|
||||
|
||||
nand_release(mtd);
|
||||
|
||||
err_exit4:
|
||||
free_irq(host->irq, host);
|
||||
err_exit3:
|
||||
if (use_dma)
|
||||
dma_release_channel(host->dma_chan);
|
||||
err_exit2:
|
||||
clk_disable(host->clk);
|
||||
clk_put(host->clk);
|
||||
platform_set_drvdata(pdev, NULL);
|
||||
err_exit1:
|
||||
lpc32xx_wp_enable(host);
|
||||
gpio_free(host->ncfg->wp_gpio);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
/*
|
||||
* Remove NAND device
|
||||
*/
|
||||
static int __devexit lpc32xx_nand_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct lpc32xx_nand_host *host = platform_get_drvdata(pdev);
|
||||
struct mtd_info *mtd = &host->mtd;
|
||||
|
||||
nand_release(mtd);
|
||||
free_irq(host->irq, host);
|
||||
if (use_dma)
|
||||
dma_release_channel(host->dma_chan);
|
||||
|
||||
clk_disable(host->clk);
|
||||
clk_put(host->clk);
|
||||
platform_set_drvdata(pdev, NULL);
|
||||
|
||||
lpc32xx_wp_enable(host);
|
||||
gpio_free(host->ncfg->wp_gpio);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM
|
||||
static int lpc32xx_nand_resume(struct platform_device *pdev)
|
||||
{
|
||||
struct lpc32xx_nand_host *host = platform_get_drvdata(pdev);
|
||||
|
||||
/* Re-enable NAND clock */
|
||||
clk_enable(host->clk);
|
||||
|
||||
/* Fresh init of NAND controller */
|
||||
lpc32xx_nand_setup(host);
|
||||
|
||||
/* Disable write protect */
|
||||
lpc32xx_wp_disable(host);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int lpc32xx_nand_suspend(struct platform_device *pdev, pm_message_t pm)
|
||||
{
|
||||
struct lpc32xx_nand_host *host = platform_get_drvdata(pdev);
|
||||
|
||||
/* Enable write protect for safety */
|
||||
lpc32xx_wp_enable(host);
|
||||
|
||||
/* Disable clock */
|
||||
clk_disable(host->clk);
|
||||
return 0;
|
||||
}
|
||||
|
||||
#else
|
||||
#define lpc32xx_nand_resume NULL
|
||||
#define lpc32xx_nand_suspend NULL
|
||||
#endif
|
||||
|
||||
static const struct of_device_id lpc32xx_nand_match[] = {
|
||||
{ .compatible = "nxp,lpc3220-mlc" },
|
||||
{ /* sentinel */ },
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, lpc32xx_nand_match);
|
||||
|
||||
static struct platform_driver lpc32xx_nand_driver = {
|
||||
.probe = lpc32xx_nand_probe,
|
||||
.remove = __devexit_p(lpc32xx_nand_remove),
|
||||
.resume = lpc32xx_nand_resume,
|
||||
.suspend = lpc32xx_nand_suspend,
|
||||
.driver = {
|
||||
.name = DRV_NAME,
|
||||
.owner = THIS_MODULE,
|
||||
.of_match_table = of_match_ptr(lpc32xx_nand_match),
|
||||
},
|
||||
};
|
||||
|
||||
module_platform_driver(lpc32xx_nand_driver);
|
||||
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_AUTHOR("Roland Stigge <stigge@antcom.de>");
|
||||
MODULE_DESCRIPTION("NAND driver for the NXP LPC32XX MLC controller");
|
1039
drivers/mtd/nand/lpc32xx_slc.c
Normal file
1039
drivers/mtd/nand/lpc32xx_slc.c
Normal file
File diff suppressed because it is too large
Load Diff
@ -506,27 +506,6 @@ static void mpc5121_nfc_write_buf(struct mtd_info *mtd,
|
||||
mpc5121_nfc_buf_copy(mtd, (u_char *)buf, len, 1);
|
||||
}
|
||||
|
||||
/* Compare buffer with NAND flash */
|
||||
static int mpc5121_nfc_verify_buf(struct mtd_info *mtd,
|
||||
const u_char *buf, int len)
|
||||
{
|
||||
u_char tmp[256];
|
||||
uint bsize;
|
||||
|
||||
while (len) {
|
||||
bsize = min(len, 256);
|
||||
mpc5121_nfc_read_buf(mtd, tmp, bsize);
|
||||
|
||||
if (memcmp(buf, tmp, bsize))
|
||||
return 1;
|
||||
|
||||
buf += bsize;
|
||||
len -= bsize;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Read byte from NFC buffers */
|
||||
static u8 mpc5121_nfc_read_byte(struct mtd_info *mtd)
|
||||
{
|
||||
@ -732,7 +711,6 @@ static int __devinit mpc5121_nfc_probe(struct platform_device *op)
|
||||
chip->read_word = mpc5121_nfc_read_word;
|
||||
chip->read_buf = mpc5121_nfc_read_buf;
|
||||
chip->write_buf = mpc5121_nfc_write_buf;
|
||||
chip->verify_buf = mpc5121_nfc_verify_buf;
|
||||
chip->select_chip = mpc5121_nfc_select_chip;
|
||||
chip->bbt_options = NAND_BBT_USE_FLASH;
|
||||
chip->ecc.mode = NAND_ECC_SOFT;
|
||||
|
@ -43,8 +43,8 @@
|
||||
|
||||
#define nfc_is_v21() (cpu_is_mx25() || cpu_is_mx35())
|
||||
#define nfc_is_v1() (cpu_is_mx31() || cpu_is_mx27() || cpu_is_mx21())
|
||||
#define nfc_is_v3_2() (cpu_is_mx51() || cpu_is_mx53())
|
||||
#define nfc_is_v3() nfc_is_v3_2()
|
||||
#define nfc_is_v3_2a() cpu_is_mx51()
|
||||
#define nfc_is_v3_2b() cpu_is_mx53()
|
||||
|
||||
/* Addresses for NFC registers */
|
||||
#define NFC_V1_V2_BUF_SIZE (host->regs + 0x00)
|
||||
@ -122,7 +122,7 @@
|
||||
#define NFC_V3_CONFIG2_2CMD_PHASES (1 << 4)
|
||||
#define NFC_V3_CONFIG2_NUM_ADDR_PHASE0 (1 << 5)
|
||||
#define NFC_V3_CONFIG2_ECC_MODE_8 (1 << 6)
|
||||
#define NFC_V3_CONFIG2_PPB(x) (((x) & 0x3) << 7)
|
||||
#define NFC_V3_CONFIG2_PPB(x, shift) (((x) & 0x3) << shift)
|
||||
#define NFC_V3_CONFIG2_NUM_ADDR_PHASE1(x) (((x) & 0x3) << 12)
|
||||
#define NFC_V3_CONFIG2_INT_MSK (1 << 15)
|
||||
#define NFC_V3_CONFIG2_ST_CMD(x) (((x) & 0xff) << 24)
|
||||
@ -174,6 +174,7 @@ struct mxc_nand_devtype_data {
|
||||
int spare_len;
|
||||
int eccbytes;
|
||||
int eccsize;
|
||||
int ppb_shift;
|
||||
};
|
||||
|
||||
struct mxc_nand_host {
|
||||
@ -745,14 +746,6 @@ static void mxc_nand_read_buf(struct mtd_info *mtd, u_char *buf, int len)
|
||||
host->buf_start += n;
|
||||
}
|
||||
|
||||
/* Used by the upper layer to verify the data in NAND Flash
|
||||
* with the data in the buf. */
|
||||
static int mxc_nand_verify_buf(struct mtd_info *mtd,
|
||||
const u_char *buf, int len)
|
||||
{
|
||||
return -EFAULT;
|
||||
}
|
||||
|
||||
/* This function is used by upper layer for select and
|
||||
* deselect of the NAND chip */
|
||||
static void mxc_nand_select_chip_v1_v3(struct mtd_info *mtd, int chip)
|
||||
@ -784,7 +777,7 @@ static void mxc_nand_select_chip_v2(struct mtd_info *mtd, int chip)
|
||||
if (chip == -1) {
|
||||
/* Disable the NFC clock */
|
||||
if (host->clk_act) {
|
||||
clk_disable(host->clk);
|
||||
clk_disable_unprepare(host->clk);
|
||||
host->clk_act = 0;
|
||||
}
|
||||
return;
|
||||
@ -792,7 +785,7 @@ static void mxc_nand_select_chip_v2(struct mtd_info *mtd, int chip)
|
||||
|
||||
if (!host->clk_act) {
|
||||
/* Enable the NFC clock */
|
||||
clk_enable(host->clk);
|
||||
clk_prepare_enable(host->clk);
|
||||
host->clk_act = 1;
|
||||
}
|
||||
|
||||
@ -1021,7 +1014,9 @@ static void preset_v3(struct mtd_info *mtd)
|
||||
}
|
||||
|
||||
if (mtd->writesize) {
|
||||
config2 |= NFC_V3_CONFIG2_PPB(ffs(mtd->erasesize / mtd->writesize) - 6);
|
||||
config2 |= NFC_V3_CONFIG2_PPB(
|
||||
ffs(mtd->erasesize / mtd->writesize) - 6,
|
||||
host->devtype_data->ppb_shift);
|
||||
host->eccsize = get_eccsize(mtd);
|
||||
if (host->eccsize == 8)
|
||||
config2 |= NFC_V3_CONFIG2_ECC_MODE_8;
|
||||
@ -1234,7 +1229,7 @@ static const struct mxc_nand_devtype_data imx25_nand_devtype_data = {
|
||||
.eccsize = 0,
|
||||
};
|
||||
|
||||
/* v3: i.MX51, i.MX53 */
|
||||
/* v3.2a: i.MX51 */
|
||||
static const struct mxc_nand_devtype_data imx51_nand_devtype_data = {
|
||||
.preset = preset_v3,
|
||||
.send_cmd = send_cmd_v3,
|
||||
@ -1258,6 +1253,34 @@ static const struct mxc_nand_devtype_data imx51_nand_devtype_data = {
|
||||
.spare_len = 64,
|
||||
.eccbytes = 0,
|
||||
.eccsize = 0,
|
||||
.ppb_shift = 7,
|
||||
};
|
||||
|
||||
/* v3.2b: i.MX53 */
|
||||
static const struct mxc_nand_devtype_data imx53_nand_devtype_data = {
|
||||
.preset = preset_v3,
|
||||
.send_cmd = send_cmd_v3,
|
||||
.send_addr = send_addr_v3,
|
||||
.send_page = send_page_v3,
|
||||
.send_read_id = send_read_id_v3,
|
||||
.get_dev_status = get_dev_status_v3,
|
||||
.check_int = check_int_v3,
|
||||
.irq_control = irq_control_v3,
|
||||
.get_ecc_status = get_ecc_status_v3,
|
||||
.ecclayout_512 = &nandv2_hw_eccoob_smallpage,
|
||||
.ecclayout_2k = &nandv2_hw_eccoob_largepage,
|
||||
.ecclayout_4k = &nandv2_hw_eccoob_smallpage, /* XXX: needs fix */
|
||||
.select_chip = mxc_nand_select_chip_v1_v3,
|
||||
.correct_data = mxc_nand_correct_data_v2_v3,
|
||||
.irqpending_quirk = 0,
|
||||
.needs_ip = 1,
|
||||
.regs_offset = 0,
|
||||
.spare0_offset = 0x1000,
|
||||
.axi_offset = 0x1e00,
|
||||
.spare_len = 64,
|
||||
.eccbytes = 0,
|
||||
.eccsize = 0,
|
||||
.ppb_shift = 8,
|
||||
};
|
||||
|
||||
#ifdef CONFIG_OF_MTD
|
||||
@ -1274,6 +1297,9 @@ static const struct of_device_id mxcnd_dt_ids[] = {
|
||||
}, {
|
||||
.compatible = "fsl,imx51-nand",
|
||||
.data = &imx51_nand_devtype_data,
|
||||
}, {
|
||||
.compatible = "fsl,imx53-nand",
|
||||
.data = &imx53_nand_devtype_data,
|
||||
},
|
||||
{ /* sentinel */ }
|
||||
};
|
||||
@ -1327,15 +1353,17 @@ static int __init mxcnd_probe_pdata(struct mxc_nand_host *host)
|
||||
host->devtype_data = &imx27_nand_devtype_data;
|
||||
} else if (nfc_is_v21()) {
|
||||
host->devtype_data = &imx25_nand_devtype_data;
|
||||
} else if (nfc_is_v3_2()) {
|
||||
} else if (nfc_is_v3_2a()) {
|
||||
host->devtype_data = &imx51_nand_devtype_data;
|
||||
} else if (nfc_is_v3_2b()) {
|
||||
host->devtype_data = &imx53_nand_devtype_data;
|
||||
} else
|
||||
BUG();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int __init mxcnd_probe(struct platform_device *pdev)
|
||||
static int __devinit mxcnd_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct nand_chip *this;
|
||||
struct mtd_info *mtd;
|
||||
@ -1344,8 +1372,8 @@ static int __init mxcnd_probe(struct platform_device *pdev)
|
||||
int err = 0;
|
||||
|
||||
/* Allocate memory for MTD device structure and private data */
|
||||
host = kzalloc(sizeof(struct mxc_nand_host) + NAND_MAX_PAGESIZE +
|
||||
NAND_MAX_OOBSIZE, GFP_KERNEL);
|
||||
host = devm_kzalloc(&pdev->dev, sizeof(struct mxc_nand_host) +
|
||||
NAND_MAX_PAGESIZE + NAND_MAX_OOBSIZE, GFP_KERNEL);
|
||||
if (!host)
|
||||
return -ENOMEM;
|
||||
|
||||
@ -1370,36 +1398,38 @@ static int __init mxcnd_probe(struct platform_device *pdev)
|
||||
this->read_word = mxc_nand_read_word;
|
||||
this->write_buf = mxc_nand_write_buf;
|
||||
this->read_buf = mxc_nand_read_buf;
|
||||
this->verify_buf = mxc_nand_verify_buf;
|
||||
|
||||
host->clk = clk_get(&pdev->dev, "nfc");
|
||||
if (IS_ERR(host->clk)) {
|
||||
err = PTR_ERR(host->clk);
|
||||
goto eclk;
|
||||
}
|
||||
|
||||
clk_prepare_enable(host->clk);
|
||||
host->clk_act = 1;
|
||||
|
||||
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
if (!res) {
|
||||
err = -ENODEV;
|
||||
goto eres;
|
||||
}
|
||||
|
||||
host->base = ioremap(res->start, resource_size(res));
|
||||
if (!host->base) {
|
||||
err = -ENOMEM;
|
||||
goto eres;
|
||||
}
|
||||
|
||||
host->main_area0 = host->base;
|
||||
host->clk = devm_clk_get(&pdev->dev, NULL);
|
||||
if (IS_ERR(host->clk))
|
||||
return PTR_ERR(host->clk);
|
||||
|
||||
err = mxcnd_probe_dt(host);
|
||||
if (err > 0)
|
||||
err = mxcnd_probe_pdata(host);
|
||||
if (err < 0)
|
||||
goto eirq;
|
||||
return err;
|
||||
|
||||
if (host->devtype_data->needs_ip) {
|
||||
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
if (!res)
|
||||
return -ENODEV;
|
||||
host->regs_ip = devm_request_and_ioremap(&pdev->dev, res);
|
||||
if (!host->regs_ip)
|
||||
return -ENOMEM;
|
||||
|
||||
res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
|
||||
} else {
|
||||
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
}
|
||||
|
||||
if (!res)
|
||||
return -ENODEV;
|
||||
|
||||
host->base = devm_request_and_ioremap(&pdev->dev, res);
|
||||
if (!host->base)
|
||||
return -ENOMEM;
|
||||
|
||||
host->main_area0 = host->base;
|
||||
|
||||
if (host->devtype_data->regs_offset)
|
||||
host->regs = host->base + host->devtype_data->regs_offset;
|
||||
@ -1414,19 +1444,6 @@ static int __init mxcnd_probe(struct platform_device *pdev)
|
||||
this->ecc.size = 512;
|
||||
this->ecc.layout = host->devtype_data->ecclayout_512;
|
||||
|
||||
if (host->devtype_data->needs_ip) {
|
||||
res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
|
||||
if (!res) {
|
||||
err = -ENODEV;
|
||||
goto eirq;
|
||||
}
|
||||
host->regs_ip = ioremap(res->start, resource_size(res));
|
||||
if (!host->regs_ip) {
|
||||
err = -ENOMEM;
|
||||
goto eirq;
|
||||
}
|
||||
}
|
||||
|
||||
if (host->pdata.hw_ecc) {
|
||||
this->ecc.calculate = mxc_nand_calculate_ecc;
|
||||
this->ecc.hwctl = mxc_nand_enable_hwecc;
|
||||
@ -1458,9 +1475,13 @@ static int __init mxcnd_probe(struct platform_device *pdev)
|
||||
*/
|
||||
host->devtype_data->irq_control(host, 0);
|
||||
|
||||
err = request_irq(host->irq, mxc_nfc_irq, IRQF_DISABLED, DRIVER_NAME, host);
|
||||
err = devm_request_irq(&pdev->dev, host->irq, mxc_nfc_irq,
|
||||
IRQF_DISABLED, DRIVER_NAME, host);
|
||||
if (err)
|
||||
goto eirq;
|
||||
return err;
|
||||
|
||||
clk_prepare_enable(host->clk);
|
||||
host->clk_act = 1;
|
||||
|
||||
/*
|
||||
* Now that we "own" the interrupt make sure the interrupt mask bit is
|
||||
@ -1512,15 +1533,7 @@ static int __init mxcnd_probe(struct platform_device *pdev)
|
||||
return 0;
|
||||
|
||||
escan:
|
||||
free_irq(host->irq, host);
|
||||
eirq:
|
||||
if (host->regs_ip)
|
||||
iounmap(host->regs_ip);
|
||||
iounmap(host->base);
|
||||
eres:
|
||||
clk_put(host->clk);
|
||||
eclk:
|
||||
kfree(host);
|
||||
clk_disable_unprepare(host->clk);
|
||||
|
||||
return err;
|
||||
}
|
||||
@ -1529,16 +1542,9 @@ static int __devexit mxcnd_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct mxc_nand_host *host = platform_get_drvdata(pdev);
|
||||
|
||||
clk_put(host->clk);
|
||||
|
||||
platform_set_drvdata(pdev, NULL);
|
||||
|
||||
nand_release(&host->mtd);
|
||||
free_irq(host->irq, host);
|
||||
if (host->regs_ip)
|
||||
iounmap(host->regs_ip);
|
||||
iounmap(host->base);
|
||||
kfree(host);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@ -1549,22 +1555,10 @@ static struct platform_driver mxcnd_driver = {
|
||||
.owner = THIS_MODULE,
|
||||
.of_match_table = of_match_ptr(mxcnd_dt_ids),
|
||||
},
|
||||
.probe = mxcnd_probe,
|
||||
.remove = __devexit_p(mxcnd_remove),
|
||||
};
|
||||
|
||||
static int __init mxc_nd_init(void)
|
||||
{
|
||||
return platform_driver_probe(&mxcnd_driver, mxcnd_probe);
|
||||
}
|
||||
|
||||
static void __exit mxc_nd_cleanup(void)
|
||||
{
|
||||
/* Unregister the device structure */
|
||||
platform_driver_unregister(&mxcnd_driver);
|
||||
}
|
||||
|
||||
module_init(mxc_nd_init);
|
||||
module_exit(mxc_nd_cleanup);
|
||||
module_platform_driver(mxcnd_driver);
|
||||
|
||||
MODULE_AUTHOR("Freescale Semiconductor, Inc.");
|
||||
MODULE_DESCRIPTION("MXC NAND MTD driver");
|
||||
|
@ -242,25 +242,6 @@ static void nand_read_buf(struct mtd_info *mtd, uint8_t *buf, int len)
|
||||
buf[i] = readb(chip->IO_ADDR_R);
|
||||
}
|
||||
|
||||
/**
|
||||
* nand_verify_buf - [DEFAULT] Verify chip data against buffer
|
||||
* @mtd: MTD device structure
|
||||
* @buf: buffer containing the data to compare
|
||||
* @len: number of bytes to compare
|
||||
*
|
||||
* Default verify function for 8bit buswidth.
|
||||
*/
|
||||
static int nand_verify_buf(struct mtd_info *mtd, const uint8_t *buf, int len)
|
||||
{
|
||||
int i;
|
||||
struct nand_chip *chip = mtd->priv;
|
||||
|
||||
for (i = 0; i < len; i++)
|
||||
if (buf[i] != readb(chip->IO_ADDR_R))
|
||||
return -EFAULT;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* nand_write_buf16 - [DEFAULT] write buffer to chip
|
||||
* @mtd: MTD device structure
|
||||
@ -300,28 +281,6 @@ static void nand_read_buf16(struct mtd_info *mtd, uint8_t *buf, int len)
|
||||
p[i] = readw(chip->IO_ADDR_R);
|
||||
}
|
||||
|
||||
/**
|
||||
* nand_verify_buf16 - [DEFAULT] Verify chip data against buffer
|
||||
* @mtd: MTD device structure
|
||||
* @buf: buffer containing the data to compare
|
||||
* @len: number of bytes to compare
|
||||
*
|
||||
* Default verify function for 16bit buswidth.
|
||||
*/
|
||||
static int nand_verify_buf16(struct mtd_info *mtd, const uint8_t *buf, int len)
|
||||
{
|
||||
int i;
|
||||
struct nand_chip *chip = mtd->priv;
|
||||
u16 *p = (u16 *) buf;
|
||||
len >>= 1;
|
||||
|
||||
for (i = 0; i < len; i++)
|
||||
if (p[i] != readw(chip->IO_ADDR_R))
|
||||
return -EFAULT;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* nand_block_bad - [DEFAULT] Read bad block marker from the chip
|
||||
* @mtd: MTD device structure
|
||||
@ -1525,7 +1484,8 @@ static int nand_do_read_ops(struct mtd_info *mtd, loff_t from,
|
||||
ret = chip->ecc.read_page_raw(mtd, chip, bufpoi,
|
||||
oob_required,
|
||||
page);
|
||||
else if (!aligned && NAND_SUBPAGE_READ(chip) && !oob)
|
||||
else if (!aligned && NAND_HAS_SUBPAGE_READ(chip) &&
|
||||
!oob)
|
||||
ret = chip->ecc.read_subpage(mtd, chip,
|
||||
col, bytes, bufpoi);
|
||||
else
|
||||
@ -1542,7 +1502,7 @@ static int nand_do_read_ops(struct mtd_info *mtd, loff_t from,
|
||||
|
||||
/* Transfer not aligned data */
|
||||
if (!aligned) {
|
||||
if (!NAND_SUBPAGE_READ(chip) && !oob &&
|
||||
if (!NAND_HAS_SUBPAGE_READ(chip) && !oob &&
|
||||
!(mtd->ecc_stats.failed - stats.failed) &&
|
||||
(ops->mode != MTD_OPS_RAW)) {
|
||||
chip->pagebuf = realpage;
|
||||
@ -1565,14 +1525,6 @@ static int nand_do_read_ops(struct mtd_info *mtd, loff_t from,
|
||||
oobreadlen -= toread;
|
||||
}
|
||||
}
|
||||
|
||||
if (!(chip->options & NAND_NO_READRDY)) {
|
||||
/* Apply delay or wait for ready/busy pin */
|
||||
if (!chip->dev_ready)
|
||||
udelay(chip->chip_delay);
|
||||
else
|
||||
nand_wait_ready(mtd);
|
||||
}
|
||||
} else {
|
||||
memcpy(buf, chip->buffers->databuf + col, bytes);
|
||||
buf += bytes;
|
||||
@ -1633,7 +1585,7 @@ static int nand_read(struct mtd_info *mtd, loff_t from, size_t len,
|
||||
ops.len = len;
|
||||
ops.datbuf = buf;
|
||||
ops.oobbuf = NULL;
|
||||
ops.mode = 0;
|
||||
ops.mode = MTD_OPS_PLACE_OOB;
|
||||
ret = nand_do_read_ops(mtd, from, &ops);
|
||||
*retlen = ops.retlen;
|
||||
nand_release_device(mtd);
|
||||
@ -1837,14 +1789,6 @@ static int nand_do_read_oob(struct mtd_info *mtd, loff_t from,
|
||||
len = min(len, readlen);
|
||||
buf = nand_transfer_oob(chip, buf, ops, len);
|
||||
|
||||
if (!(chip->options & NAND_NO_READRDY)) {
|
||||
/* Apply delay or wait for ready/busy pin */
|
||||
if (!chip->dev_ready)
|
||||
udelay(chip->chip_delay);
|
||||
else
|
||||
nand_wait_ready(mtd);
|
||||
}
|
||||
|
||||
readlen -= len;
|
||||
if (!readlen)
|
||||
break;
|
||||
@ -1927,12 +1871,14 @@ out:
|
||||
*
|
||||
* Not for syndrome calculating ECC controllers, which use a special oob layout.
|
||||
*/
|
||||
static void nand_write_page_raw(struct mtd_info *mtd, struct nand_chip *chip,
|
||||
static int nand_write_page_raw(struct mtd_info *mtd, struct nand_chip *chip,
|
||||
const uint8_t *buf, int oob_required)
|
||||
{
|
||||
chip->write_buf(mtd, buf, mtd->writesize);
|
||||
if (oob_required)
|
||||
chip->write_buf(mtd, chip->oob_poi, mtd->oobsize);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1944,7 +1890,7 @@ static void nand_write_page_raw(struct mtd_info *mtd, struct nand_chip *chip,
|
||||
*
|
||||
* We need a special oob layout and handling even when ECC isn't checked.
|
||||
*/
|
||||
static void nand_write_page_raw_syndrome(struct mtd_info *mtd,
|
||||
static int nand_write_page_raw_syndrome(struct mtd_info *mtd,
|
||||
struct nand_chip *chip,
|
||||
const uint8_t *buf, int oob_required)
|
||||
{
|
||||
@ -1974,6 +1920,8 @@ static void nand_write_page_raw_syndrome(struct mtd_info *mtd,
|
||||
size = mtd->oobsize - (oob - chip->oob_poi);
|
||||
if (size)
|
||||
chip->write_buf(mtd, oob, size);
|
||||
|
||||
return 0;
|
||||
}
|
||||
/**
|
||||
* nand_write_page_swecc - [REPLACEABLE] software ECC based page write function
|
||||
@ -1982,7 +1930,7 @@ static void nand_write_page_raw_syndrome(struct mtd_info *mtd,
|
||||
* @buf: data buffer
|
||||
* @oob_required: must write chip->oob_poi to OOB
|
||||
*/
|
||||
static void nand_write_page_swecc(struct mtd_info *mtd, struct nand_chip *chip,
|
||||
static int nand_write_page_swecc(struct mtd_info *mtd, struct nand_chip *chip,
|
||||
const uint8_t *buf, int oob_required)
|
||||
{
|
||||
int i, eccsize = chip->ecc.size;
|
||||
@ -1999,7 +1947,7 @@ static void nand_write_page_swecc(struct mtd_info *mtd, struct nand_chip *chip,
|
||||
for (i = 0; i < chip->ecc.total; i++)
|
||||
chip->oob_poi[eccpos[i]] = ecc_calc[i];
|
||||
|
||||
chip->ecc.write_page_raw(mtd, chip, buf, 1);
|
||||
return chip->ecc.write_page_raw(mtd, chip, buf, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -2009,7 +1957,7 @@ static void nand_write_page_swecc(struct mtd_info *mtd, struct nand_chip *chip,
|
||||
* @buf: data buffer
|
||||
* @oob_required: must write chip->oob_poi to OOB
|
||||
*/
|
||||
static void nand_write_page_hwecc(struct mtd_info *mtd, struct nand_chip *chip,
|
||||
static int nand_write_page_hwecc(struct mtd_info *mtd, struct nand_chip *chip,
|
||||
const uint8_t *buf, int oob_required)
|
||||
{
|
||||
int i, eccsize = chip->ecc.size;
|
||||
@ -2029,6 +1977,8 @@ static void nand_write_page_hwecc(struct mtd_info *mtd, struct nand_chip *chip,
|
||||
chip->oob_poi[eccpos[i]] = ecc_calc[i];
|
||||
|
||||
chip->write_buf(mtd, chip->oob_poi, mtd->oobsize);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -2041,7 +1991,7 @@ static void nand_write_page_hwecc(struct mtd_info *mtd, struct nand_chip *chip,
|
||||
* The hw generator calculates the error syndrome automatically. Therefore we
|
||||
* need a special oob layout and handling.
|
||||
*/
|
||||
static void nand_write_page_syndrome(struct mtd_info *mtd,
|
||||
static int nand_write_page_syndrome(struct mtd_info *mtd,
|
||||
struct nand_chip *chip,
|
||||
const uint8_t *buf, int oob_required)
|
||||
{
|
||||
@ -2075,6 +2025,8 @@ static void nand_write_page_syndrome(struct mtd_info *mtd,
|
||||
i = mtd->oobsize - (oob - chip->oob_poi);
|
||||
if (i)
|
||||
chip->write_buf(mtd, oob, i);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -2096,9 +2048,12 @@ static int nand_write_page(struct mtd_info *mtd, struct nand_chip *chip,
|
||||
chip->cmdfunc(mtd, NAND_CMD_SEQIN, 0x00, page);
|
||||
|
||||
if (unlikely(raw))
|
||||
chip->ecc.write_page_raw(mtd, chip, buf, oob_required);
|
||||
status = chip->ecc.write_page_raw(mtd, chip, buf, oob_required);
|
||||
else
|
||||
chip->ecc.write_page(mtd, chip, buf, oob_required);
|
||||
status = chip->ecc.write_page(mtd, chip, buf, oob_required);
|
||||
|
||||
if (status < 0)
|
||||
return status;
|
||||
|
||||
/*
|
||||
* Cached progamming disabled for now. Not sure if it's worth the
|
||||
@ -2125,16 +2080,6 @@ static int nand_write_page(struct mtd_info *mtd, struct nand_chip *chip,
|
||||
status = chip->waitfunc(mtd, chip);
|
||||
}
|
||||
|
||||
#ifdef CONFIG_MTD_NAND_VERIFY_WRITE
|
||||
/* Send command to read back the data */
|
||||
chip->cmdfunc(mtd, NAND_CMD_READ0, 0, page);
|
||||
|
||||
if (chip->verify_buf(mtd, buf, mtd->writesize))
|
||||
return -EIO;
|
||||
|
||||
/* Make sure the next page prog is preceded by a status read */
|
||||
chip->cmdfunc(mtd, NAND_CMD_STATUS, -1, -1);
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -2336,7 +2281,7 @@ static int panic_nand_write(struct mtd_info *mtd, loff_t to, size_t len,
|
||||
ops.len = len;
|
||||
ops.datbuf = (uint8_t *)buf;
|
||||
ops.oobbuf = NULL;
|
||||
ops.mode = 0;
|
||||
ops.mode = MTD_OPS_PLACE_OOB;
|
||||
|
||||
ret = nand_do_write_ops(mtd, to, &ops);
|
||||
|
||||
@ -2365,7 +2310,7 @@ static int nand_write(struct mtd_info *mtd, loff_t to, size_t len,
|
||||
ops.len = len;
|
||||
ops.datbuf = (uint8_t *)buf;
|
||||
ops.oobbuf = NULL;
|
||||
ops.mode = 0;
|
||||
ops.mode = MTD_OPS_PLACE_OOB;
|
||||
ret = nand_do_write_ops(mtd, to, &ops);
|
||||
*retlen = ops.retlen;
|
||||
nand_release_device(mtd);
|
||||
@ -2754,6 +2699,50 @@ static int nand_block_markbad(struct mtd_info *mtd, loff_t ofs)
|
||||
return chip->block_markbad(mtd, ofs);
|
||||
}
|
||||
|
||||
/**
|
||||
* nand_onfi_set_features- [REPLACEABLE] set features for ONFI nand
|
||||
* @mtd: MTD device structure
|
||||
* @chip: nand chip info structure
|
||||
* @addr: feature address.
|
||||
* @subfeature_param: the subfeature parameters, a four bytes array.
|
||||
*/
|
||||
static int nand_onfi_set_features(struct mtd_info *mtd, struct nand_chip *chip,
|
||||
int addr, uint8_t *subfeature_param)
|
||||
{
|
||||
int status;
|
||||
|
||||
if (!chip->onfi_version)
|
||||
return -EINVAL;
|
||||
|
||||
chip->cmdfunc(mtd, NAND_CMD_SET_FEATURES, addr, -1);
|
||||
chip->write_buf(mtd, subfeature_param, ONFI_SUBFEATURE_PARAM_LEN);
|
||||
status = chip->waitfunc(mtd, chip);
|
||||
if (status & NAND_STATUS_FAIL)
|
||||
return -EIO;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* nand_onfi_get_features- [REPLACEABLE] get features for ONFI nand
|
||||
* @mtd: MTD device structure
|
||||
* @chip: nand chip info structure
|
||||
* @addr: feature address.
|
||||
* @subfeature_param: the subfeature parameters, a four bytes array.
|
||||
*/
|
||||
static int nand_onfi_get_features(struct mtd_info *mtd, struct nand_chip *chip,
|
||||
int addr, uint8_t *subfeature_param)
|
||||
{
|
||||
if (!chip->onfi_version)
|
||||
return -EINVAL;
|
||||
|
||||
/* clear the sub feature parameters */
|
||||
memset(subfeature_param, 0, ONFI_SUBFEATURE_PARAM_LEN);
|
||||
|
||||
chip->cmdfunc(mtd, NAND_CMD_GET_FEATURES, addr, -1);
|
||||
chip->read_buf(mtd, subfeature_param, ONFI_SUBFEATURE_PARAM_LEN);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* nand_suspend - [MTD Interface] Suspend the NAND flash
|
||||
* @mtd: MTD device structure
|
||||
@ -2809,8 +2798,6 @@ static void nand_set_defaults(struct nand_chip *chip, int busw)
|
||||
chip->write_buf = busw ? nand_write_buf16 : nand_write_buf;
|
||||
if (!chip->read_buf)
|
||||
chip->read_buf = busw ? nand_read_buf16 : nand_read_buf;
|
||||
if (!chip->verify_buf)
|
||||
chip->verify_buf = busw ? nand_verify_buf16 : nand_verify_buf;
|
||||
if (!chip->scan_bbt)
|
||||
chip->scan_bbt = nand_default_bbt;
|
||||
|
||||
@ -2914,13 +2901,249 @@ static int nand_flash_detect_onfi(struct mtd_info *mtd, struct nand_chip *chip,
|
||||
if (le16_to_cpu(p->features) & 1)
|
||||
*busw = NAND_BUSWIDTH_16;
|
||||
|
||||
chip->options &= ~NAND_CHIPOPTIONS_MSK;
|
||||
chip->options |= NAND_NO_READRDY & NAND_CHIPOPTIONS_MSK;
|
||||
|
||||
pr_info("ONFI flash detected\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* nand_id_has_period - Check if an ID string has a given wraparound period
|
||||
* @id_data: the ID string
|
||||
* @arrlen: the length of the @id_data array
|
||||
* @period: the period of repitition
|
||||
*
|
||||
* Check if an ID string is repeated within a given sequence of bytes at
|
||||
* specific repetition interval period (e.g., {0x20,0x01,0x7F,0x20} has a
|
||||
* period of 2). This is a helper function for nand_id_len(). Returns non-zero
|
||||
* if the repetition has a period of @period; otherwise, returns zero.
|
||||
*/
|
||||
static int nand_id_has_period(u8 *id_data, int arrlen, int period)
|
||||
{
|
||||
int i, j;
|
||||
for (i = 0; i < period; i++)
|
||||
for (j = i + period; j < arrlen; j += period)
|
||||
if (id_data[i] != id_data[j])
|
||||
return 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* nand_id_len - Get the length of an ID string returned by CMD_READID
|
||||
* @id_data: the ID string
|
||||
* @arrlen: the length of the @id_data array
|
||||
|
||||
* Returns the length of the ID string, according to known wraparound/trailing
|
||||
* zero patterns. If no pattern exists, returns the length of the array.
|
||||
*/
|
||||
static int nand_id_len(u8 *id_data, int arrlen)
|
||||
{
|
||||
int last_nonzero, period;
|
||||
|
||||
/* Find last non-zero byte */
|
||||
for (last_nonzero = arrlen - 1; last_nonzero >= 0; last_nonzero--)
|
||||
if (id_data[last_nonzero])
|
||||
break;
|
||||
|
||||
/* All zeros */
|
||||
if (last_nonzero < 0)
|
||||
return 0;
|
||||
|
||||
/* Calculate wraparound period */
|
||||
for (period = 1; period < arrlen; period++)
|
||||
if (nand_id_has_period(id_data, arrlen, period))
|
||||
break;
|
||||
|
||||
/* There's a repeated pattern */
|
||||
if (period < arrlen)
|
||||
return period;
|
||||
|
||||
/* There are trailing zeros */
|
||||
if (last_nonzero < arrlen - 1)
|
||||
return last_nonzero + 1;
|
||||
|
||||
/* No pattern detected */
|
||||
return arrlen;
|
||||
}
|
||||
|
||||
/*
|
||||
* Many new NAND share similar device ID codes, which represent the size of the
|
||||
* chip. The rest of the parameters must be decoded according to generic or
|
||||
* manufacturer-specific "extended ID" decoding patterns.
|
||||
*/
|
||||
static void nand_decode_ext_id(struct mtd_info *mtd, struct nand_chip *chip,
|
||||
u8 id_data[8], int *busw)
|
||||
{
|
||||
int extid, id_len;
|
||||
/* The 3rd id byte holds MLC / multichip data */
|
||||
chip->cellinfo = id_data[2];
|
||||
/* The 4th id byte is the important one */
|
||||
extid = id_data[3];
|
||||
|
||||
id_len = nand_id_len(id_data, 8);
|
||||
|
||||
/*
|
||||
* Field definitions are in the following datasheets:
|
||||
* Old style (4,5 byte ID): Samsung K9GAG08U0M (p.32)
|
||||
* New style (6 byte ID): Samsung K9GAG08U0F (p.44)
|
||||
* Hynix MLC (6 byte ID): Hynix H27UBG8T2B (p.22)
|
||||
*
|
||||
* Check for ID length, cell type, and Hynix/Samsung ID to decide what
|
||||
* to do.
|
||||
*/
|
||||
if (id_len == 6 && id_data[0] == NAND_MFR_SAMSUNG) {
|
||||
/* Calc pagesize */
|
||||
mtd->writesize = 2048 << (extid & 0x03);
|
||||
extid >>= 2;
|
||||
/* Calc oobsize */
|
||||
switch (((extid >> 2) & 0x04) | (extid & 0x03)) {
|
||||
case 1:
|
||||
mtd->oobsize = 128;
|
||||
break;
|
||||
case 2:
|
||||
mtd->oobsize = 218;
|
||||
break;
|
||||
case 3:
|
||||
mtd->oobsize = 400;
|
||||
break;
|
||||
case 4:
|
||||
mtd->oobsize = 436;
|
||||
break;
|
||||
case 5:
|
||||
mtd->oobsize = 512;
|
||||
break;
|
||||
case 6:
|
||||
default: /* Other cases are "reserved" (unknown) */
|
||||
mtd->oobsize = 640;
|
||||
break;
|
||||
}
|
||||
extid >>= 2;
|
||||
/* Calc blocksize */
|
||||
mtd->erasesize = (128 * 1024) <<
|
||||
(((extid >> 1) & 0x04) | (extid & 0x03));
|
||||
*busw = 0;
|
||||
} else if (id_len == 6 && id_data[0] == NAND_MFR_HYNIX &&
|
||||
(chip->cellinfo & NAND_CI_CELLTYPE_MSK)) {
|
||||
unsigned int tmp;
|
||||
|
||||
/* Calc pagesize */
|
||||
mtd->writesize = 2048 << (extid & 0x03);
|
||||
extid >>= 2;
|
||||
/* Calc oobsize */
|
||||
switch (((extid >> 2) & 0x04) | (extid & 0x03)) {
|
||||
case 0:
|
||||
mtd->oobsize = 128;
|
||||
break;
|
||||
case 1:
|
||||
mtd->oobsize = 224;
|
||||
break;
|
||||
case 2:
|
||||
mtd->oobsize = 448;
|
||||
break;
|
||||
case 3:
|
||||
mtd->oobsize = 64;
|
||||
break;
|
||||
case 4:
|
||||
mtd->oobsize = 32;
|
||||
break;
|
||||
case 5:
|
||||
mtd->oobsize = 16;
|
||||
break;
|
||||
default:
|
||||
mtd->oobsize = 640;
|
||||
break;
|
||||
}
|
||||
extid >>= 2;
|
||||
/* Calc blocksize */
|
||||
tmp = ((extid >> 1) & 0x04) | (extid & 0x03);
|
||||
if (tmp < 0x03)
|
||||
mtd->erasesize = (128 * 1024) << tmp;
|
||||
else if (tmp == 0x03)
|
||||
mtd->erasesize = 768 * 1024;
|
||||
else
|
||||
mtd->erasesize = (64 * 1024) << tmp;
|
||||
*busw = 0;
|
||||
} else {
|
||||
/* Calc pagesize */
|
||||
mtd->writesize = 1024 << (extid & 0x03);
|
||||
extid >>= 2;
|
||||
/* Calc oobsize */
|
||||
mtd->oobsize = (8 << (extid & 0x01)) *
|
||||
(mtd->writesize >> 9);
|
||||
extid >>= 2;
|
||||
/* Calc blocksize. Blocksize is multiples of 64KiB */
|
||||
mtd->erasesize = (64 * 1024) << (extid & 0x03);
|
||||
extid >>= 2;
|
||||
/* Get buswidth information */
|
||||
*busw = (extid & 0x01) ? NAND_BUSWIDTH_16 : 0;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Old devices have chip data hardcoded in the device ID table. nand_decode_id
|
||||
* decodes a matching ID table entry and assigns the MTD size parameters for
|
||||
* the chip.
|
||||
*/
|
||||
static void nand_decode_id(struct mtd_info *mtd, struct nand_chip *chip,
|
||||
struct nand_flash_dev *type, u8 id_data[8],
|
||||
int *busw)
|
||||
{
|
||||
int maf_id = id_data[0];
|
||||
|
||||
mtd->erasesize = type->erasesize;
|
||||
mtd->writesize = type->pagesize;
|
||||
mtd->oobsize = mtd->writesize / 32;
|
||||
*busw = type->options & NAND_BUSWIDTH_16;
|
||||
|
||||
/*
|
||||
* Check for Spansion/AMD ID + repeating 5th, 6th byte since
|
||||
* some Spansion chips have erasesize that conflicts with size
|
||||
* listed in nand_ids table.
|
||||
* Data sheet (5 byte ID): Spansion S30ML-P ORNAND (p.39)
|
||||
*/
|
||||
if (maf_id == NAND_MFR_AMD && id_data[4] != 0x00 && id_data[5] == 0x00
|
||||
&& id_data[6] == 0x00 && id_data[7] == 0x00
|
||||
&& mtd->writesize == 512) {
|
||||
mtd->erasesize = 128 * 1024;
|
||||
mtd->erasesize <<= ((id_data[3] & 0x03) << 1);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Set the bad block marker/indicator (BBM/BBI) patterns according to some
|
||||
* heuristic patterns using various detected parameters (e.g., manufacturer,
|
||||
* page size, cell-type information).
|
||||
*/
|
||||
static void nand_decode_bbm_options(struct mtd_info *mtd,
|
||||
struct nand_chip *chip, u8 id_data[8])
|
||||
{
|
||||
int maf_id = id_data[0];
|
||||
|
||||
/* Set the bad block position */
|
||||
if (mtd->writesize > 512 || (chip->options & NAND_BUSWIDTH_16))
|
||||
chip->badblockpos = NAND_LARGE_BADBLOCK_POS;
|
||||
else
|
||||
chip->badblockpos = NAND_SMALL_BADBLOCK_POS;
|
||||
|
||||
/*
|
||||
* Bad block marker is stored in the last page of each block on Samsung
|
||||
* and Hynix MLC devices; stored in first two pages of each block on
|
||||
* Micron devices with 2KiB pages and on SLC Samsung, Hynix, Toshiba,
|
||||
* AMD/Spansion, and Macronix. All others scan only the first page.
|
||||
*/
|
||||
if ((chip->cellinfo & NAND_CI_CELLTYPE_MSK) &&
|
||||
(maf_id == NAND_MFR_SAMSUNG ||
|
||||
maf_id == NAND_MFR_HYNIX))
|
||||
chip->bbt_options |= NAND_BBT_SCANLASTPAGE;
|
||||
else if ((!(chip->cellinfo & NAND_CI_CELLTYPE_MSK) &&
|
||||
(maf_id == NAND_MFR_SAMSUNG ||
|
||||
maf_id == NAND_MFR_HYNIX ||
|
||||
maf_id == NAND_MFR_TOSHIBA ||
|
||||
maf_id == NAND_MFR_AMD ||
|
||||
maf_id == NAND_MFR_MACRONIX)) ||
|
||||
(mtd->writesize == 2048 &&
|
||||
maf_id == NAND_MFR_MICRON))
|
||||
chip->bbt_options |= NAND_BBT_SCAN2NDPAGE;
|
||||
}
|
||||
|
||||
/*
|
||||
* Get the flash and manufacturer id and lookup if the type is supported.
|
||||
*/
|
||||
@ -2932,7 +3155,6 @@ static struct nand_flash_dev *nand_get_flash_type(struct mtd_info *mtd,
|
||||
{
|
||||
int i, maf_idx;
|
||||
u8 id_data[8];
|
||||
int ret;
|
||||
|
||||
/* Select the device */
|
||||
chip->select_chip(mtd, 0);
|
||||
@ -2959,7 +3181,8 @@ static struct nand_flash_dev *nand_get_flash_type(struct mtd_info *mtd,
|
||||
|
||||
chip->cmdfunc(mtd, NAND_CMD_READID, 0x00, -1);
|
||||
|
||||
for (i = 0; i < 2; i++)
|
||||
/* Read entire ID string */
|
||||
for (i = 0; i < 8; i++)
|
||||
id_data[i] = chip->read_byte(mtd);
|
||||
|
||||
if (id_data[0] != *maf_id || id_data[1] != *dev_id) {
|
||||
@ -2979,18 +3202,10 @@ static struct nand_flash_dev *nand_get_flash_type(struct mtd_info *mtd,
|
||||
chip->onfi_version = 0;
|
||||
if (!type->name || !type->pagesize) {
|
||||
/* Check is chip is ONFI compliant */
|
||||
ret = nand_flash_detect_onfi(mtd, chip, &busw);
|
||||
if (ret)
|
||||
if (nand_flash_detect_onfi(mtd, chip, &busw))
|
||||
goto ident_done;
|
||||
}
|
||||
|
||||
chip->cmdfunc(mtd, NAND_CMD_READID, 0x00, -1);
|
||||
|
||||
/* Read entire ID string */
|
||||
|
||||
for (i = 0; i < 8; i++)
|
||||
id_data[i] = chip->read_byte(mtd);
|
||||
|
||||
if (!type->name)
|
||||
return ERR_PTR(-ENODEV);
|
||||
|
||||
@ -3003,86 +3218,13 @@ static struct nand_flash_dev *nand_get_flash_type(struct mtd_info *mtd,
|
||||
/* Set the pagesize, oobsize, erasesize by the driver */
|
||||
busw = chip->init_size(mtd, chip, id_data);
|
||||
} else if (!type->pagesize) {
|
||||
int extid;
|
||||
/* The 3rd id byte holds MLC / multichip data */
|
||||
chip->cellinfo = id_data[2];
|
||||
/* The 4th id byte is the important one */
|
||||
extid = id_data[3];
|
||||
|
||||
/*
|
||||
* Field definitions are in the following datasheets:
|
||||
* Old style (4,5 byte ID): Samsung K9GAG08U0M (p.32)
|
||||
* New style (6 byte ID): Samsung K9GBG08U0M (p.40)
|
||||
*
|
||||
* Check for wraparound + Samsung ID + nonzero 6th byte
|
||||
* to decide what to do.
|
||||
*/
|
||||
if (id_data[0] == id_data[6] && id_data[1] == id_data[7] &&
|
||||
id_data[0] == NAND_MFR_SAMSUNG &&
|
||||
(chip->cellinfo & NAND_CI_CELLTYPE_MSK) &&
|
||||
id_data[5] != 0x00) {
|
||||
/* Calc pagesize */
|
||||
mtd->writesize = 2048 << (extid & 0x03);
|
||||
extid >>= 2;
|
||||
/* Calc oobsize */
|
||||
switch (extid & 0x03) {
|
||||
case 1:
|
||||
mtd->oobsize = 128;
|
||||
break;
|
||||
case 2:
|
||||
mtd->oobsize = 218;
|
||||
break;
|
||||
case 3:
|
||||
mtd->oobsize = 400;
|
||||
break;
|
||||
default:
|
||||
mtd->oobsize = 436;
|
||||
break;
|
||||
}
|
||||
extid >>= 2;
|
||||
/* Calc blocksize */
|
||||
mtd->erasesize = (128 * 1024) <<
|
||||
(((extid >> 1) & 0x04) | (extid & 0x03));
|
||||
busw = 0;
|
||||
} else {
|
||||
/* Calc pagesize */
|
||||
mtd->writesize = 1024 << (extid & 0x03);
|
||||
extid >>= 2;
|
||||
/* Calc oobsize */
|
||||
mtd->oobsize = (8 << (extid & 0x01)) *
|
||||
(mtd->writesize >> 9);
|
||||
extid >>= 2;
|
||||
/* Calc blocksize. Blocksize is multiples of 64KiB */
|
||||
mtd->erasesize = (64 * 1024) << (extid & 0x03);
|
||||
extid >>= 2;
|
||||
/* Get buswidth information */
|
||||
busw = (extid & 0x01) ? NAND_BUSWIDTH_16 : 0;
|
||||
}
|
||||
/* Decode parameters from extended ID */
|
||||
nand_decode_ext_id(mtd, chip, id_data, &busw);
|
||||
} else {
|
||||
/*
|
||||
* Old devices have chip data hardcoded in the device id table.
|
||||
*/
|
||||
mtd->erasesize = type->erasesize;
|
||||
mtd->writesize = type->pagesize;
|
||||
mtd->oobsize = mtd->writesize / 32;
|
||||
busw = type->options & NAND_BUSWIDTH_16;
|
||||
|
||||
/*
|
||||
* Check for Spansion/AMD ID + repeating 5th, 6th byte since
|
||||
* some Spansion chips have erasesize that conflicts with size
|
||||
* listed in nand_ids table.
|
||||
* Data sheet (5 byte ID): Spansion S30ML-P ORNAND (p.39)
|
||||
*/
|
||||
if (*maf_id == NAND_MFR_AMD && id_data[4] != 0x00 &&
|
||||
id_data[5] == 0x00 && id_data[6] == 0x00 &&
|
||||
id_data[7] == 0x00 && mtd->writesize == 512) {
|
||||
mtd->erasesize = 128 * 1024;
|
||||
mtd->erasesize <<= ((id_data[3] & 0x03) << 1);
|
||||
}
|
||||
nand_decode_id(mtd, chip, type, id_data, &busw);
|
||||
}
|
||||
/* Get chip options, preserve non chip based options */
|
||||
chip->options &= ~NAND_CHIPOPTIONS_MSK;
|
||||
chip->options |= type->options & NAND_CHIPOPTIONS_MSK;
|
||||
/* Get chip options */
|
||||
chip->options |= type->options;
|
||||
|
||||
/*
|
||||
* Check if chip is not a Samsung device. Do not clear the
|
||||
@ -3112,6 +3254,8 @@ ident_done:
|
||||
return ERR_PTR(-EINVAL);
|
||||
}
|
||||
|
||||
nand_decode_bbm_options(mtd, chip, id_data);
|
||||
|
||||
/* Calculate the address shift from the page size */
|
||||
chip->page_shift = ffs(mtd->writesize) - 1;
|
||||
/* Convert chipsize to number of pages per chip -1 */
|
||||
@ -3128,33 +3272,6 @@ ident_done:
|
||||
|
||||
chip->badblockbits = 8;
|
||||
|
||||
/* Set the bad block position */
|
||||
if (mtd->writesize > 512 || (busw & NAND_BUSWIDTH_16))
|
||||
chip->badblockpos = NAND_LARGE_BADBLOCK_POS;
|
||||
else
|
||||
chip->badblockpos = NAND_SMALL_BADBLOCK_POS;
|
||||
|
||||
/*
|
||||
* Bad block marker is stored in the last page of each block
|
||||
* on Samsung and Hynix MLC devices; stored in first two pages
|
||||
* of each block on Micron devices with 2KiB pages and on
|
||||
* SLC Samsung, Hynix, Toshiba, AMD/Spansion, and Macronix.
|
||||
* All others scan only the first page.
|
||||
*/
|
||||
if ((chip->cellinfo & NAND_CI_CELLTYPE_MSK) &&
|
||||
(*maf_id == NAND_MFR_SAMSUNG ||
|
||||
*maf_id == NAND_MFR_HYNIX))
|
||||
chip->bbt_options |= NAND_BBT_SCANLASTPAGE;
|
||||
else if ((!(chip->cellinfo & NAND_CI_CELLTYPE_MSK) &&
|
||||
(*maf_id == NAND_MFR_SAMSUNG ||
|
||||
*maf_id == NAND_MFR_HYNIX ||
|
||||
*maf_id == NAND_MFR_TOSHIBA ||
|
||||
*maf_id == NAND_MFR_AMD ||
|
||||
*maf_id == NAND_MFR_MACRONIX)) ||
|
||||
(mtd->writesize == 2048 &&
|
||||
*maf_id == NAND_MFR_MICRON))
|
||||
chip->bbt_options |= NAND_BBT_SCAN2NDPAGE;
|
||||
|
||||
/* Check for AND chips with 4 page planes */
|
||||
if (chip->options & NAND_4PAGE_ARRAY)
|
||||
chip->erase_cmd = multi_erase_cmd;
|
||||
@ -3284,6 +3401,12 @@ int nand_scan_tail(struct mtd_info *mtd)
|
||||
if (!chip->write_page)
|
||||
chip->write_page = nand_write_page;
|
||||
|
||||
/* set for ONFI nand */
|
||||
if (!chip->onfi_set_features)
|
||||
chip->onfi_set_features = nand_onfi_set_features;
|
||||
if (!chip->onfi_get_features)
|
||||
chip->onfi_get_features = nand_onfi_get_features;
|
||||
|
||||
/*
|
||||
* Check ECC mode, default to software if 3byte/512byte hardware ECC is
|
||||
* selected and we have 256 byte pagesize fallback to software ECC
|
||||
@ -3477,6 +3600,10 @@ int nand_scan_tail(struct mtd_info *mtd)
|
||||
/* Invalidate the pagebuffer reference */
|
||||
chip->pagebuf = -1;
|
||||
|
||||
/* Large page NAND with SOFT_ECC should support subpage reads */
|
||||
if ((chip->ecc.mode == NAND_ECC_SOFT) && (chip->page_shift > 9))
|
||||
chip->options |= NAND_SUBPAGE_READ;
|
||||
|
||||
/* Fill in remaining MTD driver data */
|
||||
mtd->type = MTD_NANDFLASH;
|
||||
mtd->flags = (chip->options & NAND_ROM) ? MTD_CAP_ROM :
|
||||
|
@ -4,7 +4,7 @@
|
||||
* Overview:
|
||||
* Bad block table support for the NAND driver
|
||||
*
|
||||
* Copyright (C) 2004 Thomas Gleixner (tglx@linutronix.de)
|
||||
* Copyright © 2004 Thomas Gleixner (tglx@linutronix.de)
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
@ -22,7 +22,7 @@
|
||||
* BBT on flash. If a BBT is found then the contents are read and the memory
|
||||
* based BBT is created. If a mirrored BBT is selected then the mirror is
|
||||
* searched too and the versions are compared. If the mirror has a greater
|
||||
* version number than the mirror BBT is used to build the memory based BBT.
|
||||
* version number, then the mirror BBT is used to build the memory based BBT.
|
||||
* If the tables are not versioned, then we "or" the bad block information.
|
||||
* If one of the BBTs is out of date or does not exist it is (re)created.
|
||||
* If no BBT exists at all then the device is scanned for factory marked
|
||||
@ -62,21 +62,20 @@
|
||||
#include <linux/slab.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/mtd/mtd.h>
|
||||
#include <linux/mtd/bbm.h>
|
||||
#include <linux/mtd/nand.h>
|
||||
#include <linux/mtd/nand_ecc.h>
|
||||
#include <linux/bitops.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/vmalloc.h>
|
||||
#include <linux/export.h>
|
||||
#include <linux/string.h>
|
||||
|
||||
static int check_pattern_no_oob(uint8_t *buf, struct nand_bbt_descr *td)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = memcmp(buf, td->pattern, td->len);
|
||||
if (!ret)
|
||||
return ret;
|
||||
return -1;
|
||||
if (memcmp(buf, td->pattern, td->len))
|
||||
return -1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -92,19 +91,16 @@ static int check_pattern_no_oob(uint8_t *buf, struct nand_bbt_descr *td)
|
||||
*/
|
||||
static int check_pattern(uint8_t *buf, int len, int paglen, struct nand_bbt_descr *td)
|
||||
{
|
||||
int i, end = 0;
|
||||
int end = 0;
|
||||
uint8_t *p = buf;
|
||||
|
||||
if (td->options & NAND_BBT_NO_OOB)
|
||||
return check_pattern_no_oob(buf, td);
|
||||
|
||||
end = paglen + td->offs;
|
||||
if (td->options & NAND_BBT_SCANEMPTY) {
|
||||
for (i = 0; i < end; i++) {
|
||||
if (p[i] != 0xff)
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
if (td->options & NAND_BBT_SCANEMPTY)
|
||||
if (memchr_inv(p, 0xff, end))
|
||||
return -1;
|
||||
p += end;
|
||||
|
||||
/* Compare the pattern */
|
||||
@ -114,10 +110,8 @@ static int check_pattern(uint8_t *buf, int len, int paglen, struct nand_bbt_desc
|
||||
if (td->options & NAND_BBT_SCANEMPTY) {
|
||||
p += td->len;
|
||||
end += td->len;
|
||||
for (i = end; i < len; i++) {
|
||||
if (*p++ != 0xff)
|
||||
return -1;
|
||||
}
|
||||
if (memchr_inv(p, 0xff, len - end))
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@ -133,14 +127,9 @@ static int check_pattern(uint8_t *buf, int len, int paglen, struct nand_bbt_desc
|
||||
*/
|
||||
static int check_short_pattern(uint8_t *buf, struct nand_bbt_descr *td)
|
||||
{
|
||||
int i;
|
||||
uint8_t *p = buf;
|
||||
|
||||
/* Compare the pattern */
|
||||
for (i = 0; i < td->len; i++) {
|
||||
if (p[td->offs + i] != td->pattern[i])
|
||||
return -1;
|
||||
}
|
||||
if (memcmp(buf + td->offs, td->pattern, td->len))
|
||||
return -1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -288,7 +277,7 @@ static int read_abs_bbt(struct mtd_info *mtd, uint8_t *buf, struct nand_bbt_desc
|
||||
}
|
||||
|
||||
/* BBT marker is in the first page, no OOB */
|
||||
static int scan_read_raw_data(struct mtd_info *mtd, uint8_t *buf, loff_t offs,
|
||||
static int scan_read_data(struct mtd_info *mtd, uint8_t *buf, loff_t offs,
|
||||
struct nand_bbt_descr *td)
|
||||
{
|
||||
size_t retlen;
|
||||
@ -301,14 +290,24 @@ static int scan_read_raw_data(struct mtd_info *mtd, uint8_t *buf, loff_t offs,
|
||||
return mtd_read(mtd, offs, len, &retlen, buf);
|
||||
}
|
||||
|
||||
/* Scan read raw data from flash */
|
||||
static int scan_read_raw_oob(struct mtd_info *mtd, uint8_t *buf, loff_t offs,
|
||||
/**
|
||||
* scan_read_oob - [GENERIC] Scan data+OOB region to buffer
|
||||
* @mtd: MTD device structure
|
||||
* @buf: temporary buffer
|
||||
* @offs: offset at which to scan
|
||||
* @len: length of data region to read
|
||||
*
|
||||
* Scan read data from data+OOB. May traverse multiple pages, interleaving
|
||||
* page,OOB,page,OOB,... in buf. Completes transfer and returns the "strongest"
|
||||
* ECC condition (error or bitflip). May quit on the first (non-ECC) error.
|
||||
*/
|
||||
static int scan_read_oob(struct mtd_info *mtd, uint8_t *buf, loff_t offs,
|
||||
size_t len)
|
||||
{
|
||||
struct mtd_oob_ops ops;
|
||||
int res;
|
||||
int res, ret = 0;
|
||||
|
||||
ops.mode = MTD_OPS_RAW;
|
||||
ops.mode = MTD_OPS_PLACE_OOB;
|
||||
ops.ooboffs = 0;
|
||||
ops.ooblen = mtd->oobsize;
|
||||
|
||||
@ -318,24 +317,27 @@ static int scan_read_raw_oob(struct mtd_info *mtd, uint8_t *buf, loff_t offs,
|
||||
ops.oobbuf = buf + ops.len;
|
||||
|
||||
res = mtd_read_oob(mtd, offs, &ops);
|
||||
|
||||
if (res)
|
||||
return res;
|
||||
if (res) {
|
||||
if (!mtd_is_bitflip_or_eccerr(res))
|
||||
return res;
|
||||
else if (mtd_is_eccerr(res) || !ret)
|
||||
ret = res;
|
||||
}
|
||||
|
||||
buf += mtd->oobsize + mtd->writesize;
|
||||
len -= mtd->writesize;
|
||||
offs += mtd->writesize;
|
||||
}
|
||||
return 0;
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int scan_read_raw(struct mtd_info *mtd, uint8_t *buf, loff_t offs,
|
||||
static int scan_read(struct mtd_info *mtd, uint8_t *buf, loff_t offs,
|
||||
size_t len, struct nand_bbt_descr *td)
|
||||
{
|
||||
if (td->options & NAND_BBT_NO_OOB)
|
||||
return scan_read_raw_data(mtd, buf, offs, td);
|
||||
return scan_read_data(mtd, buf, offs, td);
|
||||
else
|
||||
return scan_read_raw_oob(mtd, buf, offs, len);
|
||||
return scan_read_oob(mtd, buf, offs, len);
|
||||
}
|
||||
|
||||
/* Scan write data with oob to flash */
|
||||
@ -373,14 +375,14 @@ static u32 bbt_get_ver_offs(struct mtd_info *mtd, struct nand_bbt_descr *td)
|
||||
* Read the bad block table(s) for all chips starting at a given page. We
|
||||
* assume that the bbt bits are in consecutive order.
|
||||
*/
|
||||
static int read_abs_bbts(struct mtd_info *mtd, uint8_t *buf,
|
||||
struct nand_bbt_descr *td, struct nand_bbt_descr *md)
|
||||
static void read_abs_bbts(struct mtd_info *mtd, uint8_t *buf,
|
||||
struct nand_bbt_descr *td, struct nand_bbt_descr *md)
|
||||
{
|
||||
struct nand_chip *this = mtd->priv;
|
||||
|
||||
/* Read the primary version, if available */
|
||||
if (td->options & NAND_BBT_VERSION) {
|
||||
scan_read_raw(mtd, buf, (loff_t)td->pages[0] << this->page_shift,
|
||||
scan_read(mtd, buf, (loff_t)td->pages[0] << this->page_shift,
|
||||
mtd->writesize, td);
|
||||
td->version[0] = buf[bbt_get_ver_offs(mtd, td)];
|
||||
pr_info("Bad block table at page %d, version 0x%02X\n",
|
||||
@ -389,28 +391,27 @@ static int read_abs_bbts(struct mtd_info *mtd, uint8_t *buf,
|
||||
|
||||
/* Read the mirror version, if available */
|
||||
if (md && (md->options & NAND_BBT_VERSION)) {
|
||||
scan_read_raw(mtd, buf, (loff_t)md->pages[0] << this->page_shift,
|
||||
mtd->writesize, td);
|
||||
scan_read(mtd, buf, (loff_t)md->pages[0] << this->page_shift,
|
||||
mtd->writesize, md);
|
||||
md->version[0] = buf[bbt_get_ver_offs(mtd, md)];
|
||||
pr_info("Bad block table at page %d, version 0x%02X\n",
|
||||
md->pages[0], md->version[0]);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Scan a given block full */
|
||||
static int scan_block_full(struct mtd_info *mtd, struct nand_bbt_descr *bd,
|
||||
loff_t offs, uint8_t *buf, size_t readlen,
|
||||
int scanlen, int len)
|
||||
int scanlen, int numpages)
|
||||
{
|
||||
int ret, j;
|
||||
|
||||
ret = scan_read_raw_oob(mtd, buf, offs, readlen);
|
||||
ret = scan_read_oob(mtd, buf, offs, readlen);
|
||||
/* Ignore ECC errors when checking for BBM */
|
||||
if (ret && !mtd_is_bitflip_or_eccerr(ret))
|
||||
return ret;
|
||||
|
||||
for (j = 0; j < len; j++, buf += scanlen) {
|
||||
for (j = 0; j < numpages; j++, buf += scanlen) {
|
||||
if (check_pattern(buf, scanlen, mtd->writesize, bd))
|
||||
return 1;
|
||||
}
|
||||
@ -419,7 +420,7 @@ static int scan_block_full(struct mtd_info *mtd, struct nand_bbt_descr *bd,
|
||||
|
||||
/* Scan a given block partially */
|
||||
static int scan_block_fast(struct mtd_info *mtd, struct nand_bbt_descr *bd,
|
||||
loff_t offs, uint8_t *buf, int len)
|
||||
loff_t offs, uint8_t *buf, int numpages)
|
||||
{
|
||||
struct mtd_oob_ops ops;
|
||||
int j, ret;
|
||||
@ -430,7 +431,7 @@ static int scan_block_fast(struct mtd_info *mtd, struct nand_bbt_descr *bd,
|
||||
ops.datbuf = NULL;
|
||||
ops.mode = MTD_OPS_PLACE_OOB;
|
||||
|
||||
for (j = 0; j < len; j++) {
|
||||
for (j = 0; j < numpages; j++) {
|
||||
/*
|
||||
* Read the full oob until read_oob is fixed to handle single
|
||||
* byte reads for 16 bit buswidth.
|
||||
@ -463,7 +464,7 @@ static int create_bbt(struct mtd_info *mtd, uint8_t *buf,
|
||||
struct nand_bbt_descr *bd, int chip)
|
||||
{
|
||||
struct nand_chip *this = mtd->priv;
|
||||
int i, numblocks, len, scanlen;
|
||||
int i, numblocks, numpages, scanlen;
|
||||
int startblock;
|
||||
loff_t from;
|
||||
size_t readlen;
|
||||
@ -471,11 +472,11 @@ static int create_bbt(struct mtd_info *mtd, uint8_t *buf,
|
||||
pr_info("Scanning device for bad blocks\n");
|
||||
|
||||
if (bd->options & NAND_BBT_SCANALLPAGES)
|
||||
len = 1 << (this->bbt_erase_shift - this->page_shift);
|
||||
numpages = 1 << (this->bbt_erase_shift - this->page_shift);
|
||||
else if (bd->options & NAND_BBT_SCAN2NDPAGE)
|
||||
len = 2;
|
||||
numpages = 2;
|
||||
else
|
||||
len = 1;
|
||||
numpages = 1;
|
||||
|
||||
if (!(bd->options & NAND_BBT_SCANEMPTY)) {
|
||||
/* We need only read few bytes from the OOB area */
|
||||
@ -484,7 +485,7 @@ static int create_bbt(struct mtd_info *mtd, uint8_t *buf,
|
||||
} else {
|
||||
/* Full page content should be read */
|
||||
scanlen = mtd->writesize + mtd->oobsize;
|
||||
readlen = len * mtd->writesize;
|
||||
readlen = numpages * mtd->writesize;
|
||||
}
|
||||
|
||||
if (chip == -1) {
|
||||
@ -508,7 +509,7 @@ static int create_bbt(struct mtd_info *mtd, uint8_t *buf,
|
||||
}
|
||||
|
||||
if (this->bbt_options & NAND_BBT_SCANLASTPAGE)
|
||||
from += mtd->erasesize - (mtd->writesize * len);
|
||||
from += mtd->erasesize - (mtd->writesize * numpages);
|
||||
|
||||
for (i = startblock; i < numblocks;) {
|
||||
int ret;
|
||||
@ -517,9 +518,9 @@ static int create_bbt(struct mtd_info *mtd, uint8_t *buf,
|
||||
|
||||
if (bd->options & NAND_BBT_SCANALLPAGES)
|
||||
ret = scan_block_full(mtd, bd, from, buf, readlen,
|
||||
scanlen, len);
|
||||
scanlen, numpages);
|
||||
else
|
||||
ret = scan_block_fast(mtd, bd, from, buf, len);
|
||||
ret = scan_block_fast(mtd, bd, from, buf, numpages);
|
||||
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
@ -594,7 +595,7 @@ static int search_bbt(struct mtd_info *mtd, uint8_t *buf, struct nand_bbt_descr
|
||||
loff_t offs = (loff_t)actblock << this->bbt_erase_shift;
|
||||
|
||||
/* Read first page */
|
||||
scan_read_raw(mtd, buf, offs, mtd->writesize, td);
|
||||
scan_read(mtd, buf, offs, mtd->writesize, td);
|
||||
if (!check_pattern(buf, scanlen, mtd->writesize, td)) {
|
||||
td->pages[i] = actblock << blocktopage;
|
||||
if (td->options & NAND_BBT_VERSION) {
|
||||
@ -626,7 +627,9 @@ static int search_bbt(struct mtd_info *mtd, uint8_t *buf, struct nand_bbt_descr
|
||||
*
|
||||
* Search and read the bad block table(s).
|
||||
*/
|
||||
static int search_read_bbts(struct mtd_info *mtd, uint8_t * buf, struct nand_bbt_descr *td, struct nand_bbt_descr *md)
|
||||
static void search_read_bbts(struct mtd_info *mtd, uint8_t *buf,
|
||||
struct nand_bbt_descr *td,
|
||||
struct nand_bbt_descr *md)
|
||||
{
|
||||
/* Search the primary table */
|
||||
search_bbt(mtd, buf, td);
|
||||
@ -634,9 +637,6 @@ static int search_read_bbts(struct mtd_info *mtd, uint8_t * buf, struct nand_bbt
|
||||
/* Search the mirror table */
|
||||
if (md)
|
||||
search_bbt(mtd, buf, md);
|
||||
|
||||
/* Force result check */
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1162,14 +1162,13 @@ int nand_scan_bbt(struct mtd_info *mtd, struct nand_bbt_descr *bd)
|
||||
|
||||
/* Is the bbt at a given page? */
|
||||
if (td->options & NAND_BBT_ABSPAGE) {
|
||||
res = read_abs_bbts(mtd, buf, td, md);
|
||||
read_abs_bbts(mtd, buf, td, md);
|
||||
} else {
|
||||
/* Search the bad block table using a pattern in oob */
|
||||
res = search_read_bbts(mtd, buf, td, md);
|
||||
search_read_bbts(mtd, buf, td, md);
|
||||
}
|
||||
|
||||
if (res)
|
||||
res = check_create(mtd, buf, bd);
|
||||
res = check_create(mtd, buf, bd);
|
||||
|
||||
/* Prevent the bbt regions from erasing / writing */
|
||||
mark_bbt_region(mtd, td);
|
||||
@ -1260,7 +1259,7 @@ static struct nand_bbt_descr bbt_main_descr = {
|
||||
.offs = 8,
|
||||
.len = 4,
|
||||
.veroffs = 12,
|
||||
.maxblocks = 4,
|
||||
.maxblocks = NAND_BBT_SCAN_MAXBLOCKS,
|
||||
.pattern = bbt_pattern
|
||||
};
|
||||
|
||||
@ -1270,27 +1269,27 @@ static struct nand_bbt_descr bbt_mirror_descr = {
|
||||
.offs = 8,
|
||||
.len = 4,
|
||||
.veroffs = 12,
|
||||
.maxblocks = 4,
|
||||
.maxblocks = NAND_BBT_SCAN_MAXBLOCKS,
|
||||
.pattern = mirror_pattern
|
||||
};
|
||||
|
||||
static struct nand_bbt_descr bbt_main_no_bbt_descr = {
|
||||
static struct nand_bbt_descr bbt_main_no_oob_descr = {
|
||||
.options = NAND_BBT_LASTBLOCK | NAND_BBT_CREATE | NAND_BBT_WRITE
|
||||
| NAND_BBT_2BIT | NAND_BBT_VERSION | NAND_BBT_PERCHIP
|
||||
| NAND_BBT_NO_OOB,
|
||||
.len = 4,
|
||||
.veroffs = 4,
|
||||
.maxblocks = 4,
|
||||
.maxblocks = NAND_BBT_SCAN_MAXBLOCKS,
|
||||
.pattern = bbt_pattern
|
||||
};
|
||||
|
||||
static struct nand_bbt_descr bbt_mirror_no_bbt_descr = {
|
||||
static struct nand_bbt_descr bbt_mirror_no_oob_descr = {
|
||||
.options = NAND_BBT_LASTBLOCK | NAND_BBT_CREATE | NAND_BBT_WRITE
|
||||
| NAND_BBT_2BIT | NAND_BBT_VERSION | NAND_BBT_PERCHIP
|
||||
| NAND_BBT_NO_OOB,
|
||||
.len = 4,
|
||||
.veroffs = 4,
|
||||
.maxblocks = 4,
|
||||
.maxblocks = NAND_BBT_SCAN_MAXBLOCKS,
|
||||
.pattern = mirror_pattern
|
||||
};
|
||||
|
||||
@ -1355,8 +1354,8 @@ int nand_default_bbt(struct mtd_info *mtd)
|
||||
/* Use the default pattern descriptors */
|
||||
if (!this->bbt_td) {
|
||||
if (this->bbt_options & NAND_BBT_NO_OOB) {
|
||||
this->bbt_td = &bbt_main_no_bbt_descr;
|
||||
this->bbt_md = &bbt_mirror_no_bbt_descr;
|
||||
this->bbt_td = &bbt_main_no_oob_descr;
|
||||
this->bbt_md = &bbt_mirror_no_oob_descr;
|
||||
} else {
|
||||
this->bbt_td = &bbt_main_descr;
|
||||
this->bbt_md = &bbt_mirror_descr;
|
||||
@ -1406,3 +1405,4 @@ int nand_isbad_bbt(struct mtd_info *mtd, loff_t offs, int allowbbt)
|
||||
|
||||
EXPORT_SYMBOL(nand_scan_bbt);
|
||||
EXPORT_SYMBOL(nand_default_bbt);
|
||||
EXPORT_SYMBOL_GPL(nand_update_bbt);
|
||||
|
@ -1,149 +0,0 @@
|
||||
/*****************************************************************************
|
||||
* Copyright 2004 - 2009 Broadcom Corporation. All rights reserved.
|
||||
*
|
||||
* Unless you and Broadcom execute a separate written software license
|
||||
* agreement governing use of this software, this software is licensed to you
|
||||
* under the terms of the GNU General Public License version 2, available at
|
||||
* http://www.broadcom.com/licenses/GPLv2.php (the "GPL").
|
||||
*
|
||||
* Notwithstanding the above, under no circumstances may you combine this
|
||||
* software in any way with any other Broadcom software provided under a
|
||||
* license other than the GPL, without Broadcom's express prior written
|
||||
* consent.
|
||||
*****************************************************************************/
|
||||
|
||||
/* ---- Include Files ---------------------------------------------------- */
|
||||
#include <mach/reg_umi.h>
|
||||
#include "nand_bcm_umi.h"
|
||||
#ifdef BOOT0_BUILD
|
||||
#include <uart.h>
|
||||
#endif
|
||||
|
||||
/* ---- External Variable Declarations ----------------------------------- */
|
||||
/* ---- External Function Prototypes ------------------------------------- */
|
||||
/* ---- Public Variables ------------------------------------------------- */
|
||||
/* ---- Private Constants and Types -------------------------------------- */
|
||||
/* ---- Private Function Prototypes -------------------------------------- */
|
||||
/* ---- Private Variables ------------------------------------------------ */
|
||||
/* ---- Private Functions ------------------------------------------------ */
|
||||
|
||||
#if NAND_ECC_BCH
|
||||
/****************************************************************************
|
||||
* nand_bch_ecc_flip_bit - Routine to flip an errored bit
|
||||
*
|
||||
* PURPOSE:
|
||||
* This is a helper routine that flips the bit (0 -> 1 or 1 -> 0) of the
|
||||
* errored bit specified
|
||||
*
|
||||
* PARAMETERS:
|
||||
* datap - Container that holds the 512 byte data
|
||||
* errorLocation - Location of the bit that needs to be flipped
|
||||
*
|
||||
* RETURNS:
|
||||
* None
|
||||
****************************************************************************/
|
||||
static void nand_bcm_umi_bch_ecc_flip_bit(uint8_t *datap, int errorLocation)
|
||||
{
|
||||
int locWithinAByte = (errorLocation & REG_UMI_BCH_ERR_LOC_BYTE) >> 0;
|
||||
int locWithinAWord = (errorLocation & REG_UMI_BCH_ERR_LOC_WORD) >> 3;
|
||||
int locWithinAPage = (errorLocation & REG_UMI_BCH_ERR_LOC_PAGE) >> 5;
|
||||
|
||||
uint8_t errorByte = 0;
|
||||
uint8_t byteMask = 1 << locWithinAByte;
|
||||
|
||||
/* BCH uses big endian, need to change the location
|
||||
* bits to little endian */
|
||||
locWithinAWord = 3 - locWithinAWord;
|
||||
|
||||
errorByte = datap[locWithinAPage * sizeof(uint32_t) + locWithinAWord];
|
||||
|
||||
#ifdef BOOT0_BUILD
|
||||
puthexs("\nECC Correct Offset: ",
|
||||
locWithinAPage * sizeof(uint32_t) + locWithinAWord);
|
||||
puthexs(" errorByte:", errorByte);
|
||||
puthex8(" Bit: ", locWithinAByte);
|
||||
#endif
|
||||
|
||||
if (errorByte & byteMask) {
|
||||
/* bit needs to be cleared */
|
||||
errorByte &= ~byteMask;
|
||||
} else {
|
||||
/* bit needs to be set */
|
||||
errorByte |= byteMask;
|
||||
}
|
||||
|
||||
/* write back the value with the fixed bit */
|
||||
datap[locWithinAPage * sizeof(uint32_t) + locWithinAWord] = errorByte;
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* nand_correct_page_bch - Routine to correct bit errors when reading NAND
|
||||
*
|
||||
* PURPOSE:
|
||||
* This routine reads the BCH registers to determine if there are any bit
|
||||
* errors during the read of the last 512 bytes of data + ECC bytes. If
|
||||
* errors exists, the routine fixes it.
|
||||
*
|
||||
* PARAMETERS:
|
||||
* datap - Container that holds the 512 byte data
|
||||
*
|
||||
* RETURNS:
|
||||
* 0 or greater = Number of errors corrected
|
||||
* (No errors are found or errors have been fixed)
|
||||
* -1 = Error(s) cannot be fixed
|
||||
****************************************************************************/
|
||||
int nand_bcm_umi_bch_correct_page(uint8_t *datap, uint8_t *readEccData,
|
||||
int numEccBytes)
|
||||
{
|
||||
int numErrors;
|
||||
int errorLocation;
|
||||
int idx;
|
||||
uint32_t regValue;
|
||||
|
||||
/* wait for read ECC to be valid */
|
||||
regValue = nand_bcm_umi_bch_poll_read_ecc_calc();
|
||||
|
||||
/*
|
||||
* read the control status register to determine if there
|
||||
* are error'ed bits
|
||||
* see if errors are correctible
|
||||
*/
|
||||
if ((regValue & REG_UMI_BCH_CTRL_STATUS_UNCORR_ERR) > 0) {
|
||||
int i;
|
||||
|
||||
for (i = 0; i < numEccBytes; i++) {
|
||||
if (readEccData[i] != 0xff) {
|
||||
/* errors cannot be fixed, return -1 */
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
/* If ECC is unprogrammed then we can't correct,
|
||||
* assume everything OK */
|
||||
return 0;
|
||||
}
|
||||
|
||||
if ((regValue & REG_UMI_BCH_CTRL_STATUS_CORR_ERR) == 0) {
|
||||
/* no errors */
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Fix errored bits by doing the following:
|
||||
* 1. Read the number of errors in the control and status register
|
||||
* 2. Read the error location registers that corresponds to the number
|
||||
* of errors reported
|
||||
* 3. Invert the bit in the data
|
||||
*/
|
||||
numErrors = (regValue & REG_UMI_BCH_CTRL_STATUS_NB_CORR_ERROR) >> 20;
|
||||
|
||||
for (idx = 0; idx < numErrors; idx++) {
|
||||
errorLocation =
|
||||
REG_UMI_BCH_ERR_LOC_ADDR(idx) & REG_UMI_BCH_ERR_LOC_MASK;
|
||||
|
||||
/* Flip bit */
|
||||
nand_bcm_umi_bch_ecc_flip_bit(datap, errorLocation);
|
||||
}
|
||||
/* Errors corrected */
|
||||
return numErrors;
|
||||
}
|
||||
#endif
|
@ -1,336 +0,0 @@
|
||||
/*****************************************************************************
|
||||
* Copyright 2003 - 2009 Broadcom Corporation. All rights reserved.
|
||||
*
|
||||
* Unless you and Broadcom execute a separate written software license
|
||||
* agreement governing use of this software, this software is licensed to you
|
||||
* under the terms of the GNU General Public License version 2, available at
|
||||
* http://www.broadcom.com/licenses/GPLv2.php (the "GPL").
|
||||
*
|
||||
* Notwithstanding the above, under no circumstances may you combine this
|
||||
* software in any way with any other Broadcom software provided under a
|
||||
* license other than the GPL, without Broadcom's express prior written
|
||||
* consent.
|
||||
*****************************************************************************/
|
||||
#ifndef NAND_BCM_UMI_H
|
||||
#define NAND_BCM_UMI_H
|
||||
|
||||
/* ---- Include Files ---------------------------------------------------- */
|
||||
#include <mach/reg_umi.h>
|
||||
#include <mach/reg_nand.h>
|
||||
#include <mach/cfg_global.h>
|
||||
|
||||
/* ---- Constants and Types ---------------------------------------------- */
|
||||
#if (CFG_GLOBAL_CHIP_FAMILY == CFG_GLOBAL_CHIP_FAMILY_BCMRING)
|
||||
#define NAND_ECC_BCH (CFG_GLOBAL_CHIP_REV > 0xA0)
|
||||
#else
|
||||
#define NAND_ECC_BCH 0
|
||||
#endif
|
||||
|
||||
#define CFG_GLOBAL_NAND_ECC_BCH_NUM_BYTES 13
|
||||
|
||||
#if NAND_ECC_BCH
|
||||
#ifdef BOOT0_BUILD
|
||||
#define NAND_ECC_NUM_BYTES 13
|
||||
#else
|
||||
#define NAND_ECC_NUM_BYTES CFG_GLOBAL_NAND_ECC_BCH_NUM_BYTES
|
||||
#endif
|
||||
#else
|
||||
#define NAND_ECC_NUM_BYTES 3
|
||||
#endif
|
||||
|
||||
#define NAND_DATA_ACCESS_SIZE 512
|
||||
|
||||
/* ---- Variable Externs ------------------------------------------ */
|
||||
/* ---- Function Prototypes --------------------------------------- */
|
||||
int nand_bcm_umi_bch_correct_page(uint8_t *datap, uint8_t *readEccData,
|
||||
int numEccBytes);
|
||||
|
||||
/* Check in device is ready */
|
||||
static inline int nand_bcm_umi_dev_ready(void)
|
||||
{
|
||||
return readl(®_UMI_NAND_RCSR) & REG_UMI_NAND_RCSR_RDY;
|
||||
}
|
||||
|
||||
/* Wait until device is ready */
|
||||
static inline void nand_bcm_umi_wait_till_ready(void)
|
||||
{
|
||||
while (nand_bcm_umi_dev_ready() == 0)
|
||||
;
|
||||
}
|
||||
|
||||
/* Enable Hamming ECC */
|
||||
static inline void nand_bcm_umi_hamming_enable_hwecc(void)
|
||||
{
|
||||
/* disable and reset ECC, 512 byte page */
|
||||
writel(readl(®_UMI_NAND_ECC_CSR) & ~(REG_UMI_NAND_ECC_CSR_ECC_ENABLE |
|
||||
REG_UMI_NAND_ECC_CSR_256BYTE), ®_UMI_NAND_ECC_CSR);
|
||||
/* enable ECC */
|
||||
writel(readl(®_UMI_NAND_ECC_CSR) | REG_UMI_NAND_ECC_CSR_ECC_ENABLE,
|
||||
®_UMI_NAND_ECC_CSR);
|
||||
}
|
||||
|
||||
#if NAND_ECC_BCH
|
||||
/* BCH ECC specifics */
|
||||
#define ECC_BITS_PER_CORRECTABLE_BIT 13
|
||||
|
||||
/* Enable BCH Read ECC */
|
||||
static inline void nand_bcm_umi_bch_enable_read_hwecc(void)
|
||||
{
|
||||
/* disable and reset ECC */
|
||||
writel(REG_UMI_BCH_CTRL_STATUS_RD_ECC_VALID, ®_UMI_BCH_CTRL_STATUS);
|
||||
/* Turn on ECC */
|
||||
writel(REG_UMI_BCH_CTRL_STATUS_ECC_RD_EN, ®_UMI_BCH_CTRL_STATUS);
|
||||
}
|
||||
|
||||
/* Enable BCH Write ECC */
|
||||
static inline void nand_bcm_umi_bch_enable_write_hwecc(void)
|
||||
{
|
||||
/* disable and reset ECC */
|
||||
writel(REG_UMI_BCH_CTRL_STATUS_WR_ECC_VALID, ®_UMI_BCH_CTRL_STATUS);
|
||||
/* Turn on ECC */
|
||||
writel(REG_UMI_BCH_CTRL_STATUS_ECC_WR_EN, ®_UMI_BCH_CTRL_STATUS);
|
||||
}
|
||||
|
||||
/* Config number of BCH ECC bytes */
|
||||
static inline void nand_bcm_umi_bch_config_ecc(uint8_t numEccBytes)
|
||||
{
|
||||
uint32_t nValue;
|
||||
uint32_t tValue;
|
||||
uint32_t kValue;
|
||||
uint32_t numBits = numEccBytes * 8;
|
||||
|
||||
/* disable and reset ECC */
|
||||
writel(REG_UMI_BCH_CTRL_STATUS_WR_ECC_VALID |
|
||||
REG_UMI_BCH_CTRL_STATUS_RD_ECC_VALID,
|
||||
®_UMI_BCH_CTRL_STATUS);
|
||||
|
||||
/* Every correctible bit requires 13 ECC bits */
|
||||
tValue = (uint32_t) (numBits / ECC_BITS_PER_CORRECTABLE_BIT);
|
||||
|
||||
/* Total data in number of bits for generating and computing BCH ECC */
|
||||
nValue = (NAND_DATA_ACCESS_SIZE + numEccBytes) * 8;
|
||||
|
||||
/* K parameter is used internally. K = N - (T * 13) */
|
||||
kValue = nValue - (tValue * ECC_BITS_PER_CORRECTABLE_BIT);
|
||||
|
||||
/* Write the settings */
|
||||
writel(nValue, ®_UMI_BCH_N);
|
||||
writel(tValue, ®_UMI_BCH_T);
|
||||
writel(kValue, ®_UMI_BCH_K);
|
||||
}
|
||||
|
||||
/* Pause during ECC read calculation to skip bytes in OOB */
|
||||
static inline void nand_bcm_umi_bch_pause_read_ecc_calc(void)
|
||||
{
|
||||
writel(REG_UMI_BCH_CTRL_STATUS_ECC_RD_EN | REG_UMI_BCH_CTRL_STATUS_PAUSE_ECC_DEC, ®_UMI_BCH_CTRL_STATUS);
|
||||
}
|
||||
|
||||
/* Resume during ECC read calculation after skipping bytes in OOB */
|
||||
static inline void nand_bcm_umi_bch_resume_read_ecc_calc(void)
|
||||
{
|
||||
writel(REG_UMI_BCH_CTRL_STATUS_ECC_RD_EN, ®_UMI_BCH_CTRL_STATUS);
|
||||
}
|
||||
|
||||
/* Poll read ECC calc to check when hardware completes */
|
||||
static inline uint32_t nand_bcm_umi_bch_poll_read_ecc_calc(void)
|
||||
{
|
||||
uint32_t regVal;
|
||||
|
||||
do {
|
||||
/* wait for ECC to be valid */
|
||||
regVal = readl(®_UMI_BCH_CTRL_STATUS);
|
||||
} while ((regVal & REG_UMI_BCH_CTRL_STATUS_RD_ECC_VALID) == 0);
|
||||
|
||||
return regVal;
|
||||
}
|
||||
|
||||
/* Poll write ECC calc to check when hardware completes */
|
||||
static inline void nand_bcm_umi_bch_poll_write_ecc_calc(void)
|
||||
{
|
||||
/* wait for ECC to be valid */
|
||||
while ((readl(®_UMI_BCH_CTRL_STATUS) & REG_UMI_BCH_CTRL_STATUS_WR_ECC_VALID)
|
||||
== 0)
|
||||
;
|
||||
}
|
||||
|
||||
/* Read the OOB and ECC, for kernel write OOB to a buffer */
|
||||
#if defined(__KERNEL__) && !defined(STANDALONE)
|
||||
static inline void nand_bcm_umi_bch_read_oobEcc(uint32_t pageSize,
|
||||
uint8_t *eccCalc, int numEccBytes, uint8_t *oobp)
|
||||
#else
|
||||
static inline void nand_bcm_umi_bch_read_oobEcc(uint32_t pageSize,
|
||||
uint8_t *eccCalc, int numEccBytes)
|
||||
#endif
|
||||
{
|
||||
int eccPos = 0;
|
||||
int numToRead = 16; /* There are 16 bytes per sector in the OOB */
|
||||
|
||||
/* ECC is already paused when this function is called */
|
||||
if (pageSize != NAND_DATA_ACCESS_SIZE) {
|
||||
/* skip BI */
|
||||
#if defined(__KERNEL__) && !defined(STANDALONE)
|
||||
*oobp++ = readb(®_NAND_DATA8);
|
||||
#else
|
||||
readb(®_NAND_DATA8);
|
||||
#endif
|
||||
numToRead--;
|
||||
}
|
||||
|
||||
while (numToRead > numEccBytes) {
|
||||
/* skip free oob region */
|
||||
#if defined(__KERNEL__) && !defined(STANDALONE)
|
||||
*oobp++ = readb(®_NAND_DATA8);
|
||||
#else
|
||||
readb(®_NAND_DATA8);
|
||||
#endif
|
||||
numToRead--;
|
||||
}
|
||||
|
||||
if (pageSize == NAND_DATA_ACCESS_SIZE) {
|
||||
/* read ECC bytes before BI */
|
||||
nand_bcm_umi_bch_resume_read_ecc_calc();
|
||||
|
||||
while (numToRead > 11) {
|
||||
#if defined(__KERNEL__) && !defined(STANDALONE)
|
||||
*oobp = readb(®_NAND_DATA8);
|
||||
eccCalc[eccPos++] = *oobp;
|
||||
oobp++;
|
||||
#else
|
||||
eccCalc[eccPos++] = readb(®_NAND_DATA8);
|
||||
#endif
|
||||
numToRead--;
|
||||
}
|
||||
|
||||
nand_bcm_umi_bch_pause_read_ecc_calc();
|
||||
|
||||
if (numToRead == 11) {
|
||||
/* read BI */
|
||||
#if defined(__KERNEL__) && !defined(STANDALONE)
|
||||
*oobp++ = readb(®_NAND_DATA8);
|
||||
#else
|
||||
readb(®_NAND_DATA8);
|
||||
#endif
|
||||
numToRead--;
|
||||
}
|
||||
|
||||
}
|
||||
/* read ECC bytes */
|
||||
nand_bcm_umi_bch_resume_read_ecc_calc();
|
||||
while (numToRead) {
|
||||
#if defined(__KERNEL__) && !defined(STANDALONE)
|
||||
*oobp = readb(®_NAND_DATA8);
|
||||
eccCalc[eccPos++] = *oobp;
|
||||
oobp++;
|
||||
#else
|
||||
eccCalc[eccPos++] = readb(®_NAND_DATA8);
|
||||
#endif
|
||||
numToRead--;
|
||||
}
|
||||
}
|
||||
|
||||
/* Helper function to write ECC */
|
||||
static inline void NAND_BCM_UMI_ECC_WRITE(int numEccBytes, int eccBytePos,
|
||||
uint8_t *oobp, uint8_t eccVal)
|
||||
{
|
||||
if (eccBytePos <= numEccBytes)
|
||||
*oobp = eccVal;
|
||||
}
|
||||
|
||||
/* Write OOB with ECC */
|
||||
static inline void nand_bcm_umi_bch_write_oobEcc(uint32_t pageSize,
|
||||
uint8_t *oobp, int numEccBytes)
|
||||
{
|
||||
uint32_t eccVal = 0xffffffff;
|
||||
|
||||
/* wait for write ECC to be valid */
|
||||
nand_bcm_umi_bch_poll_write_ecc_calc();
|
||||
|
||||
/*
|
||||
** Get the hardware ecc from the 32-bit result registers.
|
||||
** Read after 512 byte accesses. Format B3B2B1B0
|
||||
** where B3 = ecc3, etc.
|
||||
*/
|
||||
|
||||
if (pageSize == NAND_DATA_ACCESS_SIZE) {
|
||||
/* Now fill in the ECC bytes */
|
||||
if (numEccBytes >= 13)
|
||||
eccVal = readl(®_UMI_BCH_WR_ECC_3);
|
||||
|
||||
/* Usually we skip CM in oob[0,1] */
|
||||
NAND_BCM_UMI_ECC_WRITE(numEccBytes, 15, &oobp[0],
|
||||
(eccVal >> 16) & 0xff);
|
||||
NAND_BCM_UMI_ECC_WRITE(numEccBytes, 14, &oobp[1],
|
||||
(eccVal >> 8) & 0xff);
|
||||
|
||||
/* Write ECC in oob[2,3,4] */
|
||||
NAND_BCM_UMI_ECC_WRITE(numEccBytes, 13, &oobp[2],
|
||||
eccVal & 0xff); /* ECC 12 */
|
||||
|
||||
if (numEccBytes >= 9)
|
||||
eccVal = readl(®_UMI_BCH_WR_ECC_2);
|
||||
|
||||
NAND_BCM_UMI_ECC_WRITE(numEccBytes, 12, &oobp[3],
|
||||
(eccVal >> 24) & 0xff); /* ECC11 */
|
||||
NAND_BCM_UMI_ECC_WRITE(numEccBytes, 11, &oobp[4],
|
||||
(eccVal >> 16) & 0xff); /* ECC10 */
|
||||
|
||||
/* Always Skip BI in oob[5] */
|
||||
} else {
|
||||
/* Always Skip BI in oob[0] */
|
||||
|
||||
/* Now fill in the ECC bytes */
|
||||
if (numEccBytes >= 13)
|
||||
eccVal = readl(®_UMI_BCH_WR_ECC_3);
|
||||
|
||||
/* Usually skip CM in oob[1,2] */
|
||||
NAND_BCM_UMI_ECC_WRITE(numEccBytes, 15, &oobp[1],
|
||||
(eccVal >> 16) & 0xff);
|
||||
NAND_BCM_UMI_ECC_WRITE(numEccBytes, 14, &oobp[2],
|
||||
(eccVal >> 8) & 0xff);
|
||||
|
||||
/* Write ECC in oob[3-15] */
|
||||
NAND_BCM_UMI_ECC_WRITE(numEccBytes, 13, &oobp[3],
|
||||
eccVal & 0xff); /* ECC12 */
|
||||
|
||||
if (numEccBytes >= 9)
|
||||
eccVal = readl(®_UMI_BCH_WR_ECC_2);
|
||||
|
||||
NAND_BCM_UMI_ECC_WRITE(numEccBytes, 12, &oobp[4],
|
||||
(eccVal >> 24) & 0xff); /* ECC11 */
|
||||
NAND_BCM_UMI_ECC_WRITE(numEccBytes, 11, &oobp[5],
|
||||
(eccVal >> 16) & 0xff); /* ECC10 */
|
||||
}
|
||||
|
||||
/* Fill in the remainder of ECC locations */
|
||||
NAND_BCM_UMI_ECC_WRITE(numEccBytes, 10, &oobp[6],
|
||||
(eccVal >> 8) & 0xff); /* ECC9 */
|
||||
NAND_BCM_UMI_ECC_WRITE(numEccBytes, 9, &oobp[7],
|
||||
eccVal & 0xff); /* ECC8 */
|
||||
|
||||
if (numEccBytes >= 5)
|
||||
eccVal = readl(®_UMI_BCH_WR_ECC_1);
|
||||
|
||||
NAND_BCM_UMI_ECC_WRITE(numEccBytes, 8, &oobp[8],
|
||||
(eccVal >> 24) & 0xff); /* ECC7 */
|
||||
NAND_BCM_UMI_ECC_WRITE(numEccBytes, 7, &oobp[9],
|
||||
(eccVal >> 16) & 0xff); /* ECC6 */
|
||||
NAND_BCM_UMI_ECC_WRITE(numEccBytes, 6, &oobp[10],
|
||||
(eccVal >> 8) & 0xff); /* ECC5 */
|
||||
NAND_BCM_UMI_ECC_WRITE(numEccBytes, 5, &oobp[11],
|
||||
eccVal & 0xff); /* ECC4 */
|
||||
|
||||
if (numEccBytes >= 1)
|
||||
eccVal = readl(®_UMI_BCH_WR_ECC_0);
|
||||
|
||||
NAND_BCM_UMI_ECC_WRITE(numEccBytes, 4, &oobp[12],
|
||||
(eccVal >> 24) & 0xff); /* ECC3 */
|
||||
NAND_BCM_UMI_ECC_WRITE(numEccBytes, 3, &oobp[13],
|
||||
(eccVal >> 16) & 0xff); /* ECC2 */
|
||||
NAND_BCM_UMI_ECC_WRITE(numEccBytes, 2, &oobp[14],
|
||||
(eccVal >> 8) & 0xff); /* ECC1 */
|
||||
NAND_BCM_UMI_ECC_WRITE(numEccBytes, 1, &oobp[15],
|
||||
eccVal & 0xff); /* ECC0 */
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* NAND_BCM_UMI_H */
|
@ -70,7 +70,7 @@ struct nand_flash_dev nand_flash_ids[] = {
|
||||
* These are the new chips with large page size. The pagesize and the
|
||||
* erasesize is determined from the extended id bytes
|
||||
*/
|
||||
#define LP_OPTIONS (NAND_SAMSUNG_LP_OPTIONS | NAND_NO_READRDY)
|
||||
#define LP_OPTIONS NAND_SAMSUNG_LP_OPTIONS
|
||||
#define LP_OPTIONS16 (LP_OPTIONS | NAND_BUSWIDTH_16)
|
||||
|
||||
/* 512 Megabit */
|
||||
@ -157,7 +157,7 @@ struct nand_flash_dev nand_flash_ids[] = {
|
||||
* writes possible, but not implemented now
|
||||
*/
|
||||
{"AND 128MiB 3,3V 8-bit", 0x01, 2048, 128, 0x4000,
|
||||
NAND_IS_AND | NAND_NO_READRDY | NAND_4PAGE_ARRAY | BBT_AUTO_REFRESH},
|
||||
NAND_IS_AND | NAND_4PAGE_ARRAY | BBT_AUTO_REFRESH},
|
||||
|
||||
{NULL,}
|
||||
};
|
||||
@ -174,8 +174,9 @@ struct nand_manufacturers nand_manuf_ids[] = {
|
||||
{NAND_MFR_STMICRO, "ST Micro"},
|
||||
{NAND_MFR_HYNIX, "Hynix"},
|
||||
{NAND_MFR_MICRON, "Micron"},
|
||||
{NAND_MFR_AMD, "AMD"},
|
||||
{NAND_MFR_AMD, "AMD/Spansion"},
|
||||
{NAND_MFR_MACRONIX, "Macronix"},
|
||||
{NAND_MFR_EON, "Eon"},
|
||||
{0x0, "Unknown"}
|
||||
};
|
||||
|
||||
|
@ -447,8 +447,6 @@ static unsigned int rptwear_cnt = 0;
|
||||
/* MTD structure for NAND controller */
|
||||
static struct mtd_info *nsmtd;
|
||||
|
||||
static u_char ns_verify_buf[NS_LARGEST_PAGE_SIZE];
|
||||
|
||||
/*
|
||||
* Allocate array of page pointers, create slab allocation for an array
|
||||
* and initialize the array by NULL pointers.
|
||||
@ -2189,19 +2187,6 @@ static void ns_nand_read_buf(struct mtd_info *mtd, u_char *buf, int len)
|
||||
return;
|
||||
}
|
||||
|
||||
static int ns_nand_verify_buf(struct mtd_info *mtd, const u_char *buf, int len)
|
||||
{
|
||||
ns_nand_read_buf(mtd, (u_char *)&ns_verify_buf[0], len);
|
||||
|
||||
if (!memcmp(buf, &ns_verify_buf[0], len)) {
|
||||
NS_DBG("verify_buf: the buffer is OK\n");
|
||||
return 0;
|
||||
} else {
|
||||
NS_DBG("verify_buf: the buffer is wrong\n");
|
||||
return -EFAULT;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Module initialization function
|
||||
*/
|
||||
@ -2236,7 +2221,6 @@ static int __init ns_init_module(void)
|
||||
chip->dev_ready = ns_device_ready;
|
||||
chip->write_buf = ns_nand_write_buf;
|
||||
chip->read_buf = ns_nand_read_buf;
|
||||
chip->verify_buf = ns_nand_verify_buf;
|
||||
chip->read_word = ns_nand_read_word;
|
||||
chip->ecc.mode = NAND_ECC_SOFT;
|
||||
/* The NAND_SKIP_BBTSCAN option is necessary for 'overridesize' */
|
||||
@ -2333,6 +2317,7 @@ static int __init ns_init_module(void)
|
||||
uint64_t new_size = (uint64_t)nsmtd->erasesize << overridesize;
|
||||
if (new_size >> overridesize != nsmtd->erasesize) {
|
||||
NS_ERR("overridesize is too big\n");
|
||||
retval = -EINVAL;
|
||||
goto err_exit;
|
||||
}
|
||||
/* N.B. This relies on nand_scan not doing anything with the size before we change it */
|
||||
|
@ -140,18 +140,6 @@ static void ndfc_write_buf(struct mtd_info *mtd, const uint8_t *buf, int len)
|
||||
out_be32(ndfc->ndfcbase + NDFC_DATA, *p++);
|
||||
}
|
||||
|
||||
static int ndfc_verify_buf(struct mtd_info *mtd, const uint8_t *buf, int len)
|
||||
{
|
||||
struct nand_chip *chip = mtd->priv;
|
||||
struct ndfc_controller *ndfc = chip->priv;
|
||||
uint32_t *p = (uint32_t *) buf;
|
||||
|
||||
for(;len > 0; len -= 4)
|
||||
if (*p++ != in_be32(ndfc->ndfcbase + NDFC_DATA))
|
||||
return -EFAULT;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Initialize chip structure
|
||||
*/
|
||||
@ -172,7 +160,6 @@ static int ndfc_chip_init(struct ndfc_controller *ndfc,
|
||||
chip->controller = &ndfc->ndfc_control;
|
||||
chip->read_buf = ndfc_read_buf;
|
||||
chip->write_buf = ndfc_write_buf;
|
||||
chip->verify_buf = ndfc_verify_buf;
|
||||
chip->ecc.correct = nand_correct_data;
|
||||
chip->ecc.hwctl = ndfc_enable_hwecc;
|
||||
chip->ecc.calculate = ndfc_calculate_ecc;
|
||||
|
@ -112,22 +112,6 @@ static void nuc900_nand_write_buf(struct mtd_info *mtd,
|
||||
write_data_reg(nand, buf[i]);
|
||||
}
|
||||
|
||||
static int nuc900_verify_buf(struct mtd_info *mtd,
|
||||
const unsigned char *buf, int len)
|
||||
{
|
||||
int i;
|
||||
struct nuc900_nand *nand;
|
||||
|
||||
nand = container_of(mtd, struct nuc900_nand, mtd);
|
||||
|
||||
for (i = 0; i < len; i++) {
|
||||
if (buf[i] != (unsigned char)read_data_reg(nand))
|
||||
return -EFAULT;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int nuc900_check_rb(struct nuc900_nand *nand)
|
||||
{
|
||||
unsigned int val;
|
||||
@ -292,7 +276,6 @@ static int __devinit nuc900_nand_probe(struct platform_device *pdev)
|
||||
chip->read_byte = nuc900_nand_read_byte;
|
||||
chip->write_buf = nuc900_nand_write_buf;
|
||||
chip->read_buf = nuc900_nand_read_buf;
|
||||
chip->verify_buf = nuc900_verify_buf;
|
||||
chip->chip_delay = 50;
|
||||
chip->options = 0;
|
||||
chip->ecc.mode = NAND_ECC_SOFT;
|
||||
|
@ -425,7 +425,7 @@ static void omap_nand_dma_callback(void *data)
|
||||
}
|
||||
|
||||
/*
|
||||
* omap_nand_dma_transfer: configer and start dma transfer
|
||||
* omap_nand_dma_transfer: configure and start dma transfer
|
||||
* @mtd: MTD device structure
|
||||
* @addr: virtual address in RAM of source/destination
|
||||
* @len: number of data bytes to be transferred
|
||||
@ -546,7 +546,7 @@ static void omap_write_buf_dma_pref(struct mtd_info *mtd,
|
||||
}
|
||||
|
||||
/*
|
||||
* omap_nand_irq - GMPC irq handler
|
||||
* omap_nand_irq - GPMC irq handler
|
||||
* @this_irq: gpmc irq number
|
||||
* @dev: omap_nand_info structure pointer is passed here
|
||||
*/
|
||||
@ -697,27 +697,6 @@ out_copy:
|
||||
omap_write_buf8(mtd, buf, len);
|
||||
}
|
||||
|
||||
/**
|
||||
* omap_verify_buf - Verify chip data against buffer
|
||||
* @mtd: MTD device structure
|
||||
* @buf: buffer containing the data to compare
|
||||
* @len: number of bytes to compare
|
||||
*/
|
||||
static int omap_verify_buf(struct mtd_info *mtd, const u_char * buf, int len)
|
||||
{
|
||||
struct omap_nand_info *info = container_of(mtd, struct omap_nand_info,
|
||||
mtd);
|
||||
u16 *p = (u16 *) buf;
|
||||
|
||||
len >>= 1;
|
||||
while (len--) {
|
||||
if (*p++ != cpu_to_le16(readw(info->nand.IO_ADDR_R)))
|
||||
return -EFAULT;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* gen_true_ecc - This function will generate true ECC value
|
||||
* @ecc_buf: buffer to store ecc code
|
||||
@ -1326,8 +1305,8 @@ static int __devinit omap_nand_probe(struct platform_device *pdev)
|
||||
|
||||
/*
|
||||
* If RDY/BSY line is connected to OMAP then use the omap ready
|
||||
* funcrtion and the generic nand_wait function which reads the status
|
||||
* register after monitoring the RDY/BSY line.Otherwise use a standard
|
||||
* function and the generic nand_wait function which reads the status
|
||||
* register after monitoring the RDY/BSY line. Otherwise use a standard
|
||||
* chip delay which is slightly more than tR (AC Timing) of the NAND
|
||||
* device and read status register until you get a failure or success
|
||||
*/
|
||||
@ -1428,9 +1407,7 @@ static int __devinit omap_nand_probe(struct platform_device *pdev)
|
||||
goto out_release_mem_region;
|
||||
}
|
||||
|
||||
info->nand.verify_buf = omap_verify_buf;
|
||||
|
||||
/* selsect the ecc type */
|
||||
/* select the ecc type */
|
||||
if (pdata->ecc_opt == OMAP_ECC_HAMMING_CODE_DEFAULT)
|
||||
info->nand.ecc.mode = NAND_ECC_SOFT;
|
||||
else if ((pdata->ecc_opt == OMAP_ECC_HAMMING_CODE_HW) ||
|
||||
@ -1536,7 +1513,8 @@ static int omap_nand_remove(struct platform_device *pdev)
|
||||
/* Release NAND device, its internal structures and partitions */
|
||||
nand_release(&info->mtd);
|
||||
iounmap(info->nand.IO_ADDR_R);
|
||||
kfree(&info->mtd);
|
||||
release_mem_region(info->phys_base, NAND_IO_SIZE);
|
||||
kfree(info);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -21,7 +21,6 @@
|
||||
#include <linux/err.h>
|
||||
#include <asm/io.h>
|
||||
#include <asm/sizes.h>
|
||||
#include <mach/hardware.h>
|
||||
#include <linux/platform_data/mtd-orion_nand.h>
|
||||
|
||||
static void orion_nand_cmd_ctrl(struct mtd_info *mtd, int cmd, unsigned int ctrl)
|
||||
|
@ -37,6 +37,11 @@ static int __devinit plat_nand_probe(struct platform_device *pdev)
|
||||
const char **part_types;
|
||||
int err = 0;
|
||||
|
||||
if (!pdata) {
|
||||
dev_err(&pdev->dev, "platform_nand_data is missing\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (pdata->chip.nr_chips < 1) {
|
||||
dev_err(&pdev->dev, "invalid number of chips specified\n");
|
||||
return -EINVAL;
|
||||
|
@ -683,11 +683,13 @@ static void pxa3xx_nand_cmdfunc(struct mtd_info *mtd, unsigned command,
|
||||
info->state = STATE_IDLE;
|
||||
}
|
||||
|
||||
static void pxa3xx_nand_write_page_hwecc(struct mtd_info *mtd,
|
||||
static int pxa3xx_nand_write_page_hwecc(struct mtd_info *mtd,
|
||||
struct nand_chip *chip, const uint8_t *buf, int oob_required)
|
||||
{
|
||||
chip->write_buf(mtd, buf, mtd->writesize);
|
||||
chip->write_buf(mtd, chip->oob_poi, mtd->oobsize);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int pxa3xx_nand_read_page_hwecc(struct mtd_info *mtd,
|
||||
@ -771,12 +773,6 @@ static void pxa3xx_nand_write_buf(struct mtd_info *mtd,
|
||||
info->buf_start += real_len;
|
||||
}
|
||||
|
||||
static int pxa3xx_nand_verify_buf(struct mtd_info *mtd,
|
||||
const uint8_t *buf, int len)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void pxa3xx_nand_select_chip(struct mtd_info *mtd, int chip)
|
||||
{
|
||||
return;
|
||||
@ -1007,7 +1003,6 @@ KEEP_CONFIG:
|
||||
chip->ecc.size = host->page_size;
|
||||
chip->ecc.strength = 1;
|
||||
|
||||
chip->options |= NAND_NO_READRDY;
|
||||
if (host->reg_ndcr & NDCR_DWIDTH_M)
|
||||
chip->options |= NAND_BUSWIDTH_16;
|
||||
|
||||
@ -1070,7 +1065,6 @@ static int alloc_nand_resource(struct platform_device *pdev)
|
||||
chip->read_byte = pxa3xx_nand_read_byte;
|
||||
chip->read_buf = pxa3xx_nand_read_buf;
|
||||
chip->write_buf = pxa3xx_nand_write_buf;
|
||||
chip->verify_buf = pxa3xx_nand_verify_buf;
|
||||
}
|
||||
|
||||
spin_lock_init(&chip->controller->lock);
|
||||
|
@ -309,27 +309,6 @@ static uint8_t r852_read_byte(struct mtd_info *mtd)
|
||||
return r852_read_reg(dev, R852_DATALINE);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Readback the buffer to verify it
|
||||
*/
|
||||
int r852_verify_buf(struct mtd_info *mtd, const uint8_t *buf, int len)
|
||||
{
|
||||
struct r852_device *dev = r852_get_dev(mtd);
|
||||
|
||||
/* We can't be sure about anything here... */
|
||||
if (dev->card_unstable)
|
||||
return -1;
|
||||
|
||||
/* This will never happen, unless you wired up a nand chip
|
||||
with > 512 bytes page size to the reader */
|
||||
if (len > SM_SECTOR_SIZE)
|
||||
return 0;
|
||||
|
||||
r852_read_buf(mtd, dev->tmp_buffer, len);
|
||||
return memcmp(buf, dev->tmp_buffer, len);
|
||||
}
|
||||
|
||||
/*
|
||||
* Control several chip lines & send commands
|
||||
*/
|
||||
@ -882,7 +861,6 @@ int r852_probe(struct pci_dev *pci_dev, const struct pci_device_id *id)
|
||||
chip->read_byte = r852_read_byte;
|
||||
chip->read_buf = r852_read_buf;
|
||||
chip->write_buf = r852_write_buf;
|
||||
chip->verify_buf = r852_verify_buf;
|
||||
|
||||
/* ecc */
|
||||
chip->ecc.mode = NAND_ECC_HW_SYNDROME;
|
||||
|
@ -21,6 +21,8 @@
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#define pr_fmt(fmt) "nand-s3c2410: " fmt
|
||||
|
||||
#ifdef CONFIG_MTD_NAND_S3C2410_DEBUG
|
||||
#define DEBUG
|
||||
#endif
|
||||
@ -30,6 +32,7 @@
|
||||
#include <linux/init.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/string.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/ioport.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/delay.h>
|
||||
@ -43,24 +46,9 @@
|
||||
#include <linux/mtd/nand_ecc.h>
|
||||
#include <linux/mtd/partitions.h>
|
||||
|
||||
#include <asm/io.h>
|
||||
|
||||
#include <plat/regs-nand.h>
|
||||
#include <linux/platform_data/mtd-nand-s3c2410.h>
|
||||
|
||||
#ifdef CONFIG_MTD_NAND_S3C2410_HWECC
|
||||
static int hardware_ecc = 1;
|
||||
#else
|
||||
static int hardware_ecc = 0;
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_MTD_NAND_S3C2410_CLKSTOP
|
||||
static const int clock_stop = 1;
|
||||
#else
|
||||
static const int clock_stop = 0;
|
||||
#endif
|
||||
|
||||
|
||||
/* new oob placement block for use with hardware ecc generation
|
||||
*/
|
||||
|
||||
@ -109,9 +97,8 @@ enum s3c_nand_clk_state {
|
||||
* @mtds: An array of MTD instances on this controoler.
|
||||
* @platform: The platform data for this board.
|
||||
* @device: The platform device we bound to.
|
||||
* @area: The IO area resource that came from request_mem_region().
|
||||
* @clk: The clock resource for this controller.
|
||||
* @regs: The area mapped for the hardware registers described by @area.
|
||||
* @regs: The area mapped for the hardware registers.
|
||||
* @sel_reg: Pointer to the register controlling the NAND selection.
|
||||
* @sel_bit: The bit in @sel_reg to select the NAND chip.
|
||||
* @mtd_count: The number of MTDs created from this controller.
|
||||
@ -128,7 +115,6 @@ struct s3c2410_nand_info {
|
||||
|
||||
/* device info */
|
||||
struct device *device;
|
||||
struct resource *area;
|
||||
struct clk *clk;
|
||||
void __iomem *regs;
|
||||
void __iomem *sel_reg;
|
||||
@ -169,7 +155,11 @@ static struct s3c2410_platform_nand *to_nand_plat(struct platform_device *dev)
|
||||
|
||||
static inline int allow_clk_suspend(struct s3c2410_nand_info *info)
|
||||
{
|
||||
return clock_stop;
|
||||
#ifdef CONFIG_MTD_NAND_S3C2410_CLKSTOP
|
||||
return 1;
|
||||
#else
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
@ -215,7 +205,8 @@ static int s3c_nand_calc_rate(int wanted, unsigned long clk, int max)
|
||||
pr_debug("result %d from %ld, %d\n", result, clk, wanted);
|
||||
|
||||
if (result > max) {
|
||||
printk("%d ns is too big for current clock rate %ld\n", wanted, clk);
|
||||
pr_err("%d ns is too big for current clock rate %ld\n",
|
||||
wanted, clk);
|
||||
return -1;
|
||||
}
|
||||
|
||||
@ -225,7 +216,7 @@ static int s3c_nand_calc_rate(int wanted, unsigned long clk, int max)
|
||||
return result;
|
||||
}
|
||||
|
||||
#define to_ns(ticks,clk) (((ticks) * NS_IN_KHZ) / (unsigned int)(clk))
|
||||
#define to_ns(ticks, clk) (((ticks) * NS_IN_KHZ) / (unsigned int)(clk))
|
||||
|
||||
/* controller setup */
|
||||
|
||||
@ -268,7 +259,8 @@ static int s3c2410_nand_setrate(struct s3c2410_nand_info *info)
|
||||
}
|
||||
|
||||
dev_info(info->device, "Tacls=%d, %dns Twrph0=%d %dns, Twrph1=%d %dns\n",
|
||||
tacls, to_ns(tacls, clkrate), twrph0, to_ns(twrph0, clkrate), twrph1, to_ns(twrph1, clkrate));
|
||||
tacls, to_ns(tacls, clkrate), twrph0, to_ns(twrph0, clkrate),
|
||||
twrph1, to_ns(twrph1, clkrate));
|
||||
|
||||
switch (info->cpu_type) {
|
||||
case TYPE_S3C2410:
|
||||
@ -325,13 +317,13 @@ static int s3c2410_nand_inithw(struct s3c2410_nand_info *info)
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
switch (info->cpu_type) {
|
||||
case TYPE_S3C2410:
|
||||
switch (info->cpu_type) {
|
||||
case TYPE_S3C2410:
|
||||
default:
|
||||
break;
|
||||
|
||||
case TYPE_S3C2440:
|
||||
case TYPE_S3C2412:
|
||||
case TYPE_S3C2440:
|
||||
case TYPE_S3C2412:
|
||||
/* enable the controller and de-assert nFCE */
|
||||
|
||||
writel(S3C2440_NFCONT_ENABLE, info->regs + S3C2440_NFCONT);
|
||||
@ -450,6 +442,7 @@ static int s3c2412_nand_devready(struct mtd_info *mtd)
|
||||
|
||||
/* ECC handling functions */
|
||||
|
||||
#ifdef CONFIG_MTD_NAND_S3C2410_HWECC
|
||||
static int s3c2410_nand_correct_data(struct mtd_info *mtd, u_char *dat,
|
||||
u_char *read_ecc, u_char *calc_ecc)
|
||||
{
|
||||
@ -463,10 +456,8 @@ static int s3c2410_nand_correct_data(struct mtd_info *mtd, u_char *dat,
|
||||
diff1 = read_ecc[1] ^ calc_ecc[1];
|
||||
diff2 = read_ecc[2] ^ calc_ecc[2];
|
||||
|
||||
pr_debug("%s: rd %02x%02x%02x calc %02x%02x%02x diff %02x%02x%02x\n",
|
||||
__func__,
|
||||
read_ecc[0], read_ecc[1], read_ecc[2],
|
||||
calc_ecc[0], calc_ecc[1], calc_ecc[2],
|
||||
pr_debug("%s: rd %*phN calc %*phN diff %02x%02x%02x\n",
|
||||
__func__, 3, read_ecc, 3, calc_ecc,
|
||||
diff0, diff1, diff2);
|
||||
|
||||
if (diff0 == 0 && diff1 == 0 && diff2 == 0)
|
||||
@ -546,7 +537,8 @@ static void s3c2412_nand_enable_hwecc(struct mtd_info *mtd, int mode)
|
||||
unsigned long ctrl;
|
||||
|
||||
ctrl = readl(info->regs + S3C2440_NFCONT);
|
||||
writel(ctrl | S3C2412_NFCONT_INIT_MAIN_ECC, info->regs + S3C2440_NFCONT);
|
||||
writel(ctrl | S3C2412_NFCONT_INIT_MAIN_ECC,
|
||||
info->regs + S3C2440_NFCONT);
|
||||
}
|
||||
|
||||
static void s3c2440_nand_enable_hwecc(struct mtd_info *mtd, int mode)
|
||||
@ -558,7 +550,8 @@ static void s3c2440_nand_enable_hwecc(struct mtd_info *mtd, int mode)
|
||||
writel(ctrl | S3C2440_NFCONT_INITECC, info->regs + S3C2440_NFCONT);
|
||||
}
|
||||
|
||||
static int s3c2410_nand_calculate_ecc(struct mtd_info *mtd, const u_char *dat, u_char *ecc_code)
|
||||
static int s3c2410_nand_calculate_ecc(struct mtd_info *mtd, const u_char *dat,
|
||||
u_char *ecc_code)
|
||||
{
|
||||
struct s3c2410_nand_info *info = s3c2410_nand_mtd_toinfo(mtd);
|
||||
|
||||
@ -566,13 +559,13 @@ static int s3c2410_nand_calculate_ecc(struct mtd_info *mtd, const u_char *dat, u
|
||||
ecc_code[1] = readb(info->regs + S3C2410_NFECC + 1);
|
||||
ecc_code[2] = readb(info->regs + S3C2410_NFECC + 2);
|
||||
|
||||
pr_debug("%s: returning ecc %02x%02x%02x\n", __func__,
|
||||
ecc_code[0], ecc_code[1], ecc_code[2]);
|
||||
pr_debug("%s: returning ecc %*phN\n", __func__, 3, ecc_code);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int s3c2412_nand_calculate_ecc(struct mtd_info *mtd, const u_char *dat, u_char *ecc_code)
|
||||
static int s3c2412_nand_calculate_ecc(struct mtd_info *mtd, const u_char *dat,
|
||||
u_char *ecc_code)
|
||||
{
|
||||
struct s3c2410_nand_info *info = s3c2410_nand_mtd_toinfo(mtd);
|
||||
unsigned long ecc = readl(info->regs + S3C2412_NFMECC0);
|
||||
@ -581,12 +574,13 @@ static int s3c2412_nand_calculate_ecc(struct mtd_info *mtd, const u_char *dat, u
|
||||
ecc_code[1] = ecc >> 8;
|
||||
ecc_code[2] = ecc >> 16;
|
||||
|
||||
pr_debug("calculate_ecc: returning ecc %02x,%02x,%02x\n", ecc_code[0], ecc_code[1], ecc_code[2]);
|
||||
pr_debug("%s: returning ecc %*phN\n", __func__, 3, ecc_code);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int s3c2440_nand_calculate_ecc(struct mtd_info *mtd, const u_char *dat, u_char *ecc_code)
|
||||
static int s3c2440_nand_calculate_ecc(struct mtd_info *mtd, const u_char *dat,
|
||||
u_char *ecc_code)
|
||||
{
|
||||
struct s3c2410_nand_info *info = s3c2410_nand_mtd_toinfo(mtd);
|
||||
unsigned long ecc = readl(info->regs + S3C2440_NFMECC0);
|
||||
@ -599,6 +593,7 @@ static int s3c2440_nand_calculate_ecc(struct mtd_info *mtd, const u_char *dat, u
|
||||
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
/* over-ride the standard functions for a little more speed. We can
|
||||
* use read/write block to move the data buffers to/from the controller
|
||||
@ -625,13 +620,15 @@ static void s3c2440_nand_read_buf(struct mtd_info *mtd, u_char *buf, int len)
|
||||
}
|
||||
}
|
||||
|
||||
static void s3c2410_nand_write_buf(struct mtd_info *mtd, const u_char *buf, int len)
|
||||
static void s3c2410_nand_write_buf(struct mtd_info *mtd, const u_char *buf,
|
||||
int len)
|
||||
{
|
||||
struct nand_chip *this = mtd->priv;
|
||||
writesb(this->IO_ADDR_W, buf, len);
|
||||
}
|
||||
|
||||
static void s3c2440_nand_write_buf(struct mtd_info *mtd, const u_char *buf, int len)
|
||||
static void s3c2440_nand_write_buf(struct mtd_info *mtd, const u_char *buf,
|
||||
int len)
|
||||
{
|
||||
struct s3c2410_nand_info *info = s3c2410_nand_mtd_toinfo(mtd);
|
||||
|
||||
@ -675,7 +672,8 @@ static inline int s3c2410_nand_cpufreq_register(struct s3c2410_nand_info *info)
|
||||
CPUFREQ_TRANSITION_NOTIFIER);
|
||||
}
|
||||
|
||||
static inline void s3c2410_nand_cpufreq_deregister(struct s3c2410_nand_info *info)
|
||||
static inline void
|
||||
s3c2410_nand_cpufreq_deregister(struct s3c2410_nand_info *info)
|
||||
{
|
||||
cpufreq_unregister_notifier(&info->freq_transition,
|
||||
CPUFREQ_TRANSITION_NOTIFIER);
|
||||
@ -687,7 +685,8 @@ static inline int s3c2410_nand_cpufreq_register(struct s3c2410_nand_info *info)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline void s3c2410_nand_cpufreq_deregister(struct s3c2410_nand_info *info)
|
||||
static inline void
|
||||
s3c2410_nand_cpufreq_deregister(struct s3c2410_nand_info *info)
|
||||
{
|
||||
}
|
||||
#endif
|
||||
@ -717,29 +716,12 @@ static int s3c24xx_nand_remove(struct platform_device *pdev)
|
||||
pr_debug("releasing mtd %d (%p)\n", mtdno, ptr);
|
||||
nand_release(&ptr->mtd);
|
||||
}
|
||||
|
||||
kfree(info->mtds);
|
||||
}
|
||||
|
||||
/* free the common resources */
|
||||
|
||||
if (!IS_ERR(info->clk)) {
|
||||
if (!IS_ERR(info->clk))
|
||||
s3c2410_nand_clk_set_state(info, CLOCK_DISABLE);
|
||||
clk_put(info->clk);
|
||||
}
|
||||
|
||||
if (info->regs != NULL) {
|
||||
iounmap(info->regs);
|
||||
info->regs = NULL;
|
||||
}
|
||||
|
||||
if (info->area != NULL) {
|
||||
release_resource(info->area);
|
||||
kfree(info->area);
|
||||
info->area = NULL;
|
||||
}
|
||||
|
||||
kfree(info);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@ -810,7 +792,7 @@ static void s3c2410_nand_init_chip(struct s3c2410_nand_info *info,
|
||||
dev_info(info->device, "System booted from NAND\n");
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
chip->IO_ADDR_R = chip->IO_ADDR_W;
|
||||
|
||||
@ -819,32 +801,31 @@ static void s3c2410_nand_init_chip(struct s3c2410_nand_info *info,
|
||||
nmtd->mtd.owner = THIS_MODULE;
|
||||
nmtd->set = set;
|
||||
|
||||
if (hardware_ecc) {
|
||||
#ifdef CONFIG_MTD_NAND_S3C2410_HWECC
|
||||
chip->ecc.calculate = s3c2410_nand_calculate_ecc;
|
||||
chip->ecc.correct = s3c2410_nand_correct_data;
|
||||
chip->ecc.mode = NAND_ECC_HW;
|
||||
chip->ecc.strength = 1;
|
||||
|
||||
switch (info->cpu_type) {
|
||||
case TYPE_S3C2410:
|
||||
chip->ecc.hwctl = s3c2410_nand_enable_hwecc;
|
||||
chip->ecc.calculate = s3c2410_nand_calculate_ecc;
|
||||
chip->ecc.correct = s3c2410_nand_correct_data;
|
||||
chip->ecc.mode = NAND_ECC_HW;
|
||||
chip->ecc.strength = 1;
|
||||
break;
|
||||
|
||||
switch (info->cpu_type) {
|
||||
case TYPE_S3C2410:
|
||||
chip->ecc.hwctl = s3c2410_nand_enable_hwecc;
|
||||
chip->ecc.calculate = s3c2410_nand_calculate_ecc;
|
||||
break;
|
||||
case TYPE_S3C2412:
|
||||
chip->ecc.hwctl = s3c2412_nand_enable_hwecc;
|
||||
chip->ecc.calculate = s3c2412_nand_calculate_ecc;
|
||||
break;
|
||||
|
||||
case TYPE_S3C2412:
|
||||
chip->ecc.hwctl = s3c2412_nand_enable_hwecc;
|
||||
chip->ecc.calculate = s3c2412_nand_calculate_ecc;
|
||||
break;
|
||||
|
||||
case TYPE_S3C2440:
|
||||
chip->ecc.hwctl = s3c2440_nand_enable_hwecc;
|
||||
chip->ecc.calculate = s3c2440_nand_calculate_ecc;
|
||||
break;
|
||||
|
||||
}
|
||||
} else {
|
||||
chip->ecc.mode = NAND_ECC_SOFT;
|
||||
case TYPE_S3C2440:
|
||||
chip->ecc.hwctl = s3c2440_nand_enable_hwecc;
|
||||
chip->ecc.calculate = s3c2440_nand_calculate_ecc;
|
||||
break;
|
||||
}
|
||||
#else
|
||||
chip->ecc.mode = NAND_ECC_SOFT;
|
||||
#endif
|
||||
|
||||
if (set->ecc_layout != NULL)
|
||||
chip->ecc.layout = set->ecc_layout;
|
||||
@ -921,7 +902,7 @@ static void s3c2410_nand_update_chip(struct s3c2410_nand_info *info,
|
||||
static int s3c24xx_nand_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct s3c2410_platform_nand *plat = to_nand_plat(pdev);
|
||||
enum s3c_cpu_type cpu_type;
|
||||
enum s3c_cpu_type cpu_type;
|
||||
struct s3c2410_nand_info *info;
|
||||
struct s3c2410_nand_mtd *nmtd;
|
||||
struct s3c2410_nand_set *sets;
|
||||
@ -935,7 +916,7 @@ static int s3c24xx_nand_probe(struct platform_device *pdev)
|
||||
|
||||
pr_debug("s3c2410_nand_probe(%p)\n", pdev);
|
||||
|
||||
info = kzalloc(sizeof(*info), GFP_KERNEL);
|
||||
info = devm_kzalloc(&pdev->dev, sizeof(*info), GFP_KERNEL);
|
||||
if (info == NULL) {
|
||||
dev_err(&pdev->dev, "no memory for flash info\n");
|
||||
err = -ENOMEM;
|
||||
@ -949,7 +930,7 @@ static int s3c24xx_nand_probe(struct platform_device *pdev)
|
||||
|
||||
/* get the clock source and enable it */
|
||||
|
||||
info->clk = clk_get(&pdev->dev, "nand");
|
||||
info->clk = devm_clk_get(&pdev->dev, "nand");
|
||||
if (IS_ERR(info->clk)) {
|
||||
dev_err(&pdev->dev, "failed to get clock\n");
|
||||
err = -ENOENT;
|
||||
@ -961,22 +942,14 @@ static int s3c24xx_nand_probe(struct platform_device *pdev)
|
||||
/* allocate and map the resource */
|
||||
|
||||
/* currently we assume we have the one resource */
|
||||
res = pdev->resource;
|
||||
res = pdev->resource;
|
||||
size = resource_size(res);
|
||||
|
||||
info->area = request_mem_region(res->start, size, pdev->name);
|
||||
|
||||
if (info->area == NULL) {
|
||||
dev_err(&pdev->dev, "cannot reserve register region\n");
|
||||
err = -ENOENT;
|
||||
goto exit_error;
|
||||
}
|
||||
|
||||
info->device = &pdev->dev;
|
||||
info->platform = plat;
|
||||
info->regs = ioremap(res->start, size);
|
||||
info->cpu_type = cpu_type;
|
||||
info->device = &pdev->dev;
|
||||
info->platform = plat;
|
||||
info->cpu_type = cpu_type;
|
||||
|
||||
info->regs = devm_request_and_ioremap(&pdev->dev, res);
|
||||
if (info->regs == NULL) {
|
||||
dev_err(&pdev->dev, "cannot reserve register region\n");
|
||||
err = -EIO;
|
||||
@ -999,7 +972,7 @@ static int s3c24xx_nand_probe(struct platform_device *pdev)
|
||||
/* allocate our information */
|
||||
|
||||
size = nr_sets * sizeof(*info->mtds);
|
||||
info->mtds = kzalloc(size, GFP_KERNEL);
|
||||
info->mtds = devm_kzalloc(&pdev->dev, size, GFP_KERNEL);
|
||||
if (info->mtds == NULL) {
|
||||
dev_err(&pdev->dev, "failed to allocate mtd storage\n");
|
||||
err = -ENOMEM;
|
||||
@ -1011,7 +984,8 @@ static int s3c24xx_nand_probe(struct platform_device *pdev)
|
||||
nmtd = info->mtds;
|
||||
|
||||
for (setno = 0; setno < nr_sets; setno++, nmtd++) {
|
||||
pr_debug("initialising set %d (%p, info %p)\n", setno, nmtd, info);
|
||||
pr_debug("initialising set %d (%p, info %p)\n",
|
||||
setno, nmtd, info);
|
||||
|
||||
s3c2410_nand_init_chip(info, nmtd, sets);
|
||||
|
||||
@ -1134,20 +1108,7 @@ static struct platform_driver s3c24xx_nand_driver = {
|
||||
},
|
||||
};
|
||||
|
||||
static int __init s3c2410_nand_init(void)
|
||||
{
|
||||
printk("S3C24XX NAND Driver, (c) 2004 Simtec Electronics\n");
|
||||
|
||||
return platform_driver_register(&s3c24xx_nand_driver);
|
||||
}
|
||||
|
||||
static void __exit s3c2410_nand_exit(void)
|
||||
{
|
||||
platform_driver_unregister(&s3c24xx_nand_driver);
|
||||
}
|
||||
|
||||
module_init(s3c2410_nand_init);
|
||||
module_exit(s3c2410_nand_exit);
|
||||
module_platform_driver(s3c24xx_nand_driver);
|
||||
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_AUTHOR("Ben Dooks <ben@simtec.co.uk>");
|
||||
|
@ -24,10 +24,12 @@
|
||||
#include <linux/module.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/pm_runtime.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/string.h>
|
||||
|
||||
#include <linux/mtd/mtd.h>
|
||||
#include <linux/mtd/nand.h>
|
||||
@ -43,11 +45,17 @@ static struct nand_ecclayout flctl_4secc_oob_16 = {
|
||||
};
|
||||
|
||||
static struct nand_ecclayout flctl_4secc_oob_64 = {
|
||||
.eccbytes = 10,
|
||||
.eccpos = {48, 49, 50, 51, 52, 53, 54, 55, 56, 57},
|
||||
.eccbytes = 4 * 10,
|
||||
.eccpos = {
|
||||
6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
|
||||
22, 23, 24, 25, 26, 27, 28, 29, 30, 31,
|
||||
38, 39, 40, 41, 42, 43, 44, 45, 46, 47,
|
||||
54, 55, 56, 57, 58, 59, 60, 61, 62, 63 },
|
||||
.oobfree = {
|
||||
{.offset = 60,
|
||||
. length = 4} },
|
||||
{.offset = 2, .length = 4},
|
||||
{.offset = 16, .length = 6},
|
||||
{.offset = 32, .length = 6},
|
||||
{.offset = 48, .length = 6} },
|
||||
};
|
||||
|
||||
static uint8_t scan_ff_pattern[] = { 0xff, 0xff };
|
||||
@ -61,15 +69,15 @@ static struct nand_bbt_descr flctl_4secc_smallpage = {
|
||||
|
||||
static struct nand_bbt_descr flctl_4secc_largepage = {
|
||||
.options = NAND_BBT_SCAN2NDPAGE,
|
||||
.offs = 58,
|
||||
.offs = 0,
|
||||
.len = 2,
|
||||
.pattern = scan_ff_pattern,
|
||||
};
|
||||
|
||||
static void empty_fifo(struct sh_flctl *flctl)
|
||||
{
|
||||
writel(0x000c0000, FLINTDMACR(flctl)); /* FIFO Clear */
|
||||
writel(0x00000000, FLINTDMACR(flctl)); /* Clear Error flags */
|
||||
writel(flctl->flintdmacr_base | AC1CLR | AC0CLR, FLINTDMACR(flctl));
|
||||
writel(flctl->flintdmacr_base, FLINTDMACR(flctl));
|
||||
}
|
||||
|
||||
static void start_translation(struct sh_flctl *flctl)
|
||||
@ -158,27 +166,56 @@ static void wait_wfifo_ready(struct sh_flctl *flctl)
|
||||
timeout_error(flctl, __func__);
|
||||
}
|
||||
|
||||
static int wait_recfifo_ready(struct sh_flctl *flctl, int sector_number)
|
||||
static enum flctl_ecc_res_t wait_recfifo_ready
|
||||
(struct sh_flctl *flctl, int sector_number)
|
||||
{
|
||||
uint32_t timeout = LOOP_TIMEOUT_MAX;
|
||||
int checked[4];
|
||||
void __iomem *ecc_reg[4];
|
||||
int i;
|
||||
int state = FL_SUCCESS;
|
||||
uint32_t data, size;
|
||||
|
||||
memset(checked, 0, sizeof(checked));
|
||||
|
||||
/*
|
||||
* First this loops checks in FLDTCNTR if we are ready to read out the
|
||||
* oob data. This is the case if either all went fine without errors or
|
||||
* if the bottom part of the loop corrected the errors or marked them as
|
||||
* uncorrectable and the controller is given time to push the data into
|
||||
* the FIFO.
|
||||
*/
|
||||
while (timeout--) {
|
||||
/* check if all is ok and we can read out the OOB */
|
||||
size = readl(FLDTCNTR(flctl)) >> 24;
|
||||
if (size & 0xFF)
|
||||
return 0; /* success */
|
||||
if ((size & 0xFF) == 4)
|
||||
return state;
|
||||
|
||||
if (readl(FL4ECCCR(flctl)) & _4ECCFA)
|
||||
return 1; /* can't correct */
|
||||
|
||||
udelay(1);
|
||||
if (!(readl(FL4ECCCR(flctl)) & _4ECCEND))
|
||||
/* check if a correction code has been calculated */
|
||||
if (!(readl(FL4ECCCR(flctl)) & _4ECCEND)) {
|
||||
/*
|
||||
* either we wait for the fifo to be filled or a
|
||||
* correction pattern is being generated
|
||||
*/
|
||||
udelay(1);
|
||||
continue;
|
||||
}
|
||||
|
||||
/* check for an uncorrectable error */
|
||||
if (readl(FL4ECCCR(flctl)) & _4ECCFA) {
|
||||
/* check if we face a non-empty page */
|
||||
for (i = 0; i < 512; i++) {
|
||||
if (flctl->done_buff[i] != 0xff) {
|
||||
state = FL_ERROR; /* can't correct */
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (state == FL_SUCCESS)
|
||||
dev_dbg(&flctl->pdev->dev,
|
||||
"reading empty sector %d, ecc error ignored\n",
|
||||
sector_number);
|
||||
|
||||
writel(0, FL4ECCCR(flctl));
|
||||
continue;
|
||||
}
|
||||
|
||||
/* start error correction */
|
||||
ecc_reg[0] = FL4ECCRESULT0(flctl);
|
||||
@ -187,28 +224,26 @@ static int wait_recfifo_ready(struct sh_flctl *flctl, int sector_number)
|
||||
ecc_reg[3] = FL4ECCRESULT3(flctl);
|
||||
|
||||
for (i = 0; i < 3; i++) {
|
||||
uint8_t org;
|
||||
int index;
|
||||
|
||||
data = readl(ecc_reg[i]);
|
||||
if (data != INIT_FL4ECCRESULT_VAL && !checked[i]) {
|
||||
uint8_t org;
|
||||
int index;
|
||||
|
||||
if (flctl->page_size)
|
||||
index = (512 * sector_number) +
|
||||
(data >> 16);
|
||||
else
|
||||
index = data >> 16;
|
||||
if (flctl->page_size)
|
||||
index = (512 * sector_number) +
|
||||
(data >> 16);
|
||||
else
|
||||
index = data >> 16;
|
||||
|
||||
org = flctl->done_buff[index];
|
||||
flctl->done_buff[index] = org ^ (data & 0xFF);
|
||||
checked[i] = 1;
|
||||
}
|
||||
org = flctl->done_buff[index];
|
||||
flctl->done_buff[index] = org ^ (data & 0xFF);
|
||||
}
|
||||
|
||||
state = FL_REPAIRABLE;
|
||||
writel(0, FL4ECCCR(flctl));
|
||||
}
|
||||
|
||||
timeout_error(flctl, __func__);
|
||||
return 1; /* timeout */
|
||||
return FL_TIMEOUT; /* timeout */
|
||||
}
|
||||
|
||||
static void wait_wecfifo_ready(struct sh_flctl *flctl)
|
||||
@ -241,31 +276,33 @@ static void read_fiforeg(struct sh_flctl *flctl, int rlen, int offset)
|
||||
{
|
||||
int i, len_4align;
|
||||
unsigned long *buf = (unsigned long *)&flctl->done_buff[offset];
|
||||
void *fifo_addr = (void *)FLDTFIFO(flctl);
|
||||
|
||||
len_4align = (rlen + 3) / 4;
|
||||
|
||||
for (i = 0; i < len_4align; i++) {
|
||||
wait_rfifo_ready(flctl);
|
||||
buf[i] = readl(fifo_addr);
|
||||
buf[i] = readl(FLDTFIFO(flctl));
|
||||
buf[i] = be32_to_cpu(buf[i]);
|
||||
}
|
||||
}
|
||||
|
||||
static int read_ecfiforeg(struct sh_flctl *flctl, uint8_t *buff, int sector)
|
||||
static enum flctl_ecc_res_t read_ecfiforeg
|
||||
(struct sh_flctl *flctl, uint8_t *buff, int sector)
|
||||
{
|
||||
int i;
|
||||
enum flctl_ecc_res_t res;
|
||||
unsigned long *ecc_buf = (unsigned long *)buff;
|
||||
void *fifo_addr = (void *)FLECFIFO(flctl);
|
||||
|
||||
for (i = 0; i < 4; i++) {
|
||||
if (wait_recfifo_ready(flctl , sector))
|
||||
return 1;
|
||||
ecc_buf[i] = readl(fifo_addr);
|
||||
ecc_buf[i] = be32_to_cpu(ecc_buf[i]);
|
||||
res = wait_recfifo_ready(flctl , sector);
|
||||
|
||||
if (res != FL_ERROR) {
|
||||
for (i = 0; i < 4; i++) {
|
||||
ecc_buf[i] = readl(FLECFIFO(flctl));
|
||||
ecc_buf[i] = be32_to_cpu(ecc_buf[i]);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
return res;
|
||||
}
|
||||
|
||||
static void write_fiforeg(struct sh_flctl *flctl, int rlen, int offset)
|
||||
@ -281,6 +318,18 @@ static void write_fiforeg(struct sh_flctl *flctl, int rlen, int offset)
|
||||
}
|
||||
}
|
||||
|
||||
static void write_ec_fiforeg(struct sh_flctl *flctl, int rlen, int offset)
|
||||
{
|
||||
int i, len_4align;
|
||||
unsigned long *data = (unsigned long *)&flctl->done_buff[offset];
|
||||
|
||||
len_4align = (rlen + 3) / 4;
|
||||
for (i = 0; i < len_4align; i++) {
|
||||
wait_wecfifo_ready(flctl);
|
||||
writel(cpu_to_be32(data[i]), FLECFIFO(flctl));
|
||||
}
|
||||
}
|
||||
|
||||
static void set_cmd_regs(struct mtd_info *mtd, uint32_t cmd, uint32_t flcmcdr_val)
|
||||
{
|
||||
struct sh_flctl *flctl = mtd_to_flctl(mtd);
|
||||
@ -346,73 +395,65 @@ static void set_cmd_regs(struct mtd_info *mtd, uint32_t cmd, uint32_t flcmcdr_va
|
||||
static int flctl_read_page_hwecc(struct mtd_info *mtd, struct nand_chip *chip,
|
||||
uint8_t *buf, int oob_required, int page)
|
||||
{
|
||||
int i, eccsize = chip->ecc.size;
|
||||
int eccbytes = chip->ecc.bytes;
|
||||
int eccsteps = chip->ecc.steps;
|
||||
uint8_t *p = buf;
|
||||
struct sh_flctl *flctl = mtd_to_flctl(mtd);
|
||||
|
||||
for (i = 0; eccsteps; eccsteps--, i += eccbytes, p += eccsize)
|
||||
chip->read_buf(mtd, p, eccsize);
|
||||
|
||||
for (i = 0; eccsteps; eccsteps--, i += eccbytes, p += eccsize) {
|
||||
if (flctl->hwecc_cant_correct[i])
|
||||
mtd->ecc_stats.failed++;
|
||||
else
|
||||
mtd->ecc_stats.corrected += 0; /* FIXME */
|
||||
}
|
||||
|
||||
chip->read_buf(mtd, buf, mtd->writesize);
|
||||
if (oob_required)
|
||||
chip->read_buf(mtd, chip->oob_poi, mtd->oobsize);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void flctl_write_page_hwecc(struct mtd_info *mtd, struct nand_chip *chip,
|
||||
static int flctl_write_page_hwecc(struct mtd_info *mtd, struct nand_chip *chip,
|
||||
const uint8_t *buf, int oob_required)
|
||||
{
|
||||
int i, eccsize = chip->ecc.size;
|
||||
int eccbytes = chip->ecc.bytes;
|
||||
int eccsteps = chip->ecc.steps;
|
||||
const uint8_t *p = buf;
|
||||
|
||||
for (i = 0; eccsteps; eccsteps--, i += eccbytes, p += eccsize)
|
||||
chip->write_buf(mtd, p, eccsize);
|
||||
chip->write_buf(mtd, buf, mtd->writesize);
|
||||
chip->write_buf(mtd, chip->oob_poi, mtd->oobsize);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void execmd_read_page_sector(struct mtd_info *mtd, int page_addr)
|
||||
{
|
||||
struct sh_flctl *flctl = mtd_to_flctl(mtd);
|
||||
int sector, page_sectors;
|
||||
enum flctl_ecc_res_t ecc_result;
|
||||
|
||||
if (flctl->page_size)
|
||||
page_sectors = 4;
|
||||
else
|
||||
page_sectors = 1;
|
||||
|
||||
writel(readl(FLCMNCR(flctl)) | ACM_SACCES_MODE | _4ECCCORRECT,
|
||||
FLCMNCR(flctl));
|
||||
page_sectors = flctl->page_size ? 4 : 1;
|
||||
|
||||
set_cmd_regs(mtd, NAND_CMD_READ0,
|
||||
(NAND_CMD_READSTART << 8) | NAND_CMD_READ0);
|
||||
|
||||
writel(readl(FLCMNCR(flctl)) | ACM_SACCES_MODE | _4ECCCORRECT,
|
||||
FLCMNCR(flctl));
|
||||
writel(readl(FLCMDCR(flctl)) | page_sectors, FLCMDCR(flctl));
|
||||
writel(page_addr << 2, FLADR(flctl));
|
||||
|
||||
empty_fifo(flctl);
|
||||
start_translation(flctl);
|
||||
|
||||
for (sector = 0; sector < page_sectors; sector++) {
|
||||
int ret;
|
||||
|
||||
empty_fifo(flctl);
|
||||
writel(readl(FLCMDCR(flctl)) | 1, FLCMDCR(flctl));
|
||||
writel(page_addr << 2 | sector, FLADR(flctl));
|
||||
|
||||
start_translation(flctl);
|
||||
read_fiforeg(flctl, 512, 512 * sector);
|
||||
|
||||
ret = read_ecfiforeg(flctl,
|
||||
ecc_result = read_ecfiforeg(flctl,
|
||||
&flctl->done_buff[mtd->writesize + 16 * sector],
|
||||
sector);
|
||||
|
||||
if (ret)
|
||||
flctl->hwecc_cant_correct[sector] = 1;
|
||||
|
||||
writel(0x0, FL4ECCCR(flctl));
|
||||
wait_completion(flctl);
|
||||
switch (ecc_result) {
|
||||
case FL_REPAIRABLE:
|
||||
dev_info(&flctl->pdev->dev,
|
||||
"applied ecc on page 0x%x", page_addr);
|
||||
flctl->mtd.ecc_stats.corrected++;
|
||||
break;
|
||||
case FL_ERROR:
|
||||
dev_warn(&flctl->pdev->dev,
|
||||
"page 0x%x contains corrupted data\n",
|
||||
page_addr);
|
||||
flctl->mtd.ecc_stats.failed++;
|
||||
break;
|
||||
default:
|
||||
;
|
||||
}
|
||||
}
|
||||
|
||||
wait_completion(flctl);
|
||||
|
||||
writel(readl(FLCMNCR(flctl)) & ~(ACM_SACCES_MODE | _4ECCCORRECT),
|
||||
FLCMNCR(flctl));
|
||||
}
|
||||
@ -420,30 +461,20 @@ static void execmd_read_page_sector(struct mtd_info *mtd, int page_addr)
|
||||
static void execmd_read_oob(struct mtd_info *mtd, int page_addr)
|
||||
{
|
||||
struct sh_flctl *flctl = mtd_to_flctl(mtd);
|
||||
int page_sectors = flctl->page_size ? 4 : 1;
|
||||
int i;
|
||||
|
||||
set_cmd_regs(mtd, NAND_CMD_READ0,
|
||||
(NAND_CMD_READSTART << 8) | NAND_CMD_READ0);
|
||||
|
||||
empty_fifo(flctl);
|
||||
if (flctl->page_size) {
|
||||
int i;
|
||||
/* In case that the page size is 2k */
|
||||
for (i = 0; i < 16 * 3; i++)
|
||||
flctl->done_buff[i] = 0xFF;
|
||||
|
||||
set_addr(mtd, 3 * 528 + 512, page_addr);
|
||||
for (i = 0; i < page_sectors; i++) {
|
||||
set_addr(mtd, (512 + 16) * i + 512 , page_addr);
|
||||
writel(16, FLDTCNTR(flctl));
|
||||
|
||||
start_translation(flctl);
|
||||
read_fiforeg(flctl, 16, 16 * 3);
|
||||
wait_completion(flctl);
|
||||
} else {
|
||||
/* In case that the page size is 512b */
|
||||
set_addr(mtd, 512, page_addr);
|
||||
writel(16, FLDTCNTR(flctl));
|
||||
|
||||
start_translation(flctl);
|
||||
read_fiforeg(flctl, 16, 0);
|
||||
read_fiforeg(flctl, 16, 16 * i);
|
||||
wait_completion(flctl);
|
||||
}
|
||||
}
|
||||
@ -451,34 +482,26 @@ static void execmd_read_oob(struct mtd_info *mtd, int page_addr)
|
||||
static void execmd_write_page_sector(struct mtd_info *mtd)
|
||||
{
|
||||
struct sh_flctl *flctl = mtd_to_flctl(mtd);
|
||||
int i, page_addr = flctl->seqin_page_addr;
|
||||
int page_addr = flctl->seqin_page_addr;
|
||||
int sector, page_sectors;
|
||||
|
||||
if (flctl->page_size)
|
||||
page_sectors = 4;
|
||||
else
|
||||
page_sectors = 1;
|
||||
|
||||
writel(readl(FLCMNCR(flctl)) | ACM_SACCES_MODE, FLCMNCR(flctl));
|
||||
page_sectors = flctl->page_size ? 4 : 1;
|
||||
|
||||
set_cmd_regs(mtd, NAND_CMD_PAGEPROG,
|
||||
(NAND_CMD_PAGEPROG << 8) | NAND_CMD_SEQIN);
|
||||
|
||||
empty_fifo(flctl);
|
||||
writel(readl(FLCMNCR(flctl)) | ACM_SACCES_MODE, FLCMNCR(flctl));
|
||||
writel(readl(FLCMDCR(flctl)) | page_sectors, FLCMDCR(flctl));
|
||||
writel(page_addr << 2, FLADR(flctl));
|
||||
start_translation(flctl);
|
||||
|
||||
for (sector = 0; sector < page_sectors; sector++) {
|
||||
empty_fifo(flctl);
|
||||
writel(readl(FLCMDCR(flctl)) | 1, FLCMDCR(flctl));
|
||||
writel(page_addr << 2 | sector, FLADR(flctl));
|
||||
|
||||
start_translation(flctl);
|
||||
write_fiforeg(flctl, 512, 512 * sector);
|
||||
|
||||
for (i = 0; i < 4; i++) {
|
||||
wait_wecfifo_ready(flctl); /* wait for write ready */
|
||||
writel(0xFFFFFFFF, FLECFIFO(flctl));
|
||||
}
|
||||
wait_completion(flctl);
|
||||
write_ec_fiforeg(flctl, 16, mtd->writesize + 16 * sector);
|
||||
}
|
||||
|
||||
wait_completion(flctl);
|
||||
writel(readl(FLCMNCR(flctl)) & ~ACM_SACCES_MODE, FLCMNCR(flctl));
|
||||
}
|
||||
|
||||
@ -488,18 +511,12 @@ static void execmd_write_oob(struct mtd_info *mtd)
|
||||
int page_addr = flctl->seqin_page_addr;
|
||||
int sector, page_sectors;
|
||||
|
||||
if (flctl->page_size) {
|
||||
sector = 3;
|
||||
page_sectors = 4;
|
||||
} else {
|
||||
sector = 0;
|
||||
page_sectors = 1;
|
||||
}
|
||||
page_sectors = flctl->page_size ? 4 : 1;
|
||||
|
||||
set_cmd_regs(mtd, NAND_CMD_PAGEPROG,
|
||||
(NAND_CMD_PAGEPROG << 8) | NAND_CMD_SEQIN);
|
||||
|
||||
for (; sector < page_sectors; sector++) {
|
||||
for (sector = 0; sector < page_sectors; sector++) {
|
||||
empty_fifo(flctl);
|
||||
set_addr(mtd, sector * 528 + 512, page_addr);
|
||||
writel(16, FLDTCNTR(flctl)); /* set read size */
|
||||
@ -731,10 +748,9 @@ static void flctl_select_chip(struct mtd_info *mtd, int chipnr)
|
||||
static void flctl_write_buf(struct mtd_info *mtd, const uint8_t *buf, int len)
|
||||
{
|
||||
struct sh_flctl *flctl = mtd_to_flctl(mtd);
|
||||
int i, index = flctl->index;
|
||||
int index = flctl->index;
|
||||
|
||||
for (i = 0; i < len; i++)
|
||||
flctl->done_buff[index + i] = buf[i];
|
||||
memcpy(&flctl->done_buff[index], buf, len);
|
||||
flctl->index += len;
|
||||
}
|
||||
|
||||
@ -763,20 +779,11 @@ static uint16_t flctl_read_word(struct mtd_info *mtd)
|
||||
|
||||
static void flctl_read_buf(struct mtd_info *mtd, uint8_t *buf, int len)
|
||||
{
|
||||
int i;
|
||||
struct sh_flctl *flctl = mtd_to_flctl(mtd);
|
||||
int index = flctl->index;
|
||||
|
||||
for (i = 0; i < len; i++)
|
||||
buf[i] = flctl_read_byte(mtd);
|
||||
}
|
||||
|
||||
static int flctl_verify_buf(struct mtd_info *mtd, const u_char *buf, int len)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < len; i++)
|
||||
if (buf[i] != flctl_read_byte(mtd))
|
||||
return -EFAULT;
|
||||
return 0;
|
||||
memcpy(buf, &flctl->done_buff[index], len);
|
||||
flctl->index += len;
|
||||
}
|
||||
|
||||
static int flctl_chip_init_tail(struct mtd_info *mtd)
|
||||
@ -831,7 +838,7 @@ static int flctl_chip_init_tail(struct mtd_info *mtd)
|
||||
chip->ecc.mode = NAND_ECC_HW;
|
||||
|
||||
/* 4 symbols ECC enabled */
|
||||
flctl->flcmncr_base |= _4ECCEN | ECCPOS2 | ECCPOS_02;
|
||||
flctl->flcmncr_base |= _4ECCEN;
|
||||
} else {
|
||||
chip->ecc.mode = NAND_ECC_SOFT;
|
||||
}
|
||||
@ -839,6 +846,16 @@ static int flctl_chip_init_tail(struct mtd_info *mtd)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static irqreturn_t flctl_handle_flste(int irq, void *dev_id)
|
||||
{
|
||||
struct sh_flctl *flctl = dev_id;
|
||||
|
||||
dev_err(&flctl->pdev->dev, "flste irq: %x\n", readl(FLINTDMACR(flctl)));
|
||||
writel(flctl->flintdmacr_base, FLINTDMACR(flctl));
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static int __devinit flctl_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct resource *res;
|
||||
@ -847,6 +864,7 @@ static int __devinit flctl_probe(struct platform_device *pdev)
|
||||
struct nand_chip *nand;
|
||||
struct sh_flctl_platform_data *pdata;
|
||||
int ret = -ENXIO;
|
||||
int irq;
|
||||
|
||||
pdata = pdev->dev.platform_data;
|
||||
if (pdata == NULL) {
|
||||
@ -872,14 +890,27 @@ static int __devinit flctl_probe(struct platform_device *pdev)
|
||||
goto err_iomap;
|
||||
}
|
||||
|
||||
irq = platform_get_irq(pdev, 0);
|
||||
if (irq < 0) {
|
||||
dev_err(&pdev->dev, "failed to get flste irq data\n");
|
||||
goto err_flste;
|
||||
}
|
||||
|
||||
ret = request_irq(irq, flctl_handle_flste, IRQF_SHARED, "flste", flctl);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "request interrupt failed.\n");
|
||||
goto err_flste;
|
||||
}
|
||||
|
||||
platform_set_drvdata(pdev, flctl);
|
||||
flctl_mtd = &flctl->mtd;
|
||||
nand = &flctl->chip;
|
||||
flctl_mtd->priv = nand;
|
||||
flctl->pdev = pdev;
|
||||
flctl->flcmncr_base = pdata->flcmncr_val;
|
||||
flctl->hwecc = pdata->has_hwecc;
|
||||
flctl->holden = pdata->use_holden;
|
||||
flctl->flcmncr_base = pdata->flcmncr_val;
|
||||
flctl->flintdmacr_base = flctl->hwecc ? (STERINTE | ECERB) : STERINTE;
|
||||
|
||||
/* Set address of hardware control function */
|
||||
/* 20 us command delay time */
|
||||
@ -888,7 +919,6 @@ static int __devinit flctl_probe(struct platform_device *pdev)
|
||||
nand->read_byte = flctl_read_byte;
|
||||
nand->write_buf = flctl_write_buf;
|
||||
nand->read_buf = flctl_read_buf;
|
||||
nand->verify_buf = flctl_verify_buf;
|
||||
nand->select_chip = flctl_select_chip;
|
||||
nand->cmdfunc = flctl_cmdfunc;
|
||||
|
||||
@ -918,6 +948,9 @@ static int __devinit flctl_probe(struct platform_device *pdev)
|
||||
|
||||
err_chip:
|
||||
pm_runtime_disable(&pdev->dev);
|
||||
free_irq(irq, flctl);
|
||||
err_flste:
|
||||
iounmap(flctl->reg);
|
||||
err_iomap:
|
||||
kfree(flctl);
|
||||
return ret;
|
||||
@ -929,6 +962,8 @@ static int __devexit flctl_remove(struct platform_device *pdev)
|
||||
|
||||
nand_release(&flctl->mtd);
|
||||
pm_runtime_disable(&pdev->dev);
|
||||
free_irq(platform_get_irq(pdev, 0), flctl);
|
||||
iounmap(flctl->reg);
|
||||
kfree(flctl);
|
||||
|
||||
return 0;
|
||||
|
@ -98,24 +98,6 @@ static uint16_t socrates_nand_read_word(struct mtd_info *mtd)
|
||||
return word;
|
||||
}
|
||||
|
||||
/**
|
||||
* socrates_nand_verify_buf - Verify chip data against buffer
|
||||
* @mtd: MTD device structure
|
||||
* @buf: buffer containing the data to compare
|
||||
* @len: number of bytes to compare
|
||||
*/
|
||||
static int socrates_nand_verify_buf(struct mtd_info *mtd, const u8 *buf,
|
||||
int len)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < len; i++) {
|
||||
if (buf[i] != socrates_nand_read_byte(mtd))
|
||||
return -EFAULT;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Hardware specific access to control-lines
|
||||
*/
|
||||
@ -201,7 +183,6 @@ static int __devinit socrates_nand_probe(struct platform_device *ofdev)
|
||||
nand_chip->read_word = socrates_nand_read_word;
|
||||
nand_chip->write_buf = socrates_nand_write_buf;
|
||||
nand_chip->read_buf = socrates_nand_read_buf;
|
||||
nand_chip->verify_buf = socrates_nand_verify_buf;
|
||||
nand_chip->dev_ready = socrates_nand_device_ready;
|
||||
|
||||
nand_chip->ecc.mode = NAND_ECC_SOFT; /* enable ECC */
|
||||
|
@ -256,18 +256,6 @@ static void tmio_nand_read_buf(struct mtd_info *mtd, u_char *buf, int len)
|
||||
tmio_ioread16_rep(tmio->fcr + FCR_DATA, buf, len >> 1);
|
||||
}
|
||||
|
||||
static int
|
||||
tmio_nand_verify_buf(struct mtd_info *mtd, const u_char *buf, int len)
|
||||
{
|
||||
struct tmio_nand *tmio = mtd_to_tmio(mtd);
|
||||
u16 *p = (u16 *) buf;
|
||||
|
||||
for (len >>= 1; len; len--)
|
||||
if (*(p++) != tmio_ioread16(tmio->fcr + FCR_DATA))
|
||||
return -EFAULT;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void tmio_nand_enable_hwecc(struct mtd_info *mtd, int mode)
|
||||
{
|
||||
struct tmio_nand *tmio = mtd_to_tmio(mtd);
|
||||
@ -424,7 +412,6 @@ static int tmio_probe(struct platform_device *dev)
|
||||
nand_chip->read_byte = tmio_nand_read_byte;
|
||||
nand_chip->write_buf = tmio_nand_write_buf;
|
||||
nand_chip->read_buf = tmio_nand_read_buf;
|
||||
nand_chip->verify_buf = tmio_nand_verify_buf;
|
||||
|
||||
/* set eccmode using hardware ECC */
|
||||
nand_chip->ecc.mode = NAND_ECC_HW;
|
||||
|
@ -131,18 +131,6 @@ static void txx9ndfmc_read_buf(struct mtd_info *mtd, uint8_t *buf, int len)
|
||||
*buf++ = __raw_readl(ndfdtr);
|
||||
}
|
||||
|
||||
static int txx9ndfmc_verify_buf(struct mtd_info *mtd, const uint8_t *buf,
|
||||
int len)
|
||||
{
|
||||
struct platform_device *dev = mtd_to_platdev(mtd);
|
||||
void __iomem *ndfdtr = ndregaddr(dev, TXX9_NDFDTR);
|
||||
|
||||
while (len--)
|
||||
if (*buf++ != (uint8_t)__raw_readl(ndfdtr))
|
||||
return -EFAULT;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void txx9ndfmc_cmd_ctrl(struct mtd_info *mtd, int cmd,
|
||||
unsigned int ctrl)
|
||||
{
|
||||
@ -346,7 +334,6 @@ static int __init txx9ndfmc_probe(struct platform_device *dev)
|
||||
chip->read_byte = txx9ndfmc_read_byte;
|
||||
chip->read_buf = txx9ndfmc_read_buf;
|
||||
chip->write_buf = txx9ndfmc_write_buf;
|
||||
chip->verify_buf = txx9ndfmc_verify_buf;
|
||||
chip->cmd_ctrl = txx9ndfmc_cmd_ctrl;
|
||||
chip->dev_ready = txx9ndfmc_dev_ready;
|
||||
chip->ecc.calculate = txx9ndfmc_calculate_ecc;
|
||||
|
201
drivers/mtd/nand/xway_nand.c
Normal file
201
drivers/mtd/nand/xway_nand.c
Normal file
@ -0,0 +1,201 @@
|
||||
/*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 as published
|
||||
* by the Free Software Foundation.
|
||||
*
|
||||
* Copyright © 2012 John Crispin <blogic@openwrt.org>
|
||||
*/
|
||||
|
||||
#include <linux/mtd/nand.h>
|
||||
#include <linux/of_gpio.h>
|
||||
#include <linux/of_platform.h>
|
||||
|
||||
#include <lantiq_soc.h>
|
||||
|
||||
/* nand registers */
|
||||
#define EBU_ADDSEL1 0x24
|
||||
#define EBU_NAND_CON 0xB0
|
||||
#define EBU_NAND_WAIT 0xB4
|
||||
#define EBU_NAND_ECC0 0xB8
|
||||
#define EBU_NAND_ECC_AC 0xBC
|
||||
|
||||
/* nand commands */
|
||||
#define NAND_CMD_ALE (1 << 2)
|
||||
#define NAND_CMD_CLE (1 << 3)
|
||||
#define NAND_CMD_CS (1 << 4)
|
||||
#define NAND_WRITE_CMD_RESET 0xff
|
||||
#define NAND_WRITE_CMD (NAND_CMD_CS | NAND_CMD_CLE)
|
||||
#define NAND_WRITE_ADDR (NAND_CMD_CS | NAND_CMD_ALE)
|
||||
#define NAND_WRITE_DATA (NAND_CMD_CS)
|
||||
#define NAND_READ_DATA (NAND_CMD_CS)
|
||||
#define NAND_WAIT_WR_C (1 << 3)
|
||||
#define NAND_WAIT_RD (0x1)
|
||||
|
||||
/* we need to tel the ebu which addr we mapped the nand to */
|
||||
#define ADDSEL1_MASK(x) (x << 4)
|
||||
#define ADDSEL1_REGEN 1
|
||||
|
||||
/* we need to tell the EBU that we have nand attached and set it up properly */
|
||||
#define BUSCON1_SETUP (1 << 22)
|
||||
#define BUSCON1_BCGEN_RES (0x3 << 12)
|
||||
#define BUSCON1_WAITWRC2 (2 << 8)
|
||||
#define BUSCON1_WAITRDC2 (2 << 6)
|
||||
#define BUSCON1_HOLDC1 (1 << 4)
|
||||
#define BUSCON1_RECOVC1 (1 << 2)
|
||||
#define BUSCON1_CMULT4 1
|
||||
|
||||
#define NAND_CON_CE (1 << 20)
|
||||
#define NAND_CON_OUT_CS1 (1 << 10)
|
||||
#define NAND_CON_IN_CS1 (1 << 8)
|
||||
#define NAND_CON_PRE_P (1 << 7)
|
||||
#define NAND_CON_WP_P (1 << 6)
|
||||
#define NAND_CON_SE_P (1 << 5)
|
||||
#define NAND_CON_CS_P (1 << 4)
|
||||
#define NAND_CON_CSMUX (1 << 1)
|
||||
#define NAND_CON_NANDM 1
|
||||
|
||||
static void xway_reset_chip(struct nand_chip *chip)
|
||||
{
|
||||
unsigned long nandaddr = (unsigned long) chip->IO_ADDR_W;
|
||||
unsigned long flags;
|
||||
|
||||
nandaddr &= ~NAND_WRITE_ADDR;
|
||||
nandaddr |= NAND_WRITE_CMD;
|
||||
|
||||
/* finish with a reset */
|
||||
spin_lock_irqsave(&ebu_lock, flags);
|
||||
writeb(NAND_WRITE_CMD_RESET, (void __iomem *) nandaddr);
|
||||
while ((ltq_ebu_r32(EBU_NAND_WAIT) & NAND_WAIT_WR_C) == 0)
|
||||
;
|
||||
spin_unlock_irqrestore(&ebu_lock, flags);
|
||||
}
|
||||
|
||||
static void xway_select_chip(struct mtd_info *mtd, int chip)
|
||||
{
|
||||
|
||||
switch (chip) {
|
||||
case -1:
|
||||
ltq_ebu_w32_mask(NAND_CON_CE, 0, EBU_NAND_CON);
|
||||
ltq_ebu_w32_mask(NAND_CON_NANDM, 0, EBU_NAND_CON);
|
||||
break;
|
||||
case 0:
|
||||
ltq_ebu_w32_mask(0, NAND_CON_NANDM, EBU_NAND_CON);
|
||||
ltq_ebu_w32_mask(0, NAND_CON_CE, EBU_NAND_CON);
|
||||
break;
|
||||
default:
|
||||
BUG();
|
||||
}
|
||||
}
|
||||
|
||||
static void xway_cmd_ctrl(struct mtd_info *mtd, int cmd, unsigned int ctrl)
|
||||
{
|
||||
struct nand_chip *this = mtd->priv;
|
||||
unsigned long nandaddr = (unsigned long) this->IO_ADDR_W;
|
||||
unsigned long flags;
|
||||
|
||||
if (ctrl & NAND_CTRL_CHANGE) {
|
||||
nandaddr &= ~(NAND_WRITE_CMD | NAND_WRITE_ADDR);
|
||||
if (ctrl & NAND_CLE)
|
||||
nandaddr |= NAND_WRITE_CMD;
|
||||
else
|
||||
nandaddr |= NAND_WRITE_ADDR;
|
||||
this->IO_ADDR_W = (void __iomem *) nandaddr;
|
||||
}
|
||||
|
||||
if (cmd != NAND_CMD_NONE) {
|
||||
spin_lock_irqsave(&ebu_lock, flags);
|
||||
writeb(cmd, this->IO_ADDR_W);
|
||||
while ((ltq_ebu_r32(EBU_NAND_WAIT) & NAND_WAIT_WR_C) == 0)
|
||||
;
|
||||
spin_unlock_irqrestore(&ebu_lock, flags);
|
||||
}
|
||||
}
|
||||
|
||||
static int xway_dev_ready(struct mtd_info *mtd)
|
||||
{
|
||||
return ltq_ebu_r32(EBU_NAND_WAIT) & NAND_WAIT_RD;
|
||||
}
|
||||
|
||||
static unsigned char xway_read_byte(struct mtd_info *mtd)
|
||||
{
|
||||
struct nand_chip *this = mtd->priv;
|
||||
unsigned long nandaddr = (unsigned long) this->IO_ADDR_R;
|
||||
unsigned long flags;
|
||||
int ret;
|
||||
|
||||
spin_lock_irqsave(&ebu_lock, flags);
|
||||
ret = ltq_r8((void __iomem *)(nandaddr + NAND_READ_DATA));
|
||||
spin_unlock_irqrestore(&ebu_lock, flags);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int xway_nand_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct nand_chip *this = platform_get_drvdata(pdev);
|
||||
unsigned long nandaddr = (unsigned long) this->IO_ADDR_W;
|
||||
const __be32 *cs = of_get_property(pdev->dev.of_node,
|
||||
"lantiq,cs", NULL);
|
||||
u32 cs_flag = 0;
|
||||
|
||||
/* load our CS from the DT. Either we find a valid 1 or default to 0 */
|
||||
if (cs && (*cs == 1))
|
||||
cs_flag = NAND_CON_IN_CS1 | NAND_CON_OUT_CS1;
|
||||
|
||||
/* setup the EBU to run in NAND mode on our base addr */
|
||||
ltq_ebu_w32(CPHYSADDR(nandaddr)
|
||||
| ADDSEL1_MASK(3) | ADDSEL1_REGEN, EBU_ADDSEL1);
|
||||
|
||||
ltq_ebu_w32(BUSCON1_SETUP | BUSCON1_BCGEN_RES | BUSCON1_WAITWRC2
|
||||
| BUSCON1_WAITRDC2 | BUSCON1_HOLDC1 | BUSCON1_RECOVC1
|
||||
| BUSCON1_CMULT4, LTQ_EBU_BUSCON1);
|
||||
|
||||
ltq_ebu_w32(NAND_CON_NANDM | NAND_CON_CSMUX | NAND_CON_CS_P
|
||||
| NAND_CON_SE_P | NAND_CON_WP_P | NAND_CON_PRE_P
|
||||
| cs_flag, EBU_NAND_CON);
|
||||
|
||||
/* finish with a reset */
|
||||
xway_reset_chip(this);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* allow users to override the partition in DT using the cmdline */
|
||||
static const char *part_probes[] = { "cmdlinepart", "ofpart", NULL };
|
||||
|
||||
static struct platform_nand_data xway_nand_data = {
|
||||
.chip = {
|
||||
.nr_chips = 1,
|
||||
.chip_delay = 30,
|
||||
.part_probe_types = part_probes,
|
||||
},
|
||||
.ctrl = {
|
||||
.probe = xway_nand_probe,
|
||||
.cmd_ctrl = xway_cmd_ctrl,
|
||||
.dev_ready = xway_dev_ready,
|
||||
.select_chip = xway_select_chip,
|
||||
.read_byte = xway_read_byte,
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
* Try to find the node inside the DT. If it is available attach out
|
||||
* platform_nand_data
|
||||
*/
|
||||
static int __init xway_register_nand(void)
|
||||
{
|
||||
struct device_node *node;
|
||||
struct platform_device *pdev;
|
||||
|
||||
node = of_find_compatible_node(NULL, NULL, "lantiq,nand-xway");
|
||||
if (!node)
|
||||
return -ENOENT;
|
||||
pdev = of_find_device_by_node(node);
|
||||
if (!pdev)
|
||||
return -EINVAL;
|
||||
pdev->dev.platform_data = &xway_nand_data;
|
||||
of_node_put(node);
|
||||
return 0;
|
||||
}
|
||||
|
||||
subsys_initcall(xway_register_nand);
|
@ -346,7 +346,6 @@ static int sm_write_sector(struct sm_ftl *ftl,
|
||||
ret = mtd_write_oob(mtd, sm_mkoffset(ftl, zone, block, boffset), &ops);
|
||||
|
||||
/* Now we assume that hardware will catch write bitflip errors */
|
||||
/* If you are paranoid, use CONFIG_MTD_NAND_VERIFY_WRITE */
|
||||
|
||||
if (ret) {
|
||||
dbg("write to block %d at zone %d, failed with error %d",
|
||||
|
@ -6,3 +6,4 @@ obj-$(CONFIG_MTD_TESTS) += mtd_stresstest.o
|
||||
obj-$(CONFIG_MTD_TESTS) += mtd_subpagetest.o
|
||||
obj-$(CONFIG_MTD_TESTS) += mtd_torturetest.o
|
||||
obj-$(CONFIG_MTD_TESTS) += mtd_nandecctest.o
|
||||
obj-$(CONFIG_MTD_TESTS) += mtd_nandbiterrs.o
|
||||
|
460
drivers/mtd/tests/mtd_nandbiterrs.c
Normal file
460
drivers/mtd/tests/mtd_nandbiterrs.c
Normal file
@ -0,0 +1,460 @@
|
||||
/*
|
||||
* Copyright © 2012 NetCommWireless
|
||||
* Iwo Mergler <Iwo.Mergler@netcommwireless.com.au>
|
||||
*
|
||||
* Test for multi-bit error recovery on a NAND page This mostly tests the
|
||||
* ECC controller / driver.
|
||||
*
|
||||
* There are two test modes:
|
||||
*
|
||||
* 0 - artificially inserting bit errors until the ECC fails
|
||||
* This is the default method and fairly quick. It should
|
||||
* be independent of the quality of the FLASH.
|
||||
*
|
||||
* 1 - re-writing the same pattern repeatedly until the ECC fails.
|
||||
* This method relies on the physics of NAND FLASH to eventually
|
||||
* generate '0' bits if '1' has been written sufficient times.
|
||||
* Depending on the NAND, the first bit errors will appear after
|
||||
* 1000 or more writes and then will usually snowball, reaching the
|
||||
* limits of the ECC quickly.
|
||||
*
|
||||
* The test stops after 10000 cycles, should your FLASH be
|
||||
* exceptionally good and not generate bit errors before that. Try
|
||||
* a different page in that case.
|
||||
*
|
||||
* Please note that neither of these tests will significantly 'use up' any
|
||||
* FLASH endurance. Only a maximum of two erase operations will be performed.
|
||||
*
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 as published by
|
||||
* the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program; see the file COPYING. If not, write to the Free Software
|
||||
* Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
*/
|
||||
#include <linux/init.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/moduleparam.h>
|
||||
#include <linux/mtd/mtd.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/mtd/nand.h>
|
||||
#include <linux/slab.h>
|
||||
|
||||
#define msg(FMT, VA...) pr_info("mtd_nandbiterrs: "FMT, ##VA)
|
||||
|
||||
static int dev;
|
||||
module_param(dev, int, S_IRUGO);
|
||||
MODULE_PARM_DESC(dev, "MTD device number to use");
|
||||
|
||||
static unsigned page_offset;
|
||||
module_param(page_offset, uint, S_IRUGO);
|
||||
MODULE_PARM_DESC(page_offset, "Page number relative to dev start");
|
||||
|
||||
static unsigned seed;
|
||||
module_param(seed, uint, S_IRUGO);
|
||||
MODULE_PARM_DESC(seed, "Random seed");
|
||||
|
||||
static int mode;
|
||||
module_param(mode, int, S_IRUGO);
|
||||
MODULE_PARM_DESC(mode, "0=incremental errors, 1=overwrite test");
|
||||
|
||||
static unsigned max_overwrite = 10000;
|
||||
|
||||
static loff_t offset; /* Offset of the page we're using. */
|
||||
static unsigned eraseblock; /* Eraseblock number for our page. */
|
||||
|
||||
/* We assume that the ECC can correct up to a certain number
|
||||
* of biterrors per subpage. */
|
||||
static unsigned subsize; /* Size of subpages */
|
||||
static unsigned subcount; /* Number of subpages per page */
|
||||
|
||||
static struct mtd_info *mtd; /* MTD device */
|
||||
|
||||
static uint8_t *wbuffer; /* One page write / compare buffer */
|
||||
static uint8_t *rbuffer; /* One page read buffer */
|
||||
|
||||
/* 'random' bytes from known offsets */
|
||||
static uint8_t hash(unsigned offset)
|
||||
{
|
||||
unsigned v = offset;
|
||||
unsigned char c;
|
||||
v ^= 0x7f7edfd3;
|
||||
v = v ^ (v >> 3);
|
||||
v = v ^ (v >> 5);
|
||||
v = v ^ (v >> 13);
|
||||
c = v & 0xFF;
|
||||
/* Reverse bits of result. */
|
||||
c = (c & 0x0F) << 4 | (c & 0xF0) >> 4;
|
||||
c = (c & 0x33) << 2 | (c & 0xCC) >> 2;
|
||||
c = (c & 0x55) << 1 | (c & 0xAA) >> 1;
|
||||
return c;
|
||||
}
|
||||
|
||||
static int erase_block(void)
|
||||
{
|
||||
int err;
|
||||
struct erase_info ei;
|
||||
loff_t addr = eraseblock * mtd->erasesize;
|
||||
|
||||
msg("erase_block\n");
|
||||
|
||||
memset(&ei, 0, sizeof(struct erase_info));
|
||||
ei.mtd = mtd;
|
||||
ei.addr = addr;
|
||||
ei.len = mtd->erasesize;
|
||||
|
||||
err = mtd_erase(mtd, &ei);
|
||||
if (err || ei.state == MTD_ERASE_FAILED) {
|
||||
msg("error %d while erasing\n", err);
|
||||
if (!err)
|
||||
err = -EIO;
|
||||
return err;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Writes wbuffer to page */
|
||||
static int write_page(int log)
|
||||
{
|
||||
int err = 0;
|
||||
size_t written;
|
||||
|
||||
if (log)
|
||||
msg("write_page\n");
|
||||
|
||||
err = mtd_write(mtd, offset, mtd->writesize, &written, wbuffer);
|
||||
if (err || written != mtd->writesize) {
|
||||
msg("error: write failed at %#llx\n", (long long)offset);
|
||||
if (!err)
|
||||
err = -EIO;
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
/* Re-writes the data area while leaving the OOB alone. */
|
||||
static int rewrite_page(int log)
|
||||
{
|
||||
int err = 0;
|
||||
struct mtd_oob_ops ops;
|
||||
|
||||
if (log)
|
||||
msg("rewrite page\n");
|
||||
|
||||
ops.mode = MTD_OPS_RAW; /* No ECC */
|
||||
ops.len = mtd->writesize;
|
||||
ops.retlen = 0;
|
||||
ops.ooblen = 0;
|
||||
ops.oobretlen = 0;
|
||||
ops.ooboffs = 0;
|
||||
ops.datbuf = wbuffer;
|
||||
ops.oobbuf = NULL;
|
||||
|
||||
err = mtd_write_oob(mtd, offset, &ops);
|
||||
if (err || ops.retlen != mtd->writesize) {
|
||||
msg("error: write_oob failed (%d)\n", err);
|
||||
if (!err)
|
||||
err = -EIO;
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
/* Reads page into rbuffer. Returns number of corrected bit errors (>=0)
|
||||
* or error (<0) */
|
||||
static int read_page(int log)
|
||||
{
|
||||
int err = 0;
|
||||
size_t read;
|
||||
struct mtd_ecc_stats oldstats;
|
||||
|
||||
if (log)
|
||||
msg("read_page\n");
|
||||
|
||||
/* Saving last mtd stats */
|
||||
memcpy(&oldstats, &mtd->ecc_stats, sizeof(oldstats));
|
||||
|
||||
err = mtd_read(mtd, offset, mtd->writesize, &read, rbuffer);
|
||||
if (err == -EUCLEAN)
|
||||
err = mtd->ecc_stats.corrected - oldstats.corrected;
|
||||
|
||||
if (err < 0 || read != mtd->writesize) {
|
||||
msg("error: read failed at %#llx\n", (long long)offset);
|
||||
if (err >= 0)
|
||||
err = -EIO;
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
/* Verifies rbuffer against random sequence */
|
||||
static int verify_page(int log)
|
||||
{
|
||||
unsigned i, errs = 0;
|
||||
|
||||
if (log)
|
||||
msg("verify_page\n");
|
||||
|
||||
for (i = 0; i < mtd->writesize; i++) {
|
||||
if (rbuffer[i] != hash(i+seed)) {
|
||||
msg("Error: page offset %u, expected %02x, got %02x\n",
|
||||
i, hash(i+seed), rbuffer[i]);
|
||||
errs++;
|
||||
}
|
||||
}
|
||||
|
||||
if (errs)
|
||||
return -EIO;
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
|
||||
#define CBIT(v, n) ((v) & (1 << (n)))
|
||||
#define BCLR(v, n) ((v) = (v) & ~(1 << (n)))
|
||||
|
||||
/* Finds the first '1' bit in wbuffer starting at offset 'byte'
|
||||
* and sets it to '0'. */
|
||||
static int insert_biterror(unsigned byte)
|
||||
{
|
||||
int bit;
|
||||
|
||||
while (byte < mtd->writesize) {
|
||||
for (bit = 7; bit >= 0; bit--) {
|
||||
if (CBIT(wbuffer[byte], bit)) {
|
||||
BCLR(wbuffer[byte], bit);
|
||||
msg("Inserted biterror @ %u/%u\n", byte, bit);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
byte++;
|
||||
}
|
||||
msg("biterror: Failed to find a '1' bit\n");
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
/* Writes 'random' data to page and then introduces deliberate bit
|
||||
* errors into the page, while verifying each step. */
|
||||
static int incremental_errors_test(void)
|
||||
{
|
||||
int err = 0;
|
||||
unsigned i;
|
||||
unsigned errs_per_subpage = 0;
|
||||
|
||||
msg("incremental biterrors test\n");
|
||||
|
||||
for (i = 0; i < mtd->writesize; i++)
|
||||
wbuffer[i] = hash(i+seed);
|
||||
|
||||
err = write_page(1);
|
||||
if (err)
|
||||
goto exit;
|
||||
|
||||
while (1) {
|
||||
|
||||
err = rewrite_page(1);
|
||||
if (err)
|
||||
goto exit;
|
||||
|
||||
err = read_page(1);
|
||||
if (err > 0)
|
||||
msg("Read reported %d corrected bit errors\n", err);
|
||||
if (err < 0) {
|
||||
msg("After %d biterrors per subpage, read reported error %d\n",
|
||||
errs_per_subpage, err);
|
||||
err = 0;
|
||||
goto exit;
|
||||
}
|
||||
|
||||
err = verify_page(1);
|
||||
if (err) {
|
||||
msg("ECC failure, read data is incorrect despite read success\n");
|
||||
goto exit;
|
||||
}
|
||||
|
||||
msg("Successfully corrected %d bit errors per subpage\n",
|
||||
errs_per_subpage);
|
||||
|
||||
for (i = 0; i < subcount; i++) {
|
||||
err = insert_biterror(i * subsize);
|
||||
if (err < 0)
|
||||
goto exit;
|
||||
}
|
||||
errs_per_subpage++;
|
||||
}
|
||||
|
||||
exit:
|
||||
return err;
|
||||
}
|
||||
|
||||
|
||||
/* Writes 'random' data to page and then re-writes that same data repeatedly.
|
||||
This eventually develops bit errors (bits written as '1' will slowly become
|
||||
'0'), which are corrected as far as the ECC is capable of. */
|
||||
static int overwrite_test(void)
|
||||
{
|
||||
int err = 0;
|
||||
unsigned i;
|
||||
unsigned max_corrected = 0;
|
||||
unsigned opno = 0;
|
||||
/* We don't expect more than this many correctable bit errors per
|
||||
* page. */
|
||||
#define MAXBITS 512
|
||||
static unsigned bitstats[MAXBITS]; /* bit error histogram. */
|
||||
|
||||
memset(bitstats, 0, sizeof(bitstats));
|
||||
|
||||
msg("overwrite biterrors test\n");
|
||||
|
||||
for (i = 0; i < mtd->writesize; i++)
|
||||
wbuffer[i] = hash(i+seed);
|
||||
|
||||
err = write_page(1);
|
||||
if (err)
|
||||
goto exit;
|
||||
|
||||
while (opno < max_overwrite) {
|
||||
|
||||
err = rewrite_page(0);
|
||||
if (err)
|
||||
break;
|
||||
|
||||
err = read_page(0);
|
||||
if (err >= 0) {
|
||||
if (err >= MAXBITS) {
|
||||
msg("Implausible number of bit errors corrected\n");
|
||||
err = -EIO;
|
||||
break;
|
||||
}
|
||||
bitstats[err]++;
|
||||
if (err > max_corrected) {
|
||||
max_corrected = err;
|
||||
msg("Read reported %d corrected bit errors\n",
|
||||
err);
|
||||
}
|
||||
} else { /* err < 0 */
|
||||
msg("Read reported error %d\n", err);
|
||||
err = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
err = verify_page(0);
|
||||
if (err) {
|
||||
bitstats[max_corrected] = opno;
|
||||
msg("ECC failure, read data is incorrect despite read success\n");
|
||||
break;
|
||||
}
|
||||
|
||||
opno++;
|
||||
}
|
||||
|
||||
/* At this point bitstats[0] contains the number of ops with no bit
|
||||
* errors, bitstats[1] the number of ops with 1 bit error, etc. */
|
||||
msg("Bit error histogram (%d operations total):\n", opno);
|
||||
for (i = 0; i < max_corrected; i++)
|
||||
msg("Page reads with %3d corrected bit errors: %d\n",
|
||||
i, bitstats[i]);
|
||||
|
||||
exit:
|
||||
return err;
|
||||
}
|
||||
|
||||
static int __init mtd_nandbiterrs_init(void)
|
||||
{
|
||||
int err = 0;
|
||||
|
||||
msg("\n");
|
||||
msg("==================================================\n");
|
||||
msg("MTD device: %d\n", dev);
|
||||
|
||||
mtd = get_mtd_device(NULL, dev);
|
||||
if (IS_ERR(mtd)) {
|
||||
err = PTR_ERR(mtd);
|
||||
msg("error: cannot get MTD device\n");
|
||||
goto exit_mtddev;
|
||||
}
|
||||
|
||||
if (mtd->type != MTD_NANDFLASH) {
|
||||
msg("this test requires NAND flash\n");
|
||||
err = -ENODEV;
|
||||
goto exit_nand;
|
||||
}
|
||||
|
||||
msg("MTD device size %llu, eraseblock=%u, page=%u, oob=%u\n",
|
||||
(unsigned long long)mtd->size, mtd->erasesize,
|
||||
mtd->writesize, mtd->oobsize);
|
||||
|
||||
subsize = mtd->writesize >> mtd->subpage_sft;
|
||||
subcount = mtd->writesize / subsize;
|
||||
|
||||
msg("Device uses %d subpages of %d bytes\n", subcount, subsize);
|
||||
|
||||
offset = page_offset * mtd->writesize;
|
||||
eraseblock = mtd_div_by_eb(offset, mtd);
|
||||
|
||||
msg("Using page=%u, offset=%llu, eraseblock=%u\n",
|
||||
page_offset, offset, eraseblock);
|
||||
|
||||
wbuffer = kmalloc(mtd->writesize, GFP_KERNEL);
|
||||
if (!wbuffer) {
|
||||
err = -ENOMEM;
|
||||
goto exit_wbuffer;
|
||||
}
|
||||
|
||||
rbuffer = kmalloc(mtd->writesize, GFP_KERNEL);
|
||||
if (!rbuffer) {
|
||||
err = -ENOMEM;
|
||||
goto exit_rbuffer;
|
||||
}
|
||||
|
||||
err = erase_block();
|
||||
if (err)
|
||||
goto exit_error;
|
||||
|
||||
if (mode == 0)
|
||||
err = incremental_errors_test();
|
||||
else
|
||||
err = overwrite_test();
|
||||
|
||||
if (err)
|
||||
goto exit_error;
|
||||
|
||||
/* We leave the block un-erased in case of test failure. */
|
||||
err = erase_block();
|
||||
if (err)
|
||||
goto exit_error;
|
||||
|
||||
err = -EIO;
|
||||
msg("finished successfully.\n");
|
||||
msg("==================================================\n");
|
||||
|
||||
exit_error:
|
||||
kfree(rbuffer);
|
||||
exit_rbuffer:
|
||||
kfree(wbuffer);
|
||||
exit_wbuffer:
|
||||
/* Nothing */
|
||||
exit_nand:
|
||||
put_mtd_device(mtd);
|
||||
exit_mtddev:
|
||||
return err;
|
||||
}
|
||||
|
||||
static void __exit mtd_nandbiterrs_exit(void)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
module_init(mtd_nandbiterrs_init);
|
||||
module_exit(mtd_nandbiterrs_exit);
|
||||
|
||||
MODULE_DESCRIPTION("NAND bit error recovery test");
|
||||
MODULE_AUTHOR("Iwo Mergler");
|
||||
MODULE_LICENSE("GPL");
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user