mirror of
https://github.com/adulau/aha.git
synced 2024-12-27 19:26:25 +00:00
Merge git://git.infradead.org/mtd-2.6
* git://git.infradead.org/mtd-2.6: (58 commits) mtd: jedec_probe: add PSD4256G6V id mtd: OneNand support for Nomadik 8815 SoC (on NHK8815 board) mtd: nand: driver for Nomadik 8815 SoC (on NHK8815 board) m25p80: Add Spansion S25FL129P serial flashes jffs2: Use SLAB_HWCACHE_ALIGN for jffs2_raw_{dirent,inode} slabs mtd: sh_flctl: register sh_flctl using platform_driver_probe() mtd: nand: txx9ndfmc: transfer 512 byte at a time if possible mtd: nand: fix tmio_nand ecc correction mtd: nand: add __nand_correct_data helper function mtd: cfi_cmdset_0002: add 0xFF intolerance for M29W128G mtd: inftl: fix fold chain block number mtd: jedec: fix compilation problem with I28F640C3B definition mtd: nand: fix ECC Correction bug for SMC ordering for NDFC driver mtd: ofpart: Check availability of reg property instead of name property driver/Makefile: Initialize "mtd" and "spi" before "net" mtd: omap: adding DMA mode support in nand prefetch/post-write mtd: omap: add support for nand prefetch-read and post-write mtd: add nand support for w90p910 (v2) mtd: maps: add mtd-ram support to physmap_of mtd: pxa3xx_nand: add single-bit error corrections reporting ...
This commit is contained in:
commit
a7c367b95a
64 changed files with 2798 additions and 254 deletions
|
@ -1,18 +1,19 @@
|
||||||
CFI or JEDEC memory-mapped NOR flash
|
CFI or JEDEC memory-mapped NOR flash, MTD-RAM (NVRAM...)
|
||||||
|
|
||||||
Flash chips (Memory Technology Devices) are often used for solid state
|
Flash chips (Memory Technology Devices) are often used for solid state
|
||||||
file systems on embedded devices.
|
file systems on embedded devices.
|
||||||
|
|
||||||
- compatible : should contain the specific model of flash chip(s)
|
- compatible : should contain the specific model of mtd chip(s)
|
||||||
used, if known, followed by either "cfi-flash" or "jedec-flash"
|
used, if known, followed by either "cfi-flash", "jedec-flash"
|
||||||
- reg : Address range(s) of the flash chip(s)
|
or "mtd-ram".
|
||||||
|
- reg : Address range(s) of the mtd chip(s)
|
||||||
It's possible to (optionally) define multiple "reg" tuples so that
|
It's possible to (optionally) define multiple "reg" tuples so that
|
||||||
non-identical NOR chips can be described in one flash node.
|
non-identical chips can be described in one node.
|
||||||
- bank-width : Width (in bytes) of the flash bank. Equal to the
|
- bank-width : Width (in bytes) of the bank. Equal to the
|
||||||
device width times the number of interleaved chips.
|
device width times the number of interleaved chips.
|
||||||
- device-width : (optional) Width of a single flash chip. If
|
- device-width : (optional) Width of a single mtd chip. If
|
||||||
omitted, assumed to be equal to 'bank-width'.
|
omitted, assumed to be equal to 'bank-width'.
|
||||||
- #address-cells, #size-cells : Must be present if the flash has
|
- #address-cells, #size-cells : Must be present if the device has
|
||||||
sub-nodes representing partitions (see below). In this case
|
sub-nodes representing partitions (see below). In this case
|
||||||
both #address-cells and #size-cells must be equal to 1.
|
both #address-cells and #size-cells must be equal to 1.
|
||||||
|
|
||||||
|
@ -22,24 +23,24 @@ are defined:
|
||||||
- vendor-id : Contains the flash chip's vendor id (1 byte).
|
- vendor-id : Contains the flash chip's vendor id (1 byte).
|
||||||
- device-id : Contains the flash chip's device id (1 byte).
|
- device-id : Contains the flash chip's device id (1 byte).
|
||||||
|
|
||||||
In addition to the information on the flash bank itself, the
|
In addition to the information on the mtd bank itself, the
|
||||||
device tree may optionally contain additional information
|
device tree may optionally contain additional information
|
||||||
describing partitions of the flash address space. This can be
|
describing partitions of the address space. This can be
|
||||||
used on platforms which have strong conventions about which
|
used on platforms which have strong conventions about which
|
||||||
portions of the flash are used for what purposes, but which don't
|
portions of a flash are used for what purposes, but which don't
|
||||||
use an on-flash partition table such as RedBoot.
|
use an on-flash partition table such as RedBoot.
|
||||||
|
|
||||||
Each partition is represented as a sub-node of the flash device.
|
Each partition is represented as a sub-node of the mtd device.
|
||||||
Each node's name represents the name of the corresponding
|
Each node's name represents the name of the corresponding
|
||||||
partition of the flash device.
|
partition of the mtd device.
|
||||||
|
|
||||||
Flash partitions
|
Flash partitions
|
||||||
- reg : The partition's offset and size within the flash bank.
|
- reg : The partition's offset and size within the mtd bank.
|
||||||
- label : (optional) The label / name for this flash partition.
|
- label : (optional) The label / name for this partition.
|
||||||
If omitted, the label is taken from the node name (excluding
|
If omitted, the label is taken from the node name (excluding
|
||||||
the unit address).
|
the unit address).
|
||||||
- read-only : (optional) This parameter, if present, is a hint to
|
- read-only : (optional) This parameter, if present, is a hint to
|
||||||
Linux that this flash partition should only be mounted
|
Linux that this partition should only be mounted
|
||||||
read-only. This is usually used for flash partitions
|
read-only. This is usually used for flash partitions
|
||||||
containing early-boot firmware images or data which should not
|
containing early-boot firmware images or data which should not
|
||||||
be clobbered.
|
be clobbered.
|
||||||
|
@ -78,3 +79,12 @@ Here an example with multiple "reg" tuples:
|
||||||
reg = <0 0x04000000>;
|
reg = <0 0x04000000>;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
An example using SRAM:
|
||||||
|
|
||||||
|
sram@2,0 {
|
||||||
|
compatible = "samsung,k6f1616u6a", "mtd-ram";
|
||||||
|
reg = <2 0 0x00200000>;
|
||||||
|
bank-width = <2>;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
|
@ -498,7 +498,7 @@ CONFIG_MTD_CFI_I2=y
|
||||||
# CONFIG_MTD_DOC2001PLUS is not set
|
# CONFIG_MTD_DOC2001PLUS is not set
|
||||||
CONFIG_MTD_NAND=y
|
CONFIG_MTD_NAND=y
|
||||||
CONFIG_MTD_NAND_VERIFY_WRITE=y
|
CONFIG_MTD_NAND_VERIFY_WRITE=y
|
||||||
# CONFIG_MTD_NAND_ECC_SMC is not set
|
CONFIG_MTD_NAND_ECC_SMC=y
|
||||||
# CONFIG_MTD_NAND_MUSEUM_IDS is not set
|
# CONFIG_MTD_NAND_MUSEUM_IDS is not set
|
||||||
# CONFIG_MTD_NAND_GPIO is not set
|
# CONFIG_MTD_NAND_GPIO is not set
|
||||||
CONFIG_MTD_NAND_IDS=y
|
CONFIG_MTD_NAND_IDS=y
|
||||||
|
|
|
@ -16,12 +16,164 @@
|
||||||
#include <linux/amba/bus.h>
|
#include <linux/amba/bus.h>
|
||||||
#include <linux/interrupt.h>
|
#include <linux/interrupt.h>
|
||||||
#include <linux/gpio.h>
|
#include <linux/gpio.h>
|
||||||
|
#include <linux/mtd/mtd.h>
|
||||||
|
#include <linux/mtd/nand.h>
|
||||||
|
#include <linux/mtd/partitions.h>
|
||||||
|
#include <linux/io.h>
|
||||||
|
#include <asm/sizes.h>
|
||||||
#include <asm/mach-types.h>
|
#include <asm/mach-types.h>
|
||||||
#include <asm/mach/arch.h>
|
#include <asm/mach/arch.h>
|
||||||
#include <asm/mach/irq.h>
|
#include <asm/mach/irq.h>
|
||||||
|
#include <asm/mach/flash.h>
|
||||||
#include <mach/setup.h>
|
#include <mach/setup.h>
|
||||||
|
#include <mach/nand.h>
|
||||||
|
#include <mach/fsmc.h>
|
||||||
#include "clock.h"
|
#include "clock.h"
|
||||||
|
|
||||||
|
/* These adresses span 16MB, so use three individual pages */
|
||||||
|
static struct resource nhk8815_nand_resources[] = {
|
||||||
|
{
|
||||||
|
.name = "nand_addr",
|
||||||
|
.start = NAND_IO_ADDR,
|
||||||
|
.end = NAND_IO_ADDR + 0xfff,
|
||||||
|
.flags = IORESOURCE_MEM,
|
||||||
|
}, {
|
||||||
|
.name = "nand_cmd",
|
||||||
|
.start = NAND_IO_CMD,
|
||||||
|
.end = NAND_IO_CMD + 0xfff,
|
||||||
|
.flags = IORESOURCE_MEM,
|
||||||
|
}, {
|
||||||
|
.name = "nand_data",
|
||||||
|
.start = NAND_IO_DATA,
|
||||||
|
.end = NAND_IO_DATA + 0xfff,
|
||||||
|
.flags = IORESOURCE_MEM,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
static int nhk8815_nand_init(void)
|
||||||
|
{
|
||||||
|
/* FSMC setup for nand chip select (8-bit nand in 8815NHK) */
|
||||||
|
writel(0x0000000E, FSMC_PCR(0));
|
||||||
|
writel(0x000D0A00, FSMC_PMEM(0));
|
||||||
|
writel(0x00100A00, FSMC_PATT(0));
|
||||||
|
|
||||||
|
/* enable access to the chip select area */
|
||||||
|
writel(readl(FSMC_PCR(0)) | 0x04, FSMC_PCR(0));
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* These partitions are the same as those used in the 2.6.20 release
|
||||||
|
* shipped by the vendor; the first two partitions are mandated
|
||||||
|
* by the boot ROM, and the bootloader area is somehow oversized...
|
||||||
|
*/
|
||||||
|
static struct mtd_partition nhk8815_partitions[] = {
|
||||||
|
{
|
||||||
|
.name = "X-Loader(NAND)",
|
||||||
|
.offset = 0,
|
||||||
|
.size = SZ_256K,
|
||||||
|
}, {
|
||||||
|
.name = "MemInit(NAND)",
|
||||||
|
.offset = MTDPART_OFS_APPEND,
|
||||||
|
.size = SZ_256K,
|
||||||
|
}, {
|
||||||
|
.name = "BootLoader(NAND)",
|
||||||
|
.offset = MTDPART_OFS_APPEND,
|
||||||
|
.size = SZ_2M,
|
||||||
|
}, {
|
||||||
|
.name = "Kernel zImage(NAND)",
|
||||||
|
.offset = MTDPART_OFS_APPEND,
|
||||||
|
.size = 3 * SZ_1M,
|
||||||
|
}, {
|
||||||
|
.name = "Root Filesystem(NAND)",
|
||||||
|
.offset = MTDPART_OFS_APPEND,
|
||||||
|
.size = 22 * SZ_1M,
|
||||||
|
}, {
|
||||||
|
.name = "User Filesystem(NAND)",
|
||||||
|
.offset = MTDPART_OFS_APPEND,
|
||||||
|
.size = MTDPART_SIZ_FULL,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
static struct nomadik_nand_platform_data nhk8815_nand_data = {
|
||||||
|
.parts = nhk8815_partitions,
|
||||||
|
.nparts = ARRAY_SIZE(nhk8815_partitions),
|
||||||
|
.options = NAND_COPYBACK | NAND_CACHEPRG | NAND_NO_PADDING \
|
||||||
|
| NAND_NO_READRDY | NAND_NO_AUTOINCR,
|
||||||
|
.init = nhk8815_nand_init,
|
||||||
|
};
|
||||||
|
|
||||||
|
static struct platform_device nhk8815_nand_device = {
|
||||||
|
.name = "nomadik_nand",
|
||||||
|
.dev = {
|
||||||
|
.platform_data = &nhk8815_nand_data,
|
||||||
|
},
|
||||||
|
.resource = nhk8815_nand_resources,
|
||||||
|
.num_resources = ARRAY_SIZE(nhk8815_nand_resources),
|
||||||
|
};
|
||||||
|
|
||||||
|
/* These are the partitions for the OneNand device, different from above */
|
||||||
|
static struct mtd_partition nhk8815_onenand_partitions[] = {
|
||||||
|
{
|
||||||
|
.name = "X-Loader(OneNAND)",
|
||||||
|
.offset = 0,
|
||||||
|
.size = SZ_256K,
|
||||||
|
}, {
|
||||||
|
.name = "MemInit(OneNAND)",
|
||||||
|
.offset = MTDPART_OFS_APPEND,
|
||||||
|
.size = SZ_256K,
|
||||||
|
}, {
|
||||||
|
.name = "BootLoader(OneNAND)",
|
||||||
|
.offset = MTDPART_OFS_APPEND,
|
||||||
|
.size = SZ_2M-SZ_256K,
|
||||||
|
}, {
|
||||||
|
.name = "SysImage(OneNAND)",
|
||||||
|
.offset = MTDPART_OFS_APPEND,
|
||||||
|
.size = 4 * SZ_1M,
|
||||||
|
}, {
|
||||||
|
.name = "Root Filesystem(OneNAND)",
|
||||||
|
.offset = MTDPART_OFS_APPEND,
|
||||||
|
.size = 22 * SZ_1M,
|
||||||
|
}, {
|
||||||
|
.name = "User Filesystem(OneNAND)",
|
||||||
|
.offset = MTDPART_OFS_APPEND,
|
||||||
|
.size = MTDPART_SIZ_FULL,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
static struct flash_platform_data nhk8815_onenand_data = {
|
||||||
|
.parts = nhk8815_onenand_partitions,
|
||||||
|
.nr_parts = ARRAY_SIZE(nhk8815_onenand_partitions),
|
||||||
|
};
|
||||||
|
|
||||||
|
static struct resource nhk8815_onenand_resource[] = {
|
||||||
|
{
|
||||||
|
.start = 0x30000000,
|
||||||
|
.end = 0x30000000 + SZ_128K - 1,
|
||||||
|
.flags = IORESOURCE_MEM,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
static struct platform_device nhk8815_onenand_device = {
|
||||||
|
.name = "onenand",
|
||||||
|
.id = -1,
|
||||||
|
.dev = {
|
||||||
|
.platform_data = &nhk8815_onenand_data,
|
||||||
|
},
|
||||||
|
.resource = nhk8815_onenand_resource,
|
||||||
|
.num_resources = ARRAY_SIZE(nhk8815_onenand_resource),
|
||||||
|
};
|
||||||
|
|
||||||
|
static void __init nhk8815_onenand_init(void)
|
||||||
|
{
|
||||||
|
#ifdef CONFIG_ONENAND
|
||||||
|
/* Set up SMCS0 for OneNand */
|
||||||
|
writel(0x000030db, FSMC_BCR0);
|
||||||
|
writel(0x02100551, FSMC_BTR0);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
#define __MEM_4K_RESOURCE(x) \
|
#define __MEM_4K_RESOURCE(x) \
|
||||||
.res = {.start = (x), .end = (x) + SZ_4K - 1, .flags = IORESOURCE_MEM}
|
.res = {.start = (x), .end = (x) + SZ_4K - 1, .flags = IORESOURCE_MEM}
|
||||||
|
|
||||||
|
@ -81,6 +233,8 @@ static int __init nhk8815_eth_init(void)
|
||||||
device_initcall(nhk8815_eth_init);
|
device_initcall(nhk8815_eth_init);
|
||||||
|
|
||||||
static struct platform_device *nhk8815_platform_devices[] __initdata = {
|
static struct platform_device *nhk8815_platform_devices[] __initdata = {
|
||||||
|
&nhk8815_nand_device,
|
||||||
|
&nhk8815_onenand_device,
|
||||||
&nhk8815_eth_device,
|
&nhk8815_eth_device,
|
||||||
/* will add more devices */
|
/* will add more devices */
|
||||||
};
|
};
|
||||||
|
@ -90,6 +244,7 @@ static void __init nhk8815_platform_init(void)
|
||||||
int i;
|
int i;
|
||||||
|
|
||||||
cpu8815_platform_init();
|
cpu8815_platform_init();
|
||||||
|
nhk8815_onenand_init();
|
||||||
platform_add_devices(nhk8815_platform_devices,
|
platform_add_devices(nhk8815_platform_devices,
|
||||||
ARRAY_SIZE(nhk8815_platform_devices));
|
ARRAY_SIZE(nhk8815_platform_devices));
|
||||||
|
|
||||||
|
|
29
arch/arm/mach-nomadik/include/mach/fsmc.h
Normal file
29
arch/arm/mach-nomadik/include/mach/fsmc.h
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
|
||||||
|
/* Definitions for the Nomadik FSMC "Flexible Static Memory controller" */
|
||||||
|
|
||||||
|
#ifndef __ASM_ARCH_FSMC_H
|
||||||
|
#define __ASM_ARCH_FSMC_H
|
||||||
|
|
||||||
|
#include <mach/hardware.h>
|
||||||
|
/*
|
||||||
|
* Register list
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* bus control reg. and bus timing reg. for CS0..CS3 */
|
||||||
|
#define FSMC_BCR(x) (NOMADIK_FSMC_VA + (x << 3))
|
||||||
|
#define FSMC_BTR(x) (NOMADIK_FSMC_VA + (x << 3) + 0x04)
|
||||||
|
|
||||||
|
/* PC-card and NAND:
|
||||||
|
* PCR = control register
|
||||||
|
* PMEM = memory timing
|
||||||
|
* PATT = attribute timing
|
||||||
|
* PIO = I/O timing
|
||||||
|
* PECCR = ECC result
|
||||||
|
*/
|
||||||
|
#define FSMC_PCR(x) (NOMADIK_FSMC_VA + ((2 + x) << 5) + 0x00)
|
||||||
|
#define FSMC_PMEM(x) (NOMADIK_FSMC_VA + ((2 + x) << 5) + 0x08)
|
||||||
|
#define FSMC_PATT(x) (NOMADIK_FSMC_VA + ((2 + x) << 5) + 0x0c)
|
||||||
|
#define FSMC_PIO(x) (NOMADIK_FSMC_VA + ((2 + x) << 5) + 0x10)
|
||||||
|
#define FSMC_PECCR(x) (NOMADIK_FSMC_VA + ((2 + x) << 5) + 0x14)
|
||||||
|
|
||||||
|
#endif /* __ASM_ARCH_FSMC_H */
|
16
arch/arm/mach-nomadik/include/mach/nand.h
Normal file
16
arch/arm/mach-nomadik/include/mach/nand.h
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
#ifndef __ASM_ARCH_NAND_H
|
||||||
|
#define __ASM_ARCH_NAND_H
|
||||||
|
|
||||||
|
struct nomadik_nand_platform_data {
|
||||||
|
struct mtd_partition *parts;
|
||||||
|
int nparts;
|
||||||
|
int options;
|
||||||
|
int (*init) (void);
|
||||||
|
int (*exit) (void);
|
||||||
|
};
|
||||||
|
|
||||||
|
#define NAND_IO_DATA 0x40000000
|
||||||
|
#define NAND_IO_CMD 0x40800000
|
||||||
|
#define NAND_IO_ADDR 0x41000000
|
||||||
|
|
||||||
|
#endif /* __ASM_ARCH_NAND_H */
|
|
@ -87,7 +87,7 @@ static struct mtd_partition apollon_partitions[] = {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
static struct flash_platform_data apollon_flash_data = {
|
static struct onenand_platform_data apollon_flash_data = {
|
||||||
.parts = apollon_partitions,
|
.parts = apollon_partitions,
|
||||||
.nr_parts = ARRAY_SIZE(apollon_partitions),
|
.nr_parts = ARRAY_SIZE(apollon_partitions),
|
||||||
};
|
};
|
||||||
|
@ -99,7 +99,7 @@ static struct resource apollon_flash_resource[] = {
|
||||||
};
|
};
|
||||||
|
|
||||||
static struct platform_device apollon_onenand_device = {
|
static struct platform_device apollon_onenand_device = {
|
||||||
.name = "onenand",
|
.name = "onenand-flash",
|
||||||
.id = -1,
|
.id = -1,
|
||||||
.dev = {
|
.dev = {
|
||||||
.platform_data = &apollon_flash_data,
|
.platform_data = &apollon_flash_data,
|
||||||
|
|
|
@ -57,6 +57,11 @@
|
||||||
#define GPMC_CHUNK_SHIFT 24 /* 16 MB */
|
#define GPMC_CHUNK_SHIFT 24 /* 16 MB */
|
||||||
#define GPMC_SECTION_SHIFT 28 /* 128 MB */
|
#define GPMC_SECTION_SHIFT 28 /* 128 MB */
|
||||||
|
|
||||||
|
#define PREFETCH_FIFOTHRESHOLD (0x40 << 8)
|
||||||
|
#define CS_NUM_SHIFT 24
|
||||||
|
#define ENABLE_PREFETCH (0x1 << 7)
|
||||||
|
#define DMA_MPU_MODE 2
|
||||||
|
|
||||||
static struct resource gpmc_mem_root;
|
static struct resource gpmc_mem_root;
|
||||||
static struct resource gpmc_cs_mem[GPMC_CS_NUM];
|
static struct resource gpmc_cs_mem[GPMC_CS_NUM];
|
||||||
static DEFINE_SPINLOCK(gpmc_mem_lock);
|
static DEFINE_SPINLOCK(gpmc_mem_lock);
|
||||||
|
@ -386,6 +391,63 @@ void gpmc_cs_free(int cs)
|
||||||
}
|
}
|
||||||
EXPORT_SYMBOL(gpmc_cs_free);
|
EXPORT_SYMBOL(gpmc_cs_free);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* gpmc_prefetch_enable - configures and starts prefetch transfer
|
||||||
|
* @cs: nand cs (chip select) number
|
||||||
|
* @dma_mode: dma mode enable (1) or disable (0)
|
||||||
|
* @u32_count: number of bytes to be transferred
|
||||||
|
* @is_write: prefetch read(0) or write post(1) mode
|
||||||
|
*/
|
||||||
|
int gpmc_prefetch_enable(int cs, int dma_mode,
|
||||||
|
unsigned int u32_count, int is_write)
|
||||||
|
{
|
||||||
|
uint32_t prefetch_config1;
|
||||||
|
|
||||||
|
if (!(gpmc_read_reg(GPMC_PREFETCH_CONTROL))) {
|
||||||
|
/* Set the amount of bytes to be prefetched */
|
||||||
|
gpmc_write_reg(GPMC_PREFETCH_CONFIG2, u32_count);
|
||||||
|
|
||||||
|
/* Set dma/mpu mode, the prefetch read / post write and
|
||||||
|
* enable the engine. Set which cs is has requested for.
|
||||||
|
*/
|
||||||
|
prefetch_config1 = ((cs << CS_NUM_SHIFT) |
|
||||||
|
PREFETCH_FIFOTHRESHOLD |
|
||||||
|
ENABLE_PREFETCH |
|
||||||
|
(dma_mode << DMA_MPU_MODE) |
|
||||||
|
(0x1 & is_write));
|
||||||
|
gpmc_write_reg(GPMC_PREFETCH_CONFIG1, prefetch_config1);
|
||||||
|
} else {
|
||||||
|
return -EBUSY;
|
||||||
|
}
|
||||||
|
/* Start the prefetch engine */
|
||||||
|
gpmc_write_reg(GPMC_PREFETCH_CONTROL, 0x1);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
EXPORT_SYMBOL(gpmc_prefetch_enable);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* gpmc_prefetch_reset - disables and stops the prefetch engine
|
||||||
|
*/
|
||||||
|
void gpmc_prefetch_reset(void)
|
||||||
|
{
|
||||||
|
/* Stop the PFPW engine */
|
||||||
|
gpmc_write_reg(GPMC_PREFETCH_CONTROL, 0x0);
|
||||||
|
|
||||||
|
/* Reset/disable the PFPW engine */
|
||||||
|
gpmc_write_reg(GPMC_PREFETCH_CONFIG1, 0x0);
|
||||||
|
}
|
||||||
|
EXPORT_SYMBOL(gpmc_prefetch_reset);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* gpmc_prefetch_status - reads prefetch status of engine
|
||||||
|
*/
|
||||||
|
int gpmc_prefetch_status(void)
|
||||||
|
{
|
||||||
|
return gpmc_read_reg(GPMC_PREFETCH_STATUS);
|
||||||
|
}
|
||||||
|
EXPORT_SYMBOL(gpmc_prefetch_status);
|
||||||
|
|
||||||
static void __init gpmc_mem_init(void)
|
static void __init gpmc_mem_init(void)
|
||||||
{
|
{
|
||||||
int cs;
|
int cs;
|
||||||
|
@ -452,6 +514,5 @@ void __init gpmc_init(void)
|
||||||
l &= 0x03 << 3;
|
l &= 0x03 << 3;
|
||||||
l |= (0x02 << 3) | (1 << 0);
|
l |= (0x02 << 3) | (1 << 0);
|
||||||
gpmc_write_reg(GPMC_SYSCONFIG, l);
|
gpmc_write_reg(GPMC_SYSCONFIG, l);
|
||||||
|
|
||||||
gpmc_mem_init();
|
gpmc_mem_init();
|
||||||
}
|
}
|
||||||
|
|
|
@ -103,6 +103,10 @@ extern int gpmc_cs_request(int cs, unsigned long size, unsigned long *base);
|
||||||
extern void gpmc_cs_free(int cs);
|
extern void gpmc_cs_free(int cs);
|
||||||
extern int gpmc_cs_set_reserved(int cs, int reserved);
|
extern int gpmc_cs_set_reserved(int cs, int reserved);
|
||||||
extern int gpmc_cs_reserved(int cs);
|
extern int gpmc_cs_reserved(int cs);
|
||||||
|
extern int gpmc_prefetch_enable(int cs, int dma_mode,
|
||||||
|
unsigned int u32_count, int is_write);
|
||||||
|
extern void gpmc_prefetch_reset(void);
|
||||||
|
extern int gpmc_prefetch_status(void);
|
||||||
extern void __init gpmc_init(void);
|
extern void __init gpmc_init(void);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -43,6 +43,8 @@ obj-y += macintosh/
|
||||||
obj-$(CONFIG_IDE) += ide/
|
obj-$(CONFIG_IDE) += ide/
|
||||||
obj-$(CONFIG_SCSI) += scsi/
|
obj-$(CONFIG_SCSI) += scsi/
|
||||||
obj-$(CONFIG_ATA) += ata/
|
obj-$(CONFIG_ATA) += ata/
|
||||||
|
obj-$(CONFIG_MTD) += mtd/
|
||||||
|
obj-$(CONFIG_SPI) += spi/
|
||||||
obj-y += net/
|
obj-y += net/
|
||||||
obj-$(CONFIG_ATM) += atm/
|
obj-$(CONFIG_ATM) += atm/
|
||||||
obj-$(CONFIG_FUSION) += message/
|
obj-$(CONFIG_FUSION) += message/
|
||||||
|
@ -51,8 +53,6 @@ obj-y += ieee1394/
|
||||||
obj-$(CONFIG_UIO) += uio/
|
obj-$(CONFIG_UIO) += uio/
|
||||||
obj-y += cdrom/
|
obj-y += cdrom/
|
||||||
obj-y += auxdisplay/
|
obj-y += auxdisplay/
|
||||||
obj-$(CONFIG_MTD) += mtd/
|
|
||||||
obj-$(CONFIG_SPI) += spi/
|
|
||||||
obj-$(CONFIG_PCCARD) += pcmcia/
|
obj-$(CONFIG_PCCARD) += pcmcia/
|
||||||
obj-$(CONFIG_DIO) += dio/
|
obj-$(CONFIG_DIO) += dio/
|
||||||
obj-$(CONFIG_SBUS) += sbus/
|
obj-$(CONFIG_SBUS) += sbus/
|
||||||
|
|
|
@ -25,6 +25,14 @@ config MTD_DEBUG_VERBOSE
|
||||||
help
|
help
|
||||||
Determines the verbosity level of the MTD debugging messages.
|
Determines the verbosity level of the MTD debugging messages.
|
||||||
|
|
||||||
|
config MTD_TESTS
|
||||||
|
tristate "MTD tests support"
|
||||||
|
depends on m
|
||||||
|
help
|
||||||
|
This option includes various MTD tests into compilation. The tests
|
||||||
|
should normally be compiled as kernel modules. The modules perform
|
||||||
|
various checks and verifications when loaded.
|
||||||
|
|
||||||
config MTD_CONCAT
|
config MTD_CONCAT
|
||||||
tristate "MTD concatenating support"
|
tristate "MTD concatenating support"
|
||||||
help
|
help
|
||||||
|
@ -45,14 +53,6 @@ config MTD_PARTITIONS
|
||||||
devices. Partitioning on NFTL 'devices' is a different - that's the
|
devices. Partitioning on NFTL 'devices' is a different - that's the
|
||||||
'normal' form of partitioning used on a block device.
|
'normal' form of partitioning used on a block device.
|
||||||
|
|
||||||
config MTD_TESTS
|
|
||||||
tristate "MTD tests support"
|
|
||||||
depends on m
|
|
||||||
help
|
|
||||||
This option includes various MTD tests into compilation. The tests
|
|
||||||
should normally be compiled as kernel modules. The modules perform
|
|
||||||
various checks and verifications when loaded.
|
|
||||||
|
|
||||||
config MTD_REDBOOT_PARTS
|
config MTD_REDBOOT_PARTS
|
||||||
tristate "RedBoot partition table parsing"
|
tristate "RedBoot partition table parsing"
|
||||||
depends on MTD_PARTITIONS
|
depends on MTD_PARTITIONS
|
||||||
|
|
|
@ -239,7 +239,7 @@ static int parse_afs_partitions(struct mtd_info *mtd,
|
||||||
parts[idx].offset = img_ptr;
|
parts[idx].offset = img_ptr;
|
||||||
parts[idx].mask_flags = 0;
|
parts[idx].mask_flags = 0;
|
||||||
|
|
||||||
printk(" mtd%d: at 0x%08x, %5dKB, %8u, %s\n",
|
printk(" mtd%d: at 0x%08x, %5lluKiB, %8u, %s\n",
|
||||||
idx, img_ptr, parts[idx].size / 1024,
|
idx, img_ptr, parts[idx].size / 1024,
|
||||||
iis.imageNumber, str);
|
iis.imageNumber, str);
|
||||||
|
|
||||||
|
|
|
@ -282,16 +282,6 @@ static void fixup_s29gl032n_sectors(struct mtd_info *mtd, void *param)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void fixup_M29W128G_write_buffer(struct mtd_info *mtd, void *param)
|
|
||||||
{
|
|
||||||
struct map_info *map = mtd->priv;
|
|
||||||
struct cfi_private *cfi = map->fldrv_priv;
|
|
||||||
if (cfi->cfiq->BufWriteTimeoutTyp) {
|
|
||||||
pr_warning("Don't use write buffer on ST flash M29W128G\n");
|
|
||||||
cfi->cfiq->BufWriteTimeoutTyp = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static struct cfi_fixup cfi_fixup_table[] = {
|
static struct cfi_fixup cfi_fixup_table[] = {
|
||||||
{ CFI_MFR_ATMEL, CFI_ID_ANY, fixup_convert_atmel_pri, NULL },
|
{ CFI_MFR_ATMEL, CFI_ID_ANY, fixup_convert_atmel_pri, NULL },
|
||||||
#ifdef AMD_BOOTLOC_BUG
|
#ifdef AMD_BOOTLOC_BUG
|
||||||
|
@ -308,7 +298,6 @@ static struct cfi_fixup cfi_fixup_table[] = {
|
||||||
{ CFI_MFR_AMD, 0x1301, fixup_s29gl064n_sectors, NULL, },
|
{ CFI_MFR_AMD, 0x1301, fixup_s29gl064n_sectors, NULL, },
|
||||||
{ CFI_MFR_AMD, 0x1a00, fixup_s29gl032n_sectors, NULL, },
|
{ CFI_MFR_AMD, 0x1a00, fixup_s29gl032n_sectors, NULL, },
|
||||||
{ CFI_MFR_AMD, 0x1a01, fixup_s29gl032n_sectors, NULL, },
|
{ CFI_MFR_AMD, 0x1a01, fixup_s29gl032n_sectors, NULL, },
|
||||||
{ CFI_MFR_ST, 0x227E, fixup_M29W128G_write_buffer, NULL, },
|
|
||||||
#if !FORCE_WORD_WRITE
|
#if !FORCE_WORD_WRITE
|
||||||
{ CFI_MFR_ANY, CFI_ID_ANY, fixup_use_write_buffers, NULL, },
|
{ CFI_MFR_ANY, CFI_ID_ANY, fixup_use_write_buffers, NULL, },
|
||||||
#endif
|
#endif
|
||||||
|
|
4
drivers/mtd/chips/cfi_util.c
Normal file → Executable file
4
drivers/mtd/chips/cfi_util.c
Normal file → Executable file
|
@ -81,6 +81,10 @@ void __xipram cfi_qry_mode_off(uint32_t base, struct map_info *map,
|
||||||
{
|
{
|
||||||
cfi_send_gen_cmd(0xF0, 0, base, map, cfi, cfi->device_type, NULL);
|
cfi_send_gen_cmd(0xF0, 0, base, map, cfi, cfi->device_type, NULL);
|
||||||
cfi_send_gen_cmd(0xFF, 0, base, map, cfi, cfi->device_type, NULL);
|
cfi_send_gen_cmd(0xFF, 0, base, map, cfi, cfi->device_type, NULL);
|
||||||
|
/* M29W128G flashes require an additional reset command
|
||||||
|
when exit qry mode */
|
||||||
|
if ((cfi->mfr == CFI_MFR_ST) && (cfi->id == 0x227E || cfi->id == 0x7E))
|
||||||
|
cfi_send_gen_cmd(0xF0, 0, base, map, cfi, cfi->device_type, NULL);
|
||||||
}
|
}
|
||||||
EXPORT_SYMBOL_GPL(cfi_qry_mode_off);
|
EXPORT_SYMBOL_GPL(cfi_qry_mode_off);
|
||||||
|
|
||||||
|
|
|
@ -111,6 +111,11 @@
|
||||||
#define I28F320B3B 0x8897
|
#define I28F320B3B 0x8897
|
||||||
#define I28F640B3T 0x8898
|
#define I28F640B3T 0x8898
|
||||||
#define I28F640B3B 0x8899
|
#define I28F640B3B 0x8899
|
||||||
|
#define I28F640C3B 0x88CD
|
||||||
|
#define I28F160F3T 0x88F3
|
||||||
|
#define I28F160F3B 0x88F4
|
||||||
|
#define I28F160C3T 0x88C2
|
||||||
|
#define I28F160C3B 0x88C3
|
||||||
#define I82802AB 0x00ad
|
#define I82802AB 0x00ad
|
||||||
#define I82802AC 0x00ac
|
#define I82802AC 0x00ac
|
||||||
|
|
||||||
|
@ -150,6 +155,7 @@
|
||||||
#define M50LPW080 0x002F
|
#define M50LPW080 0x002F
|
||||||
#define M50FLW080A 0x0080
|
#define M50FLW080A 0x0080
|
||||||
#define M50FLW080B 0x0081
|
#define M50FLW080B 0x0081
|
||||||
|
#define PSD4256G6V 0x00e9
|
||||||
|
|
||||||
/* SST */
|
/* SST */
|
||||||
#define SST29EE020 0x0010
|
#define SST29EE020 0x0010
|
||||||
|
@ -201,6 +207,7 @@ enum uaddr {
|
||||||
MTD_UADDR_0x0555_0x02AA,
|
MTD_UADDR_0x0555_0x02AA,
|
||||||
MTD_UADDR_0x0555_0x0AAA,
|
MTD_UADDR_0x0555_0x0AAA,
|
||||||
MTD_UADDR_0x5555_0x2AAA,
|
MTD_UADDR_0x5555_0x2AAA,
|
||||||
|
MTD_UADDR_0x0AAA_0x0554,
|
||||||
MTD_UADDR_0x0AAA_0x0555,
|
MTD_UADDR_0x0AAA_0x0555,
|
||||||
MTD_UADDR_0xAAAA_0x5555,
|
MTD_UADDR_0xAAAA_0x5555,
|
||||||
MTD_UADDR_DONT_CARE, /* Requires an arbitrary address */
|
MTD_UADDR_DONT_CARE, /* Requires an arbitrary address */
|
||||||
|
@ -245,6 +252,11 @@ static const struct unlock_addr unlock_addrs[] = {
|
||||||
.addr2 = 0x2aaa
|
.addr2 = 0x2aaa
|
||||||
},
|
},
|
||||||
|
|
||||||
|
[MTD_UADDR_0x0AAA_0x0554] = {
|
||||||
|
.addr1 = 0x0AAA,
|
||||||
|
.addr2 = 0x0554
|
||||||
|
},
|
||||||
|
|
||||||
[MTD_UADDR_0x0AAA_0x0555] = {
|
[MTD_UADDR_0x0AAA_0x0555] = {
|
||||||
.addr1 = 0x0AAA,
|
.addr1 = 0x0AAA,
|
||||||
.addr2 = 0x0555
|
.addr2 = 0x0555
|
||||||
|
@ -1101,6 +1113,19 @@ static const struct amd_flash_info jedec_table[] = {
|
||||||
ERASEINFO(0x10000, 127),
|
ERASEINFO(0x10000, 127),
|
||||||
ERASEINFO(0x02000, 8),
|
ERASEINFO(0x02000, 8),
|
||||||
}
|
}
|
||||||
|
}, {
|
||||||
|
.mfr_id = MANUFACTURER_INTEL,
|
||||||
|
.dev_id = I28F640C3B,
|
||||||
|
.name = "Intel 28F640C3B",
|
||||||
|
.devtypes = CFI_DEVICETYPE_X16,
|
||||||
|
.uaddr = MTD_UADDR_UNNECESSARY,
|
||||||
|
.dev_size = SIZE_8MiB,
|
||||||
|
.cmd_set = P_ID_INTEL_STD,
|
||||||
|
.nr_regions = 2,
|
||||||
|
.regions = {
|
||||||
|
ERASEINFO(0x02000, 8),
|
||||||
|
ERASEINFO(0x10000, 127),
|
||||||
|
}
|
||||||
}, {
|
}, {
|
||||||
.mfr_id = MANUFACTURER_INTEL,
|
.mfr_id = MANUFACTURER_INTEL,
|
||||||
.dev_id = I82802AB,
|
.dev_id = I82802AB,
|
||||||
|
@ -1156,8 +1181,8 @@ static const struct amd_flash_info jedec_table[] = {
|
||||||
.mfr_id = MANUFACTURER_NEC,
|
.mfr_id = MANUFACTURER_NEC,
|
||||||
.dev_id = UPD29F064115,
|
.dev_id = UPD29F064115,
|
||||||
.name = "NEC uPD29F064115",
|
.name = "NEC uPD29F064115",
|
||||||
.devtypes = CFI_DEVICETYPE_X16|CFI_DEVICETYPE_X8,
|
.devtypes = CFI_DEVICETYPE_X16,
|
||||||
.uaddr = MTD_UADDR_0x0555_0x02AA, /* ???? */
|
.uaddr = MTD_UADDR_0xAAAA_0x5555,
|
||||||
.dev_size = SIZE_8MiB,
|
.dev_size = SIZE_8MiB,
|
||||||
.cmd_set = P_ID_AMD_STD,
|
.cmd_set = P_ID_AMD_STD,
|
||||||
.nr_regions = 3,
|
.nr_regions = 3,
|
||||||
|
@ -1725,6 +1750,18 @@ static const struct amd_flash_info jedec_table[] = {
|
||||||
ERASEINFO(0x10000,13),
|
ERASEINFO(0x10000,13),
|
||||||
ERASEINFO(0x1000,16),
|
ERASEINFO(0x1000,16),
|
||||||
}
|
}
|
||||||
|
}, {
|
||||||
|
.mfr_id = 0xff00 | MANUFACTURER_ST,
|
||||||
|
.dev_id = 0xff00 | PSD4256G6V,
|
||||||
|
.name = "ST PSD4256G6V",
|
||||||
|
.devtypes = CFI_DEVICETYPE_X16,
|
||||||
|
.uaddr = MTD_UADDR_0x0AAA_0x0554,
|
||||||
|
.dev_size = SIZE_1MiB,
|
||||||
|
.cmd_set = P_ID_AMD_STD,
|
||||||
|
.nr_regions = 1,
|
||||||
|
.regions = {
|
||||||
|
ERASEINFO(0x10000,16),
|
||||||
|
}
|
||||||
}, {
|
}, {
|
||||||
.mfr_id = MANUFACTURER_TOSHIBA,
|
.mfr_id = MANUFACTURER_TOSHIBA,
|
||||||
.dev_id = TC58FVT160,
|
.dev_id = TC58FVT160,
|
||||||
|
|
|
@ -104,6 +104,16 @@ config M25PXX_USE_FAST_READ
|
||||||
help
|
help
|
||||||
This option enables FAST_READ access supported by ST M25Pxx.
|
This option enables FAST_READ access supported by ST M25Pxx.
|
||||||
|
|
||||||
|
config MTD_SST25L
|
||||||
|
tristate "Support SST25L (non JEDEC) SPI Flash chips"
|
||||||
|
depends on SPI_MASTER
|
||||||
|
help
|
||||||
|
This enables access to the non JEDEC SST25L SPI flash chips, used
|
||||||
|
for program and data storage.
|
||||||
|
|
||||||
|
Set up your spi devices with the right board-specific platform data,
|
||||||
|
if you want to specify device partitioning.
|
||||||
|
|
||||||
config MTD_SLRAM
|
config MTD_SLRAM
|
||||||
tristate "Uncached system RAM"
|
tristate "Uncached system RAM"
|
||||||
help
|
help
|
||||||
|
|
|
@ -16,3 +16,4 @@ obj-$(CONFIG_MTD_LART) += lart.o
|
||||||
obj-$(CONFIG_MTD_BLOCK2MTD) += block2mtd.o
|
obj-$(CONFIG_MTD_BLOCK2MTD) += block2mtd.o
|
||||||
obj-$(CONFIG_MTD_DATAFLASH) += mtd_dataflash.o
|
obj-$(CONFIG_MTD_DATAFLASH) += mtd_dataflash.o
|
||||||
obj-$(CONFIG_MTD_M25P80) += m25p80.o
|
obj-$(CONFIG_MTD_M25P80) += m25p80.o
|
||||||
|
obj-$(CONFIG_MTD_SST25L) += sst25l.o
|
||||||
|
|
|
@ -393,7 +393,8 @@ static int flash_erase (struct mtd_info *mtd,struct erase_info *instr)
|
||||||
* erase range is aligned with the erase size which is in
|
* erase range is aligned with the erase size which is in
|
||||||
* effect here.
|
* effect here.
|
||||||
*/
|
*/
|
||||||
if (instr->addr & (mtd->eraseregions[i].erasesize - 1)) return (-EINVAL);
|
if (i < 0 || (instr->addr & (mtd->eraseregions[i].erasesize - 1)))
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
/* Remember the erase region we start on */
|
/* Remember the erase region we start on */
|
||||||
first = i;
|
first = i;
|
||||||
|
@ -409,7 +410,8 @@ static int flash_erase (struct mtd_info *mtd,struct erase_info *instr)
|
||||||
i--;
|
i--;
|
||||||
|
|
||||||
/* is the end aligned on a block boundary? */
|
/* is the end aligned on a block boundary? */
|
||||||
if ((instr->addr + instr->len) & (mtd->eraseregions[i].erasesize - 1)) return (-EINVAL);
|
if (i < 0 || ((instr->addr + instr->len) & (mtd->eraseregions[i].erasesize - 1)))
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
addr = instr->addr;
|
addr = instr->addr;
|
||||||
len = instr->len;
|
len = instr->len;
|
||||||
|
|
|
@ -44,6 +44,11 @@
|
||||||
#define OPCODE_SE 0xd8 /* Sector erase (usually 64KiB) */
|
#define OPCODE_SE 0xd8 /* Sector erase (usually 64KiB) */
|
||||||
#define OPCODE_RDID 0x9f /* Read JEDEC ID */
|
#define OPCODE_RDID 0x9f /* Read JEDEC ID */
|
||||||
|
|
||||||
|
/* Used for SST flashes only. */
|
||||||
|
#define OPCODE_BP 0x02 /* Byte program */
|
||||||
|
#define OPCODE_WRDI 0x04 /* Write disable */
|
||||||
|
#define OPCODE_AAI_WP 0xad /* Auto address increment word program */
|
||||||
|
|
||||||
/* Status Register bits. */
|
/* Status Register bits. */
|
||||||
#define SR_WIP 1 /* Write in progress */
|
#define SR_WIP 1 /* Write in progress */
|
||||||
#define SR_WEL 2 /* Write enable latch */
|
#define SR_WEL 2 /* Write enable latch */
|
||||||
|
@ -132,6 +137,15 @@ static inline int write_enable(struct m25p *flash)
|
||||||
return spi_write_then_read(flash->spi, &code, 1, NULL, 0);
|
return spi_write_then_read(flash->spi, &code, 1, NULL, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Send write disble instruction to the chip.
|
||||||
|
*/
|
||||||
|
static inline int write_disable(struct m25p *flash)
|
||||||
|
{
|
||||||
|
u8 code = OPCODE_WRDI;
|
||||||
|
|
||||||
|
return spi_write_then_read(flash->spi, &code, 1, NULL, 0);
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Service routine to read status register until ready, or timeout occurs.
|
* Service routine to read status register until ready, or timeout occurs.
|
||||||
|
@ -454,6 +468,111 @@ static int m25p80_write(struct mtd_info *mtd, loff_t to, size_t len,
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int sst_write(struct mtd_info *mtd, loff_t to, size_t len,
|
||||||
|
size_t *retlen, const u_char *buf)
|
||||||
|
{
|
||||||
|
struct m25p *flash = mtd_to_m25p(mtd);
|
||||||
|
struct spi_transfer t[2];
|
||||||
|
struct spi_message m;
|
||||||
|
size_t actual;
|
||||||
|
int cmd_sz, ret;
|
||||||
|
|
||||||
|
if (retlen)
|
||||||
|
*retlen = 0;
|
||||||
|
|
||||||
|
/* sanity checks */
|
||||||
|
if (!len)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
if (to + len > flash->mtd.size)
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
spi_message_init(&m);
|
||||||
|
memset(t, 0, (sizeof t));
|
||||||
|
|
||||||
|
t[0].tx_buf = flash->command;
|
||||||
|
t[0].len = CMD_SIZE;
|
||||||
|
spi_message_add_tail(&t[0], &m);
|
||||||
|
|
||||||
|
t[1].tx_buf = buf;
|
||||||
|
spi_message_add_tail(&t[1], &m);
|
||||||
|
|
||||||
|
mutex_lock(&flash->lock);
|
||||||
|
|
||||||
|
/* Wait until finished previous write command. */
|
||||||
|
ret = wait_till_ready(flash);
|
||||||
|
if (ret)
|
||||||
|
goto time_out;
|
||||||
|
|
||||||
|
write_enable(flash);
|
||||||
|
|
||||||
|
actual = to % 2;
|
||||||
|
/* Start write from odd address. */
|
||||||
|
if (actual) {
|
||||||
|
flash->command[0] = OPCODE_BP;
|
||||||
|
flash->command[1] = to >> 16;
|
||||||
|
flash->command[2] = to >> 8;
|
||||||
|
flash->command[3] = to;
|
||||||
|
|
||||||
|
/* write one byte. */
|
||||||
|
t[1].len = 1;
|
||||||
|
spi_sync(flash->spi, &m);
|
||||||
|
ret = wait_till_ready(flash);
|
||||||
|
if (ret)
|
||||||
|
goto time_out;
|
||||||
|
*retlen += m.actual_length - CMD_SIZE;
|
||||||
|
}
|
||||||
|
to += actual;
|
||||||
|
|
||||||
|
flash->command[0] = OPCODE_AAI_WP;
|
||||||
|
flash->command[1] = to >> 16;
|
||||||
|
flash->command[2] = to >> 8;
|
||||||
|
flash->command[3] = to;
|
||||||
|
|
||||||
|
/* Write out most of the data here. */
|
||||||
|
cmd_sz = CMD_SIZE;
|
||||||
|
for (; actual < len - 1; actual += 2) {
|
||||||
|
t[0].len = cmd_sz;
|
||||||
|
/* write two bytes. */
|
||||||
|
t[1].len = 2;
|
||||||
|
t[1].tx_buf = buf + actual;
|
||||||
|
|
||||||
|
spi_sync(flash->spi, &m);
|
||||||
|
ret = wait_till_ready(flash);
|
||||||
|
if (ret)
|
||||||
|
goto time_out;
|
||||||
|
*retlen += m.actual_length - cmd_sz;
|
||||||
|
cmd_sz = 1;
|
||||||
|
to += 2;
|
||||||
|
}
|
||||||
|
write_disable(flash);
|
||||||
|
ret = wait_till_ready(flash);
|
||||||
|
if (ret)
|
||||||
|
goto time_out;
|
||||||
|
|
||||||
|
/* Write out trailing byte if it exists. */
|
||||||
|
if (actual != len) {
|
||||||
|
write_enable(flash);
|
||||||
|
flash->command[0] = OPCODE_BP;
|
||||||
|
flash->command[1] = to >> 16;
|
||||||
|
flash->command[2] = to >> 8;
|
||||||
|
flash->command[3] = to;
|
||||||
|
t[0].len = CMD_SIZE;
|
||||||
|
t[1].len = 1;
|
||||||
|
t[1].tx_buf = buf + actual;
|
||||||
|
|
||||||
|
spi_sync(flash->spi, &m);
|
||||||
|
ret = wait_till_ready(flash);
|
||||||
|
if (ret)
|
||||||
|
goto time_out;
|
||||||
|
*retlen += m.actual_length - CMD_SIZE;
|
||||||
|
write_disable(flash);
|
||||||
|
}
|
||||||
|
|
||||||
|
time_out:
|
||||||
|
mutex_unlock(&flash->lock);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
/****************************************************************************/
|
/****************************************************************************/
|
||||||
|
|
||||||
|
@ -501,7 +620,10 @@ static struct flash_info __devinitdata m25p_data [] = {
|
||||||
{ "at26df321", 0x1f4701, 0, 64 * 1024, 64, SECT_4K, },
|
{ "at26df321", 0x1f4701, 0, 64 * 1024, 64, SECT_4K, },
|
||||||
|
|
||||||
/* Macronix */
|
/* Macronix */
|
||||||
|
{ "mx25l3205d", 0xc22016, 0, 64 * 1024, 64, },
|
||||||
|
{ "mx25l6405d", 0xc22017, 0, 64 * 1024, 128, },
|
||||||
{ "mx25l12805d", 0xc22018, 0, 64 * 1024, 256, },
|
{ "mx25l12805d", 0xc22018, 0, 64 * 1024, 256, },
|
||||||
|
{ "mx25l12855e", 0xc22618, 0, 64 * 1024, 256, },
|
||||||
|
|
||||||
/* Spansion -- single (large) sector size only, at least
|
/* Spansion -- single (large) sector size only, at least
|
||||||
* for the chips listed here (without boot sectors).
|
* for the chips listed here (without boot sectors).
|
||||||
|
@ -513,12 +635,18 @@ static struct flash_info __devinitdata m25p_data [] = {
|
||||||
{ "s25sl064a", 0x010216, 0, 64 * 1024, 128, },
|
{ "s25sl064a", 0x010216, 0, 64 * 1024, 128, },
|
||||||
{ "s25sl12800", 0x012018, 0x0300, 256 * 1024, 64, },
|
{ "s25sl12800", 0x012018, 0x0300, 256 * 1024, 64, },
|
||||||
{ "s25sl12801", 0x012018, 0x0301, 64 * 1024, 256, },
|
{ "s25sl12801", 0x012018, 0x0301, 64 * 1024, 256, },
|
||||||
|
{ "s25fl129p0", 0x012018, 0x4d00, 256 * 1024, 64, },
|
||||||
|
{ "s25fl129p1", 0x012018, 0x4d01, 64 * 1024, 256, },
|
||||||
|
|
||||||
/* SST -- large erase sizes are "overlays", "sectors" are 4K */
|
/* SST -- large erase sizes are "overlays", "sectors" are 4K */
|
||||||
{ "sst25vf040b", 0xbf258d, 0, 64 * 1024, 8, SECT_4K, },
|
{ "sst25vf040b", 0xbf258d, 0, 64 * 1024, 8, SECT_4K, },
|
||||||
{ "sst25vf080b", 0xbf258e, 0, 64 * 1024, 16, SECT_4K, },
|
{ "sst25vf080b", 0xbf258e, 0, 64 * 1024, 16, SECT_4K, },
|
||||||
{ "sst25vf016b", 0xbf2541, 0, 64 * 1024, 32, SECT_4K, },
|
{ "sst25vf016b", 0xbf2541, 0, 64 * 1024, 32, SECT_4K, },
|
||||||
{ "sst25vf032b", 0xbf254a, 0, 64 * 1024, 64, SECT_4K, },
|
{ "sst25vf032b", 0xbf254a, 0, 64 * 1024, 64, SECT_4K, },
|
||||||
|
{ "sst25wf512", 0xbf2501, 0, 64 * 1024, 1, SECT_4K, },
|
||||||
|
{ "sst25wf010", 0xbf2502, 0, 64 * 1024, 2, SECT_4K, },
|
||||||
|
{ "sst25wf020", 0xbf2503, 0, 64 * 1024, 4, SECT_4K, },
|
||||||
|
{ "sst25wf040", 0xbf2504, 0, 64 * 1024, 8, SECT_4K, },
|
||||||
|
|
||||||
/* ST Microelectronics -- newer production may have feature updates */
|
/* ST Microelectronics -- newer production may have feature updates */
|
||||||
{ "m25p05", 0x202010, 0, 32 * 1024, 2, },
|
{ "m25p05", 0x202010, 0, 32 * 1024, 2, },
|
||||||
|
@ -667,6 +795,11 @@ static int __devinit m25p_probe(struct spi_device *spi)
|
||||||
flash->mtd.size = info->sector_size * info->n_sectors;
|
flash->mtd.size = info->sector_size * info->n_sectors;
|
||||||
flash->mtd.erase = m25p80_erase;
|
flash->mtd.erase = m25p80_erase;
|
||||||
flash->mtd.read = m25p80_read;
|
flash->mtd.read = m25p80_read;
|
||||||
|
|
||||||
|
/* sst flash chips use AAI word program */
|
||||||
|
if (info->jedec_id >> 16 == 0xbf)
|
||||||
|
flash->mtd.write = sst_write;
|
||||||
|
else
|
||||||
flash->mtd.write = m25p80_write;
|
flash->mtd.write = m25p80_write;
|
||||||
|
|
||||||
/* prefer "small sector" erase if possible */
|
/* prefer "small sector" erase if possible */
|
||||||
|
|
|
@ -401,7 +401,7 @@ static int dataflash_write(struct mtd_info *mtd, loff_t to, size_t len,
|
||||||
(void) dataflash_waitready(priv->spi);
|
(void) dataflash_waitready(priv->spi);
|
||||||
|
|
||||||
|
|
||||||
#ifdef CONFIG_MTD_DATAFLASH_VERIFY_WRITE
|
#ifdef CONFIG_MTD_DATAFLASH_WRITE_VERIFY
|
||||||
|
|
||||||
/* (3) Compare to Buffer1 */
|
/* (3) Compare to Buffer1 */
|
||||||
addr = pageaddr << priv->page_offset;
|
addr = pageaddr << priv->page_offset;
|
||||||
|
@ -430,7 +430,7 @@ static int dataflash_write(struct mtd_info *mtd, loff_t to, size_t len,
|
||||||
} else
|
} else
|
||||||
status = 0;
|
status = 0;
|
||||||
|
|
||||||
#endif /* CONFIG_MTD_DATAFLASH_VERIFY_WRITE */
|
#endif /* CONFIG_MTD_DATAFLASH_WRITE_VERIFY */
|
||||||
|
|
||||||
remaining = remaining - writelen;
|
remaining = remaining - writelen;
|
||||||
pageaddr++;
|
pageaddr++;
|
||||||
|
|
|
@ -14,6 +14,9 @@
|
||||||
* Example:
|
* Example:
|
||||||
* phram=swap,64Mi,128Mi phram=test,900Mi,1Mi
|
* phram=swap,64Mi,128Mi phram=test,900Mi,1Mi
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#define pr_fmt(fmt) "phram: " fmt
|
||||||
|
|
||||||
#include <asm/io.h>
|
#include <asm/io.h>
|
||||||
#include <linux/init.h>
|
#include <linux/init.h>
|
||||||
#include <linux/kernel.h>
|
#include <linux/kernel.h>
|
||||||
|
@ -23,8 +26,6 @@
|
||||||
#include <linux/slab.h>
|
#include <linux/slab.h>
|
||||||
#include <linux/mtd/mtd.h>
|
#include <linux/mtd/mtd.h>
|
||||||
|
|
||||||
#define ERROR(fmt, args...) printk(KERN_ERR "phram: " fmt , ## args)
|
|
||||||
|
|
||||||
struct phram_mtd_list {
|
struct phram_mtd_list {
|
||||||
struct mtd_info mtd;
|
struct mtd_info mtd;
|
||||||
struct list_head list;
|
struct list_head list;
|
||||||
|
@ -132,7 +133,7 @@ static int register_device(char *name, unsigned long start, unsigned long len)
|
||||||
ret = -EIO;
|
ret = -EIO;
|
||||||
new->mtd.priv = ioremap(start, len);
|
new->mtd.priv = ioremap(start, len);
|
||||||
if (!new->mtd.priv) {
|
if (!new->mtd.priv) {
|
||||||
ERROR("ioremap failed\n");
|
pr_err("ioremap failed\n");
|
||||||
goto out1;
|
goto out1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -152,7 +153,7 @@ static int register_device(char *name, unsigned long start, unsigned long len)
|
||||||
|
|
||||||
ret = -EAGAIN;
|
ret = -EAGAIN;
|
||||||
if (add_mtd_device(&new->mtd)) {
|
if (add_mtd_device(&new->mtd)) {
|
||||||
ERROR("Failed to register new device\n");
|
pr_err("Failed to register new device\n");
|
||||||
goto out2;
|
goto out2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -227,8 +228,8 @@ static inline void kill_final_newline(char *str)
|
||||||
|
|
||||||
|
|
||||||
#define parse_err(fmt, args...) do { \
|
#define parse_err(fmt, args...) do { \
|
||||||
ERROR(fmt , ## args); \
|
pr_err(fmt , ## args); \
|
||||||
return 0; \
|
return 1; \
|
||||||
} while (0)
|
} while (0)
|
||||||
|
|
||||||
static int phram_setup(const char *val, struct kernel_param *kp)
|
static int phram_setup(const char *val, struct kernel_param *kp)
|
||||||
|
@ -256,12 +257,8 @@ static int phram_setup(const char *val, struct kernel_param *kp)
|
||||||
parse_err("not enough arguments\n");
|
parse_err("not enough arguments\n");
|
||||||
|
|
||||||
ret = parse_name(&name, token[0]);
|
ret = parse_name(&name, token[0]);
|
||||||
if (ret == -ENOMEM)
|
|
||||||
parse_err("out of memory\n");
|
|
||||||
if (ret == -ENOSPC)
|
|
||||||
parse_err("name too long\n");
|
|
||||||
if (ret)
|
if (ret)
|
||||||
return 0;
|
return ret;
|
||||||
|
|
||||||
ret = parse_num32(&start, token[1]);
|
ret = parse_num32(&start, token[1]);
|
||||||
if (ret) {
|
if (ret) {
|
||||||
|
@ -275,9 +272,11 @@ static int phram_setup(const char *val, struct kernel_param *kp)
|
||||||
parse_err("illegal device length\n");
|
parse_err("illegal device length\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
register_device(name, start, len);
|
ret = register_device(name, start, len);
|
||||||
|
if (!ret)
|
||||||
|
pr_info("%s device: %#x at %#x\n", name, len, start);
|
||||||
|
|
||||||
return 0;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
module_param_call(phram, phram_setup, NULL, NULL, 000);
|
module_param_call(phram, phram_setup, NULL, NULL, 000);
|
||||||
|
|
|
@ -341,7 +341,7 @@ static int __init init_slram(void)
|
||||||
#else
|
#else
|
||||||
int count;
|
int count;
|
||||||
|
|
||||||
for (count = 0; (map[count]) && (count < SLRAM_MAX_DEVICES_PARAMS);
|
for (count = 0; count < SLRAM_MAX_DEVICES_PARAMS && map[count];
|
||||||
count++) {
|
count++) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
512
drivers/mtd/devices/sst25l.c
Normal file
512
drivers/mtd/devices/sst25l.c
Normal file
|
@ -0,0 +1,512 @@
|
||||||
|
/*
|
||||||
|
* sst25l.c
|
||||||
|
*
|
||||||
|
* Driver for SST25L SPI Flash chips
|
||||||
|
*
|
||||||
|
* Copyright © 2009 Bluewater Systems Ltd
|
||||||
|
* Author: Andre Renaud <andre@bluewatersys.com>
|
||||||
|
* Author: Ryan Mallon <ryan@bluewatersys.com>
|
||||||
|
*
|
||||||
|
* Based on m25p80.c
|
||||||
|
*
|
||||||
|
* This code 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/init.h>
|
||||||
|
#include <linux/module.h>
|
||||||
|
#include <linux/device.h>
|
||||||
|
#include <linux/mutex.h>
|
||||||
|
#include <linux/interrupt.h>
|
||||||
|
|
||||||
|
#include <linux/mtd/mtd.h>
|
||||||
|
#include <linux/mtd/partitions.h>
|
||||||
|
|
||||||
|
#include <linux/spi/spi.h>
|
||||||
|
#include <linux/spi/flash.h>
|
||||||
|
|
||||||
|
/* Erases can take up to 3 seconds! */
|
||||||
|
#define MAX_READY_WAIT_JIFFIES msecs_to_jiffies(3000)
|
||||||
|
|
||||||
|
#define SST25L_CMD_WRSR 0x01 /* Write status register */
|
||||||
|
#define SST25L_CMD_WRDI 0x04 /* Write disable */
|
||||||
|
#define SST25L_CMD_RDSR 0x05 /* Read status register */
|
||||||
|
#define SST25L_CMD_WREN 0x06 /* Write enable */
|
||||||
|
#define SST25L_CMD_READ 0x03 /* High speed read */
|
||||||
|
|
||||||
|
#define SST25L_CMD_EWSR 0x50 /* Enable write status register */
|
||||||
|
#define SST25L_CMD_SECTOR_ERASE 0x20 /* Erase sector */
|
||||||
|
#define SST25L_CMD_READ_ID 0x90 /* Read device ID */
|
||||||
|
#define SST25L_CMD_AAI_PROGRAM 0xaf /* Auto address increment */
|
||||||
|
|
||||||
|
#define SST25L_STATUS_BUSY (1 << 0) /* Chip is busy */
|
||||||
|
#define SST25L_STATUS_WREN (1 << 1) /* Write enabled */
|
||||||
|
#define SST25L_STATUS_BP0 (1 << 2) /* Block protection 0 */
|
||||||
|
#define SST25L_STATUS_BP1 (1 << 3) /* Block protection 1 */
|
||||||
|
|
||||||
|
struct sst25l_flash {
|
||||||
|
struct spi_device *spi;
|
||||||
|
struct mutex lock;
|
||||||
|
struct mtd_info mtd;
|
||||||
|
|
||||||
|
int partitioned;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct flash_info {
|
||||||
|
const char *name;
|
||||||
|
uint16_t device_id;
|
||||||
|
unsigned page_size;
|
||||||
|
unsigned nr_pages;
|
||||||
|
unsigned erase_size;
|
||||||
|
};
|
||||||
|
|
||||||
|
#define to_sst25l_flash(x) container_of(x, struct sst25l_flash, mtd)
|
||||||
|
|
||||||
|
static struct flash_info __initdata sst25l_flash_info[] = {
|
||||||
|
{"sst25lf020a", 0xbf43, 256, 1024, 4096},
|
||||||
|
{"sst25lf040a", 0xbf44, 256, 2048, 4096},
|
||||||
|
};
|
||||||
|
|
||||||
|
static int sst25l_status(struct sst25l_flash *flash, int *status)
|
||||||
|
{
|
||||||
|
unsigned char command, response;
|
||||||
|
int err;
|
||||||
|
|
||||||
|
command = SST25L_CMD_RDSR;
|
||||||
|
err = spi_write_then_read(flash->spi, &command, 1, &response, 1);
|
||||||
|
if (err < 0)
|
||||||
|
return err;
|
||||||
|
|
||||||
|
*status = response;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int sst25l_write_enable(struct sst25l_flash *flash, int enable)
|
||||||
|
{
|
||||||
|
unsigned char command[2];
|
||||||
|
int status, err;
|
||||||
|
|
||||||
|
command[0] = enable ? SST25L_CMD_WREN : SST25L_CMD_WRDI;
|
||||||
|
err = spi_write(flash->spi, command, 1);
|
||||||
|
if (err)
|
||||||
|
return err;
|
||||||
|
|
||||||
|
command[0] = SST25L_CMD_EWSR;
|
||||||
|
err = spi_write(flash->spi, command, 1);
|
||||||
|
if (err)
|
||||||
|
return err;
|
||||||
|
|
||||||
|
command[0] = SST25L_CMD_WRSR;
|
||||||
|
command[1] = enable ? 0 : SST25L_STATUS_BP0 | SST25L_STATUS_BP1;
|
||||||
|
err = spi_write(flash->spi, command, 2);
|
||||||
|
if (err)
|
||||||
|
return err;
|
||||||
|
|
||||||
|
if (enable) {
|
||||||
|
err = sst25l_status(flash, &status);
|
||||||
|
if (err)
|
||||||
|
return err;
|
||||||
|
if (!(status & SST25L_STATUS_WREN))
|
||||||
|
return -EROFS;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int sst25l_wait_till_ready(struct sst25l_flash *flash)
|
||||||
|
{
|
||||||
|
unsigned long deadline;
|
||||||
|
int status, err;
|
||||||
|
|
||||||
|
deadline = jiffies + MAX_READY_WAIT_JIFFIES;
|
||||||
|
do {
|
||||||
|
err = sst25l_status(flash, &status);
|
||||||
|
if (err)
|
||||||
|
return err;
|
||||||
|
if (!(status & SST25L_STATUS_BUSY))
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
cond_resched();
|
||||||
|
} while (!time_after_eq(jiffies, deadline));
|
||||||
|
|
||||||
|
return -ETIMEDOUT;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int sst25l_erase_sector(struct sst25l_flash *flash, uint32_t offset)
|
||||||
|
{
|
||||||
|
unsigned char command[4];
|
||||||
|
int err;
|
||||||
|
|
||||||
|
err = sst25l_write_enable(flash, 1);
|
||||||
|
if (err)
|
||||||
|
return err;
|
||||||
|
|
||||||
|
command[0] = SST25L_CMD_SECTOR_ERASE;
|
||||||
|
command[1] = offset >> 16;
|
||||||
|
command[2] = offset >> 8;
|
||||||
|
command[3] = offset;
|
||||||
|
err = spi_write(flash->spi, command, 4);
|
||||||
|
if (err)
|
||||||
|
return err;
|
||||||
|
|
||||||
|
err = sst25l_wait_till_ready(flash);
|
||||||
|
if (err)
|
||||||
|
return err;
|
||||||
|
|
||||||
|
return sst25l_write_enable(flash, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int sst25l_erase(struct mtd_info *mtd, struct erase_info *instr)
|
||||||
|
{
|
||||||
|
struct sst25l_flash *flash = to_sst25l_flash(mtd);
|
||||||
|
uint32_t addr, end;
|
||||||
|
int err;
|
||||||
|
|
||||||
|
/* Sanity checks */
|
||||||
|
if (instr->addr + instr->len > flash->mtd.size)
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
if ((uint32_t)instr->len % mtd->erasesize)
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
if ((uint32_t)instr->addr % mtd->erasesize)
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
addr = instr->addr;
|
||||||
|
end = addr + instr->len;
|
||||||
|
|
||||||
|
mutex_lock(&flash->lock);
|
||||||
|
|
||||||
|
err = sst25l_wait_till_ready(flash);
|
||||||
|
if (err) {
|
||||||
|
mutex_unlock(&flash->lock);
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (addr < end) {
|
||||||
|
err = sst25l_erase_sector(flash, addr);
|
||||||
|
if (err) {
|
||||||
|
mutex_unlock(&flash->lock);
|
||||||
|
instr->state = MTD_ERASE_FAILED;
|
||||||
|
dev_err(&flash->spi->dev, "Erase failed\n");
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
addr += mtd->erasesize;
|
||||||
|
}
|
||||||
|
|
||||||
|
mutex_unlock(&flash->lock);
|
||||||
|
|
||||||
|
instr->state = MTD_ERASE_DONE;
|
||||||
|
mtd_erase_callback(instr);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int sst25l_read(struct mtd_info *mtd, loff_t from, size_t len,
|
||||||
|
size_t *retlen, unsigned char *buf)
|
||||||
|
{
|
||||||
|
struct sst25l_flash *flash = to_sst25l_flash(mtd);
|
||||||
|
struct spi_transfer transfer[2];
|
||||||
|
struct spi_message message;
|
||||||
|
unsigned char command[4];
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
/* Sanity checking */
|
||||||
|
if (len == 0)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
if (from + len > flash->mtd.size)
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
if (retlen)
|
||||||
|
*retlen = 0;
|
||||||
|
|
||||||
|
spi_message_init(&message);
|
||||||
|
memset(&transfer, 0, sizeof(transfer));
|
||||||
|
|
||||||
|
command[0] = SST25L_CMD_READ;
|
||||||
|
command[1] = from >> 16;
|
||||||
|
command[2] = from >> 8;
|
||||||
|
command[3] = from;
|
||||||
|
|
||||||
|
transfer[0].tx_buf = command;
|
||||||
|
transfer[0].len = sizeof(command);
|
||||||
|
spi_message_add_tail(&transfer[0], &message);
|
||||||
|
|
||||||
|
transfer[1].rx_buf = buf;
|
||||||
|
transfer[1].len = len;
|
||||||
|
spi_message_add_tail(&transfer[1], &message);
|
||||||
|
|
||||||
|
mutex_lock(&flash->lock);
|
||||||
|
|
||||||
|
/* Wait for previous write/erase to complete */
|
||||||
|
ret = sst25l_wait_till_ready(flash);
|
||||||
|
if (ret) {
|
||||||
|
mutex_unlock(&flash->lock);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
spi_sync(flash->spi, &message);
|
||||||
|
|
||||||
|
if (retlen && message.actual_length > sizeof(command))
|
||||||
|
*retlen += message.actual_length - sizeof(command);
|
||||||
|
|
||||||
|
mutex_unlock(&flash->lock);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int sst25l_write(struct mtd_info *mtd, loff_t to, size_t len,
|
||||||
|
size_t *retlen, const unsigned char *buf)
|
||||||
|
{
|
||||||
|
struct sst25l_flash *flash = to_sst25l_flash(mtd);
|
||||||
|
int i, j, ret, bytes, copied = 0;
|
||||||
|
unsigned char command[5];
|
||||||
|
|
||||||
|
/* Sanity checks */
|
||||||
|
if (!len)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
if (to + len > flash->mtd.size)
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
if ((uint32_t)to % mtd->writesize)
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
mutex_lock(&flash->lock);
|
||||||
|
|
||||||
|
ret = sst25l_write_enable(flash, 1);
|
||||||
|
if (ret)
|
||||||
|
goto out;
|
||||||
|
|
||||||
|
for (i = 0; i < len; i += mtd->writesize) {
|
||||||
|
ret = sst25l_wait_till_ready(flash);
|
||||||
|
if (ret)
|
||||||
|
goto out;
|
||||||
|
|
||||||
|
/* Write the first byte of the page */
|
||||||
|
command[0] = SST25L_CMD_AAI_PROGRAM;
|
||||||
|
command[1] = (to + i) >> 16;
|
||||||
|
command[2] = (to + i) >> 8;
|
||||||
|
command[3] = (to + i);
|
||||||
|
command[4] = buf[i];
|
||||||
|
ret = spi_write(flash->spi, command, 5);
|
||||||
|
if (ret < 0)
|
||||||
|
goto out;
|
||||||
|
copied++;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Write the remaining bytes using auto address
|
||||||
|
* increment mode
|
||||||
|
*/
|
||||||
|
bytes = min_t(uint32_t, mtd->writesize, len - i);
|
||||||
|
for (j = 1; j < bytes; j++, copied++) {
|
||||||
|
ret = sst25l_wait_till_ready(flash);
|
||||||
|
if (ret)
|
||||||
|
goto out;
|
||||||
|
|
||||||
|
command[1] = buf[i + j];
|
||||||
|
ret = spi_write(flash->spi, command, 2);
|
||||||
|
if (ret)
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
out:
|
||||||
|
ret = sst25l_write_enable(flash, 0);
|
||||||
|
|
||||||
|
if (retlen)
|
||||||
|
*retlen = copied;
|
||||||
|
|
||||||
|
mutex_unlock(&flash->lock);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct flash_info *__init sst25l_match_device(struct spi_device *spi)
|
||||||
|
{
|
||||||
|
struct flash_info *flash_info = NULL;
|
||||||
|
unsigned char command[4], response;
|
||||||
|
int i, err;
|
||||||
|
uint16_t id;
|
||||||
|
|
||||||
|
command[0] = SST25L_CMD_READ_ID;
|
||||||
|
command[1] = 0;
|
||||||
|
command[2] = 0;
|
||||||
|
command[3] = 0;
|
||||||
|
err = spi_write_then_read(spi, command, sizeof(command), &response, 1);
|
||||||
|
if (err < 0) {
|
||||||
|
dev_err(&spi->dev, "error reading device id msb\n");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
id = response << 8;
|
||||||
|
|
||||||
|
command[0] = SST25L_CMD_READ_ID;
|
||||||
|
command[1] = 0;
|
||||||
|
command[2] = 0;
|
||||||
|
command[3] = 1;
|
||||||
|
err = spi_write_then_read(spi, command, sizeof(command), &response, 1);
|
||||||
|
if (err < 0) {
|
||||||
|
dev_err(&spi->dev, "error reading device id lsb\n");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
id |= response;
|
||||||
|
|
||||||
|
for (i = 0; i < ARRAY_SIZE(sst25l_flash_info); i++)
|
||||||
|
if (sst25l_flash_info[i].device_id == id)
|
||||||
|
flash_info = &sst25l_flash_info[i];
|
||||||
|
|
||||||
|
if (!flash_info)
|
||||||
|
dev_err(&spi->dev, "unknown id %.4x\n", id);
|
||||||
|
|
||||||
|
return flash_info;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int __init sst25l_probe(struct spi_device *spi)
|
||||||
|
{
|
||||||
|
struct flash_info *flash_info;
|
||||||
|
struct sst25l_flash *flash;
|
||||||
|
struct flash_platform_data *data;
|
||||||
|
int ret, i;
|
||||||
|
|
||||||
|
flash_info = sst25l_match_device(spi);
|
||||||
|
if (!flash_info)
|
||||||
|
return -ENODEV;
|
||||||
|
|
||||||
|
flash = kzalloc(sizeof(struct sst25l_flash), GFP_KERNEL);
|
||||||
|
if (!flash)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
flash->spi = spi;
|
||||||
|
mutex_init(&flash->lock);
|
||||||
|
dev_set_drvdata(&spi->dev, flash);
|
||||||
|
|
||||||
|
data = spi->dev.platform_data;
|
||||||
|
if (data && data->name)
|
||||||
|
flash->mtd.name = data->name;
|
||||||
|
else
|
||||||
|
flash->mtd.name = dev_name(&spi->dev);
|
||||||
|
|
||||||
|
flash->mtd.type = MTD_NORFLASH;
|
||||||
|
flash->mtd.flags = MTD_CAP_NORFLASH;
|
||||||
|
flash->mtd.erasesize = flash_info->erase_size;
|
||||||
|
flash->mtd.writesize = flash_info->page_size;
|
||||||
|
flash->mtd.size = flash_info->page_size * flash_info->nr_pages;
|
||||||
|
flash->mtd.erase = sst25l_erase;
|
||||||
|
flash->mtd.read = sst25l_read;
|
||||||
|
flash->mtd.write = sst25l_write;
|
||||||
|
|
||||||
|
dev_info(&spi->dev, "%s (%lld KiB)\n", flash_info->name,
|
||||||
|
(long long)flash->mtd.size >> 10);
|
||||||
|
|
||||||
|
DEBUG(MTD_DEBUG_LEVEL2,
|
||||||
|
"mtd .name = %s, .size = 0x%llx (%lldMiB) "
|
||||||
|
".erasesize = 0x%.8x (%uKiB) .numeraseregions = %d\n",
|
||||||
|
flash->mtd.name,
|
||||||
|
(long long)flash->mtd.size, (long long)(flash->mtd.size >> 20),
|
||||||
|
flash->mtd.erasesize, flash->mtd.erasesize / 1024,
|
||||||
|
flash->mtd.numeraseregions);
|
||||||
|
|
||||||
|
if (flash->mtd.numeraseregions)
|
||||||
|
for (i = 0; i < flash->mtd.numeraseregions; i++)
|
||||||
|
DEBUG(MTD_DEBUG_LEVEL2,
|
||||||
|
"mtd.eraseregions[%d] = { .offset = 0x%llx, "
|
||||||
|
".erasesize = 0x%.8x (%uKiB), "
|
||||||
|
".numblocks = %d }\n",
|
||||||
|
i, (long long)flash->mtd.eraseregions[i].offset,
|
||||||
|
flash->mtd.eraseregions[i].erasesize,
|
||||||
|
flash->mtd.eraseregions[i].erasesize / 1024,
|
||||||
|
flash->mtd.eraseregions[i].numblocks);
|
||||||
|
|
||||||
|
if (mtd_has_partitions()) {
|
||||||
|
struct mtd_partition *parts = NULL;
|
||||||
|
int nr_parts = 0;
|
||||||
|
|
||||||
|
if (mtd_has_cmdlinepart()) {
|
||||||
|
static const char *part_probes[] =
|
||||||
|
{"cmdlinepart", NULL};
|
||||||
|
|
||||||
|
nr_parts = parse_mtd_partitions(&flash->mtd,
|
||||||
|
part_probes,
|
||||||
|
&parts, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nr_parts <= 0 && data && data->parts) {
|
||||||
|
parts = data->parts;
|
||||||
|
nr_parts = data->nr_parts;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nr_parts > 0) {
|
||||||
|
for (i = 0; i < nr_parts; i++) {
|
||||||
|
DEBUG(MTD_DEBUG_LEVEL2, "partitions[%d] = "
|
||||||
|
"{.name = %s, .offset = 0x%llx, "
|
||||||
|
".size = 0x%llx (%lldKiB) }\n",
|
||||||
|
i, parts[i].name,
|
||||||
|
(long long)parts[i].offset,
|
||||||
|
(long long)parts[i].size,
|
||||||
|
(long long)(parts[i].size >> 10));
|
||||||
|
}
|
||||||
|
|
||||||
|
flash->partitioned = 1;
|
||||||
|
return add_mtd_partitions(&flash->mtd,
|
||||||
|
parts, nr_parts);
|
||||||
|
}
|
||||||
|
|
||||||
|
} else if (data->nr_parts) {
|
||||||
|
dev_warn(&spi->dev, "ignoring %d default partitions on %s\n",
|
||||||
|
data->nr_parts, data->name);
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = add_mtd_device(&flash->mtd);
|
||||||
|
if (ret == 1) {
|
||||||
|
kfree(flash);
|
||||||
|
dev_set_drvdata(&spi->dev, NULL);
|
||||||
|
return -ENODEV;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int __exit sst25l_remove(struct spi_device *spi)
|
||||||
|
{
|
||||||
|
struct sst25l_flash *flash = dev_get_drvdata(&spi->dev);
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
if (mtd_has_partitions() && flash->partitioned)
|
||||||
|
ret = del_mtd_partitions(&flash->mtd);
|
||||||
|
else
|
||||||
|
ret = del_mtd_device(&flash->mtd);
|
||||||
|
if (ret == 0)
|
||||||
|
kfree(flash);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct spi_driver sst25l_driver = {
|
||||||
|
.driver = {
|
||||||
|
.name = "sst25l",
|
||||||
|
.bus = &spi_bus_type,
|
||||||
|
.owner = THIS_MODULE,
|
||||||
|
},
|
||||||
|
.probe = sst25l_probe,
|
||||||
|
.remove = __exit_p(sst25l_remove),
|
||||||
|
};
|
||||||
|
|
||||||
|
static int __init sst25l_init(void)
|
||||||
|
{
|
||||||
|
return spi_register_driver(&sst25l_driver);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void __exit sst25l_exit(void)
|
||||||
|
{
|
||||||
|
spi_unregister_driver(&sst25l_driver);
|
||||||
|
}
|
||||||
|
|
||||||
|
module_init(sst25l_init);
|
||||||
|
module_exit(sst25l_exit);
|
||||||
|
|
||||||
|
MODULE_DESCRIPTION("MTD SPI driver for SST25L Flash chips");
|
||||||
|
MODULE_AUTHOR("Andre Renaud <andre@bluewatersys.com>, "
|
||||||
|
"Ryan Mallon <ryan@bluewatersys.com>");
|
||||||
|
MODULE_LICENSE("GPL");
|
2
drivers/mtd/inftlcore.c
Normal file → Executable file
2
drivers/mtd/inftlcore.c
Normal file → Executable file
|
@ -550,7 +550,7 @@ hitused:
|
||||||
* waiting to be picked up. We're going to have to fold
|
* waiting to be picked up. We're going to have to fold
|
||||||
* a chain to make room.
|
* a chain to make room.
|
||||||
*/
|
*/
|
||||||
thisEUN = INFTL_makefreeblock(inftl, BLOCK_NIL);
|
thisEUN = INFTL_makefreeblock(inftl, block);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Hopefully we free something, lets try again.
|
* Hopefully we free something, lets try again.
|
||||||
|
|
|
@ -484,9 +484,19 @@ config MTD_BFIN_ASYNC
|
||||||
|
|
||||||
If compiled as a module, it will be called bfin-async-flash.
|
If compiled as a module, it will be called bfin-async-flash.
|
||||||
|
|
||||||
|
config MTD_GPIO_ADDR
|
||||||
|
tristate "GPIO-assisted Flash Chip Support"
|
||||||
|
depends on MTD_COMPLEX_MAPPINGS
|
||||||
|
select MTD_PARTITIONS
|
||||||
|
help
|
||||||
|
Map driver which allows flashes to be partially physically addressed
|
||||||
|
and assisted by GPIOs.
|
||||||
|
|
||||||
|
If compiled as a module, it will be called gpio-addr-flash.
|
||||||
|
|
||||||
config MTD_UCLINUX
|
config MTD_UCLINUX
|
||||||
bool "Generic uClinux RAM/ROM filesystem support"
|
bool "Generic uClinux RAM/ROM filesystem support"
|
||||||
depends on MTD_PARTITIONS && MTD_RAM && !MMU
|
depends on MTD_PARTITIONS && MTD_RAM=y && !MMU
|
||||||
help
|
help
|
||||||
Map driver to support image based filesystems for uClinux.
|
Map driver to support image based filesystems for uClinux.
|
||||||
|
|
||||||
|
|
|
@ -58,5 +58,4 @@ obj-$(CONFIG_MTD_PLATRAM) += plat-ram.o
|
||||||
obj-$(CONFIG_MTD_OMAP_NOR) += omap_nor.o
|
obj-$(CONFIG_MTD_OMAP_NOR) += omap_nor.o
|
||||||
obj-$(CONFIG_MTD_INTEL_VR_NOR) += intel_vr_nor.o
|
obj-$(CONFIG_MTD_INTEL_VR_NOR) += intel_vr_nor.o
|
||||||
obj-$(CONFIG_MTD_BFIN_ASYNC) += bfin-async-flash.o
|
obj-$(CONFIG_MTD_BFIN_ASYNC) += bfin-async-flash.o
|
||||||
obj-$(CONFIG_MTD_RBTX4939) += rbtx4939-flash.o
|
obj-$(CONFIG_MTD_GPIO_ADDR) += gpio-addr-flash.o
|
||||||
obj-$(CONFIG_MTD_VMU) += vmu-flash.o
|
|
||||||
|
|
311
drivers/mtd/maps/gpio-addr-flash.c
Normal file
311
drivers/mtd/maps/gpio-addr-flash.c
Normal file
|
@ -0,0 +1,311 @@
|
||||||
|
/*
|
||||||
|
* drivers/mtd/maps/gpio-addr-flash.c
|
||||||
|
*
|
||||||
|
* Handle the case where a flash device is mostly addressed using physical
|
||||||
|
* line and supplemented by GPIOs. This way you can hook up say a 8MiB flash
|
||||||
|
* to a 2MiB memory range and use the GPIOs to select a particular range.
|
||||||
|
*
|
||||||
|
* Copyright © 2000 Nicolas Pitre <nico@cam.org>
|
||||||
|
* Copyright © 2005-2009 Analog Devices Inc.
|
||||||
|
*
|
||||||
|
* Enter bugs at http://blackfin.uclinux.org/
|
||||||
|
*
|
||||||
|
* Licensed under the GPL-2 or later.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <linux/init.h>
|
||||||
|
#include <linux/kernel.h>
|
||||||
|
#include <linux/module.h>
|
||||||
|
#include <linux/mtd/mtd.h>
|
||||||
|
#include <linux/mtd/map.h>
|
||||||
|
#include <linux/mtd/partitions.h>
|
||||||
|
#include <linux/mtd/physmap.h>
|
||||||
|
#include <linux/platform_device.h>
|
||||||
|
#include <linux/types.h>
|
||||||
|
|
||||||
|
#include <asm/gpio.h>
|
||||||
|
#include <asm/io.h>
|
||||||
|
|
||||||
|
#define pr_devinit(fmt, args...) ({ static const __devinitconst char __fmt[] = fmt; printk(__fmt, ## args); })
|
||||||
|
|
||||||
|
#define DRIVER_NAME "gpio-addr-flash"
|
||||||
|
#define PFX DRIVER_NAME ": "
|
||||||
|
|
||||||
|
/**
|
||||||
|
* struct async_state - keep GPIO flash state
|
||||||
|
* @mtd: MTD state for this mapping
|
||||||
|
* @map: MTD map state for this flash
|
||||||
|
* @gpio_count: number of GPIOs used to address
|
||||||
|
* @gpio_addrs: array of GPIOs to twiddle
|
||||||
|
* @gpio_values: cached GPIO values
|
||||||
|
* @win_size: dedicated memory size (if no GPIOs)
|
||||||
|
*/
|
||||||
|
struct async_state {
|
||||||
|
struct mtd_info *mtd;
|
||||||
|
struct map_info map;
|
||||||
|
size_t gpio_count;
|
||||||
|
unsigned *gpio_addrs;
|
||||||
|
int *gpio_values;
|
||||||
|
unsigned long win_size;
|
||||||
|
};
|
||||||
|
#define gf_map_info_to_state(mi) ((struct async_state *)(mi)->map_priv_1)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* gf_set_gpios() - set GPIO address lines to access specified flash offset
|
||||||
|
* @state: GPIO flash state
|
||||||
|
* @ofs: desired offset to access
|
||||||
|
*
|
||||||
|
* Rather than call the GPIO framework every time, cache the last-programmed
|
||||||
|
* value. This speeds up sequential accesses (which are by far the most common
|
||||||
|
* type). We rely on the GPIO framework to treat non-zero value as high so
|
||||||
|
* that we don't have to normalize the bits.
|
||||||
|
*/
|
||||||
|
static void gf_set_gpios(struct async_state *state, unsigned long ofs)
|
||||||
|
{
|
||||||
|
size_t i = 0;
|
||||||
|
int value;
|
||||||
|
ofs /= state->win_size;
|
||||||
|
do {
|
||||||
|
value = ofs & (1 << i);
|
||||||
|
if (state->gpio_values[i] != value) {
|
||||||
|
gpio_set_value(state->gpio_addrs[i], value);
|
||||||
|
state->gpio_values[i] = value;
|
||||||
|
}
|
||||||
|
} while (++i < state->gpio_count);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* gf_read() - read a word at the specified offset
|
||||||
|
* @map: MTD map state
|
||||||
|
* @ofs: desired offset to read
|
||||||
|
*/
|
||||||
|
static map_word gf_read(struct map_info *map, unsigned long ofs)
|
||||||
|
{
|
||||||
|
struct async_state *state = gf_map_info_to_state(map);
|
||||||
|
uint16_t word;
|
||||||
|
map_word test;
|
||||||
|
|
||||||
|
gf_set_gpios(state, ofs);
|
||||||
|
|
||||||
|
word = readw(map->virt + (ofs % state->win_size));
|
||||||
|
test.x[0] = word;
|
||||||
|
return test;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* gf_copy_from() - copy a chunk of data from the flash
|
||||||
|
* @map: MTD map state
|
||||||
|
* @to: memory to copy to
|
||||||
|
* @from: flash offset to copy from
|
||||||
|
* @len: how much to copy
|
||||||
|
*
|
||||||
|
* We rely on the MTD layer to chunk up copies such that a single request here
|
||||||
|
* will not cross a window size. This allows us to only wiggle the GPIOs once
|
||||||
|
* before falling back to a normal memcpy. Reading the higher layer code shows
|
||||||
|
* that this is indeed the case, but add a BUG_ON() to future proof.
|
||||||
|
*/
|
||||||
|
static void gf_copy_from(struct map_info *map, void *to, unsigned long from, ssize_t len)
|
||||||
|
{
|
||||||
|
struct async_state *state = gf_map_info_to_state(map);
|
||||||
|
|
||||||
|
gf_set_gpios(state, from);
|
||||||
|
|
||||||
|
/* BUG if operation crosses the win_size */
|
||||||
|
BUG_ON(!((from + len) % state->win_size <= (from + len)));
|
||||||
|
|
||||||
|
/* operation does not cross the win_size, so one shot it */
|
||||||
|
memcpy_fromio(to, map->virt + (from % state->win_size), len);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* gf_write() - write a word at the specified offset
|
||||||
|
* @map: MTD map state
|
||||||
|
* @ofs: desired offset to write
|
||||||
|
*/
|
||||||
|
static void gf_write(struct map_info *map, map_word d1, unsigned long ofs)
|
||||||
|
{
|
||||||
|
struct async_state *state = gf_map_info_to_state(map);
|
||||||
|
uint16_t d;
|
||||||
|
|
||||||
|
gf_set_gpios(state, ofs);
|
||||||
|
|
||||||
|
d = d1.x[0];
|
||||||
|
writew(d, map->virt + (ofs % state->win_size));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* gf_copy_to() - copy a chunk of data to the flash
|
||||||
|
* @map: MTD map state
|
||||||
|
* @to: flash offset to copy to
|
||||||
|
* @from: memory to copy from
|
||||||
|
* @len: how much to copy
|
||||||
|
*
|
||||||
|
* See gf_copy_from() caveat.
|
||||||
|
*/
|
||||||
|
static void gf_copy_to(struct map_info *map, unsigned long to, const void *from, ssize_t len)
|
||||||
|
{
|
||||||
|
struct async_state *state = gf_map_info_to_state(map);
|
||||||
|
|
||||||
|
gf_set_gpios(state, to);
|
||||||
|
|
||||||
|
/* BUG if operation crosses the win_size */
|
||||||
|
BUG_ON(!((to + len) % state->win_size <= (to + len)));
|
||||||
|
|
||||||
|
/* operation does not cross the win_size, so one shot it */
|
||||||
|
memcpy_toio(map->virt + (to % state->win_size), from, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef CONFIG_MTD_PARTITIONS
|
||||||
|
static const char *part_probe_types[] = { "cmdlinepart", "RedBoot", NULL };
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/**
|
||||||
|
* gpio_flash_probe() - setup a mapping for a GPIO assisted flash
|
||||||
|
* @pdev: platform device
|
||||||
|
*
|
||||||
|
* The platform resource layout expected looks something like:
|
||||||
|
* struct mtd_partition partitions[] = { ... };
|
||||||
|
* struct physmap_flash_data flash_data = { ... };
|
||||||
|
* unsigned flash_gpios[] = { GPIO_XX, GPIO_XX, ... };
|
||||||
|
* struct resource flash_resource[] = {
|
||||||
|
* {
|
||||||
|
* .name = "cfi_probe",
|
||||||
|
* .start = 0x20000000,
|
||||||
|
* .end = 0x201fffff,
|
||||||
|
* .flags = IORESOURCE_MEM,
|
||||||
|
* }, {
|
||||||
|
* .start = (unsigned long)flash_gpios,
|
||||||
|
* .end = ARRAY_SIZE(flash_gpios),
|
||||||
|
* .flags = IORESOURCE_IRQ,
|
||||||
|
* }
|
||||||
|
* };
|
||||||
|
* struct platform_device flash_device = {
|
||||||
|
* .name = "gpio-addr-flash",
|
||||||
|
* .dev = { .platform_data = &flash_data, },
|
||||||
|
* .num_resources = ARRAY_SIZE(flash_resource),
|
||||||
|
* .resource = flash_resource,
|
||||||
|
* ...
|
||||||
|
* };
|
||||||
|
*/
|
||||||
|
static int __devinit gpio_flash_probe(struct platform_device *pdev)
|
||||||
|
{
|
||||||
|
int ret;
|
||||||
|
size_t i, arr_size;
|
||||||
|
struct physmap_flash_data *pdata;
|
||||||
|
struct resource *memory;
|
||||||
|
struct resource *gpios;
|
||||||
|
struct async_state *state;
|
||||||
|
|
||||||
|
pdata = pdev->dev.platform_data;
|
||||||
|
memory = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||||
|
gpios = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
|
||||||
|
|
||||||
|
if (!memory || !gpios || !gpios->end)
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
arr_size = sizeof(int) * gpios->end;
|
||||||
|
state = kzalloc(sizeof(*state) + arr_size, GFP_KERNEL);
|
||||||
|
if (!state)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
state->gpio_count = gpios->end;
|
||||||
|
state->gpio_addrs = (void *)gpios->start;
|
||||||
|
state->gpio_values = (void *)(state + 1);
|
||||||
|
state->win_size = memory->end - memory->start + 1;
|
||||||
|
memset(state->gpio_values, 0xff, arr_size);
|
||||||
|
|
||||||
|
state->map.name = DRIVER_NAME;
|
||||||
|
state->map.read = gf_read;
|
||||||
|
state->map.copy_from = gf_copy_from;
|
||||||
|
state->map.write = gf_write;
|
||||||
|
state->map.copy_to = gf_copy_to;
|
||||||
|
state->map.bankwidth = pdata->width;
|
||||||
|
state->map.size = state->win_size * (1 << state->gpio_count);
|
||||||
|
state->map.virt = (void __iomem *)memory->start;
|
||||||
|
state->map.phys = NO_XIP;
|
||||||
|
state->map.map_priv_1 = (unsigned long)state;
|
||||||
|
|
||||||
|
platform_set_drvdata(pdev, state);
|
||||||
|
|
||||||
|
i = 0;
|
||||||
|
do {
|
||||||
|
if (gpio_request(state->gpio_addrs[i], DRIVER_NAME)) {
|
||||||
|
pr_devinit(KERN_ERR PFX "failed to request gpio %d\n",
|
||||||
|
state->gpio_addrs[i]);
|
||||||
|
while (i--)
|
||||||
|
gpio_free(state->gpio_addrs[i]);
|
||||||
|
kfree(state);
|
||||||
|
return -EBUSY;
|
||||||
|
}
|
||||||
|
gpio_direction_output(state->gpio_addrs[i], 0);
|
||||||
|
} while (++i < state->gpio_count);
|
||||||
|
|
||||||
|
pr_devinit(KERN_NOTICE PFX "probing %d-bit flash bus\n",
|
||||||
|
state->map.bankwidth * 8);
|
||||||
|
state->mtd = do_map_probe(memory->name, &state->map);
|
||||||
|
if (!state->mtd) {
|
||||||
|
for (i = 0; i < state->gpio_count; ++i)
|
||||||
|
gpio_free(state->gpio_addrs[i]);
|
||||||
|
kfree(state);
|
||||||
|
return -ENXIO;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef CONFIG_MTD_PARTITIONS
|
||||||
|
ret = parse_mtd_partitions(state->mtd, part_probe_types, &pdata->parts, 0);
|
||||||
|
if (ret > 0) {
|
||||||
|
pr_devinit(KERN_NOTICE PFX "Using commandline partition definition\n");
|
||||||
|
add_mtd_partitions(state->mtd, pdata->parts, ret);
|
||||||
|
kfree(pdata->parts);
|
||||||
|
|
||||||
|
} else if (pdata->nr_parts) {
|
||||||
|
pr_devinit(KERN_NOTICE PFX "Using board partition definition\n");
|
||||||
|
add_mtd_partitions(state->mtd, pdata->parts, pdata->nr_parts);
|
||||||
|
|
||||||
|
} else
|
||||||
|
#endif
|
||||||
|
{
|
||||||
|
pr_devinit(KERN_NOTICE PFX "no partition info available, registering whole flash at once\n");
|
||||||
|
add_mtd_device(state->mtd);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int __devexit gpio_flash_remove(struct platform_device *pdev)
|
||||||
|
{
|
||||||
|
struct async_state *state = platform_get_drvdata(pdev);
|
||||||
|
size_t i = 0;
|
||||||
|
do {
|
||||||
|
gpio_free(state->gpio_addrs[i]);
|
||||||
|
} while (++i < state->gpio_count);
|
||||||
|
#ifdef CONFIG_MTD_PARTITIONS
|
||||||
|
del_mtd_partitions(state->mtd);
|
||||||
|
#endif
|
||||||
|
map_destroy(state->mtd);
|
||||||
|
kfree(state);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct platform_driver gpio_flash_driver = {
|
||||||
|
.probe = gpio_flash_probe,
|
||||||
|
.remove = __devexit_p(gpio_flash_remove),
|
||||||
|
.driver = {
|
||||||
|
.name = DRIVER_NAME,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
static int __init gpio_flash_init(void)
|
||||||
|
{
|
||||||
|
return platform_driver_register(&gpio_flash_driver);
|
||||||
|
}
|
||||||
|
module_init(gpio_flash_init);
|
||||||
|
|
||||||
|
static void __exit gpio_flash_exit(void)
|
||||||
|
{
|
||||||
|
platform_driver_unregister(&gpio_flash_driver);
|
||||||
|
}
|
||||||
|
module_exit(gpio_flash_exit);
|
||||||
|
|
||||||
|
MODULE_AUTHOR("Mike Frysinger <vapier@gentoo.org>");
|
||||||
|
MODULE_DESCRIPTION("MTD map driver for flashes addressed physically and with gpios");
|
||||||
|
MODULE_LICENSE("GPL");
|
|
@ -190,6 +190,7 @@ static int __devinit of_flash_probe(struct of_device *dev,
|
||||||
const u32 *p;
|
const u32 *p;
|
||||||
int reg_tuple_size;
|
int reg_tuple_size;
|
||||||
struct mtd_info **mtd_list = NULL;
|
struct mtd_info **mtd_list = NULL;
|
||||||
|
resource_size_t res_size;
|
||||||
|
|
||||||
reg_tuple_size = (of_n_addr_cells(dp) + of_n_size_cells(dp)) * sizeof(u32);
|
reg_tuple_size = (of_n_addr_cells(dp) + of_n_size_cells(dp)) * sizeof(u32);
|
||||||
|
|
||||||
|
@ -204,7 +205,7 @@ static int __devinit of_flash_probe(struct of_device *dev,
|
||||||
dev_err(&dev->dev, "Malformed reg property on %s\n",
|
dev_err(&dev->dev, "Malformed reg property on %s\n",
|
||||||
dev->node->full_name);
|
dev->node->full_name);
|
||||||
err = -EINVAL;
|
err = -EINVAL;
|
||||||
goto err_out;
|
goto err_flash_remove;
|
||||||
}
|
}
|
||||||
count /= reg_tuple_size;
|
count /= reg_tuple_size;
|
||||||
|
|
||||||
|
@ -212,14 +213,14 @@ static int __devinit of_flash_probe(struct of_device *dev,
|
||||||
info = kzalloc(sizeof(struct of_flash) +
|
info = kzalloc(sizeof(struct of_flash) +
|
||||||
sizeof(struct of_flash_list) * count, GFP_KERNEL);
|
sizeof(struct of_flash_list) * count, GFP_KERNEL);
|
||||||
if (!info)
|
if (!info)
|
||||||
goto err_out;
|
goto err_flash_remove;
|
||||||
|
|
||||||
mtd_list = kzalloc(sizeof(struct mtd_info) * count, GFP_KERNEL);
|
|
||||||
if (!info)
|
|
||||||
goto err_out;
|
|
||||||
|
|
||||||
dev_set_drvdata(&dev->dev, info);
|
dev_set_drvdata(&dev->dev, info);
|
||||||
|
|
||||||
|
mtd_list = kzalloc(sizeof(struct mtd_info) * count, GFP_KERNEL);
|
||||||
|
if (!mtd_list)
|
||||||
|
goto err_flash_remove;
|
||||||
|
|
||||||
for (i = 0; i < count; i++) {
|
for (i = 0; i < count; i++) {
|
||||||
err = -ENXIO;
|
err = -ENXIO;
|
||||||
if (of_address_to_resource(dp, i, &res)) {
|
if (of_address_to_resource(dp, i, &res)) {
|
||||||
|
@ -233,8 +234,8 @@ static int __devinit of_flash_probe(struct of_device *dev,
|
||||||
(unsigned long long)res.end);
|
(unsigned long long)res.end);
|
||||||
|
|
||||||
err = -EBUSY;
|
err = -EBUSY;
|
||||||
info->list[i].res = request_mem_region(res.start, res.end -
|
res_size = resource_size(&res);
|
||||||
res.start + 1,
|
info->list[i].res = request_mem_region(res.start, res_size,
|
||||||
dev_name(&dev->dev));
|
dev_name(&dev->dev));
|
||||||
if (!info->list[i].res)
|
if (!info->list[i].res)
|
||||||
goto err_out;
|
goto err_out;
|
||||||
|
@ -249,7 +250,7 @@ static int __devinit of_flash_probe(struct of_device *dev,
|
||||||
|
|
||||||
info->list[i].map.name = dev_name(&dev->dev);
|
info->list[i].map.name = dev_name(&dev->dev);
|
||||||
info->list[i].map.phys = res.start;
|
info->list[i].map.phys = res.start;
|
||||||
info->list[i].map.size = res.end - res.start + 1;
|
info->list[i].map.size = res_size;
|
||||||
info->list[i].map.bankwidth = *width;
|
info->list[i].map.bankwidth = *width;
|
||||||
|
|
||||||
err = -ENOMEM;
|
err = -ENOMEM;
|
||||||
|
@ -338,6 +339,7 @@ static int __devinit of_flash_probe(struct of_device *dev,
|
||||||
|
|
||||||
err_out:
|
err_out:
|
||||||
kfree(mtd_list);
|
kfree(mtd_list);
|
||||||
|
err_flash_remove:
|
||||||
of_flash_remove(dev);
|
of_flash_remove(dev);
|
||||||
|
|
||||||
return err;
|
return err;
|
||||||
|
@ -359,6 +361,10 @@ static struct of_device_id of_flash_match[] = {
|
||||||
.compatible = "jedec-flash",
|
.compatible = "jedec-flash",
|
||||||
.data = (void *)"jedec_probe",
|
.data = (void *)"jedec_probe",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
.compatible = "mtd-ram",
|
||||||
|
.data = (void *)"map_ram",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
.type = "rom",
|
.type = "rom",
|
||||||
.compatible = "direct-mapped"
|
.compatible = "direct-mapped"
|
||||||
|
|
|
@ -175,7 +175,7 @@ static int platram_probe(struct platform_device *pdev)
|
||||||
/* setup map parameters */
|
/* setup map parameters */
|
||||||
|
|
||||||
info->map.phys = res->start;
|
info->map.phys = res->start;
|
||||||
info->map.size = (res->end - res->start) + 1;
|
info->map.size = resource_size(res);
|
||||||
info->map.name = pdata->mapname != NULL ?
|
info->map.name = pdata->mapname != NULL ?
|
||||||
(char *)pdata->mapname : (char *)pdev->name;
|
(char *)pdata->mapname : (char *)pdev->name;
|
||||||
info->map.bankwidth = pdata->bankwidth;
|
info->map.bankwidth = pdata->bankwidth;
|
||||||
|
|
|
@ -50,7 +50,7 @@ static int fcnt;
|
||||||
|
|
||||||
static int __init init_msp_flash(void)
|
static int __init init_msp_flash(void)
|
||||||
{
|
{
|
||||||
int i, j;
|
int i, j, ret = -ENOMEM;
|
||||||
int offset, coff;
|
int offset, coff;
|
||||||
char *env;
|
char *env;
|
||||||
int pcnt;
|
int pcnt;
|
||||||
|
@ -75,14 +75,16 @@ static int __init init_msp_flash(void)
|
||||||
printk(KERN_NOTICE "Found %d PMC flash devices\n", fcnt);
|
printk(KERN_NOTICE "Found %d PMC flash devices\n", fcnt);
|
||||||
|
|
||||||
msp_flash = kmalloc(fcnt * sizeof(struct map_info *), GFP_KERNEL);
|
msp_flash = kmalloc(fcnt * sizeof(struct map_info *), GFP_KERNEL);
|
||||||
msp_parts = kmalloc(fcnt * sizeof(struct mtd_partition *), GFP_KERNEL);
|
if (!msp_flash)
|
||||||
msp_maps = kcalloc(fcnt, sizeof(struct mtd_info), GFP_KERNEL);
|
|
||||||
if (!msp_flash || !msp_parts || !msp_maps) {
|
|
||||||
kfree(msp_maps);
|
|
||||||
kfree(msp_parts);
|
|
||||||
kfree(msp_flash);
|
|
||||||
return -ENOMEM;
|
return -ENOMEM;
|
||||||
}
|
|
||||||
|
msp_parts = kmalloc(fcnt * sizeof(struct mtd_partition *), GFP_KERNEL);
|
||||||
|
if (!msp_parts)
|
||||||
|
goto free_msp_flash;
|
||||||
|
|
||||||
|
msp_maps = kcalloc(fcnt, sizeof(struct mtd_info), GFP_KERNEL);
|
||||||
|
if (!msp_maps)
|
||||||
|
goto free_msp_parts;
|
||||||
|
|
||||||
/* loop over the flash devices, initializing each */
|
/* loop over the flash devices, initializing each */
|
||||||
for (i = 0; i < fcnt; i++) {
|
for (i = 0; i < fcnt; i++) {
|
||||||
|
@ -100,13 +102,18 @@ static int __init init_msp_flash(void)
|
||||||
|
|
||||||
msp_parts[i] = kcalloc(pcnt, sizeof(struct mtd_partition),
|
msp_parts[i] = kcalloc(pcnt, sizeof(struct mtd_partition),
|
||||||
GFP_KERNEL);
|
GFP_KERNEL);
|
||||||
|
if (!msp_parts[i])
|
||||||
|
goto cleanup_loop;
|
||||||
|
|
||||||
/* now initialize the devices proper */
|
/* now initialize the devices proper */
|
||||||
flash_name[5] = '0' + i;
|
flash_name[5] = '0' + i;
|
||||||
env = prom_getenv(flash_name);
|
env = prom_getenv(flash_name);
|
||||||
|
|
||||||
if (sscanf(env, "%x:%x", &addr, &size) < 2)
|
if (sscanf(env, "%x:%x", &addr, &size) < 2) {
|
||||||
return -ENXIO;
|
ret = -ENXIO;
|
||||||
|
kfree(msp_parts[i]);
|
||||||
|
goto cleanup_loop;
|
||||||
|
}
|
||||||
addr = CPHYSADDR(addr);
|
addr = CPHYSADDR(addr);
|
||||||
|
|
||||||
printk(KERN_NOTICE
|
printk(KERN_NOTICE
|
||||||
|
@ -122,13 +129,23 @@ static int __init init_msp_flash(void)
|
||||||
*/
|
*/
|
||||||
if (size > CONFIG_MSP_FLASH_MAP_LIMIT)
|
if (size > CONFIG_MSP_FLASH_MAP_LIMIT)
|
||||||
size = CONFIG_MSP_FLASH_MAP_LIMIT;
|
size = CONFIG_MSP_FLASH_MAP_LIMIT;
|
||||||
msp_maps[i].virt = ioremap(addr, size);
|
|
||||||
msp_maps[i].bankwidth = 1;
|
|
||||||
msp_maps[i].name = strncpy(kmalloc(7, GFP_KERNEL),
|
|
||||||
flash_name, 7);
|
|
||||||
|
|
||||||
if (msp_maps[i].virt == NULL)
|
msp_maps[i].virt = ioremap(addr, size);
|
||||||
return -ENXIO;
|
if (msp_maps[i].virt == NULL) {
|
||||||
|
ret = -ENXIO;
|
||||||
|
kfree(msp_parts[i]);
|
||||||
|
goto cleanup_loop;
|
||||||
|
}
|
||||||
|
|
||||||
|
msp_maps[i].bankwidth = 1;
|
||||||
|
msp_maps[i].name = kmalloc(7, GFP_KERNEL);
|
||||||
|
if (!msp_maps[i].name) {
|
||||||
|
iounmap(msp_maps[i].virt);
|
||||||
|
kfree(msp_parts[i]);
|
||||||
|
goto cleanup_loop;
|
||||||
|
}
|
||||||
|
|
||||||
|
msp_maps[i].name = strncpy(msp_maps[i].name, flash_name, 7);
|
||||||
|
|
||||||
for (j = 0; j < pcnt; j++) {
|
for (j = 0; j < pcnt; j++) {
|
||||||
part_name[5] = '0' + i;
|
part_name[5] = '0' + i;
|
||||||
|
@ -136,8 +153,14 @@ static int __init init_msp_flash(void)
|
||||||
|
|
||||||
env = prom_getenv(part_name);
|
env = prom_getenv(part_name);
|
||||||
|
|
||||||
if (sscanf(env, "%x:%x:%n", &offset, &size, &coff) < 2)
|
if (sscanf(env, "%x:%x:%n", &offset, &size,
|
||||||
return -ENXIO;
|
&coff) < 2) {
|
||||||
|
ret = -ENXIO;
|
||||||
|
kfree(msp_maps[i].name);
|
||||||
|
iounmap(msp_maps[i].virt);
|
||||||
|
kfree(msp_parts[i]);
|
||||||
|
goto cleanup_loop;
|
||||||
|
}
|
||||||
|
|
||||||
msp_parts[i][j].size = size;
|
msp_parts[i][j].size = size;
|
||||||
msp_parts[i][j].offset = offset;
|
msp_parts[i][j].offset = offset;
|
||||||
|
@ -152,18 +175,37 @@ static int __init init_msp_flash(void)
|
||||||
add_mtd_partitions(msp_flash[i], msp_parts[i], pcnt);
|
add_mtd_partitions(msp_flash[i], msp_parts[i], pcnt);
|
||||||
} else {
|
} else {
|
||||||
printk(KERN_ERR "map probe failed for flash\n");
|
printk(KERN_ERR "map probe failed for flash\n");
|
||||||
return -ENXIO;
|
ret = -ENXIO;
|
||||||
|
kfree(msp_maps[i].name);
|
||||||
|
iounmap(msp_maps[i].virt);
|
||||||
|
kfree(msp_parts[i]);
|
||||||
|
goto cleanup_loop;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
|
cleanup_loop:
|
||||||
|
while (i--) {
|
||||||
|
del_mtd_partitions(msp_flash[i]);
|
||||||
|
map_destroy(msp_flash[i]);
|
||||||
|
kfree(msp_maps[i].name);
|
||||||
|
iounmap(msp_maps[i].virt);
|
||||||
|
kfree(msp_parts[i]);
|
||||||
|
}
|
||||||
|
kfree(msp_maps);
|
||||||
|
free_msp_parts:
|
||||||
|
kfree(msp_parts);
|
||||||
|
free_msp_flash:
|
||||||
|
kfree(msp_flash);
|
||||||
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void __exit cleanup_msp_flash(void)
|
static void __exit cleanup_msp_flash(void)
|
||||||
{
|
{
|
||||||
int i;
|
int i;
|
||||||
|
|
||||||
for (i = 0; i < sizeof(msp_flash) / sizeof(struct mtd_info **); i++) {
|
for (i = 0; i < fcnt; i++) {
|
||||||
del_mtd_partitions(msp_flash[i]);
|
del_mtd_partitions(msp_flash[i]);
|
||||||
map_destroy(msp_flash[i]);
|
map_destroy(msp_flash[i]);
|
||||||
iounmap((void *)msp_maps[i].virt);
|
iounmap((void *)msp_maps[i].virt);
|
||||||
|
|
|
@ -89,7 +89,11 @@ static int __init uclinux_mtd_init(void)
|
||||||
mtd->priv = mapp;
|
mtd->priv = mapp;
|
||||||
|
|
||||||
uclinux_ram_mtdinfo = mtd;
|
uclinux_ram_mtdinfo = mtd;
|
||||||
|
#ifdef CONFIG_MTD_PARTITIONS
|
||||||
add_mtd_partitions(mtd, uclinux_romfs, NUM_PARTITIONS);
|
add_mtd_partitions(mtd, uclinux_romfs, NUM_PARTITIONS);
|
||||||
|
#else
|
||||||
|
add_mtd_device(mtd);
|
||||||
|
#endif
|
||||||
|
|
||||||
return(0);
|
return(0);
|
||||||
}
|
}
|
||||||
|
@ -99,7 +103,11 @@ static int __init uclinux_mtd_init(void)
|
||||||
static void __exit uclinux_mtd_cleanup(void)
|
static void __exit uclinux_mtd_cleanup(void)
|
||||||
{
|
{
|
||||||
if (uclinux_ram_mtdinfo) {
|
if (uclinux_ram_mtdinfo) {
|
||||||
|
#ifdef CONFIG_MTD_PARTITIONS
|
||||||
del_mtd_partitions(uclinux_ram_mtdinfo);
|
del_mtd_partitions(uclinux_ram_mtdinfo);
|
||||||
|
#else
|
||||||
|
del_mtd_device(uclinux_ram_mtdinfo);
|
||||||
|
#endif
|
||||||
map_destroy(uclinux_ram_mtdinfo);
|
map_destroy(uclinux_ram_mtdinfo);
|
||||||
uclinux_ram_mtdinfo = NULL;
|
uclinux_ram_mtdinfo = NULL;
|
||||||
}
|
}
|
||||||
|
|
|
@ -84,7 +84,7 @@ static int erase_write (struct mtd_info *mtd, unsigned long pos,
|
||||||
remove_wait_queue(&wait_q, &wait);
|
remove_wait_queue(&wait_q, &wait);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Next, writhe data to flash.
|
* Next, write the data to flash.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
ret = mtd->write(mtd, pos, len, &retlen, buf);
|
ret = mtd->write(mtd, pos, len, &retlen, buf);
|
||||||
|
|
|
@ -427,7 +427,7 @@ static int concat_erase(struct mtd_info *mtd, struct erase_info *instr)
|
||||||
* to-be-erased area begins. Verify that the starting
|
* to-be-erased area begins. Verify that the starting
|
||||||
* offset is aligned to this region's erase size:
|
* offset is aligned to this region's erase size:
|
||||||
*/
|
*/
|
||||||
if (instr->addr & (erase_regions[i].erasesize - 1))
|
if (i < 0 || instr->addr & (erase_regions[i].erasesize - 1))
|
||||||
return -EINVAL;
|
return -EINVAL;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -440,8 +440,8 @@ static int concat_erase(struct mtd_info *mtd, struct erase_info *instr)
|
||||||
/*
|
/*
|
||||||
* check if the ending offset is aligned to this region's erase size
|
* check if the ending offset is aligned to this region's erase size
|
||||||
*/
|
*/
|
||||||
if ((instr->addr + instr->len) & (erase_regions[i].erasesize -
|
if (i < 0 || ((instr->addr + instr->len) &
|
||||||
1))
|
(erase_regions[i].erasesize - 1)))
|
||||||
return -EINVAL;
|
return -EINVAL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -213,11 +213,11 @@ static struct attribute *mtd_attrs[] = {
|
||||||
NULL,
|
NULL,
|
||||||
};
|
};
|
||||||
|
|
||||||
struct attribute_group mtd_group = {
|
static struct attribute_group mtd_group = {
|
||||||
.attrs = mtd_attrs,
|
.attrs = mtd_attrs,
|
||||||
};
|
};
|
||||||
|
|
||||||
const struct attribute_group *mtd_groups[] = {
|
static const struct attribute_group *mtd_groups[] = {
|
||||||
&mtd_group,
|
&mtd_group,
|
||||||
NULL,
|
NULL,
|
||||||
};
|
};
|
||||||
|
|
|
@ -453,6 +453,7 @@ static struct mtd_part *add_one_partition(struct mtd_info *master,
|
||||||
for (i = 0; i < max && regions[i].offset <= slave->offset; i++)
|
for (i = 0; i < max && regions[i].offset <= slave->offset; i++)
|
||||||
;
|
;
|
||||||
/* The loop searched for the region _behind_ the first one */
|
/* The loop searched for the region _behind_ the first one */
|
||||||
|
if (i > 0)
|
||||||
i--;
|
i--;
|
||||||
|
|
||||||
/* Pick biggest erasesize */
|
/* Pick biggest erasesize */
|
||||||
|
|
|
@ -80,6 +80,23 @@ config MTD_NAND_OMAP2
|
||||||
help
|
help
|
||||||
Support for NAND flash on Texas Instruments OMAP2 and OMAP3 platforms.
|
Support for NAND flash on Texas Instruments OMAP2 and OMAP3 platforms.
|
||||||
|
|
||||||
|
config MTD_NAND_OMAP_PREFETCH
|
||||||
|
bool "GPMC prefetch support for NAND Flash device"
|
||||||
|
depends on MTD_NAND && MTD_NAND_OMAP2
|
||||||
|
default y
|
||||||
|
help
|
||||||
|
The NAND device can be accessed for Read/Write using GPMC PREFETCH engine
|
||||||
|
to improve the performance.
|
||||||
|
|
||||||
|
config MTD_NAND_OMAP_PREFETCH_DMA
|
||||||
|
depends on MTD_NAND_OMAP_PREFETCH
|
||||||
|
bool "DMA mode"
|
||||||
|
default n
|
||||||
|
help
|
||||||
|
The GPMC PREFETCH engine can be configured eigther in MPU interrupt mode
|
||||||
|
or in DMA interrupt mode.
|
||||||
|
Say y for DMA mode or MPU mode will be used
|
||||||
|
|
||||||
config MTD_NAND_TS7250
|
config MTD_NAND_TS7250
|
||||||
tristate "NAND Flash device on TS-7250 board"
|
tristate "NAND Flash device on TS-7250 board"
|
||||||
depends on MACH_TS72XX
|
depends on MACH_TS72XX
|
||||||
|
@ -426,6 +443,12 @@ config MTD_NAND_MXC
|
||||||
This enables the driver for the NAND flash controller on the
|
This enables the driver for the NAND flash controller on the
|
||||||
MXC processors.
|
MXC processors.
|
||||||
|
|
||||||
|
config MTD_NAND_NOMADIK
|
||||||
|
tristate "ST Nomadik 8815 NAND support"
|
||||||
|
depends on ARCH_NOMADIK
|
||||||
|
help
|
||||||
|
Driver for the NAND flash controller on the Nomadik, with ECC.
|
||||||
|
|
||||||
config MTD_NAND_SH_FLCTL
|
config MTD_NAND_SH_FLCTL
|
||||||
tristate "Support for NAND on Renesas SuperH FLCTL"
|
tristate "Support for NAND on Renesas SuperH FLCTL"
|
||||||
depends on MTD_NAND && SUPERH && CPU_SUBTYPE_SH7723
|
depends on MTD_NAND && SUPERH && CPU_SUBTYPE_SH7723
|
||||||
|
@ -452,4 +475,11 @@ config MTD_NAND_SOCRATES
|
||||||
help
|
help
|
||||||
Enables support for NAND Flash chips wired onto Socrates board.
|
Enables support for NAND Flash chips wired onto Socrates board.
|
||||||
|
|
||||||
|
config MTD_NAND_W90P910
|
||||||
|
tristate "Support for NAND on w90p910 evaluation board."
|
||||||
|
depends on ARCH_W90X900 && MTD_PARTITIONS
|
||||||
|
help
|
||||||
|
This enables the driver for the NAND Flash on evaluation board based
|
||||||
|
on w90p910.
|
||||||
|
|
||||||
endif # MTD_NAND
|
endif # MTD_NAND
|
||||||
|
|
|
@ -40,5 +40,7 @@ obj-$(CONFIG_MTD_NAND_SH_FLCTL) += sh_flctl.o
|
||||||
obj-$(CONFIG_MTD_NAND_MXC) += mxc_nand.o
|
obj-$(CONFIG_MTD_NAND_MXC) += mxc_nand.o
|
||||||
obj-$(CONFIG_MTD_NAND_SOCRATES) += socrates_nand.o
|
obj-$(CONFIG_MTD_NAND_SOCRATES) += socrates_nand.o
|
||||||
obj-$(CONFIG_MTD_NAND_TXX9NDFMC) += txx9ndfmc.o
|
obj-$(CONFIG_MTD_NAND_TXX9NDFMC) += txx9ndfmc.o
|
||||||
|
obj-$(CONFIG_MTD_NAND_W90P910) += w90p910_nand.o
|
||||||
|
obj-$(CONFIG_MTD_NAND_NOMADIK) += nomadik_nand.o
|
||||||
|
|
||||||
nand-objs := nand_base.o nand_bbt.o
|
nand-objs := nand_base.o nand_bbt.o
|
||||||
|
|
|
@ -218,7 +218,7 @@ static int atmel_nand_calculate(struct mtd_info *mtd,
|
||||||
* buf: buffer to store read data
|
* buf: buffer to store read data
|
||||||
*/
|
*/
|
||||||
static int atmel_nand_read_page(struct mtd_info *mtd,
|
static int atmel_nand_read_page(struct mtd_info *mtd,
|
||||||
struct nand_chip *chip, uint8_t *buf)
|
struct nand_chip *chip, uint8_t *buf, int page)
|
||||||
{
|
{
|
||||||
int eccsize = chip->ecc.size;
|
int eccsize = chip->ecc.size;
|
||||||
int eccbytes = chip->ecc.bytes;
|
int eccbytes = chip->ecc.bytes;
|
||||||
|
|
|
@ -381,7 +381,7 @@ static int cafe_nand_read_oob(struct mtd_info *mtd, struct nand_chip *chip,
|
||||||
* we need a special oob layout and handling.
|
* we need a special oob layout and handling.
|
||||||
*/
|
*/
|
||||||
static int cafe_nand_read_page(struct mtd_info *mtd, struct nand_chip *chip,
|
static int cafe_nand_read_page(struct mtd_info *mtd, struct nand_chip *chip,
|
||||||
uint8_t *buf)
|
uint8_t *buf, int page)
|
||||||
{
|
{
|
||||||
struct cafe_priv *cafe = mtd->priv;
|
struct cafe_priv *cafe = mtd->priv;
|
||||||
|
|
||||||
|
|
|
@ -348,6 +348,12 @@ compare:
|
||||||
if (!(syndrome[0] | syndrome[1] | syndrome[2] | syndrome[3]))
|
if (!(syndrome[0] | syndrome[1] | syndrome[2] | syndrome[3]))
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Clear any previous address calculation by doing a dummy read of an
|
||||||
|
* error address register.
|
||||||
|
*/
|
||||||
|
davinci_nand_readl(info, NAND_ERR_ADD1_OFFSET);
|
||||||
|
|
||||||
/* Start address calculation, and wait for it to complete.
|
/* Start address calculation, and wait for it to complete.
|
||||||
* We _could_ start reading more data while this is working,
|
* We _could_ start reading more data while this is working,
|
||||||
* to speed up the overall page read.
|
* to speed up the overall page read.
|
||||||
|
@ -359,8 +365,10 @@ compare:
|
||||||
|
|
||||||
switch ((fsr >> 8) & 0x0f) {
|
switch ((fsr >> 8) & 0x0f) {
|
||||||
case 0: /* no error, should not happen */
|
case 0: /* no error, should not happen */
|
||||||
|
davinci_nand_readl(info, NAND_ERR_ERRVAL1_OFFSET);
|
||||||
return 0;
|
return 0;
|
||||||
case 1: /* five or more errors detected */
|
case 1: /* five or more errors detected */
|
||||||
|
davinci_nand_readl(info, NAND_ERR_ERRVAL1_OFFSET);
|
||||||
return -EIO;
|
return -EIO;
|
||||||
case 2: /* error addresses computed */
|
case 2: /* error addresses computed */
|
||||||
case 3:
|
case 3:
|
||||||
|
@ -500,6 +508,26 @@ static struct nand_ecclayout hwecc4_small __initconst = {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/* An ECC layout for using 4-bit ECC with large-page (2048bytes) flash,
|
||||||
|
* storing ten ECC bytes plus the manufacturer's bad block marker byte,
|
||||||
|
* and not overlapping the default BBT markers.
|
||||||
|
*/
|
||||||
|
static struct nand_ecclayout hwecc4_2048 __initconst = {
|
||||||
|
.eccbytes = 40,
|
||||||
|
.eccpos = {
|
||||||
|
/* at the end of spare sector */
|
||||||
|
24, 25, 26, 27, 28, 29, 30, 31, 32, 33,
|
||||||
|
34, 35, 36, 37, 38, 39, 40, 41, 42, 43,
|
||||||
|
44, 45, 46, 47, 48, 49, 50, 51, 52, 53,
|
||||||
|
54, 55, 56, 57, 58, 59, 60, 61, 62, 63,
|
||||||
|
},
|
||||||
|
.oobfree = {
|
||||||
|
/* 2 bytes at offset 0 hold manufacturer badblock markers */
|
||||||
|
{.offset = 2, .length = 22, },
|
||||||
|
/* 5 bytes at offset 8 hold BBT markers */
|
||||||
|
/* 8 bytes at offset 16 hold JFFS2 clean markers */
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
static int __init nand_davinci_probe(struct platform_device *pdev)
|
static int __init nand_davinci_probe(struct platform_device *pdev)
|
||||||
{
|
{
|
||||||
|
@ -690,15 +718,20 @@ static int __init nand_davinci_probe(struct platform_device *pdev)
|
||||||
info->mtd.oobsize - 16;
|
info->mtd.oobsize - 16;
|
||||||
goto syndrome_done;
|
goto syndrome_done;
|
||||||
}
|
}
|
||||||
|
if (chunks == 4) {
|
||||||
|
info->ecclayout = hwecc4_2048;
|
||||||
|
info->chip.ecc.mode = NAND_ECC_HW_OOB_FIRST;
|
||||||
|
goto syndrome_done;
|
||||||
|
}
|
||||||
|
|
||||||
/* For large page chips we'll be wanting to use a
|
/* 4KiB page chips are not yet supported. The eccpos from
|
||||||
* not-yet-implemented mode that reads OOB data
|
* nand_ecclayout cannot hold 80 bytes and change to eccpos[]
|
||||||
* before reading the body of the page, to avoid
|
* breaks userspace ioctl interface with mtd-utils. Once we
|
||||||
* the "infix OOB" model of NAND_ECC_HW_SYNDROME
|
* resolve this issue, NAND_ECC_HW_OOB_FIRST mode can be used
|
||||||
* (and preserve manufacturer badblock markings).
|
* for the 4KiB page chips.
|
||||||
*/
|
*/
|
||||||
dev_warn(&pdev->dev, "no 4-bit ECC support yet "
|
dev_warn(&pdev->dev, "no 4-bit ECC support yet "
|
||||||
"for large page NAND\n");
|
"for 4KiB-page NAND\n");
|
||||||
ret = -EIO;
|
ret = -EIO;
|
||||||
goto err_scan;
|
goto err_scan;
|
||||||
|
|
||||||
|
|
|
@ -739,7 +739,8 @@ static int fsl_elbc_chip_init_tail(struct mtd_info *mtd)
|
||||||
|
|
||||||
static int fsl_elbc_read_page(struct mtd_info *mtd,
|
static int fsl_elbc_read_page(struct mtd_info *mtd,
|
||||||
struct nand_chip *chip,
|
struct nand_chip *chip,
|
||||||
uint8_t *buf)
|
uint8_t *buf,
|
||||||
|
int page)
|
||||||
{
|
{
|
||||||
fsl_elbc_read_buf(mtd, buf, mtd->writesize);
|
fsl_elbc_read_buf(mtd, buf, mtd->writesize);
|
||||||
fsl_elbc_read_buf(mtd, chip->oob_poi, mtd->oobsize);
|
fsl_elbc_read_buf(mtd, chip->oob_poi, mtd->oobsize);
|
||||||
|
|
|
@ -857,6 +857,17 @@ static void mxc_nand_command(struct mtd_info *mtd, unsigned command,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Define some generic bad / good block scan pattern which are used
|
||||||
|
* while scanning a device for factory marked good / bad blocks. */
|
||||||
|
static uint8_t scan_ff_pattern[] = { 0xff, 0xff };
|
||||||
|
|
||||||
|
static struct nand_bbt_descr smallpage_memorybased = {
|
||||||
|
.options = NAND_BBT_SCAN2NDPAGE,
|
||||||
|
.offs = 5,
|
||||||
|
.len = 1,
|
||||||
|
.pattern = scan_ff_pattern
|
||||||
|
};
|
||||||
|
|
||||||
static int __init mxcnd_probe(struct platform_device *pdev)
|
static int __init mxcnd_probe(struct platform_device *pdev)
|
||||||
{
|
{
|
||||||
struct nand_chip *this;
|
struct nand_chip *this;
|
||||||
|
@ -973,7 +984,10 @@ static int __init mxcnd_probe(struct platform_device *pdev)
|
||||||
goto escan;
|
goto escan;
|
||||||
}
|
}
|
||||||
|
|
||||||
host->pagesize_2k = (mtd->writesize == 2048) ? 1 : 0;
|
if (mtd->writesize == 2048) {
|
||||||
|
host->pagesize_2k = 1;
|
||||||
|
this->badblock_pattern = &smallpage_memorybased;
|
||||||
|
}
|
||||||
|
|
||||||
if (this->ecc.mode == NAND_ECC_HW) {
|
if (this->ecc.mode == NAND_ECC_HW) {
|
||||||
switch (mtd->oobsize) {
|
switch (mtd->oobsize) {
|
||||||
|
|
|
@ -688,8 +688,7 @@ nand_get_device(struct nand_chip *chip, struct mtd_info *mtd, int new_state)
|
||||||
retry:
|
retry:
|
||||||
spin_lock(lock);
|
spin_lock(lock);
|
||||||
|
|
||||||
/* Hardware controller shared among independend devices */
|
/* Hardware controller shared among independent devices */
|
||||||
/* Hardware controller shared among independend devices */
|
|
||||||
if (!chip->controller->active)
|
if (!chip->controller->active)
|
||||||
chip->controller->active = chip;
|
chip->controller->active = chip;
|
||||||
|
|
||||||
|
@ -766,7 +765,7 @@ static int nand_wait(struct mtd_info *mtd, struct nand_chip *chip)
|
||||||
* Not for syndrome calculating ecc controllers, which use a special oob layout
|
* Not for syndrome calculating ecc controllers, which use a special oob layout
|
||||||
*/
|
*/
|
||||||
static int nand_read_page_raw(struct mtd_info *mtd, struct nand_chip *chip,
|
static int nand_read_page_raw(struct mtd_info *mtd, struct nand_chip *chip,
|
||||||
uint8_t *buf)
|
uint8_t *buf, int page)
|
||||||
{
|
{
|
||||||
chip->read_buf(mtd, buf, mtd->writesize);
|
chip->read_buf(mtd, buf, mtd->writesize);
|
||||||
chip->read_buf(mtd, chip->oob_poi, mtd->oobsize);
|
chip->read_buf(mtd, chip->oob_poi, mtd->oobsize);
|
||||||
|
@ -782,7 +781,7 @@ static int nand_read_page_raw(struct mtd_info *mtd, struct nand_chip *chip,
|
||||||
* We need a special oob layout and handling even when OOB isn't used.
|
* We need a special oob layout and handling even when OOB isn't used.
|
||||||
*/
|
*/
|
||||||
static int nand_read_page_raw_syndrome(struct mtd_info *mtd, struct nand_chip *chip,
|
static int nand_read_page_raw_syndrome(struct mtd_info *mtd, struct nand_chip *chip,
|
||||||
uint8_t *buf)
|
uint8_t *buf, int page)
|
||||||
{
|
{
|
||||||
int eccsize = chip->ecc.size;
|
int eccsize = chip->ecc.size;
|
||||||
int eccbytes = chip->ecc.bytes;
|
int eccbytes = chip->ecc.bytes;
|
||||||
|
@ -821,7 +820,7 @@ static int nand_read_page_raw_syndrome(struct mtd_info *mtd, struct nand_chip *c
|
||||||
* @buf: buffer to store read data
|
* @buf: buffer to store read data
|
||||||
*/
|
*/
|
||||||
static int nand_read_page_swecc(struct mtd_info *mtd, struct nand_chip *chip,
|
static int nand_read_page_swecc(struct mtd_info *mtd, struct nand_chip *chip,
|
||||||
uint8_t *buf)
|
uint8_t *buf, int page)
|
||||||
{
|
{
|
||||||
int i, eccsize = chip->ecc.size;
|
int i, eccsize = chip->ecc.size;
|
||||||
int eccbytes = chip->ecc.bytes;
|
int eccbytes = chip->ecc.bytes;
|
||||||
|
@ -831,7 +830,7 @@ static int nand_read_page_swecc(struct mtd_info *mtd, struct nand_chip *chip,
|
||||||
uint8_t *ecc_code = chip->buffers->ecccode;
|
uint8_t *ecc_code = chip->buffers->ecccode;
|
||||||
uint32_t *eccpos = chip->ecc.layout->eccpos;
|
uint32_t *eccpos = chip->ecc.layout->eccpos;
|
||||||
|
|
||||||
chip->ecc.read_page_raw(mtd, chip, buf);
|
chip->ecc.read_page_raw(mtd, chip, buf, page);
|
||||||
|
|
||||||
for (i = 0; eccsteps; eccsteps--, i += eccbytes, p += eccsize)
|
for (i = 0; eccsteps; eccsteps--, i += eccbytes, p += eccsize)
|
||||||
chip->ecc.calculate(mtd, p, &ecc_calc[i]);
|
chip->ecc.calculate(mtd, p, &ecc_calc[i]);
|
||||||
|
@ -944,7 +943,7 @@ static int nand_read_subpage(struct mtd_info *mtd, struct nand_chip *chip, uint3
|
||||||
* Not for syndrome calculating ecc controllers which need a special oob layout
|
* Not for syndrome calculating ecc controllers which need a special oob layout
|
||||||
*/
|
*/
|
||||||
static int nand_read_page_hwecc(struct mtd_info *mtd, struct nand_chip *chip,
|
static int nand_read_page_hwecc(struct mtd_info *mtd, struct nand_chip *chip,
|
||||||
uint8_t *buf)
|
uint8_t *buf, int page)
|
||||||
{
|
{
|
||||||
int i, eccsize = chip->ecc.size;
|
int i, eccsize = chip->ecc.size;
|
||||||
int eccbytes = chip->ecc.bytes;
|
int eccbytes = chip->ecc.bytes;
|
||||||
|
@ -979,6 +978,54 @@ static int nand_read_page_hwecc(struct mtd_info *mtd, struct nand_chip *chip,
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* nand_read_page_hwecc_oob_first - [REPLACABLE] hw ecc, read oob first
|
||||||
|
* @mtd: mtd info structure
|
||||||
|
* @chip: nand chip info structure
|
||||||
|
* @buf: buffer to store read data
|
||||||
|
*
|
||||||
|
* Hardware ECC for large page chips, require OOB to be read first.
|
||||||
|
* For this ECC mode, the write_page method is re-used from ECC_HW.
|
||||||
|
* These methods read/write ECC from the OOB area, unlike the
|
||||||
|
* ECC_HW_SYNDROME support with multiple ECC steps, follows the
|
||||||
|
* "infix ECC" scheme and reads/writes ECC from the data area, by
|
||||||
|
* overwriting the NAND manufacturer bad block markings.
|
||||||
|
*/
|
||||||
|
static int nand_read_page_hwecc_oob_first(struct mtd_info *mtd,
|
||||||
|
struct nand_chip *chip, uint8_t *buf, int page)
|
||||||
|
{
|
||||||
|
int i, eccsize = chip->ecc.size;
|
||||||
|
int eccbytes = chip->ecc.bytes;
|
||||||
|
int eccsteps = chip->ecc.steps;
|
||||||
|
uint8_t *p = buf;
|
||||||
|
uint8_t *ecc_code = chip->buffers->ecccode;
|
||||||
|
uint32_t *eccpos = chip->ecc.layout->eccpos;
|
||||||
|
uint8_t *ecc_calc = chip->buffers->ecccalc;
|
||||||
|
|
||||||
|
/* Read the OOB area first */
|
||||||
|
chip->cmdfunc(mtd, NAND_CMD_READOOB, 0, page);
|
||||||
|
chip->read_buf(mtd, chip->oob_poi, mtd->oobsize);
|
||||||
|
chip->cmdfunc(mtd, NAND_CMD_READ0, 0, page);
|
||||||
|
|
||||||
|
for (i = 0; i < chip->ecc.total; i++)
|
||||||
|
ecc_code[i] = chip->oob_poi[eccpos[i]];
|
||||||
|
|
||||||
|
for (i = 0; eccsteps; eccsteps--, i += eccbytes, p += eccsize) {
|
||||||
|
int stat;
|
||||||
|
|
||||||
|
chip->ecc.hwctl(mtd, NAND_ECC_READ);
|
||||||
|
chip->read_buf(mtd, p, eccsize);
|
||||||
|
chip->ecc.calculate(mtd, p, &ecc_calc[i]);
|
||||||
|
|
||||||
|
stat = chip->ecc.correct(mtd, p, &ecc_code[i], NULL);
|
||||||
|
if (stat < 0)
|
||||||
|
mtd->ecc_stats.failed++;
|
||||||
|
else
|
||||||
|
mtd->ecc_stats.corrected += stat;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* nand_read_page_syndrome - [REPLACABLE] hardware ecc syndrom based page read
|
* nand_read_page_syndrome - [REPLACABLE] hardware ecc syndrom based page read
|
||||||
* @mtd: mtd info structure
|
* @mtd: mtd info structure
|
||||||
|
@ -989,7 +1036,7 @@ static int nand_read_page_hwecc(struct mtd_info *mtd, struct nand_chip *chip,
|
||||||
* we need a special oob layout and handling.
|
* we need a special oob layout and handling.
|
||||||
*/
|
*/
|
||||||
static int nand_read_page_syndrome(struct mtd_info *mtd, struct nand_chip *chip,
|
static int nand_read_page_syndrome(struct mtd_info *mtd, struct nand_chip *chip,
|
||||||
uint8_t *buf)
|
uint8_t *buf, int page)
|
||||||
{
|
{
|
||||||
int i, eccsize = chip->ecc.size;
|
int i, eccsize = chip->ecc.size;
|
||||||
int eccbytes = chip->ecc.bytes;
|
int eccbytes = chip->ecc.bytes;
|
||||||
|
@ -1131,11 +1178,13 @@ static int nand_do_read_ops(struct mtd_info *mtd, loff_t from,
|
||||||
|
|
||||||
/* Now read the page into the buffer */
|
/* Now read the page into the buffer */
|
||||||
if (unlikely(ops->mode == MTD_OOB_RAW))
|
if (unlikely(ops->mode == MTD_OOB_RAW))
|
||||||
ret = chip->ecc.read_page_raw(mtd, chip, bufpoi);
|
ret = chip->ecc.read_page_raw(mtd, chip,
|
||||||
|
bufpoi, page);
|
||||||
else if (!aligned && NAND_SUBPAGE_READ(chip) && !oob)
|
else if (!aligned && NAND_SUBPAGE_READ(chip) && !oob)
|
||||||
ret = chip->ecc.read_subpage(mtd, chip, col, bytes, bufpoi);
|
ret = chip->ecc.read_subpage(mtd, chip, col, bytes, bufpoi);
|
||||||
else
|
else
|
||||||
ret = chip->ecc.read_page(mtd, chip, bufpoi);
|
ret = chip->ecc.read_page(mtd, chip, bufpoi,
|
||||||
|
page);
|
||||||
if (ret < 0)
|
if (ret < 0)
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
@ -1413,8 +1462,8 @@ static int nand_do_read_oob(struct mtd_info *mtd, loff_t from,
|
||||||
int len;
|
int len;
|
||||||
uint8_t *buf = ops->oobbuf;
|
uint8_t *buf = ops->oobbuf;
|
||||||
|
|
||||||
DEBUG(MTD_DEBUG_LEVEL3, "nand_read_oob: from = 0x%08Lx, len = %i\n",
|
DEBUG(MTD_DEBUG_LEVEL3, "%s: from = 0x%08Lx, len = %i\n",
|
||||||
(unsigned long long)from, readlen);
|
__func__, (unsigned long long)from, readlen);
|
||||||
|
|
||||||
if (ops->mode == MTD_OOB_AUTO)
|
if (ops->mode == MTD_OOB_AUTO)
|
||||||
len = chip->ecc.layout->oobavail;
|
len = chip->ecc.layout->oobavail;
|
||||||
|
@ -1422,8 +1471,8 @@ static int nand_do_read_oob(struct mtd_info *mtd, loff_t from,
|
||||||
len = mtd->oobsize;
|
len = mtd->oobsize;
|
||||||
|
|
||||||
if (unlikely(ops->ooboffs >= len)) {
|
if (unlikely(ops->ooboffs >= len)) {
|
||||||
DEBUG(MTD_DEBUG_LEVEL0, "nand_read_oob: "
|
DEBUG(MTD_DEBUG_LEVEL0, "%s: Attempt to start read "
|
||||||
"Attempt to start read outside oob\n");
|
"outside oob\n", __func__);
|
||||||
return -EINVAL;
|
return -EINVAL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1431,8 +1480,8 @@ static int nand_do_read_oob(struct mtd_info *mtd, loff_t from,
|
||||||
if (unlikely(from >= mtd->size ||
|
if (unlikely(from >= mtd->size ||
|
||||||
ops->ooboffs + readlen > ((mtd->size >> chip->page_shift) -
|
ops->ooboffs + readlen > ((mtd->size >> chip->page_shift) -
|
||||||
(from >> chip->page_shift)) * len)) {
|
(from >> chip->page_shift)) * len)) {
|
||||||
DEBUG(MTD_DEBUG_LEVEL0, "nand_read_oob: "
|
DEBUG(MTD_DEBUG_LEVEL0, "%s: Attempt read beyond end "
|
||||||
"Attempt read beyond end of device\n");
|
"of device\n", __func__);
|
||||||
return -EINVAL;
|
return -EINVAL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1506,8 +1555,8 @@ static int nand_read_oob(struct mtd_info *mtd, loff_t from,
|
||||||
|
|
||||||
/* Do not allow reads past end of device */
|
/* Do not allow reads past end of device */
|
||||||
if (ops->datbuf && (from + ops->len) > mtd->size) {
|
if (ops->datbuf && (from + ops->len) > mtd->size) {
|
||||||
DEBUG(MTD_DEBUG_LEVEL0, "nand_read_oob: "
|
DEBUG(MTD_DEBUG_LEVEL0, "%s: Attempt read "
|
||||||
"Attempt read beyond end of device\n");
|
"beyond end of device\n", __func__);
|
||||||
return -EINVAL;
|
return -EINVAL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1816,8 +1865,8 @@ static int nand_do_write_ops(struct mtd_info *mtd, loff_t to,
|
||||||
|
|
||||||
/* reject writes, which are not page aligned */
|
/* reject writes, which are not page aligned */
|
||||||
if (NOTALIGNED(to) || NOTALIGNED(ops->len)) {
|
if (NOTALIGNED(to) || NOTALIGNED(ops->len)) {
|
||||||
printk(KERN_NOTICE "nand_write: "
|
printk(KERN_NOTICE "%s: Attempt to write not "
|
||||||
"Attempt to write not page aligned data\n");
|
"page aligned data\n", __func__);
|
||||||
return -EINVAL;
|
return -EINVAL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1944,8 +1993,8 @@ static int nand_do_write_oob(struct mtd_info *mtd, loff_t to,
|
||||||
int chipnr, page, status, len;
|
int chipnr, page, status, len;
|
||||||
struct nand_chip *chip = mtd->priv;
|
struct nand_chip *chip = mtd->priv;
|
||||||
|
|
||||||
DEBUG(MTD_DEBUG_LEVEL3, "nand_write_oob: to = 0x%08x, len = %i\n",
|
DEBUG(MTD_DEBUG_LEVEL3, "%s: to = 0x%08x, len = %i\n",
|
||||||
(unsigned int)to, (int)ops->ooblen);
|
__func__, (unsigned int)to, (int)ops->ooblen);
|
||||||
|
|
||||||
if (ops->mode == MTD_OOB_AUTO)
|
if (ops->mode == MTD_OOB_AUTO)
|
||||||
len = chip->ecc.layout->oobavail;
|
len = chip->ecc.layout->oobavail;
|
||||||
|
@ -1954,14 +2003,14 @@ static int nand_do_write_oob(struct mtd_info *mtd, loff_t to,
|
||||||
|
|
||||||
/* Do not allow write past end of page */
|
/* Do not allow write past end of page */
|
||||||
if ((ops->ooboffs + ops->ooblen) > len) {
|
if ((ops->ooboffs + ops->ooblen) > len) {
|
||||||
DEBUG(MTD_DEBUG_LEVEL0, "nand_write_oob: "
|
DEBUG(MTD_DEBUG_LEVEL0, "%s: Attempt to write "
|
||||||
"Attempt to write past end of page\n");
|
"past end of page\n", __func__);
|
||||||
return -EINVAL;
|
return -EINVAL;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (unlikely(ops->ooboffs >= len)) {
|
if (unlikely(ops->ooboffs >= len)) {
|
||||||
DEBUG(MTD_DEBUG_LEVEL0, "nand_do_write_oob: "
|
DEBUG(MTD_DEBUG_LEVEL0, "%s: Attempt to start "
|
||||||
"Attempt to start write outside oob\n");
|
"write outside oob\n", __func__);
|
||||||
return -EINVAL;
|
return -EINVAL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1970,8 +2019,8 @@ static int nand_do_write_oob(struct mtd_info *mtd, loff_t to,
|
||||||
ops->ooboffs + ops->ooblen >
|
ops->ooboffs + ops->ooblen >
|
||||||
((mtd->size >> chip->page_shift) -
|
((mtd->size >> chip->page_shift) -
|
||||||
(to >> chip->page_shift)) * len)) {
|
(to >> chip->page_shift)) * len)) {
|
||||||
DEBUG(MTD_DEBUG_LEVEL0, "nand_do_write_oob: "
|
DEBUG(MTD_DEBUG_LEVEL0, "%s: Attempt write beyond "
|
||||||
"Attempt write beyond end of device\n");
|
"end of device\n", __func__);
|
||||||
return -EINVAL;
|
return -EINVAL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2026,8 +2075,8 @@ static int nand_write_oob(struct mtd_info *mtd, loff_t to,
|
||||||
|
|
||||||
/* Do not allow writes past end of device */
|
/* Do not allow writes past end of device */
|
||||||
if (ops->datbuf && (to + ops->len) > mtd->size) {
|
if (ops->datbuf && (to + ops->len) > mtd->size) {
|
||||||
DEBUG(MTD_DEBUG_LEVEL0, "nand_write_oob: "
|
DEBUG(MTD_DEBUG_LEVEL0, "%s: Attempt write beyond "
|
||||||
"Attempt write beyond end of device\n");
|
"end of device\n", __func__);
|
||||||
return -EINVAL;
|
return -EINVAL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2117,26 +2166,27 @@ int nand_erase_nand(struct mtd_info *mtd, struct erase_info *instr,
|
||||||
unsigned int bbt_masked_page = 0xffffffff;
|
unsigned int bbt_masked_page = 0xffffffff;
|
||||||
loff_t len;
|
loff_t len;
|
||||||
|
|
||||||
DEBUG(MTD_DEBUG_LEVEL3, "nand_erase: start = 0x%012llx, len = %llu\n",
|
DEBUG(MTD_DEBUG_LEVEL3, "%s: start = 0x%012llx, len = %llu\n",
|
||||||
(unsigned long long)instr->addr, (unsigned long long)instr->len);
|
__func__, (unsigned long long)instr->addr,
|
||||||
|
(unsigned long long)instr->len);
|
||||||
|
|
||||||
/* Start address must align on block boundary */
|
/* Start address must align on block boundary */
|
||||||
if (instr->addr & ((1 << chip->phys_erase_shift) - 1)) {
|
if (instr->addr & ((1 << chip->phys_erase_shift) - 1)) {
|
||||||
DEBUG(MTD_DEBUG_LEVEL0, "nand_erase: Unaligned address\n");
|
DEBUG(MTD_DEBUG_LEVEL0, "%s: Unaligned address\n", __func__);
|
||||||
return -EINVAL;
|
return -EINVAL;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Length must align on block boundary */
|
/* Length must align on block boundary */
|
||||||
if (instr->len & ((1 << chip->phys_erase_shift) - 1)) {
|
if (instr->len & ((1 << chip->phys_erase_shift) - 1)) {
|
||||||
DEBUG(MTD_DEBUG_LEVEL0, "nand_erase: "
|
DEBUG(MTD_DEBUG_LEVEL0, "%s: Length not block aligned\n",
|
||||||
"Length not block aligned\n");
|
__func__);
|
||||||
return -EINVAL;
|
return -EINVAL;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Do not allow erase past end of device */
|
/* Do not allow erase past end of device */
|
||||||
if ((instr->len + instr->addr) > mtd->size) {
|
if ((instr->len + instr->addr) > mtd->size) {
|
||||||
DEBUG(MTD_DEBUG_LEVEL0, "nand_erase: "
|
DEBUG(MTD_DEBUG_LEVEL0, "%s: Erase past end of device\n",
|
||||||
"Erase past end of device\n");
|
__func__);
|
||||||
return -EINVAL;
|
return -EINVAL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2157,8 +2207,8 @@ int nand_erase_nand(struct mtd_info *mtd, struct erase_info *instr,
|
||||||
|
|
||||||
/* Check, if it is write protected */
|
/* Check, if it is write protected */
|
||||||
if (nand_check_wp(mtd)) {
|
if (nand_check_wp(mtd)) {
|
||||||
DEBUG(MTD_DEBUG_LEVEL0, "nand_erase: "
|
DEBUG(MTD_DEBUG_LEVEL0, "%s: Device is write protected!!!\n",
|
||||||
"Device is write protected!!!\n");
|
__func__);
|
||||||
instr->state = MTD_ERASE_FAILED;
|
instr->state = MTD_ERASE_FAILED;
|
||||||
goto erase_exit;
|
goto erase_exit;
|
||||||
}
|
}
|
||||||
|
@ -2183,8 +2233,8 @@ int nand_erase_nand(struct mtd_info *mtd, struct erase_info *instr,
|
||||||
*/
|
*/
|
||||||
if (nand_block_checkbad(mtd, ((loff_t) page) <<
|
if (nand_block_checkbad(mtd, ((loff_t) page) <<
|
||||||
chip->page_shift, 0, allowbbt)) {
|
chip->page_shift, 0, allowbbt)) {
|
||||||
printk(KERN_WARNING "nand_erase: attempt to erase a "
|
printk(KERN_WARNING "%s: attempt to erase a bad block "
|
||||||
"bad block at page 0x%08x\n", page);
|
"at page 0x%08x\n", __func__, page);
|
||||||
instr->state = MTD_ERASE_FAILED;
|
instr->state = MTD_ERASE_FAILED;
|
||||||
goto erase_exit;
|
goto erase_exit;
|
||||||
}
|
}
|
||||||
|
@ -2211,8 +2261,8 @@ int nand_erase_nand(struct mtd_info *mtd, struct erase_info *instr,
|
||||||
|
|
||||||
/* See if block erase succeeded */
|
/* See if block erase succeeded */
|
||||||
if (status & NAND_STATUS_FAIL) {
|
if (status & NAND_STATUS_FAIL) {
|
||||||
DEBUG(MTD_DEBUG_LEVEL0, "nand_erase: "
|
DEBUG(MTD_DEBUG_LEVEL0, "%s: Failed erase, "
|
||||||
"Failed erase, page 0x%08x\n", page);
|
"page 0x%08x\n", __func__, page);
|
||||||
instr->state = MTD_ERASE_FAILED;
|
instr->state = MTD_ERASE_FAILED;
|
||||||
instr->fail_addr =
|
instr->fail_addr =
|
||||||
((loff_t)page << chip->page_shift);
|
((loff_t)page << chip->page_shift);
|
||||||
|
@ -2272,9 +2322,9 @@ int nand_erase_nand(struct mtd_info *mtd, struct erase_info *instr,
|
||||||
if (!rewrite_bbt[chipnr])
|
if (!rewrite_bbt[chipnr])
|
||||||
continue;
|
continue;
|
||||||
/* update the BBT for chip */
|
/* update the BBT for chip */
|
||||||
DEBUG(MTD_DEBUG_LEVEL0, "nand_erase_nand: nand_update_bbt "
|
DEBUG(MTD_DEBUG_LEVEL0, "%s: nand_update_bbt "
|
||||||
"(%d:0x%0llx 0x%0x)\n", chipnr, rewrite_bbt[chipnr],
|
"(%d:0x%0llx 0x%0x)\n", __func__, chipnr,
|
||||||
chip->bbt_td->pages[chipnr]);
|
rewrite_bbt[chipnr], chip->bbt_td->pages[chipnr]);
|
||||||
nand_update_bbt(mtd, rewrite_bbt[chipnr]);
|
nand_update_bbt(mtd, rewrite_bbt[chipnr]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2292,7 +2342,7 @@ static void nand_sync(struct mtd_info *mtd)
|
||||||
{
|
{
|
||||||
struct nand_chip *chip = mtd->priv;
|
struct nand_chip *chip = mtd->priv;
|
||||||
|
|
||||||
DEBUG(MTD_DEBUG_LEVEL3, "nand_sync: called\n");
|
DEBUG(MTD_DEBUG_LEVEL3, "%s: called\n", __func__);
|
||||||
|
|
||||||
/* Grab the lock and see if the device is available */
|
/* Grab the lock and see if the device is available */
|
||||||
nand_get_device(chip, mtd, FL_SYNCING);
|
nand_get_device(chip, mtd, FL_SYNCING);
|
||||||
|
@ -2356,8 +2406,8 @@ static void nand_resume(struct mtd_info *mtd)
|
||||||
if (chip->state == FL_PM_SUSPENDED)
|
if (chip->state == FL_PM_SUSPENDED)
|
||||||
nand_release_device(mtd);
|
nand_release_device(mtd);
|
||||||
else
|
else
|
||||||
printk(KERN_ERR "nand_resume() called for a chip which is not "
|
printk(KERN_ERR "%s called for a chip which is not "
|
||||||
"in suspended state\n");
|
"in suspended state\n", __func__);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -2671,6 +2721,17 @@ int nand_scan_tail(struct mtd_info *mtd)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
switch (chip->ecc.mode) {
|
switch (chip->ecc.mode) {
|
||||||
|
case NAND_ECC_HW_OOB_FIRST:
|
||||||
|
/* Similar to NAND_ECC_HW, but a separate read_page handle */
|
||||||
|
if (!chip->ecc.calculate || !chip->ecc.correct ||
|
||||||
|
!chip->ecc.hwctl) {
|
||||||
|
printk(KERN_WARNING "No ECC functions supplied; "
|
||||||
|
"Hardware ECC not possible\n");
|
||||||
|
BUG();
|
||||||
|
}
|
||||||
|
if (!chip->ecc.read_page)
|
||||||
|
chip->ecc.read_page = nand_read_page_hwecc_oob_first;
|
||||||
|
|
||||||
case NAND_ECC_HW:
|
case NAND_ECC_HW:
|
||||||
/* Use standard hwecc read page function ? */
|
/* Use standard hwecc read page function ? */
|
||||||
if (!chip->ecc.read_page)
|
if (!chip->ecc.read_page)
|
||||||
|
@ -2693,7 +2754,7 @@ int nand_scan_tail(struct mtd_info *mtd)
|
||||||
chip->ecc.read_page == nand_read_page_hwecc ||
|
chip->ecc.read_page == nand_read_page_hwecc ||
|
||||||
!chip->ecc.write_page ||
|
!chip->ecc.write_page ||
|
||||||
chip->ecc.write_page == nand_write_page_hwecc)) {
|
chip->ecc.write_page == nand_write_page_hwecc)) {
|
||||||
printk(KERN_WARNING "No ECC functions supplied, "
|
printk(KERN_WARNING "No ECC functions supplied; "
|
||||||
"Hardware ECC not possible\n");
|
"Hardware ECC not possible\n");
|
||||||
BUG();
|
BUG();
|
||||||
}
|
}
|
||||||
|
@ -2728,6 +2789,7 @@ int nand_scan_tail(struct mtd_info *mtd)
|
||||||
chip->ecc.write_page_raw = nand_write_page_raw;
|
chip->ecc.write_page_raw = nand_write_page_raw;
|
||||||
chip->ecc.read_oob = nand_read_oob_std;
|
chip->ecc.read_oob = nand_read_oob_std;
|
||||||
chip->ecc.write_oob = nand_write_oob_std;
|
chip->ecc.write_oob = nand_write_oob_std;
|
||||||
|
if (!chip->ecc.size)
|
||||||
chip->ecc.size = 256;
|
chip->ecc.size = 256;
|
||||||
chip->ecc.bytes = 3;
|
chip->ecc.bytes = 3;
|
||||||
break;
|
break;
|
||||||
|
@ -2858,7 +2920,8 @@ int nand_scan(struct mtd_info *mtd, int maxchips)
|
||||||
|
|
||||||
/* Many callers got this wrong, so check for it for a while... */
|
/* Many callers got this wrong, so check for it for a while... */
|
||||||
if (!mtd->owner && caller_is_module()) {
|
if (!mtd->owner && caller_is_module()) {
|
||||||
printk(KERN_CRIT "nand_scan() called with NULL mtd->owner!\n");
|
printk(KERN_CRIT "%s called with NULL mtd->owner!\n",
|
||||||
|
__func__);
|
||||||
BUG();
|
BUG();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -417,22 +417,22 @@ int nand_calculate_ecc(struct mtd_info *mtd, const unsigned char *buf,
|
||||||
EXPORT_SYMBOL(nand_calculate_ecc);
|
EXPORT_SYMBOL(nand_calculate_ecc);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* nand_correct_data - [NAND Interface] Detect and correct bit error(s)
|
* __nand_correct_data - [NAND Interface] Detect and correct bit error(s)
|
||||||
* @mtd: MTD block structure
|
|
||||||
* @buf: raw data read from the chip
|
* @buf: raw data read from the chip
|
||||||
* @read_ecc: ECC from the chip
|
* @read_ecc: ECC from the chip
|
||||||
* @calc_ecc: the ECC calculated from raw data
|
* @calc_ecc: the ECC calculated from raw data
|
||||||
|
* @eccsize: data bytes per ecc step (256 or 512)
|
||||||
*
|
*
|
||||||
* Detect and correct a 1 bit error for 256/512 byte block
|
* Detect and correct a 1 bit error for eccsize byte block
|
||||||
*/
|
*/
|
||||||
int nand_correct_data(struct mtd_info *mtd, unsigned char *buf,
|
int __nand_correct_data(unsigned char *buf,
|
||||||
unsigned char *read_ecc, unsigned char *calc_ecc)
|
unsigned char *read_ecc, unsigned char *calc_ecc,
|
||||||
|
unsigned int eccsize)
|
||||||
{
|
{
|
||||||
unsigned char b0, b1, b2, bit_addr;
|
unsigned char b0, b1, b2, bit_addr;
|
||||||
unsigned int byte_addr;
|
unsigned int byte_addr;
|
||||||
/* 256 or 512 bytes/ecc */
|
/* 256 or 512 bytes/ecc */
|
||||||
const uint32_t eccsize_mult =
|
const uint32_t eccsize_mult = eccsize >> 8;
|
||||||
(((struct nand_chip *)mtd->priv)->ecc.size) >> 8;
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* b0 to b2 indicate which bit is faulty (if any)
|
* b0 to b2 indicate which bit is faulty (if any)
|
||||||
|
@ -495,6 +495,23 @@ int nand_correct_data(struct mtd_info *mtd, unsigned char *buf,
|
||||||
printk(KERN_ERR "uncorrectable error : ");
|
printk(KERN_ERR "uncorrectable error : ");
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
EXPORT_SYMBOL(__nand_correct_data);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* nand_correct_data - [NAND Interface] Detect and correct bit error(s)
|
||||||
|
* @mtd: MTD block structure
|
||||||
|
* @buf: raw data read from the chip
|
||||||
|
* @read_ecc: ECC from the chip
|
||||||
|
* @calc_ecc: the ECC calculated from raw data
|
||||||
|
*
|
||||||
|
* Detect and correct a 1 bit error for 256/512 byte block
|
||||||
|
*/
|
||||||
|
int nand_correct_data(struct mtd_info *mtd, unsigned char *buf,
|
||||||
|
unsigned char *read_ecc, unsigned char *calc_ecc)
|
||||||
|
{
|
||||||
|
return __nand_correct_data(buf, read_ecc, calc_ecc,
|
||||||
|
((struct nand_chip *)mtd->priv)->ecc.size);
|
||||||
|
}
|
||||||
EXPORT_SYMBOL(nand_correct_data);
|
EXPORT_SYMBOL(nand_correct_data);
|
||||||
|
|
||||||
MODULE_LICENSE("GPL");
|
MODULE_LICENSE("GPL");
|
||||||
|
|
|
@ -102,8 +102,8 @@ static int ndfc_calculate_ecc(struct mtd_info *mtd,
|
||||||
wmb();
|
wmb();
|
||||||
ecc = in_be32(ndfc->ndfcbase + NDFC_ECC);
|
ecc = in_be32(ndfc->ndfcbase + NDFC_ECC);
|
||||||
/* The NDFC uses Smart Media (SMC) bytes order */
|
/* The NDFC uses Smart Media (SMC) bytes order */
|
||||||
ecc_code[0] = p[2];
|
ecc_code[0] = p[1];
|
||||||
ecc_code[1] = p[1];
|
ecc_code[1] = p[2];
|
||||||
ecc_code[2] = p[3];
|
ecc_code[2] = p[3];
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
|
250
drivers/mtd/nand/nomadik_nand.c
Normal file
250
drivers/mtd/nand/nomadik_nand.c
Normal file
|
@ -0,0 +1,250 @@
|
||||||
|
/*
|
||||||
|
* drivers/mtd/nand/nomadik_nand.c
|
||||||
|
*
|
||||||
|
* Overview:
|
||||||
|
* Driver for on-board NAND flash on Nomadik Platforms
|
||||||
|
*
|
||||||
|
* Copyright © 2007 STMicroelectronics Pvt. Ltd.
|
||||||
|
* Author: Sachin Verma <sachin.verma@st.com>
|
||||||
|
*
|
||||||
|
* Copyright © 2009 Alessandro Rubini
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <linux/init.h>
|
||||||
|
#include <linux/module.h>
|
||||||
|
#include <linux/types.h>
|
||||||
|
#include <linux/mtd/mtd.h>
|
||||||
|
#include <linux/mtd/nand.h>
|
||||||
|
#include <linux/mtd/nand_ecc.h>
|
||||||
|
#include <linux/platform_device.h>
|
||||||
|
#include <linux/mtd/partitions.h>
|
||||||
|
#include <linux/io.h>
|
||||||
|
#include <mach/nand.h>
|
||||||
|
#include <mach/fsmc.h>
|
||||||
|
|
||||||
|
#include <mtd/mtd-abi.h>
|
||||||
|
|
||||||
|
struct nomadik_nand_host {
|
||||||
|
struct mtd_info mtd;
|
||||||
|
struct nand_chip nand;
|
||||||
|
void __iomem *data_va;
|
||||||
|
void __iomem *cmd_va;
|
||||||
|
void __iomem *addr_va;
|
||||||
|
struct nand_bbt_descr *bbt_desc;
|
||||||
|
};
|
||||||
|
|
||||||
|
static struct nand_ecclayout nomadik_ecc_layout = {
|
||||||
|
.eccbytes = 3 * 4,
|
||||||
|
.eccpos = { /* each subpage has 16 bytes: pos 2,3,4 hosts ECC */
|
||||||
|
0x02, 0x03, 0x04,
|
||||||
|
0x12, 0x13, 0x14,
|
||||||
|
0x22, 0x23, 0x24,
|
||||||
|
0x32, 0x33, 0x34},
|
||||||
|
/* let's keep bytes 5,6,7 for us, just in case we change ECC algo */
|
||||||
|
.oobfree = { {0x08, 0x08}, {0x18, 0x08}, {0x28, 0x08}, {0x38, 0x08} },
|
||||||
|
};
|
||||||
|
|
||||||
|
static void nomadik_ecc_control(struct mtd_info *mtd, int mode)
|
||||||
|
{
|
||||||
|
/* No need to enable hw ecc, it's on by default */
|
||||||
|
}
|
||||||
|
|
||||||
|
static void nomadik_cmd_ctrl(struct mtd_info *mtd, int cmd, unsigned int ctrl)
|
||||||
|
{
|
||||||
|
struct nand_chip *nand = mtd->priv;
|
||||||
|
struct nomadik_nand_host *host = nand->priv;
|
||||||
|
|
||||||
|
if (cmd == NAND_CMD_NONE)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (ctrl & NAND_CLE)
|
||||||
|
writeb(cmd, host->cmd_va);
|
||||||
|
else
|
||||||
|
writeb(cmd, host->addr_va);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int nomadik_nand_probe(struct platform_device *pdev)
|
||||||
|
{
|
||||||
|
struct nomadik_nand_platform_data *pdata = pdev->dev.platform_data;
|
||||||
|
struct nomadik_nand_host *host;
|
||||||
|
struct mtd_info *mtd;
|
||||||
|
struct nand_chip *nand;
|
||||||
|
struct resource *res;
|
||||||
|
int ret = 0;
|
||||||
|
|
||||||
|
/* Allocate memory for the device structure (and zero it) */
|
||||||
|
host = kzalloc(sizeof(struct nomadik_nand_host), GFP_KERNEL);
|
||||||
|
if (!host) {
|
||||||
|
dev_err(&pdev->dev, "Failed to allocate device structure.\n");
|
||||||
|
return -ENOMEM;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Call the client's init function, if any */
|
||||||
|
if (pdata->init)
|
||||||
|
ret = pdata->init();
|
||||||
|
if (ret < 0) {
|
||||||
|
dev_err(&pdev->dev, "Init function failed\n");
|
||||||
|
goto err;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ioremap three regions */
|
||||||
|
res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "nand_addr");
|
||||||
|
if (!res) {
|
||||||
|
ret = -EIO;
|
||||||
|
goto err_unmap;
|
||||||
|
}
|
||||||
|
host->addr_va = ioremap(res->start, res->end - res->start + 1);
|
||||||
|
|
||||||
|
res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "nand_data");
|
||||||
|
if (!res) {
|
||||||
|
ret = -EIO;
|
||||||
|
goto err_unmap;
|
||||||
|
}
|
||||||
|
host->data_va = ioremap(res->start, res->end - res->start + 1);
|
||||||
|
|
||||||
|
res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "nand_cmd");
|
||||||
|
if (!res) {
|
||||||
|
ret = -EIO;
|
||||||
|
goto err_unmap;
|
||||||
|
}
|
||||||
|
host->cmd_va = ioremap(res->start, res->end - res->start + 1);
|
||||||
|
|
||||||
|
if (!host->addr_va || !host->data_va || !host->cmd_va) {
|
||||||
|
ret = -ENOMEM;
|
||||||
|
goto err_unmap;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Link all private pointers */
|
||||||
|
mtd = &host->mtd;
|
||||||
|
nand = &host->nand;
|
||||||
|
mtd->priv = nand;
|
||||||
|
nand->priv = host;
|
||||||
|
|
||||||
|
host->mtd.owner = THIS_MODULE;
|
||||||
|
nand->IO_ADDR_R = host->data_va;
|
||||||
|
nand->IO_ADDR_W = host->data_va;
|
||||||
|
nand->cmd_ctrl = nomadik_cmd_ctrl;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This stanza declares ECC_HW but uses soft routines. It's because
|
||||||
|
* HW claims to make the calculation but not the correction. However,
|
||||||
|
* I haven't managed to get the desired data out of it until now.
|
||||||
|
*/
|
||||||
|
nand->ecc.mode = NAND_ECC_SOFT;
|
||||||
|
nand->ecc.layout = &nomadik_ecc_layout;
|
||||||
|
nand->ecc.hwctl = nomadik_ecc_control;
|
||||||
|
nand->ecc.size = 512;
|
||||||
|
nand->ecc.bytes = 3;
|
||||||
|
|
||||||
|
nand->options = pdata->options;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Scan to find existance of the device
|
||||||
|
*/
|
||||||
|
if (nand_scan(&host->mtd, 1)) {
|
||||||
|
ret = -ENXIO;
|
||||||
|
goto err_unmap;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef CONFIG_MTD_PARTITIONS
|
||||||
|
add_mtd_partitions(&host->mtd, pdata->parts, pdata->nparts);
|
||||||
|
#else
|
||||||
|
pr_info("Registering %s as whole device\n", mtd->name);
|
||||||
|
add_mtd_device(mtd);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
platform_set_drvdata(pdev, host);
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
err_unmap:
|
||||||
|
if (host->cmd_va)
|
||||||
|
iounmap(host->cmd_va);
|
||||||
|
if (host->data_va)
|
||||||
|
iounmap(host->data_va);
|
||||||
|
if (host->addr_va)
|
||||||
|
iounmap(host->addr_va);
|
||||||
|
err:
|
||||||
|
kfree(host);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Clean up routine
|
||||||
|
*/
|
||||||
|
static int nomadik_nand_remove(struct platform_device *pdev)
|
||||||
|
{
|
||||||
|
struct nomadik_nand_host *host = platform_get_drvdata(pdev);
|
||||||
|
struct nomadik_nand_platform_data *pdata = pdev->dev.platform_data;
|
||||||
|
|
||||||
|
if (pdata->exit)
|
||||||
|
pdata->exit();
|
||||||
|
|
||||||
|
if (host) {
|
||||||
|
iounmap(host->cmd_va);
|
||||||
|
iounmap(host->data_va);
|
||||||
|
iounmap(host->addr_va);
|
||||||
|
kfree(host);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int nomadik_nand_suspend(struct device *dev)
|
||||||
|
{
|
||||||
|
struct nomadik_nand_host *host = dev_get_drvdata(dev);
|
||||||
|
int ret = 0;
|
||||||
|
if (host)
|
||||||
|
ret = host->mtd.suspend(&host->mtd);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int nomadik_nand_resume(struct device *dev)
|
||||||
|
{
|
||||||
|
struct nomadik_nand_host *host = dev_get_drvdata(dev);
|
||||||
|
if (host)
|
||||||
|
host->mtd.resume(&host->mtd);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct dev_pm_ops nomadik_nand_pm_ops = {
|
||||||
|
.suspend = nomadik_nand_suspend,
|
||||||
|
.resume = nomadik_nand_resume,
|
||||||
|
};
|
||||||
|
|
||||||
|
static struct platform_driver nomadik_nand_driver = {
|
||||||
|
.probe = nomadik_nand_probe,
|
||||||
|
.remove = nomadik_nand_remove,
|
||||||
|
.driver = {
|
||||||
|
.owner = THIS_MODULE,
|
||||||
|
.name = "nomadik_nand",
|
||||||
|
.pm = &nomadik_nand_pm_ops,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
static int __init nand_nomadik_init(void)
|
||||||
|
{
|
||||||
|
pr_info("Nomadik NAND driver\n");
|
||||||
|
return platform_driver_register(&nomadik_nand_driver);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void __exit nand_nomadik_exit(void)
|
||||||
|
{
|
||||||
|
platform_driver_unregister(&nomadik_nand_driver);
|
||||||
|
}
|
||||||
|
|
||||||
|
module_init(nand_nomadik_init);
|
||||||
|
module_exit(nand_nomadik_exit);
|
||||||
|
|
||||||
|
MODULE_LICENSE("GPL");
|
||||||
|
MODULE_AUTHOR("ST Microelectronics (sachin.verma@st.com)");
|
||||||
|
MODULE_DESCRIPTION("NAND driver for Nomadik Platform");
|
|
@ -18,8 +18,7 @@
|
||||||
#include <linux/mtd/partitions.h>
|
#include <linux/mtd/partitions.h>
|
||||||
#include <linux/io.h>
|
#include <linux/io.h>
|
||||||
|
|
||||||
#include <asm/dma.h>
|
#include <mach/dma.h>
|
||||||
|
|
||||||
#include <mach/gpmc.h>
|
#include <mach/gpmc.h>
|
||||||
#include <mach/nand.h>
|
#include <mach/nand.h>
|
||||||
|
|
||||||
|
@ -112,6 +111,27 @@
|
||||||
static const char *part_probes[] = { "cmdlinepart", NULL };
|
static const char *part_probes[] = { "cmdlinepart", NULL };
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef CONFIG_MTD_NAND_OMAP_PREFETCH
|
||||||
|
static int use_prefetch = 1;
|
||||||
|
|
||||||
|
/* "modprobe ... use_prefetch=0" etc */
|
||||||
|
module_param(use_prefetch, bool, 0);
|
||||||
|
MODULE_PARM_DESC(use_prefetch, "enable/disable use of PREFETCH");
|
||||||
|
|
||||||
|
#ifdef CONFIG_MTD_NAND_OMAP_PREFETCH_DMA
|
||||||
|
static int use_dma = 1;
|
||||||
|
|
||||||
|
/* "modprobe ... use_dma=0" etc */
|
||||||
|
module_param(use_dma, bool, 0);
|
||||||
|
MODULE_PARM_DESC(use_dma, "enable/disable use of DMA");
|
||||||
|
#else
|
||||||
|
const int use_dma;
|
||||||
|
#endif
|
||||||
|
#else
|
||||||
|
const int use_prefetch;
|
||||||
|
const int use_dma;
|
||||||
|
#endif
|
||||||
|
|
||||||
struct omap_nand_info {
|
struct omap_nand_info {
|
||||||
struct nand_hw_control controller;
|
struct nand_hw_control controller;
|
||||||
struct omap_nand_platform_data *pdata;
|
struct omap_nand_platform_data *pdata;
|
||||||
|
@ -124,6 +144,9 @@ struct omap_nand_info {
|
||||||
unsigned long phys_base;
|
unsigned long phys_base;
|
||||||
void __iomem *gpmc_cs_baseaddr;
|
void __iomem *gpmc_cs_baseaddr;
|
||||||
void __iomem *gpmc_baseaddr;
|
void __iomem *gpmc_baseaddr;
|
||||||
|
void __iomem *nand_pref_fifo_add;
|
||||||
|
struct completion comp;
|
||||||
|
int dma_ch;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -188,6 +211,38 @@ static void omap_hwcontrol(struct mtd_info *mtd, int cmd, unsigned int ctrl)
|
||||||
__raw_writeb(cmd, info->nand.IO_ADDR_W);
|
__raw_writeb(cmd, info->nand.IO_ADDR_W);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* omap_read_buf8 - read data from NAND controller into buffer
|
||||||
|
* @mtd: MTD device structure
|
||||||
|
* @buf: buffer to store date
|
||||||
|
* @len: number of bytes to read
|
||||||
|
*/
|
||||||
|
static void omap_read_buf8(struct mtd_info *mtd, u_char *buf, int len)
|
||||||
|
{
|
||||||
|
struct nand_chip *nand = mtd->priv;
|
||||||
|
|
||||||
|
ioread8_rep(nand->IO_ADDR_R, buf, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* omap_write_buf8 - write buffer to NAND controller
|
||||||
|
* @mtd: MTD device structure
|
||||||
|
* @buf: data buffer
|
||||||
|
* @len: number of bytes to write
|
||||||
|
*/
|
||||||
|
static void omap_write_buf8(struct mtd_info *mtd, const u_char *buf, int len)
|
||||||
|
{
|
||||||
|
struct omap_nand_info *info = container_of(mtd,
|
||||||
|
struct omap_nand_info, mtd);
|
||||||
|
u_char *p = (u_char *)buf;
|
||||||
|
|
||||||
|
while (len--) {
|
||||||
|
iowrite8(*p++, info->nand.IO_ADDR_W);
|
||||||
|
while (GPMC_BUF_EMPTY == (readl(info->gpmc_baseaddr +
|
||||||
|
GPMC_STATUS) & GPMC_BUF_FULL));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* omap_read_buf16 - read data from NAND controller into buffer
|
* omap_read_buf16 - read data from NAND controller into buffer
|
||||||
* @mtd: MTD device structure
|
* @mtd: MTD device structure
|
||||||
|
@ -198,7 +253,7 @@ static void omap_read_buf16(struct mtd_info *mtd, u_char *buf, int len)
|
||||||
{
|
{
|
||||||
struct nand_chip *nand = mtd->priv;
|
struct nand_chip *nand = mtd->priv;
|
||||||
|
|
||||||
__raw_readsw(nand->IO_ADDR_R, buf, len / 2);
|
ioread16_rep(nand->IO_ADDR_R, buf, len / 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -217,13 +272,242 @@ static void omap_write_buf16(struct mtd_info *mtd, const u_char * buf, int len)
|
||||||
len >>= 1;
|
len >>= 1;
|
||||||
|
|
||||||
while (len--) {
|
while (len--) {
|
||||||
writew(*p++, info->nand.IO_ADDR_W);
|
iowrite16(*p++, info->nand.IO_ADDR_W);
|
||||||
|
|
||||||
while (GPMC_BUF_EMPTY == (readl(info->gpmc_baseaddr +
|
while (GPMC_BUF_EMPTY == (readl(info->gpmc_baseaddr +
|
||||||
GPMC_STATUS) & GPMC_BUF_FULL))
|
GPMC_STATUS) & GPMC_BUF_FULL))
|
||||||
;
|
;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* omap_read_buf_pref - read data from NAND controller into buffer
|
||||||
|
* @mtd: MTD device structure
|
||||||
|
* @buf: buffer to store date
|
||||||
|
* @len: number of bytes to read
|
||||||
|
*/
|
||||||
|
static void omap_read_buf_pref(struct mtd_info *mtd, u_char *buf, int len)
|
||||||
|
{
|
||||||
|
struct omap_nand_info *info = container_of(mtd,
|
||||||
|
struct omap_nand_info, mtd);
|
||||||
|
uint32_t pfpw_status = 0, r_count = 0;
|
||||||
|
int ret = 0;
|
||||||
|
u32 *p = (u32 *)buf;
|
||||||
|
|
||||||
|
/* take care of subpage reads */
|
||||||
|
for (; len % 4 != 0; ) {
|
||||||
|
*buf++ = __raw_readb(info->nand.IO_ADDR_R);
|
||||||
|
len--;
|
||||||
|
}
|
||||||
|
p = (u32 *) buf;
|
||||||
|
|
||||||
|
/* configure and start prefetch transfer */
|
||||||
|
ret = gpmc_prefetch_enable(info->gpmc_cs, 0x0, len, 0x0);
|
||||||
|
if (ret) {
|
||||||
|
/* PFPW engine is busy, use cpu copy method */
|
||||||
|
if (info->nand.options & NAND_BUSWIDTH_16)
|
||||||
|
omap_read_buf16(mtd, buf, len);
|
||||||
|
else
|
||||||
|
omap_read_buf8(mtd, buf, len);
|
||||||
|
} else {
|
||||||
|
do {
|
||||||
|
pfpw_status = gpmc_prefetch_status();
|
||||||
|
r_count = ((pfpw_status >> 24) & 0x7F) >> 2;
|
||||||
|
ioread32_rep(info->nand_pref_fifo_add, p, r_count);
|
||||||
|
p += r_count;
|
||||||
|
len -= r_count << 2;
|
||||||
|
} while (len);
|
||||||
|
|
||||||
|
/* disable and stop the PFPW engine */
|
||||||
|
gpmc_prefetch_reset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* omap_write_buf_pref - write buffer to NAND controller
|
||||||
|
* @mtd: MTD device structure
|
||||||
|
* @buf: data buffer
|
||||||
|
* @len: number of bytes to write
|
||||||
|
*/
|
||||||
|
static void omap_write_buf_pref(struct mtd_info *mtd,
|
||||||
|
const u_char *buf, int len)
|
||||||
|
{
|
||||||
|
struct omap_nand_info *info = container_of(mtd,
|
||||||
|
struct omap_nand_info, mtd);
|
||||||
|
uint32_t pfpw_status = 0, w_count = 0;
|
||||||
|
int i = 0, ret = 0;
|
||||||
|
u16 *p = (u16 *) buf;
|
||||||
|
|
||||||
|
/* take care of subpage writes */
|
||||||
|
if (len % 2 != 0) {
|
||||||
|
writeb(*buf, info->nand.IO_ADDR_R);
|
||||||
|
p = (u16 *)(buf + 1);
|
||||||
|
len--;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* configure and start prefetch transfer */
|
||||||
|
ret = gpmc_prefetch_enable(info->gpmc_cs, 0x0, len, 0x1);
|
||||||
|
if (ret) {
|
||||||
|
/* PFPW engine is busy, use cpu copy method */
|
||||||
|
if (info->nand.options & NAND_BUSWIDTH_16)
|
||||||
|
omap_write_buf16(mtd, buf, len);
|
||||||
|
else
|
||||||
|
omap_write_buf8(mtd, buf, len);
|
||||||
|
} else {
|
||||||
|
pfpw_status = gpmc_prefetch_status();
|
||||||
|
while (pfpw_status & 0x3FFF) {
|
||||||
|
w_count = ((pfpw_status >> 24) & 0x7F) >> 1;
|
||||||
|
for (i = 0; (i < w_count) && len; i++, len -= 2)
|
||||||
|
iowrite16(*p++, info->nand_pref_fifo_add);
|
||||||
|
pfpw_status = gpmc_prefetch_status();
|
||||||
|
}
|
||||||
|
|
||||||
|
/* disable and stop the PFPW engine */
|
||||||
|
gpmc_prefetch_reset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef CONFIG_MTD_NAND_OMAP_PREFETCH_DMA
|
||||||
|
/*
|
||||||
|
* omap_nand_dma_cb: callback on the completion of dma transfer
|
||||||
|
* @lch: logical channel
|
||||||
|
* @ch_satuts: channel status
|
||||||
|
* @data: pointer to completion data structure
|
||||||
|
*/
|
||||||
|
static void omap_nand_dma_cb(int lch, u16 ch_status, void *data)
|
||||||
|
{
|
||||||
|
complete((struct completion *) data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* omap_nand_dma_transfer: configer and start dma transfer
|
||||||
|
* @mtd: MTD device structure
|
||||||
|
* @addr: virtual address in RAM of source/destination
|
||||||
|
* @len: number of data bytes to be transferred
|
||||||
|
* @is_write: flag for read/write operation
|
||||||
|
*/
|
||||||
|
static inline int omap_nand_dma_transfer(struct mtd_info *mtd, void *addr,
|
||||||
|
unsigned int len, int is_write)
|
||||||
|
{
|
||||||
|
struct omap_nand_info *info = container_of(mtd,
|
||||||
|
struct omap_nand_info, mtd);
|
||||||
|
uint32_t prefetch_status = 0;
|
||||||
|
enum dma_data_direction dir = is_write ? DMA_TO_DEVICE :
|
||||||
|
DMA_FROM_DEVICE;
|
||||||
|
dma_addr_t dma_addr;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
/* The fifo depth is 64 bytes. We have a sync at each frame and frame
|
||||||
|
* length is 64 bytes.
|
||||||
|
*/
|
||||||
|
int buf_len = len >> 6;
|
||||||
|
|
||||||
|
if (addr >= high_memory) {
|
||||||
|
struct page *p1;
|
||||||
|
|
||||||
|
if (((size_t)addr & PAGE_MASK) !=
|
||||||
|
((size_t)(addr + len - 1) & PAGE_MASK))
|
||||||
|
goto out_copy;
|
||||||
|
p1 = vmalloc_to_page(addr);
|
||||||
|
if (!p1)
|
||||||
|
goto out_copy;
|
||||||
|
addr = page_address(p1) + ((size_t)addr & ~PAGE_MASK);
|
||||||
|
}
|
||||||
|
|
||||||
|
dma_addr = dma_map_single(&info->pdev->dev, addr, len, dir);
|
||||||
|
if (dma_mapping_error(&info->pdev->dev, dma_addr)) {
|
||||||
|
dev_err(&info->pdev->dev,
|
||||||
|
"Couldn't DMA map a %d byte buffer\n", len);
|
||||||
|
goto out_copy;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_write) {
|
||||||
|
omap_set_dma_dest_params(info->dma_ch, 0, OMAP_DMA_AMODE_CONSTANT,
|
||||||
|
info->phys_base, 0, 0);
|
||||||
|
omap_set_dma_src_params(info->dma_ch, 0, OMAP_DMA_AMODE_POST_INC,
|
||||||
|
dma_addr, 0, 0);
|
||||||
|
omap_set_dma_transfer_params(info->dma_ch, OMAP_DMA_DATA_TYPE_S32,
|
||||||
|
0x10, buf_len, OMAP_DMA_SYNC_FRAME,
|
||||||
|
OMAP24XX_DMA_GPMC, OMAP_DMA_DST_SYNC);
|
||||||
|
} else {
|
||||||
|
omap_set_dma_src_params(info->dma_ch, 0, OMAP_DMA_AMODE_CONSTANT,
|
||||||
|
info->phys_base, 0, 0);
|
||||||
|
omap_set_dma_dest_params(info->dma_ch, 0, OMAP_DMA_AMODE_POST_INC,
|
||||||
|
dma_addr, 0, 0);
|
||||||
|
omap_set_dma_transfer_params(info->dma_ch, OMAP_DMA_DATA_TYPE_S32,
|
||||||
|
0x10, buf_len, OMAP_DMA_SYNC_FRAME,
|
||||||
|
OMAP24XX_DMA_GPMC, OMAP_DMA_SRC_SYNC);
|
||||||
|
}
|
||||||
|
/* configure and start prefetch transfer */
|
||||||
|
ret = gpmc_prefetch_enable(info->gpmc_cs, 0x1, len, is_write);
|
||||||
|
if (ret)
|
||||||
|
/* PFPW engine is busy, use cpu copy methode */
|
||||||
|
goto out_copy;
|
||||||
|
|
||||||
|
init_completion(&info->comp);
|
||||||
|
|
||||||
|
omap_start_dma(info->dma_ch);
|
||||||
|
|
||||||
|
/* setup and start DMA using dma_addr */
|
||||||
|
wait_for_completion(&info->comp);
|
||||||
|
|
||||||
|
while (0x3fff & (prefetch_status = gpmc_prefetch_status()))
|
||||||
|
;
|
||||||
|
/* disable and stop the PFPW engine */
|
||||||
|
gpmc_prefetch_reset();
|
||||||
|
|
||||||
|
dma_unmap_single(&info->pdev->dev, dma_addr, len, dir);
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
out_copy:
|
||||||
|
if (info->nand.options & NAND_BUSWIDTH_16)
|
||||||
|
is_write == 0 ? omap_read_buf16(mtd, (u_char *) addr, len)
|
||||||
|
: omap_write_buf16(mtd, (u_char *) addr, len);
|
||||||
|
else
|
||||||
|
is_write == 0 ? omap_read_buf8(mtd, (u_char *) addr, len)
|
||||||
|
: omap_write_buf8(mtd, (u_char *) addr, len);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
static void omap_nand_dma_cb(int lch, u16 ch_status, void *data) {}
|
||||||
|
static inline int omap_nand_dma_transfer(struct mtd_info *mtd, void *addr,
|
||||||
|
unsigned int len, int is_write)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/**
|
||||||
|
* omap_read_buf_dma_pref - read data from NAND controller into buffer
|
||||||
|
* @mtd: MTD device structure
|
||||||
|
* @buf: buffer to store date
|
||||||
|
* @len: number of bytes to read
|
||||||
|
*/
|
||||||
|
static void omap_read_buf_dma_pref(struct mtd_info *mtd, u_char *buf, int len)
|
||||||
|
{
|
||||||
|
if (len <= mtd->oobsize)
|
||||||
|
omap_read_buf_pref(mtd, buf, len);
|
||||||
|
else
|
||||||
|
/* start transfer in DMA mode */
|
||||||
|
omap_nand_dma_transfer(mtd, buf, len, 0x0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* omap_write_buf_dma_pref - write buffer to NAND controller
|
||||||
|
* @mtd: MTD device structure
|
||||||
|
* @buf: data buffer
|
||||||
|
* @len: number of bytes to write
|
||||||
|
*/
|
||||||
|
static void omap_write_buf_dma_pref(struct mtd_info *mtd,
|
||||||
|
const u_char *buf, int len)
|
||||||
|
{
|
||||||
|
if (len <= mtd->oobsize)
|
||||||
|
omap_write_buf_pref(mtd, buf, len);
|
||||||
|
else
|
||||||
|
/* start transfer in DMA mode */
|
||||||
|
omap_nand_dma_transfer(mtd, buf, len, 0x1);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* omap_verify_buf - Verify chip data against buffer
|
* omap_verify_buf - Verify chip data against buffer
|
||||||
* @mtd: MTD device structure
|
* @mtd: MTD device structure
|
||||||
|
@ -658,17 +942,12 @@ static int __devinit omap_nand_probe(struct platform_device *pdev)
|
||||||
err = -ENOMEM;
|
err = -ENOMEM;
|
||||||
goto out_release_mem_region;
|
goto out_release_mem_region;
|
||||||
}
|
}
|
||||||
|
|
||||||
info->nand.controller = &info->controller;
|
info->nand.controller = &info->controller;
|
||||||
|
|
||||||
info->nand.IO_ADDR_W = info->nand.IO_ADDR_R;
|
info->nand.IO_ADDR_W = info->nand.IO_ADDR_R;
|
||||||
info->nand.cmd_ctrl = omap_hwcontrol;
|
info->nand.cmd_ctrl = omap_hwcontrol;
|
||||||
|
|
||||||
/* REVISIT: only supports 16-bit NAND flash */
|
|
||||||
|
|
||||||
info->nand.read_buf = omap_read_buf16;
|
|
||||||
info->nand.write_buf = omap_write_buf16;
|
|
||||||
info->nand.verify_buf = omap_verify_buf;
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* If RDY/BSY line is connected to OMAP then use the omap ready
|
* If RDY/BSY line is connected to OMAP then use the omap ready
|
||||||
* funcrtion and the generic nand_wait function which reads the status
|
* funcrtion and the generic nand_wait function which reads the status
|
||||||
|
@ -689,6 +968,40 @@ static int __devinit omap_nand_probe(struct platform_device *pdev)
|
||||||
== 0x1000)
|
== 0x1000)
|
||||||
info->nand.options |= NAND_BUSWIDTH_16;
|
info->nand.options |= NAND_BUSWIDTH_16;
|
||||||
|
|
||||||
|
if (use_prefetch) {
|
||||||
|
/* copy the virtual address of nand base for fifo access */
|
||||||
|
info->nand_pref_fifo_add = info->nand.IO_ADDR_R;
|
||||||
|
|
||||||
|
info->nand.read_buf = omap_read_buf_pref;
|
||||||
|
info->nand.write_buf = omap_write_buf_pref;
|
||||||
|
if (use_dma) {
|
||||||
|
err = omap_request_dma(OMAP24XX_DMA_GPMC, "NAND",
|
||||||
|
omap_nand_dma_cb, &info->comp, &info->dma_ch);
|
||||||
|
if (err < 0) {
|
||||||
|
info->dma_ch = -1;
|
||||||
|
printk(KERN_WARNING "DMA request failed."
|
||||||
|
" Non-dma data transfer mode\n");
|
||||||
|
} else {
|
||||||
|
omap_set_dma_dest_burst_mode(info->dma_ch,
|
||||||
|
OMAP_DMA_DATA_BURST_16);
|
||||||
|
omap_set_dma_src_burst_mode(info->dma_ch,
|
||||||
|
OMAP_DMA_DATA_BURST_16);
|
||||||
|
|
||||||
|
info->nand.read_buf = omap_read_buf_dma_pref;
|
||||||
|
info->nand.write_buf = omap_write_buf_dma_pref;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (info->nand.options & NAND_BUSWIDTH_16) {
|
||||||
|
info->nand.read_buf = omap_read_buf16;
|
||||||
|
info->nand.write_buf = omap_write_buf16;
|
||||||
|
} else {
|
||||||
|
info->nand.read_buf = omap_read_buf8;
|
||||||
|
info->nand.write_buf = omap_write_buf8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
info->nand.verify_buf = omap_verify_buf;
|
||||||
|
|
||||||
#ifdef CONFIG_MTD_NAND_OMAP_HWECC
|
#ifdef CONFIG_MTD_NAND_OMAP_HWECC
|
||||||
info->nand.ecc.bytes = 3;
|
info->nand.ecc.bytes = 3;
|
||||||
info->nand.ecc.size = 512;
|
info->nand.ecc.size = 512;
|
||||||
|
@ -744,9 +1057,12 @@ static int omap_nand_remove(struct platform_device *pdev)
|
||||||
struct omap_nand_info *info = mtd->priv;
|
struct omap_nand_info *info = mtd->priv;
|
||||||
|
|
||||||
platform_set_drvdata(pdev, NULL);
|
platform_set_drvdata(pdev, NULL);
|
||||||
|
if (use_dma)
|
||||||
|
omap_free_dma(info->dma_ch);
|
||||||
|
|
||||||
/* Release NAND device, its internal structures and partitions */
|
/* Release NAND device, its internal structures and partitions */
|
||||||
nand_release(&info->mtd);
|
nand_release(&info->mtd);
|
||||||
iounmap(info->nand.IO_ADDR_R);
|
iounmap(info->nand_pref_fifo_add);
|
||||||
kfree(&info->mtd);
|
kfree(&info->mtd);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
@ -763,6 +1079,15 @@ static struct platform_driver omap_nand_driver = {
|
||||||
static int __init omap_nand_init(void)
|
static int __init omap_nand_init(void)
|
||||||
{
|
{
|
||||||
printk(KERN_INFO "%s driver initializing\n", DRIVER_NAME);
|
printk(KERN_INFO "%s driver initializing\n", DRIVER_NAME);
|
||||||
|
|
||||||
|
/* This check is required if driver is being
|
||||||
|
* loaded run time as a module
|
||||||
|
*/
|
||||||
|
if ((1 == use_dma) && (0 == use_prefetch)) {
|
||||||
|
printk(KERN_INFO"Wrong parameters: 'use_dma' can not be 1 "
|
||||||
|
"without use_prefetch'. Prefetch will not be"
|
||||||
|
" used in either mode (mpu or dma)\n");
|
||||||
|
}
|
||||||
return platform_driver_register(&omap_nand_driver);
|
return platform_driver_register(&omap_nand_driver);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -171,7 +171,6 @@ static int __devexit orion_nand_remove(struct platform_device *pdev)
|
||||||
}
|
}
|
||||||
|
|
||||||
static struct platform_driver orion_nand_driver = {
|
static struct platform_driver orion_nand_driver = {
|
||||||
.probe = orion_nand_probe,
|
|
||||||
.remove = __devexit_p(orion_nand_remove),
|
.remove = __devexit_p(orion_nand_remove),
|
||||||
.driver = {
|
.driver = {
|
||||||
.name = "orion_nand",
|
.name = "orion_nand",
|
||||||
|
@ -181,7 +180,7 @@ static struct platform_driver orion_nand_driver = {
|
||||||
|
|
||||||
static int __init orion_nand_init(void)
|
static int __init orion_nand_init(void)
|
||||||
{
|
{
|
||||||
return platform_driver_register(&orion_nand_driver);
|
return platform_driver_probe(&orion_nand_driver, orion_nand_probe);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void __exit orion_nand_exit(void)
|
static void __exit orion_nand_exit(void)
|
||||||
|
|
|
@ -102,6 +102,7 @@ enum {
|
||||||
ERR_SENDCMD = -2,
|
ERR_SENDCMD = -2,
|
||||||
ERR_DBERR = -3,
|
ERR_DBERR = -3,
|
||||||
ERR_BBERR = -4,
|
ERR_BBERR = -4,
|
||||||
|
ERR_SBERR = -5,
|
||||||
};
|
};
|
||||||
|
|
||||||
enum {
|
enum {
|
||||||
|
@ -564,11 +565,13 @@ static irqreturn_t pxa3xx_nand_irq(int irq, void *devid)
|
||||||
|
|
||||||
status = nand_readl(info, NDSR);
|
status = nand_readl(info, NDSR);
|
||||||
|
|
||||||
if (status & (NDSR_RDDREQ | NDSR_DBERR)) {
|
if (status & (NDSR_RDDREQ | NDSR_DBERR | NDSR_SBERR)) {
|
||||||
if (status & NDSR_DBERR)
|
if (status & NDSR_DBERR)
|
||||||
info->retcode = ERR_DBERR;
|
info->retcode = ERR_DBERR;
|
||||||
|
else if (status & NDSR_SBERR)
|
||||||
|
info->retcode = ERR_SBERR;
|
||||||
|
|
||||||
disable_int(info, NDSR_RDDREQ | NDSR_DBERR);
|
disable_int(info, NDSR_RDDREQ | NDSR_DBERR | NDSR_SBERR);
|
||||||
|
|
||||||
if (info->use_dma) {
|
if (info->use_dma) {
|
||||||
info->state = STATE_DMA_READING;
|
info->state = STATE_DMA_READING;
|
||||||
|
@ -670,7 +673,7 @@ static void pxa3xx_nand_cmdfunc(struct mtd_info *mtd, unsigned command,
|
||||||
if (prepare_read_prog_cmd(info, cmdset->read1, column, page_addr))
|
if (prepare_read_prog_cmd(info, cmdset->read1, column, page_addr))
|
||||||
break;
|
break;
|
||||||
|
|
||||||
pxa3xx_nand_do_cmd(info, NDSR_RDDREQ | NDSR_DBERR);
|
pxa3xx_nand_do_cmd(info, NDSR_RDDREQ | NDSR_DBERR | NDSR_SBERR);
|
||||||
|
|
||||||
/* We only are OOB, so if the data has error, does not matter */
|
/* We only are OOB, so if the data has error, does not matter */
|
||||||
if (info->retcode == ERR_DBERR)
|
if (info->retcode == ERR_DBERR)
|
||||||
|
@ -687,7 +690,7 @@ static void pxa3xx_nand_cmdfunc(struct mtd_info *mtd, unsigned command,
|
||||||
if (prepare_read_prog_cmd(info, cmdset->read1, column, page_addr))
|
if (prepare_read_prog_cmd(info, cmdset->read1, column, page_addr))
|
||||||
break;
|
break;
|
||||||
|
|
||||||
pxa3xx_nand_do_cmd(info, NDSR_RDDREQ | NDSR_DBERR);
|
pxa3xx_nand_do_cmd(info, NDSR_RDDREQ | NDSR_DBERR | NDSR_SBERR);
|
||||||
|
|
||||||
if (info->retcode == ERR_DBERR) {
|
if (info->retcode == ERR_DBERR) {
|
||||||
/* for blank page (all 0xff), HW will calculate its ECC as
|
/* for blank page (all 0xff), HW will calculate its ECC as
|
||||||
|
@ -861,8 +864,12 @@ static int pxa3xx_nand_ecc_correct(struct mtd_info *mtd,
|
||||||
* consider it as a ecc error which will tell the caller the
|
* consider it as a ecc error which will tell the caller the
|
||||||
* read fail We have distinguish all the errors, but the
|
* read fail We have distinguish all the errors, but the
|
||||||
* nand_read_ecc only check this function return value
|
* nand_read_ecc only check this function return value
|
||||||
|
*
|
||||||
|
* Corrected (single-bit) errors must also be noted.
|
||||||
*/
|
*/
|
||||||
if (info->retcode != ERR_NONE)
|
if (info->retcode == ERR_SBERR)
|
||||||
|
return 1;
|
||||||
|
else if (info->retcode != ERR_NONE)
|
||||||
return -1;
|
return -1;
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
|
|
@ -329,7 +329,7 @@ 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,
|
static int flctl_read_page_hwecc(struct mtd_info *mtd, struct nand_chip *chip,
|
||||||
uint8_t *buf)
|
uint8_t *buf, int page)
|
||||||
{
|
{
|
||||||
int i, eccsize = chip->ecc.size;
|
int i, eccsize = chip->ecc.size;
|
||||||
int eccbytes = chip->ecc.bytes;
|
int eccbytes = chip->ecc.bytes;
|
||||||
|
@ -857,7 +857,6 @@ static int __exit flctl_remove(struct platform_device *pdev)
|
||||||
}
|
}
|
||||||
|
|
||||||
static struct platform_driver flctl_driver = {
|
static struct platform_driver flctl_driver = {
|
||||||
.probe = flctl_probe,
|
|
||||||
.remove = flctl_remove,
|
.remove = flctl_remove,
|
||||||
.driver = {
|
.driver = {
|
||||||
.name = "sh_flctl",
|
.name = "sh_flctl",
|
||||||
|
@ -867,7 +866,7 @@ static struct platform_driver flctl_driver = {
|
||||||
|
|
||||||
static int __init flctl_nand_init(void)
|
static int __init flctl_nand_init(void)
|
||||||
{
|
{
|
||||||
return platform_driver_register(&flctl_driver);
|
return platform_driver_probe(&flctl_driver, flctl_probe);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void __exit flctl_nand_cleanup(void)
|
static void __exit flctl_nand_cleanup(void)
|
||||||
|
|
|
@ -301,6 +301,21 @@ static int tmio_nand_calculate_ecc(struct mtd_info *mtd, const u_char *dat,
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int tmio_nand_correct_data(struct mtd_info *mtd, unsigned char *buf,
|
||||||
|
unsigned char *read_ecc, unsigned char *calc_ecc)
|
||||||
|
{
|
||||||
|
int r0, r1;
|
||||||
|
|
||||||
|
/* assume ecc.size = 512 and ecc.bytes = 6 */
|
||||||
|
r0 = __nand_correct_data(buf, read_ecc, calc_ecc, 256);
|
||||||
|
if (r0 < 0)
|
||||||
|
return r0;
|
||||||
|
r1 = __nand_correct_data(buf + 256, read_ecc + 3, calc_ecc + 3, 256);
|
||||||
|
if (r1 < 0)
|
||||||
|
return r1;
|
||||||
|
return r0 + r1;
|
||||||
|
}
|
||||||
|
|
||||||
static int tmio_hw_init(struct platform_device *dev, struct tmio_nand *tmio)
|
static int tmio_hw_init(struct platform_device *dev, struct tmio_nand *tmio)
|
||||||
{
|
{
|
||||||
struct mfd_cell *cell = (struct mfd_cell *)dev->dev.platform_data;
|
struct mfd_cell *cell = (struct mfd_cell *)dev->dev.platform_data;
|
||||||
|
@ -424,7 +439,7 @@ static int tmio_probe(struct platform_device *dev)
|
||||||
nand_chip->ecc.bytes = 6;
|
nand_chip->ecc.bytes = 6;
|
||||||
nand_chip->ecc.hwctl = tmio_nand_enable_hwecc;
|
nand_chip->ecc.hwctl = tmio_nand_enable_hwecc;
|
||||||
nand_chip->ecc.calculate = tmio_nand_calculate_ecc;
|
nand_chip->ecc.calculate = tmio_nand_calculate_ecc;
|
||||||
nand_chip->ecc.correct = nand_correct_data;
|
nand_chip->ecc.correct = tmio_nand_correct_data;
|
||||||
|
|
||||||
if (data)
|
if (data)
|
||||||
nand_chip->badblock_pattern = data->badblock_pattern;
|
nand_chip->badblock_pattern = data->badblock_pattern;
|
||||||
|
|
|
@ -189,18 +189,43 @@ static int txx9ndfmc_calculate_ecc(struct mtd_info *mtd, const uint8_t *dat,
|
||||||
uint8_t *ecc_code)
|
uint8_t *ecc_code)
|
||||||
{
|
{
|
||||||
struct platform_device *dev = mtd_to_platdev(mtd);
|
struct platform_device *dev = mtd_to_platdev(mtd);
|
||||||
|
struct nand_chip *chip = mtd->priv;
|
||||||
|
int eccbytes;
|
||||||
u32 mcr = txx9ndfmc_read(dev, TXX9_NDFMCR);
|
u32 mcr = txx9ndfmc_read(dev, TXX9_NDFMCR);
|
||||||
|
|
||||||
mcr &= ~TXX9_NDFMCR_ECC_ALL;
|
mcr &= ~TXX9_NDFMCR_ECC_ALL;
|
||||||
txx9ndfmc_write(dev, mcr | TXX9_NDFMCR_ECC_OFF, TXX9_NDFMCR);
|
txx9ndfmc_write(dev, mcr | TXX9_NDFMCR_ECC_OFF, TXX9_NDFMCR);
|
||||||
txx9ndfmc_write(dev, mcr | TXX9_NDFMCR_ECC_READ, TXX9_NDFMCR);
|
txx9ndfmc_write(dev, mcr | TXX9_NDFMCR_ECC_READ, TXX9_NDFMCR);
|
||||||
|
for (eccbytes = chip->ecc.bytes; eccbytes > 0; eccbytes -= 3) {
|
||||||
ecc_code[1] = txx9ndfmc_read(dev, TXX9_NDFDTR);
|
ecc_code[1] = txx9ndfmc_read(dev, TXX9_NDFDTR);
|
||||||
ecc_code[0] = txx9ndfmc_read(dev, TXX9_NDFDTR);
|
ecc_code[0] = txx9ndfmc_read(dev, TXX9_NDFDTR);
|
||||||
ecc_code[2] = txx9ndfmc_read(dev, TXX9_NDFDTR);
|
ecc_code[2] = txx9ndfmc_read(dev, TXX9_NDFDTR);
|
||||||
|
ecc_code += 3;
|
||||||
|
}
|
||||||
txx9ndfmc_write(dev, mcr | TXX9_NDFMCR_ECC_OFF, TXX9_NDFMCR);
|
txx9ndfmc_write(dev, mcr | TXX9_NDFMCR_ECC_OFF, TXX9_NDFMCR);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int txx9ndfmc_correct_data(struct mtd_info *mtd, unsigned char *buf,
|
||||||
|
unsigned char *read_ecc, unsigned char *calc_ecc)
|
||||||
|
{
|
||||||
|
struct nand_chip *chip = mtd->priv;
|
||||||
|
int eccsize;
|
||||||
|
int corrected = 0;
|
||||||
|
int stat;
|
||||||
|
|
||||||
|
for (eccsize = chip->ecc.size; eccsize > 0; eccsize -= 256) {
|
||||||
|
stat = __nand_correct_data(buf, read_ecc, calc_ecc, 256);
|
||||||
|
if (stat < 0)
|
||||||
|
return stat;
|
||||||
|
corrected += stat;
|
||||||
|
buf += 256;
|
||||||
|
read_ecc += 3;
|
||||||
|
calc_ecc += 3;
|
||||||
|
}
|
||||||
|
return corrected;
|
||||||
|
}
|
||||||
|
|
||||||
static void txx9ndfmc_enable_hwecc(struct mtd_info *mtd, int mode)
|
static void txx9ndfmc_enable_hwecc(struct mtd_info *mtd, int mode)
|
||||||
{
|
{
|
||||||
struct platform_device *dev = mtd_to_platdev(mtd);
|
struct platform_device *dev = mtd_to_platdev(mtd);
|
||||||
|
@ -244,6 +269,22 @@ static void txx9ndfmc_initialize(struct platform_device *dev)
|
||||||
#define TXX9NDFMC_NS_TO_CYC(gbusclk, ns) \
|
#define TXX9NDFMC_NS_TO_CYC(gbusclk, ns) \
|
||||||
DIV_ROUND_UP((ns) * DIV_ROUND_UP(gbusclk, 1000), 1000000)
|
DIV_ROUND_UP((ns) * DIV_ROUND_UP(gbusclk, 1000), 1000000)
|
||||||
|
|
||||||
|
static int txx9ndfmc_nand_scan(struct mtd_info *mtd)
|
||||||
|
{
|
||||||
|
struct nand_chip *chip = mtd->priv;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
ret = nand_scan_ident(mtd, 1);
|
||||||
|
if (!ret) {
|
||||||
|
if (mtd->writesize >= 512) {
|
||||||
|
chip->ecc.size = mtd->writesize;
|
||||||
|
chip->ecc.bytes = 3 * (mtd->writesize / 256);
|
||||||
|
}
|
||||||
|
ret = nand_scan_tail(mtd);
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
static int __init txx9ndfmc_probe(struct platform_device *dev)
|
static int __init txx9ndfmc_probe(struct platform_device *dev)
|
||||||
{
|
{
|
||||||
struct txx9ndfmc_platform_data *plat = dev->dev.platform_data;
|
struct txx9ndfmc_platform_data *plat = dev->dev.platform_data;
|
||||||
|
@ -321,9 +362,10 @@ static int __init txx9ndfmc_probe(struct platform_device *dev)
|
||||||
chip->cmd_ctrl = txx9ndfmc_cmd_ctrl;
|
chip->cmd_ctrl = txx9ndfmc_cmd_ctrl;
|
||||||
chip->dev_ready = txx9ndfmc_dev_ready;
|
chip->dev_ready = txx9ndfmc_dev_ready;
|
||||||
chip->ecc.calculate = txx9ndfmc_calculate_ecc;
|
chip->ecc.calculate = txx9ndfmc_calculate_ecc;
|
||||||
chip->ecc.correct = nand_correct_data;
|
chip->ecc.correct = txx9ndfmc_correct_data;
|
||||||
chip->ecc.hwctl = txx9ndfmc_enable_hwecc;
|
chip->ecc.hwctl = txx9ndfmc_enable_hwecc;
|
||||||
chip->ecc.mode = NAND_ECC_HW;
|
chip->ecc.mode = NAND_ECC_HW;
|
||||||
|
/* txx9ndfmc_nand_scan will overwrite ecc.size and ecc.bytes */
|
||||||
chip->ecc.size = 256;
|
chip->ecc.size = 256;
|
||||||
chip->ecc.bytes = 3;
|
chip->ecc.bytes = 3;
|
||||||
chip->chip_delay = 100;
|
chip->chip_delay = 100;
|
||||||
|
@ -349,7 +391,7 @@ static int __init txx9ndfmc_probe(struct platform_device *dev)
|
||||||
if (plat->wide_mask & (1 << i))
|
if (plat->wide_mask & (1 << i))
|
||||||
chip->options |= NAND_BUSWIDTH_16;
|
chip->options |= NAND_BUSWIDTH_16;
|
||||||
|
|
||||||
if (nand_scan(mtd, 1)) {
|
if (txx9ndfmc_nand_scan(mtd)) {
|
||||||
kfree(txx9_priv->mtdname);
|
kfree(txx9_priv->mtdname);
|
||||||
kfree(txx9_priv);
|
kfree(txx9_priv);
|
||||||
continue;
|
continue;
|
||||||
|
|
382
drivers/mtd/nand/w90p910_nand.c
Normal file
382
drivers/mtd/nand/w90p910_nand.c
Normal file
|
@ -0,0 +1,382 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2009 Nuvoton technology corporation.
|
||||||
|
*
|
||||||
|
* Wan ZongShun <mcuos.com@gmail.com>
|
||||||
|
*
|
||||||
|
* 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;version 2 of the License.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <linux/slab.h>
|
||||||
|
#include <linux/init.h>
|
||||||
|
#include <linux/module.h>
|
||||||
|
#include <linux/interrupt.h>
|
||||||
|
#include <linux/io.h>
|
||||||
|
#include <linux/platform_device.h>
|
||||||
|
#include <linux/delay.h>
|
||||||
|
#include <linux/clk.h>
|
||||||
|
#include <linux/err.h>
|
||||||
|
|
||||||
|
#include <linux/mtd/mtd.h>
|
||||||
|
#include <linux/mtd/nand.h>
|
||||||
|
#include <linux/mtd/partitions.h>
|
||||||
|
|
||||||
|
#define REG_FMICSR 0x00
|
||||||
|
#define REG_SMCSR 0xa0
|
||||||
|
#define REG_SMISR 0xac
|
||||||
|
#define REG_SMCMD 0xb0
|
||||||
|
#define REG_SMADDR 0xb4
|
||||||
|
#define REG_SMDATA 0xb8
|
||||||
|
|
||||||
|
#define RESET_FMI 0x01
|
||||||
|
#define NAND_EN 0x08
|
||||||
|
#define READYBUSY (0x01 << 18)
|
||||||
|
|
||||||
|
#define SWRST 0x01
|
||||||
|
#define PSIZE (0x01 << 3)
|
||||||
|
#define DMARWEN (0x03 << 1)
|
||||||
|
#define BUSWID (0x01 << 4)
|
||||||
|
#define ECC4EN (0x01 << 5)
|
||||||
|
#define WP (0x01 << 24)
|
||||||
|
#define NANDCS (0x01 << 25)
|
||||||
|
#define ENDADDR (0x01 << 31)
|
||||||
|
|
||||||
|
#define read_data_reg(dev) \
|
||||||
|
__raw_readl((dev)->reg + REG_SMDATA)
|
||||||
|
|
||||||
|
#define write_data_reg(dev, val) \
|
||||||
|
__raw_writel((val), (dev)->reg + REG_SMDATA)
|
||||||
|
|
||||||
|
#define write_cmd_reg(dev, val) \
|
||||||
|
__raw_writel((val), (dev)->reg + REG_SMCMD)
|
||||||
|
|
||||||
|
#define write_addr_reg(dev, val) \
|
||||||
|
__raw_writel((val), (dev)->reg + REG_SMADDR)
|
||||||
|
|
||||||
|
struct w90p910_nand {
|
||||||
|
struct mtd_info mtd;
|
||||||
|
struct nand_chip chip;
|
||||||
|
void __iomem *reg;
|
||||||
|
struct clk *clk;
|
||||||
|
spinlock_t lock;
|
||||||
|
};
|
||||||
|
|
||||||
|
static const struct mtd_partition partitions[] = {
|
||||||
|
{
|
||||||
|
.name = "NAND FS 0",
|
||||||
|
.offset = 0,
|
||||||
|
.size = 8 * 1024 * 1024
|
||||||
|
},
|
||||||
|
{
|
||||||
|
.name = "NAND FS 1",
|
||||||
|
.offset = MTDPART_OFS_APPEND,
|
||||||
|
.size = MTDPART_SIZ_FULL
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
static unsigned char w90p910_nand_read_byte(struct mtd_info *mtd)
|
||||||
|
{
|
||||||
|
unsigned char ret;
|
||||||
|
struct w90p910_nand *nand;
|
||||||
|
|
||||||
|
nand = container_of(mtd, struct w90p910_nand, mtd);
|
||||||
|
|
||||||
|
ret = (unsigned char)read_data_reg(nand);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void w90p910_nand_read_buf(struct mtd_info *mtd,
|
||||||
|
unsigned char *buf, int len)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
struct w90p910_nand *nand;
|
||||||
|
|
||||||
|
nand = container_of(mtd, struct w90p910_nand, mtd);
|
||||||
|
|
||||||
|
for (i = 0; i < len; i++)
|
||||||
|
buf[i] = (unsigned char)read_data_reg(nand);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void w90p910_nand_write_buf(struct mtd_info *mtd,
|
||||||
|
const unsigned char *buf, int len)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
struct w90p910_nand *nand;
|
||||||
|
|
||||||
|
nand = container_of(mtd, struct w90p910_nand, mtd);
|
||||||
|
|
||||||
|
for (i = 0; i < len; i++)
|
||||||
|
write_data_reg(nand, buf[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int w90p910_verify_buf(struct mtd_info *mtd,
|
||||||
|
const unsigned char *buf, int len)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
struct w90p910_nand *nand;
|
||||||
|
|
||||||
|
nand = container_of(mtd, struct w90p910_nand, mtd);
|
||||||
|
|
||||||
|
for (i = 0; i < len; i++) {
|
||||||
|
if (buf[i] != (unsigned char)read_data_reg(nand))
|
||||||
|
return -EFAULT;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int w90p910_check_rb(struct w90p910_nand *nand)
|
||||||
|
{
|
||||||
|
unsigned int val;
|
||||||
|
spin_lock(&nand->lock);
|
||||||
|
val = __raw_readl(REG_SMISR);
|
||||||
|
val &= READYBUSY;
|
||||||
|
spin_unlock(&nand->lock);
|
||||||
|
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int w90p910_nand_devready(struct mtd_info *mtd)
|
||||||
|
{
|
||||||
|
struct w90p910_nand *nand;
|
||||||
|
int ready;
|
||||||
|
|
||||||
|
nand = container_of(mtd, struct w90p910_nand, mtd);
|
||||||
|
|
||||||
|
ready = (w90p910_check_rb(nand)) ? 1 : 0;
|
||||||
|
return ready;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void w90p910_nand_command_lp(struct mtd_info *mtd,
|
||||||
|
unsigned int command, int column, int page_addr)
|
||||||
|
{
|
||||||
|
register struct nand_chip *chip = mtd->priv;
|
||||||
|
struct w90p910_nand *nand;
|
||||||
|
|
||||||
|
nand = container_of(mtd, struct w90p910_nand, mtd);
|
||||||
|
|
||||||
|
if (command == NAND_CMD_READOOB) {
|
||||||
|
column += mtd->writesize;
|
||||||
|
command = NAND_CMD_READ0;
|
||||||
|
}
|
||||||
|
|
||||||
|
write_cmd_reg(nand, command & 0xff);
|
||||||
|
|
||||||
|
if (column != -1 || page_addr != -1) {
|
||||||
|
|
||||||
|
if (column != -1) {
|
||||||
|
if (chip->options & NAND_BUSWIDTH_16)
|
||||||
|
column >>= 1;
|
||||||
|
write_addr_reg(nand, column);
|
||||||
|
write_addr_reg(nand, column >> 8 | ENDADDR);
|
||||||
|
}
|
||||||
|
if (page_addr != -1) {
|
||||||
|
write_addr_reg(nand, page_addr);
|
||||||
|
|
||||||
|
if (chip->chipsize > (128 << 20)) {
|
||||||
|
write_addr_reg(nand, page_addr >> 8);
|
||||||
|
write_addr_reg(nand, page_addr >> 16 | ENDADDR);
|
||||||
|
} else {
|
||||||
|
write_addr_reg(nand, page_addr >> 8 | ENDADDR);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (command) {
|
||||||
|
case NAND_CMD_CACHEDPROG:
|
||||||
|
case NAND_CMD_PAGEPROG:
|
||||||
|
case NAND_CMD_ERASE1:
|
||||||
|
case NAND_CMD_ERASE2:
|
||||||
|
case NAND_CMD_SEQIN:
|
||||||
|
case NAND_CMD_RNDIN:
|
||||||
|
case NAND_CMD_STATUS:
|
||||||
|
case NAND_CMD_DEPLETE1:
|
||||||
|
return;
|
||||||
|
|
||||||
|
case NAND_CMD_STATUS_ERROR:
|
||||||
|
case NAND_CMD_STATUS_ERROR0:
|
||||||
|
case NAND_CMD_STATUS_ERROR1:
|
||||||
|
case NAND_CMD_STATUS_ERROR2:
|
||||||
|
case NAND_CMD_STATUS_ERROR3:
|
||||||
|
udelay(chip->chip_delay);
|
||||||
|
return;
|
||||||
|
|
||||||
|
case NAND_CMD_RESET:
|
||||||
|
if (chip->dev_ready)
|
||||||
|
break;
|
||||||
|
udelay(chip->chip_delay);
|
||||||
|
|
||||||
|
write_cmd_reg(nand, NAND_CMD_STATUS);
|
||||||
|
write_cmd_reg(nand, command);
|
||||||
|
|
||||||
|
while (!w90p910_check_rb(nand))
|
||||||
|
;
|
||||||
|
|
||||||
|
return;
|
||||||
|
|
||||||
|
case NAND_CMD_RNDOUT:
|
||||||
|
write_cmd_reg(nand, NAND_CMD_RNDOUTSTART);
|
||||||
|
return;
|
||||||
|
|
||||||
|
case NAND_CMD_READ0:
|
||||||
|
|
||||||
|
write_cmd_reg(nand, NAND_CMD_READSTART);
|
||||||
|
default:
|
||||||
|
|
||||||
|
if (!chip->dev_ready) {
|
||||||
|
udelay(chip->chip_delay);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Apply this short delay always to ensure that we do wait tWB in
|
||||||
|
* any case on any machine. */
|
||||||
|
ndelay(100);
|
||||||
|
|
||||||
|
while (!chip->dev_ready(mtd))
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void w90p910_nand_enable(struct w90p910_nand *nand)
|
||||||
|
{
|
||||||
|
unsigned int val;
|
||||||
|
spin_lock(&nand->lock);
|
||||||
|
__raw_writel(RESET_FMI, (nand->reg + REG_FMICSR));
|
||||||
|
|
||||||
|
val = __raw_readl(nand->reg + REG_FMICSR);
|
||||||
|
|
||||||
|
if (!(val & NAND_EN))
|
||||||
|
__raw_writel(val | NAND_EN, REG_FMICSR);
|
||||||
|
|
||||||
|
val = __raw_readl(nand->reg + REG_SMCSR);
|
||||||
|
|
||||||
|
val &= ~(SWRST|PSIZE|DMARWEN|BUSWID|ECC4EN|NANDCS);
|
||||||
|
val |= WP;
|
||||||
|
|
||||||
|
__raw_writel(val, nand->reg + REG_SMCSR);
|
||||||
|
|
||||||
|
spin_unlock(&nand->lock);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int __devinit w90p910_nand_probe(struct platform_device *pdev)
|
||||||
|
{
|
||||||
|
struct w90p910_nand *w90p910_nand;
|
||||||
|
struct nand_chip *chip;
|
||||||
|
int retval;
|
||||||
|
struct resource *res;
|
||||||
|
|
||||||
|
retval = 0;
|
||||||
|
|
||||||
|
w90p910_nand = kzalloc(sizeof(struct w90p910_nand), GFP_KERNEL);
|
||||||
|
if (!w90p910_nand)
|
||||||
|
return -ENOMEM;
|
||||||
|
chip = &(w90p910_nand->chip);
|
||||||
|
|
||||||
|
w90p910_nand->mtd.priv = chip;
|
||||||
|
w90p910_nand->mtd.owner = THIS_MODULE;
|
||||||
|
spin_lock_init(&w90p910_nand->lock);
|
||||||
|
|
||||||
|
w90p910_nand->clk = clk_get(&pdev->dev, NULL);
|
||||||
|
if (IS_ERR(w90p910_nand->clk)) {
|
||||||
|
retval = -ENOENT;
|
||||||
|
goto fail1;
|
||||||
|
}
|
||||||
|
clk_enable(w90p910_nand->clk);
|
||||||
|
|
||||||
|
chip->cmdfunc = w90p910_nand_command_lp;
|
||||||
|
chip->dev_ready = w90p910_nand_devready;
|
||||||
|
chip->read_byte = w90p910_nand_read_byte;
|
||||||
|
chip->write_buf = w90p910_nand_write_buf;
|
||||||
|
chip->read_buf = w90p910_nand_read_buf;
|
||||||
|
chip->verify_buf = w90p910_verify_buf;
|
||||||
|
chip->chip_delay = 50;
|
||||||
|
chip->options = 0;
|
||||||
|
chip->ecc.mode = NAND_ECC_SOFT;
|
||||||
|
|
||||||
|
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||||
|
if (!res) {
|
||||||
|
retval = -ENXIO;
|
||||||
|
goto fail1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!request_mem_region(res->start, resource_size(res), pdev->name)) {
|
||||||
|
retval = -EBUSY;
|
||||||
|
goto fail1;
|
||||||
|
}
|
||||||
|
|
||||||
|
w90p910_nand->reg = ioremap(res->start, resource_size(res));
|
||||||
|
if (!w90p910_nand->reg) {
|
||||||
|
retval = -ENOMEM;
|
||||||
|
goto fail2;
|
||||||
|
}
|
||||||
|
|
||||||
|
w90p910_nand_enable(w90p910_nand);
|
||||||
|
|
||||||
|
if (nand_scan(&(w90p910_nand->mtd), 1)) {
|
||||||
|
retval = -ENXIO;
|
||||||
|
goto fail3;
|
||||||
|
}
|
||||||
|
|
||||||
|
add_mtd_partitions(&(w90p910_nand->mtd), partitions,
|
||||||
|
ARRAY_SIZE(partitions));
|
||||||
|
|
||||||
|
platform_set_drvdata(pdev, w90p910_nand);
|
||||||
|
|
||||||
|
return retval;
|
||||||
|
|
||||||
|
fail3: iounmap(w90p910_nand->reg);
|
||||||
|
fail2: release_mem_region(res->start, resource_size(res));
|
||||||
|
fail1: kfree(w90p910_nand);
|
||||||
|
return retval;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int __devexit w90p910_nand_remove(struct platform_device *pdev)
|
||||||
|
{
|
||||||
|
struct w90p910_nand *w90p910_nand = platform_get_drvdata(pdev);
|
||||||
|
struct resource *res;
|
||||||
|
|
||||||
|
iounmap(w90p910_nand->reg);
|
||||||
|
|
||||||
|
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||||
|
release_mem_region(res->start, resource_size(res));
|
||||||
|
|
||||||
|
clk_disable(w90p910_nand->clk);
|
||||||
|
clk_put(w90p910_nand->clk);
|
||||||
|
|
||||||
|
kfree(w90p910_nand);
|
||||||
|
|
||||||
|
platform_set_drvdata(pdev, NULL);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct platform_driver w90p910_nand_driver = {
|
||||||
|
.probe = w90p910_nand_probe,
|
||||||
|
.remove = __devexit_p(w90p910_nand_remove),
|
||||||
|
.driver = {
|
||||||
|
.name = "w90p910-fmi",
|
||||||
|
.owner = THIS_MODULE,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
static int __init w90p910_nand_init(void)
|
||||||
|
{
|
||||||
|
return platform_driver_register(&w90p910_nand_driver);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void __exit w90p910_nand_exit(void)
|
||||||
|
{
|
||||||
|
platform_driver_unregister(&w90p910_nand_driver);
|
||||||
|
}
|
||||||
|
|
||||||
|
module_init(w90p910_nand_init);
|
||||||
|
module_exit(w90p910_nand_exit);
|
||||||
|
|
||||||
|
MODULE_AUTHOR("Wan ZongShun <mcuos.com@gmail.com>");
|
||||||
|
MODULE_DESCRIPTION("w90p910 nand driver!");
|
||||||
|
MODULE_LICENSE("GPL");
|
||||||
|
MODULE_ALIAS("platform:w90p910-fmi");
|
|
@ -46,21 +46,12 @@ int __devinit of_mtd_parse_partitions(struct device *dev,
|
||||||
const u32 *reg;
|
const u32 *reg;
|
||||||
int len;
|
int len;
|
||||||
|
|
||||||
/* check if this is a partition node */
|
reg = of_get_property(pp, "reg", &len);
|
||||||
partname = of_get_property(pp, "name", &len);
|
if (!reg) {
|
||||||
if (strcmp(partname, "partition") != 0) {
|
|
||||||
nr_parts--;
|
nr_parts--;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
reg = of_get_property(pp, "reg", &len);
|
|
||||||
if (!reg || (len != 2 * sizeof(u32))) {
|
|
||||||
of_node_put(pp);
|
|
||||||
dev_err(dev, "Invalid 'reg' on %s\n", node->full_name);
|
|
||||||
kfree(*pparts);
|
|
||||||
*pparts = NULL;
|
|
||||||
return -EINVAL;
|
|
||||||
}
|
|
||||||
(*pparts)[i].offset = reg[0];
|
(*pparts)[i].offset = reg[0];
|
||||||
(*pparts)[i].size = reg[1];
|
(*pparts)[i].size = reg[1];
|
||||||
|
|
||||||
|
@ -75,6 +66,14 @@ int __devinit of_mtd_parse_partitions(struct device *dev,
|
||||||
i++;
|
i++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!i) {
|
||||||
|
of_node_put(pp);
|
||||||
|
dev_err(dev, "No valid partition found on %s\n", node->full_name);
|
||||||
|
kfree(*pparts);
|
||||||
|
*pparts = NULL;
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
|
||||||
return nr_parts;
|
return nr_parts;
|
||||||
}
|
}
|
||||||
EXPORT_SYMBOL(of_mtd_parse_partitions);
|
EXPORT_SYMBOL(of_mtd_parse_partitions);
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
menuconfig MTD_ONENAND
|
menuconfig MTD_ONENAND
|
||||||
tristate "OneNAND Device Support"
|
tristate "OneNAND Device Support"
|
||||||
depends on MTD
|
depends on MTD
|
||||||
|
select MTD_PARTITIONS
|
||||||
help
|
help
|
||||||
This enables support for accessing all type of OneNAND flash
|
This enables support for accessing all type of OneNAND flash
|
||||||
devices. For further information see
|
devices. For further information see
|
||||||
|
@ -23,7 +24,6 @@ config MTD_ONENAND_VERIFY_WRITE
|
||||||
|
|
||||||
config MTD_ONENAND_GENERIC
|
config MTD_ONENAND_GENERIC
|
||||||
tristate "OneNAND Flash device via platform device driver"
|
tristate "OneNAND Flash device via platform device driver"
|
||||||
depends on ARM
|
|
||||||
help
|
help
|
||||||
Support for OneNAND flash via platform device driver.
|
Support for OneNAND flash via platform device driver.
|
||||||
|
|
||||||
|
@ -66,7 +66,6 @@ config MTD_ONENAND_2X_PROGRAM
|
||||||
|
|
||||||
config MTD_ONENAND_SIM
|
config MTD_ONENAND_SIM
|
||||||
tristate "OneNAND simulator support"
|
tristate "OneNAND simulator support"
|
||||||
depends on MTD_PARTITIONS
|
|
||||||
help
|
help
|
||||||
The simulator may simulate various OneNAND flash chips for the
|
The simulator may simulate various OneNAND flash chips for the
|
||||||
OneNAND MTD layer.
|
OneNAND MTD layer.
|
||||||
|
|
|
@ -19,12 +19,16 @@
|
||||||
#include <linux/mtd/mtd.h>
|
#include <linux/mtd/mtd.h>
|
||||||
#include <linux/mtd/onenand.h>
|
#include <linux/mtd/onenand.h>
|
||||||
#include <linux/mtd/partitions.h>
|
#include <linux/mtd/partitions.h>
|
||||||
|
|
||||||
#include <asm/io.h>
|
#include <asm/io.h>
|
||||||
#include <asm/mach/flash.h>
|
|
||||||
|
|
||||||
#define DRIVER_NAME "onenand"
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Note: Driver name and platform data format have been updated!
|
||||||
|
*
|
||||||
|
* This version of the driver is named "onenand-flash" and takes struct
|
||||||
|
* onenand_platform_data as platform data. The old ARM-specific version
|
||||||
|
* with the name "onenand" used to take struct flash_platform_data.
|
||||||
|
*/
|
||||||
|
#define DRIVER_NAME "onenand-flash"
|
||||||
|
|
||||||
#ifdef CONFIG_MTD_PARTITIONS
|
#ifdef CONFIG_MTD_PARTITIONS
|
||||||
static const char *part_probes[] = { "cmdlinepart", NULL, };
|
static const char *part_probes[] = { "cmdlinepart", NULL, };
|
||||||
|
@ -39,16 +43,16 @@ struct onenand_info {
|
||||||
static int __devinit generic_onenand_probe(struct platform_device *pdev)
|
static int __devinit generic_onenand_probe(struct platform_device *pdev)
|
||||||
{
|
{
|
||||||
struct onenand_info *info;
|
struct onenand_info *info;
|
||||||
struct flash_platform_data *pdata = pdev->dev.platform_data;
|
struct onenand_platform_data *pdata = pdev->dev.platform_data;
|
||||||
struct resource *res = pdev->resource;
|
struct resource *res = pdev->resource;
|
||||||
unsigned long size = res->end - res->start + 1;
|
unsigned long size = resource_size(res);
|
||||||
int err;
|
int err;
|
||||||
|
|
||||||
info = kzalloc(sizeof(struct onenand_info), GFP_KERNEL);
|
info = kzalloc(sizeof(struct onenand_info), GFP_KERNEL);
|
||||||
if (!info)
|
if (!info)
|
||||||
return -ENOMEM;
|
return -ENOMEM;
|
||||||
|
|
||||||
if (!request_mem_region(res->start, size, pdev->dev.driver->name)) {
|
if (!request_mem_region(res->start, size, dev_name(&pdev->dev))) {
|
||||||
err = -EBUSY;
|
err = -EBUSY;
|
||||||
goto out_free_info;
|
goto out_free_info;
|
||||||
}
|
}
|
||||||
|
@ -59,7 +63,7 @@ static int __devinit generic_onenand_probe(struct platform_device *pdev)
|
||||||
goto out_release_mem_region;
|
goto out_release_mem_region;
|
||||||
}
|
}
|
||||||
|
|
||||||
info->onenand.mmcontrol = pdata->mmcontrol;
|
info->onenand.mmcontrol = pdata ? pdata->mmcontrol : 0;
|
||||||
info->onenand.irq = platform_get_irq(pdev, 0);
|
info->onenand.irq = platform_get_irq(pdev, 0);
|
||||||
|
|
||||||
info->mtd.name = dev_name(&pdev->dev);
|
info->mtd.name = dev_name(&pdev->dev);
|
||||||
|
@ -75,7 +79,7 @@ static int __devinit generic_onenand_probe(struct platform_device *pdev)
|
||||||
err = parse_mtd_partitions(&info->mtd, part_probes, &info->parts, 0);
|
err = parse_mtd_partitions(&info->mtd, part_probes, &info->parts, 0);
|
||||||
if (err > 0)
|
if (err > 0)
|
||||||
add_mtd_partitions(&info->mtd, info->parts, err);
|
add_mtd_partitions(&info->mtd, info->parts, err);
|
||||||
else if (err <= 0 && pdata->parts)
|
else if (err <= 0 && pdata && pdata->parts)
|
||||||
add_mtd_partitions(&info->mtd, pdata->parts, pdata->nr_parts);
|
add_mtd_partitions(&info->mtd, pdata->parts, pdata->nr_parts);
|
||||||
else
|
else
|
||||||
#endif
|
#endif
|
||||||
|
@ -99,7 +103,7 @@ static int __devexit generic_onenand_remove(struct platform_device *pdev)
|
||||||
{
|
{
|
||||||
struct onenand_info *info = platform_get_drvdata(pdev);
|
struct onenand_info *info = platform_get_drvdata(pdev);
|
||||||
struct resource *res = pdev->resource;
|
struct resource *res = pdev->resource;
|
||||||
unsigned long size = res->end - res->start + 1;
|
unsigned long size = resource_size(res);
|
||||||
|
|
||||||
platform_set_drvdata(pdev, NULL);
|
platform_set_drvdata(pdev, NULL);
|
||||||
|
|
||||||
|
|
|
@ -1191,7 +1191,7 @@ static int onenand_read_ops_nolock(struct mtd_info *mtd, loff_t from,
|
||||||
/*
|
/*
|
||||||
* Chip boundary handling in DDP
|
* Chip boundary handling in DDP
|
||||||
* Now we issued chip 1 read and pointed chip 1
|
* Now we issued chip 1 read and pointed chip 1
|
||||||
* bufferam so we have to point chip 0 bufferam.
|
* bufferram so we have to point chip 0 bufferram.
|
||||||
*/
|
*/
|
||||||
if (ONENAND_IS_DDP(this) &&
|
if (ONENAND_IS_DDP(this) &&
|
||||||
unlikely(from == (this->chipsize >> 1))) {
|
unlikely(from == (this->chipsize >> 1))) {
|
||||||
|
@ -1867,8 +1867,8 @@ static int onenand_write_ops_nolock(struct mtd_info *mtd, loff_t to,
|
||||||
ONENAND_SET_NEXT_BUFFERRAM(this);
|
ONENAND_SET_NEXT_BUFFERRAM(this);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* 2 PLANE, MLC, and Flex-OneNAND doesn't support
|
* 2 PLANE, MLC, and Flex-OneNAND do not support
|
||||||
* write-while-programe feature.
|
* write-while-program feature.
|
||||||
*/
|
*/
|
||||||
if (!ONENAND_IS_2PLANE(this) && !first) {
|
if (!ONENAND_IS_2PLANE(this) && !first) {
|
||||||
ONENAND_SET_PREV_BUFFERRAM(this);
|
ONENAND_SET_PREV_BUFFERRAM(this);
|
||||||
|
@ -1879,7 +1879,7 @@ static int onenand_write_ops_nolock(struct mtd_info *mtd, loff_t to,
|
||||||
onenand_update_bufferram(mtd, prev, !ret && !prev_subpage);
|
onenand_update_bufferram(mtd, prev, !ret && !prev_subpage);
|
||||||
if (ret) {
|
if (ret) {
|
||||||
written -= prevlen;
|
written -= prevlen;
|
||||||
printk(KERN_ERR "onenand_write_ops_nolock: write filaed %d\n", ret);
|
printk(KERN_ERR "onenand_write_ops_nolock: write failed %d\n", ret);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1905,7 +1905,7 @@ static int onenand_write_ops_nolock(struct mtd_info *mtd, loff_t to,
|
||||||
/* In partial page write we don't update bufferram */
|
/* In partial page write we don't update bufferram */
|
||||||
onenand_update_bufferram(mtd, to, !ret && !subpage);
|
onenand_update_bufferram(mtd, to, !ret && !subpage);
|
||||||
if (ret) {
|
if (ret) {
|
||||||
printk(KERN_ERR "onenand_write_ops_nolock: write filaed %d\n", ret);
|
printk(KERN_ERR "onenand_write_ops_nolock: write failed %d\n", ret);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2201,7 +2201,7 @@ static int onenand_erase(struct mtd_info *mtd, struct erase_info *instr)
|
||||||
/* Grab the lock and see if the device is available */
|
/* Grab the lock and see if the device is available */
|
||||||
onenand_get_device(mtd, FL_ERASING);
|
onenand_get_device(mtd, FL_ERASING);
|
||||||
|
|
||||||
/* Loop throught the pages */
|
/* Loop through the blocks */
|
||||||
instr->state = MTD_ERASING;
|
instr->state = MTD_ERASING;
|
||||||
|
|
||||||
while (len) {
|
while (len) {
|
||||||
|
@ -2328,7 +2328,7 @@ static int onenand_default_block_markbad(struct mtd_info *mtd, loff_t ofs)
|
||||||
if (bbm->bbt)
|
if (bbm->bbt)
|
||||||
bbm->bbt[block >> 2] |= 0x01 << ((block & 0x03) << 1);
|
bbm->bbt[block >> 2] |= 0x01 << ((block & 0x03) << 1);
|
||||||
|
|
||||||
/* We write two bytes, so we dont have to mess with 16 bit access */
|
/* We write two bytes, so we don't have to mess with 16-bit access */
|
||||||
ofs += mtd->oobsize + (bbm->badblockpos & ~0x01);
|
ofs += mtd->oobsize + (bbm->badblockpos & ~0x01);
|
||||||
/* FIXME : What to do when marking SLC block in partition
|
/* FIXME : What to do when marking SLC block in partition
|
||||||
* with MLC erasesize? For now, it is not advisable to
|
* with MLC erasesize? For now, it is not advisable to
|
||||||
|
@ -2557,7 +2557,7 @@ static void onenand_unlock_all(struct mtd_info *mtd)
|
||||||
|
|
||||||
#ifdef CONFIG_MTD_ONENAND_OTP
|
#ifdef CONFIG_MTD_ONENAND_OTP
|
||||||
|
|
||||||
/* Interal OTP operation */
|
/* Internal OTP operation */
|
||||||
typedef int (*otp_op_t)(struct mtd_info *mtd, loff_t form, size_t len,
|
typedef int (*otp_op_t)(struct mtd_info *mtd, loff_t form, size_t len,
|
||||||
size_t *retlen, u_char *buf);
|
size_t *retlen, u_char *buf);
|
||||||
|
|
||||||
|
@ -2921,7 +2921,7 @@ static void onenand_check_features(struct mtd_info *mtd)
|
||||||
this->options |= ONENAND_HAS_2PLANE;
|
this->options |= ONENAND_HAS_2PLANE;
|
||||||
|
|
||||||
case ONENAND_DEVICE_DENSITY_2Gb:
|
case ONENAND_DEVICE_DENSITY_2Gb:
|
||||||
/* 2Gb DDP don't have 2 plane */
|
/* 2Gb DDP does not have 2 plane */
|
||||||
if (!ONENAND_IS_DDP(this))
|
if (!ONENAND_IS_DDP(this))
|
||||||
this->options |= ONENAND_HAS_2PLANE;
|
this->options |= ONENAND_HAS_2PLANE;
|
||||||
this->options |= ONENAND_HAS_UNLOCK_ALL;
|
this->options |= ONENAND_HAS_UNLOCK_ALL;
|
||||||
|
@ -3364,7 +3364,7 @@ static int onenand_probe(struct mtd_info *mtd)
|
||||||
/* It's real page size */
|
/* It's real page size */
|
||||||
this->writesize = mtd->writesize;
|
this->writesize = mtd->writesize;
|
||||||
|
|
||||||
/* REVIST: Multichip handling */
|
/* REVISIT: Multichip handling */
|
||||||
|
|
||||||
if (FLEXONENAND(this))
|
if (FLEXONENAND(this))
|
||||||
flexonenand_get_size(mtd);
|
flexonenand_get_size(mtd);
|
||||||
|
|
|
@ -512,7 +512,7 @@ static int __init mtd_oobtest_init(void)
|
||||||
goto out;
|
goto out;
|
||||||
|
|
||||||
addr0 = 0;
|
addr0 = 0;
|
||||||
for (i = 0; bbt[i] && i < ebcnt; ++i)
|
for (i = 0; i < ebcnt && bbt[i]; ++i)
|
||||||
addr0 += mtd->erasesize;
|
addr0 += mtd->erasesize;
|
||||||
|
|
||||||
/* Attempt to write off end of OOB */
|
/* Attempt to write off end of OOB */
|
||||||
|
|
|
@ -116,11 +116,11 @@ static int verify_eraseblock(int ebnum)
|
||||||
loff_t addr = ebnum * mtd->erasesize;
|
loff_t addr = ebnum * mtd->erasesize;
|
||||||
|
|
||||||
addr0 = 0;
|
addr0 = 0;
|
||||||
for (i = 0; bbt[i] && i < ebcnt; ++i)
|
for (i = 0; i < ebcnt && bbt[i]; ++i)
|
||||||
addr0 += mtd->erasesize;
|
addr0 += mtd->erasesize;
|
||||||
|
|
||||||
addrn = mtd->size;
|
addrn = mtd->size;
|
||||||
for (i = 0; bbt[ebcnt - i - 1] && i < ebcnt; ++i)
|
for (i = 0; i < ebcnt && bbt[ebcnt - i - 1]; ++i)
|
||||||
addrn -= mtd->erasesize;
|
addrn -= mtd->erasesize;
|
||||||
|
|
||||||
set_random_data(writebuf, mtd->erasesize);
|
set_random_data(writebuf, mtd->erasesize);
|
||||||
|
@ -219,11 +219,11 @@ static int crosstest(void)
|
||||||
memset(pp1, 0, pgsize * 4);
|
memset(pp1, 0, pgsize * 4);
|
||||||
|
|
||||||
addr0 = 0;
|
addr0 = 0;
|
||||||
for (i = 0; bbt[i] && i < ebcnt; ++i)
|
for (i = 0; i < ebcnt && bbt[i]; ++i)
|
||||||
addr0 += mtd->erasesize;
|
addr0 += mtd->erasesize;
|
||||||
|
|
||||||
addrn = mtd->size;
|
addrn = mtd->size;
|
||||||
for (i = 0; bbt[ebcnt - i - 1] && i < ebcnt; ++i)
|
for (i = 0; i < ebcnt && bbt[ebcnt - i - 1]; ++i)
|
||||||
addrn -= mtd->erasesize;
|
addrn -= mtd->erasesize;
|
||||||
|
|
||||||
/* Read 2nd-to-last page to pp1 */
|
/* Read 2nd-to-last page to pp1 */
|
||||||
|
@ -317,7 +317,7 @@ static int erasecrosstest(void)
|
||||||
|
|
||||||
ebnum = 0;
|
ebnum = 0;
|
||||||
addr0 = 0;
|
addr0 = 0;
|
||||||
for (i = 0; bbt[i] && i < ebcnt; ++i) {
|
for (i = 0; i < ebcnt && bbt[i]; ++i) {
|
||||||
addr0 += mtd->erasesize;
|
addr0 += mtd->erasesize;
|
||||||
ebnum += 1;
|
ebnum += 1;
|
||||||
}
|
}
|
||||||
|
@ -413,7 +413,7 @@ static int erasetest(void)
|
||||||
|
|
||||||
ebnum = 0;
|
ebnum = 0;
|
||||||
addr0 = 0;
|
addr0 = 0;
|
||||||
for (i = 0; bbt[i] && i < ebcnt; ++i) {
|
for (i = 0; i < ebcnt && bbt[i]; ++i) {
|
||||||
addr0 += mtd->erasesize;
|
addr0 += mtd->erasesize;
|
||||||
ebnum += 1;
|
ebnum += 1;
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,6 +15,7 @@
|
||||||
#include <linux/completion.h>
|
#include <linux/completion.h>
|
||||||
#include <linux/sched.h>
|
#include <linux/sched.h>
|
||||||
#include <linux/freezer.h>
|
#include <linux/freezer.h>
|
||||||
|
#include <linux/kthread.h>
|
||||||
#include "nodelist.h"
|
#include "nodelist.h"
|
||||||
|
|
||||||
|
|
||||||
|
@ -31,7 +32,7 @@ void jffs2_garbage_collect_trigger(struct jffs2_sb_info *c)
|
||||||
/* This must only ever be called when no GC thread is currently running */
|
/* This must only ever be called when no GC thread is currently running */
|
||||||
int jffs2_start_garbage_collect_thread(struct jffs2_sb_info *c)
|
int jffs2_start_garbage_collect_thread(struct jffs2_sb_info *c)
|
||||||
{
|
{
|
||||||
pid_t pid;
|
struct task_struct *tsk;
|
||||||
int ret = 0;
|
int ret = 0;
|
||||||
|
|
||||||
BUG_ON(c->gc_task);
|
BUG_ON(c->gc_task);
|
||||||
|
@ -39,15 +40,16 @@ int jffs2_start_garbage_collect_thread(struct jffs2_sb_info *c)
|
||||||
init_completion(&c->gc_thread_start);
|
init_completion(&c->gc_thread_start);
|
||||||
init_completion(&c->gc_thread_exit);
|
init_completion(&c->gc_thread_exit);
|
||||||
|
|
||||||
pid = kernel_thread(jffs2_garbage_collect_thread, c, CLONE_FS|CLONE_FILES);
|
tsk = kthread_run(jffs2_garbage_collect_thread, c, "jffs2_gcd_mtd%d", c->mtd->index);
|
||||||
if (pid < 0) {
|
if (IS_ERR(tsk)) {
|
||||||
printk(KERN_WARNING "fork failed for JFFS2 garbage collect thread: %d\n", -pid);
|
printk(KERN_WARNING "fork failed for JFFS2 garbage collect thread: %ld\n", -PTR_ERR(tsk));
|
||||||
complete(&c->gc_thread_exit);
|
complete(&c->gc_thread_exit);
|
||||||
ret = pid;
|
ret = PTR_ERR(tsk);
|
||||||
} else {
|
} else {
|
||||||
/* Wait for it... */
|
/* Wait for it... */
|
||||||
D1(printk(KERN_DEBUG "JFFS2: Garbage collect thread is pid %d\n", pid));
|
D1(printk(KERN_DEBUG "JFFS2: Garbage collect thread is pid %d\n", tsk->pid));
|
||||||
wait_for_completion(&c->gc_thread_start);
|
wait_for_completion(&c->gc_thread_start);
|
||||||
|
ret = tsk->pid;
|
||||||
}
|
}
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
|
@ -71,7 +73,6 @@ static int jffs2_garbage_collect_thread(void *_c)
|
||||||
{
|
{
|
||||||
struct jffs2_sb_info *c = _c;
|
struct jffs2_sb_info *c = _c;
|
||||||
|
|
||||||
daemonize("jffs2_gcd_mtd%d", c->mtd->index);
|
|
||||||
allow_signal(SIGKILL);
|
allow_signal(SIGKILL);
|
||||||
allow_signal(SIGSTOP);
|
allow_signal(SIGSTOP);
|
||||||
allow_signal(SIGCONT);
|
allow_signal(SIGCONT);
|
||||||
|
@ -107,6 +108,11 @@ static int jffs2_garbage_collect_thread(void *_c)
|
||||||
* the GC thread get there first. */
|
* the GC thread get there first. */
|
||||||
schedule_timeout_interruptible(msecs_to_jiffies(50));
|
schedule_timeout_interruptible(msecs_to_jiffies(50));
|
||||||
|
|
||||||
|
if (kthread_should_stop()) {
|
||||||
|
D1(printk(KERN_DEBUG "jffs2_garbage_collect_thread(): kthread_stop() called.\n"));
|
||||||
|
goto die;
|
||||||
|
}
|
||||||
|
|
||||||
/* Put_super will send a SIGKILL and then wait on the sem.
|
/* Put_super will send a SIGKILL and then wait on the sem.
|
||||||
*/
|
*/
|
||||||
while (signal_pending(current) || freezing(current)) {
|
while (signal_pending(current) || freezing(current)) {
|
||||||
|
|
|
@ -39,13 +39,13 @@ int __init jffs2_create_slab_caches(void)
|
||||||
|
|
||||||
raw_dirent_slab = kmem_cache_create("jffs2_raw_dirent",
|
raw_dirent_slab = kmem_cache_create("jffs2_raw_dirent",
|
||||||
sizeof(struct jffs2_raw_dirent),
|
sizeof(struct jffs2_raw_dirent),
|
||||||
0, 0, NULL);
|
0, SLAB_HWCACHE_ALIGN, NULL);
|
||||||
if (!raw_dirent_slab)
|
if (!raw_dirent_slab)
|
||||||
goto err;
|
goto err;
|
||||||
|
|
||||||
raw_inode_slab = kmem_cache_create("jffs2_raw_inode",
|
raw_inode_slab = kmem_cache_create("jffs2_raw_inode",
|
||||||
sizeof(struct jffs2_raw_inode),
|
sizeof(struct jffs2_raw_inode),
|
||||||
0, 0, NULL);
|
0, SLAB_HWCACHE_ALIGN, NULL);
|
||||||
if (!raw_inode_slab)
|
if (!raw_inode_slab)
|
||||||
goto err;
|
goto err;
|
||||||
|
|
||||||
|
|
|
@ -121,6 +121,7 @@ typedef enum {
|
||||||
NAND_ECC_SOFT,
|
NAND_ECC_SOFT,
|
||||||
NAND_ECC_HW,
|
NAND_ECC_HW,
|
||||||
NAND_ECC_HW_SYNDROME,
|
NAND_ECC_HW_SYNDROME,
|
||||||
|
NAND_ECC_HW_OOB_FIRST,
|
||||||
} nand_ecc_modes_t;
|
} nand_ecc_modes_t;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -271,13 +272,13 @@ struct nand_ecc_ctrl {
|
||||||
uint8_t *calc_ecc);
|
uint8_t *calc_ecc);
|
||||||
int (*read_page_raw)(struct mtd_info *mtd,
|
int (*read_page_raw)(struct mtd_info *mtd,
|
||||||
struct nand_chip *chip,
|
struct nand_chip *chip,
|
||||||
uint8_t *buf);
|
uint8_t *buf, int page);
|
||||||
void (*write_page_raw)(struct mtd_info *mtd,
|
void (*write_page_raw)(struct mtd_info *mtd,
|
||||||
struct nand_chip *chip,
|
struct nand_chip *chip,
|
||||||
const uint8_t *buf);
|
const uint8_t *buf);
|
||||||
int (*read_page)(struct mtd_info *mtd,
|
int (*read_page)(struct mtd_info *mtd,
|
||||||
struct nand_chip *chip,
|
struct nand_chip *chip,
|
||||||
uint8_t *buf);
|
uint8_t *buf, int page);
|
||||||
int (*read_subpage)(struct mtd_info *mtd,
|
int (*read_subpage)(struct mtd_info *mtd,
|
||||||
struct nand_chip *chip,
|
struct nand_chip *chip,
|
||||||
uint32_t offs, uint32_t len,
|
uint32_t offs, uint32_t len,
|
||||||
|
|
|
@ -20,6 +20,12 @@ struct mtd_info;
|
||||||
*/
|
*/
|
||||||
int nand_calculate_ecc(struct mtd_info *mtd, const u_char *dat, u_char *ecc_code);
|
int nand_calculate_ecc(struct mtd_info *mtd, const u_char *dat, u_char *ecc_code);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Detect and correct a 1 bit error for eccsize byte block
|
||||||
|
*/
|
||||||
|
int __nand_correct_data(u_char *dat, u_char *read_ecc, u_char *calc_ecc,
|
||||||
|
unsigned int eccsize);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Detect and correct a 1 bit error for 256 byte block
|
* Detect and correct a 1 bit error for 256 byte block
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -214,4 +214,12 @@ unsigned onenand_block(struct onenand_chip *this, loff_t addr);
|
||||||
loff_t onenand_addr(struct onenand_chip *this, int block);
|
loff_t onenand_addr(struct onenand_chip *this, int block);
|
||||||
int flexonenand_region(struct mtd_info *mtd, loff_t addr);
|
int flexonenand_region(struct mtd_info *mtd, loff_t addr);
|
||||||
|
|
||||||
|
struct mtd_partition;
|
||||||
|
|
||||||
|
struct onenand_platform_data {
|
||||||
|
void (*mmcontrol)(struct mtd_info *mtd, int sync_read);
|
||||||
|
struct mtd_partition *parts;
|
||||||
|
unsigned int nr_parts;
|
||||||
|
};
|
||||||
|
|
||||||
#endif /* __LINUX_MTD_ONENAND_H */
|
#endif /* __LINUX_MTD_ONENAND_H */
|
||||||
|
|
|
@ -207,6 +207,9 @@
|
||||||
#define ONENAND_ECC_2BIT (1 << 1)
|
#define ONENAND_ECC_2BIT (1 << 1)
|
||||||
#define ONENAND_ECC_2BIT_ALL (0xAAAA)
|
#define ONENAND_ECC_2BIT_ALL (0xAAAA)
|
||||||
#define FLEXONENAND_UNCORRECTABLE_ERROR (0x1010)
|
#define FLEXONENAND_UNCORRECTABLE_ERROR (0x1010)
|
||||||
|
#define ONENAND_ECC_3BIT (1 << 2)
|
||||||
|
#define ONENAND_ECC_4BIT (1 << 3)
|
||||||
|
#define ONENAND_ECC_4BIT_UNCORRECTABLE (0x1010)
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* One-Time Programmable (OTP)
|
* One-Time Programmable (OTP)
|
||||||
|
|
Loading…
Reference in a new issue