mirror of
https://mirrors.bfsu.edu.cn/git/linux.git
synced 2024-11-11 12:28:41 +08:00
Merge git://git.infradead.org/mtd-2.6
* git://git.infradead.org/mtd-2.6: (67 commits) [MTD] [MAPS] Fix printk format warning in nettel.c [MTD] [NAND] add cmdline parsing (mtdparts=) support to cafe_nand [MTD] CFI: remove major/minor version check for command set 0x0002 [MTD] [NAND] ndfc driver [MTD] [TESTS] Fix some size_t printk format warnings [MTD] LPDDR Makefile and KConfig [MTD] LPDDR extended physmap driver to support LPDDR flash [MTD] LPDDR added new pfow_base parameter [MTD] LPDDR Command set driver [MTD] LPDDR PFOW definition [MTD] LPDDR QINFO records definitions [MTD] LPDDR qinfo probing. [MTD] [NAND] pxa3xx: convert from ns to clock ticks more accurately [MTD] [NAND] pxa3xx: fix non-page-aligned reads [MTD] [NAND] fix nandsim sched.h references [MTD] [NAND] alauda: use USB API functions rather than constants [MTD] struct device - replace bus_id with dev_name(), dev_set_name() [MTD] fix m25p80 64-bit divisions [MTD] fix dataflash 64-bit divisions [MTD] [NAND] Set the fsl elbc ECCM according the settings in bootloader. ... Fixed up trivial debug conflicts in drivers/mtd/devices/{m25p80.c,mtd_dataflash.c}
This commit is contained in:
commit
7c51d57e9d
39
Documentation/powerpc/dts-bindings/4xx/ndfc.txt
Normal file
39
Documentation/powerpc/dts-bindings/4xx/ndfc.txt
Normal file
@ -0,0 +1,39 @@
|
||||
AMCC NDFC (NanD Flash Controller)
|
||||
|
||||
Required properties:
|
||||
- compatible : "ibm,ndfc".
|
||||
- reg : should specify chip select and size used for the chip (0x2000).
|
||||
|
||||
Optional properties:
|
||||
- ccr : NDFC config and control register value (default 0).
|
||||
- bank-settings : NDFC bank configuration register value (default 0).
|
||||
|
||||
Notes:
|
||||
- partition(s) - follows the OF MTD standard for partitions
|
||||
|
||||
Example:
|
||||
|
||||
ndfc@1,0 {
|
||||
compatible = "ibm,ndfc";
|
||||
reg = <0x00000001 0x00000000 0x00002000>;
|
||||
ccr = <0x00001000>;
|
||||
bank-settings = <0x80002222>;
|
||||
#address-cells = <1>;
|
||||
#size-cells = <1>;
|
||||
|
||||
nand {
|
||||
#address-cells = <1>;
|
||||
#size-cells = <1>;
|
||||
|
||||
partition@0 {
|
||||
label = "kernel";
|
||||
reg = <0x00000000 0x00200000>;
|
||||
};
|
||||
partition@200000 {
|
||||
label = "root";
|
||||
reg = <0x00200000 0x03E00000>;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
|
@ -27,6 +27,7 @@
|
||||
#include <linux/spi/spi.h>
|
||||
#include <linux/spi/ads7846.h>
|
||||
#include <linux/spi/corgi_lcd.h>
|
||||
#include <linux/mtd/sharpsl.h>
|
||||
#include <video/w100fb.h>
|
||||
|
||||
#include <asm/setup.h>
|
||||
@ -542,6 +543,55 @@ err_free_1:
|
||||
static inline void corgi_init_spi(void) {}
|
||||
#endif
|
||||
|
||||
static struct mtd_partition sharpsl_nand_partitions[] = {
|
||||
{
|
||||
.name = "System Area",
|
||||
.offset = 0,
|
||||
.size = 7 * 1024 * 1024,
|
||||
},
|
||||
{
|
||||
.name = "Root Filesystem",
|
||||
.offset = 7 * 1024 * 1024,
|
||||
.size = 25 * 1024 * 1024,
|
||||
},
|
||||
{
|
||||
.name = "Home Filesystem",
|
||||
.offset = MTDPART_OFS_APPEND,
|
||||
.size = MTDPART_SIZ_FULL,
|
||||
},
|
||||
};
|
||||
|
||||
static uint8_t scan_ff_pattern[] = { 0xff, 0xff };
|
||||
|
||||
static struct nand_bbt_descr sharpsl_bbt = {
|
||||
.options = 0,
|
||||
.offs = 4,
|
||||
.len = 2,
|
||||
.pattern = scan_ff_pattern
|
||||
};
|
||||
|
||||
static struct sharpsl_nand_platform_data sharpsl_nand_platform_data = {
|
||||
.badblock_pattern = &sharpsl_bbt,
|
||||
.partitions = sharpsl_nand_partitions,
|
||||
.nr_partitions = ARRAY_SIZE(sharpsl_nand_partitions),
|
||||
};
|
||||
|
||||
static struct resource sharpsl_nand_resources[] = {
|
||||
{
|
||||
.start = 0x0C000000,
|
||||
.end = 0x0C000FFF,
|
||||
.flags = IORESOURCE_MEM,
|
||||
},
|
||||
};
|
||||
|
||||
static struct platform_device sharpsl_nand_device = {
|
||||
.name = "sharpsl-nand",
|
||||
.id = -1,
|
||||
.resource = sharpsl_nand_resources,
|
||||
.num_resources = ARRAY_SIZE(sharpsl_nand_resources),
|
||||
.dev.platform_data = &sharpsl_nand_platform_data,
|
||||
};
|
||||
|
||||
static struct mtd_partition sharpsl_rom_parts[] = {
|
||||
{
|
||||
.name ="Boot PROM Filesystem",
|
||||
@ -577,6 +627,7 @@ static struct platform_device *devices[] __initdata = {
|
||||
&corgifb_device,
|
||||
&corgikbd_device,
|
||||
&corgiled_device,
|
||||
&sharpsl_nand_device,
|
||||
&sharpsl_rom_device,
|
||||
};
|
||||
|
||||
@ -617,6 +668,9 @@ static void __init corgi_init(void)
|
||||
|
||||
platform_scoop_config = &corgi_pcmcia_config;
|
||||
|
||||
if (machine_is_husky())
|
||||
sharpsl_nand_partitions[1].size = 53 * 1024 * 1024;
|
||||
|
||||
platform_add_devices(devices, ARRAY_SIZE(devices));
|
||||
}
|
||||
|
||||
|
@ -24,6 +24,7 @@
|
||||
#include <linux/gpio.h>
|
||||
#include <linux/spi/spi.h>
|
||||
#include <linux/spi/ads7846.h>
|
||||
#include <linux/mtd/sharpsl.h>
|
||||
|
||||
#include <mach/hardware.h>
|
||||
#include <asm/mach-types.h>
|
||||
@ -414,6 +415,55 @@ static struct pxafb_mach_info poodle_fb_info = {
|
||||
.lcd_conn = LCD_COLOR_TFT_16BPP,
|
||||
};
|
||||
|
||||
static struct mtd_partition sharpsl_nand_partitions[] = {
|
||||
{
|
||||
.name = "System Area",
|
||||
.offset = 0,
|
||||
.size = 7 * 1024 * 1024,
|
||||
},
|
||||
{
|
||||
.name = "Root Filesystem",
|
||||
.offset = 7 * 1024 * 1024,
|
||||
.size = 22 * 1024 * 1024,
|
||||
},
|
||||
{
|
||||
.name = "Home Filesystem",
|
||||
.offset = MTDPART_OFS_APPEND,
|
||||
.size = MTDPART_SIZ_FULL,
|
||||
},
|
||||
};
|
||||
|
||||
static uint8_t scan_ff_pattern[] = { 0xff, 0xff };
|
||||
|
||||
static struct nand_bbt_descr sharpsl_bbt = {
|
||||
.options = 0,
|
||||
.offs = 4,
|
||||
.len = 2,
|
||||
.pattern = scan_ff_pattern
|
||||
};
|
||||
|
||||
static struct sharpsl_nand_platform_data sharpsl_nand_platform_data = {
|
||||
.badblock_pattern = &sharpsl_bbt,
|
||||
.partitions = sharpsl_nand_partitions,
|
||||
.nr_partitions = ARRAY_SIZE(sharpsl_nand_partitions),
|
||||
};
|
||||
|
||||
static struct resource sharpsl_nand_resources[] = {
|
||||
{
|
||||
.start = 0x0C000000,
|
||||
.end = 0x0C000FFF,
|
||||
.flags = IORESOURCE_MEM,
|
||||
},
|
||||
};
|
||||
|
||||
static struct platform_device sharpsl_nand_device = {
|
||||
.name = "sharpsl-nand",
|
||||
.id = -1,
|
||||
.resource = sharpsl_nand_resources,
|
||||
.num_resources = ARRAY_SIZE(sharpsl_nand_resources),
|
||||
.dev.platform_data = &sharpsl_nand_platform_data,
|
||||
};
|
||||
|
||||
static struct mtd_partition sharpsl_rom_parts[] = {
|
||||
{
|
||||
.name ="Boot PROM Filesystem",
|
||||
@ -447,6 +497,7 @@ static struct platform_device sharpsl_rom_device = {
|
||||
static struct platform_device *devices[] __initdata = {
|
||||
&poodle_locomo_device,
|
||||
&poodle_scoop_device,
|
||||
&sharpsl_nand_device,
|
||||
&sharpsl_rom_device,
|
||||
};
|
||||
|
||||
|
@ -31,6 +31,7 @@
|
||||
#include <linux/spi/spi.h>
|
||||
#include <linux/spi/ads7846.h>
|
||||
#include <linux/spi/corgi_lcd.h>
|
||||
#include <linux/mtd/sharpsl.h>
|
||||
|
||||
#include <asm/setup.h>
|
||||
#include <asm/memory.h>
|
||||
@ -613,6 +614,54 @@ static struct pxafb_mach_info spitz_pxafb_info = {
|
||||
.lcd_conn = LCD_COLOR_TFT_16BPP | LCD_ALTERNATE_MAPPING,
|
||||
};
|
||||
|
||||
static struct mtd_partition sharpsl_nand_partitions[] = {
|
||||
{
|
||||
.name = "System Area",
|
||||
.offset = 0,
|
||||
.size = 7 * 1024 * 1024,
|
||||
},
|
||||
{
|
||||
.name = "Root Filesystem",
|
||||
.offset = 7 * 1024 * 1024,
|
||||
},
|
||||
{
|
||||
.name = "Home Filesystem",
|
||||
.offset = MTDPART_OFS_APPEND,
|
||||
.size = MTDPART_SIZ_FULL,
|
||||
},
|
||||
};
|
||||
|
||||
static uint8_t scan_ff_pattern[] = { 0xff, 0xff };
|
||||
|
||||
static struct nand_bbt_descr sharpsl_bbt = {
|
||||
.options = 0,
|
||||
.offs = 4,
|
||||
.len = 2,
|
||||
.pattern = scan_ff_pattern
|
||||
};
|
||||
|
||||
static struct sharpsl_nand_platform_data sharpsl_nand_platform_data = {
|
||||
.badblock_pattern = &sharpsl_bbt,
|
||||
.partitions = sharpsl_nand_partitions,
|
||||
.nr_partitions = ARRAY_SIZE(sharpsl_nand_partitions),
|
||||
};
|
||||
|
||||
static struct resource sharpsl_nand_resources[] = {
|
||||
{
|
||||
.start = 0x0C000000,
|
||||
.end = 0x0C000FFF,
|
||||
.flags = IORESOURCE_MEM,
|
||||
},
|
||||
};
|
||||
|
||||
static struct platform_device sharpsl_nand_device = {
|
||||
.name = "sharpsl-nand",
|
||||
.id = -1,
|
||||
.resource = sharpsl_nand_resources,
|
||||
.num_resources = ARRAY_SIZE(sharpsl_nand_resources),
|
||||
.dev.platform_data = &sharpsl_nand_platform_data,
|
||||
};
|
||||
|
||||
|
||||
static struct mtd_partition sharpsl_rom_parts[] = {
|
||||
{
|
||||
@ -648,6 +697,7 @@ static struct platform_device *devices[] __initdata = {
|
||||
&spitzscoop_device,
|
||||
&spitzkbd_device,
|
||||
&spitzled_device,
|
||||
&sharpsl_nand_device,
|
||||
&sharpsl_rom_device,
|
||||
};
|
||||
|
||||
@ -671,6 +721,14 @@ static void __init common_init(void)
|
||||
pm_power_off = spitz_poweroff;
|
||||
arm_pm_restart = spitz_restart;
|
||||
|
||||
if (machine_is_spitz()) {
|
||||
sharpsl_nand_partitions[1].size = 5 * 1024 * 1024;
|
||||
} else if (machine_is_akita()) {
|
||||
sharpsl_nand_partitions[1].size = 58 * 1024 * 1024;
|
||||
} else if (machine_is_borzoi()) {
|
||||
sharpsl_nand_partitions[1].size = 32 * 1024 * 1024;
|
||||
}
|
||||
|
||||
PMCR = 0x00;
|
||||
|
||||
/* Stop 3.6MHz and drive HIGH to PCMCIA and CS */
|
||||
@ -715,10 +773,29 @@ static struct i2c_board_info akita_i2c_board_info[] = {
|
||||
},
|
||||
};
|
||||
|
||||
static struct nand_bbt_descr sharpsl_akita_bbt = {
|
||||
.options = 0,
|
||||
.offs = 4,
|
||||
.len = 1,
|
||||
.pattern = scan_ff_pattern
|
||||
};
|
||||
|
||||
static struct nand_ecclayout akita_oobinfo = {
|
||||
.eccbytes = 24,
|
||||
.eccpos = {
|
||||
0x5, 0x1, 0x2, 0x3, 0x6, 0x7, 0x15, 0x11,
|
||||
0x12, 0x13, 0x16, 0x17, 0x25, 0x21, 0x22, 0x23,
|
||||
0x26, 0x27, 0x35, 0x31, 0x32, 0x33, 0x36, 0x37},
|
||||
.oobfree = {{0x08, 0x09}}
|
||||
};
|
||||
|
||||
static void __init akita_init(void)
|
||||
{
|
||||
spitz_ficp_platform_data.transceiver_mode = akita_irda_transceiver_mode;
|
||||
|
||||
sharpsl_nand_platform_data.badblock_pattern = &sharpsl_akita_bbt;
|
||||
sharpsl_nand_platform_data.ecc_layout = &akita_oobinfo;
|
||||
|
||||
/* We just pretend the second element of the array doesn't exist */
|
||||
spitz_pcmcia_config.num_devs = 1;
|
||||
platform_scoop_config = &spitz_pcmcia_config;
|
||||
|
@ -45,6 +45,14 @@ config MTD_PARTITIONS
|
||||
devices. Partitioning on NFTL 'devices' is a different - that's the
|
||||
'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
|
||||
tristate "RedBoot partition table parsing"
|
||||
depends on MTD_PARTITIONS
|
||||
@ -316,6 +324,8 @@ source "drivers/mtd/nand/Kconfig"
|
||||
|
||||
source "drivers/mtd/onenand/Kconfig"
|
||||
|
||||
source "drivers/mtd/lpddr/Kconfig"
|
||||
|
||||
source "drivers/mtd/ubi/Kconfig"
|
||||
|
||||
endif # MTD
|
||||
|
@ -29,6 +29,6 @@ obj-$(CONFIG_MTD_OOPS) += mtdoops.o
|
||||
nftl-objs := nftlcore.o nftlmount.o
|
||||
inftl-objs := inftlcore.o inftlmount.o
|
||||
|
||||
obj-y += chips/ maps/ devices/ nand/ onenand/
|
||||
obj-y += chips/ lpddr/ maps/ devices/ nand/ onenand/ tests/
|
||||
|
||||
obj-$(CONFIG_MTD_UBI) += ubi/
|
||||
|
@ -58,8 +58,8 @@ static int cfi_intelext_write_buffers(struct mtd_info *, loff_t, size_t, size_t
|
||||
static int cfi_intelext_writev(struct mtd_info *, const struct kvec *, unsigned long, loff_t, size_t *);
|
||||
static int cfi_intelext_erase_varsize(struct mtd_info *, struct erase_info *);
|
||||
static void cfi_intelext_sync (struct mtd_info *);
|
||||
static int cfi_intelext_lock(struct mtd_info *mtd, loff_t ofs, size_t len);
|
||||
static int cfi_intelext_unlock(struct mtd_info *mtd, loff_t ofs, size_t len);
|
||||
static int cfi_intelext_lock(struct mtd_info *mtd, loff_t ofs, uint64_t len);
|
||||
static int cfi_intelext_unlock(struct mtd_info *mtd, loff_t ofs, uint64_t len);
|
||||
#ifdef CONFIG_MTD_OTP
|
||||
static int cfi_intelext_read_fact_prot_reg (struct mtd_info *, loff_t, size_t, size_t *, u_char *);
|
||||
static int cfi_intelext_read_user_prot_reg (struct mtd_info *, loff_t, size_t, size_t *, u_char *);
|
||||
@ -558,8 +558,8 @@ static struct mtd_info *cfi_intelext_setup(struct mtd_info *mtd)
|
||||
}
|
||||
|
||||
for (i=0; i<mtd->numeraseregions;i++){
|
||||
printk(KERN_DEBUG "erase region %d: offset=0x%x,size=0x%x,blocks=%d\n",
|
||||
i,mtd->eraseregions[i].offset,
|
||||
printk(KERN_DEBUG "erase region %d: offset=0x%llx,size=0x%x,blocks=%d\n",
|
||||
i,(unsigned long long)mtd->eraseregions[i].offset,
|
||||
mtd->eraseregions[i].erasesize,
|
||||
mtd->eraseregions[i].numblocks);
|
||||
}
|
||||
@ -2058,7 +2058,7 @@ out: put_chip(map, chip, adr);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int cfi_intelext_lock(struct mtd_info *mtd, loff_t ofs, size_t len)
|
||||
static int cfi_intelext_lock(struct mtd_info *mtd, loff_t ofs, uint64_t len)
|
||||
{
|
||||
int ret;
|
||||
|
||||
@ -2082,7 +2082,7 @@ static int cfi_intelext_lock(struct mtd_info *mtd, loff_t ofs, size_t len)
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int cfi_intelext_unlock(struct mtd_info *mtd, loff_t ofs, size_t len)
|
||||
static int cfi_intelext_unlock(struct mtd_info *mtd, loff_t ofs, uint64_t len)
|
||||
{
|
||||
int ret;
|
||||
|
||||
|
@ -71,8 +71,8 @@ static int get_chip(struct map_info *map, struct flchip *chip, unsigned long adr
|
||||
static void put_chip(struct map_info *map, struct flchip *chip, unsigned long adr);
|
||||
#include "fwh_lock.h"
|
||||
|
||||
static int cfi_atmel_lock(struct mtd_info *mtd, loff_t ofs, size_t len);
|
||||
static int cfi_atmel_unlock(struct mtd_info *mtd, loff_t ofs, size_t len);
|
||||
static int cfi_atmel_lock(struct mtd_info *mtd, loff_t ofs, uint64_t len);
|
||||
static int cfi_atmel_unlock(struct mtd_info *mtd, loff_t ofs, uint64_t len);
|
||||
|
||||
static struct mtd_chip_driver cfi_amdstd_chipdrv = {
|
||||
.probe = NULL, /* Not usable directly */
|
||||
@ -322,6 +322,14 @@ static struct cfi_fixup fixup_table[] = {
|
||||
};
|
||||
|
||||
|
||||
static void cfi_fixup_major_minor(struct cfi_private *cfi,
|
||||
struct cfi_pri_amdstd *extp)
|
||||
{
|
||||
if (cfi->mfr == CFI_MFR_SAMSUNG && cfi->id == 0x257e &&
|
||||
extp->MajorVersion == '0')
|
||||
extp->MajorVersion = '1';
|
||||
}
|
||||
|
||||
struct mtd_info *cfi_cmdset_0002(struct map_info *map, int primary)
|
||||
{
|
||||
struct cfi_private *cfi = map->fldrv_priv;
|
||||
@ -363,6 +371,8 @@ struct mtd_info *cfi_cmdset_0002(struct map_info *map, int primary)
|
||||
return NULL;
|
||||
}
|
||||
|
||||
cfi_fixup_major_minor(cfi, extp);
|
||||
|
||||
if (extp->MajorVersion != '1' ||
|
||||
(extp->MinorVersion < '0' || extp->MinorVersion > '4')) {
|
||||
printk(KERN_ERR " Unknown Amd/Fujitsu Extended Query "
|
||||
@ -1774,12 +1784,12 @@ out_unlock:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int cfi_atmel_lock(struct mtd_info *mtd, loff_t ofs, size_t len)
|
||||
static int cfi_atmel_lock(struct mtd_info *mtd, loff_t ofs, uint64_t len)
|
||||
{
|
||||
return cfi_varsize_frob(mtd, do_atmel_lock, ofs, len, NULL);
|
||||
}
|
||||
|
||||
static int cfi_atmel_unlock(struct mtd_info *mtd, loff_t ofs, size_t len)
|
||||
static int cfi_atmel_unlock(struct mtd_info *mtd, loff_t ofs, uint64_t len)
|
||||
{
|
||||
return cfi_varsize_frob(mtd, do_atmel_unlock, ofs, len, NULL);
|
||||
}
|
||||
|
@ -42,8 +42,8 @@ static int cfi_staa_writev(struct mtd_info *mtd, const struct kvec *vecs,
|
||||
unsigned long count, loff_t to, size_t *retlen);
|
||||
static int cfi_staa_erase_varsize(struct mtd_info *, struct erase_info *);
|
||||
static void cfi_staa_sync (struct mtd_info *);
|
||||
static int cfi_staa_lock(struct mtd_info *mtd, loff_t ofs, size_t len);
|
||||
static int cfi_staa_unlock(struct mtd_info *mtd, loff_t ofs, size_t len);
|
||||
static int cfi_staa_lock(struct mtd_info *mtd, loff_t ofs, uint64_t len);
|
||||
static int cfi_staa_unlock(struct mtd_info *mtd, loff_t ofs, uint64_t len);
|
||||
static int cfi_staa_suspend (struct mtd_info *);
|
||||
static void cfi_staa_resume (struct mtd_info *);
|
||||
|
||||
@ -221,8 +221,8 @@ static struct mtd_info *cfi_staa_setup(struct map_info *map)
|
||||
}
|
||||
|
||||
for (i=0; i<mtd->numeraseregions;i++){
|
||||
printk(KERN_DEBUG "%d: offset=0x%x,size=0x%x,blocks=%d\n",
|
||||
i,mtd->eraseregions[i].offset,
|
||||
printk(KERN_DEBUG "%d: offset=0x%llx,size=0x%x,blocks=%d\n",
|
||||
i, (unsigned long long)mtd->eraseregions[i].offset,
|
||||
mtd->eraseregions[i].erasesize,
|
||||
mtd->eraseregions[i].numblocks);
|
||||
}
|
||||
@ -964,7 +964,7 @@ static int cfi_staa_erase_varsize(struct mtd_info *mtd,
|
||||
adr += regions[i].erasesize;
|
||||
len -= regions[i].erasesize;
|
||||
|
||||
if (adr % (1<< cfi->chipshift) == ((regions[i].offset + (regions[i].erasesize * regions[i].numblocks)) %( 1<< cfi->chipshift)))
|
||||
if (adr % (1<< cfi->chipshift) == (((unsigned long)regions[i].offset + (regions[i].erasesize * regions[i].numblocks)) %( 1<< cfi->chipshift)))
|
||||
i++;
|
||||
|
||||
if (adr >> cfi->chipshift) {
|
||||
@ -1135,7 +1135,7 @@ retry:
|
||||
spin_unlock_bh(chip->mutex);
|
||||
return 0;
|
||||
}
|
||||
static int cfi_staa_lock(struct mtd_info *mtd, loff_t ofs, size_t len)
|
||||
static int cfi_staa_lock(struct mtd_info *mtd, loff_t ofs, uint64_t len)
|
||||
{
|
||||
struct map_info *map = mtd->priv;
|
||||
struct cfi_private *cfi = map->fldrv_priv;
|
||||
@ -1284,7 +1284,7 @@ retry:
|
||||
spin_unlock_bh(chip->mutex);
|
||||
return 0;
|
||||
}
|
||||
static int cfi_staa_unlock(struct mtd_info *mtd, loff_t ofs, size_t len)
|
||||
static int cfi_staa_unlock(struct mtd_info *mtd, loff_t ofs, uint64_t len)
|
||||
{
|
||||
struct map_info *map = mtd->priv;
|
||||
struct cfi_private *cfi = map->fldrv_priv;
|
||||
|
@ -77,7 +77,7 @@ static int fwh_xxlock_oneblock(struct map_info *map, struct flchip *chip,
|
||||
}
|
||||
|
||||
|
||||
static int fwh_lock_varsize(struct mtd_info *mtd, loff_t ofs, size_t len)
|
||||
static int fwh_lock_varsize(struct mtd_info *mtd, loff_t ofs, uint64_t len)
|
||||
{
|
||||
int ret;
|
||||
|
||||
@ -88,7 +88,7 @@ static int fwh_lock_varsize(struct mtd_info *mtd, loff_t ofs, size_t len)
|
||||
}
|
||||
|
||||
|
||||
static int fwh_unlock_varsize(struct mtd_info *mtd, loff_t ofs, size_t len)
|
||||
static int fwh_unlock_varsize(struct mtd_info *mtd, loff_t ofs, uint64_t len)
|
||||
{
|
||||
int ret;
|
||||
|
||||
|
@ -619,7 +619,7 @@ static struct mtd_partition lart_partitions[] = {
|
||||
};
|
||||
#endif
|
||||
|
||||
int __init lart_flash_init (void)
|
||||
static int __init lart_flash_init (void)
|
||||
{
|
||||
int result;
|
||||
memset (&mtd,0,sizeof (mtd));
|
||||
@ -690,7 +690,7 @@ int __init lart_flash_init (void)
|
||||
return (result);
|
||||
}
|
||||
|
||||
void __exit lart_flash_exit (void)
|
||||
static void __exit lart_flash_exit (void)
|
||||
{
|
||||
#ifndef HAVE_PARTITIONS
|
||||
del_mtd_device (&mtd);
|
||||
@ -705,5 +705,3 @@ module_exit (lart_flash_exit);
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_AUTHOR("Abraham vd Merwe <abraham@2d3d.co.za>");
|
||||
MODULE_DESCRIPTION("MTD driver for Intel 28F160F3 on LART board");
|
||||
|
||||
|
||||
|
@ -20,6 +20,7 @@
|
||||
#include <linux/device.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/math64.h>
|
||||
|
||||
#include <linux/mtd/mtd.h>
|
||||
#include <linux/mtd/partitions.h>
|
||||
@ -169,9 +170,9 @@ static int wait_till_ready(struct m25p *flash)
|
||||
*/
|
||||
static int erase_chip(struct m25p *flash)
|
||||
{
|
||||
DEBUG(MTD_DEBUG_LEVEL3, "%s: %s %dKiB\n",
|
||||
dev_name(&flash->spi->dev), __func__,
|
||||
flash->mtd.size / 1024);
|
||||
DEBUG(MTD_DEBUG_LEVEL3, "%s: %s %lldKiB\n",
|
||||
dev_name(&flash->spi->dev), __func__,
|
||||
(long long)(flash->mtd.size >> 10));
|
||||
|
||||
/* Wait until finished previous write command. */
|
||||
if (wait_till_ready(flash))
|
||||
@ -232,18 +233,18 @@ static int m25p80_erase(struct mtd_info *mtd, struct erase_info *instr)
|
||||
{
|
||||
struct m25p *flash = mtd_to_m25p(mtd);
|
||||
u32 addr,len;
|
||||
uint32_t rem;
|
||||
|
||||
DEBUG(MTD_DEBUG_LEVEL2, "%s: %s %s 0x%08x, len %d\n",
|
||||
dev_name(&flash->spi->dev), __func__, "at",
|
||||
(u32)instr->addr, instr->len);
|
||||
DEBUG(MTD_DEBUG_LEVEL2, "%s: %s %s 0x%llx, len %lld\n",
|
||||
dev_name(&flash->spi->dev), __func__, "at",
|
||||
(long long)instr->addr, (long long)instr->len);
|
||||
|
||||
/* sanity checks */
|
||||
if (instr->addr + instr->len > flash->mtd.size)
|
||||
return -EINVAL;
|
||||
if ((instr->addr % mtd->erasesize) != 0
|
||||
|| (instr->len % mtd->erasesize) != 0) {
|
||||
div_u64_rem(instr->len, mtd->erasesize, &rem);
|
||||
if (rem)
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
addr = instr->addr;
|
||||
len = instr->len;
|
||||
@ -677,24 +678,24 @@ static int __devinit m25p_probe(struct spi_device *spi)
|
||||
flash->mtd.erasesize = info->sector_size;
|
||||
}
|
||||
|
||||
dev_info(&spi->dev, "%s (%d Kbytes)\n", info->name,
|
||||
flash->mtd.size / 1024);
|
||||
dev_info(&spi->dev, "%s (%lld Kbytes)\n", info->name,
|
||||
(long long)flash->mtd.size >> 10);
|
||||
|
||||
DEBUG(MTD_DEBUG_LEVEL2,
|
||||
"mtd .name = %s, .size = 0x%.8x (%uMiB) "
|
||||
"mtd .name = %s, .size = 0x%llx (%lldMiB) "
|
||||
".erasesize = 0x%.8x (%uKiB) .numeraseregions = %d\n",
|
||||
flash->mtd.name,
|
||||
flash->mtd.size, flash->mtd.size / (1024*1024),
|
||||
(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%.8x, "
|
||||
"mtd.eraseregions[%d] = { .offset = 0x%llx, "
|
||||
".erasesize = 0x%.8x (%uKiB), "
|
||||
".numblocks = %d }\n",
|
||||
i, flash->mtd.eraseregions[i].offset,
|
||||
i, (long long)flash->mtd.eraseregions[i].offset,
|
||||
flash->mtd.eraseregions[i].erasesize,
|
||||
flash->mtd.eraseregions[i].erasesize / 1024,
|
||||
flash->mtd.eraseregions[i].numblocks);
|
||||
@ -722,12 +723,12 @@ static int __devinit m25p_probe(struct spi_device *spi)
|
||||
if (nr_parts > 0) {
|
||||
for (i = 0; i < nr_parts; i++) {
|
||||
DEBUG(MTD_DEBUG_LEVEL2, "partitions[%d] = "
|
||||
"{.name = %s, .offset = 0x%.8x, "
|
||||
".size = 0x%.8x (%uKiB) }\n",
|
||||
"{.name = %s, .offset = 0x%llx, "
|
||||
".size = 0x%llx (%lldKiB) }\n",
|
||||
i, parts[i].name,
|
||||
parts[i].offset,
|
||||
parts[i].size,
|
||||
parts[i].size / 1024);
|
||||
(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);
|
||||
|
@ -16,6 +16,7 @@
|
||||
#include <linux/device.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/math64.h>
|
||||
|
||||
#include <linux/spi/spi.h>
|
||||
#include <linux/spi/flash.h>
|
||||
@ -152,15 +153,20 @@ static int dataflash_erase(struct mtd_info *mtd, struct erase_info *instr)
|
||||
struct spi_message msg;
|
||||
unsigned blocksize = priv->page_size << 3;
|
||||
uint8_t *command;
|
||||
uint32_t rem;
|
||||
|
||||
DEBUG(MTD_DEBUG_LEVEL2, "%s: erase addr=0x%x len 0x%x\n",
|
||||
dev_name(&spi->dev),
|
||||
instr->addr, instr->len);
|
||||
DEBUG(MTD_DEBUG_LEVEL2, "%s: erase addr=0x%llx len 0x%llx\n",
|
||||
dev_name(&spi->dev), (long long)instr->addr,
|
||||
(long long)instr->len);
|
||||
|
||||
/* Sanity checks */
|
||||
if ((instr->addr + instr->len) > mtd->size
|
||||
|| (instr->len % priv->page_size) != 0
|
||||
|| (instr->addr % priv->page_size) != 0)
|
||||
if (instr->addr + instr->len > mtd->size)
|
||||
return -EINVAL;
|
||||
div_u64_rem(instr->len, priv->page_size, &rem);
|
||||
if (rem)
|
||||
return -EINVAL;
|
||||
div_u64_rem(instr->addr, priv->page_size, &rem);
|
||||
if (rem)
|
||||
return -EINVAL;
|
||||
|
||||
spi_message_init(&msg);
|
||||
@ -178,7 +184,7 @@ static int dataflash_erase(struct mtd_info *mtd, struct erase_info *instr)
|
||||
/* Calculate flash page address; use block erase (for speed) if
|
||||
* we're at a block boundary and need to erase the whole block.
|
||||
*/
|
||||
pageaddr = instr->addr / priv->page_size;
|
||||
pageaddr = div_u64(instr->len, priv->page_size);
|
||||
do_block = (pageaddr & 0x7) == 0 && instr->len >= blocksize;
|
||||
pageaddr = pageaddr << priv->page_offset;
|
||||
|
||||
@ -667,8 +673,8 @@ add_dataflash_otp(struct spi_device *spi, char *name,
|
||||
if (revision >= 'c')
|
||||
otp_tag = otp_setup(device, revision);
|
||||
|
||||
dev_info(&spi->dev, "%s (%d KBytes) pagesize %d bytes%s\n",
|
||||
name, DIV_ROUND_UP(device->size, 1024),
|
||||
dev_info(&spi->dev, "%s (%lld KBytes) pagesize %d bytes%s\n",
|
||||
name, (long long)((device->size + 1023) >> 10),
|
||||
pagesize, otp_tag);
|
||||
dev_set_drvdata(&spi->dev, priv);
|
||||
|
||||
|
@ -109,25 +109,25 @@ module_param(shuffle_freq, int, 0);
|
||||
/* Each memory region corresponds to a minor device */
|
||||
typedef struct partition_t {
|
||||
struct mtd_blktrans_dev mbd;
|
||||
u_int32_t state;
|
||||
u_int32_t *VirtualBlockMap;
|
||||
u_int32_t *VirtualPageMap;
|
||||
u_int32_t FreeTotal;
|
||||
uint32_t state;
|
||||
uint32_t *VirtualBlockMap;
|
||||
uint32_t *VirtualPageMap;
|
||||
uint32_t FreeTotal;
|
||||
struct eun_info_t {
|
||||
u_int32_t Offset;
|
||||
u_int32_t EraseCount;
|
||||
u_int32_t Free;
|
||||
u_int32_t Deleted;
|
||||
uint32_t Offset;
|
||||
uint32_t EraseCount;
|
||||
uint32_t Free;
|
||||
uint32_t Deleted;
|
||||
} *EUNInfo;
|
||||
struct xfer_info_t {
|
||||
u_int32_t Offset;
|
||||
u_int32_t EraseCount;
|
||||
u_int16_t state;
|
||||
uint32_t Offset;
|
||||
uint32_t EraseCount;
|
||||
uint16_t state;
|
||||
} *XferInfo;
|
||||
u_int16_t bam_index;
|
||||
u_int32_t *bam_cache;
|
||||
u_int16_t DataUnits;
|
||||
u_int32_t BlocksPerUnit;
|
||||
uint16_t bam_index;
|
||||
uint32_t *bam_cache;
|
||||
uint16_t DataUnits;
|
||||
uint32_t BlocksPerUnit;
|
||||
erase_unit_header_t header;
|
||||
} partition_t;
|
||||
|
||||
@ -199,8 +199,8 @@ static int scan_header(partition_t *part)
|
||||
static int build_maps(partition_t *part)
|
||||
{
|
||||
erase_unit_header_t header;
|
||||
u_int16_t xvalid, xtrans, i;
|
||||
u_int blocks, j;
|
||||
uint16_t xvalid, xtrans, i;
|
||||
unsigned blocks, j;
|
||||
int hdr_ok, ret = -1;
|
||||
ssize_t retval;
|
||||
loff_t offset;
|
||||
@ -269,14 +269,14 @@ static int build_maps(partition_t *part)
|
||||
|
||||
/* Set up virtual page map */
|
||||
blocks = le32_to_cpu(header.FormattedSize) >> header.BlockSize;
|
||||
part->VirtualBlockMap = vmalloc(blocks * sizeof(u_int32_t));
|
||||
part->VirtualBlockMap = vmalloc(blocks * sizeof(uint32_t));
|
||||
if (!part->VirtualBlockMap)
|
||||
goto out_XferInfo;
|
||||
|
||||
memset(part->VirtualBlockMap, 0xff, blocks * sizeof(u_int32_t));
|
||||
memset(part->VirtualBlockMap, 0xff, blocks * sizeof(uint32_t));
|
||||
part->BlocksPerUnit = (1 << header.EraseUnitSize) >> header.BlockSize;
|
||||
|
||||
part->bam_cache = kmalloc(part->BlocksPerUnit * sizeof(u_int32_t),
|
||||
part->bam_cache = kmalloc(part->BlocksPerUnit * sizeof(uint32_t),
|
||||
GFP_KERNEL);
|
||||
if (!part->bam_cache)
|
||||
goto out_VirtualBlockMap;
|
||||
@ -290,7 +290,7 @@ static int build_maps(partition_t *part)
|
||||
offset = part->EUNInfo[i].Offset + le32_to_cpu(header.BAMOffset);
|
||||
|
||||
ret = part->mbd.mtd->read(part->mbd.mtd, offset,
|
||||
part->BlocksPerUnit * sizeof(u_int32_t), &retval,
|
||||
part->BlocksPerUnit * sizeof(uint32_t), &retval,
|
||||
(unsigned char *)part->bam_cache);
|
||||
|
||||
if (ret)
|
||||
@ -332,7 +332,7 @@ out:
|
||||
======================================================================*/
|
||||
|
||||
static int erase_xfer(partition_t *part,
|
||||
u_int16_t xfernum)
|
||||
uint16_t xfernum)
|
||||
{
|
||||
int ret;
|
||||
struct xfer_info_t *xfer;
|
||||
@ -408,7 +408,7 @@ static int prepare_xfer(partition_t *part, int i)
|
||||
erase_unit_header_t header;
|
||||
struct xfer_info_t *xfer;
|
||||
int nbam, ret;
|
||||
u_int32_t ctl;
|
||||
uint32_t ctl;
|
||||
ssize_t retlen;
|
||||
loff_t offset;
|
||||
|
||||
@ -430,15 +430,15 @@ static int prepare_xfer(partition_t *part, int i)
|
||||
}
|
||||
|
||||
/* Write the BAM stub */
|
||||
nbam = (part->BlocksPerUnit * sizeof(u_int32_t) +
|
||||
nbam = (part->BlocksPerUnit * sizeof(uint32_t) +
|
||||
le32_to_cpu(part->header.BAMOffset) + SECTOR_SIZE - 1) / SECTOR_SIZE;
|
||||
|
||||
offset = xfer->Offset + le32_to_cpu(part->header.BAMOffset);
|
||||
ctl = cpu_to_le32(BLOCK_CONTROL);
|
||||
|
||||
for (i = 0; i < nbam; i++, offset += sizeof(u_int32_t)) {
|
||||
for (i = 0; i < nbam; i++, offset += sizeof(uint32_t)) {
|
||||
|
||||
ret = part->mbd.mtd->write(part->mbd.mtd, offset, sizeof(u_int32_t),
|
||||
ret = part->mbd.mtd->write(part->mbd.mtd, offset, sizeof(uint32_t),
|
||||
&retlen, (u_char *)&ctl);
|
||||
|
||||
if (ret)
|
||||
@ -461,18 +461,18 @@ static int prepare_xfer(partition_t *part, int i)
|
||||
|
||||
======================================================================*/
|
||||
|
||||
static int copy_erase_unit(partition_t *part, u_int16_t srcunit,
|
||||
u_int16_t xferunit)
|
||||
static int copy_erase_unit(partition_t *part, uint16_t srcunit,
|
||||
uint16_t xferunit)
|
||||
{
|
||||
u_char buf[SECTOR_SIZE];
|
||||
struct eun_info_t *eun;
|
||||
struct xfer_info_t *xfer;
|
||||
u_int32_t src, dest, free, i;
|
||||
u_int16_t unit;
|
||||
uint32_t src, dest, free, i;
|
||||
uint16_t unit;
|
||||
int ret;
|
||||
ssize_t retlen;
|
||||
loff_t offset;
|
||||
u_int16_t srcunitswap = cpu_to_le16(srcunit);
|
||||
uint16_t srcunitswap = cpu_to_le16(srcunit);
|
||||
|
||||
eun = &part->EUNInfo[srcunit];
|
||||
xfer = &part->XferInfo[xferunit];
|
||||
@ -486,7 +486,7 @@ static int copy_erase_unit(partition_t *part, u_int16_t srcunit,
|
||||
offset = eun->Offset + le32_to_cpu(part->header.BAMOffset);
|
||||
|
||||
ret = part->mbd.mtd->read(part->mbd.mtd, offset,
|
||||
part->BlocksPerUnit * sizeof(u_int32_t),
|
||||
part->BlocksPerUnit * sizeof(uint32_t),
|
||||
&retlen, (u_char *) (part->bam_cache));
|
||||
|
||||
/* mark the cache bad, in case we get an error later */
|
||||
@ -503,7 +503,7 @@ static int copy_erase_unit(partition_t *part, u_int16_t srcunit,
|
||||
offset = xfer->Offset + 20; /* Bad! */
|
||||
unit = cpu_to_le16(0x7fff);
|
||||
|
||||
ret = part->mbd.mtd->write(part->mbd.mtd, offset, sizeof(u_int16_t),
|
||||
ret = part->mbd.mtd->write(part->mbd.mtd, offset, sizeof(uint16_t),
|
||||
&retlen, (u_char *) &unit);
|
||||
|
||||
if (ret) {
|
||||
@ -560,7 +560,7 @@ static int copy_erase_unit(partition_t *part, u_int16_t srcunit,
|
||||
|
||||
|
||||
/* All clear? Then update the LogicalEUN again */
|
||||
ret = part->mbd.mtd->write(part->mbd.mtd, xfer->Offset + 20, sizeof(u_int16_t),
|
||||
ret = part->mbd.mtd->write(part->mbd.mtd, xfer->Offset + 20, sizeof(uint16_t),
|
||||
&retlen, (u_char *)&srcunitswap);
|
||||
|
||||
if (ret) {
|
||||
@ -605,8 +605,8 @@ static int copy_erase_unit(partition_t *part, u_int16_t srcunit,
|
||||
|
||||
static int reclaim_block(partition_t *part)
|
||||
{
|
||||
u_int16_t i, eun, xfer;
|
||||
u_int32_t best;
|
||||
uint16_t i, eun, xfer;
|
||||
uint32_t best;
|
||||
int queued, ret;
|
||||
|
||||
DEBUG(0, "ftl_cs: reclaiming space...\n");
|
||||
@ -723,10 +723,10 @@ static void dump_lists(partition_t *part)
|
||||
}
|
||||
#endif
|
||||
|
||||
static u_int32_t find_free(partition_t *part)
|
||||
static uint32_t find_free(partition_t *part)
|
||||
{
|
||||
u_int16_t stop, eun;
|
||||
u_int32_t blk;
|
||||
uint16_t stop, eun;
|
||||
uint32_t blk;
|
||||
size_t retlen;
|
||||
int ret;
|
||||
|
||||
@ -749,7 +749,7 @@ static u_int32_t find_free(partition_t *part)
|
||||
|
||||
ret = part->mbd.mtd->read(part->mbd.mtd,
|
||||
part->EUNInfo[eun].Offset + le32_to_cpu(part->header.BAMOffset),
|
||||
part->BlocksPerUnit * sizeof(u_int32_t),
|
||||
part->BlocksPerUnit * sizeof(uint32_t),
|
||||
&retlen, (u_char *) (part->bam_cache));
|
||||
|
||||
if (ret) {
|
||||
@ -786,7 +786,7 @@ static u_int32_t find_free(partition_t *part)
|
||||
static int ftl_read(partition_t *part, caddr_t buffer,
|
||||
u_long sector, u_long nblocks)
|
||||
{
|
||||
u_int32_t log_addr, bsize;
|
||||
uint32_t log_addr, bsize;
|
||||
u_long i;
|
||||
int ret;
|
||||
size_t offset, retlen;
|
||||
@ -829,14 +829,14 @@ static int ftl_read(partition_t *part, caddr_t buffer,
|
||||
|
||||
======================================================================*/
|
||||
|
||||
static int set_bam_entry(partition_t *part, u_int32_t log_addr,
|
||||
u_int32_t virt_addr)
|
||||
static int set_bam_entry(partition_t *part, uint32_t log_addr,
|
||||
uint32_t virt_addr)
|
||||
{
|
||||
u_int32_t bsize, blk, le_virt_addr;
|
||||
uint32_t bsize, blk, le_virt_addr;
|
||||
#ifdef PSYCHO_DEBUG
|
||||
u_int32_t old_addr;
|
||||
uint32_t old_addr;
|
||||
#endif
|
||||
u_int16_t eun;
|
||||
uint16_t eun;
|
||||
int ret;
|
||||
size_t retlen, offset;
|
||||
|
||||
@ -845,11 +845,11 @@ static int set_bam_entry(partition_t *part, u_int32_t log_addr,
|
||||
bsize = 1 << part->header.EraseUnitSize;
|
||||
eun = log_addr / bsize;
|
||||
blk = (log_addr % bsize) / SECTOR_SIZE;
|
||||
offset = (part->EUNInfo[eun].Offset + blk * sizeof(u_int32_t) +
|
||||
offset = (part->EUNInfo[eun].Offset + blk * sizeof(uint32_t) +
|
||||
le32_to_cpu(part->header.BAMOffset));
|
||||
|
||||
#ifdef PSYCHO_DEBUG
|
||||
ret = part->mbd.mtd->read(part->mbd.mtd, offset, sizeof(u_int32_t),
|
||||
ret = part->mbd.mtd->read(part->mbd.mtd, offset, sizeof(uint32_t),
|
||||
&retlen, (u_char *)&old_addr);
|
||||
if (ret) {
|
||||
printk(KERN_WARNING"ftl: Error reading old_addr in set_bam_entry: %d\n",ret);
|
||||
@ -886,7 +886,7 @@ static int set_bam_entry(partition_t *part, u_int32_t log_addr,
|
||||
#endif
|
||||
part->bam_cache[blk] = le_virt_addr;
|
||||
}
|
||||
ret = part->mbd.mtd->write(part->mbd.mtd, offset, sizeof(u_int32_t),
|
||||
ret = part->mbd.mtd->write(part->mbd.mtd, offset, sizeof(uint32_t),
|
||||
&retlen, (u_char *)&le_virt_addr);
|
||||
|
||||
if (ret) {
|
||||
@ -900,7 +900,7 @@ static int set_bam_entry(partition_t *part, u_int32_t log_addr,
|
||||
static int ftl_write(partition_t *part, caddr_t buffer,
|
||||
u_long sector, u_long nblocks)
|
||||
{
|
||||
u_int32_t bsize, log_addr, virt_addr, old_addr, blk;
|
||||
uint32_t bsize, log_addr, virt_addr, old_addr, blk;
|
||||
u_long i;
|
||||
int ret;
|
||||
size_t retlen, offset;
|
||||
|
@ -50,7 +50,7 @@ static void inftl_add_mtd(struct mtd_blktrans_ops *tr, struct mtd_info *mtd)
|
||||
struct INFTLrecord *inftl;
|
||||
unsigned long temp;
|
||||
|
||||
if (mtd->type != MTD_NANDFLASH)
|
||||
if (mtd->type != MTD_NANDFLASH || mtd->size > UINT_MAX)
|
||||
return;
|
||||
/* OK, this is moderately ugly. But probably safe. Alternatives? */
|
||||
if (memcmp(mtd->name, "DiskOnChip", 10))
|
||||
|
@ -63,7 +63,7 @@ static int find_boot_record(struct INFTLrecord *inftl)
|
||||
* otherwise.
|
||||
*/
|
||||
inftl->EraseSize = inftl->mbd.mtd->erasesize;
|
||||
inftl->nb_blocks = inftl->mbd.mtd->size / inftl->EraseSize;
|
||||
inftl->nb_blocks = (u32)inftl->mbd.mtd->size / inftl->EraseSize;
|
||||
|
||||
inftl->MediaUnit = BLOCK_NIL;
|
||||
|
||||
@ -187,7 +187,7 @@ static int find_boot_record(struct INFTLrecord *inftl)
|
||||
mh->BlockMultiplierBits);
|
||||
inftl->EraseSize = inftl->mbd.mtd->erasesize <<
|
||||
mh->BlockMultiplierBits;
|
||||
inftl->nb_blocks = inftl->mbd.mtd->size / inftl->EraseSize;
|
||||
inftl->nb_blocks = (u32)inftl->mbd.mtd->size / inftl->EraseSize;
|
||||
block >>= mh->BlockMultiplierBits;
|
||||
}
|
||||
|
||||
|
22
drivers/mtd/lpddr/Kconfig
Normal file
22
drivers/mtd/lpddr/Kconfig
Normal file
@ -0,0 +1,22 @@
|
||||
# drivers/mtd/chips/Kconfig
|
||||
|
||||
menu "LPDDR flash memory drivers"
|
||||
depends on MTD!=n
|
||||
|
||||
config MTD_LPDDR
|
||||
tristate "Support for LPDDR flash chips"
|
||||
select MTD_QINFO_PROBE
|
||||
help
|
||||
This option enables support of LPDDR (Low power double data rate)
|
||||
flash chips. Synonymous with Mobile-DDR. It is a new standard for
|
||||
DDR memories, intended for battery-operated systems.
|
||||
|
||||
config MTD_QINFO_PROBE
|
||||
tristate "Detect flash chips by QINFO probe"
|
||||
help
|
||||
Device Information for LPDDR chips is offered through the Overlay
|
||||
Window QINFO interface, permits software to be used for entire
|
||||
families of devices. This serves similar purpose of CFI on legacy
|
||||
Flash products
|
||||
endmenu
|
||||
|
6
drivers/mtd/lpddr/Makefile
Normal file
6
drivers/mtd/lpddr/Makefile
Normal file
@ -0,0 +1,6 @@
|
||||
#
|
||||
# linux/drivers/mtd/lpddr/Makefile
|
||||
#
|
||||
|
||||
obj-$(CONFIG_MTD_QINFO_PROBE) += qinfo_probe.o
|
||||
obj-$(CONFIG_MTD_LPDDR) += lpddr_cmds.o
|
796
drivers/mtd/lpddr/lpddr_cmds.c
Normal file
796
drivers/mtd/lpddr/lpddr_cmds.c
Normal file
@ -0,0 +1,796 @@
|
||||
/*
|
||||
* LPDDR flash memory device operations. This module provides read, write,
|
||||
* erase, lock/unlock support for LPDDR flash memories
|
||||
* (C) 2008 Korolev Alexey <akorolev@infradead.org>
|
||||
* (C) 2008 Vasiliy Leonenko <vasiliy.leonenko@gmail.com>
|
||||
* Many thanks to Roman Borisov for intial enabling
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
||||
* 02110-1301, USA.
|
||||
* TODO:
|
||||
* Implement VPP management
|
||||
* Implement XIP support
|
||||
* Implement OTP support
|
||||
*/
|
||||
#include <linux/mtd/pfow.h>
|
||||
#include <linux/mtd/qinfo.h>
|
||||
|
||||
static int lpddr_read(struct mtd_info *mtd, loff_t adr, size_t len,
|
||||
size_t *retlen, u_char *buf);
|
||||
static int lpddr_write_buffers(struct mtd_info *mtd, loff_t to,
|
||||
size_t len, size_t *retlen, const u_char *buf);
|
||||
static int lpddr_writev(struct mtd_info *mtd, const struct kvec *vecs,
|
||||
unsigned long count, loff_t to, size_t *retlen);
|
||||
static int lpddr_erase(struct mtd_info *mtd, struct erase_info *instr);
|
||||
static int lpddr_lock(struct mtd_info *mtd, loff_t ofs, uint64_t len);
|
||||
static int lpddr_unlock(struct mtd_info *mtd, loff_t ofs, uint64_t len);
|
||||
static int lpddr_point(struct mtd_info *mtd, loff_t adr, size_t len,
|
||||
size_t *retlen, void **mtdbuf, resource_size_t *phys);
|
||||
static void lpddr_unpoint(struct mtd_info *mtd, loff_t adr, size_t len);
|
||||
static int get_chip(struct map_info *map, struct flchip *chip, int mode);
|
||||
static int chip_ready(struct map_info *map, struct flchip *chip, int mode);
|
||||
static void put_chip(struct map_info *map, struct flchip *chip);
|
||||
|
||||
struct mtd_info *lpddr_cmdset(struct map_info *map)
|
||||
{
|
||||
struct lpddr_private *lpddr = map->fldrv_priv;
|
||||
struct flchip_shared *shared;
|
||||
struct flchip *chip;
|
||||
struct mtd_info *mtd;
|
||||
int numchips;
|
||||
int i, j;
|
||||
|
||||
mtd = kzalloc(sizeof(*mtd), GFP_KERNEL);
|
||||
if (!mtd) {
|
||||
printk(KERN_ERR "Failed to allocate memory for MTD device\n");
|
||||
return NULL;
|
||||
}
|
||||
mtd->priv = map;
|
||||
mtd->type = MTD_NORFLASH;
|
||||
|
||||
/* Fill in the default mtd operations */
|
||||
mtd->read = lpddr_read;
|
||||
mtd->type = MTD_NORFLASH;
|
||||
mtd->flags = MTD_CAP_NORFLASH;
|
||||
mtd->flags &= ~MTD_BIT_WRITEABLE;
|
||||
mtd->erase = lpddr_erase;
|
||||
mtd->write = lpddr_write_buffers;
|
||||
mtd->writev = lpddr_writev;
|
||||
mtd->read_oob = NULL;
|
||||
mtd->write_oob = NULL;
|
||||
mtd->sync = NULL;
|
||||
mtd->lock = lpddr_lock;
|
||||
mtd->unlock = lpddr_unlock;
|
||||
mtd->suspend = NULL;
|
||||
mtd->resume = NULL;
|
||||
if (map_is_linear(map)) {
|
||||
mtd->point = lpddr_point;
|
||||
mtd->unpoint = lpddr_unpoint;
|
||||
}
|
||||
mtd->block_isbad = NULL;
|
||||
mtd->block_markbad = NULL;
|
||||
mtd->size = 1 << lpddr->qinfo->DevSizeShift;
|
||||
mtd->erasesize = 1 << lpddr->qinfo->UniformBlockSizeShift;
|
||||
mtd->writesize = 1 << lpddr->qinfo->BufSizeShift;
|
||||
|
||||
shared = kmalloc(sizeof(struct flchip_shared) * lpddr->numchips,
|
||||
GFP_KERNEL);
|
||||
if (!shared) {
|
||||
kfree(lpddr);
|
||||
kfree(mtd);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
chip = &lpddr->chips[0];
|
||||
numchips = lpddr->numchips / lpddr->qinfo->HWPartsNum;
|
||||
for (i = 0; i < numchips; i++) {
|
||||
shared[i].writing = shared[i].erasing = NULL;
|
||||
spin_lock_init(&shared[i].lock);
|
||||
for (j = 0; j < lpddr->qinfo->HWPartsNum; j++) {
|
||||
*chip = lpddr->chips[i];
|
||||
chip->start += j << lpddr->chipshift;
|
||||
chip->oldstate = chip->state = FL_READY;
|
||||
chip->priv = &shared[i];
|
||||
/* those should be reset too since
|
||||
they create memory references. */
|
||||
init_waitqueue_head(&chip->wq);
|
||||
spin_lock_init(&chip->_spinlock);
|
||||
chip->mutex = &chip->_spinlock;
|
||||
chip++;
|
||||
}
|
||||
}
|
||||
|
||||
return mtd;
|
||||
}
|
||||
EXPORT_SYMBOL(lpddr_cmdset);
|
||||
|
||||
static int wait_for_ready(struct map_info *map, struct flchip *chip,
|
||||
unsigned int chip_op_time)
|
||||
{
|
||||
unsigned int timeo, reset_timeo, sleep_time;
|
||||
unsigned int dsr;
|
||||
flstate_t chip_state = chip->state;
|
||||
int ret = 0;
|
||||
|
||||
/* set our timeout to 8 times the expected delay */
|
||||
timeo = chip_op_time * 8;
|
||||
if (!timeo)
|
||||
timeo = 500000;
|
||||
reset_timeo = timeo;
|
||||
sleep_time = chip_op_time / 2;
|
||||
|
||||
for (;;) {
|
||||
dsr = CMDVAL(map_read(map, map->pfow_base + PFOW_DSR));
|
||||
if (dsr & DSR_READY_STATUS)
|
||||
break;
|
||||
if (!timeo) {
|
||||
printk(KERN_ERR "%s: Flash timeout error state %d \n",
|
||||
map->name, chip_state);
|
||||
ret = -ETIME;
|
||||
break;
|
||||
}
|
||||
|
||||
/* OK Still waiting. Drop the lock, wait a while and retry. */
|
||||
spin_unlock(chip->mutex);
|
||||
if (sleep_time >= 1000000/HZ) {
|
||||
/*
|
||||
* Half of the normal delay still remaining
|
||||
* can be performed with a sleeping delay instead
|
||||
* of busy waiting.
|
||||
*/
|
||||
msleep(sleep_time/1000);
|
||||
timeo -= sleep_time;
|
||||
sleep_time = 1000000/HZ;
|
||||
} else {
|
||||
udelay(1);
|
||||
cond_resched();
|
||||
timeo--;
|
||||
}
|
||||
spin_lock(chip->mutex);
|
||||
|
||||
while (chip->state != chip_state) {
|
||||
/* Someone's suspended the operation: sleep */
|
||||
DECLARE_WAITQUEUE(wait, current);
|
||||
set_current_state(TASK_UNINTERRUPTIBLE);
|
||||
add_wait_queue(&chip->wq, &wait);
|
||||
spin_unlock(chip->mutex);
|
||||
schedule();
|
||||
remove_wait_queue(&chip->wq, &wait);
|
||||
spin_lock(chip->mutex);
|
||||
}
|
||||
if (chip->erase_suspended || chip->write_suspended) {
|
||||
/* Suspend has occured while sleep: reset timeout */
|
||||
timeo = reset_timeo;
|
||||
chip->erase_suspended = chip->write_suspended = 0;
|
||||
}
|
||||
}
|
||||
/* check status for errors */
|
||||
if (dsr & DSR_ERR) {
|
||||
/* Clear DSR*/
|
||||
map_write(map, CMD(~(DSR_ERR)), map->pfow_base + PFOW_DSR);
|
||||
printk(KERN_WARNING"%s: Bad status on wait: 0x%x \n",
|
||||
map->name, dsr);
|
||||
print_drs_error(dsr);
|
||||
ret = -EIO;
|
||||
}
|
||||
chip->state = FL_READY;
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int get_chip(struct map_info *map, struct flchip *chip, int mode)
|
||||
{
|
||||
int ret;
|
||||
DECLARE_WAITQUEUE(wait, current);
|
||||
|
||||
retry:
|
||||
if (chip->priv && (mode == FL_WRITING || mode == FL_ERASING)
|
||||
&& chip->state != FL_SYNCING) {
|
||||
/*
|
||||
* OK. We have possibility for contension on the write/erase
|
||||
* operations which are global to the real chip and not per
|
||||
* partition. So let's fight it over in the partition which
|
||||
* currently has authority on the operation.
|
||||
*
|
||||
* The rules are as follows:
|
||||
*
|
||||
* - any write operation must own shared->writing.
|
||||
*
|
||||
* - any erase operation must own _both_ shared->writing and
|
||||
* shared->erasing.
|
||||
*
|
||||
* - contension arbitration is handled in the owner's context.
|
||||
*
|
||||
* The 'shared' struct can be read and/or written only when
|
||||
* its lock is taken.
|
||||
*/
|
||||
struct flchip_shared *shared = chip->priv;
|
||||
struct flchip *contender;
|
||||
spin_lock(&shared->lock);
|
||||
contender = shared->writing;
|
||||
if (contender && contender != chip) {
|
||||
/*
|
||||
* The engine to perform desired operation on this
|
||||
* partition is already in use by someone else.
|
||||
* Let's fight over it in the context of the chip
|
||||
* currently using it. If it is possible to suspend,
|
||||
* that other partition will do just that, otherwise
|
||||
* it'll happily send us to sleep. In any case, when
|
||||
* get_chip returns success we're clear to go ahead.
|
||||
*/
|
||||
ret = spin_trylock(contender->mutex);
|
||||
spin_unlock(&shared->lock);
|
||||
if (!ret)
|
||||
goto retry;
|
||||
spin_unlock(chip->mutex);
|
||||
ret = chip_ready(map, contender, mode);
|
||||
spin_lock(chip->mutex);
|
||||
|
||||
if (ret == -EAGAIN) {
|
||||
spin_unlock(contender->mutex);
|
||||
goto retry;
|
||||
}
|
||||
if (ret) {
|
||||
spin_unlock(contender->mutex);
|
||||
return ret;
|
||||
}
|
||||
spin_lock(&shared->lock);
|
||||
|
||||
/* We should not own chip if it is already in FL_SYNCING
|
||||
* state. Put contender and retry. */
|
||||
if (chip->state == FL_SYNCING) {
|
||||
put_chip(map, contender);
|
||||
spin_unlock(contender->mutex);
|
||||
goto retry;
|
||||
}
|
||||
spin_unlock(contender->mutex);
|
||||
}
|
||||
|
||||
/* Check if we have suspended erase on this chip.
|
||||
Must sleep in such a case. */
|
||||
if (mode == FL_ERASING && shared->erasing
|
||||
&& shared->erasing->oldstate == FL_ERASING) {
|
||||
spin_unlock(&shared->lock);
|
||||
set_current_state(TASK_UNINTERRUPTIBLE);
|
||||
add_wait_queue(&chip->wq, &wait);
|
||||
spin_unlock(chip->mutex);
|
||||
schedule();
|
||||
remove_wait_queue(&chip->wq, &wait);
|
||||
spin_lock(chip->mutex);
|
||||
goto retry;
|
||||
}
|
||||
|
||||
/* We now own it */
|
||||
shared->writing = chip;
|
||||
if (mode == FL_ERASING)
|
||||
shared->erasing = chip;
|
||||
spin_unlock(&shared->lock);
|
||||
}
|
||||
|
||||
ret = chip_ready(map, chip, mode);
|
||||
if (ret == -EAGAIN)
|
||||
goto retry;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int chip_ready(struct map_info *map, struct flchip *chip, int mode)
|
||||
{
|
||||
struct lpddr_private *lpddr = map->fldrv_priv;
|
||||
int ret = 0;
|
||||
DECLARE_WAITQUEUE(wait, current);
|
||||
|
||||
/* Prevent setting state FL_SYNCING for chip in suspended state. */
|
||||
if (FL_SYNCING == mode && FL_READY != chip->oldstate)
|
||||
goto sleep;
|
||||
|
||||
switch (chip->state) {
|
||||
case FL_READY:
|
||||
case FL_JEDEC_QUERY:
|
||||
return 0;
|
||||
|
||||
case FL_ERASING:
|
||||
if (!lpddr->qinfo->SuspEraseSupp ||
|
||||
!(mode == FL_READY || mode == FL_POINT))
|
||||
goto sleep;
|
||||
|
||||
map_write(map, CMD(LPDDR_SUSPEND),
|
||||
map->pfow_base + PFOW_PROGRAM_ERASE_SUSPEND);
|
||||
chip->oldstate = FL_ERASING;
|
||||
chip->state = FL_ERASE_SUSPENDING;
|
||||
ret = wait_for_ready(map, chip, 0);
|
||||
if (ret) {
|
||||
/* Oops. something got wrong. */
|
||||
/* Resume and pretend we weren't here. */
|
||||
map_write(map, CMD(LPDDR_RESUME),
|
||||
map->pfow_base + PFOW_COMMAND_CODE);
|
||||
map_write(map, CMD(LPDDR_START_EXECUTION),
|
||||
map->pfow_base + PFOW_COMMAND_EXECUTE);
|
||||
chip->state = FL_ERASING;
|
||||
chip->oldstate = FL_READY;
|
||||
printk(KERN_ERR "%s: suspend operation failed."
|
||||
"State may be wrong \n", map->name);
|
||||
return -EIO;
|
||||
}
|
||||
chip->erase_suspended = 1;
|
||||
chip->state = FL_READY;
|
||||
return 0;
|
||||
/* Erase suspend */
|
||||
case FL_POINT:
|
||||
/* Only if there's no operation suspended... */
|
||||
if (mode == FL_READY && chip->oldstate == FL_READY)
|
||||
return 0;
|
||||
|
||||
default:
|
||||
sleep:
|
||||
set_current_state(TASK_UNINTERRUPTIBLE);
|
||||
add_wait_queue(&chip->wq, &wait);
|
||||
spin_unlock(chip->mutex);
|
||||
schedule();
|
||||
remove_wait_queue(&chip->wq, &wait);
|
||||
spin_lock(chip->mutex);
|
||||
return -EAGAIN;
|
||||
}
|
||||
}
|
||||
|
||||
static void put_chip(struct map_info *map, struct flchip *chip)
|
||||
{
|
||||
if (chip->priv) {
|
||||
struct flchip_shared *shared = chip->priv;
|
||||
spin_lock(&shared->lock);
|
||||
if (shared->writing == chip && chip->oldstate == FL_READY) {
|
||||
/* We own the ability to write, but we're done */
|
||||
shared->writing = shared->erasing;
|
||||
if (shared->writing && shared->writing != chip) {
|
||||
/* give back the ownership */
|
||||
struct flchip *loaner = shared->writing;
|
||||
spin_lock(loaner->mutex);
|
||||
spin_unlock(&shared->lock);
|
||||
spin_unlock(chip->mutex);
|
||||
put_chip(map, loaner);
|
||||
spin_lock(chip->mutex);
|
||||
spin_unlock(loaner->mutex);
|
||||
wake_up(&chip->wq);
|
||||
return;
|
||||
}
|
||||
shared->erasing = NULL;
|
||||
shared->writing = NULL;
|
||||
} else if (shared->erasing == chip && shared->writing != chip) {
|
||||
/*
|
||||
* We own the ability to erase without the ability
|
||||
* to write, which means the erase was suspended
|
||||
* and some other partition is currently writing.
|
||||
* Don't let the switch below mess things up since
|
||||
* we don't have ownership to resume anything.
|
||||
*/
|
||||
spin_unlock(&shared->lock);
|
||||
wake_up(&chip->wq);
|
||||
return;
|
||||
}
|
||||
spin_unlock(&shared->lock);
|
||||
}
|
||||
|
||||
switch (chip->oldstate) {
|
||||
case FL_ERASING:
|
||||
chip->state = chip->oldstate;
|
||||
map_write(map, CMD(LPDDR_RESUME),
|
||||
map->pfow_base + PFOW_COMMAND_CODE);
|
||||
map_write(map, CMD(LPDDR_START_EXECUTION),
|
||||
map->pfow_base + PFOW_COMMAND_EXECUTE);
|
||||
chip->oldstate = FL_READY;
|
||||
chip->state = FL_ERASING;
|
||||
break;
|
||||
case FL_READY:
|
||||
break;
|
||||
default:
|
||||
printk(KERN_ERR "%s: put_chip() called with oldstate %d!\n",
|
||||
map->name, chip->oldstate);
|
||||
}
|
||||
wake_up(&chip->wq);
|
||||
}
|
||||
|
||||
int do_write_buffer(struct map_info *map, struct flchip *chip,
|
||||
unsigned long adr, const struct kvec **pvec,
|
||||
unsigned long *pvec_seek, int len)
|
||||
{
|
||||
struct lpddr_private *lpddr = map->fldrv_priv;
|
||||
map_word datum;
|
||||
int ret, wbufsize, word_gap, words;
|
||||
const struct kvec *vec;
|
||||
unsigned long vec_seek;
|
||||
unsigned long prog_buf_ofs;
|
||||
|
||||
wbufsize = 1 << lpddr->qinfo->BufSizeShift;
|
||||
|
||||
spin_lock(chip->mutex);
|
||||
ret = get_chip(map, chip, FL_WRITING);
|
||||
if (ret) {
|
||||
spin_unlock(chip->mutex);
|
||||
return ret;
|
||||
}
|
||||
/* Figure out the number of words to write */
|
||||
word_gap = (-adr & (map_bankwidth(map)-1));
|
||||
words = (len - word_gap + map_bankwidth(map) - 1) / map_bankwidth(map);
|
||||
if (!word_gap) {
|
||||
words--;
|
||||
} else {
|
||||
word_gap = map_bankwidth(map) - word_gap;
|
||||
adr -= word_gap;
|
||||
datum = map_word_ff(map);
|
||||
}
|
||||
/* Write data */
|
||||
/* Get the program buffer offset from PFOW register data first*/
|
||||
prog_buf_ofs = map->pfow_base + CMDVAL(map_read(map,
|
||||
map->pfow_base + PFOW_PROGRAM_BUFFER_OFFSET));
|
||||
vec = *pvec;
|
||||
vec_seek = *pvec_seek;
|
||||
do {
|
||||
int n = map_bankwidth(map) - word_gap;
|
||||
|
||||
if (n > vec->iov_len - vec_seek)
|
||||
n = vec->iov_len - vec_seek;
|
||||
if (n > len)
|
||||
n = len;
|
||||
|
||||
if (!word_gap && (len < map_bankwidth(map)))
|
||||
datum = map_word_ff(map);
|
||||
|
||||
datum = map_word_load_partial(map, datum,
|
||||
vec->iov_base + vec_seek, word_gap, n);
|
||||
|
||||
len -= n;
|
||||
word_gap += n;
|
||||
if (!len || word_gap == map_bankwidth(map)) {
|
||||
map_write(map, datum, prog_buf_ofs);
|
||||
prog_buf_ofs += map_bankwidth(map);
|
||||
word_gap = 0;
|
||||
}
|
||||
|
||||
vec_seek += n;
|
||||
if (vec_seek == vec->iov_len) {
|
||||
vec++;
|
||||
vec_seek = 0;
|
||||
}
|
||||
} while (len);
|
||||
*pvec = vec;
|
||||
*pvec_seek = vec_seek;
|
||||
|
||||
/* GO GO GO */
|
||||
send_pfow_command(map, LPDDR_BUFF_PROGRAM, adr, wbufsize, NULL);
|
||||
chip->state = FL_WRITING;
|
||||
ret = wait_for_ready(map, chip, (1<<lpddr->qinfo->ProgBufferTime));
|
||||
if (ret) {
|
||||
printk(KERN_WARNING"%s Buffer program error: %d at %lx; \n",
|
||||
map->name, ret, adr);
|
||||
goto out;
|
||||
}
|
||||
|
||||
out: put_chip(map, chip);
|
||||
spin_unlock(chip->mutex);
|
||||
return ret;
|
||||
}
|
||||
|
||||
int do_erase_oneblock(struct mtd_info *mtd, loff_t adr)
|
||||
{
|
||||
struct map_info *map = mtd->priv;
|
||||
struct lpddr_private *lpddr = map->fldrv_priv;
|
||||
int chipnum = adr >> lpddr->chipshift;
|
||||
struct flchip *chip = &lpddr->chips[chipnum];
|
||||
int ret;
|
||||
|
||||
spin_lock(chip->mutex);
|
||||
ret = get_chip(map, chip, FL_ERASING);
|
||||
if (ret) {
|
||||
spin_unlock(chip->mutex);
|
||||
return ret;
|
||||
}
|
||||
send_pfow_command(map, LPDDR_BLOCK_ERASE, adr, 0, NULL);
|
||||
chip->state = FL_ERASING;
|
||||
ret = wait_for_ready(map, chip, (1<<lpddr->qinfo->BlockEraseTime)*1000);
|
||||
if (ret) {
|
||||
printk(KERN_WARNING"%s Erase block error %d at : %llx\n",
|
||||
map->name, ret, adr);
|
||||
goto out;
|
||||
}
|
||||
out: put_chip(map, chip);
|
||||
spin_unlock(chip->mutex);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int lpddr_read(struct mtd_info *mtd, loff_t adr, size_t len,
|
||||
size_t *retlen, u_char *buf)
|
||||
{
|
||||
struct map_info *map = mtd->priv;
|
||||
struct lpddr_private *lpddr = map->fldrv_priv;
|
||||
int chipnum = adr >> lpddr->chipshift;
|
||||
struct flchip *chip = &lpddr->chips[chipnum];
|
||||
int ret = 0;
|
||||
|
||||
spin_lock(chip->mutex);
|
||||
ret = get_chip(map, chip, FL_READY);
|
||||
if (ret) {
|
||||
spin_unlock(chip->mutex);
|
||||
return ret;
|
||||
}
|
||||
|
||||
map_copy_from(map, buf, adr, len);
|
||||
*retlen = len;
|
||||
|
||||
put_chip(map, chip);
|
||||
spin_unlock(chip->mutex);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int lpddr_point(struct mtd_info *mtd, loff_t adr, size_t len,
|
||||
size_t *retlen, void **mtdbuf, resource_size_t *phys)
|
||||
{
|
||||
struct map_info *map = mtd->priv;
|
||||
struct lpddr_private *lpddr = map->fldrv_priv;
|
||||
int chipnum = adr >> lpddr->chipshift;
|
||||
unsigned long ofs, last_end = 0;
|
||||
struct flchip *chip = &lpddr->chips[chipnum];
|
||||
int ret = 0;
|
||||
|
||||
if (!map->virt || (adr + len > mtd->size))
|
||||
return -EINVAL;
|
||||
|
||||
/* ofs: offset within the first chip that the first read should start */
|
||||
ofs = adr - (chipnum << lpddr->chipshift);
|
||||
|
||||
*mtdbuf = (void *)map->virt + chip->start + ofs;
|
||||
*retlen = 0;
|
||||
|
||||
while (len) {
|
||||
unsigned long thislen;
|
||||
|
||||
if (chipnum >= lpddr->numchips)
|
||||
break;
|
||||
|
||||
/* We cannot point across chips that are virtually disjoint */
|
||||
if (!last_end)
|
||||
last_end = chip->start;
|
||||
else if (chip->start != last_end)
|
||||
break;
|
||||
|
||||
if ((len + ofs - 1) >> lpddr->chipshift)
|
||||
thislen = (1<<lpddr->chipshift) - ofs;
|
||||
else
|
||||
thislen = len;
|
||||
/* get the chip */
|
||||
spin_lock(chip->mutex);
|
||||
ret = get_chip(map, chip, FL_POINT);
|
||||
spin_unlock(chip->mutex);
|
||||
if (ret)
|
||||
break;
|
||||
|
||||
chip->state = FL_POINT;
|
||||
chip->ref_point_counter++;
|
||||
*retlen += thislen;
|
||||
len -= thislen;
|
||||
|
||||
ofs = 0;
|
||||
last_end += 1 << lpddr->chipshift;
|
||||
chipnum++;
|
||||
chip = &lpddr->chips[chipnum];
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void lpddr_unpoint (struct mtd_info *mtd, loff_t adr, size_t len)
|
||||
{
|
||||
struct map_info *map = mtd->priv;
|
||||
struct lpddr_private *lpddr = map->fldrv_priv;
|
||||
int chipnum = adr >> lpddr->chipshift;
|
||||
unsigned long ofs;
|
||||
|
||||
/* ofs: offset within the first chip that the first read should start */
|
||||
ofs = adr - (chipnum << lpddr->chipshift);
|
||||
|
||||
while (len) {
|
||||
unsigned long thislen;
|
||||
struct flchip *chip;
|
||||
|
||||
chip = &lpddr->chips[chipnum];
|
||||
if (chipnum >= lpddr->numchips)
|
||||
break;
|
||||
|
||||
if ((len + ofs - 1) >> lpddr->chipshift)
|
||||
thislen = (1<<lpddr->chipshift) - ofs;
|
||||
else
|
||||
thislen = len;
|
||||
|
||||
spin_lock(chip->mutex);
|
||||
if (chip->state == FL_POINT) {
|
||||
chip->ref_point_counter--;
|
||||
if (chip->ref_point_counter == 0)
|
||||
chip->state = FL_READY;
|
||||
} else
|
||||
printk(KERN_WARNING "%s: Warning: unpoint called on non"
|
||||
"pointed region\n", map->name);
|
||||
|
||||
put_chip(map, chip);
|
||||
spin_unlock(chip->mutex);
|
||||
|
||||
len -= thislen;
|
||||
ofs = 0;
|
||||
chipnum++;
|
||||
}
|
||||
}
|
||||
|
||||
static int lpddr_write_buffers(struct mtd_info *mtd, loff_t to, size_t len,
|
||||
size_t *retlen, const u_char *buf)
|
||||
{
|
||||
struct kvec vec;
|
||||
|
||||
vec.iov_base = (void *) buf;
|
||||
vec.iov_len = len;
|
||||
|
||||
return lpddr_writev(mtd, &vec, 1, to, retlen);
|
||||
}
|
||||
|
||||
|
||||
static int lpddr_writev(struct mtd_info *mtd, const struct kvec *vecs,
|
||||
unsigned long count, loff_t to, size_t *retlen)
|
||||
{
|
||||
struct map_info *map = mtd->priv;
|
||||
struct lpddr_private *lpddr = map->fldrv_priv;
|
||||
int ret = 0;
|
||||
int chipnum;
|
||||
unsigned long ofs, vec_seek, i;
|
||||
int wbufsize = 1 << lpddr->qinfo->BufSizeShift;
|
||||
|
||||
size_t len = 0;
|
||||
|
||||
for (i = 0; i < count; i++)
|
||||
len += vecs[i].iov_len;
|
||||
|
||||
*retlen = 0;
|
||||
if (!len)
|
||||
return 0;
|
||||
|
||||
chipnum = to >> lpddr->chipshift;
|
||||
|
||||
ofs = to;
|
||||
vec_seek = 0;
|
||||
|
||||
do {
|
||||
/* We must not cross write block boundaries */
|
||||
int size = wbufsize - (ofs & (wbufsize-1));
|
||||
|
||||
if (size > len)
|
||||
size = len;
|
||||
|
||||
ret = do_write_buffer(map, &lpddr->chips[chipnum],
|
||||
ofs, &vecs, &vec_seek, size);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ofs += size;
|
||||
(*retlen) += size;
|
||||
len -= size;
|
||||
|
||||
/* Be nice and reschedule with the chip in a usable
|
||||
* state for other processes */
|
||||
cond_resched();
|
||||
|
||||
} while (len);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int lpddr_erase(struct mtd_info *mtd, struct erase_info *instr)
|
||||
{
|
||||
unsigned long ofs, len;
|
||||
int ret;
|
||||
struct map_info *map = mtd->priv;
|
||||
struct lpddr_private *lpddr = map->fldrv_priv;
|
||||
int size = 1 << lpddr->qinfo->UniformBlockSizeShift;
|
||||
|
||||
ofs = instr->addr;
|
||||
len = instr->len;
|
||||
|
||||
if (ofs > mtd->size || (len + ofs) > mtd->size)
|
||||
return -EINVAL;
|
||||
|
||||
while (len > 0) {
|
||||
ret = do_erase_oneblock(mtd, ofs);
|
||||
if (ret)
|
||||
return ret;
|
||||
ofs += size;
|
||||
len -= size;
|
||||
}
|
||||
instr->state = MTD_ERASE_DONE;
|
||||
mtd_erase_callback(instr);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#define DO_XXLOCK_LOCK 1
|
||||
#define DO_XXLOCK_UNLOCK 2
|
||||
int do_xxlock(struct mtd_info *mtd, loff_t adr, uint32_t len, int thunk)
|
||||
{
|
||||
int ret = 0;
|
||||
struct map_info *map = mtd->priv;
|
||||
struct lpddr_private *lpddr = map->fldrv_priv;
|
||||
int chipnum = adr >> lpddr->chipshift;
|
||||
struct flchip *chip = &lpddr->chips[chipnum];
|
||||
|
||||
spin_lock(chip->mutex);
|
||||
ret = get_chip(map, chip, FL_LOCKING);
|
||||
if (ret) {
|
||||
spin_unlock(chip->mutex);
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (thunk == DO_XXLOCK_LOCK) {
|
||||
send_pfow_command(map, LPDDR_LOCK_BLOCK, adr, adr + len, NULL);
|
||||
chip->state = FL_LOCKING;
|
||||
} else if (thunk == DO_XXLOCK_UNLOCK) {
|
||||
send_pfow_command(map, LPDDR_UNLOCK_BLOCK, adr, adr + len, NULL);
|
||||
chip->state = FL_UNLOCKING;
|
||||
} else
|
||||
BUG();
|
||||
|
||||
ret = wait_for_ready(map, chip, 1);
|
||||
if (ret) {
|
||||
printk(KERN_ERR "%s: block unlock error status %d \n",
|
||||
map->name, ret);
|
||||
goto out;
|
||||
}
|
||||
out: put_chip(map, chip);
|
||||
spin_unlock(chip->mutex);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int lpddr_lock(struct mtd_info *mtd, loff_t ofs, uint64_t len)
|
||||
{
|
||||
return do_xxlock(mtd, ofs, len, DO_XXLOCK_LOCK);
|
||||
}
|
||||
|
||||
static int lpddr_unlock(struct mtd_info *mtd, loff_t ofs, uint64_t len)
|
||||
{
|
||||
return do_xxlock(mtd, ofs, len, DO_XXLOCK_UNLOCK);
|
||||
}
|
||||
|
||||
int word_program(struct map_info *map, loff_t adr, uint32_t curval)
|
||||
{
|
||||
int ret;
|
||||
struct lpddr_private *lpddr = map->fldrv_priv;
|
||||
int chipnum = adr >> lpddr->chipshift;
|
||||
struct flchip *chip = &lpddr->chips[chipnum];
|
||||
|
||||
spin_lock(chip->mutex);
|
||||
ret = get_chip(map, chip, FL_WRITING);
|
||||
if (ret) {
|
||||
spin_unlock(chip->mutex);
|
||||
return ret;
|
||||
}
|
||||
|
||||
send_pfow_command(map, LPDDR_WORD_PROGRAM, adr, 0x00, (map_word *)&curval);
|
||||
|
||||
ret = wait_for_ready(map, chip, (1<<lpddr->qinfo->SingleWordProgTime));
|
||||
if (ret) {
|
||||
printk(KERN_WARNING"%s word_program error at: %llx; val: %x\n",
|
||||
map->name, adr, curval);
|
||||
goto out;
|
||||
}
|
||||
|
||||
out: put_chip(map, chip);
|
||||
spin_unlock(chip->mutex);
|
||||
return ret;
|
||||
}
|
||||
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_AUTHOR("Alexey Korolev <akorolev@infradead.org>");
|
||||
MODULE_DESCRIPTION("MTD driver for LPDDR flash chips");
|
255
drivers/mtd/lpddr/qinfo_probe.c
Normal file
255
drivers/mtd/lpddr/qinfo_probe.c
Normal file
@ -0,0 +1,255 @@
|
||||
/*
|
||||
* Probing flash chips with QINFO records.
|
||||
* (C) 2008 Korolev Alexey <akorolev@infradead.org>
|
||||
* (C) 2008 Vasiliy Leonenko <vasiliy.leonenko@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; 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.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
||||
* 02110-1301, USA.
|
||||
*/
|
||||
#include <linux/module.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/errno.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/interrupt.h>
|
||||
|
||||
#include <linux/mtd/xip.h>
|
||||
#include <linux/mtd/map.h>
|
||||
#include <linux/mtd/pfow.h>
|
||||
#include <linux/mtd/qinfo.h>
|
||||
|
||||
static int lpddr_chip_setup(struct map_info *map, struct lpddr_private *lpddr);
|
||||
struct mtd_info *lpddr_probe(struct map_info *map);
|
||||
static struct lpddr_private *lpddr_probe_chip(struct map_info *map);
|
||||
static int lpddr_pfow_present(struct map_info *map,
|
||||
struct lpddr_private *lpddr);
|
||||
|
||||
static struct qinfo_query_info qinfo_array[] = {
|
||||
/* General device info */
|
||||
{0, 0, "DevSizeShift", "Device size 2^n bytes"},
|
||||
{0, 3, "BufSizeShift", "Program buffer size 2^n bytes"},
|
||||
/* Erase block information */
|
||||
{1, 1, "TotalBlocksNum", "Total number of blocks"},
|
||||
{1, 2, "UniformBlockSizeShift", "Uniform block size 2^n bytes"},
|
||||
/* Partition information */
|
||||
{2, 1, "HWPartsNum", "Number of hardware partitions"},
|
||||
/* Optional features */
|
||||
{5, 1, "SuspEraseSupp", "Suspend erase supported"},
|
||||
/* Operation typical time */
|
||||
{10, 0, "SingleWordProgTime", "Single word program 2^n u-sec"},
|
||||
{10, 1, "ProgBufferTime", "Program buffer write 2^n u-sec"},
|
||||
{10, 2, "BlockEraseTime", "Block erase 2^n m-sec"},
|
||||
{10, 3, "FullChipEraseTime", "Full chip erase 2^n m-sec"},
|
||||
};
|
||||
|
||||
static long lpddr_get_qinforec_pos(struct map_info *map, char *id_str)
|
||||
{
|
||||
int qinfo_lines = sizeof(qinfo_array)/sizeof(struct qinfo_query_info);
|
||||
int i;
|
||||
int bankwidth = map_bankwidth(map) * 8;
|
||||
int major, minor;
|
||||
|
||||
for (i = 0; i < qinfo_lines; i++) {
|
||||
if (strcmp(id_str, qinfo_array[i].id_str) == 0) {
|
||||
major = qinfo_array[i].major & ((1 << bankwidth) - 1);
|
||||
minor = qinfo_array[i].minor & ((1 << bankwidth) - 1);
|
||||
return minor | (major << bankwidth);
|
||||
}
|
||||
}
|
||||
printk(KERN_ERR"%s qinfo id string is wrong! \n", map->name);
|
||||
BUG();
|
||||
return -1;
|
||||
}
|
||||
|
||||
static uint16_t lpddr_info_query(struct map_info *map, char *id_str)
|
||||
{
|
||||
unsigned int dsr, val;
|
||||
int bits_per_chip = map_bankwidth(map) * 8;
|
||||
unsigned long adr = lpddr_get_qinforec_pos(map, id_str);
|
||||
int attempts = 20;
|
||||
|
||||
/* Write a request for the PFOW record */
|
||||
map_write(map, CMD(LPDDR_INFO_QUERY),
|
||||
map->pfow_base + PFOW_COMMAND_CODE);
|
||||
map_write(map, CMD(adr & ((1 << bits_per_chip) - 1)),
|
||||
map->pfow_base + PFOW_COMMAND_ADDRESS_L);
|
||||
map_write(map, CMD(adr >> bits_per_chip),
|
||||
map->pfow_base + PFOW_COMMAND_ADDRESS_H);
|
||||
map_write(map, CMD(LPDDR_START_EXECUTION),
|
||||
map->pfow_base + PFOW_COMMAND_EXECUTE);
|
||||
|
||||
while ((attempts--) > 0) {
|
||||
dsr = CMDVAL(map_read(map, map->pfow_base + PFOW_DSR));
|
||||
if (dsr & DSR_READY_STATUS)
|
||||
break;
|
||||
udelay(10);
|
||||
}
|
||||
|
||||
val = CMDVAL(map_read(map, map->pfow_base + PFOW_COMMAND_DATA));
|
||||
return val;
|
||||
}
|
||||
|
||||
static int lpddr_pfow_present(struct map_info *map, struct lpddr_private *lpddr)
|
||||
{
|
||||
map_word pfow_val[4];
|
||||
|
||||
/* Check identification string */
|
||||
pfow_val[0] = map_read(map, map->pfow_base + PFOW_QUERY_STRING_P);
|
||||
pfow_val[1] = map_read(map, map->pfow_base + PFOW_QUERY_STRING_F);
|
||||
pfow_val[2] = map_read(map, map->pfow_base + PFOW_QUERY_STRING_O);
|
||||
pfow_val[3] = map_read(map, map->pfow_base + PFOW_QUERY_STRING_W);
|
||||
|
||||
if (!map_word_equal(map, CMD('P'), pfow_val[0]))
|
||||
goto out;
|
||||
|
||||
if (!map_word_equal(map, CMD('F'), pfow_val[1]))
|
||||
goto out;
|
||||
|
||||
if (!map_word_equal(map, CMD('O'), pfow_val[2]))
|
||||
goto out;
|
||||
|
||||
if (!map_word_equal(map, CMD('W'), pfow_val[3]))
|
||||
goto out;
|
||||
|
||||
return 1; /* "PFOW" is found */
|
||||
out:
|
||||
printk(KERN_WARNING"%s: PFOW string at 0x%lx is not found \n",
|
||||
map->name, map->pfow_base);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int lpddr_chip_setup(struct map_info *map, struct lpddr_private *lpddr)
|
||||
{
|
||||
|
||||
lpddr->qinfo = kmalloc(sizeof(struct qinfo_chip), GFP_KERNEL);
|
||||
if (!lpddr->qinfo) {
|
||||
printk(KERN_WARNING "%s: no memory for LPDDR qinfo structure\n",
|
||||
map->name);
|
||||
return 0;
|
||||
}
|
||||
memset(lpddr->qinfo, 0, sizeof(struct qinfo_chip));
|
||||
|
||||
/* Get the ManuID */
|
||||
lpddr->ManufactId = CMDVAL(map_read(map, map->pfow_base + PFOW_MANUFACTURER_ID));
|
||||
/* Get the DeviceID */
|
||||
lpddr->DevId = CMDVAL(map_read(map, map->pfow_base + PFOW_DEVICE_ID));
|
||||
/* read parameters from chip qinfo table */
|
||||
lpddr->qinfo->DevSizeShift = lpddr_info_query(map, "DevSizeShift");
|
||||
lpddr->qinfo->TotalBlocksNum = lpddr_info_query(map, "TotalBlocksNum");
|
||||
lpddr->qinfo->BufSizeShift = lpddr_info_query(map, "BufSizeShift");
|
||||
lpddr->qinfo->HWPartsNum = lpddr_info_query(map, "HWPartsNum");
|
||||
lpddr->qinfo->UniformBlockSizeShift =
|
||||
lpddr_info_query(map, "UniformBlockSizeShift");
|
||||
lpddr->qinfo->SuspEraseSupp = lpddr_info_query(map, "SuspEraseSupp");
|
||||
lpddr->qinfo->SingleWordProgTime =
|
||||
lpddr_info_query(map, "SingleWordProgTime");
|
||||
lpddr->qinfo->ProgBufferTime = lpddr_info_query(map, "ProgBufferTime");
|
||||
lpddr->qinfo->BlockEraseTime = lpddr_info_query(map, "BlockEraseTime");
|
||||
return 1;
|
||||
}
|
||||
static struct lpddr_private *lpddr_probe_chip(struct map_info *map)
|
||||
{
|
||||
struct lpddr_private lpddr;
|
||||
struct lpddr_private *retlpddr;
|
||||
int numvirtchips;
|
||||
|
||||
|
||||
if ((map->pfow_base + 0x1000) >= map->size) {
|
||||
printk(KERN_NOTICE"%s Probe at base (0x%08lx) past the end of"
|
||||
"the map(0x%08lx)\n", map->name,
|
||||
(unsigned long)map->pfow_base, map->size - 1);
|
||||
return NULL;
|
||||
}
|
||||
memset(&lpddr, 0, sizeof(struct lpddr_private));
|
||||
if (!lpddr_pfow_present(map, &lpddr))
|
||||
return NULL;
|
||||
|
||||
if (!lpddr_chip_setup(map, &lpddr))
|
||||
return NULL;
|
||||
|
||||
/* Ok so we found a chip */
|
||||
lpddr.chipshift = lpddr.qinfo->DevSizeShift;
|
||||
lpddr.numchips = 1;
|
||||
|
||||
numvirtchips = lpddr.numchips * lpddr.qinfo->HWPartsNum;
|
||||
retlpddr = kmalloc(sizeof(struct lpddr_private) +
|
||||
numvirtchips * sizeof(struct flchip), GFP_KERNEL);
|
||||
if (!retlpddr)
|
||||
return NULL;
|
||||
|
||||
memset(retlpddr, 0, sizeof(struct lpddr_private) +
|
||||
numvirtchips * sizeof(struct flchip));
|
||||
memcpy(retlpddr, &lpddr, sizeof(struct lpddr_private));
|
||||
|
||||
retlpddr->numchips = numvirtchips;
|
||||
retlpddr->chipshift = retlpddr->qinfo->DevSizeShift -
|
||||
__ffs(retlpddr->qinfo->HWPartsNum);
|
||||
|
||||
return retlpddr;
|
||||
}
|
||||
|
||||
struct mtd_info *lpddr_probe(struct map_info *map)
|
||||
{
|
||||
struct mtd_info *mtd = NULL;
|
||||
struct lpddr_private *lpddr;
|
||||
|
||||
/* First probe the map to see if we havecan open PFOW here */
|
||||
lpddr = lpddr_probe_chip(map);
|
||||
if (!lpddr)
|
||||
return NULL;
|
||||
|
||||
map->fldrv_priv = lpddr;
|
||||
mtd = lpddr_cmdset(map);
|
||||
if (mtd) {
|
||||
if (mtd->size > map->size) {
|
||||
printk(KERN_WARNING "Reducing visibility of %ldKiB chip"
|
||||
"to %ldKiB\n", (unsigned long)mtd->size >> 10,
|
||||
(unsigned long)map->size >> 10);
|
||||
mtd->size = map->size;
|
||||
}
|
||||
return mtd;
|
||||
}
|
||||
|
||||
kfree(lpddr->qinfo);
|
||||
kfree(lpddr);
|
||||
map->fldrv_priv = NULL;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static struct mtd_chip_driver lpddr_chipdrv = {
|
||||
.probe = lpddr_probe,
|
||||
.name = "qinfo_probe",
|
||||
.module = THIS_MODULE
|
||||
};
|
||||
|
||||
static int __init lpddr_probe_init(void)
|
||||
{
|
||||
register_mtd_chip_driver(&lpddr_chipdrv);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void __exit lpddr_probe_exit(void)
|
||||
{
|
||||
unregister_mtd_chip_driver(&lpddr_chipdrv);
|
||||
}
|
||||
|
||||
module_init(lpddr_probe_init);
|
||||
module_exit(lpddr_probe_exit);
|
||||
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_AUTHOR("Vasiliy Leonenko <vasiliy.leonenko@gmail.com>");
|
||||
MODULE_DESCRIPTION("Driver to probe qinfo flash chips");
|
||||
|
@ -10,8 +10,8 @@ config MTD_COMPLEX_MAPPINGS
|
||||
paged mappings of flash chips.
|
||||
|
||||
config MTD_PHYSMAP
|
||||
tristate "CFI Flash device in physical memory map"
|
||||
depends on MTD_CFI || MTD_JEDECPROBE || MTD_ROM
|
||||
tristate "Flash device in physical memory map"
|
||||
depends on MTD_CFI || MTD_JEDECPROBE || MTD_ROM || MTD_LPDDR
|
||||
help
|
||||
This provides a 'mapping' driver which allows the NOR Flash and
|
||||
ROM driver code to communicate with chips which are mapped
|
||||
@ -23,9 +23,20 @@ config MTD_PHYSMAP
|
||||
To compile this driver as a module, choose M here: the
|
||||
module will be called physmap.
|
||||
|
||||
config MTD_PHYSMAP_COMPAT
|
||||
bool "Physmap compat support"
|
||||
depends on MTD_PHYSMAP
|
||||
default n
|
||||
help
|
||||
Setup a simple mapping via the Kconfig options. Normally the
|
||||
physmap configuration options are done via your board's
|
||||
resource file.
|
||||
|
||||
If unsure, say N here.
|
||||
|
||||
config MTD_PHYSMAP_START
|
||||
hex "Physical start address of flash mapping"
|
||||
depends on MTD_PHYSMAP
|
||||
depends on MTD_PHYSMAP_COMPAT
|
||||
default "0x8000000"
|
||||
help
|
||||
This is the physical memory location at which the flash chips
|
||||
@ -37,7 +48,7 @@ config MTD_PHYSMAP_START
|
||||
|
||||
config MTD_PHYSMAP_LEN
|
||||
hex "Physical length of flash mapping"
|
||||
depends on MTD_PHYSMAP
|
||||
depends on MTD_PHYSMAP_COMPAT
|
||||
default "0"
|
||||
help
|
||||
This is the total length of the mapping of the flash chips on
|
||||
@ -51,7 +62,7 @@ config MTD_PHYSMAP_LEN
|
||||
|
||||
config MTD_PHYSMAP_BANKWIDTH
|
||||
int "Bank width in octets"
|
||||
depends on MTD_PHYSMAP
|
||||
depends on MTD_PHYSMAP_COMPAT
|
||||
default "2"
|
||||
help
|
||||
This is the total width of the data bus of the flash devices
|
||||
|
@ -111,7 +111,7 @@ static struct mtd_partition alchemy_partitions[] = {
|
||||
|
||||
static struct mtd_info *mymtd;
|
||||
|
||||
int __init alchemy_mtd_init(void)
|
||||
static int __init alchemy_mtd_init(void)
|
||||
{
|
||||
struct mtd_partition *parts;
|
||||
int nb_parts = 0;
|
||||
|
@ -232,8 +232,8 @@ static int __devinit amd76xrom_init_one (struct pci_dev *pdev,
|
||||
/* Trim the size if we are larger than the map */
|
||||
if (map->mtd->size > map->map.size) {
|
||||
printk(KERN_WARNING MOD_NAME
|
||||
" rom(%u) larger than window(%lu). fixing...\n",
|
||||
map->mtd->size, map->map.size);
|
||||
" rom(%llu) larger than window(%lu). fixing...\n",
|
||||
(unsigned long long)map->mtd->size, map->map.size);
|
||||
map->mtd->size = map->map.size;
|
||||
}
|
||||
if (window->rsrc.parent) {
|
||||
|
@ -88,7 +88,7 @@ struct mtd_partition flagadm_parts[] = {
|
||||
|
||||
static struct mtd_info *mymtd;
|
||||
|
||||
int __init init_flagadm(void)
|
||||
static int __init init_flagadm(void)
|
||||
{
|
||||
printk(KERN_NOTICE "FlagaDM flash device: %x at %x\n",
|
||||
FLASH_SIZE, FLASH_PHYS_ADDR);
|
||||
|
@ -263,8 +263,8 @@ static int __devinit ck804xrom_init_one (struct pci_dev *pdev,
|
||||
/* Trim the size if we are larger than the map */
|
||||
if (map->mtd->size > map->map.size) {
|
||||
printk(KERN_WARNING MOD_NAME
|
||||
" rom(%u) larger than window(%lu). fixing...\n",
|
||||
map->mtd->size, map->map.size);
|
||||
" rom(%llu) larger than window(%lu). fixing...\n",
|
||||
(unsigned long long)map->mtd->size, map->map.size);
|
||||
map->mtd->size = map->map.size;
|
||||
}
|
||||
if (window->rsrc.parent) {
|
||||
|
@ -69,7 +69,7 @@ struct map_info dbox2_flash_map = {
|
||||
.phys = WINDOW_ADDR,
|
||||
};
|
||||
|
||||
int __init init_dbox2_flash(void)
|
||||
static int __init init_dbox2_flash(void)
|
||||
{
|
||||
printk(KERN_NOTICE "D-Box 2 flash driver (size->0x%X mem->0x%X)\n", WINDOW_SIZE, WINDOW_ADDR);
|
||||
dbox2_flash_map.virt = ioremap(WINDOW_ADDR, WINDOW_SIZE);
|
||||
|
@ -71,7 +71,7 @@ static const char *probes[] = { "RedBoot", "cmdlinepart", NULL };
|
||||
static int mtd_parts_nb = 0;
|
||||
static struct mtd_partition *mtd_parts = 0;
|
||||
|
||||
int __init init_edb7312nor(void)
|
||||
static int __init init_edb7312nor(void)
|
||||
{
|
||||
static const char *rom_probe_types[] = PROBETYPES;
|
||||
const char **type;
|
||||
|
@ -324,8 +324,8 @@ static int __devinit esb2rom_init_one(struct pci_dev *pdev,
|
||||
/* Trim the size if we are larger than the map */
|
||||
if (map->mtd->size > map->map.size) {
|
||||
printk(KERN_WARNING MOD_NAME
|
||||
" rom(%u) larger than window(%lu). fixing...\n",
|
||||
map->mtd->size, map->map.size);
|
||||
" rom(%llu) larger than window(%lu). fixing...\n",
|
||||
(unsigned long long)map->mtd->size, map->map.size);
|
||||
map->mtd->size = map->map.size;
|
||||
}
|
||||
if (window->rsrc.parent) {
|
||||
|
@ -181,7 +181,7 @@ __setup("MTD_Partition=", MTD_New_Partition);
|
||||
/* Backwards-spelling-compatibility */
|
||||
__setup("MTD_Partion=", MTD_New_Partition);
|
||||
|
||||
int __init init_fortunet(void)
|
||||
static int __init init_fortunet(void)
|
||||
{
|
||||
int ix,iy;
|
||||
for(iy=ix=0;ix<MAX_NUM_REGIONS;ix++)
|
||||
|
@ -65,7 +65,7 @@ static const char *probes[] = { "cmdlinepart", NULL };
|
||||
/*
|
||||
* Initialize FLASH support
|
||||
*/
|
||||
int __init h720x_mtd_init(void)
|
||||
static int __init h720x_mtd_init(void)
|
||||
{
|
||||
|
||||
char *part_type = NULL;
|
||||
|
@ -258,8 +258,8 @@ static int __devinit ichxrom_init_one (struct pci_dev *pdev,
|
||||
/* Trim the size if we are larger than the map */
|
||||
if (map->mtd->size > map->map.size) {
|
||||
printk(KERN_WARNING MOD_NAME
|
||||
" rom(%u) larger than window(%lu). fixing...\n",
|
||||
map->mtd->size, map->map.size);
|
||||
" rom(%llu) larger than window(%lu). fixing...\n",
|
||||
(unsigned long long)map->mtd->size, map->map.size);
|
||||
map->mtd->size = map->map.size;
|
||||
}
|
||||
if (window->rsrc.parent) {
|
||||
|
@ -70,7 +70,7 @@ static struct mtd_partition *mtd_parts[NUM_FLASHBANKS];
|
||||
|
||||
static const char *probes[] = { "cmdlinepart", NULL };
|
||||
|
||||
int __init init_impa7(void)
|
||||
static int __init init_impa7(void)
|
||||
{
|
||||
static const char *rom_probe_types[] = PROBETYPES;
|
||||
const char **type;
|
||||
|
@ -202,7 +202,7 @@ static const char *part_probes[] = { "cmdlinepart", "RedBoot", NULL };
|
||||
|
||||
static int __init h1900_special_case(void);
|
||||
|
||||
int __init ipaq_mtd_init(void)
|
||||
static int __init ipaq_mtd_init(void)
|
||||
{
|
||||
struct mtd_partition *parts = NULL;
|
||||
int nb_parts = 0;
|
||||
|
@ -55,7 +55,7 @@ struct map_info mbx_map = {
|
||||
.bankwidth = 4,
|
||||
};
|
||||
|
||||
int __init init_mbx(void)
|
||||
static int __init init_mbx(void)
|
||||
{
|
||||
printk(KERN_NOTICE "Motorola MBX flash device: 0x%x at 0x%x\n", WINDOW_SIZE*4, WINDOW_ADDR);
|
||||
mbx_map.virt = ioremap(WINDOW_ADDR, WINDOW_SIZE * 4);
|
||||
|
@ -226,7 +226,7 @@ static int __init nettel_init(void)
|
||||
|
||||
if ((amd_mtd = do_map_probe("jedec_probe", &nettel_amd_map))) {
|
||||
printk(KERN_NOTICE "SNAPGEAR: AMD flash device size = %dK\n",
|
||||
amd_mtd->size>>10);
|
||||
(int)(amd_mtd->size>>10));
|
||||
|
||||
amd_mtd->owner = THIS_MODULE;
|
||||
|
||||
@ -357,13 +357,12 @@ static int __init nettel_init(void)
|
||||
*intel1par = 0;
|
||||
}
|
||||
|
||||
printk(KERN_NOTICE "SNAPGEAR: Intel flash device size = %dK\n",
|
||||
(intel_mtd->size >> 10));
|
||||
printk(KERN_NOTICE "SNAPGEAR: Intel flash device size = %lldKiB\n",
|
||||
(unsigned long long)(intel_mtd->size >> 10));
|
||||
|
||||
intel_mtd->owner = THIS_MODULE;
|
||||
|
||||
num_intel_partitions = sizeof(nettel_intel_partitions) /
|
||||
sizeof(nettel_intel_partitions[0]);
|
||||
num_intel_partitions = ARRAY_SIZE(nettel_intel_partitions);
|
||||
|
||||
if (intelboot) {
|
||||
/*
|
||||
|
@ -184,7 +184,7 @@ void cleanup_oct5066(void)
|
||||
release_region(PAGE_IO, 1);
|
||||
}
|
||||
|
||||
int __init init_oct5066(void)
|
||||
static int __init init_oct5066(void)
|
||||
{
|
||||
int i;
|
||||
int ret = 0;
|
||||
|
@ -29,7 +29,6 @@ struct physmap_flash_info {
|
||||
struct map_info map[MAX_RESOURCES];
|
||||
#ifdef CONFIG_MTD_PARTITIONS
|
||||
int nr_parts;
|
||||
struct mtd_partition *parts;
|
||||
#endif
|
||||
};
|
||||
|
||||
@ -56,14 +55,10 @@ static int physmap_flash_remove(struct platform_device *dev)
|
||||
for (i = 0; i < MAX_RESOURCES; i++) {
|
||||
if (info->mtd[i] != NULL) {
|
||||
#ifdef CONFIG_MTD_PARTITIONS
|
||||
if (info->nr_parts) {
|
||||
if (info->nr_parts || physmap_data->nr_parts)
|
||||
del_mtd_partitions(info->mtd[i]);
|
||||
kfree(info->parts);
|
||||
} else if (physmap_data->nr_parts) {
|
||||
del_mtd_partitions(info->mtd[i]);
|
||||
} else {
|
||||
else
|
||||
del_mtd_device(info->mtd[i]);
|
||||
}
|
||||
#else
|
||||
del_mtd_device(info->mtd[i]);
|
||||
#endif
|
||||
@ -73,7 +68,12 @@ static int physmap_flash_remove(struct platform_device *dev)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const char *rom_probe_types[] = { "cfi_probe", "jedec_probe", "map_rom", NULL };
|
||||
static const char *rom_probe_types[] = {
|
||||
"cfi_probe",
|
||||
"jedec_probe",
|
||||
"qinfo_probe",
|
||||
"map_rom",
|
||||
NULL };
|
||||
#ifdef CONFIG_MTD_PARTITIONS
|
||||
static const char *part_probe_types[] = { "cmdlinepart", "RedBoot", NULL };
|
||||
#endif
|
||||
@ -86,6 +86,9 @@ static int physmap_flash_probe(struct platform_device *dev)
|
||||
int err = 0;
|
||||
int i;
|
||||
int devices_found = 0;
|
||||
#ifdef CONFIG_MTD_PARTITIONS
|
||||
struct mtd_partition *parts;
|
||||
#endif
|
||||
|
||||
physmap_data = dev->dev.platform_data;
|
||||
if (physmap_data == NULL)
|
||||
@ -119,6 +122,7 @@ static int physmap_flash_probe(struct platform_device *dev)
|
||||
info->map[i].size = dev->resource[i].end - dev->resource[i].start + 1;
|
||||
info->map[i].bankwidth = physmap_data->width;
|
||||
info->map[i].set_vpp = physmap_data->set_vpp;
|
||||
info->map[i].pfow_base = physmap_data->pfow_base;
|
||||
|
||||
info->map[i].virt = devm_ioremap(&dev->dev, info->map[i].phys,
|
||||
info->map[i].size);
|
||||
@ -163,9 +167,10 @@ static int physmap_flash_probe(struct platform_device *dev)
|
||||
goto err_out;
|
||||
|
||||
#ifdef CONFIG_MTD_PARTITIONS
|
||||
err = parse_mtd_partitions(info->cmtd, part_probe_types, &info->parts, 0);
|
||||
err = parse_mtd_partitions(info->cmtd, part_probe_types, &parts, 0);
|
||||
if (err > 0) {
|
||||
add_mtd_partitions(info->cmtd, info->parts, err);
|
||||
add_mtd_partitions(info->cmtd, parts, err);
|
||||
kfree(parts);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -251,14 +256,7 @@ static struct platform_driver physmap_flash_driver = {
|
||||
};
|
||||
|
||||
|
||||
#ifdef CONFIG_MTD_PHYSMAP_LEN
|
||||
#if CONFIG_MTD_PHYSMAP_LEN != 0
|
||||
#warning using PHYSMAP compat code
|
||||
#define PHYSMAP_COMPAT
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef PHYSMAP_COMPAT
|
||||
#ifdef CONFIG_MTD_PHYSMAP_COMPAT
|
||||
static struct physmap_flash_data physmap_flash_data = {
|
||||
.width = CONFIG_MTD_PHYSMAP_BANKWIDTH,
|
||||
};
|
||||
@ -302,7 +300,7 @@ static int __init physmap_init(void)
|
||||
int err;
|
||||
|
||||
err = platform_driver_register(&physmap_flash_driver);
|
||||
#ifdef PHYSMAP_COMPAT
|
||||
#ifdef CONFIG_MTD_PHYSMAP_COMPAT
|
||||
if (err == 0)
|
||||
platform_device_register(&physmap_flash);
|
||||
#endif
|
||||
@ -312,7 +310,7 @@ static int __init physmap_init(void)
|
||||
|
||||
static void __exit physmap_exit(void)
|
||||
{
|
||||
#ifdef PHYSMAP_COMPAT
|
||||
#ifdef CONFIG_MTD_PHYSMAP_COMPAT
|
||||
platform_device_unregister(&physmap_flash);
|
||||
#endif
|
||||
platform_driver_unregister(&physmap_flash_driver);
|
||||
@ -326,8 +324,7 @@ MODULE_AUTHOR("David Woodhouse <dwmw2@infradead.org>");
|
||||
MODULE_DESCRIPTION("Generic configurable MTD map driver");
|
||||
|
||||
/* legacy platform drivers can't hotplug or coldplg */
|
||||
#ifndef PHYSMAP_COMPAT
|
||||
#ifndef CONFIG_MTD_PHYSMAP_COMPAT
|
||||
/* work with hotplug and coldplug */
|
||||
MODULE_ALIAS("platform:physmap-flash");
|
||||
#endif
|
||||
|
||||
|
@ -48,7 +48,7 @@ static int fcnt;
|
||||
|
||||
#define DEBUG_MARKER printk(KERN_NOTICE "%s[%d]\n", __func__, __LINE__)
|
||||
|
||||
int __init init_msp_flash(void)
|
||||
static int __init init_msp_flash(void)
|
||||
{
|
||||
int i, j;
|
||||
int offset, coff;
|
||||
|
@ -122,7 +122,7 @@ struct map_info redwood_flash_map = {
|
||||
|
||||
static struct mtd_info *redwood_mtd;
|
||||
|
||||
int __init init_redwood_flash(void)
|
||||
static int __init init_redwood_flash(void)
|
||||
{
|
||||
int err;
|
||||
|
||||
|
@ -23,7 +23,7 @@ static struct map_info rpxlite_map = {
|
||||
.phys = WINDOW_ADDR,
|
||||
};
|
||||
|
||||
int __init init_rpxlite(void)
|
||||
static int __init init_rpxlite(void)
|
||||
{
|
||||
printk(KERN_NOTICE "RPX Lite or CLLF flash device: %x at %x\n", WINDOW_SIZE*4, WINDOW_ADDR);
|
||||
rpxlite_map.virt = ioremap(WINDOW_ADDR, WINDOW_SIZE * 4);
|
||||
|
@ -136,7 +136,7 @@ static struct mtd_part_def sbc8240_part_banks[NUM_FLASH_BANKS];
|
||||
#endif /* CONFIG_MTD_PARTITIONS */
|
||||
|
||||
|
||||
int __init init_sbc8240_mtd (void)
|
||||
static int __init init_sbc8240_mtd (void)
|
||||
{
|
||||
static struct _cjs {
|
||||
u_long addr;
|
||||
|
@ -118,7 +118,8 @@ scb2_fixup_mtd(struct mtd_info *mtd)
|
||||
struct mtd_erase_region_info *region = &mtd->eraseregions[i];
|
||||
|
||||
if (region->numblocks * region->erasesize > mtd->size) {
|
||||
region->numblocks = (mtd->size / region->erasesize);
|
||||
region->numblocks = ((unsigned long)mtd->size /
|
||||
region->erasesize);
|
||||
done = 1;
|
||||
} else {
|
||||
region->numblocks = 0;
|
||||
@ -187,8 +188,9 @@ scb2_flash_probe(struct pci_dev *dev, const struct pci_device_id *ent)
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
printk(KERN_NOTICE MODNAME ": chip size 0x%x at offset 0x%x\n",
|
||||
scb2_mtd->size, SCB2_WINDOW - scb2_mtd->size);
|
||||
printk(KERN_NOTICE MODNAME ": chip size 0x%llx at offset 0x%llx\n",
|
||||
(unsigned long long)scb2_mtd->size,
|
||||
(unsigned long long)(SCB2_WINDOW - scb2_mtd->size));
|
||||
|
||||
add_mtd_device(scb2_mtd);
|
||||
|
||||
|
@ -47,7 +47,7 @@ static struct mtd_partition sharpsl_partitions[1] = {
|
||||
}
|
||||
};
|
||||
|
||||
int __init init_sharpsl(void)
|
||||
static int __init init_sharpsl(void)
|
||||
{
|
||||
struct mtd_partition *parts;
|
||||
int nb_parts = 0;
|
||||
|
@ -109,7 +109,7 @@ static struct mtd_partition tqm8xxl_fs_partitions[] = {
|
||||
};
|
||||
#endif
|
||||
|
||||
int __init init_tqm_mtd(void)
|
||||
static int __init init_tqm_mtd(void)
|
||||
{
|
||||
int idx = 0, ret = 0;
|
||||
unsigned long flash_addr, flash_size, mtd_size = 0;
|
||||
|
@ -51,7 +51,7 @@ int uclinux_point(struct mtd_info *mtd, loff_t from, size_t len,
|
||||
|
||||
/****************************************************************************/
|
||||
|
||||
int __init uclinux_mtd_init(void)
|
||||
static int __init uclinux_mtd_init(void)
|
||||
{
|
||||
struct mtd_info *mtd;
|
||||
struct map_info *mapp;
|
||||
@ -94,7 +94,7 @@ int __init uclinux_mtd_init(void)
|
||||
|
||||
/****************************************************************************/
|
||||
|
||||
void __exit uclinux_mtd_cleanup(void)
|
||||
static void __exit uclinux_mtd_cleanup(void)
|
||||
{
|
||||
if (uclinux_ram_mtdinfo) {
|
||||
del_mtd_partitions(uclinux_ram_mtdinfo);
|
||||
|
@ -146,7 +146,7 @@ static void __exit cleanup_vmax301(void)
|
||||
iounmap((void *)vmax_map[0].map_priv_1 - WINDOW_START);
|
||||
}
|
||||
|
||||
int __init init_vmax301(void)
|
||||
static int __init init_vmax301(void)
|
||||
{
|
||||
int i;
|
||||
unsigned long iomapadr;
|
||||
|
@ -74,7 +74,7 @@ do { \
|
||||
} \
|
||||
} while (0);
|
||||
|
||||
int __init init_sbc82xx_flash(void)
|
||||
static int __init init_sbc82xx_flash(void)
|
||||
{
|
||||
volatile memctl_cpm2_t *mc = &cpm2_immr->im_memctl;
|
||||
int bigflash;
|
||||
|
@ -450,16 +450,20 @@ static int mtd_ioctl(struct inode *inode, struct file *file,
|
||||
if (!erase)
|
||||
ret = -ENOMEM;
|
||||
else {
|
||||
struct erase_info_user einfo;
|
||||
|
||||
wait_queue_head_t waitq;
|
||||
DECLARE_WAITQUEUE(wait, current);
|
||||
|
||||
init_waitqueue_head(&waitq);
|
||||
|
||||
if (copy_from_user(&erase->addr, argp,
|
||||
if (copy_from_user(&einfo, argp,
|
||||
sizeof(struct erase_info_user))) {
|
||||
kfree(erase);
|
||||
return -EFAULT;
|
||||
}
|
||||
erase->addr = einfo.start;
|
||||
erase->len = einfo.length;
|
||||
erase->mtd = mtd;
|
||||
erase->callback = mtdchar_erase_callback;
|
||||
erase->priv = (unsigned long)&waitq;
|
||||
|
@ -197,7 +197,7 @@ concat_writev(struct mtd_info *mtd, const struct kvec *vecs,
|
||||
continue;
|
||||
}
|
||||
|
||||
size = min(total_len, (size_t)(subdev->size - to));
|
||||
size = min_t(uint64_t, total_len, subdev->size - to);
|
||||
wsize = size; /* store for future use */
|
||||
|
||||
entry_high = entry_low;
|
||||
@ -385,7 +385,7 @@ static int concat_erase(struct mtd_info *mtd, struct erase_info *instr)
|
||||
struct mtd_concat *concat = CONCAT(mtd);
|
||||
struct mtd_info *subdev;
|
||||
int i, err;
|
||||
u_int32_t length, offset = 0;
|
||||
uint64_t length, offset = 0;
|
||||
struct erase_info *erase;
|
||||
|
||||
if (!(mtd->flags & MTD_WRITEABLE))
|
||||
@ -518,7 +518,7 @@ static int concat_erase(struct mtd_info *mtd, struct erase_info *instr)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int concat_lock(struct mtd_info *mtd, loff_t ofs, size_t len)
|
||||
static int concat_lock(struct mtd_info *mtd, loff_t ofs, uint64_t len)
|
||||
{
|
||||
struct mtd_concat *concat = CONCAT(mtd);
|
||||
int i, err = -EINVAL;
|
||||
@ -528,7 +528,7 @@ static int concat_lock(struct mtd_info *mtd, loff_t ofs, size_t len)
|
||||
|
||||
for (i = 0; i < concat->num_subdev; i++) {
|
||||
struct mtd_info *subdev = concat->subdev[i];
|
||||
size_t size;
|
||||
uint64_t size;
|
||||
|
||||
if (ofs >= subdev->size) {
|
||||
size = 0;
|
||||
@ -556,7 +556,7 @@ static int concat_lock(struct mtd_info *mtd, loff_t ofs, size_t len)
|
||||
return err;
|
||||
}
|
||||
|
||||
static int concat_unlock(struct mtd_info *mtd, loff_t ofs, size_t len)
|
||||
static int concat_unlock(struct mtd_info *mtd, loff_t ofs, uint64_t len)
|
||||
{
|
||||
struct mtd_concat *concat = CONCAT(mtd);
|
||||
int i, err = 0;
|
||||
@ -566,7 +566,7 @@ static int concat_unlock(struct mtd_info *mtd, loff_t ofs, size_t len)
|
||||
|
||||
for (i = 0; i < concat->num_subdev; i++) {
|
||||
struct mtd_info *subdev = concat->subdev[i];
|
||||
size_t size;
|
||||
uint64_t size;
|
||||
|
||||
if (ofs >= subdev->size) {
|
||||
size = 0;
|
||||
@ -696,7 +696,7 @@ struct mtd_info *mtd_concat_create(struct mtd_info *subdev[], /* subdevices to c
|
||||
int i;
|
||||
size_t size;
|
||||
struct mtd_concat *concat;
|
||||
u_int32_t max_erasesize, curr_erasesize;
|
||||
uint32_t max_erasesize, curr_erasesize;
|
||||
int num_erase_region;
|
||||
|
||||
printk(KERN_NOTICE "Concatenating MTD devices:\n");
|
||||
@ -842,12 +842,14 @@ struct mtd_info *mtd_concat_create(struct mtd_info *subdev[], /* subdevices to c
|
||||
concat->mtd.erasesize = curr_erasesize;
|
||||
concat->mtd.numeraseregions = 0;
|
||||
} else {
|
||||
uint64_t tmp64;
|
||||
|
||||
/*
|
||||
* erase block size varies across the subdevices: allocate
|
||||
* space to store the data describing the variable erase regions
|
||||
*/
|
||||
struct mtd_erase_region_info *erase_region_p;
|
||||
u_int32_t begin, position;
|
||||
uint64_t begin, position;
|
||||
|
||||
concat->mtd.erasesize = max_erasesize;
|
||||
concat->mtd.numeraseregions = num_erase_region;
|
||||
@ -879,8 +881,9 @@ struct mtd_info *mtd_concat_create(struct mtd_info *subdev[], /* subdevices to c
|
||||
erase_region_p->offset = begin;
|
||||
erase_region_p->erasesize =
|
||||
curr_erasesize;
|
||||
erase_region_p->numblocks =
|
||||
(position - begin) / curr_erasesize;
|
||||
tmp64 = position - begin;
|
||||
do_div(tmp64, curr_erasesize);
|
||||
erase_region_p->numblocks = tmp64;
|
||||
begin = position;
|
||||
|
||||
curr_erasesize = subdev[i]->erasesize;
|
||||
@ -897,9 +900,9 @@ struct mtd_info *mtd_concat_create(struct mtd_info *subdev[], /* subdevices to c
|
||||
erase_region_p->offset = begin;
|
||||
erase_region_p->erasesize =
|
||||
curr_erasesize;
|
||||
erase_region_p->numblocks =
|
||||
(position -
|
||||
begin) / curr_erasesize;
|
||||
tmp64 = position - begin;
|
||||
do_div(tmp64, curr_erasesize);
|
||||
erase_region_p->numblocks = tmp64;
|
||||
begin = position;
|
||||
|
||||
curr_erasesize =
|
||||
@ -909,14 +912,16 @@ struct mtd_info *mtd_concat_create(struct mtd_info *subdev[], /* subdevices to c
|
||||
}
|
||||
position +=
|
||||
subdev[i]->eraseregions[j].
|
||||
numblocks * curr_erasesize;
|
||||
numblocks * (uint64_t)curr_erasesize;
|
||||
}
|
||||
}
|
||||
}
|
||||
/* Now write the final entry */
|
||||
erase_region_p->offset = begin;
|
||||
erase_region_p->erasesize = curr_erasesize;
|
||||
erase_region_p->numblocks = (position - begin) / curr_erasesize;
|
||||
tmp64 = position - begin;
|
||||
do_div(tmp64, curr_erasesize);
|
||||
erase_region_p->numblocks = tmp64;
|
||||
}
|
||||
|
||||
return &concat->mtd;
|
||||
|
@ -57,6 +57,19 @@ int add_mtd_device(struct mtd_info *mtd)
|
||||
mtd->index = i;
|
||||
mtd->usecount = 0;
|
||||
|
||||
if (is_power_of_2(mtd->erasesize))
|
||||
mtd->erasesize_shift = ffs(mtd->erasesize) - 1;
|
||||
else
|
||||
mtd->erasesize_shift = 0;
|
||||
|
||||
if (is_power_of_2(mtd->writesize))
|
||||
mtd->writesize_shift = ffs(mtd->writesize) - 1;
|
||||
else
|
||||
mtd->writesize_shift = 0;
|
||||
|
||||
mtd->erasesize_mask = (1 << mtd->erasesize_shift) - 1;
|
||||
mtd->writesize_mask = (1 << mtd->writesize_shift) - 1;
|
||||
|
||||
/* Some chips always power up locked. Unlock them now */
|
||||
if ((mtd->flags & MTD_WRITEABLE)
|
||||
&& (mtd->flags & MTD_POWERUP_LOCK) && mtd->unlock) {
|
||||
@ -344,7 +357,8 @@ static inline int mtd_proc_info (char *buf, int i)
|
||||
if (!this)
|
||||
return 0;
|
||||
|
||||
return sprintf(buf, "mtd%d: %8.8x %8.8x \"%s\"\n", i, this->size,
|
||||
return sprintf(buf, "mtd%d: %8.8llx %8.8x \"%s\"\n", i,
|
||||
(unsigned long long)this->size,
|
||||
this->erasesize, this->name);
|
||||
}
|
||||
|
||||
|
@ -80,9 +80,9 @@ static int mtdoops_erase_block(struct mtd_info *mtd, int offset)
|
||||
if (ret) {
|
||||
set_current_state(TASK_RUNNING);
|
||||
remove_wait_queue(&wait_q, &wait);
|
||||
printk (KERN_WARNING "mtdoops: erase of region [0x%x, 0x%x] "
|
||||
printk (KERN_WARNING "mtdoops: erase of region [0x%llx, 0x%llx] "
|
||||
"on \"%s\" failed\n",
|
||||
erase.addr, erase.len, mtd->name);
|
||||
(unsigned long long)erase.addr, (unsigned long long)erase.len, mtd->name);
|
||||
return ret;
|
||||
}
|
||||
|
||||
@ -289,7 +289,10 @@ static void mtdoops_notify_add(struct mtd_info *mtd)
|
||||
}
|
||||
|
||||
cxt->mtd = mtd;
|
||||
cxt->oops_pages = mtd->size / OOPS_PAGE_SIZE;
|
||||
if (mtd->size > INT_MAX)
|
||||
cxt->oops_pages = INT_MAX / OOPS_PAGE_SIZE;
|
||||
else
|
||||
cxt->oops_pages = (int)mtd->size / OOPS_PAGE_SIZE;
|
||||
|
||||
find_next_position(cxt);
|
||||
|
||||
|
@ -26,7 +26,7 @@ static LIST_HEAD(mtd_partitions);
|
||||
struct mtd_part {
|
||||
struct mtd_info mtd;
|
||||
struct mtd_info *master;
|
||||
u_int32_t offset;
|
||||
uint64_t offset;
|
||||
int index;
|
||||
struct list_head list;
|
||||
int registered;
|
||||
@ -235,7 +235,7 @@ void mtd_erase_callback(struct erase_info *instr)
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(mtd_erase_callback);
|
||||
|
||||
static int part_lock(struct mtd_info *mtd, loff_t ofs, size_t len)
|
||||
static int part_lock(struct mtd_info *mtd, loff_t ofs, uint64_t len)
|
||||
{
|
||||
struct mtd_part *part = PART(mtd);
|
||||
if ((len + ofs) > mtd->size)
|
||||
@ -243,7 +243,7 @@ static int part_lock(struct mtd_info *mtd, loff_t ofs, size_t len)
|
||||
return part->master->lock(part->master, ofs + part->offset, len);
|
||||
}
|
||||
|
||||
static int part_unlock(struct mtd_info *mtd, loff_t ofs, size_t len)
|
||||
static int part_unlock(struct mtd_info *mtd, loff_t ofs, uint64_t len)
|
||||
{
|
||||
struct mtd_part *part = PART(mtd);
|
||||
if ((len + ofs) > mtd->size)
|
||||
@ -317,7 +317,7 @@ EXPORT_SYMBOL(del_mtd_partitions);
|
||||
|
||||
static struct mtd_part *add_one_partition(struct mtd_info *master,
|
||||
const struct mtd_partition *part, int partno,
|
||||
u_int32_t cur_offset)
|
||||
uint64_t cur_offset)
|
||||
{
|
||||
struct mtd_part *slave;
|
||||
|
||||
@ -395,19 +395,19 @@ static struct mtd_part *add_one_partition(struct mtd_info *master,
|
||||
slave->offset = cur_offset;
|
||||
if (slave->offset == MTDPART_OFS_NXTBLK) {
|
||||
slave->offset = cur_offset;
|
||||
if ((cur_offset % master->erasesize) != 0) {
|
||||
if (mtd_mod_by_eb(cur_offset, master) != 0) {
|
||||
/* Round up to next erasesize */
|
||||
slave->offset = ((cur_offset / master->erasesize) + 1) * master->erasesize;
|
||||
slave->offset = (mtd_div_by_eb(cur_offset, master) + 1) * master->erasesize;
|
||||
printk(KERN_NOTICE "Moving partition %d: "
|
||||
"0x%08x -> 0x%08x\n", partno,
|
||||
cur_offset, slave->offset);
|
||||
"0x%012llx -> 0x%012llx\n", partno,
|
||||
(unsigned long long)cur_offset, (unsigned long long)slave->offset);
|
||||
}
|
||||
}
|
||||
if (slave->mtd.size == MTDPART_SIZ_FULL)
|
||||
slave->mtd.size = master->size - slave->offset;
|
||||
|
||||
printk(KERN_NOTICE "0x%08x-0x%08x : \"%s\"\n", slave->offset,
|
||||
slave->offset + slave->mtd.size, slave->mtd.name);
|
||||
printk(KERN_NOTICE "0x%012llx-0x%012llx : \"%s\"\n", (unsigned long long)slave->offset,
|
||||
(unsigned long long)(slave->offset + slave->mtd.size), slave->mtd.name);
|
||||
|
||||
/* let's do some sanity checks */
|
||||
if (slave->offset >= master->size) {
|
||||
@ -420,13 +420,13 @@ static struct mtd_part *add_one_partition(struct mtd_info *master,
|
||||
}
|
||||
if (slave->offset + slave->mtd.size > master->size) {
|
||||
slave->mtd.size = master->size - slave->offset;
|
||||
printk(KERN_WARNING"mtd: partition \"%s\" extends beyond the end of device \"%s\" -- size truncated to %#x\n",
|
||||
part->name, master->name, slave->mtd.size);
|
||||
printk(KERN_WARNING"mtd: partition \"%s\" extends beyond the end of device \"%s\" -- size truncated to %#llx\n",
|
||||
part->name, master->name, (unsigned long long)slave->mtd.size);
|
||||
}
|
||||
if (master->numeraseregions > 1) {
|
||||
/* Deal with variable erase size stuff */
|
||||
int i, max = master->numeraseregions;
|
||||
u32 end = slave->offset + slave->mtd.size;
|
||||
u64 end = slave->offset + slave->mtd.size;
|
||||
struct mtd_erase_region_info *regions = master->eraseregions;
|
||||
|
||||
/* Find the first erase regions which is part of this
|
||||
@ -449,7 +449,7 @@ static struct mtd_part *add_one_partition(struct mtd_info *master,
|
||||
}
|
||||
|
||||
if ((slave->mtd.flags & MTD_WRITEABLE) &&
|
||||
(slave->offset % slave->mtd.erasesize)) {
|
||||
mtd_mod_by_eb(slave->offset, &slave->mtd)) {
|
||||
/* Doesn't start on a boundary of major erase size */
|
||||
/* FIXME: Let it be writable if it is on a boundary of
|
||||
* _minor_ erase size though */
|
||||
@ -458,7 +458,7 @@ static struct mtd_part *add_one_partition(struct mtd_info *master,
|
||||
part->name);
|
||||
}
|
||||
if ((slave->mtd.flags & MTD_WRITEABLE) &&
|
||||
(slave->mtd.size % slave->mtd.erasesize)) {
|
||||
mtd_mod_by_eb(slave->mtd.size, &slave->mtd)) {
|
||||
slave->mtd.flags &= ~MTD_WRITEABLE;
|
||||
printk(KERN_WARNING"mtd: partition \"%s\" doesn't end on an erase block -- force read-only\n",
|
||||
part->name);
|
||||
@ -466,7 +466,7 @@ static struct mtd_part *add_one_partition(struct mtd_info *master,
|
||||
|
||||
slave->mtd.ecclayout = master->ecclayout;
|
||||
if (master->block_isbad) {
|
||||
uint32_t offs = 0;
|
||||
uint64_t offs = 0;
|
||||
|
||||
while (offs < slave->mtd.size) {
|
||||
if (master->block_isbad(master,
|
||||
@ -501,7 +501,7 @@ int add_mtd_partitions(struct mtd_info *master,
|
||||
int nbparts)
|
||||
{
|
||||
struct mtd_part *slave;
|
||||
u_int32_t cur_offset = 0;
|
||||
uint64_t cur_offset = 0;
|
||||
int i;
|
||||
|
||||
printk(KERN_NOTICE "Creating %d MTD partitions on \"%s\":\n", nbparts, master->name);
|
||||
|
@ -163,6 +163,13 @@ config MTD_NAND_S3C2410_HWECC
|
||||
incorrect ECC generation, and if using these, the default of
|
||||
software ECC is preferable.
|
||||
|
||||
config MTD_NAND_NDFC
|
||||
tristate "NDFC NanD Flash Controller"
|
||||
depends on 4xx
|
||||
select MTD_NAND_ECC_SMC
|
||||
help
|
||||
NDFC Nand Flash Controllers are integrated in IBM/AMCC's 4xx SoCs
|
||||
|
||||
config MTD_NAND_S3C2410_CLKSTOP
|
||||
bool "S3C2410 NAND IDLE clock stop"
|
||||
depends on MTD_NAND_S3C2410
|
||||
|
@ -676,11 +676,11 @@ static int alauda_probe(struct usb_interface *interface,
|
||||
goto error;
|
||||
|
||||
al->write_out = usb_sndbulkpipe(al->dev,
|
||||
ep_wr->bEndpointAddress & USB_ENDPOINT_NUMBER_MASK);
|
||||
usb_endpoint_num(ep_wr));
|
||||
al->bulk_in = usb_rcvbulkpipe(al->dev,
|
||||
ep_in->bEndpointAddress & USB_ENDPOINT_NUMBER_MASK);
|
||||
usb_endpoint_num(ep_in));
|
||||
al->bulk_out = usb_sndbulkpipe(al->dev,
|
||||
ep_out->bEndpointAddress & USB_ENDPOINT_NUMBER_MASK);
|
||||
usb_endpoint_num(ep_out));
|
||||
|
||||
/* second device is identical up to now */
|
||||
memcpy(al+1, al, sizeof(*al));
|
||||
|
@ -90,7 +90,7 @@ static int timing[3];
|
||||
module_param_array(timing, int, &numtimings, 0644);
|
||||
|
||||
#ifdef CONFIG_MTD_PARTITIONS
|
||||
static const char *part_probes[] = { "RedBoot", NULL };
|
||||
static const char *part_probes[] = { "cmdlinepart", "RedBoot", NULL };
|
||||
#endif
|
||||
|
||||
/* Hrm. Why isn't this already conditional on something in the struct device? */
|
||||
@ -805,10 +805,13 @@ static int __devinit cafe_nand_probe(struct pci_dev *pdev,
|
||||
add_mtd_device(mtd);
|
||||
|
||||
#ifdef CONFIG_MTD_PARTITIONS
|
||||
#ifdef CONFIG_MTD_CMDLINE_PARTS
|
||||
mtd->name = "cafe_nand";
|
||||
#endif
|
||||
nr_parts = parse_mtd_partitions(mtd, part_probes, &parts, 0);
|
||||
if (nr_parts > 0) {
|
||||
cafe->parts = parts;
|
||||
dev_info(&cafe->pdev->dev, "%d RedBoot partitions found\n", nr_parts);
|
||||
dev_info(&cafe->pdev->dev, "%d partitions found\n", nr_parts);
|
||||
add_mtd_partitions(mtd, parts, nr_parts);
|
||||
}
|
||||
#endif
|
||||
|
@ -777,7 +777,9 @@ static int fsl_elbc_chip_init(struct fsl_elbc_mtd *priv)
|
||||
/* Fill in fsl_elbc_mtd structure */
|
||||
priv->mtd.priv = chip;
|
||||
priv->mtd.owner = THIS_MODULE;
|
||||
priv->fmr = 0; /* rest filled in later */
|
||||
|
||||
/* Set the ECCM according to the settings in bootloader.*/
|
||||
priv->fmr = in_be32(&lbc->fmr) & FMR_ECCM;
|
||||
|
||||
/* fill in nand_chip structure */
|
||||
/* set up function call table */
|
||||
|
@ -2014,13 +2014,14 @@ static int nand_erase(struct mtd_info *mtd, struct erase_info *instr)
|
||||
int nand_erase_nand(struct mtd_info *mtd, struct erase_info *instr,
|
||||
int allowbbt)
|
||||
{
|
||||
int page, len, status, pages_per_block, ret, chipnr;
|
||||
int page, status, pages_per_block, ret, chipnr;
|
||||
struct nand_chip *chip = mtd->priv;
|
||||
int rewrite_bbt[NAND_MAX_CHIPS]={0};
|
||||
loff_t rewrite_bbt[NAND_MAX_CHIPS]={0};
|
||||
unsigned int bbt_masked_page = 0xffffffff;
|
||||
loff_t len;
|
||||
|
||||
DEBUG(MTD_DEBUG_LEVEL3, "nand_erase: start = 0x%08x, len = %i\n",
|
||||
(unsigned int)instr->addr, (unsigned int)instr->len);
|
||||
DEBUG(MTD_DEBUG_LEVEL3, "nand_erase: start = 0x%012llx, len = %llu\n",
|
||||
(unsigned long long)instr->addr, (unsigned long long)instr->len);
|
||||
|
||||
/* Start address must align on block boundary */
|
||||
if (instr->addr & ((1 << chip->phys_erase_shift) - 1)) {
|
||||
@ -2116,7 +2117,8 @@ int nand_erase_nand(struct mtd_info *mtd, struct erase_info *instr,
|
||||
DEBUG(MTD_DEBUG_LEVEL0, "nand_erase: "
|
||||
"Failed erase, page 0x%08x\n", page);
|
||||
instr->state = MTD_ERASE_FAILED;
|
||||
instr->fail_addr = (page << chip->page_shift);
|
||||
instr->fail_addr =
|
||||
((loff_t)page << chip->page_shift);
|
||||
goto erase_exit;
|
||||
}
|
||||
|
||||
@ -2126,7 +2128,8 @@ int nand_erase_nand(struct mtd_info *mtd, struct erase_info *instr,
|
||||
*/
|
||||
if (bbt_masked_page != 0xffffffff &&
|
||||
(page & BBT_PAGE_MASK) == bbt_masked_page)
|
||||
rewrite_bbt[chipnr] = (page << chip->page_shift);
|
||||
rewrite_bbt[chipnr] =
|
||||
((loff_t)page << chip->page_shift);
|
||||
|
||||
/* Increment page address and decrement length */
|
||||
len -= (1 << chip->phys_erase_shift);
|
||||
@ -2173,7 +2176,7 @@ int nand_erase_nand(struct mtd_info *mtd, struct erase_info *instr,
|
||||
continue;
|
||||
/* update the BBT for chip */
|
||||
DEBUG(MTD_DEBUG_LEVEL0, "nand_erase_nand: nand_update_bbt "
|
||||
"(%d:0x%0x 0x%0x)\n", chipnr, rewrite_bbt[chipnr],
|
||||
"(%d:0x%0llx 0x%0x)\n", chipnr, rewrite_bbt[chipnr],
|
||||
chip->bbt_td->pages[chipnr]);
|
||||
nand_update_bbt(mtd, rewrite_bbt[chipnr]);
|
||||
}
|
||||
@ -2365,7 +2368,7 @@ static struct nand_flash_dev *nand_get_flash_type(struct mtd_info *mtd,
|
||||
if (!mtd->name)
|
||||
mtd->name = type->name;
|
||||
|
||||
chip->chipsize = type->chipsize << 20;
|
||||
chip->chipsize = (uint64_t)type->chipsize << 20;
|
||||
|
||||
/* Newer devices have all the information in additional id bytes */
|
||||
if (!type->pagesize) {
|
||||
@ -2423,7 +2426,10 @@ static struct nand_flash_dev *nand_get_flash_type(struct mtd_info *mtd,
|
||||
|
||||
chip->bbt_erase_shift = chip->phys_erase_shift =
|
||||
ffs(mtd->erasesize) - 1;
|
||||
chip->chip_shift = ffs(chip->chipsize) - 1;
|
||||
if (chip->chipsize & 0xffffffff)
|
||||
chip->chip_shift = ffs((unsigned)chip->chipsize) - 1;
|
||||
else
|
||||
chip->chip_shift = ffs((unsigned)(chip->chipsize >> 32)) + 32 - 1;
|
||||
|
||||
/* Set the bad block position */
|
||||
chip->badblockpos = mtd->writesize > 512 ?
|
||||
@ -2517,7 +2523,6 @@ int nand_scan_ident(struct mtd_info *mtd, int maxchips)
|
||||
/**
|
||||
* nand_scan_tail - [NAND Interface] Scan for the NAND device
|
||||
* @mtd: MTD device structure
|
||||
* @maxchips: Number of chips to scan for
|
||||
*
|
||||
* This is the second phase of the normal nand_scan() function. It
|
||||
* fills out all the uninitialized function pointers with the defaults
|
||||
|
@ -171,16 +171,16 @@ static int read_bbt(struct mtd_info *mtd, uint8_t *buf, int page, int num,
|
||||
if (tmp == msk)
|
||||
continue;
|
||||
if (reserved_block_code && (tmp == reserved_block_code)) {
|
||||
printk(KERN_DEBUG "nand_read_bbt: Reserved block at 0x%08x\n",
|
||||
((offs << 2) + (act >> 1)) << this->bbt_erase_shift);
|
||||
printk(KERN_DEBUG "nand_read_bbt: Reserved block at 0x%012llx\n",
|
||||
(loff_t)((offs << 2) + (act >> 1)) << this->bbt_erase_shift);
|
||||
this->bbt[offs + (act >> 3)] |= 0x2 << (act & 0x06);
|
||||
mtd->ecc_stats.bbtblocks++;
|
||||
continue;
|
||||
}
|
||||
/* Leave it for now, if its matured we can move this
|
||||
* message to MTD_DEBUG_LEVEL0 */
|
||||
printk(KERN_DEBUG "nand_read_bbt: Bad block at 0x%08x\n",
|
||||
((offs << 2) + (act >> 1)) << this->bbt_erase_shift);
|
||||
printk(KERN_DEBUG "nand_read_bbt: Bad block at 0x%012llx\n",
|
||||
(loff_t)((offs << 2) + (act >> 1)) << this->bbt_erase_shift);
|
||||
/* Factory marked bad or worn out ? */
|
||||
if (tmp == 0)
|
||||
this->bbt[offs + (act >> 3)] |= 0x3 << (act & 0x06);
|
||||
@ -284,7 +284,7 @@ static int read_abs_bbts(struct mtd_info *mtd, uint8_t *buf,
|
||||
|
||||
/* Read the primary version, if available */
|
||||
if (td->options & NAND_BBT_VERSION) {
|
||||
scan_read_raw(mtd, buf, td->pages[0] << this->page_shift,
|
||||
scan_read_raw(mtd, buf, (loff_t)td->pages[0] << this->page_shift,
|
||||
mtd->writesize);
|
||||
td->version[0] = buf[mtd->writesize + td->veroffs];
|
||||
printk(KERN_DEBUG "Bad block table at page %d, version 0x%02X\n",
|
||||
@ -293,7 +293,7 @@ static int read_abs_bbts(struct mtd_info *mtd, uint8_t *buf,
|
||||
|
||||
/* Read the mirror version, if available */
|
||||
if (md && (md->options & NAND_BBT_VERSION)) {
|
||||
scan_read_raw(mtd, buf, md->pages[0] << this->page_shift,
|
||||
scan_read_raw(mtd, buf, (loff_t)md->pages[0] << this->page_shift,
|
||||
mtd->writesize);
|
||||
md->version[0] = buf[mtd->writesize + md->veroffs];
|
||||
printk(KERN_DEBUG "Bad block table at page %d, version 0x%02X\n",
|
||||
@ -411,7 +411,7 @@ static int create_bbt(struct mtd_info *mtd, uint8_t *buf,
|
||||
numblocks = this->chipsize >> (this->bbt_erase_shift - 1);
|
||||
startblock = chip * numblocks;
|
||||
numblocks += startblock;
|
||||
from = startblock << (this->bbt_erase_shift - 1);
|
||||
from = (loff_t)startblock << (this->bbt_erase_shift - 1);
|
||||
}
|
||||
|
||||
for (i = startblock; i < numblocks;) {
|
||||
@ -428,8 +428,8 @@ static int create_bbt(struct mtd_info *mtd, uint8_t *buf,
|
||||
|
||||
if (ret) {
|
||||
this->bbt[i >> 3] |= 0x03 << (i & 0x6);
|
||||
printk(KERN_WARNING "Bad eraseblock %d at 0x%08x\n",
|
||||
i >> 1, (unsigned int)from);
|
||||
printk(KERN_WARNING "Bad eraseblock %d at 0x%012llx\n",
|
||||
i >> 1, (unsigned long long)from);
|
||||
mtd->ecc_stats.badblocks++;
|
||||
}
|
||||
|
||||
@ -495,7 +495,7 @@ static int search_bbt(struct mtd_info *mtd, uint8_t *buf, struct nand_bbt_descr
|
||||
for (block = 0; block < td->maxblocks; block++) {
|
||||
|
||||
int actblock = startblock + dir * block;
|
||||
loff_t offs = actblock << this->bbt_erase_shift;
|
||||
loff_t offs = (loff_t)actblock << this->bbt_erase_shift;
|
||||
|
||||
/* Read first page */
|
||||
scan_read_raw(mtd, buf, offs, mtd->writesize);
|
||||
@ -719,7 +719,7 @@ static int write_bbt(struct mtd_info *mtd, uint8_t *buf,
|
||||
|
||||
memset(&einfo, 0, sizeof(einfo));
|
||||
einfo.mtd = mtd;
|
||||
einfo.addr = (unsigned long)to;
|
||||
einfo.addr = to;
|
||||
einfo.len = 1 << this->bbt_erase_shift;
|
||||
res = nand_erase_nand(mtd, &einfo, 1);
|
||||
if (res < 0)
|
||||
@ -729,8 +729,8 @@ static int write_bbt(struct mtd_info *mtd, uint8_t *buf,
|
||||
if (res < 0)
|
||||
goto outerr;
|
||||
|
||||
printk(KERN_DEBUG "Bad block table written to 0x%08x, version "
|
||||
"0x%02X\n", (unsigned int)to, td->version[chip]);
|
||||
printk(KERN_DEBUG "Bad block table written to 0x%012llx, version "
|
||||
"0x%02X\n", (unsigned long long)to, td->version[chip]);
|
||||
|
||||
/* Mark it as used */
|
||||
td->pages[chip] = page;
|
||||
@ -910,7 +910,7 @@ static void mark_bbt_region(struct mtd_info *mtd, struct nand_bbt_descr *td)
|
||||
newval = oldval | (0x2 << (block & 0x06));
|
||||
this->bbt[(block >> 3)] = newval;
|
||||
if ((oldval != newval) && td->reserved_block_code)
|
||||
nand_update_bbt(mtd, block << (this->bbt_erase_shift - 1));
|
||||
nand_update_bbt(mtd, (loff_t)block << (this->bbt_erase_shift - 1));
|
||||
continue;
|
||||
}
|
||||
update = 0;
|
||||
@ -931,7 +931,7 @@ static void mark_bbt_region(struct mtd_info *mtd, struct nand_bbt_descr *td)
|
||||
new ones have been marked, then we need to update the stored
|
||||
bbts. This should only happen once. */
|
||||
if (update && td->reserved_block_code)
|
||||
nand_update_bbt(mtd, (block - 2) << (this->bbt_erase_shift - 1));
|
||||
nand_update_bbt(mtd, (loff_t)(block - 2) << (this->bbt_erase_shift - 1));
|
||||
}
|
||||
}
|
||||
|
||||
@ -1027,7 +1027,6 @@ int nand_update_bbt(struct mtd_info *mtd, loff_t offs)
|
||||
if (!this->bbt || !td)
|
||||
return -EINVAL;
|
||||
|
||||
len = mtd->size >> (this->bbt_erase_shift + 2);
|
||||
/* Allocate a temporary buffer for one eraseblock incl. oob */
|
||||
len = (1 << this->bbt_erase_shift);
|
||||
len += (len >> this->page_shift) * mtd->oobsize;
|
||||
|
@ -38,6 +38,9 @@
|
||||
#include <linux/delay.h>
|
||||
#include <linux/list.h>
|
||||
#include <linux/random.h>
|
||||
#include <linux/sched.h>
|
||||
#include <linux/fs.h>
|
||||
#include <linux/pagemap.h>
|
||||
|
||||
/* Default simulator parameters values */
|
||||
#if !defined(CONFIG_NANDSIM_FIRST_ID_BYTE) || \
|
||||
@ -100,6 +103,7 @@ static unsigned int bitflips = 0;
|
||||
static char *gravepages = NULL;
|
||||
static unsigned int rptwear = 0;
|
||||
static unsigned int overridesize = 0;
|
||||
static char *cache_file = NULL;
|
||||
|
||||
module_param(first_id_byte, uint, 0400);
|
||||
module_param(second_id_byte, uint, 0400);
|
||||
@ -122,12 +126,13 @@ module_param(bitflips, uint, 0400);
|
||||
module_param(gravepages, charp, 0400);
|
||||
module_param(rptwear, uint, 0400);
|
||||
module_param(overridesize, uint, 0400);
|
||||
module_param(cache_file, charp, 0400);
|
||||
|
||||
MODULE_PARM_DESC(first_id_byte, "The first byte returned by NAND Flash 'read ID' command (manufacturer ID)");
|
||||
MODULE_PARM_DESC(second_id_byte, "The second byte returned by NAND Flash 'read ID' command (chip ID)");
|
||||
MODULE_PARM_DESC(third_id_byte, "The third byte returned by NAND Flash 'read ID' command");
|
||||
MODULE_PARM_DESC(fourth_id_byte, "The fourth byte returned by NAND Flash 'read ID' command");
|
||||
MODULE_PARM_DESC(access_delay, "Initial page access delay (microiseconds)");
|
||||
MODULE_PARM_DESC(access_delay, "Initial page access delay (microseconds)");
|
||||
MODULE_PARM_DESC(programm_delay, "Page programm delay (microseconds");
|
||||
MODULE_PARM_DESC(erase_delay, "Sector erase delay (milliseconds)");
|
||||
MODULE_PARM_DESC(output_cycle, "Word output (from flash) time (nanodeconds)");
|
||||
@ -153,6 +158,7 @@ MODULE_PARM_DESC(rptwear, "Number of erases inbetween reporting wear, if
|
||||
MODULE_PARM_DESC(overridesize, "Specifies the NAND Flash size overriding the ID bytes. "
|
||||
"The size is specified in erase blocks and as the exponent of a power of two"
|
||||
" e.g. 5 means a size of 32 erase blocks");
|
||||
MODULE_PARM_DESC(cache_file, "File to use to cache nand pages instead of memory");
|
||||
|
||||
/* The largest possible page size */
|
||||
#define NS_LARGEST_PAGE_SIZE 2048
|
||||
@ -266,6 +272,9 @@ MODULE_PARM_DESC(overridesize, "Specifies the NAND Flash size overriding the I
|
||||
*/
|
||||
#define NS_MAX_PREVSTATES 1
|
||||
|
||||
/* Maximum page cache pages needed to read or write a NAND page to the cache_file */
|
||||
#define NS_MAX_HELD_PAGES 16
|
||||
|
||||
/*
|
||||
* A union to represent flash memory contents and flash buffer.
|
||||
*/
|
||||
@ -295,6 +304,9 @@ struct nandsim {
|
||||
/* The simulated NAND flash pages array */
|
||||
union ns_mem *pages;
|
||||
|
||||
/* Slab allocator for nand pages */
|
||||
struct kmem_cache *nand_pages_slab;
|
||||
|
||||
/* Internal buffer of page + OOB size bytes */
|
||||
union ns_mem buf;
|
||||
|
||||
@ -335,6 +347,13 @@ struct nandsim {
|
||||
int ale; /* address Latch Enable */
|
||||
int wp; /* write Protect */
|
||||
} lines;
|
||||
|
||||
/* Fields needed when using a cache file */
|
||||
struct file *cfile; /* Open file */
|
||||
unsigned char *pages_written; /* Which pages have been written */
|
||||
void *file_buf;
|
||||
struct page *held_pages[NS_MAX_HELD_PAGES];
|
||||
int held_cnt;
|
||||
};
|
||||
|
||||
/*
|
||||
@ -420,25 +439,69 @@ static struct mtd_info *nsmtd;
|
||||
static u_char ns_verify_buf[NS_LARGEST_PAGE_SIZE];
|
||||
|
||||
/*
|
||||
* Allocate array of page pointers and initialize the array to NULL
|
||||
* pointers.
|
||||
* Allocate array of page pointers, create slab allocation for an array
|
||||
* and initialize the array by NULL pointers.
|
||||
*
|
||||
* RETURNS: 0 if success, -ENOMEM if memory alloc fails.
|
||||
*/
|
||||
static int alloc_device(struct nandsim *ns)
|
||||
{
|
||||
int i;
|
||||
struct file *cfile;
|
||||
int i, err;
|
||||
|
||||
if (cache_file) {
|
||||
cfile = filp_open(cache_file, O_CREAT | O_RDWR | O_LARGEFILE, 0600);
|
||||
if (IS_ERR(cfile))
|
||||
return PTR_ERR(cfile);
|
||||
if (!cfile->f_op || (!cfile->f_op->read && !cfile->f_op->aio_read)) {
|
||||
NS_ERR("alloc_device: cache file not readable\n");
|
||||
err = -EINVAL;
|
||||
goto err_close;
|
||||
}
|
||||
if (!cfile->f_op->write && !cfile->f_op->aio_write) {
|
||||
NS_ERR("alloc_device: cache file not writeable\n");
|
||||
err = -EINVAL;
|
||||
goto err_close;
|
||||
}
|
||||
ns->pages_written = vmalloc(ns->geom.pgnum);
|
||||
if (!ns->pages_written) {
|
||||
NS_ERR("alloc_device: unable to allocate pages written array\n");
|
||||
err = -ENOMEM;
|
||||
goto err_close;
|
||||
}
|
||||
ns->file_buf = kmalloc(ns->geom.pgszoob, GFP_KERNEL);
|
||||
if (!ns->file_buf) {
|
||||
NS_ERR("alloc_device: unable to allocate file buf\n");
|
||||
err = -ENOMEM;
|
||||
goto err_free;
|
||||
}
|
||||
ns->cfile = cfile;
|
||||
memset(ns->pages_written, 0, ns->geom.pgnum);
|
||||
return 0;
|
||||
}
|
||||
|
||||
ns->pages = vmalloc(ns->geom.pgnum * sizeof(union ns_mem));
|
||||
if (!ns->pages) {
|
||||
NS_ERR("alloc_map: unable to allocate page array\n");
|
||||
NS_ERR("alloc_device: unable to allocate page array\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
for (i = 0; i < ns->geom.pgnum; i++) {
|
||||
ns->pages[i].byte = NULL;
|
||||
}
|
||||
ns->nand_pages_slab = kmem_cache_create("nandsim",
|
||||
ns->geom.pgszoob, 0, 0, NULL);
|
||||
if (!ns->nand_pages_slab) {
|
||||
NS_ERR("cache_create: unable to create kmem_cache\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
err_free:
|
||||
vfree(ns->pages_written);
|
||||
err_close:
|
||||
filp_close(cfile, NULL);
|
||||
return err;
|
||||
}
|
||||
|
||||
/*
|
||||
@ -448,11 +511,20 @@ static void free_device(struct nandsim *ns)
|
||||
{
|
||||
int i;
|
||||
|
||||
if (ns->cfile) {
|
||||
kfree(ns->file_buf);
|
||||
vfree(ns->pages_written);
|
||||
filp_close(ns->cfile, NULL);
|
||||
return;
|
||||
}
|
||||
|
||||
if (ns->pages) {
|
||||
for (i = 0; i < ns->geom.pgnum; i++) {
|
||||
if (ns->pages[i].byte)
|
||||
kfree(ns->pages[i].byte);
|
||||
kmem_cache_free(ns->nand_pages_slab,
|
||||
ns->pages[i].byte);
|
||||
}
|
||||
kmem_cache_destroy(ns->nand_pages_slab);
|
||||
vfree(ns->pages);
|
||||
}
|
||||
}
|
||||
@ -464,7 +536,7 @@ static char *get_partition_name(int i)
|
||||
return kstrdup(buf, GFP_KERNEL);
|
||||
}
|
||||
|
||||
static u_int64_t divide(u_int64_t n, u_int32_t d)
|
||||
static uint64_t divide(uint64_t n, uint32_t d)
|
||||
{
|
||||
do_div(n, d);
|
||||
return n;
|
||||
@ -480,8 +552,8 @@ static int init_nandsim(struct mtd_info *mtd)
|
||||
struct nand_chip *chip = (struct nand_chip *)mtd->priv;
|
||||
struct nandsim *ns = (struct nandsim *)(chip->priv);
|
||||
int i, ret = 0;
|
||||
u_int64_t remains;
|
||||
u_int64_t next_offset;
|
||||
uint64_t remains;
|
||||
uint64_t next_offset;
|
||||
|
||||
if (NS_IS_INITIALIZED(ns)) {
|
||||
NS_ERR("init_nandsim: nandsim is already initialized\n");
|
||||
@ -548,7 +620,7 @@ static int init_nandsim(struct mtd_info *mtd)
|
||||
remains = ns->geom.totsz;
|
||||
next_offset = 0;
|
||||
for (i = 0; i < parts_num; ++i) {
|
||||
u_int64_t part_sz = (u_int64_t)parts[i] * ns->geom.secsz;
|
||||
uint64_t part_sz = (uint64_t)parts[i] * ns->geom.secsz;
|
||||
|
||||
if (!part_sz || part_sz > remains) {
|
||||
NS_ERR("bad partition size.\n");
|
||||
@ -1211,6 +1283,97 @@ static int find_operation(struct nandsim *ns, uint32_t flag)
|
||||
return -1;
|
||||
}
|
||||
|
||||
static void put_pages(struct nandsim *ns)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < ns->held_cnt; i++)
|
||||
page_cache_release(ns->held_pages[i]);
|
||||
}
|
||||
|
||||
/* Get page cache pages in advance to provide NOFS memory allocation */
|
||||
static int get_pages(struct nandsim *ns, struct file *file, size_t count, loff_t pos)
|
||||
{
|
||||
pgoff_t index, start_index, end_index;
|
||||
struct page *page;
|
||||
struct address_space *mapping = file->f_mapping;
|
||||
|
||||
start_index = pos >> PAGE_CACHE_SHIFT;
|
||||
end_index = (pos + count - 1) >> PAGE_CACHE_SHIFT;
|
||||
if (end_index - start_index + 1 > NS_MAX_HELD_PAGES)
|
||||
return -EINVAL;
|
||||
ns->held_cnt = 0;
|
||||
for (index = start_index; index <= end_index; index++) {
|
||||
page = find_get_page(mapping, index);
|
||||
if (page == NULL) {
|
||||
page = find_or_create_page(mapping, index, GFP_NOFS);
|
||||
if (page == NULL) {
|
||||
write_inode_now(mapping->host, 1);
|
||||
page = find_or_create_page(mapping, index, GFP_NOFS);
|
||||
}
|
||||
if (page == NULL) {
|
||||
put_pages(ns);
|
||||
return -ENOMEM;
|
||||
}
|
||||
unlock_page(page);
|
||||
}
|
||||
ns->held_pages[ns->held_cnt++] = page;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int set_memalloc(void)
|
||||
{
|
||||
if (current->flags & PF_MEMALLOC)
|
||||
return 0;
|
||||
current->flags |= PF_MEMALLOC;
|
||||
return 1;
|
||||
}
|
||||
|
||||
static void clear_memalloc(int memalloc)
|
||||
{
|
||||
if (memalloc)
|
||||
current->flags &= ~PF_MEMALLOC;
|
||||
}
|
||||
|
||||
static ssize_t read_file(struct nandsim *ns, struct file *file, void *buf, size_t count, loff_t *pos)
|
||||
{
|
||||
mm_segment_t old_fs;
|
||||
ssize_t tx;
|
||||
int err, memalloc;
|
||||
|
||||
err = get_pages(ns, file, count, *pos);
|
||||
if (err)
|
||||
return err;
|
||||
old_fs = get_fs();
|
||||
set_fs(get_ds());
|
||||
memalloc = set_memalloc();
|
||||
tx = vfs_read(file, (char __user *)buf, count, pos);
|
||||
clear_memalloc(memalloc);
|
||||
set_fs(old_fs);
|
||||
put_pages(ns);
|
||||
return tx;
|
||||
}
|
||||
|
||||
static ssize_t write_file(struct nandsim *ns, struct file *file, void *buf, size_t count, loff_t *pos)
|
||||
{
|
||||
mm_segment_t old_fs;
|
||||
ssize_t tx;
|
||||
int err, memalloc;
|
||||
|
||||
err = get_pages(ns, file, count, *pos);
|
||||
if (err)
|
||||
return err;
|
||||
old_fs = get_fs();
|
||||
set_fs(get_ds());
|
||||
memalloc = set_memalloc();
|
||||
tx = vfs_write(file, (char __user *)buf, count, pos);
|
||||
clear_memalloc(memalloc);
|
||||
set_fs(old_fs);
|
||||
put_pages(ns);
|
||||
return tx;
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns a pointer to the current page.
|
||||
*/
|
||||
@ -1227,6 +1390,38 @@ static inline u_char *NS_PAGE_BYTE_OFF(struct nandsim *ns)
|
||||
return NS_GET_PAGE(ns)->byte + ns->regs.column + ns->regs.off;
|
||||
}
|
||||
|
||||
int do_read_error(struct nandsim *ns, int num)
|
||||
{
|
||||
unsigned int page_no = ns->regs.row;
|
||||
|
||||
if (read_error(page_no)) {
|
||||
int i;
|
||||
memset(ns->buf.byte, 0xFF, num);
|
||||
for (i = 0; i < num; ++i)
|
||||
ns->buf.byte[i] = random32();
|
||||
NS_WARN("simulating read error in page %u\n", page_no);
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void do_bit_flips(struct nandsim *ns, int num)
|
||||
{
|
||||
if (bitflips && random32() < (1 << 22)) {
|
||||
int flips = 1;
|
||||
if (bitflips > 1)
|
||||
flips = (random32() % (int) bitflips) + 1;
|
||||
while (flips--) {
|
||||
int pos = random32() % (num * 8);
|
||||
ns->buf.byte[pos / 8] ^= (1 << (pos % 8));
|
||||
NS_WARN("read_page: flipping bit %d in page %d "
|
||||
"reading from %d ecc: corrected=%u failed=%u\n",
|
||||
pos, ns->regs.row, ns->regs.column + ns->regs.off,
|
||||
nsmtd->ecc_stats.corrected, nsmtd->ecc_stats.failed);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Fill the NAND buffer with data read from the specified page.
|
||||
*/
|
||||
@ -1234,36 +1429,40 @@ static void read_page(struct nandsim *ns, int num)
|
||||
{
|
||||
union ns_mem *mypage;
|
||||
|
||||
if (ns->cfile) {
|
||||
if (!ns->pages_written[ns->regs.row]) {
|
||||
NS_DBG("read_page: page %d not written\n", ns->regs.row);
|
||||
memset(ns->buf.byte, 0xFF, num);
|
||||
} else {
|
||||
loff_t pos;
|
||||
ssize_t tx;
|
||||
|
||||
NS_DBG("read_page: page %d written, reading from %d\n",
|
||||
ns->regs.row, ns->regs.column + ns->regs.off);
|
||||
if (do_read_error(ns, num))
|
||||
return;
|
||||
pos = (loff_t)ns->regs.row * ns->geom.pgszoob + ns->regs.column + ns->regs.off;
|
||||
tx = read_file(ns, ns->cfile, ns->buf.byte, num, &pos);
|
||||
if (tx != num) {
|
||||
NS_ERR("read_page: read error for page %d ret %ld\n", ns->regs.row, (long)tx);
|
||||
return;
|
||||
}
|
||||
do_bit_flips(ns, num);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
mypage = NS_GET_PAGE(ns);
|
||||
if (mypage->byte == NULL) {
|
||||
NS_DBG("read_page: page %d not allocated\n", ns->regs.row);
|
||||
memset(ns->buf.byte, 0xFF, num);
|
||||
} else {
|
||||
unsigned int page_no = ns->regs.row;
|
||||
NS_DBG("read_page: page %d allocated, reading from %d\n",
|
||||
ns->regs.row, ns->regs.column + ns->regs.off);
|
||||
if (read_error(page_no)) {
|
||||
int i;
|
||||
memset(ns->buf.byte, 0xFF, num);
|
||||
for (i = 0; i < num; ++i)
|
||||
ns->buf.byte[i] = random32();
|
||||
NS_WARN("simulating read error in page %u\n", page_no);
|
||||
if (do_read_error(ns, num))
|
||||
return;
|
||||
}
|
||||
memcpy(ns->buf.byte, NS_PAGE_BYTE_OFF(ns), num);
|
||||
if (bitflips && random32() < (1 << 22)) {
|
||||
int flips = 1;
|
||||
if (bitflips > 1)
|
||||
flips = (random32() % (int) bitflips) + 1;
|
||||
while (flips--) {
|
||||
int pos = random32() % (num * 8);
|
||||
ns->buf.byte[pos / 8] ^= (1 << (pos % 8));
|
||||
NS_WARN("read_page: flipping bit %d in page %d "
|
||||
"reading from %d ecc: corrected=%u failed=%u\n",
|
||||
pos, ns->regs.row, ns->regs.column + ns->regs.off,
|
||||
nsmtd->ecc_stats.corrected, nsmtd->ecc_stats.failed);
|
||||
}
|
||||
}
|
||||
do_bit_flips(ns, num);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1275,11 +1474,20 @@ static void erase_sector(struct nandsim *ns)
|
||||
union ns_mem *mypage;
|
||||
int i;
|
||||
|
||||
if (ns->cfile) {
|
||||
for (i = 0; i < ns->geom.pgsec; i++)
|
||||
if (ns->pages_written[ns->regs.row + i]) {
|
||||
NS_DBG("erase_sector: freeing page %d\n", ns->regs.row + i);
|
||||
ns->pages_written[ns->regs.row + i] = 0;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
mypage = NS_GET_PAGE(ns);
|
||||
for (i = 0; i < ns->geom.pgsec; i++) {
|
||||
if (mypage->byte != NULL) {
|
||||
NS_DBG("erase_sector: freeing page %d\n", ns->regs.row+i);
|
||||
kfree(mypage->byte);
|
||||
kmem_cache_free(ns->nand_pages_slab, mypage->byte);
|
||||
mypage->byte = NULL;
|
||||
}
|
||||
mypage++;
|
||||
@ -1295,16 +1503,57 @@ static int prog_page(struct nandsim *ns, int num)
|
||||
union ns_mem *mypage;
|
||||
u_char *pg_off;
|
||||
|
||||
if (ns->cfile) {
|
||||
loff_t off, pos;
|
||||
ssize_t tx;
|
||||
int all;
|
||||
|
||||
NS_DBG("prog_page: writing page %d\n", ns->regs.row);
|
||||
pg_off = ns->file_buf + ns->regs.column + ns->regs.off;
|
||||
off = (loff_t)ns->regs.row * ns->geom.pgszoob + ns->regs.column + ns->regs.off;
|
||||
if (!ns->pages_written[ns->regs.row]) {
|
||||
all = 1;
|
||||
memset(ns->file_buf, 0xff, ns->geom.pgszoob);
|
||||
} else {
|
||||
all = 0;
|
||||
pos = off;
|
||||
tx = read_file(ns, ns->cfile, pg_off, num, &pos);
|
||||
if (tx != num) {
|
||||
NS_ERR("prog_page: read error for page %d ret %ld\n", ns->regs.row, (long)tx);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
for (i = 0; i < num; i++)
|
||||
pg_off[i] &= ns->buf.byte[i];
|
||||
if (all) {
|
||||
pos = (loff_t)ns->regs.row * ns->geom.pgszoob;
|
||||
tx = write_file(ns, ns->cfile, ns->file_buf, ns->geom.pgszoob, &pos);
|
||||
if (tx != ns->geom.pgszoob) {
|
||||
NS_ERR("prog_page: write error for page %d ret %ld\n", ns->regs.row, (long)tx);
|
||||
return -1;
|
||||
}
|
||||
ns->pages_written[ns->regs.row] = 1;
|
||||
} else {
|
||||
pos = off;
|
||||
tx = write_file(ns, ns->cfile, pg_off, num, &pos);
|
||||
if (tx != num) {
|
||||
NS_ERR("prog_page: write error for page %d ret %ld\n", ns->regs.row, (long)tx);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
mypage = NS_GET_PAGE(ns);
|
||||
if (mypage->byte == NULL) {
|
||||
NS_DBG("prog_page: allocating page %d\n", ns->regs.row);
|
||||
/*
|
||||
* We allocate memory with GFP_NOFS because a flash FS may
|
||||
* utilize this. If it is holding an FS lock, then gets here,
|
||||
* then kmalloc runs writeback which goes to the FS again
|
||||
* and deadlocks. This was seen in practice.
|
||||
* then kernel memory alloc runs writeback which goes to the FS
|
||||
* again and deadlocks. This was seen in practice.
|
||||
*/
|
||||
mypage->byte = kmalloc(ns->geom.pgszoob, GFP_NOFS);
|
||||
mypage->byte = kmem_cache_alloc(ns->nand_pages_slab, GFP_NOFS);
|
||||
if (mypage->byte == NULL) {
|
||||
NS_ERR("prog_page: error allocating memory for page %d\n", ns->regs.row);
|
||||
return -1;
|
||||
@ -1736,13 +1985,17 @@ static void ns_nand_write_byte(struct mtd_info *mtd, u_char byte)
|
||||
|
||||
/* Check if chip is expecting command */
|
||||
if (NS_STATE(ns->nxstate) != STATE_UNKNOWN && !(ns->nxstate & STATE_CMD_MASK)) {
|
||||
/*
|
||||
* We are in situation when something else (not command)
|
||||
* was expected but command was input. In this case ignore
|
||||
* previous command(s)/state(s) and accept the last one.
|
||||
*/
|
||||
NS_WARN("write_byte: command (%#x) wasn't expected, expected state is %s, "
|
||||
"ignore previous states\n", (uint)byte, get_state_name(ns->nxstate));
|
||||
/* Do not warn if only 2 id bytes are read */
|
||||
if (!(ns->regs.command == NAND_CMD_READID &&
|
||||
NS_STATE(ns->state) == STATE_DATAOUT_ID && ns->regs.count == 2)) {
|
||||
/*
|
||||
* We are in situation when something else (not command)
|
||||
* was expected but command was input. In this case ignore
|
||||
* previous command(s)/state(s) and accept the last one.
|
||||
*/
|
||||
NS_WARN("write_byte: command (%#x) wasn't expected, expected state is %s, "
|
||||
"ignore previous states\n", (uint)byte, get_state_name(ns->nxstate));
|
||||
}
|
||||
switch_to_ready_state(ns, NS_STATUS_FAILED(ns));
|
||||
}
|
||||
|
||||
@ -2044,7 +2297,7 @@ static int __init ns_init_module(void)
|
||||
}
|
||||
|
||||
if (overridesize) {
|
||||
u_int64_t new_size = (u_int64_t)nsmtd->erasesize << overridesize;
|
||||
uint64_t new_size = (uint64_t)nsmtd->erasesize << overridesize;
|
||||
if (new_size >> overridesize != nsmtd->erasesize) {
|
||||
NS_ERR("overridesize is too big\n");
|
||||
goto err_exit;
|
||||
|
@ -2,12 +2,20 @@
|
||||
* drivers/mtd/ndfc.c
|
||||
*
|
||||
* Overview:
|
||||
* Platform independend driver for NDFC (NanD Flash Controller)
|
||||
* Platform independent driver for NDFC (NanD Flash Controller)
|
||||
* integrated into EP440 cores
|
||||
*
|
||||
* Ported to an OF platform driver by Sean MacLennan
|
||||
*
|
||||
* The NDFC supports multiple chips, but this driver only supports a
|
||||
* single chip since I do not have access to any boards with
|
||||
* multiple chips.
|
||||
*
|
||||
* Author: Thomas Gleixner
|
||||
*
|
||||
* Copyright 2006 IBM
|
||||
* Copyright 2008 PIKA Technologies
|
||||
* Sean MacLennan <smaclennan@pikatech.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
|
||||
@ -21,27 +29,20 @@
|
||||
#include <linux/mtd/partitions.h>
|
||||
#include <linux/mtd/ndfc.h>
|
||||
#include <linux/mtd/mtd.h>
|
||||
#include <linux/platform_device.h>
|
||||
|
||||
#include <linux/of_platform.h>
|
||||
#include <asm/io.h>
|
||||
#ifdef CONFIG_40x
|
||||
#include <asm/ibm405.h>
|
||||
#else
|
||||
#include <asm/ibm44x.h>
|
||||
#endif
|
||||
|
||||
struct ndfc_nand_mtd {
|
||||
struct mtd_info mtd;
|
||||
struct nand_chip chip;
|
||||
struct platform_nand_chip *pl_chip;
|
||||
};
|
||||
|
||||
static struct ndfc_nand_mtd ndfc_mtd[NDFC_MAX_BANKS];
|
||||
|
||||
struct ndfc_controller {
|
||||
void __iomem *ndfcbase;
|
||||
struct nand_hw_control ndfc_control;
|
||||
atomic_t childs_active;
|
||||
struct of_device *ofdev;
|
||||
void __iomem *ndfcbase;
|
||||
struct mtd_info mtd;
|
||||
struct nand_chip chip;
|
||||
int chip_select;
|
||||
struct nand_hw_control ndfc_control;
|
||||
#ifdef CONFIG_MTD_PARTITIONS
|
||||
struct mtd_partition *parts;
|
||||
#endif
|
||||
};
|
||||
|
||||
static struct ndfc_controller ndfc_ctrl;
|
||||
@ -50,17 +51,14 @@ static void ndfc_select_chip(struct mtd_info *mtd, int chip)
|
||||
{
|
||||
uint32_t ccr;
|
||||
struct ndfc_controller *ndfc = &ndfc_ctrl;
|
||||
struct nand_chip *nandchip = mtd->priv;
|
||||
struct ndfc_nand_mtd *nandmtd = nandchip->priv;
|
||||
struct platform_nand_chip *pchip = nandmtd->pl_chip;
|
||||
|
||||
ccr = __raw_readl(ndfc->ndfcbase + NDFC_CCR);
|
||||
ccr = in_be32(ndfc->ndfcbase + NDFC_CCR);
|
||||
if (chip >= 0) {
|
||||
ccr &= ~NDFC_CCR_BS_MASK;
|
||||
ccr |= NDFC_CCR_BS(chip + pchip->chip_offset);
|
||||
ccr |= NDFC_CCR_BS(chip + ndfc->chip_select);
|
||||
} else
|
||||
ccr |= NDFC_CCR_RESET_CE;
|
||||
__raw_writel(ccr, ndfc->ndfcbase + NDFC_CCR);
|
||||
out_be32(ndfc->ndfcbase + NDFC_CCR, ccr);
|
||||
}
|
||||
|
||||
static void ndfc_hwcontrol(struct mtd_info *mtd, int cmd, unsigned int ctrl)
|
||||
@ -80,7 +78,7 @@ static int ndfc_ready(struct mtd_info *mtd)
|
||||
{
|
||||
struct ndfc_controller *ndfc = &ndfc_ctrl;
|
||||
|
||||
return __raw_readl(ndfc->ndfcbase + NDFC_STAT) & NDFC_STAT_IS_READY;
|
||||
return in_be32(ndfc->ndfcbase + NDFC_STAT) & NDFC_STAT_IS_READY;
|
||||
}
|
||||
|
||||
static void ndfc_enable_hwecc(struct mtd_info *mtd, int mode)
|
||||
@ -88,9 +86,9 @@ static void ndfc_enable_hwecc(struct mtd_info *mtd, int mode)
|
||||
uint32_t ccr;
|
||||
struct ndfc_controller *ndfc = &ndfc_ctrl;
|
||||
|
||||
ccr = __raw_readl(ndfc->ndfcbase + NDFC_CCR);
|
||||
ccr = in_be32(ndfc->ndfcbase + NDFC_CCR);
|
||||
ccr |= NDFC_CCR_RESET_ECC;
|
||||
__raw_writel(ccr, ndfc->ndfcbase + NDFC_CCR);
|
||||
out_be32(ndfc->ndfcbase + NDFC_CCR, ccr);
|
||||
wmb();
|
||||
}
|
||||
|
||||
@ -102,9 +100,10 @@ static int ndfc_calculate_ecc(struct mtd_info *mtd,
|
||||
uint8_t *p = (uint8_t *)&ecc;
|
||||
|
||||
wmb();
|
||||
ecc = __raw_readl(ndfc->ndfcbase + NDFC_ECC);
|
||||
ecc_code[0] = p[1];
|
||||
ecc_code[1] = p[2];
|
||||
ecc = in_be32(ndfc->ndfcbase + NDFC_ECC);
|
||||
/* The NDFC uses Smart Media (SMC) bytes order */
|
||||
ecc_code[0] = p[2];
|
||||
ecc_code[1] = p[1];
|
||||
ecc_code[2] = p[3];
|
||||
|
||||
return 0;
|
||||
@ -123,7 +122,7 @@ static void ndfc_read_buf(struct mtd_info *mtd, uint8_t *buf, int len)
|
||||
uint32_t *p = (uint32_t *) buf;
|
||||
|
||||
for(;len > 0; len -= 4)
|
||||
*p++ = __raw_readl(ndfc->ndfcbase + NDFC_DATA);
|
||||
*p++ = in_be32(ndfc->ndfcbase + NDFC_DATA);
|
||||
}
|
||||
|
||||
static void ndfc_write_buf(struct mtd_info *mtd, const uint8_t *buf, int len)
|
||||
@ -132,7 +131,7 @@ static void ndfc_write_buf(struct mtd_info *mtd, const uint8_t *buf, int len)
|
||||
uint32_t *p = (uint32_t *) buf;
|
||||
|
||||
for(;len > 0; len -= 4)
|
||||
__raw_writel(*p++, ndfc->ndfcbase + NDFC_DATA);
|
||||
out_be32(ndfc->ndfcbase + NDFC_DATA, *p++);
|
||||
}
|
||||
|
||||
static int ndfc_verify_buf(struct mtd_info *mtd, const uint8_t *buf, int len)
|
||||
@ -141,7 +140,7 @@ static int ndfc_verify_buf(struct mtd_info *mtd, const uint8_t *buf, int len)
|
||||
uint32_t *p = (uint32_t *) buf;
|
||||
|
||||
for(;len > 0; len -= 4)
|
||||
if (*p++ != __raw_readl(ndfc->ndfcbase + NDFC_DATA))
|
||||
if (*p++ != in_be32(ndfc->ndfcbase + NDFC_DATA))
|
||||
return -EFAULT;
|
||||
return 0;
|
||||
}
|
||||
@ -149,10 +148,19 @@ static int ndfc_verify_buf(struct mtd_info *mtd, const uint8_t *buf, int len)
|
||||
/*
|
||||
* Initialize chip structure
|
||||
*/
|
||||
static void ndfc_chip_init(struct ndfc_nand_mtd *mtd)
|
||||
static int ndfc_chip_init(struct ndfc_controller *ndfc,
|
||||
struct device_node *node)
|
||||
{
|
||||
struct ndfc_controller *ndfc = &ndfc_ctrl;
|
||||
struct nand_chip *chip = &mtd->chip;
|
||||
#ifdef CONFIG_MTD_PARTITIONS
|
||||
#ifdef CONFIG_MTD_CMDLINE_PARTS
|
||||
static const char *part_types[] = { "cmdlinepart", NULL };
|
||||
#else
|
||||
static const char *part_types[] = { NULL };
|
||||
#endif
|
||||
#endif
|
||||
struct device_node *flash_np;
|
||||
struct nand_chip *chip = &ndfc->chip;
|
||||
int ret;
|
||||
|
||||
chip->IO_ADDR_R = ndfc->ndfcbase + NDFC_DATA;
|
||||
chip->IO_ADDR_W = ndfc->ndfcbase + NDFC_DATA;
|
||||
@ -160,8 +168,6 @@ static void ndfc_chip_init(struct ndfc_nand_mtd *mtd)
|
||||
chip->dev_ready = ndfc_ready;
|
||||
chip->select_chip = ndfc_select_chip;
|
||||
chip->chip_delay = 50;
|
||||
chip->priv = mtd;
|
||||
chip->options = mtd->pl_chip->options;
|
||||
chip->controller = &ndfc->ndfc_control;
|
||||
chip->read_buf = ndfc_read_buf;
|
||||
chip->write_buf = ndfc_write_buf;
|
||||
@ -172,143 +178,136 @@ static void ndfc_chip_init(struct ndfc_nand_mtd *mtd)
|
||||
chip->ecc.mode = NAND_ECC_HW;
|
||||
chip->ecc.size = 256;
|
||||
chip->ecc.bytes = 3;
|
||||
chip->ecclayout = chip->ecc.layout = mtd->pl_chip->ecclayout;
|
||||
mtd->mtd.priv = chip;
|
||||
mtd->mtd.owner = THIS_MODULE;
|
||||
}
|
||||
|
||||
static int ndfc_chip_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct platform_nand_chip *nc = pdev->dev.platform_data;
|
||||
struct ndfc_chip_settings *settings = nc->priv;
|
||||
struct ndfc_controller *ndfc = &ndfc_ctrl;
|
||||
struct ndfc_nand_mtd *nandmtd;
|
||||
ndfc->mtd.priv = chip;
|
||||
ndfc->mtd.owner = THIS_MODULE;
|
||||
|
||||
if (nc->chip_offset >= NDFC_MAX_BANKS || nc->nr_chips > NDFC_MAX_BANKS)
|
||||
return -EINVAL;
|
||||
|
||||
/* Set the bank settings */
|
||||
__raw_writel(settings->bank_settings,
|
||||
ndfc->ndfcbase + NDFC_BCFG0 + (nc->chip_offset << 2));
|
||||
|
||||
nandmtd = &ndfc_mtd[pdev->id];
|
||||
if (nandmtd->pl_chip)
|
||||
return -EBUSY;
|
||||
|
||||
nandmtd->pl_chip = nc;
|
||||
ndfc_chip_init(nandmtd);
|
||||
|
||||
/* Scan for chips */
|
||||
if (nand_scan(&nandmtd->mtd, nc->nr_chips)) {
|
||||
nandmtd->pl_chip = NULL;
|
||||
flash_np = of_get_next_child(node, NULL);
|
||||
if (!flash_np)
|
||||
return -ENODEV;
|
||||
|
||||
ndfc->mtd.name = kasprintf(GFP_KERNEL, "%s.%s",
|
||||
ndfc->ofdev->dev.bus_id, flash_np->name);
|
||||
if (!ndfc->mtd.name) {
|
||||
ret = -ENOMEM;
|
||||
goto err;
|
||||
}
|
||||
|
||||
ret = nand_scan(&ndfc->mtd, 1);
|
||||
if (ret)
|
||||
goto err;
|
||||
|
||||
#ifdef CONFIG_MTD_PARTITIONS
|
||||
printk("Number of partitions %d\n", nc->nr_partitions);
|
||||
if (nc->nr_partitions) {
|
||||
/* Add the full device, so complete dumps can be made */
|
||||
add_mtd_device(&nandmtd->mtd);
|
||||
add_mtd_partitions(&nandmtd->mtd, nc->partitions,
|
||||
nc->nr_partitions);
|
||||
ret = parse_mtd_partitions(&ndfc->mtd, part_types, &ndfc->parts, 0);
|
||||
if (ret < 0)
|
||||
goto err;
|
||||
|
||||
} else
|
||||
#else
|
||||
add_mtd_device(&nandmtd->mtd);
|
||||
#endif
|
||||
|
||||
atomic_inc(&ndfc->childs_active);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ndfc_chip_remove(struct platform_device *pdev)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ndfc_nand_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct platform_nand_ctrl *nc = pdev->dev.platform_data;
|
||||
struct ndfc_controller_settings *settings = nc->priv;
|
||||
struct resource *res = pdev->resource;
|
||||
struct ndfc_controller *ndfc = &ndfc_ctrl;
|
||||
unsigned long long phys = settings->ndfc_erpn | res->start;
|
||||
|
||||
#ifndef CONFIG_PHYS_64BIT
|
||||
ndfc->ndfcbase = ioremap((phys_addr_t)phys, res->end - res->start + 1);
|
||||
#else
|
||||
ndfc->ndfcbase = ioremap64(phys, res->end - res->start + 1);
|
||||
#endif
|
||||
if (!ndfc->ndfcbase) {
|
||||
printk(KERN_ERR "NDFC: ioremap failed\n");
|
||||
return -EIO;
|
||||
#ifdef CONFIG_MTD_OF_PARTS
|
||||
if (ret == 0) {
|
||||
ret = of_mtd_parse_partitions(&ndfc->ofdev->dev, flash_np,
|
||||
&ndfc->parts);
|
||||
if (ret < 0)
|
||||
goto err;
|
||||
}
|
||||
#endif
|
||||
|
||||
__raw_writel(settings->ccr_settings, ndfc->ndfcbase + NDFC_CCR);
|
||||
if (ret > 0)
|
||||
ret = add_mtd_partitions(&ndfc->mtd, ndfc->parts, ret);
|
||||
else
|
||||
#endif
|
||||
ret = add_mtd_device(&ndfc->mtd);
|
||||
|
||||
err:
|
||||
of_node_put(flash_np);
|
||||
if (ret)
|
||||
kfree(ndfc->mtd.name);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int __devinit ndfc_probe(struct of_device *ofdev,
|
||||
const struct of_device_id *match)
|
||||
{
|
||||
struct ndfc_controller *ndfc = &ndfc_ctrl;
|
||||
const u32 *reg;
|
||||
u32 ccr;
|
||||
int err, len;
|
||||
|
||||
spin_lock_init(&ndfc->ndfc_control.lock);
|
||||
init_waitqueue_head(&ndfc->ndfc_control.wq);
|
||||
ndfc->ofdev = ofdev;
|
||||
dev_set_drvdata(&ofdev->dev, ndfc);
|
||||
|
||||
platform_set_drvdata(pdev, ndfc);
|
||||
|
||||
printk("NDFC NAND Driver initialized. Chip-Rev: 0x%08x\n",
|
||||
__raw_readl(ndfc->ndfcbase + NDFC_REVID));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ndfc_nand_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct ndfc_controller *ndfc = platform_get_drvdata(pdev);
|
||||
|
||||
if (atomic_read(&ndfc->childs_active))
|
||||
return -EBUSY;
|
||||
|
||||
if (ndfc) {
|
||||
platform_set_drvdata(pdev, NULL);
|
||||
iounmap(ndfc_ctrl.ndfcbase);
|
||||
ndfc_ctrl.ndfcbase = NULL;
|
||||
/* Read the reg property to get the chip select */
|
||||
reg = of_get_property(ofdev->node, "reg", &len);
|
||||
if (reg == NULL || len != 12) {
|
||||
dev_err(&ofdev->dev, "unable read reg property (%d)\n", len);
|
||||
return -ENOENT;
|
||||
}
|
||||
ndfc->chip_select = reg[0];
|
||||
|
||||
ndfc->ndfcbase = of_iomap(ofdev->node, 0);
|
||||
if (!ndfc->ndfcbase) {
|
||||
dev_err(&ofdev->dev, "failed to get memory\n");
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
ccr = NDFC_CCR_BS(ndfc->chip_select);
|
||||
|
||||
/* It is ok if ccr does not exist - just default to 0 */
|
||||
reg = of_get_property(ofdev->node, "ccr", NULL);
|
||||
if (reg)
|
||||
ccr |= *reg;
|
||||
|
||||
out_be32(ndfc->ndfcbase + NDFC_CCR, ccr);
|
||||
|
||||
/* Set the bank settings if given */
|
||||
reg = of_get_property(ofdev->node, "bank-settings", NULL);
|
||||
if (reg) {
|
||||
int offset = NDFC_BCFG0 + (ndfc->chip_select << 2);
|
||||
out_be32(ndfc->ndfcbase + offset, *reg);
|
||||
}
|
||||
|
||||
err = ndfc_chip_init(ndfc, ofdev->node);
|
||||
if (err) {
|
||||
iounmap(ndfc->ndfcbase);
|
||||
return err;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* driver device registration */
|
||||
static int __devexit ndfc_remove(struct of_device *ofdev)
|
||||
{
|
||||
struct ndfc_controller *ndfc = dev_get_drvdata(&ofdev->dev);
|
||||
|
||||
static struct platform_driver ndfc_chip_driver = {
|
||||
.probe = ndfc_chip_probe,
|
||||
.remove = ndfc_chip_remove,
|
||||
.driver = {
|
||||
.name = "ndfc-chip",
|
||||
.owner = THIS_MODULE,
|
||||
},
|
||||
nand_release(&ndfc->mtd);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct of_device_id ndfc_match[] = {
|
||||
{ .compatible = "ibm,ndfc", },
|
||||
{}
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, ndfc_match);
|
||||
|
||||
static struct platform_driver ndfc_nand_driver = {
|
||||
.probe = ndfc_nand_probe,
|
||||
.remove = ndfc_nand_remove,
|
||||
.driver = {
|
||||
.name = "ndfc-nand",
|
||||
.owner = THIS_MODULE,
|
||||
static struct of_platform_driver ndfc_driver = {
|
||||
.driver = {
|
||||
.name = "ndfc",
|
||||
},
|
||||
.match_table = ndfc_match,
|
||||
.probe = ndfc_probe,
|
||||
.remove = __devexit_p(ndfc_remove),
|
||||
};
|
||||
|
||||
static int __init ndfc_nand_init(void)
|
||||
{
|
||||
int ret;
|
||||
|
||||
spin_lock_init(&ndfc_ctrl.ndfc_control.lock);
|
||||
init_waitqueue_head(&ndfc_ctrl.ndfc_control.wq);
|
||||
|
||||
ret = platform_driver_register(&ndfc_nand_driver);
|
||||
if (!ret)
|
||||
ret = platform_driver_register(&ndfc_chip_driver);
|
||||
return ret;
|
||||
return of_register_platform_driver(&ndfc_driver);
|
||||
}
|
||||
|
||||
static void __exit ndfc_nand_exit(void)
|
||||
{
|
||||
platform_driver_unregister(&ndfc_chip_driver);
|
||||
platform_driver_unregister(&ndfc_nand_driver);
|
||||
of_unregister_platform_driver(&ndfc_driver);
|
||||
}
|
||||
|
||||
module_init(ndfc_nand_init);
|
||||
@ -316,6 +315,4 @@ module_exit(ndfc_nand_exit);
|
||||
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_AUTHOR("Thomas Gleixner <tglx@linutronix.de>");
|
||||
MODULE_DESCRIPTION("Platform driver for NDFC");
|
||||
MODULE_ALIAS("platform:ndfc-chip");
|
||||
MODULE_ALIAS("platform:ndfc-nand");
|
||||
MODULE_DESCRIPTION("OF Platform driver for NDFC");
|
||||
|
@ -298,7 +298,7 @@ static struct pxa3xx_nand_flash *builtin_flash_types[] = {
|
||||
#define NDTR1_tAR(c) (min((c), 15) << 0)
|
||||
|
||||
/* convert nano-seconds to nand flash controller clock cycles */
|
||||
#define ns2cycle(ns, clk) (int)(((ns) * (clk / 1000000) / 1000) + 1)
|
||||
#define ns2cycle(ns, clk) (int)(((ns) * (clk / 1000000) / 1000) - 1)
|
||||
|
||||
static void pxa3xx_nand_set_timing(struct pxa3xx_nand_info *info,
|
||||
const struct pxa3xx_nand_timing *t)
|
||||
@ -368,14 +368,14 @@ static int prepare_read_prog_cmd(struct pxa3xx_nand_info *info,
|
||||
/* large block, 2 cycles for column address
|
||||
* row address starts from 3rd cycle
|
||||
*/
|
||||
info->ndcb1 |= (page_addr << 16) | (column & 0xffff);
|
||||
info->ndcb1 |= page_addr << 16;
|
||||
if (info->row_addr_cycles == 3)
|
||||
info->ndcb2 = (page_addr >> 16) & 0xff;
|
||||
} else
|
||||
/* small block, 1 cycles for column address
|
||||
* row address starts from 2nd cycle
|
||||
*/
|
||||
info->ndcb1 = (page_addr << 8) | (column & 0xff);
|
||||
info->ndcb1 = page_addr << 8;
|
||||
|
||||
if (cmd == cmdset->program)
|
||||
info->ndcb0 |= NDCB0_CMD_TYPE(1) | NDCB0_AUTO_RS;
|
||||
|
@ -2,6 +2,7 @@
|
||||
* drivers/mtd/nand/sharpsl.c
|
||||
*
|
||||
* Copyright (C) 2004 Richard Purdie
|
||||
* Copyright (C) 2008 Dmitry Baryshkov
|
||||
*
|
||||
* Based on Sharp's NAND driver sharp_sl.c
|
||||
*
|
||||
@ -19,22 +20,31 @@
|
||||
#include <linux/mtd/nand.h>
|
||||
#include <linux/mtd/nand_ecc.h>
|
||||
#include <linux/mtd/partitions.h>
|
||||
#include <linux/mtd/sharpsl.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/platform_device.h>
|
||||
|
||||
#include <asm/io.h>
|
||||
#include <mach/hardware.h>
|
||||
#include <asm/mach-types.h>
|
||||
|
||||
static void __iomem *sharpsl_io_base;
|
||||
static int sharpsl_phys_base = 0x0C000000;
|
||||
struct sharpsl_nand {
|
||||
struct mtd_info mtd;
|
||||
struct nand_chip chip;
|
||||
|
||||
void __iomem *io;
|
||||
};
|
||||
|
||||
#define mtd_to_sharpsl(_mtd) container_of(_mtd, struct sharpsl_nand, mtd)
|
||||
|
||||
/* register offset */
|
||||
#define ECCLPLB sharpsl_io_base+0x00 /* line parity 7 - 0 bit */
|
||||
#define ECCLPUB sharpsl_io_base+0x04 /* line parity 15 - 8 bit */
|
||||
#define ECCCP sharpsl_io_base+0x08 /* column parity 5 - 0 bit */
|
||||
#define ECCCNTR sharpsl_io_base+0x0C /* ECC byte counter */
|
||||
#define ECCCLRR sharpsl_io_base+0x10 /* cleare ECC */
|
||||
#define FLASHIO sharpsl_io_base+0x14 /* Flash I/O */
|
||||
#define FLASHCTL sharpsl_io_base+0x18 /* Flash Control */
|
||||
#define ECCLPLB 0x00 /* line parity 7 - 0 bit */
|
||||
#define ECCLPUB 0x04 /* line parity 15 - 8 bit */
|
||||
#define ECCCP 0x08 /* column parity 5 - 0 bit */
|
||||
#define ECCCNTR 0x0C /* ECC byte counter */
|
||||
#define ECCCLRR 0x10 /* cleare ECC */
|
||||
#define FLASHIO 0x14 /* Flash I/O */
|
||||
#define FLASHCTL 0x18 /* Flash Control */
|
||||
|
||||
/* Flash control bit */
|
||||
#define FLRYBY (1 << 5)
|
||||
@ -44,35 +54,6 @@ static int sharpsl_phys_base = 0x0C000000;
|
||||
#define FLCLE (1 << 1)
|
||||
#define FLCE0 (1 << 0)
|
||||
|
||||
/*
|
||||
* MTD structure for SharpSL
|
||||
*/
|
||||
static struct mtd_info *sharpsl_mtd = NULL;
|
||||
|
||||
/*
|
||||
* Define partitions for flash device
|
||||
*/
|
||||
#define DEFAULT_NUM_PARTITIONS 3
|
||||
|
||||
static int nr_partitions;
|
||||
static struct mtd_partition sharpsl_nand_default_partition_info[] = {
|
||||
{
|
||||
.name = "System Area",
|
||||
.offset = 0,
|
||||
.size = 7 * 1024 * 1024,
|
||||
},
|
||||
{
|
||||
.name = "Root Filesystem",
|
||||
.offset = 7 * 1024 * 1024,
|
||||
.size = 30 * 1024 * 1024,
|
||||
},
|
||||
{
|
||||
.name = "Home Filesystem",
|
||||
.offset = MTDPART_OFS_APPEND,
|
||||
.size = MTDPART_SIZ_FULL,
|
||||
},
|
||||
};
|
||||
|
||||
/*
|
||||
* hardware specific access to control-lines
|
||||
* ctrl:
|
||||
@ -84,6 +65,7 @@ static struct mtd_partition sharpsl_nand_default_partition_info[] = {
|
||||
static void sharpsl_nand_hwcontrol(struct mtd_info *mtd, int cmd,
|
||||
unsigned int ctrl)
|
||||
{
|
||||
struct sharpsl_nand *sharpsl = mtd_to_sharpsl(mtd);
|
||||
struct nand_chip *chip = mtd->priv;
|
||||
|
||||
if (ctrl & NAND_CTRL_CHANGE) {
|
||||
@ -93,103 +75,97 @@ static void sharpsl_nand_hwcontrol(struct mtd_info *mtd, int cmd,
|
||||
|
||||
bits ^= 0x11;
|
||||
|
||||
writeb((readb(FLASHCTL) & ~0x17) | bits, FLASHCTL);
|
||||
writeb((readb(sharpsl->io + FLASHCTL) & ~0x17) | bits, sharpsl->io + FLASHCTL);
|
||||
}
|
||||
|
||||
if (cmd != NAND_CMD_NONE)
|
||||
writeb(cmd, chip->IO_ADDR_W);
|
||||
}
|
||||
|
||||
static uint8_t scan_ff_pattern[] = { 0xff, 0xff };
|
||||
|
||||
static struct nand_bbt_descr sharpsl_bbt = {
|
||||
.options = 0,
|
||||
.offs = 4,
|
||||
.len = 2,
|
||||
.pattern = scan_ff_pattern
|
||||
};
|
||||
|
||||
static struct nand_bbt_descr sharpsl_akita_bbt = {
|
||||
.options = 0,
|
||||
.offs = 4,
|
||||
.len = 1,
|
||||
.pattern = scan_ff_pattern
|
||||
};
|
||||
|
||||
static struct nand_ecclayout akita_oobinfo = {
|
||||
.eccbytes = 24,
|
||||
.eccpos = {
|
||||
0x5, 0x1, 0x2, 0x3, 0x6, 0x7, 0x15, 0x11,
|
||||
0x12, 0x13, 0x16, 0x17, 0x25, 0x21, 0x22, 0x23,
|
||||
0x26, 0x27, 0x35, 0x31, 0x32, 0x33, 0x36, 0x37},
|
||||
.oobfree = {{0x08, 0x09}}
|
||||
};
|
||||
|
||||
static int sharpsl_nand_dev_ready(struct mtd_info *mtd)
|
||||
{
|
||||
return !((readb(FLASHCTL) & FLRYBY) == 0);
|
||||
struct sharpsl_nand *sharpsl = mtd_to_sharpsl(mtd);
|
||||
return !((readb(sharpsl->io + FLASHCTL) & FLRYBY) == 0);
|
||||
}
|
||||
|
||||
static void sharpsl_nand_enable_hwecc(struct mtd_info *mtd, int mode)
|
||||
{
|
||||
writeb(0, ECCCLRR);
|
||||
struct sharpsl_nand *sharpsl = mtd_to_sharpsl(mtd);
|
||||
writeb(0, sharpsl->io + ECCCLRR);
|
||||
}
|
||||
|
||||
static int sharpsl_nand_calculate_ecc(struct mtd_info *mtd, const u_char * dat, u_char * ecc_code)
|
||||
{
|
||||
ecc_code[0] = ~readb(ECCLPUB);
|
||||
ecc_code[1] = ~readb(ECCLPLB);
|
||||
ecc_code[2] = (~readb(ECCCP) << 2) | 0x03;
|
||||
return readb(ECCCNTR) != 0;
|
||||
struct sharpsl_nand *sharpsl = mtd_to_sharpsl(mtd);
|
||||
ecc_code[0] = ~readb(sharpsl->io + ECCLPUB);
|
||||
ecc_code[1] = ~readb(sharpsl->io + ECCLPLB);
|
||||
ecc_code[2] = (~readb(sharpsl->io + ECCCP) << 2) | 0x03;
|
||||
return readb(sharpsl->io + ECCCNTR) != 0;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_MTD_PARTITIONS
|
||||
const char *part_probes[] = { "cmdlinepart", NULL };
|
||||
static const char *part_probes[] = { "cmdlinepart", NULL };
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Main initialization routine
|
||||
*/
|
||||
static int __init sharpsl_nand_init(void)
|
||||
static int __devinit sharpsl_nand_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct nand_chip *this;
|
||||
#ifdef CONFIG_MTD_PARTITIONS
|
||||
struct mtd_partition *sharpsl_partition_info;
|
||||
int nr_partitions;
|
||||
#endif
|
||||
struct resource *r;
|
||||
int err = 0;
|
||||
struct sharpsl_nand *sharpsl;
|
||||
struct sharpsl_nand_platform_data *data = pdev->dev.platform_data;
|
||||
|
||||
if (!data) {
|
||||
dev_err(&pdev->dev, "no platform data!\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* Allocate memory for MTD device structure and private data */
|
||||
sharpsl_mtd = kmalloc(sizeof(struct mtd_info) + sizeof(struct nand_chip), GFP_KERNEL);
|
||||
if (!sharpsl_mtd) {
|
||||
sharpsl = kzalloc(sizeof(struct sharpsl_nand), GFP_KERNEL);
|
||||
if (!sharpsl) {
|
||||
printk("Unable to allocate SharpSL NAND MTD device structure.\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
if (!r) {
|
||||
dev_err(&pdev->dev, "no io memory resource defined!\n");
|
||||
err = -ENODEV;
|
||||
goto err_get_res;
|
||||
}
|
||||
|
||||
/* map physical address */
|
||||
sharpsl_io_base = ioremap(sharpsl_phys_base, 0x1000);
|
||||
if (!sharpsl_io_base) {
|
||||
sharpsl->io = ioremap(r->start, resource_size(r));
|
||||
if (!sharpsl->io) {
|
||||
printk("ioremap to access Sharp SL NAND chip failed\n");
|
||||
kfree(sharpsl_mtd);
|
||||
return -EIO;
|
||||
err = -EIO;
|
||||
goto err_ioremap;
|
||||
}
|
||||
|
||||
/* Get pointer to private data */
|
||||
this = (struct nand_chip *)(&sharpsl_mtd[1]);
|
||||
|
||||
/* Initialize structures */
|
||||
memset(sharpsl_mtd, 0, sizeof(struct mtd_info));
|
||||
memset(this, 0, sizeof(struct nand_chip));
|
||||
this = (struct nand_chip *)(&sharpsl->chip);
|
||||
|
||||
/* Link the private data with the MTD structure */
|
||||
sharpsl_mtd->priv = this;
|
||||
sharpsl_mtd->owner = THIS_MODULE;
|
||||
sharpsl->mtd.priv = this;
|
||||
sharpsl->mtd.owner = THIS_MODULE;
|
||||
|
||||
platform_set_drvdata(pdev, sharpsl);
|
||||
|
||||
/*
|
||||
* PXA initialize
|
||||
*/
|
||||
writeb(readb(FLASHCTL) | FLWP, FLASHCTL);
|
||||
writeb(readb(sharpsl->io + FLASHCTL) | FLWP, sharpsl->io + FLASHCTL);
|
||||
|
||||
/* Set address of NAND IO lines */
|
||||
this->IO_ADDR_R = FLASHIO;
|
||||
this->IO_ADDR_W = FLASHIO;
|
||||
this->IO_ADDR_R = sharpsl->io + FLASHIO;
|
||||
this->IO_ADDR_W = sharpsl->io + FLASHIO;
|
||||
/* Set address of hardware control function */
|
||||
this->cmd_ctrl = sharpsl_nand_hwcontrol;
|
||||
this->dev_ready = sharpsl_nand_dev_ready;
|
||||
@ -199,68 +175,89 @@ static int __init sharpsl_nand_init(void)
|
||||
this->ecc.mode = NAND_ECC_HW;
|
||||
this->ecc.size = 256;
|
||||
this->ecc.bytes = 3;
|
||||
this->badblock_pattern = &sharpsl_bbt;
|
||||
if (machine_is_akita() || machine_is_borzoi()) {
|
||||
this->badblock_pattern = &sharpsl_akita_bbt;
|
||||
this->ecc.layout = &akita_oobinfo;
|
||||
}
|
||||
this->badblock_pattern = data->badblock_pattern;
|
||||
this->ecc.layout = data->ecc_layout;
|
||||
this->ecc.hwctl = sharpsl_nand_enable_hwecc;
|
||||
this->ecc.calculate = sharpsl_nand_calculate_ecc;
|
||||
this->ecc.correct = nand_correct_data;
|
||||
|
||||
/* Scan to find existence of the device */
|
||||
err = nand_scan(sharpsl_mtd, 1);
|
||||
if (err) {
|
||||
iounmap(sharpsl_io_base);
|
||||
kfree(sharpsl_mtd);
|
||||
return err;
|
||||
}
|
||||
err = nand_scan(&sharpsl->mtd, 1);
|
||||
if (err)
|
||||
goto err_scan;
|
||||
|
||||
/* Register the partitions */
|
||||
sharpsl_mtd->name = "sharpsl-nand";
|
||||
nr_partitions = parse_mtd_partitions(sharpsl_mtd, part_probes, &sharpsl_partition_info, 0);
|
||||
|
||||
sharpsl->mtd.name = "sharpsl-nand";
|
||||
#ifdef CONFIG_MTD_PARTITIONS
|
||||
nr_partitions = parse_mtd_partitions(&sharpsl->mtd, part_probes, &sharpsl_partition_info, 0);
|
||||
if (nr_partitions <= 0) {
|
||||
nr_partitions = DEFAULT_NUM_PARTITIONS;
|
||||
sharpsl_partition_info = sharpsl_nand_default_partition_info;
|
||||
if (machine_is_poodle()) {
|
||||
sharpsl_partition_info[1].size = 22 * 1024 * 1024;
|
||||
} else if (machine_is_corgi() || machine_is_shepherd()) {
|
||||
sharpsl_partition_info[1].size = 25 * 1024 * 1024;
|
||||
} else if (machine_is_husky()) {
|
||||
sharpsl_partition_info[1].size = 53 * 1024 * 1024;
|
||||
} else if (machine_is_spitz()) {
|
||||
sharpsl_partition_info[1].size = 5 * 1024 * 1024;
|
||||
} else if (machine_is_akita()) {
|
||||
sharpsl_partition_info[1].size = 58 * 1024 * 1024;
|
||||
} else if (machine_is_borzoi()) {
|
||||
sharpsl_partition_info[1].size = 32 * 1024 * 1024;
|
||||
}
|
||||
nr_partitions = data->nr_partitions;
|
||||
sharpsl_partition_info = data->partitions;
|
||||
}
|
||||
|
||||
add_mtd_partitions(sharpsl_mtd, sharpsl_partition_info, nr_partitions);
|
||||
if (nr_partitions > 0)
|
||||
err = add_mtd_partitions(&sharpsl->mtd, sharpsl_partition_info, nr_partitions);
|
||||
else
|
||||
#endif
|
||||
err = add_mtd_device(&sharpsl->mtd);
|
||||
if (err)
|
||||
goto err_add;
|
||||
|
||||
/* Return happy */
|
||||
return 0;
|
||||
}
|
||||
|
||||
module_init(sharpsl_nand_init);
|
||||
err_add:
|
||||
nand_release(&sharpsl->mtd);
|
||||
|
||||
err_scan:
|
||||
platform_set_drvdata(pdev, NULL);
|
||||
iounmap(sharpsl->io);
|
||||
err_ioremap:
|
||||
err_get_res:
|
||||
kfree(sharpsl);
|
||||
return err;
|
||||
}
|
||||
|
||||
/*
|
||||
* Clean up routine
|
||||
*/
|
||||
static void __exit sharpsl_nand_cleanup(void)
|
||||
static int __devexit sharpsl_nand_remove(struct platform_device *pdev)
|
||||
{
|
||||
/* Release resources, unregister device */
|
||||
nand_release(sharpsl_mtd);
|
||||
struct sharpsl_nand *sharpsl = platform_get_drvdata(pdev);
|
||||
|
||||
iounmap(sharpsl_io_base);
|
||||
/* Release resources, unregister device */
|
||||
nand_release(&sharpsl->mtd);
|
||||
|
||||
platform_set_drvdata(pdev, NULL);
|
||||
|
||||
iounmap(sharpsl->io);
|
||||
|
||||
/* Free the MTD device structure */
|
||||
kfree(sharpsl_mtd);
|
||||
kfree(sharpsl);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
module_exit(sharpsl_nand_cleanup);
|
||||
static struct platform_driver sharpsl_nand_driver = {
|
||||
.driver = {
|
||||
.name = "sharpsl-nand",
|
||||
.owner = THIS_MODULE,
|
||||
},
|
||||
.probe = sharpsl_nand_probe,
|
||||
.remove = __devexit_p(sharpsl_nand_remove),
|
||||
};
|
||||
|
||||
static int __init sharpsl_nand_init(void)
|
||||
{
|
||||
return platform_driver_register(&sharpsl_nand_driver);
|
||||
}
|
||||
module_init(sharpsl_nand_init);
|
||||
|
||||
static void __exit sharpsl_nand_exit(void)
|
||||
{
|
||||
platform_driver_unregister(&sharpsl_nand_driver);
|
||||
}
|
||||
module_exit(sharpsl_nand_exit);
|
||||
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_AUTHOR("Richard Purdie <rpurdie@rpsys.net>");
|
||||
|
@ -39,7 +39,7 @@ static void nftl_add_mtd(struct mtd_blktrans_ops *tr, struct mtd_info *mtd)
|
||||
struct NFTLrecord *nftl;
|
||||
unsigned long temp;
|
||||
|
||||
if (mtd->type != MTD_NANDFLASH)
|
||||
if (mtd->type != MTD_NANDFLASH || mtd->size > UINT_MAX)
|
||||
return;
|
||||
/* OK, this is moderately ugly. But probably safe. Alternatives? */
|
||||
if (memcmp(mtd->name, "DiskOnChip", 10))
|
||||
|
@ -51,7 +51,7 @@ static int find_boot_record(struct NFTLrecord *nftl)
|
||||
the mtd device accordingly. We could even get rid of
|
||||
nftl->EraseSize if there were any point in doing so. */
|
||||
nftl->EraseSize = nftl->mbd.mtd->erasesize;
|
||||
nftl->nb_blocks = nftl->mbd.mtd->size / nftl->EraseSize;
|
||||
nftl->nb_blocks = (u32)nftl->mbd.mtd->size / nftl->EraseSize;
|
||||
|
||||
nftl->MediaUnit = BLOCK_NIL;
|
||||
nftl->SpareMediaUnit = BLOCK_NIL;
|
||||
@ -168,7 +168,7 @@ device is already correct.
|
||||
printk(KERN_NOTICE "WARNING: Support for NFTL with UnitSizeFactor 0x%02x is experimental\n",
|
||||
mh->UnitSizeFactor);
|
||||
nftl->EraseSize = nftl->mbd.mtd->erasesize << (0xff - mh->UnitSizeFactor);
|
||||
nftl->nb_blocks = nftl->mbd.mtd->size / nftl->EraseSize;
|
||||
nftl->nb_blocks = (u32)nftl->mbd.mtd->size / nftl->EraseSize;
|
||||
}
|
||||
#endif
|
||||
nftl->nb_boot_blocks = le16_to_cpu(mh->FirstPhysicalEUN);
|
||||
|
@ -1772,7 +1772,7 @@ static int onenand_erase(struct mtd_info *mtd, struct erase_info *instr)
|
||||
int len;
|
||||
int ret = 0;
|
||||
|
||||
DEBUG(MTD_DEBUG_LEVEL3, "onenand_erase: start = 0x%08x, len = %i\n", (unsigned int) instr->addr, (unsigned int) instr->len);
|
||||
DEBUG(MTD_DEBUG_LEVEL3, "onenand_erase: start = 0x%012llx, len = %llu\n", (unsigned long long) instr->addr, (unsigned long long) instr->len);
|
||||
|
||||
block_size = (1 << this->erase_shift);
|
||||
|
||||
@ -1810,7 +1810,7 @@ static int onenand_erase(struct mtd_info *mtd, struct erase_info *instr)
|
||||
|
||||
/* Check if we have a bad block, we do not erase bad blocks */
|
||||
if (onenand_block_isbad_nolock(mtd, addr, 0)) {
|
||||
printk (KERN_WARNING "onenand_erase: attempt to erase a bad block at addr 0x%08x\n", (unsigned int) addr);
|
||||
printk (KERN_WARNING "onenand_erase: attempt to erase a bad block at addr 0x%012llx\n", (unsigned long long) addr);
|
||||
instr->state = MTD_ERASE_FAILED;
|
||||
goto erase_exit;
|
||||
}
|
||||
@ -2029,7 +2029,7 @@ static int onenand_do_lock_cmd(struct mtd_info *mtd, loff_t ofs, size_t len, int
|
||||
*
|
||||
* Lock one or more blocks
|
||||
*/
|
||||
static int onenand_lock(struct mtd_info *mtd, loff_t ofs, size_t len)
|
||||
static int onenand_lock(struct mtd_info *mtd, loff_t ofs, uint64_t len)
|
||||
{
|
||||
int ret;
|
||||
|
||||
@ -2047,7 +2047,7 @@ static int onenand_lock(struct mtd_info *mtd, loff_t ofs, size_t len)
|
||||
*
|
||||
* Unlock one or more blocks
|
||||
*/
|
||||
static int onenand_unlock(struct mtd_info *mtd, loff_t ofs, size_t len)
|
||||
static int onenand_unlock(struct mtd_info *mtd, loff_t ofs, uint64_t len)
|
||||
{
|
||||
int ret;
|
||||
|
||||
|
@ -21,8 +21,6 @@
|
||||
|
||||
#include <asm/types.h>
|
||||
|
||||
#define const_cpu_to_le16 __constant_cpu_to_le16
|
||||
|
||||
static int block_size = 0;
|
||||
module_param(block_size, int, 0);
|
||||
MODULE_PARM_DESC(block_size, "Block size to use by RFD, defaults to erase unit size");
|
||||
@ -156,7 +154,7 @@ static int scan_header(struct partition *part)
|
||||
size_t retlen;
|
||||
|
||||
sectors_per_block = part->block_size / SECTOR_SIZE;
|
||||
part->total_blocks = part->mbd.mtd->size / part->block_size;
|
||||
part->total_blocks = (u32)part->mbd.mtd->size / part->block_size;
|
||||
|
||||
if (part->total_blocks < 2)
|
||||
return -ENOENT;
|
||||
@ -276,16 +274,17 @@ static void erase_callback(struct erase_info *erase)
|
||||
|
||||
part = (struct partition*)erase->priv;
|
||||
|
||||
i = erase->addr / part->block_size;
|
||||
if (i >= part->total_blocks || part->blocks[i].offset != erase->addr) {
|
||||
printk(KERN_ERR PREFIX "erase callback for unknown offset %x "
|
||||
"on '%s'\n", erase->addr, part->mbd.mtd->name);
|
||||
i = (u32)erase->addr / part->block_size;
|
||||
if (i >= part->total_blocks || part->blocks[i].offset != erase->addr ||
|
||||
erase->addr > UINT_MAX) {
|
||||
printk(KERN_ERR PREFIX "erase callback for unknown offset %llx "
|
||||
"on '%s'\n", (unsigned long long)erase->addr, part->mbd.mtd->name);
|
||||
return;
|
||||
}
|
||||
|
||||
if (erase->state != MTD_ERASE_DONE) {
|
||||
printk(KERN_WARNING PREFIX "erase failed at 0x%x on '%s', "
|
||||
"state %d\n", erase->addr,
|
||||
printk(KERN_WARNING PREFIX "erase failed at 0x%llx on '%s', "
|
||||
"state %d\n", (unsigned long long)erase->addr,
|
||||
part->mbd.mtd->name, erase->state);
|
||||
|
||||
part->blocks[i].state = BLOCK_FAILED;
|
||||
@ -297,7 +296,7 @@ static void erase_callback(struct erase_info *erase)
|
||||
return;
|
||||
}
|
||||
|
||||
magic = const_cpu_to_le16(RFD_MAGIC);
|
||||
magic = cpu_to_le16(RFD_MAGIC);
|
||||
|
||||
part->blocks[i].state = BLOCK_ERASED;
|
||||
part->blocks[i].free_sectors = part->data_sectors_per_block;
|
||||
@ -345,9 +344,9 @@ static int erase_block(struct partition *part, int block)
|
||||
rc = part->mbd.mtd->erase(part->mbd.mtd, erase);
|
||||
|
||||
if (rc) {
|
||||
printk(KERN_ERR PREFIX "erase of region %x,%x on '%s' "
|
||||
"failed\n", erase->addr, erase->len,
|
||||
part->mbd.mtd->name);
|
||||
printk(KERN_ERR PREFIX "erase of region %llx,%llx on '%s' "
|
||||
"failed\n", (unsigned long long)erase->addr,
|
||||
(unsigned long long)erase->len, part->mbd.mtd->name);
|
||||
kfree(erase);
|
||||
}
|
||||
|
||||
@ -587,7 +586,7 @@ static int mark_sector_deleted(struct partition *part, u_long old_addr)
|
||||
int block, offset, rc;
|
||||
u_long addr;
|
||||
size_t retlen;
|
||||
u16 del = const_cpu_to_le16(SECTOR_DELETED);
|
||||
u16 del = cpu_to_le16(SECTOR_DELETED);
|
||||
|
||||
block = old_addr / part->block_size;
|
||||
offset = (old_addr % part->block_size) / SECTOR_SIZE -
|
||||
@ -763,7 +762,7 @@ static void rfd_ftl_add_mtd(struct mtd_blktrans_ops *tr, struct mtd_info *mtd)
|
||||
{
|
||||
struct partition *part;
|
||||
|
||||
if (mtd->type != MTD_NORFLASH)
|
||||
if (mtd->type != MTD_NORFLASH || mtd->size > UINT_MAX)
|
||||
return;
|
||||
|
||||
part = kzalloc(sizeof(struct partition), GFP_KERNEL);
|
||||
|
@ -294,7 +294,8 @@ static void ssfdcr_add_mtd(struct mtd_blktrans_ops *tr, struct mtd_info *mtd)
|
||||
int cis_sector;
|
||||
|
||||
/* Check for small page NAND flash */
|
||||
if (mtd->type != MTD_NANDFLASH || mtd->oobsize != OOB_SIZE)
|
||||
if (mtd->type != MTD_NANDFLASH || mtd->oobsize != OOB_SIZE ||
|
||||
mtd->size > UINT_MAX)
|
||||
return;
|
||||
|
||||
/* Check for SSDFC format by reading CIS/IDI sector */
|
||||
@ -316,7 +317,7 @@ static void ssfdcr_add_mtd(struct mtd_blktrans_ops *tr, struct mtd_info *mtd)
|
||||
|
||||
ssfdc->cis_block = cis_sector / (mtd->erasesize >> SECTOR_SHIFT);
|
||||
ssfdc->erase_size = mtd->erasesize;
|
||||
ssfdc->map_len = mtd->size / mtd->erasesize;
|
||||
ssfdc->map_len = (u32)mtd->size / mtd->erasesize;
|
||||
|
||||
DEBUG(MTD_DEBUG_LEVEL1,
|
||||
"SSFDC_RO: cis_block=%d,erase_size=%d,map_len=%d,n_zones=%d\n",
|
||||
@ -327,7 +328,7 @@ static void ssfdcr_add_mtd(struct mtd_blktrans_ops *tr, struct mtd_info *mtd)
|
||||
ssfdc->heads = 16;
|
||||
ssfdc->sectors = 32;
|
||||
get_chs(mtd->size, NULL, &ssfdc->heads, &ssfdc->sectors);
|
||||
ssfdc->cylinders = (unsigned short)((mtd->size >> SECTOR_SHIFT) /
|
||||
ssfdc->cylinders = (unsigned short)(((u32)mtd->size >> SECTOR_SHIFT) /
|
||||
((long)ssfdc->sectors * (long)ssfdc->heads));
|
||||
|
||||
DEBUG(MTD_DEBUG_LEVEL1, "SSFDC_RO: using C:%d H:%d S:%d == %ld sects\n",
|
||||
|
7
drivers/mtd/tests/Makefile
Normal file
7
drivers/mtd/tests/Makefile
Normal file
@ -0,0 +1,7 @@
|
||||
obj-$(CONFIG_MTD_TESTS) += mtd_oobtest.o
|
||||
obj-$(CONFIG_MTD_TESTS) += mtd_pagetest.o
|
||||
obj-$(CONFIG_MTD_TESTS) += mtd_readtest.o
|
||||
obj-$(CONFIG_MTD_TESTS) += mtd_speedtest.o
|
||||
obj-$(CONFIG_MTD_TESTS) += mtd_stresstest.o
|
||||
obj-$(CONFIG_MTD_TESTS) += mtd_subpagetest.o
|
||||
obj-$(CONFIG_MTD_TESTS) += mtd_torturetest.o
|
742
drivers/mtd/tests/mtd_oobtest.c
Normal file
742
drivers/mtd/tests/mtd_oobtest.c
Normal file
@ -0,0 +1,742 @@
|
||||
/*
|
||||
* Copyright (C) 2006-2008 Nokia Corporation
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 as published by
|
||||
* the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program; see the file COPYING. If not, write to the Free Software
|
||||
* Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
*
|
||||
* Test OOB read and write on MTD device.
|
||||
*
|
||||
* Author: Adrian Hunter <ext-adrian.hunter@nokia.com>
|
||||
*/
|
||||
|
||||
#include <asm/div64.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/moduleparam.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/mtd/mtd.h>
|
||||
#include <linux/sched.h>
|
||||
|
||||
#define PRINT_PREF KERN_INFO "mtd_oobtest: "
|
||||
|
||||
static int dev;
|
||||
module_param(dev, int, S_IRUGO);
|
||||
MODULE_PARM_DESC(dev, "MTD device number to use");
|
||||
|
||||
static struct mtd_info *mtd;
|
||||
static unsigned char *readbuf;
|
||||
static unsigned char *writebuf;
|
||||
static unsigned char *bbt;
|
||||
|
||||
static int ebcnt;
|
||||
static int pgcnt;
|
||||
static int errcnt;
|
||||
static int use_offset;
|
||||
static int use_len;
|
||||
static int use_len_max;
|
||||
static int vary_offset;
|
||||
static unsigned long next = 1;
|
||||
|
||||
static inline unsigned int simple_rand(void)
|
||||
{
|
||||
next = next * 1103515245 + 12345;
|
||||
return (unsigned int)((next / 65536) % 32768);
|
||||
}
|
||||
|
||||
static inline void simple_srand(unsigned long seed)
|
||||
{
|
||||
next = seed;
|
||||
}
|
||||
|
||||
static void set_random_data(unsigned char *buf, size_t len)
|
||||
{
|
||||
size_t i;
|
||||
|
||||
for (i = 0; i < len; ++i)
|
||||
buf[i] = simple_rand();
|
||||
}
|
||||
|
||||
static int erase_eraseblock(int ebnum)
|
||||
{
|
||||
int err;
|
||||
struct erase_info ei;
|
||||
loff_t addr = ebnum * mtd->erasesize;
|
||||
|
||||
memset(&ei, 0, sizeof(struct erase_info));
|
||||
ei.mtd = mtd;
|
||||
ei.addr = addr;
|
||||
ei.len = mtd->erasesize;
|
||||
|
||||
err = mtd->erase(mtd, &ei);
|
||||
if (err) {
|
||||
printk(PRINT_PREF "error %d while erasing EB %d\n", err, ebnum);
|
||||
return err;
|
||||
}
|
||||
|
||||
if (ei.state == MTD_ERASE_FAILED) {
|
||||
printk(PRINT_PREF "some erase error occurred at EB %d\n",
|
||||
ebnum);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int erase_whole_device(void)
|
||||
{
|
||||
int err;
|
||||
unsigned int i;
|
||||
|
||||
printk(PRINT_PREF "erasing whole device\n");
|
||||
for (i = 0; i < ebcnt; ++i) {
|
||||
if (bbt[i])
|
||||
continue;
|
||||
err = erase_eraseblock(i);
|
||||
if (err)
|
||||
return err;
|
||||
cond_resched();
|
||||
}
|
||||
printk(PRINT_PREF "erased %u eraseblocks\n", i);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void do_vary_offset(void)
|
||||
{
|
||||
use_len -= 1;
|
||||
if (use_len < 1) {
|
||||
use_offset += 1;
|
||||
if (use_offset >= use_len_max)
|
||||
use_offset = 0;
|
||||
use_len = use_len_max - use_offset;
|
||||
}
|
||||
}
|
||||
|
||||
static int write_eraseblock(int ebnum)
|
||||
{
|
||||
int i;
|
||||
struct mtd_oob_ops ops;
|
||||
int err = 0;
|
||||
loff_t addr = ebnum * mtd->erasesize;
|
||||
|
||||
for (i = 0; i < pgcnt; ++i, addr += mtd->writesize) {
|
||||
set_random_data(writebuf, use_len);
|
||||
ops.mode = MTD_OOB_AUTO;
|
||||
ops.len = 0;
|
||||
ops.retlen = 0;
|
||||
ops.ooblen = use_len;
|
||||
ops.oobretlen = 0;
|
||||
ops.ooboffs = use_offset;
|
||||
ops.datbuf = 0;
|
||||
ops.oobbuf = writebuf;
|
||||
err = mtd->write_oob(mtd, addr, &ops);
|
||||
if (err || ops.oobretlen != use_len) {
|
||||
printk(PRINT_PREF "error: writeoob failed at %#llx\n",
|
||||
(long long)addr);
|
||||
printk(PRINT_PREF "error: use_len %d, use_offset %d\n",
|
||||
use_len, use_offset);
|
||||
errcnt += 1;
|
||||
return err ? err : -1;
|
||||
}
|
||||
if (vary_offset)
|
||||
do_vary_offset();
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static int write_whole_device(void)
|
||||
{
|
||||
int err;
|
||||
unsigned int i;
|
||||
|
||||
printk(PRINT_PREF "writing OOBs of whole device\n");
|
||||
for (i = 0; i < ebcnt; ++i) {
|
||||
if (bbt[i])
|
||||
continue;
|
||||
err = write_eraseblock(i);
|
||||
if (err)
|
||||
return err;
|
||||
if (i % 256 == 0)
|
||||
printk(PRINT_PREF "written up to eraseblock %u\n", i);
|
||||
cond_resched();
|
||||
}
|
||||
printk(PRINT_PREF "written %u eraseblocks\n", i);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int verify_eraseblock(int ebnum)
|
||||
{
|
||||
int i;
|
||||
struct mtd_oob_ops ops;
|
||||
int err = 0;
|
||||
loff_t addr = ebnum * mtd->erasesize;
|
||||
|
||||
for (i = 0; i < pgcnt; ++i, addr += mtd->writesize) {
|
||||
set_random_data(writebuf, use_len);
|
||||
ops.mode = MTD_OOB_AUTO;
|
||||
ops.len = 0;
|
||||
ops.retlen = 0;
|
||||
ops.ooblen = use_len;
|
||||
ops.oobretlen = 0;
|
||||
ops.ooboffs = use_offset;
|
||||
ops.datbuf = 0;
|
||||
ops.oobbuf = readbuf;
|
||||
err = mtd->read_oob(mtd, addr, &ops);
|
||||
if (err || ops.oobretlen != use_len) {
|
||||
printk(PRINT_PREF "error: readoob failed at %#llx\n",
|
||||
(long long)addr);
|
||||
errcnt += 1;
|
||||
return err ? err : -1;
|
||||
}
|
||||
if (memcmp(readbuf, writebuf, use_len)) {
|
||||
printk(PRINT_PREF "error: verify failed at %#llx\n",
|
||||
(long long)addr);
|
||||
errcnt += 1;
|
||||
if (errcnt > 1000) {
|
||||
printk(PRINT_PREF "error: too many errors\n");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
if (use_offset != 0 || use_len < mtd->ecclayout->oobavail) {
|
||||
int k;
|
||||
|
||||
ops.mode = MTD_OOB_AUTO;
|
||||
ops.len = 0;
|
||||
ops.retlen = 0;
|
||||
ops.ooblen = mtd->ecclayout->oobavail;
|
||||
ops.oobretlen = 0;
|
||||
ops.ooboffs = 0;
|
||||
ops.datbuf = 0;
|
||||
ops.oobbuf = readbuf;
|
||||
err = mtd->read_oob(mtd, addr, &ops);
|
||||
if (err || ops.oobretlen != mtd->ecclayout->oobavail) {
|
||||
printk(PRINT_PREF "error: readoob failed at "
|
||||
"%#llx\n", (long long)addr);
|
||||
errcnt += 1;
|
||||
return err ? err : -1;
|
||||
}
|
||||
if (memcmp(readbuf + use_offset, writebuf, use_len)) {
|
||||
printk(PRINT_PREF "error: verify failed at "
|
||||
"%#llx\n", (long long)addr);
|
||||
errcnt += 1;
|
||||
if (errcnt > 1000) {
|
||||
printk(PRINT_PREF "error: too many "
|
||||
"errors\n");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
for (k = 0; k < use_offset; ++k)
|
||||
if (readbuf[k] != 0xff) {
|
||||
printk(PRINT_PREF "error: verify 0xff "
|
||||
"failed at %#llx\n",
|
||||
(long long)addr);
|
||||
errcnt += 1;
|
||||
if (errcnt > 1000) {
|
||||
printk(PRINT_PREF "error: too "
|
||||
"many errors\n");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
for (k = use_offset + use_len;
|
||||
k < mtd->ecclayout->oobavail; ++k)
|
||||
if (readbuf[k] != 0xff) {
|
||||
printk(PRINT_PREF "error: verify 0xff "
|
||||
"failed at %#llx\n",
|
||||
(long long)addr);
|
||||
errcnt += 1;
|
||||
if (errcnt > 1000) {
|
||||
printk(PRINT_PREF "error: too "
|
||||
"many errors\n");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (vary_offset)
|
||||
do_vary_offset();
|
||||
}
|
||||
return err;
|
||||
}
|
||||
|
||||
static int verify_eraseblock_in_one_go(int ebnum)
|
||||
{
|
||||
struct mtd_oob_ops ops;
|
||||
int err = 0;
|
||||
loff_t addr = ebnum * mtd->erasesize;
|
||||
size_t len = mtd->ecclayout->oobavail * pgcnt;
|
||||
|
||||
set_random_data(writebuf, len);
|
||||
ops.mode = MTD_OOB_AUTO;
|
||||
ops.len = 0;
|
||||
ops.retlen = 0;
|
||||
ops.ooblen = len;
|
||||
ops.oobretlen = 0;
|
||||
ops.ooboffs = 0;
|
||||
ops.datbuf = 0;
|
||||
ops.oobbuf = readbuf;
|
||||
err = mtd->read_oob(mtd, addr, &ops);
|
||||
if (err || ops.oobretlen != len) {
|
||||
printk(PRINT_PREF "error: readoob failed at %#llx\n",
|
||||
(long long)addr);
|
||||
errcnt += 1;
|
||||
return err ? err : -1;
|
||||
}
|
||||
if (memcmp(readbuf, writebuf, len)) {
|
||||
printk(PRINT_PREF "error: verify failed at %#llx\n",
|
||||
(long long)addr);
|
||||
errcnt += 1;
|
||||
if (errcnt > 1000) {
|
||||
printk(PRINT_PREF "error: too many errors\n");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static int verify_all_eraseblocks(void)
|
||||
{
|
||||
int err;
|
||||
unsigned int i;
|
||||
|
||||
printk(PRINT_PREF "verifying all eraseblocks\n");
|
||||
for (i = 0; i < ebcnt; ++i) {
|
||||
if (bbt[i])
|
||||
continue;
|
||||
err = verify_eraseblock(i);
|
||||
if (err)
|
||||
return err;
|
||||
if (i % 256 == 0)
|
||||
printk(PRINT_PREF "verified up to eraseblock %u\n", i);
|
||||
cond_resched();
|
||||
}
|
||||
printk(PRINT_PREF "verified %u eraseblocks\n", i);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int is_block_bad(int ebnum)
|
||||
{
|
||||
int ret;
|
||||
loff_t addr = ebnum * mtd->erasesize;
|
||||
|
||||
ret = mtd->block_isbad(mtd, addr);
|
||||
if (ret)
|
||||
printk(PRINT_PREF "block %d is bad\n", ebnum);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int scan_for_bad_eraseblocks(void)
|
||||
{
|
||||
int i, bad = 0;
|
||||
|
||||
bbt = kmalloc(ebcnt, GFP_KERNEL);
|
||||
if (!bbt) {
|
||||
printk(PRINT_PREF "error: cannot allocate memory\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
memset(bbt, 0 , ebcnt);
|
||||
|
||||
printk(PRINT_PREF "scanning for bad eraseblocks\n");
|
||||
for (i = 0; i < ebcnt; ++i) {
|
||||
bbt[i] = is_block_bad(i) ? 1 : 0;
|
||||
if (bbt[i])
|
||||
bad += 1;
|
||||
cond_resched();
|
||||
}
|
||||
printk(PRINT_PREF "scanned %d eraseblocks, %d are bad\n", i, bad);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int __init mtd_oobtest_init(void)
|
||||
{
|
||||
int err = 0;
|
||||
unsigned int i;
|
||||
uint64_t tmp;
|
||||
struct mtd_oob_ops ops;
|
||||
loff_t addr = 0, addr0;
|
||||
|
||||
printk(KERN_INFO "\n");
|
||||
printk(KERN_INFO "=================================================\n");
|
||||
printk(PRINT_PREF "MTD device: %d\n", dev);
|
||||
|
||||
mtd = get_mtd_device(NULL, dev);
|
||||
if (IS_ERR(mtd)) {
|
||||
err = PTR_ERR(mtd);
|
||||
printk(PRINT_PREF "error: cannot get MTD device\n");
|
||||
return err;
|
||||
}
|
||||
|
||||
if (mtd->type != MTD_NANDFLASH) {
|
||||
printk(PRINT_PREF "this test requires NAND flash\n");
|
||||
goto out;
|
||||
}
|
||||
|
||||
tmp = mtd->size;
|
||||
do_div(tmp, mtd->erasesize);
|
||||
ebcnt = tmp;
|
||||
pgcnt = mtd->erasesize / mtd->writesize;
|
||||
|
||||
printk(PRINT_PREF "MTD device size %llu, eraseblock size %u, "
|
||||
"page size %u, count of eraseblocks %u, pages per "
|
||||
"eraseblock %u, OOB size %u\n",
|
||||
(unsigned long long)mtd->size, mtd->erasesize,
|
||||
mtd->writesize, ebcnt, pgcnt, mtd->oobsize);
|
||||
|
||||
err = -ENOMEM;
|
||||
mtd->erasesize = mtd->erasesize;
|
||||
readbuf = kmalloc(mtd->erasesize, GFP_KERNEL);
|
||||
if (!readbuf) {
|
||||
printk(PRINT_PREF "error: cannot allocate memory\n");
|
||||
goto out;
|
||||
}
|
||||
writebuf = kmalloc(mtd->erasesize, GFP_KERNEL);
|
||||
if (!writebuf) {
|
||||
printk(PRINT_PREF "error: cannot allocate memory\n");
|
||||
goto out;
|
||||
}
|
||||
|
||||
err = scan_for_bad_eraseblocks();
|
||||
if (err)
|
||||
goto out;
|
||||
|
||||
use_offset = 0;
|
||||
use_len = mtd->ecclayout->oobavail;
|
||||
use_len_max = mtd->ecclayout->oobavail;
|
||||
vary_offset = 0;
|
||||
|
||||
/* First test: write all OOB, read it back and verify */
|
||||
printk(PRINT_PREF "test 1 of 5\n");
|
||||
|
||||
err = erase_whole_device();
|
||||
if (err)
|
||||
goto out;
|
||||
|
||||
simple_srand(1);
|
||||
err = write_whole_device();
|
||||
if (err)
|
||||
goto out;
|
||||
|
||||
simple_srand(1);
|
||||
err = verify_all_eraseblocks();
|
||||
if (err)
|
||||
goto out;
|
||||
|
||||
/*
|
||||
* Second test: write all OOB, a block at a time, read it back and
|
||||
* verify.
|
||||
*/
|
||||
printk(PRINT_PREF "test 2 of 5\n");
|
||||
|
||||
err = erase_whole_device();
|
||||
if (err)
|
||||
goto out;
|
||||
|
||||
simple_srand(3);
|
||||
err = write_whole_device();
|
||||
if (err)
|
||||
goto out;
|
||||
|
||||
/* Check all eraseblocks */
|
||||
simple_srand(3);
|
||||
printk(PRINT_PREF "verifying all eraseblocks\n");
|
||||
for (i = 0; i < ebcnt; ++i) {
|
||||
if (bbt[i])
|
||||
continue;
|
||||
err = verify_eraseblock_in_one_go(i);
|
||||
if (err)
|
||||
goto out;
|
||||
if (i % 256 == 0)
|
||||
printk(PRINT_PREF "verified up to eraseblock %u\n", i);
|
||||
cond_resched();
|
||||
}
|
||||
printk(PRINT_PREF "verified %u eraseblocks\n", i);
|
||||
|
||||
/*
|
||||
* Third test: write OOB at varying offsets and lengths, read it back
|
||||
* and verify.
|
||||
*/
|
||||
printk(PRINT_PREF "test 3 of 5\n");
|
||||
|
||||
err = erase_whole_device();
|
||||
if (err)
|
||||
goto out;
|
||||
|
||||
/* Write all eraseblocks */
|
||||
use_offset = 0;
|
||||
use_len = mtd->ecclayout->oobavail;
|
||||
use_len_max = mtd->ecclayout->oobavail;
|
||||
vary_offset = 1;
|
||||
simple_srand(5);
|
||||
printk(PRINT_PREF "writing OOBs of whole device\n");
|
||||
for (i = 0; i < ebcnt; ++i) {
|
||||
if (bbt[i])
|
||||
continue;
|
||||
err = write_eraseblock(i);
|
||||
if (err)
|
||||
goto out;
|
||||
if (i % 256 == 0)
|
||||
printk(PRINT_PREF "written up to eraseblock %u\n", i);
|
||||
cond_resched();
|
||||
}
|
||||
printk(PRINT_PREF "written %u eraseblocks\n", i);
|
||||
|
||||
/* Check all eraseblocks */
|
||||
use_offset = 0;
|
||||
use_len = mtd->ecclayout->oobavail;
|
||||
use_len_max = mtd->ecclayout->oobavail;
|
||||
vary_offset = 1;
|
||||
simple_srand(5);
|
||||
err = verify_all_eraseblocks();
|
||||
if (err)
|
||||
goto out;
|
||||
|
||||
use_offset = 0;
|
||||
use_len = mtd->ecclayout->oobavail;
|
||||
use_len_max = mtd->ecclayout->oobavail;
|
||||
vary_offset = 0;
|
||||
|
||||
/* Fourth test: try to write off end of device */
|
||||
printk(PRINT_PREF "test 4 of 5\n");
|
||||
|
||||
err = erase_whole_device();
|
||||
if (err)
|
||||
goto out;
|
||||
|
||||
addr0 = 0;
|
||||
for (i = 0; bbt[i] && i < ebcnt; ++i)
|
||||
addr0 += mtd->erasesize;
|
||||
|
||||
/* Attempt to write off end of OOB */
|
||||
ops.mode = MTD_OOB_AUTO;
|
||||
ops.len = 0;
|
||||
ops.retlen = 0;
|
||||
ops.ooblen = 1;
|
||||
ops.oobretlen = 0;
|
||||
ops.ooboffs = mtd->ecclayout->oobavail;
|
||||
ops.datbuf = 0;
|
||||
ops.oobbuf = writebuf;
|
||||
printk(PRINT_PREF "attempting to start write past end of OOB\n");
|
||||
printk(PRINT_PREF "an error is expected...\n");
|
||||
err = mtd->write_oob(mtd, addr0, &ops);
|
||||
if (err) {
|
||||
printk(PRINT_PREF "error occurred as expected\n");
|
||||
err = 0;
|
||||
} else {
|
||||
printk(PRINT_PREF "error: can write past end of OOB\n");
|
||||
errcnt += 1;
|
||||
}
|
||||
|
||||
/* Attempt to read off end of OOB */
|
||||
ops.mode = MTD_OOB_AUTO;
|
||||
ops.len = 0;
|
||||
ops.retlen = 0;
|
||||
ops.ooblen = 1;
|
||||
ops.oobretlen = 0;
|
||||
ops.ooboffs = mtd->ecclayout->oobavail;
|
||||
ops.datbuf = 0;
|
||||
ops.oobbuf = readbuf;
|
||||
printk(PRINT_PREF "attempting to start read past end of OOB\n");
|
||||
printk(PRINT_PREF "an error is expected...\n");
|
||||
err = mtd->read_oob(mtd, addr0, &ops);
|
||||
if (err) {
|
||||
printk(PRINT_PREF "error occurred as expected\n");
|
||||
err = 0;
|
||||
} else {
|
||||
printk(PRINT_PREF "error: can read past end of OOB\n");
|
||||
errcnt += 1;
|
||||
}
|
||||
|
||||
if (bbt[ebcnt - 1])
|
||||
printk(PRINT_PREF "skipping end of device tests because last "
|
||||
"block is bad\n");
|
||||
else {
|
||||
/* Attempt to write off end of device */
|
||||
ops.mode = MTD_OOB_AUTO;
|
||||
ops.len = 0;
|
||||
ops.retlen = 0;
|
||||
ops.ooblen = mtd->ecclayout->oobavail + 1;
|
||||
ops.oobretlen = 0;
|
||||
ops.ooboffs = 0;
|
||||
ops.datbuf = 0;
|
||||
ops.oobbuf = writebuf;
|
||||
printk(PRINT_PREF "attempting to write past end of device\n");
|
||||
printk(PRINT_PREF "an error is expected...\n");
|
||||
err = mtd->write_oob(mtd, mtd->size - mtd->writesize, &ops);
|
||||
if (err) {
|
||||
printk(PRINT_PREF "error occurred as expected\n");
|
||||
err = 0;
|
||||
} else {
|
||||
printk(PRINT_PREF "error: wrote past end of device\n");
|
||||
errcnt += 1;
|
||||
}
|
||||
|
||||
/* Attempt to read off end of device */
|
||||
ops.mode = MTD_OOB_AUTO;
|
||||
ops.len = 0;
|
||||
ops.retlen = 0;
|
||||
ops.ooblen = mtd->ecclayout->oobavail + 1;
|
||||
ops.oobretlen = 0;
|
||||
ops.ooboffs = 0;
|
||||
ops.datbuf = 0;
|
||||
ops.oobbuf = readbuf;
|
||||
printk(PRINT_PREF "attempting to read past end of device\n");
|
||||
printk(PRINT_PREF "an error is expected...\n");
|
||||
err = mtd->read_oob(mtd, mtd->size - mtd->writesize, &ops);
|
||||
if (err) {
|
||||
printk(PRINT_PREF "error occurred as expected\n");
|
||||
err = 0;
|
||||
} else {
|
||||
printk(PRINT_PREF "error: read past end of device\n");
|
||||
errcnt += 1;
|
||||
}
|
||||
|
||||
err = erase_eraseblock(ebcnt - 1);
|
||||
if (err)
|
||||
goto out;
|
||||
|
||||
/* Attempt to write off end of device */
|
||||
ops.mode = MTD_OOB_AUTO;
|
||||
ops.len = 0;
|
||||
ops.retlen = 0;
|
||||
ops.ooblen = mtd->ecclayout->oobavail;
|
||||
ops.oobretlen = 0;
|
||||
ops.ooboffs = 1;
|
||||
ops.datbuf = 0;
|
||||
ops.oobbuf = writebuf;
|
||||
printk(PRINT_PREF "attempting to write past end of device\n");
|
||||
printk(PRINT_PREF "an error is expected...\n");
|
||||
err = mtd->write_oob(mtd, mtd->size - mtd->writesize, &ops);
|
||||
if (err) {
|
||||
printk(PRINT_PREF "error occurred as expected\n");
|
||||
err = 0;
|
||||
} else {
|
||||
printk(PRINT_PREF "error: wrote past end of device\n");
|
||||
errcnt += 1;
|
||||
}
|
||||
|
||||
/* Attempt to read off end of device */
|
||||
ops.mode = MTD_OOB_AUTO;
|
||||
ops.len = 0;
|
||||
ops.retlen = 0;
|
||||
ops.ooblen = mtd->ecclayout->oobavail;
|
||||
ops.oobretlen = 0;
|
||||
ops.ooboffs = 1;
|
||||
ops.datbuf = 0;
|
||||
ops.oobbuf = readbuf;
|
||||
printk(PRINT_PREF "attempting to read past end of device\n");
|
||||
printk(PRINT_PREF "an error is expected...\n");
|
||||
err = mtd->read_oob(mtd, mtd->size - mtd->writesize, &ops);
|
||||
if (err) {
|
||||
printk(PRINT_PREF "error occurred as expected\n");
|
||||
err = 0;
|
||||
} else {
|
||||
printk(PRINT_PREF "error: read past end of device\n");
|
||||
errcnt += 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* Fifth test: write / read across block boundaries */
|
||||
printk(PRINT_PREF "test 5 of 5\n");
|
||||
|
||||
/* Erase all eraseblocks */
|
||||
err = erase_whole_device();
|
||||
if (err)
|
||||
goto out;
|
||||
|
||||
/* Write all eraseblocks */
|
||||
simple_srand(11);
|
||||
printk(PRINT_PREF "writing OOBs of whole device\n");
|
||||
for (i = 0; i < ebcnt - 1; ++i) {
|
||||
int cnt = 2;
|
||||
int pg;
|
||||
size_t sz = mtd->ecclayout->oobavail;
|
||||
if (bbt[i] || bbt[i + 1])
|
||||
continue;
|
||||
addr = (i + 1) * mtd->erasesize - mtd->writesize;
|
||||
for (pg = 0; pg < cnt; ++pg) {
|
||||
set_random_data(writebuf, sz);
|
||||
ops.mode = MTD_OOB_AUTO;
|
||||
ops.len = 0;
|
||||
ops.retlen = 0;
|
||||
ops.ooblen = sz;
|
||||
ops.oobretlen = 0;
|
||||
ops.ooboffs = 0;
|
||||
ops.datbuf = 0;
|
||||
ops.oobbuf = writebuf;
|
||||
err = mtd->write_oob(mtd, addr, &ops);
|
||||
if (err)
|
||||
goto out;
|
||||
if (i % 256 == 0)
|
||||
printk(PRINT_PREF "written up to eraseblock "
|
||||
"%u\n", i);
|
||||
cond_resched();
|
||||
addr += mtd->writesize;
|
||||
}
|
||||
}
|
||||
printk(PRINT_PREF "written %u eraseblocks\n", i);
|
||||
|
||||
/* Check all eraseblocks */
|
||||
simple_srand(11);
|
||||
printk(PRINT_PREF "verifying all eraseblocks\n");
|
||||
for (i = 0; i < ebcnt - 1; ++i) {
|
||||
if (bbt[i] || bbt[i + 1])
|
||||
continue;
|
||||
set_random_data(writebuf, mtd->ecclayout->oobavail * 2);
|
||||
addr = (i + 1) * mtd->erasesize - mtd->writesize;
|
||||
ops.mode = MTD_OOB_AUTO;
|
||||
ops.len = 0;
|
||||
ops.retlen = 0;
|
||||
ops.ooblen = mtd->ecclayout->oobavail * 2;
|
||||
ops.oobretlen = 0;
|
||||
ops.ooboffs = 0;
|
||||
ops.datbuf = 0;
|
||||
ops.oobbuf = readbuf;
|
||||
err = mtd->read_oob(mtd, addr, &ops);
|
||||
if (err)
|
||||
goto out;
|
||||
if (memcmp(readbuf, writebuf, mtd->ecclayout->oobavail * 2)) {
|
||||
printk(PRINT_PREF "error: verify failed at %#llx\n",
|
||||
(long long)addr);
|
||||
errcnt += 1;
|
||||
if (errcnt > 1000) {
|
||||
printk(PRINT_PREF "error: too many errors\n");
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
if (i % 256 == 0)
|
||||
printk(PRINT_PREF "verified up to eraseblock %u\n", i);
|
||||
cond_resched();
|
||||
}
|
||||
printk(PRINT_PREF "verified %u eraseblocks\n", i);
|
||||
|
||||
printk(PRINT_PREF "finished with %d errors\n", errcnt);
|
||||
out:
|
||||
kfree(bbt);
|
||||
kfree(writebuf);
|
||||
kfree(readbuf);
|
||||
put_mtd_device(mtd);
|
||||
if (err)
|
||||
printk(PRINT_PREF "error %d occurred\n", err);
|
||||
printk(KERN_INFO "=================================================\n");
|
||||
return err;
|
||||
}
|
||||
module_init(mtd_oobtest_init);
|
||||
|
||||
static void __exit mtd_oobtest_exit(void)
|
||||
{
|
||||
return;
|
||||
}
|
||||
module_exit(mtd_oobtest_exit);
|
||||
|
||||
MODULE_DESCRIPTION("Out-of-band test module");
|
||||
MODULE_AUTHOR("Adrian Hunter");
|
||||
MODULE_LICENSE("GPL");
|
632
drivers/mtd/tests/mtd_pagetest.c
Normal file
632
drivers/mtd/tests/mtd_pagetest.c
Normal file
@ -0,0 +1,632 @@
|
||||
/*
|
||||
* Copyright (C) 2006-2008 Nokia Corporation
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 as published by
|
||||
* the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program; see the file COPYING. If not, write to the Free Software
|
||||
* Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
*
|
||||
* Test page read and write on MTD device.
|
||||
*
|
||||
* Author: Adrian Hunter <ext-adrian.hunter@nokia.com>
|
||||
*/
|
||||
|
||||
#include <asm/div64.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/moduleparam.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/mtd/mtd.h>
|
||||
#include <linux/sched.h>
|
||||
|
||||
#define PRINT_PREF KERN_INFO "mtd_pagetest: "
|
||||
|
||||
static int dev;
|
||||
module_param(dev, int, S_IRUGO);
|
||||
MODULE_PARM_DESC(dev, "MTD device number to use");
|
||||
|
||||
static struct mtd_info *mtd;
|
||||
static unsigned char *twopages;
|
||||
static unsigned char *writebuf;
|
||||
static unsigned char *boundary;
|
||||
static unsigned char *bbt;
|
||||
|
||||
static int pgsize;
|
||||
static int bufsize;
|
||||
static int ebcnt;
|
||||
static int pgcnt;
|
||||
static int errcnt;
|
||||
static unsigned long next = 1;
|
||||
|
||||
static inline unsigned int simple_rand(void)
|
||||
{
|
||||
next = next * 1103515245 + 12345;
|
||||
return (unsigned int)((next / 65536) % 32768);
|
||||
}
|
||||
|
||||
static inline void simple_srand(unsigned long seed)
|
||||
{
|
||||
next = seed;
|
||||
}
|
||||
|
||||
static void set_random_data(unsigned char *buf, size_t len)
|
||||
{
|
||||
size_t i;
|
||||
|
||||
for (i = 0; i < len; ++i)
|
||||
buf[i] = simple_rand();
|
||||
}
|
||||
|
||||
static int erase_eraseblock(int ebnum)
|
||||
{
|
||||
int err;
|
||||
struct erase_info ei;
|
||||
loff_t addr = ebnum * mtd->erasesize;
|
||||
|
||||
memset(&ei, 0, sizeof(struct erase_info));
|
||||
ei.mtd = mtd;
|
||||
ei.addr = addr;
|
||||
ei.len = mtd->erasesize;
|
||||
|
||||
err = mtd->erase(mtd, &ei);
|
||||
if (err) {
|
||||
printk(PRINT_PREF "error %d while erasing EB %d\n", err, ebnum);
|
||||
return err;
|
||||
}
|
||||
|
||||
if (ei.state == MTD_ERASE_FAILED) {
|
||||
printk(PRINT_PREF "some erase error occurred at EB %d\n",
|
||||
ebnum);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int write_eraseblock(int ebnum)
|
||||
{
|
||||
int err = 0;
|
||||
size_t written = 0;
|
||||
loff_t addr = ebnum * mtd->erasesize;
|
||||
|
||||
set_random_data(writebuf, mtd->erasesize);
|
||||
cond_resched();
|
||||
err = mtd->write(mtd, addr, mtd->erasesize, &written, writebuf);
|
||||
if (err || written != mtd->erasesize)
|
||||
printk(PRINT_PREF "error: write failed at %#llx\n",
|
||||
(long long)addr);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static int verify_eraseblock(int ebnum)
|
||||
{
|
||||
uint32_t j;
|
||||
size_t read = 0;
|
||||
int err = 0, i;
|
||||
loff_t addr0, addrn;
|
||||
loff_t addr = ebnum * mtd->erasesize;
|
||||
|
||||
addr0 = 0;
|
||||
for (i = 0; bbt[i] && i < ebcnt; ++i)
|
||||
addr0 += mtd->erasesize;
|
||||
|
||||
addrn = mtd->size;
|
||||
for (i = 0; bbt[ebcnt - i - 1] && i < ebcnt; ++i)
|
||||
addrn -= mtd->erasesize;
|
||||
|
||||
set_random_data(writebuf, mtd->erasesize);
|
||||
for (j = 0; j < pgcnt - 1; ++j, addr += pgsize) {
|
||||
/* Do a read to set the internal dataRAMs to different data */
|
||||
err = mtd->read(mtd, addr0, bufsize, &read, twopages);
|
||||
if (err == -EUCLEAN)
|
||||
err = 0;
|
||||
if (err || read != bufsize) {
|
||||
printk(PRINT_PREF "error: read failed at %#llx\n",
|
||||
(long long)addr0);
|
||||
return err;
|
||||
}
|
||||
err = mtd->read(mtd, addrn - bufsize, bufsize, &read, twopages);
|
||||
if (err == -EUCLEAN)
|
||||
err = 0;
|
||||
if (err || read != bufsize) {
|
||||
printk(PRINT_PREF "error: read failed at %#llx\n",
|
||||
(long long)(addrn - bufsize));
|
||||
return err;
|
||||
}
|
||||
memset(twopages, 0, bufsize);
|
||||
read = 0;
|
||||
err = mtd->read(mtd, addr, bufsize, &read, twopages);
|
||||
if (err == -EUCLEAN)
|
||||
err = 0;
|
||||
if (err || read != bufsize) {
|
||||
printk(PRINT_PREF "error: read failed at %#llx\n",
|
||||
(long long)addr);
|
||||
break;
|
||||
}
|
||||
if (memcmp(twopages, writebuf + (j * pgsize), bufsize)) {
|
||||
printk(PRINT_PREF "error: verify failed at %#llx\n",
|
||||
(long long)addr);
|
||||
errcnt += 1;
|
||||
}
|
||||
}
|
||||
/* Check boundary between eraseblocks */
|
||||
if (addr <= addrn - pgsize - pgsize && !bbt[ebnum + 1]) {
|
||||
unsigned long oldnext = next;
|
||||
/* Do a read to set the internal dataRAMs to different data */
|
||||
err = mtd->read(mtd, addr0, bufsize, &read, twopages);
|
||||
if (err == -EUCLEAN)
|
||||
err = 0;
|
||||
if (err || read != bufsize) {
|
||||
printk(PRINT_PREF "error: read failed at %#llx\n",
|
||||
(long long)addr0);
|
||||
return err;
|
||||
}
|
||||
err = mtd->read(mtd, addrn - bufsize, bufsize, &read, twopages);
|
||||
if (err == -EUCLEAN)
|
||||
err = 0;
|
||||
if (err || read != bufsize) {
|
||||
printk(PRINT_PREF "error: read failed at %#llx\n",
|
||||
(long long)(addrn - bufsize));
|
||||
return err;
|
||||
}
|
||||
memset(twopages, 0, bufsize);
|
||||
read = 0;
|
||||
err = mtd->read(mtd, addr, bufsize, &read, twopages);
|
||||
if (err == -EUCLEAN)
|
||||
err = 0;
|
||||
if (err || read != bufsize) {
|
||||
printk(PRINT_PREF "error: read failed at %#llx\n",
|
||||
(long long)addr);
|
||||
return err;
|
||||
}
|
||||
memcpy(boundary, writebuf + mtd->erasesize - pgsize, pgsize);
|
||||
set_random_data(boundary + pgsize, pgsize);
|
||||
if (memcmp(twopages, boundary, bufsize)) {
|
||||
printk(PRINT_PREF "error: verify failed at %#llx\n",
|
||||
(long long)addr);
|
||||
errcnt += 1;
|
||||
}
|
||||
next = oldnext;
|
||||
}
|
||||
return err;
|
||||
}
|
||||
|
||||
static int crosstest(void)
|
||||
{
|
||||
size_t read = 0;
|
||||
int err = 0, i;
|
||||
loff_t addr, addr0, addrn;
|
||||
unsigned char *pp1, *pp2, *pp3, *pp4;
|
||||
|
||||
printk(PRINT_PREF "crosstest\n");
|
||||
pp1 = kmalloc(pgsize * 4, GFP_KERNEL);
|
||||
if (!pp1) {
|
||||
printk(PRINT_PREF "error: cannot allocate memory\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
pp2 = pp1 + pgsize;
|
||||
pp3 = pp2 + pgsize;
|
||||
pp4 = pp3 + pgsize;
|
||||
memset(pp1, 0, pgsize * 4);
|
||||
|
||||
addr0 = 0;
|
||||
for (i = 0; bbt[i] && i < ebcnt; ++i)
|
||||
addr0 += mtd->erasesize;
|
||||
|
||||
addrn = mtd->size;
|
||||
for (i = 0; bbt[ebcnt - i - 1] && i < ebcnt; ++i)
|
||||
addrn -= mtd->erasesize;
|
||||
|
||||
/* Read 2nd-to-last page to pp1 */
|
||||
read = 0;
|
||||
addr = addrn - pgsize - pgsize;
|
||||
err = mtd->read(mtd, addr, pgsize, &read, pp1);
|
||||
if (err == -EUCLEAN)
|
||||
err = 0;
|
||||
if (err || read != pgsize) {
|
||||
printk(PRINT_PREF "error: read failed at %#llx\n",
|
||||
(long long)addr);
|
||||
kfree(pp1);
|
||||
return err;
|
||||
}
|
||||
|
||||
/* Read 3rd-to-last page to pp1 */
|
||||
read = 0;
|
||||
addr = addrn - pgsize - pgsize - pgsize;
|
||||
err = mtd->read(mtd, addr, pgsize, &read, pp1);
|
||||
if (err == -EUCLEAN)
|
||||
err = 0;
|
||||
if (err || read != pgsize) {
|
||||
printk(PRINT_PREF "error: read failed at %#llx\n",
|
||||
(long long)addr);
|
||||
kfree(pp1);
|
||||
return err;
|
||||
}
|
||||
|
||||
/* Read first page to pp2 */
|
||||
read = 0;
|
||||
addr = addr0;
|
||||
printk(PRINT_PREF "reading page at %#llx\n", (long long)addr);
|
||||
err = mtd->read(mtd, addr, pgsize, &read, pp2);
|
||||
if (err == -EUCLEAN)
|
||||
err = 0;
|
||||
if (err || read != pgsize) {
|
||||
printk(PRINT_PREF "error: read failed at %#llx\n",
|
||||
(long long)addr);
|
||||
kfree(pp1);
|
||||
return err;
|
||||
}
|
||||
|
||||
/* Read last page to pp3 */
|
||||
read = 0;
|
||||
addr = addrn - pgsize;
|
||||
printk(PRINT_PREF "reading page at %#llx\n", (long long)addr);
|
||||
err = mtd->read(mtd, addr, pgsize, &read, pp3);
|
||||
if (err == -EUCLEAN)
|
||||
err = 0;
|
||||
if (err || read != pgsize) {
|
||||
printk(PRINT_PREF "error: read failed at %#llx\n",
|
||||
(long long)addr);
|
||||
kfree(pp1);
|
||||
return err;
|
||||
}
|
||||
|
||||
/* Read first page again to pp4 */
|
||||
read = 0;
|
||||
addr = addr0;
|
||||
printk(PRINT_PREF "reading page at %#llx\n", (long long)addr);
|
||||
err = mtd->read(mtd, addr, pgsize, &read, pp4);
|
||||
if (err == -EUCLEAN)
|
||||
err = 0;
|
||||
if (err || read != pgsize) {
|
||||
printk(PRINT_PREF "error: read failed at %#llx\n",
|
||||
(long long)addr);
|
||||
kfree(pp1);
|
||||
return err;
|
||||
}
|
||||
|
||||
/* pp2 and pp4 should be the same */
|
||||
printk(PRINT_PREF "verifying pages read at %#llx match\n",
|
||||
(long long)addr0);
|
||||
if (memcmp(pp2, pp4, pgsize)) {
|
||||
printk(PRINT_PREF "verify failed!\n");
|
||||
errcnt += 1;
|
||||
} else if (!err)
|
||||
printk(PRINT_PREF "crosstest ok\n");
|
||||
kfree(pp1);
|
||||
return err;
|
||||
}
|
||||
|
||||
static int erasecrosstest(void)
|
||||
{
|
||||
size_t read = 0, written = 0;
|
||||
int err = 0, i, ebnum, ok = 1, ebnum2;
|
||||
loff_t addr0;
|
||||
char *readbuf = twopages;
|
||||
|
||||
printk(PRINT_PREF "erasecrosstest\n");
|
||||
|
||||
ebnum = 0;
|
||||
addr0 = 0;
|
||||
for (i = 0; bbt[i] && i < ebcnt; ++i) {
|
||||
addr0 += mtd->erasesize;
|
||||
ebnum += 1;
|
||||
}
|
||||
|
||||
ebnum2 = ebcnt - 1;
|
||||
while (ebnum2 && bbt[ebnum2])
|
||||
ebnum2 -= 1;
|
||||
|
||||
printk(PRINT_PREF "erasing block %d\n", ebnum);
|
||||
err = erase_eraseblock(ebnum);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
printk(PRINT_PREF "writing 1st page of block %d\n", ebnum);
|
||||
set_random_data(writebuf, pgsize);
|
||||
strcpy(writebuf, "There is no data like this!");
|
||||
err = mtd->write(mtd, addr0, pgsize, &written, writebuf);
|
||||
if (err || written != pgsize) {
|
||||
printk(PRINT_PREF "error: write failed at %#llx\n",
|
||||
(long long)addr0);
|
||||
return err ? err : -1;
|
||||
}
|
||||
|
||||
printk(PRINT_PREF "reading 1st page of block %d\n", ebnum);
|
||||
memset(readbuf, 0, pgsize);
|
||||
err = mtd->read(mtd, addr0, pgsize, &read, readbuf);
|
||||
if (err == -EUCLEAN)
|
||||
err = 0;
|
||||
if (err || read != pgsize) {
|
||||
printk(PRINT_PREF "error: read failed at %#llx\n",
|
||||
(long long)addr0);
|
||||
return err ? err : -1;
|
||||
}
|
||||
|
||||
printk(PRINT_PREF "verifying 1st page of block %d\n", ebnum);
|
||||
if (memcmp(writebuf, readbuf, pgsize)) {
|
||||
printk(PRINT_PREF "verify failed!\n");
|
||||
errcnt += 1;
|
||||
ok = 0;
|
||||
return err;
|
||||
}
|
||||
|
||||
printk(PRINT_PREF "erasing block %d\n", ebnum);
|
||||
err = erase_eraseblock(ebnum);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
printk(PRINT_PREF "writing 1st page of block %d\n", ebnum);
|
||||
set_random_data(writebuf, pgsize);
|
||||
strcpy(writebuf, "There is no data like this!");
|
||||
err = mtd->write(mtd, addr0, pgsize, &written, writebuf);
|
||||
if (err || written != pgsize) {
|
||||
printk(PRINT_PREF "error: write failed at %#llx\n",
|
||||
(long long)addr0);
|
||||
return err ? err : -1;
|
||||
}
|
||||
|
||||
printk(PRINT_PREF "erasing block %d\n", ebnum2);
|
||||
err = erase_eraseblock(ebnum2);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
printk(PRINT_PREF "reading 1st page of block %d\n", ebnum);
|
||||
memset(readbuf, 0, pgsize);
|
||||
err = mtd->read(mtd, addr0, pgsize, &read, readbuf);
|
||||
if (err == -EUCLEAN)
|
||||
err = 0;
|
||||
if (err || read != pgsize) {
|
||||
printk(PRINT_PREF "error: read failed at %#llx\n",
|
||||
(long long)addr0);
|
||||
return err ? err : -1;
|
||||
}
|
||||
|
||||
printk(PRINT_PREF "verifying 1st page of block %d\n", ebnum);
|
||||
if (memcmp(writebuf, readbuf, pgsize)) {
|
||||
printk(PRINT_PREF "verify failed!\n");
|
||||
errcnt += 1;
|
||||
ok = 0;
|
||||
}
|
||||
|
||||
if (ok && !err)
|
||||
printk(PRINT_PREF "erasecrosstest ok\n");
|
||||
return err;
|
||||
}
|
||||
|
||||
static int erasetest(void)
|
||||
{
|
||||
size_t read = 0, written = 0;
|
||||
int err = 0, i, ebnum, ok = 1;
|
||||
loff_t addr0;
|
||||
|
||||
printk(PRINT_PREF "erasetest\n");
|
||||
|
||||
ebnum = 0;
|
||||
addr0 = 0;
|
||||
for (i = 0; bbt[i] && i < ebcnt; ++i) {
|
||||
addr0 += mtd->erasesize;
|
||||
ebnum += 1;
|
||||
}
|
||||
|
||||
printk(PRINT_PREF "erasing block %d\n", ebnum);
|
||||
err = erase_eraseblock(ebnum);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
printk(PRINT_PREF "writing 1st page of block %d\n", ebnum);
|
||||
set_random_data(writebuf, pgsize);
|
||||
err = mtd->write(mtd, addr0, pgsize, &written, writebuf);
|
||||
if (err || written != pgsize) {
|
||||
printk(PRINT_PREF "error: write failed at %#llx\n",
|
||||
(long long)addr0);
|
||||
return err ? err : -1;
|
||||
}
|
||||
|
||||
printk(PRINT_PREF "erasing block %d\n", ebnum);
|
||||
err = erase_eraseblock(ebnum);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
printk(PRINT_PREF "reading 1st page of block %d\n", ebnum);
|
||||
err = mtd->read(mtd, addr0, pgsize, &read, twopages);
|
||||
if (err == -EUCLEAN)
|
||||
err = 0;
|
||||
if (err || read != pgsize) {
|
||||
printk(PRINT_PREF "error: read failed at %#llx\n",
|
||||
(long long)addr0);
|
||||
return err ? err : -1;
|
||||
}
|
||||
|
||||
printk(PRINT_PREF "verifying 1st page of block %d is all 0xff\n",
|
||||
ebnum);
|
||||
for (i = 0; i < pgsize; ++i)
|
||||
if (twopages[i] != 0xff) {
|
||||
printk(PRINT_PREF "verifying all 0xff failed at %d\n",
|
||||
i);
|
||||
errcnt += 1;
|
||||
ok = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
if (ok && !err)
|
||||
printk(PRINT_PREF "erasetest ok\n");
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static int is_block_bad(int ebnum)
|
||||
{
|
||||
loff_t addr = ebnum * mtd->erasesize;
|
||||
int ret;
|
||||
|
||||
ret = mtd->block_isbad(mtd, addr);
|
||||
if (ret)
|
||||
printk(PRINT_PREF "block %d is bad\n", ebnum);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int scan_for_bad_eraseblocks(void)
|
||||
{
|
||||
int i, bad = 0;
|
||||
|
||||
bbt = kmalloc(ebcnt, GFP_KERNEL);
|
||||
if (!bbt) {
|
||||
printk(PRINT_PREF "error: cannot allocate memory\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
memset(bbt, 0 , ebcnt);
|
||||
|
||||
printk(PRINT_PREF "scanning for bad eraseblocks\n");
|
||||
for (i = 0; i < ebcnt; ++i) {
|
||||
bbt[i] = is_block_bad(i) ? 1 : 0;
|
||||
if (bbt[i])
|
||||
bad += 1;
|
||||
cond_resched();
|
||||
}
|
||||
printk(PRINT_PREF "scanned %d eraseblocks, %d are bad\n", i, bad);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int __init mtd_pagetest_init(void)
|
||||
{
|
||||
int err = 0;
|
||||
uint64_t tmp;
|
||||
uint32_t i;
|
||||
|
||||
printk(KERN_INFO "\n");
|
||||
printk(KERN_INFO "=================================================\n");
|
||||
printk(PRINT_PREF "MTD device: %d\n", dev);
|
||||
|
||||
mtd = get_mtd_device(NULL, dev);
|
||||
if (IS_ERR(mtd)) {
|
||||
err = PTR_ERR(mtd);
|
||||
printk(PRINT_PREF "error: cannot get MTD device\n");
|
||||
return err;
|
||||
}
|
||||
|
||||
if (mtd->type != MTD_NANDFLASH) {
|
||||
printk(PRINT_PREF "this test requires NAND flash\n");
|
||||
goto out;
|
||||
}
|
||||
|
||||
tmp = mtd->size;
|
||||
do_div(tmp, mtd->erasesize);
|
||||
ebcnt = tmp;
|
||||
pgcnt = mtd->erasesize / mtd->writesize;
|
||||
|
||||
printk(PRINT_PREF "MTD device size %llu, eraseblock size %u, "
|
||||
"page size %u, count of eraseblocks %u, pages per "
|
||||
"eraseblock %u, OOB size %u\n",
|
||||
(unsigned long long)mtd->size, mtd->erasesize,
|
||||
pgsize, ebcnt, pgcnt, mtd->oobsize);
|
||||
|
||||
err = -ENOMEM;
|
||||
bufsize = pgsize * 2;
|
||||
writebuf = kmalloc(mtd->erasesize, GFP_KERNEL);
|
||||
if (!writebuf) {
|
||||
printk(PRINT_PREF "error: cannot allocate memory\n");
|
||||
goto out;
|
||||
}
|
||||
twopages = kmalloc(bufsize, GFP_KERNEL);
|
||||
if (!twopages) {
|
||||
printk(PRINT_PREF "error: cannot allocate memory\n");
|
||||
goto out;
|
||||
}
|
||||
boundary = kmalloc(bufsize, GFP_KERNEL);
|
||||
if (!boundary) {
|
||||
printk(PRINT_PREF "error: cannot allocate memory\n");
|
||||
goto out;
|
||||
}
|
||||
|
||||
err = scan_for_bad_eraseblocks();
|
||||
if (err)
|
||||
goto out;
|
||||
|
||||
/* Erase all eraseblocks */
|
||||
printk(PRINT_PREF "erasing whole device\n");
|
||||
for (i = 0; i < ebcnt; ++i) {
|
||||
if (bbt[i])
|
||||
continue;
|
||||
err = erase_eraseblock(i);
|
||||
if (err)
|
||||
goto out;
|
||||
cond_resched();
|
||||
}
|
||||
printk(PRINT_PREF "erased %u eraseblocks\n", i);
|
||||
|
||||
/* Write all eraseblocks */
|
||||
simple_srand(1);
|
||||
printk(PRINT_PREF "writing whole device\n");
|
||||
for (i = 0; i < ebcnt; ++i) {
|
||||
if (bbt[i])
|
||||
continue;
|
||||
err = write_eraseblock(i);
|
||||
if (err)
|
||||
goto out;
|
||||
if (i % 256 == 0)
|
||||
printk(PRINT_PREF "written up to eraseblock %u\n", i);
|
||||
cond_resched();
|
||||
}
|
||||
printk(PRINT_PREF "written %u eraseblocks\n", i);
|
||||
|
||||
/* Check all eraseblocks */
|
||||
simple_srand(1);
|
||||
printk(PRINT_PREF "verifying all eraseblocks\n");
|
||||
for (i = 0; i < ebcnt; ++i) {
|
||||
if (bbt[i])
|
||||
continue;
|
||||
err = verify_eraseblock(i);
|
||||
if (err)
|
||||
goto out;
|
||||
if (i % 256 == 0)
|
||||
printk(PRINT_PREF "verified up to eraseblock %u\n", i);
|
||||
cond_resched();
|
||||
}
|
||||
printk(PRINT_PREF "verified %u eraseblocks\n", i);
|
||||
|
||||
err = crosstest();
|
||||
if (err)
|
||||
goto out;
|
||||
|
||||
err = erasecrosstest();
|
||||
if (err)
|
||||
goto out;
|
||||
|
||||
err = erasetest();
|
||||
if (err)
|
||||
goto out;
|
||||
|
||||
printk(PRINT_PREF "finished with %d errors\n", errcnt);
|
||||
out:
|
||||
|
||||
kfree(bbt);
|
||||
kfree(boundary);
|
||||
kfree(twopages);
|
||||
kfree(writebuf);
|
||||
put_mtd_device(mtd);
|
||||
if (err)
|
||||
printk(PRINT_PREF "error %d occurred\n", err);
|
||||
printk(KERN_INFO "=================================================\n");
|
||||
return err;
|
||||
}
|
||||
module_init(mtd_pagetest_init);
|
||||
|
||||
static void __exit mtd_pagetest_exit(void)
|
||||
{
|
||||
return;
|
||||
}
|
||||
module_exit(mtd_pagetest_exit);
|
||||
|
||||
MODULE_DESCRIPTION("NAND page test");
|
||||
MODULE_AUTHOR("Adrian Hunter");
|
||||
MODULE_LICENSE("GPL");
|
253
drivers/mtd/tests/mtd_readtest.c
Normal file
253
drivers/mtd/tests/mtd_readtest.c
Normal file
@ -0,0 +1,253 @@
|
||||
/*
|
||||
* Copyright (C) 2006-2008 Nokia Corporation
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 as published by
|
||||
* the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program; see the file COPYING. If not, write to the Free Software
|
||||
* Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
*
|
||||
* Check MTD device read.
|
||||
*
|
||||
* Author: Adrian Hunter <ext-adrian.hunter@nokia.com>
|
||||
*/
|
||||
|
||||
#include <linux/init.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/moduleparam.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/mtd/mtd.h>
|
||||
#include <linux/sched.h>
|
||||
|
||||
#define PRINT_PREF KERN_INFO "mtd_readtest: "
|
||||
|
||||
static int dev;
|
||||
module_param(dev, int, S_IRUGO);
|
||||
MODULE_PARM_DESC(dev, "MTD device number to use");
|
||||
|
||||
static struct mtd_info *mtd;
|
||||
static unsigned char *iobuf;
|
||||
static unsigned char *iobuf1;
|
||||
static unsigned char *bbt;
|
||||
|
||||
static int pgsize;
|
||||
static int ebcnt;
|
||||
static int pgcnt;
|
||||
|
||||
static int read_eraseblock_by_page(int ebnum)
|
||||
{
|
||||
size_t read = 0;
|
||||
int i, ret, err = 0;
|
||||
loff_t addr = ebnum * mtd->erasesize;
|
||||
void *buf = iobuf;
|
||||
void *oobbuf = iobuf1;
|
||||
|
||||
for (i = 0; i < pgcnt; i++) {
|
||||
memset(buf, 0 , pgcnt);
|
||||
ret = mtd->read(mtd, addr, pgsize, &read, buf);
|
||||
if (ret == -EUCLEAN)
|
||||
ret = 0;
|
||||
if (ret || read != pgsize) {
|
||||
printk(PRINT_PREF "error: read failed at %#llx\n",
|
||||
(long long)addr);
|
||||
if (!err)
|
||||
err = ret;
|
||||
if (!err)
|
||||
err = -EINVAL;
|
||||
}
|
||||
if (mtd->oobsize) {
|
||||
struct mtd_oob_ops ops;
|
||||
|
||||
ops.mode = MTD_OOB_PLACE;
|
||||
ops.len = 0;
|
||||
ops.retlen = 0;
|
||||
ops.ooblen = mtd->oobsize;
|
||||
ops.oobretlen = 0;
|
||||
ops.ooboffs = 0;
|
||||
ops.datbuf = 0;
|
||||
ops.oobbuf = oobbuf;
|
||||
ret = mtd->read_oob(mtd, addr, &ops);
|
||||
if (ret || ops.oobretlen != mtd->oobsize) {
|
||||
printk(PRINT_PREF "error: read oob failed at "
|
||||
"%#llx\n", (long long)addr);
|
||||
if (!err)
|
||||
err = ret;
|
||||
if (!err)
|
||||
err = -EINVAL;
|
||||
}
|
||||
oobbuf += mtd->oobsize;
|
||||
}
|
||||
addr += pgsize;
|
||||
buf += pgsize;
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static void dump_eraseblock(int ebnum)
|
||||
{
|
||||
int i, j, n;
|
||||
char line[128];
|
||||
int pg, oob;
|
||||
|
||||
printk(PRINT_PREF "dumping eraseblock %d\n", ebnum);
|
||||
n = mtd->erasesize;
|
||||
for (i = 0; i < n;) {
|
||||
char *p = line;
|
||||
|
||||
p += sprintf(p, "%05x: ", i);
|
||||
for (j = 0; j < 32 && i < n; j++, i++)
|
||||
p += sprintf(p, "%02x", (unsigned int)iobuf[i]);
|
||||
printk(KERN_CRIT "%s\n", line);
|
||||
cond_resched();
|
||||
}
|
||||
if (!mtd->oobsize)
|
||||
return;
|
||||
printk(PRINT_PREF "dumping oob from eraseblock %d\n", ebnum);
|
||||
n = mtd->oobsize;
|
||||
for (pg = 0, i = 0; pg < pgcnt; pg++)
|
||||
for (oob = 0; oob < n;) {
|
||||
char *p = line;
|
||||
|
||||
p += sprintf(p, "%05x: ", i);
|
||||
for (j = 0; j < 32 && oob < n; j++, oob++, i++)
|
||||
p += sprintf(p, "%02x",
|
||||
(unsigned int)iobuf1[i]);
|
||||
printk(KERN_CRIT "%s\n", line);
|
||||
cond_resched();
|
||||
}
|
||||
}
|
||||
|
||||
static int is_block_bad(int ebnum)
|
||||
{
|
||||
loff_t addr = ebnum * mtd->erasesize;
|
||||
int ret;
|
||||
|
||||
ret = mtd->block_isbad(mtd, addr);
|
||||
if (ret)
|
||||
printk(PRINT_PREF "block %d is bad\n", ebnum);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int scan_for_bad_eraseblocks(void)
|
||||
{
|
||||
int i, bad = 0;
|
||||
|
||||
bbt = kmalloc(ebcnt, GFP_KERNEL);
|
||||
if (!bbt) {
|
||||
printk(PRINT_PREF "error: cannot allocate memory\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
memset(bbt, 0 , ebcnt);
|
||||
|
||||
printk(PRINT_PREF "scanning for bad eraseblocks\n");
|
||||
for (i = 0; i < ebcnt; ++i) {
|
||||
bbt[i] = is_block_bad(i) ? 1 : 0;
|
||||
if (bbt[i])
|
||||
bad += 1;
|
||||
cond_resched();
|
||||
}
|
||||
printk(PRINT_PREF "scanned %d eraseblocks, %d are bad\n", i, bad);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int __init mtd_readtest_init(void)
|
||||
{
|
||||
uint64_t tmp;
|
||||
int err, i;
|
||||
|
||||
printk(KERN_INFO "\n");
|
||||
printk(KERN_INFO "=================================================\n");
|
||||
printk(PRINT_PREF "MTD device: %d\n", dev);
|
||||
|
||||
mtd = get_mtd_device(NULL, dev);
|
||||
if (IS_ERR(mtd)) {
|
||||
err = PTR_ERR(mtd);
|
||||
printk(PRINT_PREF "error: Cannot get MTD device\n");
|
||||
return err;
|
||||
}
|
||||
|
||||
if (mtd->writesize == 1) {
|
||||
printk(PRINT_PREF "not NAND flash, assume page size is 512 "
|
||||
"bytes.\n");
|
||||
pgsize = 512;
|
||||
} else
|
||||
pgsize = mtd->writesize;
|
||||
|
||||
tmp = mtd->size;
|
||||
do_div(tmp, mtd->erasesize);
|
||||
ebcnt = tmp;
|
||||
pgcnt = mtd->erasesize / mtd->writesize;
|
||||
|
||||
printk(PRINT_PREF "MTD device size %llu, eraseblock size %u, "
|
||||
"page size %u, count of eraseblocks %u, pages per "
|
||||
"eraseblock %u, OOB size %u\n",
|
||||
(unsigned long long)mtd->size, mtd->erasesize,
|
||||
pgsize, ebcnt, pgcnt, mtd->oobsize);
|
||||
|
||||
err = -ENOMEM;
|
||||
iobuf = kmalloc(mtd->erasesize, GFP_KERNEL);
|
||||
if (!iobuf) {
|
||||
printk(PRINT_PREF "error: cannot allocate memory\n");
|
||||
goto out;
|
||||
}
|
||||
iobuf1 = kmalloc(mtd->erasesize, GFP_KERNEL);
|
||||
if (!iobuf1) {
|
||||
printk(PRINT_PREF "error: cannot allocate memory\n");
|
||||
goto out;
|
||||
}
|
||||
|
||||
err = scan_for_bad_eraseblocks();
|
||||
if (err)
|
||||
goto out;
|
||||
|
||||
/* Read all eraseblocks 1 page at a time */
|
||||
printk(PRINT_PREF "testing page read\n");
|
||||
for (i = 0; i < ebcnt; ++i) {
|
||||
int ret;
|
||||
|
||||
if (bbt[i])
|
||||
continue;
|
||||
ret = read_eraseblock_by_page(i);
|
||||
if (ret) {
|
||||
dump_eraseblock(i);
|
||||
if (!err)
|
||||
err = ret;
|
||||
}
|
||||
cond_resched();
|
||||
}
|
||||
|
||||
if (err)
|
||||
printk(PRINT_PREF "finished with errors\n");
|
||||
else
|
||||
printk(PRINT_PREF "finished\n");
|
||||
|
||||
out:
|
||||
|
||||
kfree(iobuf);
|
||||
kfree(iobuf1);
|
||||
kfree(bbt);
|
||||
put_mtd_device(mtd);
|
||||
if (err)
|
||||
printk(PRINT_PREF "error %d occurred\n", err);
|
||||
printk(KERN_INFO "=================================================\n");
|
||||
return err;
|
||||
}
|
||||
module_init(mtd_readtest_init);
|
||||
|
||||
static void __exit mtd_readtest_exit(void)
|
||||
{
|
||||
return;
|
||||
}
|
||||
module_exit(mtd_readtest_exit);
|
||||
|
||||
MODULE_DESCRIPTION("Read test module");
|
||||
MODULE_AUTHOR("Adrian Hunter");
|
||||
MODULE_LICENSE("GPL");
|
502
drivers/mtd/tests/mtd_speedtest.c
Normal file
502
drivers/mtd/tests/mtd_speedtest.c
Normal file
@ -0,0 +1,502 @@
|
||||
/*
|
||||
* Copyright (C) 2007 Nokia Corporation
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 as published by
|
||||
* the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program; see the file COPYING. If not, write to the Free Software
|
||||
* Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
*
|
||||
* Test read and write speed of a MTD device.
|
||||
*
|
||||
* Author: Adrian Hunter <ext-adrian.hunter@nokia.com>
|
||||
*/
|
||||
|
||||
#include <linux/init.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/moduleparam.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/mtd/mtd.h>
|
||||
#include <linux/sched.h>
|
||||
|
||||
#define PRINT_PREF KERN_INFO "mtd_speedtest: "
|
||||
|
||||
static int dev;
|
||||
module_param(dev, int, S_IRUGO);
|
||||
MODULE_PARM_DESC(dev, "MTD device number to use");
|
||||
|
||||
static struct mtd_info *mtd;
|
||||
static unsigned char *iobuf;
|
||||
static unsigned char *bbt;
|
||||
|
||||
static int pgsize;
|
||||
static int ebcnt;
|
||||
static int pgcnt;
|
||||
static int goodebcnt;
|
||||
static struct timeval start, finish;
|
||||
static unsigned long next = 1;
|
||||
|
||||
static inline unsigned int simple_rand(void)
|
||||
{
|
||||
next = next * 1103515245 + 12345;
|
||||
return (unsigned int)((next / 65536) % 32768);
|
||||
}
|
||||
|
||||
static inline void simple_srand(unsigned long seed)
|
||||
{
|
||||
next = seed;
|
||||
}
|
||||
|
||||
static void set_random_data(unsigned char *buf, size_t len)
|
||||
{
|
||||
size_t i;
|
||||
|
||||
for (i = 0; i < len; ++i)
|
||||
buf[i] = simple_rand();
|
||||
}
|
||||
|
||||
static int erase_eraseblock(int ebnum)
|
||||
{
|
||||
int err;
|
||||
struct erase_info ei;
|
||||
loff_t addr = ebnum * mtd->erasesize;
|
||||
|
||||
memset(&ei, 0, sizeof(struct erase_info));
|
||||
ei.mtd = mtd;
|
||||
ei.addr = addr;
|
||||
ei.len = mtd->erasesize;
|
||||
|
||||
err = mtd->erase(mtd, &ei);
|
||||
if (err) {
|
||||
printk(PRINT_PREF "error %d while erasing EB %d\n", err, ebnum);
|
||||
return err;
|
||||
}
|
||||
|
||||
if (ei.state == MTD_ERASE_FAILED) {
|
||||
printk(PRINT_PREF "some erase error occurred at EB %d\n",
|
||||
ebnum);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int erase_whole_device(void)
|
||||
{
|
||||
int err;
|
||||
unsigned int i;
|
||||
|
||||
for (i = 0; i < ebcnt; ++i) {
|
||||
if (bbt[i])
|
||||
continue;
|
||||
err = erase_eraseblock(i);
|
||||
if (err)
|
||||
return err;
|
||||
cond_resched();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int write_eraseblock(int ebnum)
|
||||
{
|
||||
size_t written = 0;
|
||||
int err = 0;
|
||||
loff_t addr = ebnum * mtd->erasesize;
|
||||
|
||||
err = mtd->write(mtd, addr, mtd->erasesize, &written, iobuf);
|
||||
if (err || written != mtd->erasesize) {
|
||||
printk(PRINT_PREF "error: write failed at %#llx\n", addr);
|
||||
if (!err)
|
||||
err = -EINVAL;
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static int write_eraseblock_by_page(int ebnum)
|
||||
{
|
||||
size_t written = 0;
|
||||
int i, err = 0;
|
||||
loff_t addr = ebnum * mtd->erasesize;
|
||||
void *buf = iobuf;
|
||||
|
||||
for (i = 0; i < pgcnt; i++) {
|
||||
err = mtd->write(mtd, addr, pgsize, &written, buf);
|
||||
if (err || written != pgsize) {
|
||||
printk(PRINT_PREF "error: write failed at %#llx\n",
|
||||
addr);
|
||||
if (!err)
|
||||
err = -EINVAL;
|
||||
break;
|
||||
}
|
||||
addr += pgsize;
|
||||
buf += pgsize;
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static int write_eraseblock_by_2pages(int ebnum)
|
||||
{
|
||||
size_t written = 0, sz = pgsize * 2;
|
||||
int i, n = pgcnt / 2, err = 0;
|
||||
loff_t addr = ebnum * mtd->erasesize;
|
||||
void *buf = iobuf;
|
||||
|
||||
for (i = 0; i < n; i++) {
|
||||
err = mtd->write(mtd, addr, sz, &written, buf);
|
||||
if (err || written != sz) {
|
||||
printk(PRINT_PREF "error: write failed at %#llx\n",
|
||||
addr);
|
||||
if (!err)
|
||||
err = -EINVAL;
|
||||
return err;
|
||||
}
|
||||
addr += sz;
|
||||
buf += sz;
|
||||
}
|
||||
if (pgcnt % 2) {
|
||||
err = mtd->write(mtd, addr, pgsize, &written, buf);
|
||||
if (err || written != pgsize) {
|
||||
printk(PRINT_PREF "error: write failed at %#llx\n",
|
||||
addr);
|
||||
if (!err)
|
||||
err = -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static int read_eraseblock(int ebnum)
|
||||
{
|
||||
size_t read = 0;
|
||||
int err = 0;
|
||||
loff_t addr = ebnum * mtd->erasesize;
|
||||
|
||||
err = mtd->read(mtd, addr, mtd->erasesize, &read, iobuf);
|
||||
/* Ignore corrected ECC errors */
|
||||
if (err == -EUCLEAN)
|
||||
err = 0;
|
||||
if (err || read != mtd->erasesize) {
|
||||
printk(PRINT_PREF "error: read failed at %#llx\n", addr);
|
||||
if (!err)
|
||||
err = -EINVAL;
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static int read_eraseblock_by_page(int ebnum)
|
||||
{
|
||||
size_t read = 0;
|
||||
int i, err = 0;
|
||||
loff_t addr = ebnum * mtd->erasesize;
|
||||
void *buf = iobuf;
|
||||
|
||||
for (i = 0; i < pgcnt; i++) {
|
||||
err = mtd->read(mtd, addr, pgsize, &read, buf);
|
||||
/* Ignore corrected ECC errors */
|
||||
if (err == -EUCLEAN)
|
||||
err = 0;
|
||||
if (err || read != pgsize) {
|
||||
printk(PRINT_PREF "error: read failed at %#llx\n",
|
||||
addr);
|
||||
if (!err)
|
||||
err = -EINVAL;
|
||||
break;
|
||||
}
|
||||
addr += pgsize;
|
||||
buf += pgsize;
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static int read_eraseblock_by_2pages(int ebnum)
|
||||
{
|
||||
size_t read = 0, sz = pgsize * 2;
|
||||
int i, n = pgcnt / 2, err = 0;
|
||||
loff_t addr = ebnum * mtd->erasesize;
|
||||
void *buf = iobuf;
|
||||
|
||||
for (i = 0; i < n; i++) {
|
||||
err = mtd->read(mtd, addr, sz, &read, buf);
|
||||
/* Ignore corrected ECC errors */
|
||||
if (err == -EUCLEAN)
|
||||
err = 0;
|
||||
if (err || read != sz) {
|
||||
printk(PRINT_PREF "error: read failed at %#llx\n",
|
||||
addr);
|
||||
if (!err)
|
||||
err = -EINVAL;
|
||||
return err;
|
||||
}
|
||||
addr += sz;
|
||||
buf += sz;
|
||||
}
|
||||
if (pgcnt % 2) {
|
||||
err = mtd->read(mtd, addr, pgsize, &read, buf);
|
||||
/* Ignore corrected ECC errors */
|
||||
if (err == -EUCLEAN)
|
||||
err = 0;
|
||||
if (err || read != pgsize) {
|
||||
printk(PRINT_PREF "error: read failed at %#llx\n",
|
||||
addr);
|
||||
if (!err)
|
||||
err = -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static int is_block_bad(int ebnum)
|
||||
{
|
||||
loff_t addr = ebnum * mtd->erasesize;
|
||||
int ret;
|
||||
|
||||
ret = mtd->block_isbad(mtd, addr);
|
||||
if (ret)
|
||||
printk(PRINT_PREF "block %d is bad\n", ebnum);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static inline void start_timing(void)
|
||||
{
|
||||
do_gettimeofday(&start);
|
||||
}
|
||||
|
||||
static inline void stop_timing(void)
|
||||
{
|
||||
do_gettimeofday(&finish);
|
||||
}
|
||||
|
||||
static long calc_speed(void)
|
||||
{
|
||||
long ms, k, speed;
|
||||
|
||||
ms = (finish.tv_sec - start.tv_sec) * 1000 +
|
||||
(finish.tv_usec - start.tv_usec) / 1000;
|
||||
k = goodebcnt * mtd->erasesize / 1024;
|
||||
speed = (k * 1000) / ms;
|
||||
return speed;
|
||||
}
|
||||
|
||||
static int scan_for_bad_eraseblocks(void)
|
||||
{
|
||||
int i, bad = 0;
|
||||
|
||||
bbt = kmalloc(ebcnt, GFP_KERNEL);
|
||||
if (!bbt) {
|
||||
printk(PRINT_PREF "error: cannot allocate memory\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
memset(bbt, 0 , ebcnt);
|
||||
|
||||
printk(PRINT_PREF "scanning for bad eraseblocks\n");
|
||||
for (i = 0; i < ebcnt; ++i) {
|
||||
bbt[i] = is_block_bad(i) ? 1 : 0;
|
||||
if (bbt[i])
|
||||
bad += 1;
|
||||
cond_resched();
|
||||
}
|
||||
printk(PRINT_PREF "scanned %d eraseblocks, %d are bad\n", i, bad);
|
||||
goodebcnt = ebcnt - bad;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int __init mtd_speedtest_init(void)
|
||||
{
|
||||
int err, i;
|
||||
long speed;
|
||||
uint64_t tmp;
|
||||
|
||||
printk(KERN_INFO "\n");
|
||||
printk(KERN_INFO "=================================================\n");
|
||||
printk(PRINT_PREF "MTD device: %d\n", dev);
|
||||
|
||||
mtd = get_mtd_device(NULL, dev);
|
||||
if (IS_ERR(mtd)) {
|
||||
err = PTR_ERR(mtd);
|
||||
printk(PRINT_PREF "error: cannot get MTD device\n");
|
||||
return err;
|
||||
}
|
||||
|
||||
if (mtd->writesize == 1) {
|
||||
printk(PRINT_PREF "not NAND flash, assume page size is 512 "
|
||||
"bytes.\n");
|
||||
pgsize = 512;
|
||||
} else
|
||||
pgsize = mtd->writesize;
|
||||
|
||||
tmp = mtd->size;
|
||||
do_div(tmp, mtd->erasesize);
|
||||
ebcnt = tmp;
|
||||
pgcnt = mtd->erasesize / mtd->writesize;
|
||||
|
||||
printk(PRINT_PREF "MTD device size %llu, eraseblock size %u, "
|
||||
"page size %u, count of eraseblocks %u, pages per "
|
||||
"eraseblock %u, OOB size %u\n",
|
||||
(unsigned long long)mtd->size, mtd->erasesize,
|
||||
pgsize, ebcnt, pgcnt, mtd->oobsize);
|
||||
|
||||
err = -ENOMEM;
|
||||
iobuf = kmalloc(mtd->erasesize, GFP_KERNEL);
|
||||
if (!iobuf) {
|
||||
printk(PRINT_PREF "error: cannot allocate memory\n");
|
||||
goto out;
|
||||
}
|
||||
|
||||
simple_srand(1);
|
||||
set_random_data(iobuf, mtd->erasesize);
|
||||
|
||||
err = scan_for_bad_eraseblocks();
|
||||
if (err)
|
||||
goto out;
|
||||
|
||||
err = erase_whole_device();
|
||||
if (err)
|
||||
goto out;
|
||||
|
||||
/* Write all eraseblocks, 1 eraseblock at a time */
|
||||
printk(PRINT_PREF "testing eraseblock write speed\n");
|
||||
start_timing();
|
||||
for (i = 0; i < ebcnt; ++i) {
|
||||
if (bbt[i])
|
||||
continue;
|
||||
err = write_eraseblock(i);
|
||||
if (err)
|
||||
goto out;
|
||||
cond_resched();
|
||||
}
|
||||
stop_timing();
|
||||
speed = calc_speed();
|
||||
printk(PRINT_PREF "eraseblock write speed is %ld KiB/s\n", speed);
|
||||
|
||||
/* Read all eraseblocks, 1 eraseblock at a time */
|
||||
printk(PRINT_PREF "testing eraseblock read speed\n");
|
||||
start_timing();
|
||||
for (i = 0; i < ebcnt; ++i) {
|
||||
if (bbt[i])
|
||||
continue;
|
||||
err = read_eraseblock(i);
|
||||
if (err)
|
||||
goto out;
|
||||
cond_resched();
|
||||
}
|
||||
stop_timing();
|
||||
speed = calc_speed();
|
||||
printk(PRINT_PREF "eraseblock read speed is %ld KiB/s\n", speed);
|
||||
|
||||
err = erase_whole_device();
|
||||
if (err)
|
||||
goto out;
|
||||
|
||||
/* Write all eraseblocks, 1 page at a time */
|
||||
printk(PRINT_PREF "testing page write speed\n");
|
||||
start_timing();
|
||||
for (i = 0; i < ebcnt; ++i) {
|
||||
if (bbt[i])
|
||||
continue;
|
||||
err = write_eraseblock_by_page(i);
|
||||
if (err)
|
||||
goto out;
|
||||
cond_resched();
|
||||
}
|
||||
stop_timing();
|
||||
speed = calc_speed();
|
||||
printk(PRINT_PREF "page write speed is %ld KiB/s\n", speed);
|
||||
|
||||
/* Read all eraseblocks, 1 page at a time */
|
||||
printk(PRINT_PREF "testing page read speed\n");
|
||||
start_timing();
|
||||
for (i = 0; i < ebcnt; ++i) {
|
||||
if (bbt[i])
|
||||
continue;
|
||||
err = read_eraseblock_by_page(i);
|
||||
if (err)
|
||||
goto out;
|
||||
cond_resched();
|
||||
}
|
||||
stop_timing();
|
||||
speed = calc_speed();
|
||||
printk(PRINT_PREF "page read speed is %ld KiB/s\n", speed);
|
||||
|
||||
err = erase_whole_device();
|
||||
if (err)
|
||||
goto out;
|
||||
|
||||
/* Write all eraseblocks, 2 pages at a time */
|
||||
printk(PRINT_PREF "testing 2 page write speed\n");
|
||||
start_timing();
|
||||
for (i = 0; i < ebcnt; ++i) {
|
||||
if (bbt[i])
|
||||
continue;
|
||||
err = write_eraseblock_by_2pages(i);
|
||||
if (err)
|
||||
goto out;
|
||||
cond_resched();
|
||||
}
|
||||
stop_timing();
|
||||
speed = calc_speed();
|
||||
printk(PRINT_PREF "2 page write speed is %ld KiB/s\n", speed);
|
||||
|
||||
/* Read all eraseblocks, 2 pages at a time */
|
||||
printk(PRINT_PREF "testing 2 page read speed\n");
|
||||
start_timing();
|
||||
for (i = 0; i < ebcnt; ++i) {
|
||||
if (bbt[i])
|
||||
continue;
|
||||
err = read_eraseblock_by_2pages(i);
|
||||
if (err)
|
||||
goto out;
|
||||
cond_resched();
|
||||
}
|
||||
stop_timing();
|
||||
speed = calc_speed();
|
||||
printk(PRINT_PREF "2 page read speed is %ld KiB/s\n", speed);
|
||||
|
||||
/* Erase all eraseblocks */
|
||||
printk(PRINT_PREF "Testing erase speed\n");
|
||||
start_timing();
|
||||
for (i = 0; i < ebcnt; ++i) {
|
||||
if (bbt[i])
|
||||
continue;
|
||||
err = erase_eraseblock(i);
|
||||
if (err)
|
||||
goto out;
|
||||
cond_resched();
|
||||
}
|
||||
stop_timing();
|
||||
speed = calc_speed();
|
||||
printk(PRINT_PREF "erase speed is %ld KiB/s\n", speed);
|
||||
|
||||
printk(PRINT_PREF "finished\n");
|
||||
out:
|
||||
kfree(iobuf);
|
||||
kfree(bbt);
|
||||
put_mtd_device(mtd);
|
||||
if (err)
|
||||
printk(PRINT_PREF "error %d occurred\n", err);
|
||||
printk(KERN_INFO "=================================================\n");
|
||||
return err;
|
||||
}
|
||||
module_init(mtd_speedtest_init);
|
||||
|
||||
static void __exit mtd_speedtest_exit(void)
|
||||
{
|
||||
return;
|
||||
}
|
||||
module_exit(mtd_speedtest_exit);
|
||||
|
||||
MODULE_DESCRIPTION("Speed test module");
|
||||
MODULE_AUTHOR("Adrian Hunter");
|
||||
MODULE_LICENSE("GPL");
|
330
drivers/mtd/tests/mtd_stresstest.c
Normal file
330
drivers/mtd/tests/mtd_stresstest.c
Normal file
@ -0,0 +1,330 @@
|
||||
/*
|
||||
* Copyright (C) 2006-2008 Nokia Corporation
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 as published by
|
||||
* the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program; see the file COPYING. If not, write to the Free Software
|
||||
* Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
*
|
||||
* Test random reads, writes and erases on MTD device.
|
||||
*
|
||||
* Author: Adrian Hunter <ext-adrian.hunter@nokia.com>
|
||||
*/
|
||||
|
||||
#include <linux/init.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/moduleparam.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/mtd/mtd.h>
|
||||
#include <linux/sched.h>
|
||||
#include <linux/vmalloc.h>
|
||||
|
||||
#define PRINT_PREF KERN_INFO "mtd_stresstest: "
|
||||
|
||||
static int dev;
|
||||
module_param(dev, int, S_IRUGO);
|
||||
MODULE_PARM_DESC(dev, "MTD device number to use");
|
||||
|
||||
static int count = 10000;
|
||||
module_param(count, int, S_IRUGO);
|
||||
MODULE_PARM_DESC(count, "Number of operations to do (default is 10000)");
|
||||
|
||||
static struct mtd_info *mtd;
|
||||
static unsigned char *writebuf;
|
||||
static unsigned char *readbuf;
|
||||
static unsigned char *bbt;
|
||||
static int *offsets;
|
||||
|
||||
static int pgsize;
|
||||
static int bufsize;
|
||||
static int ebcnt;
|
||||
static int pgcnt;
|
||||
static unsigned long next = 1;
|
||||
|
||||
static inline unsigned int simple_rand(void)
|
||||
{
|
||||
next = next * 1103515245 + 12345;
|
||||
return (unsigned int)((next / 65536) % 32768);
|
||||
}
|
||||
|
||||
static inline void simple_srand(unsigned long seed)
|
||||
{
|
||||
next = seed;
|
||||
}
|
||||
|
||||
static int rand_eb(void)
|
||||
{
|
||||
int eb;
|
||||
|
||||
again:
|
||||
if (ebcnt < 32768)
|
||||
eb = simple_rand();
|
||||
else
|
||||
eb = (simple_rand() << 15) | simple_rand();
|
||||
/* Read or write up 2 eraseblocks at a time - hence 'ebcnt - 1' */
|
||||
eb %= (ebcnt - 1);
|
||||
if (bbt[eb])
|
||||
goto again;
|
||||
return eb;
|
||||
}
|
||||
|
||||
static int rand_offs(void)
|
||||
{
|
||||
int offs;
|
||||
|
||||
if (bufsize < 32768)
|
||||
offs = simple_rand();
|
||||
else
|
||||
offs = (simple_rand() << 15) | simple_rand();
|
||||
offs %= bufsize;
|
||||
return offs;
|
||||
}
|
||||
|
||||
static int rand_len(int offs)
|
||||
{
|
||||
int len;
|
||||
|
||||
if (bufsize < 32768)
|
||||
len = simple_rand();
|
||||
else
|
||||
len = (simple_rand() << 15) | simple_rand();
|
||||
len %= (bufsize - offs);
|
||||
return len;
|
||||
}
|
||||
|
||||
static int erase_eraseblock(int ebnum)
|
||||
{
|
||||
int err;
|
||||
struct erase_info ei;
|
||||
loff_t addr = ebnum * mtd->erasesize;
|
||||
|
||||
memset(&ei, 0, sizeof(struct erase_info));
|
||||
ei.mtd = mtd;
|
||||
ei.addr = addr;
|
||||
ei.len = mtd->erasesize;
|
||||
|
||||
err = mtd->erase(mtd, &ei);
|
||||
if (unlikely(err)) {
|
||||
printk(PRINT_PREF "error %d while erasing EB %d\n", err, ebnum);
|
||||
return err;
|
||||
}
|
||||
|
||||
if (unlikely(ei.state == MTD_ERASE_FAILED)) {
|
||||
printk(PRINT_PREF "some erase error occurred at EB %d\n",
|
||||
ebnum);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int is_block_bad(int ebnum)
|
||||
{
|
||||
loff_t addr = ebnum * mtd->erasesize;
|
||||
int ret;
|
||||
|
||||
ret = mtd->block_isbad(mtd, addr);
|
||||
if (ret)
|
||||
printk(PRINT_PREF "block %d is bad\n", ebnum);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int do_read(void)
|
||||
{
|
||||
size_t read = 0;
|
||||
int eb = rand_eb();
|
||||
int offs = rand_offs();
|
||||
int len = rand_len(offs), err;
|
||||
loff_t addr;
|
||||
|
||||
if (bbt[eb + 1]) {
|
||||
if (offs >= mtd->erasesize)
|
||||
offs -= mtd->erasesize;
|
||||
if (offs + len > mtd->erasesize)
|
||||
len = mtd->erasesize - offs;
|
||||
}
|
||||
addr = eb * mtd->erasesize + offs;
|
||||
err = mtd->read(mtd, addr, len, &read, readbuf);
|
||||
if (err == -EUCLEAN)
|
||||
err = 0;
|
||||
if (unlikely(err || read != len)) {
|
||||
printk(PRINT_PREF "error: read failed at 0x%llx\n",
|
||||
(long long)addr);
|
||||
if (!err)
|
||||
err = -EINVAL;
|
||||
return err;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int do_write(void)
|
||||
{
|
||||
int eb = rand_eb(), offs, err, len;
|
||||
size_t written = 0;
|
||||
loff_t addr;
|
||||
|
||||
offs = offsets[eb];
|
||||
if (offs >= mtd->erasesize) {
|
||||
err = erase_eraseblock(eb);
|
||||
if (err)
|
||||
return err;
|
||||
offs = offsets[eb] = 0;
|
||||
}
|
||||
len = rand_len(offs);
|
||||
len = ((len + pgsize - 1) / pgsize) * pgsize;
|
||||
if (offs + len > mtd->erasesize) {
|
||||
if (bbt[eb + 1])
|
||||
len = mtd->erasesize - offs;
|
||||
else {
|
||||
err = erase_eraseblock(eb + 1);
|
||||
if (err)
|
||||
return err;
|
||||
offsets[eb + 1] = 0;
|
||||
}
|
||||
}
|
||||
addr = eb * mtd->erasesize + offs;
|
||||
err = mtd->write(mtd, addr, len, &written, writebuf);
|
||||
if (unlikely(err || written != len)) {
|
||||
printk(PRINT_PREF "error: write failed at 0x%llx\n",
|
||||
(long long)addr);
|
||||
if (!err)
|
||||
err = -EINVAL;
|
||||
return err;
|
||||
}
|
||||
offs += len;
|
||||
while (offs > mtd->erasesize) {
|
||||
offsets[eb++] = mtd->erasesize;
|
||||
offs -= mtd->erasesize;
|
||||
}
|
||||
offsets[eb] = offs;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int do_operation(void)
|
||||
{
|
||||
if (simple_rand() & 1)
|
||||
return do_read();
|
||||
else
|
||||
return do_write();
|
||||
}
|
||||
|
||||
static int scan_for_bad_eraseblocks(void)
|
||||
{
|
||||
int i, bad = 0;
|
||||
|
||||
bbt = kmalloc(ebcnt, GFP_KERNEL);
|
||||
if (!bbt) {
|
||||
printk(PRINT_PREF "error: cannot allocate memory\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
memset(bbt, 0 , ebcnt);
|
||||
|
||||
printk(PRINT_PREF "scanning for bad eraseblocks\n");
|
||||
for (i = 0; i < ebcnt; ++i) {
|
||||
bbt[i] = is_block_bad(i) ? 1 : 0;
|
||||
if (bbt[i])
|
||||
bad += 1;
|
||||
cond_resched();
|
||||
}
|
||||
printk(PRINT_PREF "scanned %d eraseblocks, %d are bad\n", i, bad);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int __init mtd_stresstest_init(void)
|
||||
{
|
||||
int err;
|
||||
int i, op;
|
||||
uint64_t tmp;
|
||||
|
||||
printk(KERN_INFO "\n");
|
||||
printk(KERN_INFO "=================================================\n");
|
||||
printk(PRINT_PREF "MTD device: %d\n", dev);
|
||||
|
||||
mtd = get_mtd_device(NULL, dev);
|
||||
if (IS_ERR(mtd)) {
|
||||
err = PTR_ERR(mtd);
|
||||
printk(PRINT_PREF "error: cannot get MTD device\n");
|
||||
return err;
|
||||
}
|
||||
|
||||
if (mtd->writesize == 1) {
|
||||
printk(PRINT_PREF "not NAND flash, assume page size is 512 "
|
||||
"bytes.\n");
|
||||
pgsize = 512;
|
||||
} else
|
||||
pgsize = mtd->writesize;
|
||||
|
||||
tmp = mtd->size;
|
||||
do_div(tmp, mtd->erasesize);
|
||||
ebcnt = tmp;
|
||||
pgcnt = mtd->erasesize / mtd->writesize;
|
||||
|
||||
printk(PRINT_PREF "MTD device size %llu, eraseblock size %u, "
|
||||
"page size %u, count of eraseblocks %u, pages per "
|
||||
"eraseblock %u, OOB size %u\n",
|
||||
(unsigned long long)mtd->size, mtd->erasesize,
|
||||
pgsize, ebcnt, pgcnt, mtd->oobsize);
|
||||
|
||||
/* Read or write up 2 eraseblocks at a time */
|
||||
bufsize = mtd->erasesize * 2;
|
||||
|
||||
err = -ENOMEM;
|
||||
readbuf = vmalloc(bufsize);
|
||||
writebuf = vmalloc(bufsize);
|
||||
offsets = kmalloc(ebcnt * sizeof(int), GFP_KERNEL);
|
||||
if (!readbuf || !writebuf || !offsets) {
|
||||
printk(PRINT_PREF "error: cannot allocate memory\n");
|
||||
goto out;
|
||||
}
|
||||
for (i = 0; i < ebcnt; i++)
|
||||
offsets[i] = mtd->erasesize;
|
||||
simple_srand(current->pid);
|
||||
for (i = 0; i < bufsize; i++)
|
||||
writebuf[i] = simple_rand();
|
||||
|
||||
err = scan_for_bad_eraseblocks();
|
||||
if (err)
|
||||
goto out;
|
||||
|
||||
/* Do operations */
|
||||
printk(PRINT_PREF "doing operations\n");
|
||||
for (op = 0; op < count; op++) {
|
||||
if ((op & 1023) == 0)
|
||||
printk(PRINT_PREF "%d operations done\n", op);
|
||||
err = do_operation();
|
||||
if (err)
|
||||
goto out;
|
||||
cond_resched();
|
||||
}
|
||||
printk(PRINT_PREF "finished, %d operations done\n", op);
|
||||
|
||||
out:
|
||||
kfree(offsets);
|
||||
kfree(bbt);
|
||||
vfree(writebuf);
|
||||
vfree(readbuf);
|
||||
put_mtd_device(mtd);
|
||||
if (err)
|
||||
printk(PRINT_PREF "error %d occurred\n", err);
|
||||
printk(KERN_INFO "=================================================\n");
|
||||
return err;
|
||||
}
|
||||
module_init(mtd_stresstest_init);
|
||||
|
||||
static void __exit mtd_stresstest_exit(void)
|
||||
{
|
||||
return;
|
||||
}
|
||||
module_exit(mtd_stresstest_exit);
|
||||
|
||||
MODULE_DESCRIPTION("Stress test module");
|
||||
MODULE_AUTHOR("Adrian Hunter");
|
||||
MODULE_LICENSE("GPL");
|
525
drivers/mtd/tests/mtd_subpagetest.c
Normal file
525
drivers/mtd/tests/mtd_subpagetest.c
Normal file
@ -0,0 +1,525 @@
|
||||
/*
|
||||
* Copyright (C) 2006-2007 Nokia Corporation
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 as published by
|
||||
* the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program; see the file COPYING. If not, write to the Free Software
|
||||
* Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
*
|
||||
* Test sub-page read and write on MTD device.
|
||||
* Author: Adrian Hunter <ext-adrian.hunter@nokia.com>
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/init.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/moduleparam.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/mtd/mtd.h>
|
||||
#include <linux/sched.h>
|
||||
|
||||
#define PRINT_PREF KERN_INFO "mtd_subpagetest: "
|
||||
|
||||
static int dev;
|
||||
module_param(dev, int, S_IRUGO);
|
||||
MODULE_PARM_DESC(dev, "MTD device number to use");
|
||||
|
||||
static struct mtd_info *mtd;
|
||||
static unsigned char *writebuf;
|
||||
static unsigned char *readbuf;
|
||||
static unsigned char *bbt;
|
||||
|
||||
static int subpgsize;
|
||||
static int bufsize;
|
||||
static int ebcnt;
|
||||
static int pgcnt;
|
||||
static int errcnt;
|
||||
static unsigned long next = 1;
|
||||
|
||||
static inline unsigned int simple_rand(void)
|
||||
{
|
||||
next = next * 1103515245 + 12345;
|
||||
return (unsigned int)((next / 65536) % 32768);
|
||||
}
|
||||
|
||||
static inline void simple_srand(unsigned long seed)
|
||||
{
|
||||
next = seed;
|
||||
}
|
||||
|
||||
static void set_random_data(unsigned char *buf, size_t len)
|
||||
{
|
||||
size_t i;
|
||||
|
||||
for (i = 0; i < len; ++i)
|
||||
buf[i] = simple_rand();
|
||||
}
|
||||
|
||||
static inline void clear_data(unsigned char *buf, size_t len)
|
||||
{
|
||||
memset(buf, 0, len);
|
||||
}
|
||||
|
||||
static int erase_eraseblock(int ebnum)
|
||||
{
|
||||
int err;
|
||||
struct erase_info ei;
|
||||
loff_t addr = ebnum * mtd->erasesize;
|
||||
|
||||
memset(&ei, 0, sizeof(struct erase_info));
|
||||
ei.mtd = mtd;
|
||||
ei.addr = addr;
|
||||
ei.len = mtd->erasesize;
|
||||
|
||||
err = mtd->erase(mtd, &ei);
|
||||
if (err) {
|
||||
printk(PRINT_PREF "error %d while erasing EB %d\n", err, ebnum);
|
||||
return err;
|
||||
}
|
||||
|
||||
if (ei.state == MTD_ERASE_FAILED) {
|
||||
printk(PRINT_PREF "some erase error occurred at EB %d\n",
|
||||
ebnum);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int erase_whole_device(void)
|
||||
{
|
||||
int err;
|
||||
unsigned int i;
|
||||
|
||||
printk(PRINT_PREF "erasing whole device\n");
|
||||
for (i = 0; i < ebcnt; ++i) {
|
||||
if (bbt[i])
|
||||
continue;
|
||||
err = erase_eraseblock(i);
|
||||
if (err)
|
||||
return err;
|
||||
cond_resched();
|
||||
}
|
||||
printk(PRINT_PREF "erased %u eraseblocks\n", i);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int write_eraseblock(int ebnum)
|
||||
{
|
||||
size_t written = 0;
|
||||
int err = 0;
|
||||
loff_t addr = ebnum * mtd->erasesize;
|
||||
|
||||
set_random_data(writebuf, subpgsize);
|
||||
err = mtd->write(mtd, addr, subpgsize, &written, writebuf);
|
||||
if (unlikely(err || written != subpgsize)) {
|
||||
printk(PRINT_PREF "error: write failed at %#llx\n",
|
||||
(long long)addr);
|
||||
if (written != subpgsize) {
|
||||
printk(PRINT_PREF " write size: %#x\n", subpgsize);
|
||||
printk(PRINT_PREF " written: %#zx\n", written);
|
||||
}
|
||||
return err ? err : -1;
|
||||
}
|
||||
|
||||
addr += subpgsize;
|
||||
|
||||
set_random_data(writebuf, subpgsize);
|
||||
err = mtd->write(mtd, addr, subpgsize, &written, writebuf);
|
||||
if (unlikely(err || written != subpgsize)) {
|
||||
printk(PRINT_PREF "error: write failed at %#llx\n",
|
||||
(long long)addr);
|
||||
if (written != subpgsize) {
|
||||
printk(PRINT_PREF " write size: %#x\n", subpgsize);
|
||||
printk(PRINT_PREF " written: %#zx\n", written);
|
||||
}
|
||||
return err ? err : -1;
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static int write_eraseblock2(int ebnum)
|
||||
{
|
||||
size_t written = 0;
|
||||
int err = 0, k;
|
||||
loff_t addr = ebnum * mtd->erasesize;
|
||||
|
||||
for (k = 1; k < 33; ++k) {
|
||||
if (addr + (subpgsize * k) > (ebnum + 1) * mtd->erasesize)
|
||||
break;
|
||||
set_random_data(writebuf, subpgsize * k);
|
||||
err = mtd->write(mtd, addr, subpgsize * k, &written, writebuf);
|
||||
if (unlikely(err || written != subpgsize * k)) {
|
||||
printk(PRINT_PREF "error: write failed at %#llx\n",
|
||||
(long long)addr);
|
||||
if (written != subpgsize) {
|
||||
printk(PRINT_PREF " write size: %#x\n",
|
||||
subpgsize * k);
|
||||
printk(PRINT_PREF " written: %#08zx\n",
|
||||
written);
|
||||
}
|
||||
return err ? err : -1;
|
||||
}
|
||||
addr += subpgsize * k;
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static void print_subpage(unsigned char *p)
|
||||
{
|
||||
int i, j;
|
||||
|
||||
for (i = 0; i < subpgsize; ) {
|
||||
for (j = 0; i < subpgsize && j < 32; ++i, ++j)
|
||||
printk("%02x", *p++);
|
||||
printk("\n");
|
||||
}
|
||||
}
|
||||
|
||||
static int verify_eraseblock(int ebnum)
|
||||
{
|
||||
size_t read = 0;
|
||||
int err = 0;
|
||||
loff_t addr = ebnum * mtd->erasesize;
|
||||
|
||||
set_random_data(writebuf, subpgsize);
|
||||
clear_data(readbuf, subpgsize);
|
||||
read = 0;
|
||||
err = mtd->read(mtd, addr, subpgsize, &read, readbuf);
|
||||
if (unlikely(err || read != subpgsize)) {
|
||||
if (err == -EUCLEAN && read == subpgsize) {
|
||||
printk(PRINT_PREF "ECC correction at %#llx\n",
|
||||
(long long)addr);
|
||||
err = 0;
|
||||
} else {
|
||||
printk(PRINT_PREF "error: read failed at %#llx\n",
|
||||
(long long)addr);
|
||||
return err ? err : -1;
|
||||
}
|
||||
}
|
||||
if (unlikely(memcmp(readbuf, writebuf, subpgsize))) {
|
||||
printk(PRINT_PREF "error: verify failed at %#llx\n",
|
||||
(long long)addr);
|
||||
printk(PRINT_PREF "------------- written----------------\n");
|
||||
print_subpage(writebuf);
|
||||
printk(PRINT_PREF "------------- read ------------------\n");
|
||||
print_subpage(readbuf);
|
||||
printk(PRINT_PREF "-------------------------------------\n");
|
||||
errcnt += 1;
|
||||
}
|
||||
|
||||
addr += subpgsize;
|
||||
|
||||
set_random_data(writebuf, subpgsize);
|
||||
clear_data(readbuf, subpgsize);
|
||||
read = 0;
|
||||
err = mtd->read(mtd, addr, subpgsize, &read, readbuf);
|
||||
if (unlikely(err || read != subpgsize)) {
|
||||
if (err == -EUCLEAN && read == subpgsize) {
|
||||
printk(PRINT_PREF "ECC correction at %#llx\n",
|
||||
(long long)addr);
|
||||
err = 0;
|
||||
} else {
|
||||
printk(PRINT_PREF "error: read failed at %#llx\n",
|
||||
(long long)addr);
|
||||
return err ? err : -1;
|
||||
}
|
||||
}
|
||||
if (unlikely(memcmp(readbuf, writebuf, subpgsize))) {
|
||||
printk(PRINT_PREF "error: verify failed at %#llx\n",
|
||||
(long long)addr);
|
||||
printk(PRINT_PREF "------------- written----------------\n");
|
||||
print_subpage(writebuf);
|
||||
printk(PRINT_PREF "------------- read ------------------\n");
|
||||
print_subpage(readbuf);
|
||||
printk(PRINT_PREF "-------------------------------------\n");
|
||||
errcnt += 1;
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static int verify_eraseblock2(int ebnum)
|
||||
{
|
||||
size_t read = 0;
|
||||
int err = 0, k;
|
||||
loff_t addr = ebnum * mtd->erasesize;
|
||||
|
||||
for (k = 1; k < 33; ++k) {
|
||||
if (addr + (subpgsize * k) > (ebnum + 1) * mtd->erasesize)
|
||||
break;
|
||||
set_random_data(writebuf, subpgsize * k);
|
||||
clear_data(readbuf, subpgsize * k);
|
||||
read = 0;
|
||||
err = mtd->read(mtd, addr, subpgsize * k, &read, readbuf);
|
||||
if (unlikely(err || read != subpgsize * k)) {
|
||||
if (err == -EUCLEAN && read == subpgsize * k) {
|
||||
printk(PRINT_PREF "ECC correction at %#llx\n",
|
||||
(long long)addr);
|
||||
err = 0;
|
||||
} else {
|
||||
printk(PRINT_PREF "error: read failed at "
|
||||
"%#llx\n", (long long)addr);
|
||||
return err ? err : -1;
|
||||
}
|
||||
}
|
||||
if (unlikely(memcmp(readbuf, writebuf, subpgsize * k))) {
|
||||
printk(PRINT_PREF "error: verify failed at %#llx\n",
|
||||
(long long)addr);
|
||||
errcnt += 1;
|
||||
}
|
||||
addr += subpgsize * k;
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static int verify_eraseblock_ff(int ebnum)
|
||||
{
|
||||
uint32_t j;
|
||||
size_t read = 0;
|
||||
int err = 0;
|
||||
loff_t addr = ebnum * mtd->erasesize;
|
||||
|
||||
memset(writebuf, 0xff, subpgsize);
|
||||
for (j = 0; j < mtd->erasesize / subpgsize; ++j) {
|
||||
clear_data(readbuf, subpgsize);
|
||||
read = 0;
|
||||
err = mtd->read(mtd, addr, subpgsize, &read, readbuf);
|
||||
if (unlikely(err || read != subpgsize)) {
|
||||
if (err == -EUCLEAN && read == subpgsize) {
|
||||
printk(PRINT_PREF "ECC correction at %#llx\n",
|
||||
(long long)addr);
|
||||
err = 0;
|
||||
} else {
|
||||
printk(PRINT_PREF "error: read failed at "
|
||||
"%#llx\n", (long long)addr);
|
||||
return err ? err : -1;
|
||||
}
|
||||
}
|
||||
if (unlikely(memcmp(readbuf, writebuf, subpgsize))) {
|
||||
printk(PRINT_PREF "error: verify 0xff failed at "
|
||||
"%#llx\n", (long long)addr);
|
||||
errcnt += 1;
|
||||
}
|
||||
addr += subpgsize;
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static int verify_all_eraseblocks_ff(void)
|
||||
{
|
||||
int err;
|
||||
unsigned int i;
|
||||
|
||||
printk(PRINT_PREF "verifying all eraseblocks for 0xff\n");
|
||||
for (i = 0; i < ebcnt; ++i) {
|
||||
if (bbt[i])
|
||||
continue;
|
||||
err = verify_eraseblock_ff(i);
|
||||
if (err)
|
||||
return err;
|
||||
if (i % 256 == 0)
|
||||
printk(PRINT_PREF "verified up to eraseblock %u\n", i);
|
||||
cond_resched();
|
||||
}
|
||||
printk(PRINT_PREF "verified %u eraseblocks\n", i);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int is_block_bad(int ebnum)
|
||||
{
|
||||
loff_t addr = ebnum * mtd->erasesize;
|
||||
int ret;
|
||||
|
||||
ret = mtd->block_isbad(mtd, addr);
|
||||
if (ret)
|
||||
printk(PRINT_PREF "block %d is bad\n", ebnum);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int scan_for_bad_eraseblocks(void)
|
||||
{
|
||||
int i, bad = 0;
|
||||
|
||||
bbt = kmalloc(ebcnt, GFP_KERNEL);
|
||||
if (!bbt) {
|
||||
printk(PRINT_PREF "error: cannot allocate memory\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
memset(bbt, 0 , ebcnt);
|
||||
|
||||
printk(PRINT_PREF "scanning for bad eraseblocks\n");
|
||||
for (i = 0; i < ebcnt; ++i) {
|
||||
bbt[i] = is_block_bad(i) ? 1 : 0;
|
||||
if (bbt[i])
|
||||
bad += 1;
|
||||
cond_resched();
|
||||
}
|
||||
printk(PRINT_PREF "scanned %d eraseblocks, %d are bad\n", i, bad);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int __init mtd_subpagetest_init(void)
|
||||
{
|
||||
int err = 0;
|
||||
uint32_t i;
|
||||
uint64_t tmp;
|
||||
|
||||
printk(KERN_INFO "\n");
|
||||
printk(KERN_INFO "=================================================\n");
|
||||
printk(PRINT_PREF "MTD device: %d\n", dev);
|
||||
|
||||
mtd = get_mtd_device(NULL, dev);
|
||||
if (IS_ERR(mtd)) {
|
||||
err = PTR_ERR(mtd);
|
||||
printk(PRINT_PREF "error: cannot get MTD device\n");
|
||||
return err;
|
||||
}
|
||||
|
||||
if (mtd->type != MTD_NANDFLASH) {
|
||||
printk(PRINT_PREF "this test requires NAND flash\n");
|
||||
goto out;
|
||||
}
|
||||
|
||||
subpgsize = mtd->writesize >> mtd->subpage_sft;
|
||||
printk(PRINT_PREF "MTD device size %llu, eraseblock size %u, "
|
||||
"page size %u, subpage size %u, count of eraseblocks %u, "
|
||||
"pages per eraseblock %u, OOB size %u\n",
|
||||
(unsigned long long)mtd->size, mtd->erasesize,
|
||||
mtd->writesize, subpgsize, ebcnt, pgcnt, mtd->oobsize);
|
||||
|
||||
err = -ENOMEM;
|
||||
bufsize = subpgsize * 32;
|
||||
writebuf = kmalloc(bufsize, GFP_KERNEL);
|
||||
if (!writebuf) {
|
||||
printk(PRINT_PREF "error: cannot allocate memory\n");
|
||||
goto out;
|
||||
}
|
||||
readbuf = kmalloc(bufsize, GFP_KERNEL);
|
||||
if (!readbuf) {
|
||||
printk(PRINT_PREF "error: cannot allocate memory\n");
|
||||
goto out;
|
||||
}
|
||||
|
||||
tmp = mtd->size;
|
||||
do_div(tmp, mtd->erasesize);
|
||||
ebcnt = tmp;
|
||||
pgcnt = mtd->erasesize / mtd->writesize;
|
||||
|
||||
err = scan_for_bad_eraseblocks();
|
||||
if (err)
|
||||
goto out;
|
||||
|
||||
err = erase_whole_device();
|
||||
if (err)
|
||||
goto out;
|
||||
|
||||
printk(PRINT_PREF "writing whole device\n");
|
||||
simple_srand(1);
|
||||
for (i = 0; i < ebcnt; ++i) {
|
||||
if (bbt[i])
|
||||
continue;
|
||||
err = write_eraseblock(i);
|
||||
if (unlikely(err))
|
||||
goto out;
|
||||
if (i % 256 == 0)
|
||||
printk(PRINT_PREF "written up to eraseblock %u\n", i);
|
||||
cond_resched();
|
||||
}
|
||||
printk(PRINT_PREF "written %u eraseblocks\n", i);
|
||||
|
||||
simple_srand(1);
|
||||
printk(PRINT_PREF "verifying all eraseblocks\n");
|
||||
for (i = 0; i < ebcnt; ++i) {
|
||||
if (bbt[i])
|
||||
continue;
|
||||
err = verify_eraseblock(i);
|
||||
if (unlikely(err))
|
||||
goto out;
|
||||
if (i % 256 == 0)
|
||||
printk(PRINT_PREF "verified up to eraseblock %u\n", i);
|
||||
cond_resched();
|
||||
}
|
||||
printk(PRINT_PREF "verified %u eraseblocks\n", i);
|
||||
|
||||
err = erase_whole_device();
|
||||
if (err)
|
||||
goto out;
|
||||
|
||||
err = verify_all_eraseblocks_ff();
|
||||
if (err)
|
||||
goto out;
|
||||
|
||||
/* Write all eraseblocks */
|
||||
simple_srand(3);
|
||||
printk(PRINT_PREF "writing whole device\n");
|
||||
for (i = 0; i < ebcnt; ++i) {
|
||||
if (bbt[i])
|
||||
continue;
|
||||
err = write_eraseblock2(i);
|
||||
if (unlikely(err))
|
||||
goto out;
|
||||
if (i % 256 == 0)
|
||||
printk(PRINT_PREF "written up to eraseblock %u\n", i);
|
||||
cond_resched();
|
||||
}
|
||||
printk(PRINT_PREF "written %u eraseblocks\n", i);
|
||||
|
||||
/* Check all eraseblocks */
|
||||
simple_srand(3);
|
||||
printk(PRINT_PREF "verifying all eraseblocks\n");
|
||||
for (i = 0; i < ebcnt; ++i) {
|
||||
if (bbt[i])
|
||||
continue;
|
||||
err = verify_eraseblock2(i);
|
||||
if (unlikely(err))
|
||||
goto out;
|
||||
if (i % 256 == 0)
|
||||
printk(PRINT_PREF "verified up to eraseblock %u\n", i);
|
||||
cond_resched();
|
||||
}
|
||||
printk(PRINT_PREF "verified %u eraseblocks\n", i);
|
||||
|
||||
err = erase_whole_device();
|
||||
if (err)
|
||||
goto out;
|
||||
|
||||
err = verify_all_eraseblocks_ff();
|
||||
if (err)
|
||||
goto out;
|
||||
|
||||
printk(PRINT_PREF "finished with %d errors\n", errcnt);
|
||||
|
||||
out:
|
||||
kfree(bbt);
|
||||
kfree(readbuf);
|
||||
kfree(writebuf);
|
||||
put_mtd_device(mtd);
|
||||
if (err)
|
||||
printk(PRINT_PREF "error %d occurred\n", err);
|
||||
printk(KERN_INFO "=================================================\n");
|
||||
return err;
|
||||
}
|
||||
module_init(mtd_subpagetest_init);
|
||||
|
||||
static void __exit mtd_subpagetest_exit(void)
|
||||
{
|
||||
return;
|
||||
}
|
||||
module_exit(mtd_subpagetest_exit);
|
||||
|
||||
MODULE_DESCRIPTION("Subpage test module");
|
||||
MODULE_AUTHOR("Adrian Hunter");
|
||||
MODULE_LICENSE("GPL");
|
530
drivers/mtd/tests/mtd_torturetest.c
Normal file
530
drivers/mtd/tests/mtd_torturetest.c
Normal file
@ -0,0 +1,530 @@
|
||||
/*
|
||||
* Copyright (C) 2006-2008 Artem Bityutskiy
|
||||
* Copyright (C) 2006-2008 Jarkko Lavinen
|
||||
* Copyright (C) 2006-2008 Adrian Hunter
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 as published by
|
||||
* the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program; see the file COPYING. If not, write to the Free Software
|
||||
* Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
*
|
||||
* Authors: Artem Bityutskiy, Jarkko Lavinen, Adria Hunter
|
||||
*
|
||||
* WARNING: this test program may kill your flash and your device. Do not
|
||||
* use it unless you know what you do. Authors are not responsible for any
|
||||
* damage caused by this program.
|
||||
*/
|
||||
|
||||
#include <linux/init.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/moduleparam.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/mtd/mtd.h>
|
||||
#include <linux/sched.h>
|
||||
|
||||
#define PRINT_PREF KERN_INFO "mtd_torturetest: "
|
||||
#define RETRIES 3
|
||||
|
||||
static int eb = 8;
|
||||
module_param(eb, int, S_IRUGO);
|
||||
MODULE_PARM_DESC(eb, "eraseblock number within the selected MTD device");
|
||||
|
||||
static int ebcnt = 32;
|
||||
module_param(ebcnt, int, S_IRUGO);
|
||||
MODULE_PARM_DESC(ebcnt, "number of consecutive eraseblocks to torture");
|
||||
|
||||
static int pgcnt;
|
||||
module_param(pgcnt, int, S_IRUGO);
|
||||
MODULE_PARM_DESC(pgcnt, "number of pages per eraseblock to torture (0 => all)");
|
||||
|
||||
static int dev;
|
||||
module_param(dev, int, S_IRUGO);
|
||||
MODULE_PARM_DESC(dev, "MTD device number to use");
|
||||
|
||||
static int gran = 512;
|
||||
module_param(gran, int, S_IRUGO);
|
||||
MODULE_PARM_DESC(gran, "how often the status information should be printed");
|
||||
|
||||
static int check = 1;
|
||||
module_param(check, int, S_IRUGO);
|
||||
MODULE_PARM_DESC(check, "if the written data should be checked");
|
||||
|
||||
static unsigned int cycles_count;
|
||||
module_param(cycles_count, uint, S_IRUGO);
|
||||
MODULE_PARM_DESC(cycles_count, "how many erase cycles to do "
|
||||
"(infinite by default)");
|
||||
|
||||
static struct mtd_info *mtd;
|
||||
|
||||
/* This buffer contains 0x555555...0xAAAAAA... pattern */
|
||||
static unsigned char *patt_5A5;
|
||||
/* This buffer contains 0xAAAAAA...0x555555... pattern */
|
||||
static unsigned char *patt_A5A;
|
||||
/* This buffer contains all 0xFF bytes */
|
||||
static unsigned char *patt_FF;
|
||||
/* This a temporary buffer is use when checking data */
|
||||
static unsigned char *check_buf;
|
||||
/* How many erase cycles were done */
|
||||
static unsigned int erase_cycles;
|
||||
|
||||
static int pgsize;
|
||||
static struct timeval start, finish;
|
||||
|
||||
static void report_corrupt(unsigned char *read, unsigned char *written);
|
||||
|
||||
static inline void start_timing(void)
|
||||
{
|
||||
do_gettimeofday(&start);
|
||||
}
|
||||
|
||||
static inline void stop_timing(void)
|
||||
{
|
||||
do_gettimeofday(&finish);
|
||||
}
|
||||
|
||||
/*
|
||||
* Erase eraseblock number @ebnum.
|
||||
*/
|
||||
static inline int erase_eraseblock(int ebnum)
|
||||
{
|
||||
int err;
|
||||
struct erase_info ei;
|
||||
loff_t addr = ebnum * mtd->erasesize;
|
||||
|
||||
memset(&ei, 0, sizeof(struct erase_info));
|
||||
ei.mtd = mtd;
|
||||
ei.addr = addr;
|
||||
ei.len = mtd->erasesize;
|
||||
|
||||
err = mtd->erase(mtd, &ei);
|
||||
if (err) {
|
||||
printk(PRINT_PREF "error %d while erasing EB %d\n", err, ebnum);
|
||||
return err;
|
||||
}
|
||||
|
||||
if (ei.state == MTD_ERASE_FAILED) {
|
||||
printk(PRINT_PREF "some erase error occurred at EB %d\n",
|
||||
ebnum);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Check that the contents of eraseblock number @enbum is equivalent to the
|
||||
* @buf buffer.
|
||||
*/
|
||||
static inline int check_eraseblock(int ebnum, unsigned char *buf)
|
||||
{
|
||||
int err, retries = 0;
|
||||
size_t read = 0;
|
||||
loff_t addr = ebnum * mtd->erasesize;
|
||||
size_t len = mtd->erasesize;
|
||||
|
||||
if (pgcnt) {
|
||||
addr = (ebnum + 1) * mtd->erasesize - pgcnt * pgsize;
|
||||
len = pgcnt * pgsize;
|
||||
}
|
||||
|
||||
retry:
|
||||
err = mtd->read(mtd, addr, len, &read, check_buf);
|
||||
if (err == -EUCLEAN)
|
||||
printk(PRINT_PREF "single bit flip occurred at EB %d "
|
||||
"MTD reported that it was fixed.\n", ebnum);
|
||||
else if (err) {
|
||||
printk(PRINT_PREF "error %d while reading EB %d, "
|
||||
"read %zd\n", err, ebnum, read);
|
||||
return err;
|
||||
}
|
||||
|
||||
if (read != len) {
|
||||
printk(PRINT_PREF "failed to read %zd bytes from EB %d, "
|
||||
"read only %zd, but no error reported\n",
|
||||
len, ebnum, read);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
if (memcmp(buf, check_buf, len)) {
|
||||
printk(PRINT_PREF "read wrong data from EB %d\n", ebnum);
|
||||
report_corrupt(check_buf, buf);
|
||||
|
||||
if (retries++ < RETRIES) {
|
||||
/* Try read again */
|
||||
yield();
|
||||
printk(PRINT_PREF "re-try reading data from EB %d\n",
|
||||
ebnum);
|
||||
goto retry;
|
||||
} else {
|
||||
printk(PRINT_PREF "retried %d times, still errors, "
|
||||
"give-up\n", RETRIES);
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
if (retries != 0)
|
||||
printk(PRINT_PREF "only attempt number %d was OK (!!!)\n",
|
||||
retries);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline int write_pattern(int ebnum, void *buf)
|
||||
{
|
||||
int err;
|
||||
size_t written = 0;
|
||||
loff_t addr = ebnum * mtd->erasesize;
|
||||
size_t len = mtd->erasesize;
|
||||
|
||||
if (pgcnt) {
|
||||
addr = (ebnum + 1) * mtd->erasesize - pgcnt * pgsize;
|
||||
len = pgcnt * pgsize;
|
||||
}
|
||||
err = mtd->write(mtd, addr, len, &written, buf);
|
||||
if (err) {
|
||||
printk(PRINT_PREF "error %d while writing EB %d, written %zd"
|
||||
" bytes\n", err, ebnum, written);
|
||||
return err;
|
||||
}
|
||||
if (written != len) {
|
||||
printk(PRINT_PREF "written only %zd bytes of %zd, but no error"
|
||||
" reported\n", written, len);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int __init tort_init(void)
|
||||
{
|
||||
int err = 0, i, infinite = !cycles_count;
|
||||
int bad_ebs[ebcnt];
|
||||
|
||||
printk(KERN_INFO "\n");
|
||||
printk(KERN_INFO "=================================================\n");
|
||||
printk(PRINT_PREF "Warning: this program is trying to wear out your "
|
||||
"flash, stop it if this is not wanted.\n");
|
||||
printk(PRINT_PREF "MTD device: %d\n", dev);
|
||||
printk(PRINT_PREF "torture %d eraseblocks (%d-%d) of mtd%d\n",
|
||||
ebcnt, eb, eb + ebcnt - 1, dev);
|
||||
if (pgcnt)
|
||||
printk(PRINT_PREF "torturing just %d pages per eraseblock\n",
|
||||
pgcnt);
|
||||
printk(PRINT_PREF "write verify %s\n", check ? "enabled" : "disabled");
|
||||
|
||||
mtd = get_mtd_device(NULL, dev);
|
||||
if (IS_ERR(mtd)) {
|
||||
err = PTR_ERR(mtd);
|
||||
printk(PRINT_PREF "error: cannot get MTD device\n");
|
||||
return err;
|
||||
}
|
||||
|
||||
if (mtd->writesize == 1) {
|
||||
printk(PRINT_PREF "not NAND flash, assume page size is 512 "
|
||||
"bytes.\n");
|
||||
pgsize = 512;
|
||||
} else
|
||||
pgsize = mtd->writesize;
|
||||
|
||||
if (pgcnt && (pgcnt > mtd->erasesize / pgsize || pgcnt < 0)) {
|
||||
printk(PRINT_PREF "error: invalid pgcnt value %d\n", pgcnt);
|
||||
goto out_mtd;
|
||||
}
|
||||
|
||||
err = -ENOMEM;
|
||||
patt_5A5 = kmalloc(mtd->erasesize, GFP_KERNEL);
|
||||
if (!patt_5A5) {
|
||||
printk(PRINT_PREF "error: cannot allocate memory\n");
|
||||
goto out_mtd;
|
||||
}
|
||||
|
||||
patt_A5A = kmalloc(mtd->erasesize, GFP_KERNEL);
|
||||
if (!patt_A5A) {
|
||||
printk(PRINT_PREF "error: cannot allocate memory\n");
|
||||
goto out_patt_5A5;
|
||||
}
|
||||
|
||||
patt_FF = kmalloc(mtd->erasesize, GFP_KERNEL);
|
||||
if (!patt_FF) {
|
||||
printk(PRINT_PREF "error: cannot allocate memory\n");
|
||||
goto out_patt_A5A;
|
||||
}
|
||||
|
||||
check_buf = kmalloc(mtd->erasesize, GFP_KERNEL);
|
||||
if (!check_buf) {
|
||||
printk(PRINT_PREF "error: cannot allocate memory\n");
|
||||
goto out_patt_FF;
|
||||
}
|
||||
|
||||
err = 0;
|
||||
|
||||
/* Initialize patterns */
|
||||
memset(patt_FF, 0xFF, mtd->erasesize);
|
||||
for (i = 0; i < mtd->erasesize / pgsize; i++) {
|
||||
if (!(i & 1)) {
|
||||
memset(patt_5A5 + i * pgsize, 0x55, pgsize);
|
||||
memset(patt_A5A + i * pgsize, 0xAA, pgsize);
|
||||
} else {
|
||||
memset(patt_5A5 + i * pgsize, 0xAA, pgsize);
|
||||
memset(patt_A5A + i * pgsize, 0x55, pgsize);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Check if there is a bad eraseblock among those we are going to test.
|
||||
*/
|
||||
memset(&bad_ebs[0], 0, sizeof(int) * ebcnt);
|
||||
if (mtd->block_isbad) {
|
||||
for (i = eb; i < eb + ebcnt; i++) {
|
||||
err = mtd->block_isbad(mtd,
|
||||
(loff_t)i * mtd->erasesize);
|
||||
|
||||
if (err < 0) {
|
||||
printk(PRINT_PREF "block_isbad() returned %d "
|
||||
"for EB %d\n", err, i);
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (err) {
|
||||
printk("EB %d is bad. Skip it.\n", i);
|
||||
bad_ebs[i - eb] = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
start_timing();
|
||||
while (1) {
|
||||
int i;
|
||||
void *patt;
|
||||
|
||||
/* Erase all eraseblocks */
|
||||
for (i = eb; i < eb + ebcnt; i++) {
|
||||
if (bad_ebs[i - eb])
|
||||
continue;
|
||||
err = erase_eraseblock(i);
|
||||
if (err)
|
||||
goto out;
|
||||
cond_resched();
|
||||
}
|
||||
|
||||
/* Check if the eraseblocks contain only 0xFF bytes */
|
||||
if (check) {
|
||||
for (i = eb; i < eb + ebcnt; i++) {
|
||||
if (bad_ebs[i - eb])
|
||||
continue;
|
||||
err = check_eraseblock(i, patt_FF);
|
||||
if (err) {
|
||||
printk(PRINT_PREF "verify failed"
|
||||
" for 0xFF... pattern\n");
|
||||
goto out;
|
||||
}
|
||||
cond_resched();
|
||||
}
|
||||
}
|
||||
|
||||
/* Write the pattern */
|
||||
for (i = eb; i < eb + ebcnt; i++) {
|
||||
if (bad_ebs[i - eb])
|
||||
continue;
|
||||
if ((eb + erase_cycles) & 1)
|
||||
patt = patt_5A5;
|
||||
else
|
||||
patt = patt_A5A;
|
||||
err = write_pattern(i, patt);
|
||||
if (err)
|
||||
goto out;
|
||||
cond_resched();
|
||||
}
|
||||
|
||||
/* Verify what we wrote */
|
||||
if (check) {
|
||||
for (i = eb; i < eb + ebcnt; i++) {
|
||||
if (bad_ebs[i - eb])
|
||||
continue;
|
||||
if ((eb + erase_cycles) & 1)
|
||||
patt = patt_5A5;
|
||||
else
|
||||
patt = patt_A5A;
|
||||
err = check_eraseblock(i, patt);
|
||||
if (err) {
|
||||
printk(PRINT_PREF "verify failed for %s"
|
||||
" pattern\n",
|
||||
((eb + erase_cycles) & 1) ?
|
||||
"0x55AA55..." : "0xAA55AA...");
|
||||
goto out;
|
||||
}
|
||||
cond_resched();
|
||||
}
|
||||
}
|
||||
|
||||
erase_cycles += 1;
|
||||
|
||||
if (erase_cycles % gran == 0) {
|
||||
long ms;
|
||||
|
||||
stop_timing();
|
||||
ms = (finish.tv_sec - start.tv_sec) * 1000 +
|
||||
(finish.tv_usec - start.tv_usec) / 1000;
|
||||
printk(PRINT_PREF "%08u erase cycles done, took %lu "
|
||||
"milliseconds (%lu seconds)\n",
|
||||
erase_cycles, ms, ms / 1000);
|
||||
start_timing();
|
||||
}
|
||||
|
||||
if (!infinite && --cycles_count == 0)
|
||||
break;
|
||||
}
|
||||
out:
|
||||
|
||||
printk(PRINT_PREF "finished after %u erase cycles\n",
|
||||
erase_cycles);
|
||||
kfree(check_buf);
|
||||
out_patt_FF:
|
||||
kfree(patt_FF);
|
||||
out_patt_A5A:
|
||||
kfree(patt_A5A);
|
||||
out_patt_5A5:
|
||||
kfree(patt_5A5);
|
||||
out_mtd:
|
||||
put_mtd_device(mtd);
|
||||
if (err)
|
||||
printk(PRINT_PREF "error %d occurred during torturing\n", err);
|
||||
printk(KERN_INFO "=================================================\n");
|
||||
return err;
|
||||
}
|
||||
module_init(tort_init);
|
||||
|
||||
static void __exit tort_exit(void)
|
||||
{
|
||||
return;
|
||||
}
|
||||
module_exit(tort_exit);
|
||||
|
||||
static int countdiffs(unsigned char *buf, unsigned char *check_buf,
|
||||
unsigned offset, unsigned len, unsigned *bytesp,
|
||||
unsigned *bitsp);
|
||||
static void print_bufs(unsigned char *read, unsigned char *written, int start,
|
||||
int len);
|
||||
|
||||
/*
|
||||
* Report the detailed information about how the read EB differs from what was
|
||||
* written.
|
||||
*/
|
||||
static void report_corrupt(unsigned char *read, unsigned char *written)
|
||||
{
|
||||
int i;
|
||||
int bytes, bits, pages, first;
|
||||
int offset, len;
|
||||
size_t check_len = mtd->erasesize;
|
||||
|
||||
if (pgcnt)
|
||||
check_len = pgcnt * pgsize;
|
||||
|
||||
bytes = bits = pages = 0;
|
||||
for (i = 0; i < check_len; i += pgsize)
|
||||
if (countdiffs(written, read, i, pgsize, &bytes,
|
||||
&bits) >= 0)
|
||||
pages++;
|
||||
|
||||
printk(PRINT_PREF "verify fails on %d pages, %d bytes/%d bits\n",
|
||||
pages, bytes, bits);
|
||||
printk(PRINT_PREF "The following is a list of all differences between"
|
||||
" what was read from flash and what was expected\n");
|
||||
|
||||
for (i = 0; i < check_len; i += pgsize) {
|
||||
cond_resched();
|
||||
bytes = bits = 0;
|
||||
first = countdiffs(written, read, i, pgsize, &bytes,
|
||||
&bits);
|
||||
if (first < 0)
|
||||
continue;
|
||||
|
||||
printk("-------------------------------------------------------"
|
||||
"----------------------------------\n");
|
||||
|
||||
printk(PRINT_PREF "Page %zd has %d bytes/%d bits failing verify,"
|
||||
" starting at offset 0x%x\n",
|
||||
(mtd->erasesize - check_len + i) / pgsize,
|
||||
bytes, bits, first);
|
||||
|
||||
offset = first & ~0x7;
|
||||
len = ((first + bytes) | 0x7) + 1 - offset;
|
||||
|
||||
print_bufs(read, written, offset, len);
|
||||
}
|
||||
}
|
||||
|
||||
static void print_bufs(unsigned char *read, unsigned char *written, int start,
|
||||
int len)
|
||||
{
|
||||
int i = 0, j1, j2;
|
||||
char *diff;
|
||||
|
||||
printk("Offset Read Written\n");
|
||||
while (i < len) {
|
||||
printk("0x%08x: ", start + i);
|
||||
diff = " ";
|
||||
for (j1 = 0; j1 < 8 && i + j1 < len; j1++) {
|
||||
printk(" %02x", read[start + i + j1]);
|
||||
if (read[start + i + j1] != written[start + i + j1])
|
||||
diff = "***";
|
||||
}
|
||||
|
||||
while (j1 < 8) {
|
||||
printk(" ");
|
||||
j1 += 1;
|
||||
}
|
||||
|
||||
printk(" %s ", diff);
|
||||
|
||||
for (j2 = 0; j2 < 8 && i + j2 < len; j2++)
|
||||
printk(" %02x", written[start + i + j2]);
|
||||
printk("\n");
|
||||
i += 8;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Count the number of differing bytes and bits and return the first differing
|
||||
* offset.
|
||||
*/
|
||||
static int countdiffs(unsigned char *buf, unsigned char *check_buf,
|
||||
unsigned offset, unsigned len, unsigned *bytesp,
|
||||
unsigned *bitsp)
|
||||
{
|
||||
unsigned i, bit;
|
||||
int first = -1;
|
||||
|
||||
for (i = offset; i < offset + len; i++)
|
||||
if (buf[i] != check_buf[i]) {
|
||||
first = i;
|
||||
break;
|
||||
}
|
||||
|
||||
while (i < offset + len) {
|
||||
if (buf[i] != check_buf[i]) {
|
||||
(*bytesp)++;
|
||||
bit = 1;
|
||||
while (bit < 256) {
|
||||
if ((buf[i] & bit) != (check_buf[i] & bit))
|
||||
(*bitsp)++;
|
||||
bit <<= 1;
|
||||
}
|
||||
}
|
||||
i++;
|
||||
}
|
||||
|
||||
return first;
|
||||
}
|
||||
|
||||
MODULE_DESCRIPTION("Eraseblock torturing module");
|
||||
MODULE_AUTHOR("Artem Bityutskiy, Jarkko Lavinen, Adrian Hunter");
|
||||
MODULE_LICENSE("GPL");
|
@ -561,7 +561,7 @@ static int io_init(struct ubi_device *ubi)
|
||||
*/
|
||||
|
||||
ubi->peb_size = ubi->mtd->erasesize;
|
||||
ubi->peb_count = ubi->mtd->size / ubi->mtd->erasesize;
|
||||
ubi->peb_count = mtd_div_by_eb(ubi->mtd->size, ubi->mtd);
|
||||
ubi->flash_size = ubi->mtd->size;
|
||||
|
||||
if (ubi->mtd->block_isbad && ubi->mtd->block_markbad)
|
||||
|
@ -215,7 +215,8 @@ static int gluebi_erase(struct mtd_info *mtd, struct erase_info *instr)
|
||||
struct ubi_volume *vol;
|
||||
struct ubi_device *ubi;
|
||||
|
||||
dbg_gen("erase %u bytes at offset %u", instr->len, instr->addr);
|
||||
dbg_gen("erase %llu bytes at offset %llu", (unsigned long long)instr->len,
|
||||
(unsigned long long)instr->addr);
|
||||
|
||||
if (instr->addr < 0 || instr->addr > mtd->size - mtd->erasesize)
|
||||
return -EINVAL;
|
||||
@ -223,11 +224,11 @@ static int gluebi_erase(struct mtd_info *mtd, struct erase_info *instr)
|
||||
if (instr->len < 0 || instr->addr + instr->len > mtd->size)
|
||||
return -EINVAL;
|
||||
|
||||
if (instr->addr % mtd->writesize || instr->len % mtd->writesize)
|
||||
if (mtd_mod_by_ws(instr->addr, mtd) || mtd_mod_by_ws(instr->len, mtd))
|
||||
return -EINVAL;
|
||||
|
||||
lnum = instr->addr / mtd->erasesize;
|
||||
count = instr->len / mtd->erasesize;
|
||||
lnum = mtd_div_by_eb(instr->addr, mtd);
|
||||
count = mtd_div_by_eb(instr->len, mtd);
|
||||
|
||||
vol = container_of(mtd, struct ubi_volume, gluebi_mtd);
|
||||
ubi = vol->ubi;
|
||||
@ -255,7 +256,7 @@ static int gluebi_erase(struct mtd_info *mtd, struct erase_info *instr)
|
||||
|
||||
out_err:
|
||||
instr->state = MTD_ERASE_FAILED;
|
||||
instr->fail_addr = lnum * mtd->erasesize;
|
||||
instr->fail_addr = (long long)lnum * mtd->erasesize;
|
||||
return err;
|
||||
}
|
||||
|
||||
@ -294,7 +295,7 @@ int ubi_create_gluebi(struct ubi_device *ubi, struct ubi_volume *vol)
|
||||
* bytes.
|
||||
*/
|
||||
if (vol->vol_type == UBI_DYNAMIC_VOLUME)
|
||||
mtd->size = vol->usable_leb_size * vol->reserved_pebs;
|
||||
mtd->size = (long long)vol->usable_leb_size * vol->reserved_pebs;
|
||||
else
|
||||
mtd->size = vol->used_bytes;
|
||||
|
||||
@ -304,8 +305,8 @@ int ubi_create_gluebi(struct ubi_device *ubi, struct ubi_volume *vol)
|
||||
return -ENFILE;
|
||||
}
|
||||
|
||||
dbg_gen("added mtd%d (\"%s\"), size %u, EB size %u",
|
||||
mtd->index, mtd->name, mtd->size, mtd->erasesize);
|
||||
dbg_gen("added mtd%d (\"%s\"), size %llu, EB size %u",
|
||||
mtd->index, mtd->name, (unsigned long long)mtd->size, mtd->erasesize);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -22,9 +22,7 @@
|
||||
|
||||
|
||||
#define BIT_DIVIDER_MIPS 1043
|
||||
static int bits_mips[8] = { 277,249,290,267,229,341,212,241}; /* mips32 */
|
||||
|
||||
#include <linux/errno.h>
|
||||
static int bits_mips[8] = { 277, 249, 290, 267, 229, 341, 212, 241};
|
||||
|
||||
struct pushpull {
|
||||
unsigned char *buf;
|
||||
@ -43,7 +41,9 @@ struct rubin_state {
|
||||
int bits[8];
|
||||
};
|
||||
|
||||
static inline void init_pushpull(struct pushpull *pp, char *buf, unsigned buflen, unsigned ofs, unsigned reserve)
|
||||
static inline void init_pushpull(struct pushpull *pp, char *buf,
|
||||
unsigned buflen, unsigned ofs,
|
||||
unsigned reserve)
|
||||
{
|
||||
pp->buf = buf;
|
||||
pp->buflen = buflen;
|
||||
@ -53,16 +53,14 @@ static inline void init_pushpull(struct pushpull *pp, char *buf, unsigned buflen
|
||||
|
||||
static inline int pushbit(struct pushpull *pp, int bit, int use_reserved)
|
||||
{
|
||||
if (pp->ofs >= pp->buflen - (use_reserved?0:pp->reserve)) {
|
||||
if (pp->ofs >= pp->buflen - (use_reserved?0:pp->reserve))
|
||||
return -ENOSPC;
|
||||
}
|
||||
|
||||
if (bit) {
|
||||
pp->buf[pp->ofs >> 3] |= (1<<(7-(pp->ofs &7)));
|
||||
}
|
||||
else {
|
||||
pp->buf[pp->ofs >> 3] &= ~(1<<(7-(pp->ofs &7)));
|
||||
}
|
||||
if (bit)
|
||||
pp->buf[pp->ofs >> 3] |= (1<<(7-(pp->ofs & 7)));
|
||||
else
|
||||
pp->buf[pp->ofs >> 3] &= ~(1<<(7-(pp->ofs & 7)));
|
||||
|
||||
pp->ofs++;
|
||||
|
||||
return 0;
|
||||
@ -97,6 +95,7 @@ static void init_rubin(struct rubin_state *rs, int div, int *bits)
|
||||
rs->p = (long) (2 * UPPER_BIT_RUBIN);
|
||||
rs->bit_number = (long) 0;
|
||||
rs->bit_divider = div;
|
||||
|
||||
for (c=0; c<8; c++)
|
||||
rs->bits[c] = bits[c];
|
||||
}
|
||||
@ -108,7 +107,8 @@ static int encode(struct rubin_state *rs, long A, long B, int symbol)
|
||||
long i0, i1;
|
||||
int ret;
|
||||
|
||||
while ((rs->q >= UPPER_BIT_RUBIN) || ((rs->p + rs->q) <= UPPER_BIT_RUBIN)) {
|
||||
while ((rs->q >= UPPER_BIT_RUBIN) ||
|
||||
((rs->p + rs->q) <= UPPER_BIT_RUBIN)) {
|
||||
rs->bit_number++;
|
||||
|
||||
ret = pushbit(&rs->pp, (rs->q & UPPER_BIT_RUBIN) ? 1 : 0, 0);
|
||||
@ -119,12 +119,12 @@ static int encode(struct rubin_state *rs, long A, long B, int symbol)
|
||||
rs->p <<= 1;
|
||||
}
|
||||
i0 = A * rs->p / (A + B);
|
||||
if (i0 <= 0) {
|
||||
if (i0 <= 0)
|
||||
i0 = 1;
|
||||
}
|
||||
if (i0 >= rs->p) {
|
||||
|
||||
if (i0 >= rs->p)
|
||||
i0 = rs->p - 1;
|
||||
}
|
||||
|
||||
i1 = rs->p - i0;
|
||||
|
||||
if (symbol == 0)
|
||||
@ -157,11 +157,13 @@ static void init_decode(struct rubin_state *rs, int div, int *bits)
|
||||
/* behalve lower */
|
||||
rs->rec_q = 0;
|
||||
|
||||
for (rs->bit_number = 0; rs->bit_number++ < RUBIN_REG_SIZE; rs->rec_q = rs->rec_q * 2 + (long) (pullbit(&rs->pp)))
|
||||
for (rs->bit_number = 0; rs->bit_number++ < RUBIN_REG_SIZE;
|
||||
rs->rec_q = rs->rec_q * 2 + (long) (pullbit(&rs->pp)))
|
||||
;
|
||||
}
|
||||
|
||||
static void __do_decode(struct rubin_state *rs, unsigned long p, unsigned long q)
|
||||
static void __do_decode(struct rubin_state *rs, unsigned long p,
|
||||
unsigned long q)
|
||||
{
|
||||
register unsigned long lower_bits_rubin = LOWER_BITS_RUBIN;
|
||||
unsigned long rec_q;
|
||||
@ -207,12 +209,11 @@ static int decode(struct rubin_state *rs, long A, long B)
|
||||
__do_decode(rs, p, q);
|
||||
|
||||
i0 = A * rs->p / (A + B);
|
||||
if (i0 <= 0) {
|
||||
if (i0 <= 0)
|
||||
i0 = 1;
|
||||
}
|
||||
if (i0 >= rs->p) {
|
||||
|
||||
if (i0 >= rs->p)
|
||||
i0 = rs->p - 1;
|
||||
}
|
||||
|
||||
threshold = rs->q + i0;
|
||||
symbol = rs->rec_q >= threshold;
|
||||
@ -234,14 +235,15 @@ static int out_byte(struct rubin_state *rs, unsigned char byte)
|
||||
struct rubin_state rs_copy;
|
||||
rs_copy = *rs;
|
||||
|
||||
for (i=0;i<8;i++) {
|
||||
ret = encode(rs, rs->bit_divider-rs->bits[i],rs->bits[i],byte&1);
|
||||
for (i=0; i<8; i++) {
|
||||
ret = encode(rs, rs->bit_divider-rs->bits[i],
|
||||
rs->bits[i], byte & 1);
|
||||
if (ret) {
|
||||
/* Failed. Restore old state */
|
||||
*rs = rs_copy;
|
||||
return ret;
|
||||
}
|
||||
byte=byte>>1;
|
||||
byte >>= 1 ;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@ -251,7 +253,8 @@ static int in_byte(struct rubin_state *rs)
|
||||
int i, result = 0, bit_divider = rs->bit_divider;
|
||||
|
||||
for (i = 0; i < 8; i++)
|
||||
result |= decode(rs, bit_divider - rs->bits[i], rs->bits[i]) << i;
|
||||
result |= decode(rs, bit_divider - rs->bits[i],
|
||||
rs->bits[i]) << i;
|
||||
|
||||
return result;
|
||||
}
|
||||
@ -259,7 +262,8 @@ static int in_byte(struct rubin_state *rs)
|
||||
|
||||
|
||||
static int rubin_do_compress(int bit_divider, int *bits, unsigned char *data_in,
|
||||
unsigned char *cpage_out, uint32_t *sourcelen, uint32_t *dstlen)
|
||||
unsigned char *cpage_out, uint32_t *sourcelen,
|
||||
uint32_t *dstlen)
|
||||
{
|
||||
int outpos = 0;
|
||||
int pos=0;
|
||||
@ -295,7 +299,8 @@ static int rubin_do_compress(int bit_divider, int *bits, unsigned char *data_in,
|
||||
int jffs2_rubinmips_compress(unsigned char *data_in, unsigned char *cpage_out,
|
||||
uint32_t *sourcelen, uint32_t *dstlen, void *model)
|
||||
{
|
||||
return rubin_do_compress(BIT_DIVIDER_MIPS, bits_mips, data_in, cpage_out, sourcelen, dstlen);
|
||||
return rubin_do_compress(BIT_DIVIDER_MIPS, bits_mips, data_in,
|
||||
cpage_out, sourcelen, dstlen);
|
||||
}
|
||||
#endif
|
||||
static int jffs2_dynrubin_compress(unsigned char *data_in,
|
||||
@ -316,9 +321,8 @@ static int jffs2_dynrubin_compress(unsigned char *data_in,
|
||||
return -1;
|
||||
|
||||
memset(histo, 0, 256);
|
||||
for (i=0; i<mysrclen; i++) {
|
||||
for (i=0; i<mysrclen; i++)
|
||||
histo[data_in[i]]++;
|
||||
}
|
||||
memset(bits, 0, sizeof(int)*8);
|
||||
for (i=0; i<256; i++) {
|
||||
if (i&128)
|
||||
@ -346,7 +350,8 @@ static int jffs2_dynrubin_compress(unsigned char *data_in,
|
||||
cpage_out[i] = bits[i];
|
||||
}
|
||||
|
||||
ret = rubin_do_compress(256, bits, data_in, cpage_out+8, &mysrclen, &mydstlen);
|
||||
ret = rubin_do_compress(256, bits, data_in, cpage_out+8, &mysrclen,
|
||||
&mydstlen);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
@ -363,8 +368,10 @@ static int jffs2_dynrubin_compress(unsigned char *data_in,
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void rubin_do_decompress(int bit_divider, int *bits, unsigned char *cdata_in,
|
||||
unsigned char *page_out, uint32_t srclen, uint32_t destlen)
|
||||
static void rubin_do_decompress(int bit_divider, int *bits,
|
||||
unsigned char *cdata_in,
|
||||
unsigned char *page_out, uint32_t srclen,
|
||||
uint32_t destlen)
|
||||
{
|
||||
int outpos = 0;
|
||||
struct rubin_state rs;
|
||||
@ -372,9 +379,8 @@ static void rubin_do_decompress(int bit_divider, int *bits, unsigned char *cdata
|
||||
init_pushpull(&rs.pp, cdata_in, srclen, 0, 0);
|
||||
init_decode(&rs, bit_divider, bits);
|
||||
|
||||
while (outpos < destlen) {
|
||||
while (outpos < destlen)
|
||||
page_out[outpos++] = in_byte(&rs);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -383,7 +389,8 @@ static int jffs2_rubinmips_decompress(unsigned char *data_in,
|
||||
uint32_t sourcelen, uint32_t dstlen,
|
||||
void *model)
|
||||
{
|
||||
rubin_do_decompress(BIT_DIVIDER_MIPS, bits_mips, data_in, cpage_out, sourcelen, dstlen);
|
||||
rubin_do_decompress(BIT_DIVIDER_MIPS, bits_mips, data_in,
|
||||
cpage_out, sourcelen, dstlen);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -398,52 +405,53 @@ static int jffs2_dynrubin_decompress(unsigned char *data_in,
|
||||
for (c=0; c<8; c++)
|
||||
bits[c] = data_in[c];
|
||||
|
||||
rubin_do_decompress(256, bits, data_in+8, cpage_out, sourcelen-8, dstlen);
|
||||
rubin_do_decompress(256, bits, data_in+8, cpage_out, sourcelen-8,
|
||||
dstlen);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct jffs2_compressor jffs2_rubinmips_comp = {
|
||||
.priority = JFFS2_RUBINMIPS_PRIORITY,
|
||||
.name = "rubinmips",
|
||||
.compr = JFFS2_COMPR_DYNRUBIN,
|
||||
.compress = NULL, /*&jffs2_rubinmips_compress,*/
|
||||
.decompress = &jffs2_rubinmips_decompress,
|
||||
.priority = JFFS2_RUBINMIPS_PRIORITY,
|
||||
.name = "rubinmips",
|
||||
.compr = JFFS2_COMPR_DYNRUBIN,
|
||||
.compress = NULL, /*&jffs2_rubinmips_compress,*/
|
||||
.decompress = &jffs2_rubinmips_decompress,
|
||||
#ifdef JFFS2_RUBINMIPS_DISABLED
|
||||
.disabled = 1,
|
||||
.disabled = 1,
|
||||
#else
|
||||
.disabled = 0,
|
||||
.disabled = 0,
|
||||
#endif
|
||||
};
|
||||
|
||||
int jffs2_rubinmips_init(void)
|
||||
{
|
||||
return jffs2_register_compressor(&jffs2_rubinmips_comp);
|
||||
return jffs2_register_compressor(&jffs2_rubinmips_comp);
|
||||
}
|
||||
|
||||
void jffs2_rubinmips_exit(void)
|
||||
{
|
||||
jffs2_unregister_compressor(&jffs2_rubinmips_comp);
|
||||
jffs2_unregister_compressor(&jffs2_rubinmips_comp);
|
||||
}
|
||||
|
||||
static struct jffs2_compressor jffs2_dynrubin_comp = {
|
||||
.priority = JFFS2_DYNRUBIN_PRIORITY,
|
||||
.name = "dynrubin",
|
||||
.compr = JFFS2_COMPR_RUBINMIPS,
|
||||
.compress = jffs2_dynrubin_compress,
|
||||
.decompress = &jffs2_dynrubin_decompress,
|
||||
.priority = JFFS2_DYNRUBIN_PRIORITY,
|
||||
.name = "dynrubin",
|
||||
.compr = JFFS2_COMPR_RUBINMIPS,
|
||||
.compress = jffs2_dynrubin_compress,
|
||||
.decompress = &jffs2_dynrubin_decompress,
|
||||
#ifdef JFFS2_DYNRUBIN_DISABLED
|
||||
.disabled = 1,
|
||||
.disabled = 1,
|
||||
#else
|
||||
.disabled = 0,
|
||||
.disabled = 0,
|
||||
#endif
|
||||
};
|
||||
|
||||
int jffs2_dynrubin_init(void)
|
||||
{
|
||||
return jffs2_register_compressor(&jffs2_dynrubin_comp);
|
||||
return jffs2_register_compressor(&jffs2_dynrubin_comp);
|
||||
}
|
||||
|
||||
void jffs2_dynrubin_exit(void)
|
||||
{
|
||||
jffs2_unregister_compressor(&jffs2_dynrubin_comp);
|
||||
jffs2_unregister_compressor(&jffs2_dynrubin_comp);
|
||||
}
|
||||
|
@ -175,7 +175,7 @@ static void jffs2_erase_failed(struct jffs2_sb_info *c, struct jffs2_eraseblock
|
||||
{
|
||||
/* For NAND, if the failure did not occur at the device level for a
|
||||
specific physical page, don't bother updating the bad block table. */
|
||||
if (jffs2_cleanmarker_oob(c) && (bad_offset != MTD_FAIL_ADDR_UNKNOWN)) {
|
||||
if (jffs2_cleanmarker_oob(c) && (bad_offset != (uint32_t)MTD_FAIL_ADDR_UNKNOWN)) {
|
||||
/* We had a device-level failure to erase. Let's see if we've
|
||||
failed too many times. */
|
||||
if (!jffs2_write_nand_badblock(c, jeb, bad_offset)) {
|
||||
@ -209,7 +209,8 @@ static void jffs2_erase_callback(struct erase_info *instr)
|
||||
struct erase_priv_struct *priv = (void *)instr->priv;
|
||||
|
||||
if(instr->state != MTD_ERASE_DONE) {
|
||||
printk(KERN_WARNING "Erase at 0x%08x finished, but state != MTD_ERASE_DONE. State is 0x%x instead.\n", instr->addr, instr->state);
|
||||
printk(KERN_WARNING "Erase at 0x%08llx finished, but state != MTD_ERASE_DONE. State is 0x%x instead.\n",
|
||||
(unsigned long long)instr->addr, instr->state);
|
||||
jffs2_erase_failed(priv->c, priv->jeb, instr->fail_addr);
|
||||
} else {
|
||||
jffs2_erase_succeeded(priv->c, priv->jeb);
|
||||
|
@ -520,6 +520,7 @@ struct cfi_fixup {
|
||||
|
||||
#define CFI_MFR_AMD 0x0001
|
||||
#define CFI_MFR_ATMEL 0x001F
|
||||
#define CFI_MFR_SAMSUNG 0x00EC
|
||||
#define CFI_MFR_ST 0x0020 /* STMicroelectronics */
|
||||
|
||||
void cfi_fixup(struct mtd_info *mtd, struct cfi_fixup* fixups);
|
||||
|
@ -32,25 +32,25 @@
|
||||
#define _LINUX_FTL_H
|
||||
|
||||
typedef struct erase_unit_header_t {
|
||||
u_int8_t LinkTargetTuple[5];
|
||||
u_int8_t DataOrgTuple[10];
|
||||
u_int8_t NumTransferUnits;
|
||||
u_int32_t EraseCount;
|
||||
u_int16_t LogicalEUN;
|
||||
u_int8_t BlockSize;
|
||||
u_int8_t EraseUnitSize;
|
||||
u_int16_t FirstPhysicalEUN;
|
||||
u_int16_t NumEraseUnits;
|
||||
u_int32_t FormattedSize;
|
||||
u_int32_t FirstVMAddress;
|
||||
u_int16_t NumVMPages;
|
||||
u_int8_t Flags;
|
||||
u_int8_t Code;
|
||||
u_int32_t SerialNumber;
|
||||
u_int32_t AltEUHOffset;
|
||||
u_int32_t BAMOffset;
|
||||
u_int8_t Reserved[12];
|
||||
u_int8_t EndTuple[2];
|
||||
uint8_t LinkTargetTuple[5];
|
||||
uint8_t DataOrgTuple[10];
|
||||
uint8_t NumTransferUnits;
|
||||
uint32_t EraseCount;
|
||||
uint16_t LogicalEUN;
|
||||
uint8_t BlockSize;
|
||||
uint8_t EraseUnitSize;
|
||||
uint16_t FirstPhysicalEUN;
|
||||
uint16_t NumEraseUnits;
|
||||
uint32_t FormattedSize;
|
||||
uint32_t FirstVMAddress;
|
||||
uint16_t NumVMPages;
|
||||
uint8_t Flags;
|
||||
uint8_t Code;
|
||||
uint32_t SerialNumber;
|
||||
uint32_t AltEUHOffset;
|
||||
uint32_t BAMOffset;
|
||||
uint8_t Reserved[12];
|
||||
uint8_t EndTuple[2];
|
||||
} erase_unit_header_t;
|
||||
|
||||
/* Flags in erase_unit_header_t */
|
||||
|
@ -223,6 +223,7 @@ struct map_info {
|
||||
must leave it enabled. */
|
||||
void (*set_vpp)(struct map_info *, int);
|
||||
|
||||
unsigned long pfow_base;
|
||||
unsigned long map_priv_1;
|
||||
unsigned long map_priv_2;
|
||||
void *fldrv_priv;
|
||||
|
@ -15,6 +15,8 @@
|
||||
#include <linux/mtd/compatmac.h>
|
||||
#include <mtd/mtd-abi.h>
|
||||
|
||||
#include <asm/div64.h>
|
||||
|
||||
#define MTD_CHAR_MAJOR 90
|
||||
#define MTD_BLOCK_MAJOR 31
|
||||
#define MAX_MTD_DEVICES 32
|
||||
@ -25,20 +27,20 @@
|
||||
#define MTD_ERASE_DONE 0x08
|
||||
#define MTD_ERASE_FAILED 0x10
|
||||
|
||||
#define MTD_FAIL_ADDR_UNKNOWN 0xffffffff
|
||||
#define MTD_FAIL_ADDR_UNKNOWN -1LL
|
||||
|
||||
/* If the erase fails, fail_addr might indicate exactly which block failed. If
|
||||
fail_addr = MTD_FAIL_ADDR_UNKNOWN, the failure was not at the device level or was not
|
||||
specific to any particular block. */
|
||||
struct erase_info {
|
||||
struct mtd_info *mtd;
|
||||
u_int32_t addr;
|
||||
u_int32_t len;
|
||||
u_int32_t fail_addr;
|
||||
uint64_t addr;
|
||||
uint64_t len;
|
||||
uint64_t fail_addr;
|
||||
u_long time;
|
||||
u_long retries;
|
||||
u_int dev;
|
||||
u_int cell;
|
||||
unsigned dev;
|
||||
unsigned cell;
|
||||
void (*callback) (struct erase_info *self);
|
||||
u_long priv;
|
||||
u_char state;
|
||||
@ -46,9 +48,9 @@ struct erase_info {
|
||||
};
|
||||
|
||||
struct mtd_erase_region_info {
|
||||
u_int32_t offset; /* At which this region starts, from the beginning of the MTD */
|
||||
u_int32_t erasesize; /* For this region */
|
||||
u_int32_t numblocks; /* Number of blocks of erasesize in this region */
|
||||
uint64_t offset; /* At which this region starts, from the beginning of the MTD */
|
||||
uint32_t erasesize; /* For this region */
|
||||
uint32_t numblocks; /* Number of blocks of erasesize in this region */
|
||||
unsigned long *lockmap; /* If keeping bitmap of locks */
|
||||
};
|
||||
|
||||
@ -100,14 +102,14 @@ struct mtd_oob_ops {
|
||||
|
||||
struct mtd_info {
|
||||
u_char type;
|
||||
u_int32_t flags;
|
||||
u_int32_t size; // Total size of the MTD
|
||||
uint32_t flags;
|
||||
uint64_t size; // Total size of the MTD
|
||||
|
||||
/* "Major" erase size for the device. Naïve users may take this
|
||||
* to be the only erase size available, or may use the more detailed
|
||||
* information below if they desire
|
||||
*/
|
||||
u_int32_t erasesize;
|
||||
uint32_t erasesize;
|
||||
/* Minimal writable flash unit size. In case of NOR flash it is 1 (even
|
||||
* though individual bits can be cleared), in case of NAND flash it is
|
||||
* one NAND page (or half, or one-fourths of it), in case of ECC-ed NOR
|
||||
@ -115,10 +117,20 @@ struct mtd_info {
|
||||
* Any driver registering a struct mtd_info must ensure a writesize of
|
||||
* 1 or larger.
|
||||
*/
|
||||
u_int32_t writesize;
|
||||
uint32_t writesize;
|
||||
|
||||
u_int32_t oobsize; // Amount of OOB data per block (e.g. 16)
|
||||
u_int32_t oobavail; // Available OOB bytes per block
|
||||
uint32_t oobsize; // Amount of OOB data per block (e.g. 16)
|
||||
uint32_t oobavail; // Available OOB bytes per block
|
||||
|
||||
/*
|
||||
* If erasesize is a power of 2 then the shift is stored in
|
||||
* erasesize_shift otherwise erasesize_shift is zero. Ditto writesize.
|
||||
*/
|
||||
unsigned int erasesize_shift;
|
||||
unsigned int writesize_shift;
|
||||
/* Masks based on erasesize_shift and writesize_shift */
|
||||
unsigned int erasesize_mask;
|
||||
unsigned int writesize_mask;
|
||||
|
||||
// Kernel-only stuff starts here.
|
||||
const char *name;
|
||||
@ -190,8 +202,8 @@ struct mtd_info {
|
||||
void (*sync) (struct mtd_info *mtd);
|
||||
|
||||
/* Chip-supported device locking */
|
||||
int (*lock) (struct mtd_info *mtd, loff_t ofs, size_t len);
|
||||
int (*unlock) (struct mtd_info *mtd, loff_t ofs, size_t len);
|
||||
int (*lock) (struct mtd_info *mtd, loff_t ofs, uint64_t len);
|
||||
int (*unlock) (struct mtd_info *mtd, loff_t ofs, uint64_t len);
|
||||
|
||||
/* Power Management functions */
|
||||
int (*suspend) (struct mtd_info *mtd);
|
||||
@ -221,6 +233,35 @@ struct mtd_info {
|
||||
void (*put_device) (struct mtd_info *mtd);
|
||||
};
|
||||
|
||||
static inline uint32_t mtd_div_by_eb(uint64_t sz, struct mtd_info *mtd)
|
||||
{
|
||||
if (mtd->erasesize_shift)
|
||||
return sz >> mtd->erasesize_shift;
|
||||
do_div(sz, mtd->erasesize);
|
||||
return sz;
|
||||
}
|
||||
|
||||
static inline uint32_t mtd_mod_by_eb(uint64_t sz, struct mtd_info *mtd)
|
||||
{
|
||||
if (mtd->erasesize_shift)
|
||||
return sz & mtd->erasesize_mask;
|
||||
return do_div(sz, mtd->erasesize);
|
||||
}
|
||||
|
||||
static inline uint32_t mtd_div_by_ws(uint64_t sz, struct mtd_info *mtd)
|
||||
{
|
||||
if (mtd->writesize_shift)
|
||||
return sz >> mtd->writesize_shift;
|
||||
do_div(sz, mtd->writesize);
|
||||
return sz;
|
||||
}
|
||||
|
||||
static inline uint32_t mtd_mod_by_ws(uint64_t sz, struct mtd_info *mtd)
|
||||
{
|
||||
if (mtd->writesize_shift)
|
||||
return sz & mtd->writesize_mask;
|
||||
return do_div(sz, mtd->writesize);
|
||||
}
|
||||
|
||||
/* Kernel-side ioctl definitions */
|
||||
|
||||
|
@ -335,17 +335,12 @@ struct nand_buffers {
|
||||
* @erase_cmd: [INTERN] erase command write function, selectable due to AND support
|
||||
* @scan_bbt: [REPLACEABLE] function to scan bad block table
|
||||
* @chip_delay: [BOARDSPECIFIC] chip dependent delay for transfering data from array to read regs (tR)
|
||||
* @wq: [INTERN] wait queue to sleep on if a NAND operation is in progress
|
||||
* @state: [INTERN] the current state of the NAND device
|
||||
* @oob_poi: poison value buffer
|
||||
* @page_shift: [INTERN] number of address bits in a page (column address bits)
|
||||
* @phys_erase_shift: [INTERN] number of address bits in a physical eraseblock
|
||||
* @bbt_erase_shift: [INTERN] number of address bits in a bbt entry
|
||||
* @chip_shift: [INTERN] number of address bits in one chip
|
||||
* @datbuf: [INTERN] internal buffer for one page + oob
|
||||
* @oobbuf: [INTERN] oob buffer for one eraseblock
|
||||
* @oobdirty: [INTERN] indicates that oob_buf must be reinitialized
|
||||
* @data_poi: [INTERN] pointer to a data buffer
|
||||
* @options: [BOARDSPECIFIC] various chip options. They can partly be set to inform nand_scan about
|
||||
* special functionality. See the defines for further explanation
|
||||
* @badblockpos: [INTERN] position of the bad block marker in the oob area
|
||||
@ -399,7 +394,7 @@ struct nand_chip {
|
||||
int bbt_erase_shift;
|
||||
int chip_shift;
|
||||
int numchips;
|
||||
unsigned long chipsize;
|
||||
uint64_t chipsize;
|
||||
int pagemask;
|
||||
int pagebuf;
|
||||
int subpagesize;
|
||||
|
@ -36,9 +36,9 @@
|
||||
|
||||
struct mtd_partition {
|
||||
char *name; /* identifier string */
|
||||
u_int32_t size; /* partition size */
|
||||
u_int32_t offset; /* offset within the master MTD space */
|
||||
u_int32_t mask_flags; /* master MTD flags to mask out for this partition */
|
||||
uint64_t size; /* partition size */
|
||||
uint64_t offset; /* offset within the master MTD space */
|
||||
uint32_t mask_flags; /* master MTD flags to mask out for this partition */
|
||||
struct nand_ecclayout *ecclayout; /* out of band layout for this partition (NAND only)*/
|
||||
struct mtd_info **mtdp; /* pointer to store the MTD object */
|
||||
};
|
||||
|
159
include/linux/mtd/pfow.h
Normal file
159
include/linux/mtd/pfow.h
Normal file
@ -0,0 +1,159 @@
|
||||
/* Primary function overlay window definitions
|
||||
* and service functions used by LPDDR chips
|
||||
*/
|
||||
#ifndef __LINUX_MTD_PFOW_H
|
||||
#define __LINUX_MTD_PFOW_H
|
||||
|
||||
#include <linux/mtd/qinfo.h>
|
||||
|
||||
/* PFOW registers addressing */
|
||||
/* Address of symbol "P" */
|
||||
#define PFOW_QUERY_STRING_P 0x0000
|
||||
/* Address of symbol "F" */
|
||||
#define PFOW_QUERY_STRING_F 0x0002
|
||||
/* Address of symbol "O" */
|
||||
#define PFOW_QUERY_STRING_O 0x0004
|
||||
/* Address of symbol "W" */
|
||||
#define PFOW_QUERY_STRING_W 0x0006
|
||||
/* Identification info for LPDDR chip */
|
||||
#define PFOW_MANUFACTURER_ID 0x0020
|
||||
#define PFOW_DEVICE_ID 0x0022
|
||||
/* Address in PFOW where prog buffer can can be found */
|
||||
#define PFOW_PROGRAM_BUFFER_OFFSET 0x0040
|
||||
/* Size of program buffer in words */
|
||||
#define PFOW_PROGRAM_BUFFER_SIZE 0x0042
|
||||
/* Address command code register */
|
||||
#define PFOW_COMMAND_CODE 0x0080
|
||||
/* command data register */
|
||||
#define PFOW_COMMAND_DATA 0x0084
|
||||
/* command address register lower address bits */
|
||||
#define PFOW_COMMAND_ADDRESS_L 0x0088
|
||||
/* command address register upper address bits */
|
||||
#define PFOW_COMMAND_ADDRESS_H 0x008a
|
||||
/* number of bytes to be proggrammed lower address bits */
|
||||
#define PFOW_DATA_COUNT_L 0x0090
|
||||
/* number of bytes to be proggrammed higher address bits */
|
||||
#define PFOW_DATA_COUNT_H 0x0092
|
||||
/* command execution register, the only possible value is 0x01 */
|
||||
#define PFOW_COMMAND_EXECUTE 0x00c0
|
||||
/* 0x01 should be written at this address to clear buffer */
|
||||
#define PFOW_CLEAR_PROGRAM_BUFFER 0x00c4
|
||||
/* device program/erase suspend register */
|
||||
#define PFOW_PROGRAM_ERASE_SUSPEND 0x00c8
|
||||
/* device status register */
|
||||
#define PFOW_DSR 0x00cc
|
||||
|
||||
/* LPDDR memory device command codes */
|
||||
/* They are possible values of PFOW command code register */
|
||||
#define LPDDR_WORD_PROGRAM 0x0041
|
||||
#define LPDDR_BUFF_PROGRAM 0x00E9
|
||||
#define LPDDR_BLOCK_ERASE 0x0020
|
||||
#define LPDDR_LOCK_BLOCK 0x0061
|
||||
#define LPDDR_UNLOCK_BLOCK 0x0062
|
||||
#define LPDDR_READ_BLOCK_LOCK_STATUS 0x0065
|
||||
#define LPDDR_INFO_QUERY 0x0098
|
||||
#define LPDDR_READ_OTP 0x0097
|
||||
#define LPDDR_PROG_OTP 0x00C0
|
||||
#define LPDDR_RESUME 0x00D0
|
||||
|
||||
/* Defines possible value of PFOW command execution register */
|
||||
#define LPDDR_START_EXECUTION 0x0001
|
||||
|
||||
/* Defines possible value of PFOW program/erase suspend register */
|
||||
#define LPDDR_SUSPEND 0x0001
|
||||
|
||||
/* Possible values of PFOW device status register */
|
||||
/* access R - read; RC read & clearable */
|
||||
#define DSR_DPS (1<<1) /* RC; device protect status
|
||||
* 0 - not protected 1 - locked */
|
||||
#define DSR_PSS (1<<2) /* R; program suspend status;
|
||||
* 0-prog in progress/completed,
|
||||
* 1- prog suspended */
|
||||
#define DSR_VPPS (1<<3) /* RC; 0-Vpp OK, * 1-Vpp low */
|
||||
#define DSR_PROGRAM_STATUS (1<<4) /* RC; 0-successful, 1-error */
|
||||
#define DSR_ERASE_STATUS (1<<5) /* RC; erase or blank check status;
|
||||
* 0-success erase/blank check,
|
||||
* 1 blank check error */
|
||||
#define DSR_ESS (1<<6) /* R; erase suspend status;
|
||||
* 0-erase in progress/complete,
|
||||
* 1 erase suspended */
|
||||
#define DSR_READY_STATUS (1<<7) /* R; Device status
|
||||
* 0-busy,
|
||||
* 1-ready */
|
||||
#define DSR_RPS (0x3<<8) /* RC; region program status
|
||||
* 00 - Success,
|
||||
* 01-re-program attempt in region with
|
||||
* object mode data,
|
||||
* 10-object mode program w attempt in
|
||||
* region with control mode data
|
||||
* 11-attempt to program invalid half
|
||||
* with 0x41 command */
|
||||
#define DSR_AOS (1<<12) /* RC; 1- AO related failure */
|
||||
#define DSR_AVAILABLE (1<<15) /* R; Device availbility
|
||||
* 1 - Device available
|
||||
* 0 - not available */
|
||||
|
||||
/* The superset of all possible error bits in DSR */
|
||||
#define DSR_ERR 0x133A
|
||||
|
||||
static inline void send_pfow_command(struct map_info *map,
|
||||
unsigned long cmd_code, unsigned long adr,
|
||||
unsigned long len, map_word *datum)
|
||||
{
|
||||
int bits_per_chip = map_bankwidth(map) * 8;
|
||||
int chipnum;
|
||||
struct lpddr_private *lpddr = map->fldrv_priv;
|
||||
chipnum = adr >> lpddr->chipshift;
|
||||
|
||||
map_write(map, CMD(cmd_code), map->pfow_base + PFOW_COMMAND_CODE);
|
||||
map_write(map, CMD(adr & ((1<<bits_per_chip) - 1)),
|
||||
map->pfow_base + PFOW_COMMAND_ADDRESS_L);
|
||||
map_write(map, CMD(adr>>bits_per_chip),
|
||||
map->pfow_base + PFOW_COMMAND_ADDRESS_H);
|
||||
if (len) {
|
||||
map_write(map, CMD(len & ((1<<bits_per_chip) - 1)),
|
||||
map->pfow_base + PFOW_DATA_COUNT_L);
|
||||
map_write(map, CMD(len>>bits_per_chip),
|
||||
map->pfow_base + PFOW_DATA_COUNT_H);
|
||||
}
|
||||
if (datum)
|
||||
map_write(map, *datum, map->pfow_base + PFOW_COMMAND_DATA);
|
||||
|
||||
/* Command execution start */
|
||||
map_write(map, CMD(LPDDR_START_EXECUTION),
|
||||
map->pfow_base + PFOW_COMMAND_EXECUTE);
|
||||
}
|
||||
|
||||
static inline void print_drs_error(unsigned dsr)
|
||||
{
|
||||
int prog_status = (dsr & DSR_RPS) >> 8;
|
||||
|
||||
if (!(dsr & DSR_AVAILABLE))
|
||||
printk(KERN_NOTICE"DSR.15: (0) Device not Available\n");
|
||||
if (prog_status & 0x03)
|
||||
printk(KERN_NOTICE"DSR.9,8: (11) Attempt to program invalid "
|
||||
"half with 41h command\n");
|
||||
else if (prog_status & 0x02)
|
||||
printk(KERN_NOTICE"DSR.9,8: (10) Object Mode Program attempt "
|
||||
"in region with Control Mode data\n");
|
||||
else if (prog_status & 0x01)
|
||||
printk(KERN_NOTICE"DSR.9,8: (01) Program attempt in region "
|
||||
"with Object Mode data\n");
|
||||
if (!(dsr & DSR_READY_STATUS))
|
||||
printk(KERN_NOTICE"DSR.7: (0) Device is Busy\n");
|
||||
if (dsr & DSR_ESS)
|
||||
printk(KERN_NOTICE"DSR.6: (1) Erase Suspended\n");
|
||||
if (dsr & DSR_ERASE_STATUS)
|
||||
printk(KERN_NOTICE"DSR.5: (1) Erase/Blank check error\n");
|
||||
if (dsr & DSR_PROGRAM_STATUS)
|
||||
printk(KERN_NOTICE"DSR.4: (1) Program Error\n");
|
||||
if (dsr & DSR_VPPS)
|
||||
printk(KERN_NOTICE"DSR.3: (1) Vpp low detect, operation "
|
||||
"aborted\n");
|
||||
if (dsr & DSR_PSS)
|
||||
printk(KERN_NOTICE"DSR.2: (1) Program suspended\n");
|
||||
if (dsr & DSR_DPS)
|
||||
printk(KERN_NOTICE"DSR.1: (1) Aborted Erase/Program attempt "
|
||||
"on locked block\n");
|
||||
}
|
||||
#endif /* __LINUX_MTD_PFOW_H */
|
@ -24,6 +24,7 @@ struct physmap_flash_data {
|
||||
unsigned int width;
|
||||
void (*set_vpp)(struct map_info *, int);
|
||||
unsigned int nr_parts;
|
||||
unsigned int pfow_base;
|
||||
struct mtd_partition *parts;
|
||||
};
|
||||
|
||||
|
91
include/linux/mtd/qinfo.h
Normal file
91
include/linux/mtd/qinfo.h
Normal file
@ -0,0 +1,91 @@
|
||||
#ifndef __LINUX_MTD_QINFO_H
|
||||
#define __LINUX_MTD_QINFO_H
|
||||
|
||||
#include <linux/mtd/map.h>
|
||||
#include <linux/wait.h>
|
||||
#include <linux/spinlock.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/mtd/mtd.h>
|
||||
#include <linux/mtd/flashchip.h>
|
||||
#include <linux/mtd/partitions.h>
|
||||
|
||||
/* lpddr_private describes lpddr flash chip in memory map
|
||||
* @ManufactId - Chip Manufacture ID
|
||||
* @DevId - Chip Device ID
|
||||
* @qinfo - pointer to qinfo records describing the chip
|
||||
* @numchips - number of chips including virual RWW partitions
|
||||
* @chipshift - Chip/partiton size 2^chipshift
|
||||
* @chips - per-chip data structure
|
||||
*/
|
||||
struct lpddr_private {
|
||||
uint16_t ManufactId;
|
||||
uint16_t DevId;
|
||||
struct qinfo_chip *qinfo;
|
||||
int numchips;
|
||||
unsigned long chipshift;
|
||||
struct flchip chips[0];
|
||||
};
|
||||
|
||||
/* qinfo_query_info structure contains request information for
|
||||
* each qinfo record
|
||||
* @major - major number of qinfo record
|
||||
* @major - minor number of qinfo record
|
||||
* @id_str - descriptive string to access the record
|
||||
* @desc - detailed description for the qinfo record
|
||||
*/
|
||||
struct qinfo_query_info {
|
||||
uint8_t major;
|
||||
uint8_t minor;
|
||||
char *id_str;
|
||||
char *desc;
|
||||
};
|
||||
|
||||
/*
|
||||
* qinfo_chip structure contains necessary qinfo records data
|
||||
* @DevSizeShift - Device size 2^n bytes
|
||||
* @BufSizeShift - Program buffer size 2^n bytes
|
||||
* @TotalBlocksNum - Total number of blocks
|
||||
* @UniformBlockSizeShift - Uniform block size 2^UniformBlockSizeShift bytes
|
||||
* @HWPartsNum - Number of hardware partitions
|
||||
* @SuspEraseSupp - Suspend erase supported
|
||||
* @SingleWordProgTime - Single word program 2^SingleWordProgTime u-sec
|
||||
* @ProgBufferTime - Program buffer write 2^ProgBufferTime u-sec
|
||||
* @BlockEraseTime - Block erase 2^BlockEraseTime m-sec
|
||||
*/
|
||||
struct qinfo_chip {
|
||||
/* General device info */
|
||||
uint16_t DevSizeShift;
|
||||
uint16_t BufSizeShift;
|
||||
/* Erase block information */
|
||||
uint16_t TotalBlocksNum;
|
||||
uint16_t UniformBlockSizeShift;
|
||||
/* Partition information */
|
||||
uint16_t HWPartsNum;
|
||||
/* Optional features */
|
||||
uint16_t SuspEraseSupp;
|
||||
/* Operation typical time */
|
||||
uint16_t SingleWordProgTime;
|
||||
uint16_t ProgBufferTime;
|
||||
uint16_t BlockEraseTime;
|
||||
};
|
||||
|
||||
/* defines for fixup usage */
|
||||
#define LPDDR_MFR_ANY 0xffff
|
||||
#define LPDDR_ID_ANY 0xffff
|
||||
#define NUMONYX_MFGR_ID 0x0089
|
||||
#define R18_DEVICE_ID_1G 0x893c
|
||||
|
||||
static inline map_word lpddr_build_cmd(u_long cmd, struct map_info *map)
|
||||
{
|
||||
map_word val = { {0} };
|
||||
val.x[0] = cmd;
|
||||
return val;
|
||||
}
|
||||
|
||||
#define CMD(x) lpddr_build_cmd(x, map)
|
||||
#define CMDVAL(cmd) cmd.x[0]
|
||||
|
||||
struct mtd_info *lpddr_cmdset(struct map_info *);
|
||||
|
||||
#endif
|
||||
|
20
include/linux/mtd/sharpsl.h
Normal file
20
include/linux/mtd/sharpsl.h
Normal file
@ -0,0 +1,20 @@
|
||||
/*
|
||||
* SharpSL NAND support
|
||||
*
|
||||
* Copyright (C) 2008 Dmitry Baryshkov
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*/
|
||||
|
||||
#include <linux/mtd/nand.h>
|
||||
#include <linux/mtd/nand_ecc.h>
|
||||
#include <linux/mtd/partitions.h>
|
||||
|
||||
struct sharpsl_nand_platform_data {
|
||||
struct nand_bbt_descr *badblock_pattern;
|
||||
struct nand_ecclayout *ecc_layout;
|
||||
struct mtd_partition *partitions;
|
||||
unsigned int nr_partitions;
|
||||
};
|
Loading…
Reference in New Issue
Block a user