diff options
Diffstat (limited to 'drivers')
50 files changed, 2560 insertions, 312 deletions
diff --git a/drivers/mtd/Makefile b/drivers/mtd/Makefile index 4521b1ecce4..82d1e4de475 100644 --- a/drivers/mtd/Makefile +++ b/drivers/mtd/Makefile @@ -4,7 +4,7 @@ # Core functionality. obj-$(CONFIG_MTD) += mtd.o -mtd-y := mtdcore.o mtdsuper.o +mtd-y := mtdcore.o mtdsuper.o mtdbdi.o mtd-$(CONFIG_MTD_PARTITIONS) += mtdpart.o obj-$(CONFIG_MTD_CONCAT) += mtdconcat.o diff --git a/drivers/mtd/ar7part.c b/drivers/mtd/ar7part.c index ecf170b55c3..6697a1ec72d 100644 --- a/drivers/mtd/ar7part.c +++ b/drivers/mtd/ar7part.c @@ -44,8 +44,6 @@ struct ar7_bin_rec { unsigned int address; }; -static struct mtd_partition ar7_parts[AR7_PARTS]; - static int create_mtd_partitions(struct mtd_info *master, struct mtd_partition **pparts, unsigned long origin) @@ -57,7 +55,11 @@ static int create_mtd_partitions(struct mtd_info *master, unsigned int root_offset = ROOT_OFFSET; int retries = 10; + struct mtd_partition *ar7_parts; + ar7_parts = kzalloc(sizeof(*ar7_parts) * AR7_PARTS, GFP_KERNEL); + if (!ar7_parts) + return -ENOMEM; ar7_parts[0].name = "loader"; ar7_parts[0].offset = 0; ar7_parts[0].size = master->erasesize; diff --git a/drivers/mtd/chips/cfi_cmdset_0001.c b/drivers/mtd/chips/cfi_cmdset_0001.c index f5ab6fa1057..c240454fd11 100644 --- a/drivers/mtd/chips/cfi_cmdset_0001.c +++ b/drivers/mtd/chips/cfi_cmdset_0001.c @@ -1236,10 +1236,14 @@ static int inval_cache_and_wait_for_operation( remove_wait_queue(&chip->wq, &wait); spin_lock(chip->mutex); } - if (chip->erase_suspended || chip->write_suspended) { - /* Suspend has occured while sleep: reset timeout */ + if (chip->erase_suspended && chip_state == FL_ERASING) { + /* Erase suspend occured while sleep: reset timeout */ timeo = reset_timeo; chip->erase_suspended = 0; + } + if (chip->write_suspended && chip_state == FL_WRITING) { + /* Write suspend occured while sleep: reset timeout */ + timeo = reset_timeo; chip->write_suspended = 0; } } diff --git a/drivers/mtd/chips/cfi_cmdset_0002.c b/drivers/mtd/chips/cfi_cmdset_0002.c index 94bb61e1904..61ea833e090 100644 --- a/drivers/mtd/chips/cfi_cmdset_0002.c +++ b/drivers/mtd/chips/cfi_cmdset_0002.c @@ -282,6 +282,16 @@ static void fixup_s29gl032n_sectors(struct mtd_info *mtd, void *param) } } +static void fixup_M29W128G_write_buffer(struct mtd_info *mtd, void *param) +{ + struct map_info *map = mtd->priv; + struct cfi_private *cfi = map->fldrv_priv; + if (cfi->cfiq->BufWriteTimeoutTyp) { + pr_warning("Don't use write buffer on ST flash M29W128G\n"); + cfi->cfiq->BufWriteTimeoutTyp = 0; + } +} + static struct cfi_fixup cfi_fixup_table[] = { { CFI_MFR_ATMEL, CFI_ID_ANY, fixup_convert_atmel_pri, NULL }, #ifdef AMD_BOOTLOC_BUG @@ -298,6 +308,7 @@ static struct cfi_fixup cfi_fixup_table[] = { { CFI_MFR_AMD, 0x1301, fixup_s29gl064n_sectors, NULL, }, { CFI_MFR_AMD, 0x1a00, fixup_s29gl032n_sectors, NULL, }, { CFI_MFR_AMD, 0x1a01, fixup_s29gl032n_sectors, NULL, }, + { CFI_MFR_ST, 0x227E, fixup_M29W128G_write_buffer, NULL, }, #if !FORCE_WORD_WRITE { CFI_MFR_ANY, CFI_ID_ANY, fixup_use_write_buffers, NULL, }, #endif diff --git a/drivers/mtd/chips/jedec_probe.c b/drivers/mtd/chips/jedec_probe.c index 2f3f2f719ba..e824b9b9b05 100644 --- a/drivers/mtd/chips/jedec_probe.c +++ b/drivers/mtd/chips/jedec_probe.c @@ -159,6 +159,7 @@ #define SST39LF800 0x2781 #define SST39LF160 0x2782 #define SST39VF1601 0x234b +#define SST39VF3201 0x235b #define SST39LF512 0x00D4 #define SST39LF010 0x00D5 #define SST39LF020 0x00D6 @@ -1490,6 +1491,21 @@ static const struct amd_flash_info jedec_table[] = { ERASEINFO(0x1000,256) } }, { + .mfr_id = MANUFACTURER_SST, /* should be CFI */ + .dev_id = SST39VF3201, + .name = "SST 39VF3201", + .devtypes = CFI_DEVICETYPE_X16, + .uaddr = MTD_UADDR_0xAAAA_0x5555, + .dev_size = SIZE_4MiB, + .cmd_set = P_ID_AMD_STD, + .nr_regions = 4, + .regions = { + ERASEINFO(0x1000,256), + ERASEINFO(0x1000,256), + ERASEINFO(0x1000,256), + ERASEINFO(0x1000,256) + } + }, { .mfr_id = MANUFACTURER_SST, .dev_id = SST36VF3203, .name = "SST 36VF3203", diff --git a/drivers/mtd/chips/map_ram.c b/drivers/mtd/chips/map_ram.c index 072dd8abf33..6bdc50c727e 100644 --- a/drivers/mtd/chips/map_ram.c +++ b/drivers/mtd/chips/map_ram.c @@ -21,6 +21,8 @@ static int mapram_write (struct mtd_info *, loff_t, size_t, size_t *, const u_ch static int mapram_erase (struct mtd_info *, struct erase_info *); static void mapram_nop (struct mtd_info *); static struct mtd_info *map_ram_probe(struct map_info *map); +static unsigned long mapram_unmapped_area(struct mtd_info *, unsigned long, + unsigned long, unsigned long); static struct mtd_chip_driver mapram_chipdrv = { @@ -64,6 +66,7 @@ static struct mtd_info *map_ram_probe(struct map_info *map) mtd->type = MTD_RAM; mtd->size = map->size; mtd->erase = mapram_erase; + mtd->get_unmapped_area = mapram_unmapped_area; mtd->read = mapram_read; mtd->write = mapram_write; mtd->sync = mapram_nop; @@ -79,6 +82,20 @@ static struct mtd_info *map_ram_probe(struct map_info *map) } +/* + * Allow NOMMU mmap() to directly map the device (if not NULL) + * - return the address to which the offset maps + * - return -ENOSYS to indicate refusal to do the mapping + */ +static unsigned long mapram_unmapped_area(struct mtd_info *mtd, + unsigned long len, + unsigned long offset, + unsigned long flags) +{ + struct map_info *map = mtd->priv; + return (unsigned long) map->virt + offset; +} + static int mapram_read (struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen, u_char *buf) { struct map_info *map = mtd->priv; diff --git a/drivers/mtd/chips/map_rom.c b/drivers/mtd/chips/map_rom.c index c76d6e5f47e..076090a67b9 100644 --- a/drivers/mtd/chips/map_rom.c +++ b/drivers/mtd/chips/map_rom.c @@ -20,6 +20,8 @@ static int maprom_write (struct mtd_info *, loff_t, size_t, size_t *, const u_ch static void maprom_nop (struct mtd_info *); static struct mtd_info *map_rom_probe(struct map_info *map); static int maprom_erase (struct mtd_info *mtd, struct erase_info *info); +static unsigned long maprom_unmapped_area(struct mtd_info *, unsigned long, + unsigned long, unsigned long); static struct mtd_chip_driver maprom_chipdrv = { .probe = map_rom_probe, @@ -40,6 +42,7 @@ static struct mtd_info *map_rom_probe(struct map_info *map) mtd->name = map->name; mtd->type = MTD_ROM; mtd->size = map->size; + mtd->get_unmapped_area = maprom_unmapped_area; mtd->read = maprom_read; mtd->write = maprom_write; mtd->sync = maprom_nop; @@ -53,6 +56,20 @@ static struct mtd_info *map_rom_probe(struct map_info *map) } +/* + * Allow NOMMU mmap() to directly map the device (if not NULL) + * - return the address to which the offset maps + * - return -ENOSYS to indicate refusal to do the mapping + */ +static unsigned long maprom_unmapped_area(struct mtd_info *mtd, + unsigned long len, + unsigned long offset, + unsigned long flags) +{ + struct map_info *map = mtd->priv; + return (unsigned long) map->virt + offset; +} + static int maprom_read (struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen, u_char *buf) { struct map_info *map = mtd->priv; diff --git a/drivers/mtd/cmdlinepart.c b/drivers/mtd/cmdlinepart.c index 50a340388e7..5011fa73f91 100644 --- a/drivers/mtd/cmdlinepart.c +++ b/drivers/mtd/cmdlinepart.c @@ -335,7 +335,11 @@ static int parse_cmdline_partitions(struct mtd_info *master, } offset += part->parts[i].size; } - *pparts = part->parts; + *pparts = kmemdup(part->parts, + sizeof(*part->parts) * part->num_parts, + GFP_KERNEL); + if (!*pparts) + return -ENOMEM; return part->num_parts; } } diff --git a/drivers/mtd/devices/doc2000.c b/drivers/mtd/devices/doc2000.c index 50de839c77a..5bf5f460e13 100644 --- a/drivers/mtd/devices/doc2000.c +++ b/drivers/mtd/devices/doc2000.c @@ -10,7 +10,6 @@ #include <asm/errno.h> #include <asm/io.h> #include <asm/uaccess.h> -#include <linux/miscdevice.h> #include <linux/delay.h> #include <linux/slab.h> #include <linux/sched.h> diff --git a/drivers/mtd/devices/doc2001.c b/drivers/mtd/devices/doc2001.c index e32c568c114..0990f780362 100644 --- a/drivers/mtd/devices/doc2001.c +++ b/drivers/mtd/devices/doc2001.c @@ -10,7 +10,6 @@ #include <asm/errno.h> #include <asm/io.h> #include <asm/uaccess.h> -#include <linux/miscdevice.h> #include <linux/delay.h> #include <linux/slab.h> #include <linux/init.h> diff --git a/drivers/mtd/devices/doc2001plus.c b/drivers/mtd/devices/doc2001plus.c index d853f891b58..719b2915dc3 100644 --- a/drivers/mtd/devices/doc2001plus.c +++ b/drivers/mtd/devices/doc2001plus.c @@ -14,7 +14,6 @@ #include <asm/errno.h> #include <asm/io.h> #include <asm/uaccess.h> -#include <linux/miscdevice.h> #include <linux/delay.h> #include <linux/slab.h> #include <linux/init.h> diff --git a/drivers/mtd/devices/docecc.c b/drivers/mtd/devices/docecc.c index 874e51b110a..a19cda52da5 100644 --- a/drivers/mtd/devices/docecc.c +++ b/drivers/mtd/devices/docecc.c @@ -26,7 +26,6 @@ #include <asm/errno.h> #include <asm/io.h> #include <asm/uaccess.h> -#include <linux/miscdevice.h> #include <linux/delay.h> #include <linux/slab.h> #include <linux/init.h> diff --git a/drivers/mtd/devices/m25p80.c b/drivers/mtd/devices/m25p80.c index 7c3fc766dcf..8185b1f3e5e 100644 --- a/drivers/mtd/devices/m25p80.c +++ b/drivers/mtd/devices/m25p80.c @@ -65,12 +65,6 @@ #define FAST_READ_DUMMY_BYTE 0 #endif -#ifdef CONFIG_MTD_PARTITIONS -#define mtd_has_partitions() (1) -#else -#define mtd_has_partitions() (0) -#endif - /****************************************************************************/ struct m25p { @@ -678,6 +672,8 @@ static int __devinit m25p_probe(struct spi_device *spi) flash->mtd.erasesize = info->sector_size; } + flash->mtd.dev.parent = &spi->dev; + dev_info(&spi->dev, "%s (%lld Kbytes)\n", info->name, (long long)flash->mtd.size >> 10); @@ -708,12 +704,13 @@ static int __devinit m25p_probe(struct spi_device *spi) struct mtd_partition *parts = NULL; int nr_parts = 0; -#ifdef CONFIG_MTD_CMDLINE_PARTS - static const char *part_probes[] = { "cmdlinepart", NULL, }; + if (mtd_has_cmdlinepart()) { + static const char *part_probes[] + = { "cmdlinepart", NULL, }; - nr_parts = parse_mtd_partitions(&flash->mtd, - part_probes, &parts, 0); -#endif + nr_parts = parse_mtd_partitions(&flash->mtd, + part_probes, &parts, 0); + } if (nr_parts <= 0 && data && data->parts) { parts = data->parts; diff --git a/drivers/mtd/devices/mtd_dataflash.c b/drivers/mtd/devices/mtd_dataflash.c index 6d9f810565c..62dee54af0a 100644 --- a/drivers/mtd/devices/mtd_dataflash.c +++ b/drivers/mtd/devices/mtd_dataflash.c @@ -98,12 +98,6 @@ struct dataflash { struct mtd_info mtd; }; -#ifdef CONFIG_MTD_PARTITIONS -#define mtd_has_partitions() (1) -#else -#define mtd_has_partitions() (0) -#endif - /* ......................................................................... */ /* @@ -670,6 +664,8 @@ add_dataflash_otp(struct spi_device *spi, char *name, device->write = dataflash_write; device->priv = priv; + device->dev.parent = &spi->dev; + if (revision >= 'c') otp_tag = otp_setup(device, revision); @@ -682,11 +678,13 @@ add_dataflash_otp(struct spi_device *spi, char *name, struct mtd_partition *parts; int nr_parts = 0; -#ifdef CONFIG_MTD_CMDLINE_PARTS - static const char *part_probes[] = { "cmdlinepart", NULL, }; + if (mtd_has_cmdlinepart()) { + static const char *part_probes[] + = { "cmdlinepart", NULL, }; - nr_parts = parse_mtd_partitions(device, part_probes, &parts, 0); -#endif + nr_parts = parse_mtd_partitions(device, + part_probes, &parts, 0); + } if (nr_parts <= 0 && pdata && pdata->parts) { parts = pdata->parts; diff --git a/drivers/mtd/devices/mtdram.c b/drivers/mtd/devices/mtdram.c index 3aaca88847d..fce5ff7589a 100644 --- a/drivers/mtd/devices/mtdram.c +++ b/drivers/mtd/devices/mtdram.c @@ -65,6 +65,19 @@ static void ram_unpoint(struct mtd_info *mtd, loff_t from, size_t len) { } +/* + * Allow NOMMU mmap() to directly map the device (if not NULL) + * - return the address to which the offset maps + * - return -ENOSYS to indicate refusal to do the mapping + */ +static unsigned long ram_get_unmapped_area(struct mtd_info *mtd, + unsigned long len, + unsigned long offset, + unsigned long flags) +{ + return (unsigned long) mtd->priv + offset; +} + static int ram_read(struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen, u_char *buf) { @@ -116,6 +129,7 @@ int mtdram_init_device(struct mtd_info *mtd, void *mapped_address, mtd->erase = ram_erase; mtd->point = ram_point; mtd->unpoint = ram_unpoint; + mtd->get_unmapped_area = ram_get_unmapped_area; mtd->read = ram_read; mtd->write = ram_write; diff --git a/drivers/mtd/inftlmount.c b/drivers/mtd/inftlmount.c index f751dd97c54..32e82aef3e5 100644 --- a/drivers/mtd/inftlmount.c +++ b/drivers/mtd/inftlmount.c @@ -28,7 +28,6 @@ #include <asm/errno.h> #include <asm/io.h> #include <asm/uaccess.h> -#include <linux/miscdevice.h> #include <linux/delay.h> #include <linux/slab.h> #include <linux/init.h> diff --git a/drivers/mtd/internal.h b/drivers/mtd/internal.h new file mode 100644 index 00000000000..c658fe7216b --- /dev/null +++ b/drivers/mtd/internal.h @@ -0,0 +1,17 @@ +/* Internal MTD definitions + * + * Copyright © 2006 Red Hat, Inc. All Rights Reserved. + * Written by David Howells (dhowells@redhat.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. + */ + +/* + * mtdbdi.c + */ +extern struct backing_dev_info mtd_bdi_unmappable; +extern struct backing_dev_info mtd_bdi_ro_mappable; +extern struct backing_dev_info mtd_bdi_rw_mappable; diff --git a/drivers/mtd/maps/Kconfig b/drivers/mtd/maps/Kconfig index 729f899a5cd..82923bd2d9c 100644 --- a/drivers/mtd/maps/Kconfig +++ b/drivers/mtd/maps/Kconfig @@ -529,12 +529,6 @@ config MTD_DMV182 help Map driver for Dy-4 SVME/DMV-182 board. -config MTD_SHARP_SL - tristate "ROM mapped on Sharp SL Series" - depends on ARCH_PXA - help - This enables access to the flash chip on the Sharp SL Series of PDAs. - config MTD_INTEL_VR_NOR tristate "NOR flash on Intel Vermilion Range Expansion Bus CS0" depends on PCI @@ -542,6 +536,12 @@ config MTD_INTEL_VR_NOR Map driver for a NOR flash bank located on the Expansion Bus of the Intel Vermilion Range chipset. +config MTD_RBTX4939 + tristate "Map driver for RBTX4939 board" + depends on TOSHIBA_RBTX4939 && MTD_CFI && MTD_COMPLEX_MAPPINGS + help + Map driver for NOR flash chips on RBTX4939 board. + config MTD_PLATRAM tristate "Map driver for platform device RAM (mtd-ram)" select MTD_RAM diff --git a/drivers/mtd/maps/Makefile b/drivers/mtd/maps/Makefile index 26b28a7a90b..2dbc1bec848 100644 --- a/drivers/mtd/maps/Makefile +++ b/drivers/mtd/maps/Makefile @@ -56,9 +56,9 @@ obj-$(CONFIG_MTD_IXP4XX) += ixp4xx.o obj-$(CONFIG_MTD_IXP2000) += ixp2000.o obj-$(CONFIG_MTD_WRSBC8260) += wr_sbc82xx_flash.o obj-$(CONFIG_MTD_DMV182) += dmv182.o -obj-$(CONFIG_MTD_SHARP_SL) += sharpsl-flash.o obj-$(CONFIG_MTD_PLATRAM) += plat-ram.o obj-$(CONFIG_MTD_OMAP_NOR) += omap_nor.o obj-$(CONFIG_MTD_INTEL_VR_NOR) += intel_vr_nor.o obj-$(CONFIG_MTD_BFIN_ASYNC) += bfin-async-flash.o +obj-$(CONFIG_MTD_RBTX4939) += rbtx4939-flash.o obj-$(CONFIG_MTD_VMU) += vmu-flash.o diff --git a/drivers/mtd/maps/omap_nor.c b/drivers/mtd/maps/omap_nor.c index 7e50e9b1b78..a24478102b1 100644 --- a/drivers/mtd/maps/omap_nor.c +++ b/drivers/mtd/maps/omap_nor.c @@ -115,6 +115,8 @@ static int __init omapflash_probe(struct platform_device *pdev) } info->mtd->owner = THIS_MODULE; + info->mtd->dev.parent = &pdev->dev; + #ifdef CONFIG_MTD_PARTITIONS err = parse_mtd_partitions(info->mtd, part_probes, &info->parts, 0); if (err > 0) diff --git a/drivers/mtd/maps/physmap.c b/drivers/mtd/maps/physmap.c index 229718222db..29a90115735 100644 --- a/drivers/mtd/maps/physmap.c +++ b/drivers/mtd/maps/physmap.c @@ -147,6 +147,7 @@ static int physmap_flash_probe(struct platform_device *dev) devices_found++; } info->mtd[i]->owner = THIS_MODULE; + info->mtd[i]->dev.parent = &dev->dev; } if (devices_found == 1) { diff --git a/drivers/mtd/maps/physmap_of.c b/drivers/mtd/maps/physmap_of.c index fbf0ca939d7..c83a60fada5 100644 --- a/drivers/mtd/maps/physmap_of.c +++ b/drivers/mtd/maps/physmap_of.c @@ -219,6 +219,7 @@ static int __devinit of_flash_probe(struct of_device *dev, goto err_out; } info->mtd->owner = THIS_MODULE; + info->mtd->dev.parent = &dev->dev; #ifdef CONFIG_MTD_PARTITIONS /* First look for RedBoot table or partitions on the command diff --git a/drivers/mtd/maps/plat-ram.c b/drivers/mtd/maps/plat-ram.c index e7dd9c8a965..49c9ece7647 100644 --- a/drivers/mtd/maps/plat-ram.c +++ b/drivers/mtd/maps/plat-ram.c @@ -224,6 +224,7 @@ static int platram_probe(struct platform_device *pdev) } info->mtd->owner = THIS_MODULE; + info->mtd->dev.parent = &pdev->dev; platram_setrw(info, PLATRAM_RW); diff --git a/drivers/mtd/maps/rbtx4939-flash.c b/drivers/mtd/maps/rbtx4939-flash.c new file mode 100644 index 00000000000..d39f0adac84 --- /dev/null +++ b/drivers/mtd/maps/rbtx4939-flash.c @@ -0,0 +1,208 @@ +/* + * rbtx4939-flash (based on physmap.c) + * + * This is a simplified physmap driver with map_init callback function. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * Copyright (C) 2009 Atsushi Nemoto <anemo@mba.ocn.ne.jp> + */ + +#include <linux/module.h> +#include <linux/types.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/device.h> +#include <linux/platform_device.h> +#include <linux/mtd/mtd.h> +#include <linux/mtd/map.h> +#include <linux/mtd/partitions.h> +#include <asm/txx9/rbtx4939.h> + +struct rbtx4939_flash_info { + struct mtd_info *mtd; + struct map_info map; +#ifdef CONFIG_MTD_PARTITIONS + int nr_parts; + struct mtd_partition *parts; +#endif +}; + +static int rbtx4939_flash_remove(struct platform_device *dev) +{ + struct rbtx4939_flash_info *info; + + info = platform_get_drvdata(dev); + if (!info) + return 0; + platform_set_drvdata(dev, NULL); + + if (info->mtd) { +#ifdef CONFIG_MTD_PARTITIONS + struct rbtx4939_flash_data *pdata = dev->dev.platform_data; + + if (info->nr_parts) { + del_mtd_partitions(info->mtd); + kfree(info->parts); + } else if (pdata->nr_parts) + del_mtd_partitions(info->mtd); + else + del_mtd_device(info->mtd); +#else + del_mtd_device(info->mtd); +#endif + map_destroy(info->mtd); + } + return 0; +} + +static const char *rom_probe_types[] = { "cfi_probe", "jedec_probe", NULL }; +#ifdef CONFIG_MTD_PARTITIONS +static const char *part_probe_types[] = { "cmdlinepart", NULL }; +#endif + +static int rbtx4939_flash_probe(struct platform_device *dev) +{ + struct rbtx4939_flash_data *pdata; + struct rbtx4939_flash_info *info; + struct resource *res; + const char **probe_type; + int err = 0; + unsigned long size; + + pdata = dev->dev.platform_data; + if (!pdata) + return -ENODEV; + + res = platform_get_resource(dev, IORESOURCE_MEM, 0); + if (!res) + return -ENODEV; + info = devm_kzalloc(&dev->dev, sizeof(struct rbtx4939_flash_info), + GFP_KERNEL); + if (!info) + return -ENOMEM; + + platform_set_drvdata(dev, info); + + size = resource_size(res); + pr_notice("rbtx4939 platform flash device: %pR\n", res); + + if (!devm_request_mem_region(&dev->dev, res->start, size, + dev_name(&dev->dev))) + return -EBUSY; + + info->map.name = dev_name(&dev->dev); + info->map.phys = res->start; + info->map.size = size; + info->map.bankwidth = pdata->width; + + info->map.virt = devm_ioremap(&dev->dev, info->map.phys, size); + if (!info->map.virt) + return -EBUSY; + + if (pdata->map_init) + (*pdata->map_init)(&info->map); + else + simple_map_init(&info->map); + + probe_type = rom_probe_types; + for (; !info->mtd && *probe_type; probe_type++) + info->mtd = do_map_probe(*probe_type, &info->map); + if (!info->mtd) { + dev_err(&dev->dev, "map_probe failed\n"); + err = -ENXIO; + goto err_out; + } + info->mtd->owner = THIS_MODULE; + if (err) + goto err_out; + +#ifdef CONFIG_MTD_PARTITIONS + err = parse_mtd_partitions(info->mtd, part_probe_types, + &info->parts, 0); + if (err > 0) { + add_mtd_partitions(info->mtd, info->parts, err); + info->nr_parts = err; + return 0; + } + + if (pdata->nr_parts) { + pr_notice("Using rbtx4939 partition information\n"); + add_mtd_partitions(info->mtd, pdata->parts, pdata->nr_parts); + return 0; + } +#endif + + add_mtd_device(info->mtd); + return 0; + +err_out: + rbtx4939_flash_remove(dev); + return err; +} + +#ifdef CONFIG_PM +static int rbtx4939_flash_suspend(struct platform_device *dev, + pm_message_t state) +{ + struct rbtx4939_flash_info *info = platform_get_drvdata(dev); + + if (info->mtd->suspend) + return info->mtd->suspend(info->mtd); + return 0; +} + +static int rbtx4939_flash_resume(struct platform_device *dev) +{ + struct rbtx4939_flash_info *info = platform_get_drvdata(dev); + + if (info->mtd->resume) + info->mtd->resume(info->mtd); + return 0; +} + +static void rbtx4939_flash_shutdown(struct platform_device *dev) +{ + struct rbtx4939_flash_info *info = platform_get_drvdata(dev); + + if (info->mtd->suspend && info->mtd->resume) + if (info->mtd->suspend(info->mtd) == 0) + info->mtd->resume(info->mtd); +} +#else +#define rbtx4939_flash_suspend NULL +#define rbtx4939_flash_resume NULL +#define rbtx4939_flash_shutdown NULL +#endif + +static struct platform_driver rbtx4939_flash_driver = { + .probe = rbtx4939_flash_probe, + .remove = rbtx4939_flash_remove, + .suspend = rbtx4939_flash_suspend, + .resume = rbtx4939_flash_resume, + .shutdown = rbtx4939_flash_shutdown, + .driver = { + .name = "rbtx4939-flash", + .owner = THIS_MODULE, + }, +}; + +static int __init rbtx4939_flash_init(void) +{ + return platform_driver_register(&rbtx4939_flash_driver); +} + +static void __exit rbtx4939_flash_exit(void) +{ + platform_driver_unregister(&rbtx4939_flash_driver); +} + +module_init(rbtx4939_flash_init); +module_exit(rbtx4939_flash_exit); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("RBTX4939 MTD map driver"); +MODULE_ALIAS("platform:rbtx4939-flash"); diff --git a/drivers/mtd/maps/sa1100-flash.c b/drivers/mtd/maps/sa1100-flash.c index 8f57b6f40aa..05e9362dc7f 100644 --- a/drivers/mtd/maps/sa1100-flash.c +++ b/drivers/mtd/maps/sa1100-flash.c @@ -351,7 +351,7 @@ sa1100_setup_mtd(struct platform_device *pdev, struct flash_platform_data *plat) static const char *part_probes[] = { "cmdlinepart", "RedBoot", NULL }; -static int __init sa1100_mtd_probe(struct platform_device *pdev) +static int __devinit sa1100_mtd_probe(struct platform_device *pdev) { struct flash_platform_data *plat = pdev->dev.platform_data; struct mtd_partition *parts; diff --git a/drivers/mtd/maps/sharpsl-flash.c b/drivers/mtd/maps/sharpsl-flash.c deleted file mode 100644 index b392f096c70..00000000000 --- a/drivers/mtd/maps/sharpsl-flash.c +++ /dev/null @@ -1,116 +0,0 @@ -/* - * sharpsl-flash.c - * - * Copyright (C) 2001 Lineo Japan, Inc. - * Copyright (C) 2002 SHARP - * - * based on rpxlite.c,v 1.15 2001/10/02 15:05:14 dwmw2 Exp - * Handle mapping of the flash on the RPX Lite and CLLF boards - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - */ - -#include <linux/module.h> -#include <linux/types.h> -#include <linux/kernel.h> -#include <linux/mtd/mtd.h> -#include <linux/mtd/map.h> -#include <linux/mtd/partitions.h> -#include <asm/io.h> -#include <asm/mach-types.h> - -#define WINDOW_ADDR 0x00000000 -#define WINDOW_SIZE 0x00800000 -#define BANK_WIDTH 2 - -static struct mtd_info *mymtd; - -struct map_info sharpsl_map = { - .name = "sharpsl-flash", - .size = WINDOW_SIZE, - .bankwidth = BANK_WIDTH, - .phys = WINDOW_ADDR -}; - -static struct mtd_partition sharpsl_partitions[1] = { - { - name: "Boot PROM Filesystem", - } -}; - -static int __init init_sharpsl(void) -{ - struct mtd_partition *parts; - int nb_parts = 0; - char *part_type = "static"; - - printk(KERN_NOTICE "Sharp SL series flash device: %x at %x\n", - WINDOW_SIZE, WINDOW_ADDR); - sharpsl_map.virt = ioremap(WINDOW_ADDR, WINDOW_SIZE); - if (!sharpsl_map.virt) { - printk("Failed to ioremap\n"); - return -EIO; - } - - simple_map_init(&sharpsl_map); - - mymtd = do_map_probe("map_rom", &sharpsl_map); - if (!mymtd) { - iounmap(sharpsl_map.virt); - return -ENXIO; - } - - mymtd->owner = THIS_MODULE; - - if (machine_is_corgi() || machine_is_shepherd() || machine_is_husky() - || machine_is_poodle()) { - sharpsl_partitions[0].size=0x006d0000; - sharpsl_partitions[0].offset=0x00120000; - } else if (machine_is_tosa()) { - sharpsl_partitions[0].size=0x006a0000; - sharpsl_partitions[0].offset=0x00160000; - } else if (machine_is_spitz() || machine_is_akita() || machine_is_borzoi()) { - sharpsl_partitions[0].size=0x006b0000; - sharpsl_partitions[0].offset=0x00140000; - } else { - map_destroy(mymtd); - iounmap(sharpsl_map.virt); - return -ENODEV; - } - - parts = sharpsl_partitions; - nb_parts = ARRAY_SIZE(sharpsl_partitions); - - printk(KERN_NOTICE "Using %s partition definition\n", part_type); - add_mtd_partitions(mymtd, parts, nb_parts); - - return 0; -} - -static void __exit cleanup_sharpsl(void) -{ - if (mymtd) { - del_mtd_partitions(mymtd); - map_destroy(mymtd); - } - if (sharpsl_map.virt) { - iounmap(sharpsl_map.virt); - sharpsl_map.virt = 0; - } -} - -module_init(init_sharpsl); -module_exit(cleanup_sharpsl); - -MODULE_LICENSE("GPL"); -MODULE_AUTHOR("SHARP (Original: Arnold Christensen <AKC@pel.dk>)"); -MODULE_DESCRIPTION("MTD map driver for SHARP SL series"); diff --git a/drivers/mtd/mtd_blkdevs.c b/drivers/mtd/mtd_blkdevs.c index 1409f01406f..a49a9c8f2cb 100644 --- a/drivers/mtd/mtd_blkdevs.c +++ b/drivers/mtd/mtd_blkdevs.c @@ -286,6 +286,7 @@ int add_mtd_blktrans_dev(struct mtd_blktrans_dev *new) gd->private_data = new; new->blkcore_priv = gd; gd->queue = tr->blkcore_priv->rq; + gd->driverfs_dev = new->mtd->dev.parent; if (new->readonly) set_disk_ro(gd, 1); @@ -382,11 +383,12 @@ int register_mtd_blktrans(struct mtd_blktrans_ops *tr) tr->blkcore_priv->thread = kthread_run(mtd_blktrans_thread, tr, "%sd", tr->name); if (IS_ERR(tr->blkcore_priv->thread)) { + int ret = PTR_ERR(tr->blkcore_priv->thread); blk_cleanup_queue(tr->blkcore_priv->rq); unregister_blkdev(tr->major, tr->name); kfree(tr->blkcore_priv); mutex_unlock(&mtd_table_mutex); - return PTR_ERR(tr->blkcore_priv->thread); + return ret; } INIT_LIST_HEAD(&tr->devs); diff --git a/drivers/mtd/mtdbdi.c b/drivers/mtd/mtdbdi.c new file mode 100644 index 00000000000..5ca5aed0b22 --- /dev/null +++ b/drivers/mtd/mtdbdi.c @@ -0,0 +1,43 @@ +/* MTD backing device capabilities + * + * Copyright © 2006 Red Hat, Inc. All Rights Reserved. + * Written by David Howells (dhowells@redhat.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. + */ + +#include <linux/backing-dev.h> +#include <linux/mtd/mtd.h> +#include "internal.h" + +/* + * backing device capabilities for non-mappable devices (such as NAND flash) + * - permits private mappings, copies are taken of the data + */ +struct backing_dev_info mtd_bdi_unmappable = { + .capabilities = BDI_CAP_MAP_COPY, +}; + +/* + * backing device capabilities for R/O mappable devices (such as ROM) + * - permits private mappings, copies are taken of the data + * - permits non-writable shared mappings + */ +struct backing_dev_info mtd_bdi_ro_mappable = { + .capabilities = (BDI_CAP_MAP_COPY | BDI_CAP_MAP_DIRECT | + BDI_CAP_EXEC_MAP | BDI_CAP_READ_MAP), +}; + +/* + * backing device capabilities for writable mappable devices (such as RAM) + * - permits private mappings, copies are taken of the data + * - permits non-writable shared mappings + */ +struct backing_dev_info mtd_bdi_rw_mappable = { + .capabilities = (BDI_CAP_MAP_COPY | BDI_CAP_MAP_DIRECT | + BDI_CAP_EXEC_MAP | BDI_CAP_READ_MAP | + BDI_CAP_WRITE_MAP), +}; diff --git a/drivers/mtd/mtdchar.c b/drivers/mtd/mtdchar.c index e9ec59e9a56..763d3f0a1f4 100644 --- a/drivers/mtd/mtdchar.c +++ b/drivers/mtd/mtdchar.c @@ -13,39 +13,13 @@ #include <linux/slab.h> #include <linux/sched.h> #include <linux/smp_lock.h> +#include <linux/backing-dev.h> #include <linux/mtd/mtd.h> #include <linux/mtd/compatmac.h> #include <asm/uaccess.h> -static struct class *mtd_class; - -static void mtd_notify_add(struct mtd_info* mtd) -{ - if (!mtd) - return; - - device_create(mtd_class, NULL, MKDEV(MTD_CHAR_MAJOR, mtd->index*2), - NULL, "mtd%d", mtd->index); - - device_create(mtd_class, NULL, MKDEV(MTD_CHAR_MAJOR, mtd->index*2+1), - NULL, "mtd%dro", mtd->index); -} - -static void mtd_notify_remove(struct mtd_info* mtd) -{ - if (!mtd) - return; - - device_destroy(mtd_class, MKDEV(MTD_CHAR_MAJOR, mtd->index*2)); - device_destroy(mtd_class, MKDEV(MTD_CHAR_MAJOR, mtd->index*2+1)); -} - -static struct mtd_notifier notifier = { - .add = mtd_notify_add, - .remove = mtd_notify_remove, -}; /* * Data structure to hold the pointer to the mtd device as well @@ -107,12 +81,15 @@ static int mtd_open(struct inode *inode, struct file *file) goto out; } - if (MTD_ABSENT == mtd->type) { + if (mtd->type == MTD_ABSENT) { put_mtd_device(mtd); ret = -ENODEV; goto out; } + if (mtd->backing_dev_info) + file->f_mapping->backing_dev_info = mtd->backing_dev_info; + /* You can't open it RW if it's not a writeable device */ if ((file->f_mode & FMODE_WRITE) && !(mtd->flags & MTD_WRITEABLE)) { put_mtd_device(mtd); @@ -781,6 +758,59 @@ static int mtd_ioctl(struct inode *inode, struct file *file, return ret; } /* memory_ioctl */ +/* + * try to determine where a shared mapping can be made + * - only supported for NOMMU at the moment (MMU can't doesn't copy private + * mappings) + */ +#ifndef CONFIG_MMU +static unsigned long mtd_get_unmapped_area(struct file *file, + unsigned long addr, + unsigned long len, + unsigned long pgoff, + unsigned long flags) +{ + struct mtd_file_info *mfi = file->private_data; + struct mtd_info *mtd = mfi->mtd; + + if (mtd->get_unmapped_area) { + unsigned long offset; + + if (addr != 0) + return (unsigned long) -EINVAL; + + if (len > mtd->size || pgoff >= (mtd->size >> PAGE_SHIFT)) + return (unsigned long) -EINVAL; + + offset = pgoff << PAGE_SHIFT; + if (offset > mtd->size - len) + return (unsigned long) -EINVAL; + + return mtd->get_unmapped_area(mtd, len, offset, flags); + } + + /* can't map directly */ + return (unsigned long) -ENOSYS; +} +#endif + +/* + * set up a mapping for shared memory segments + */ +static int mtd_mmap(struct file *file, struct vm_area_struct *vma) +{ +#ifdef CONFIG_MMU + struct mtd_file_info *mfi = file->private_data; + struct mtd_info *mtd = mfi->mtd; + + if (mtd->type == MTD_RAM || mtd->type == MTD_ROM) + return 0; + return -ENOSYS; +#else + return vma->vm_flags & VM_SHARED ? 0 : -ENOSYS; +#endif +} + static const struct file_operations mtd_fops = { .owner = THIS_MODULE, .llseek = mtd_lseek, @@ -789,39 +819,36 @@ static const struct file_operations mtd_fops = { .ioctl = mtd_ioctl, .open = mtd_open, .release = mtd_close, + .mmap = mtd_mmap, +#ifndef CONFIG_MMU + .get_unmapped_area = mtd_get_unmapped_area, +#endif }; static int __init init_mtdchar(void) { - if (register_chrdev(MTD_CHAR_MAJOR, "mtd", &mtd_fops)) { + int status; + + status = register_chrdev(MTD_CHAR_MAJOR, "mtd", &mtd_fops); + if (status < 0) { printk(KERN_NOTICE "Can't allocate major number %d for Memory Technology Devices.\n", MTD_CHAR_MAJOR); - return -EAGAIN; } - mtd_class = class_create(THIS_MODULE, "mtd"); - - if (IS_ERR(mtd_class)) { - printk(KERN_ERR "Error creating mtd class.\n"); - unregister_chrdev(MTD_CHAR_MAJOR, "mtd"); - return PTR_ERR(mtd_class); - } - - register_mtd_user(¬ifier); - return 0; + return status; } static void __exit cleanup_mtdchar(void) { - unregister_mtd_user(¬ifier); - class_destroy(mtd_class); unregister_chrdev(MTD_CHAR_MAJOR, "mtd"); } module_init(init_mtdchar); module_exit(cleanup_mtdchar); +MODULE_ALIAS_CHARDEV_MAJOR(MTD_CHAR_MAJOR); MODULE_LICENSE("GPL"); MODULE_AUTHOR("David Woodhouse <dwmw2@infradead.org>"); MODULE_DESCRIPTION("Direct character-device access to MTD devices"); +MODULE_ALIAS_CHARDEV_MAJOR(MTD_CHAR_MAJOR); diff --git a/drivers/mtd/mtdconcat.c b/drivers/mtd/mtdconcat.c index 3dbb1b38db6..792b547786b 100644 --- a/drivers/mtd/mtdconcat.c +++ b/drivers/mtd/mtdconcat.c @@ -13,6 +13,7 @@ #include <linux/slab.h> #include <linux/sched.h> #include <linux/types.h> +#include <linux/backing-dev.h> #include <linux/mtd/mtd.h> #include <linux/mtd/concat.h> @@ -684,6 +685,40 @@ static int concat_block_markbad(struct mtd_info *mtd, loff_t ofs) } /* + * try to support NOMMU mmaps on concatenated devices + * - we don't support subdev spanning as we can't guarantee it'll work + */ +static unsigned long concat_get_unmapped_area(struct mtd_info *mtd, + unsigned long len, + unsigned long offset, + unsigned long flags) +{ + struct mtd_concat *concat = CONCAT(mtd); + int i; + + for (i = 0; i < concat->num_subdev; i++) { + struct mtd_info *subdev = concat->subdev[i]; + + if (offset >= subdev->size) { + offset -= subdev->size; + continue; + } + + /* we've found the subdev over which the mapping will reside */ + if (offset + len > subdev->size) + return (unsigned long) -EINVAL; + + if (subdev->get_unmapped_area) + return subdev->get_unmapped_area(subdev, len, offset, + flags); + + break; + } + + return (unsigned long) -ENOSYS; +} + +/* * This function constructs a virtual MTD device by concatenating * num_devs MTD devices. A pointer to the new device object is * stored to *new_dev upon success. This function does _not_ @@ -740,6 +775,8 @@ struct mtd_info *mtd_concat_create(struct mtd_info *subdev[], /* subdevices to c concat->mtd.ecc_stats.badblocks = subdev[0]->ecc_stats.badblocks; + concat->mtd.backing_dev_info = subdev[0]->backing_dev_info; + concat->subdev[0] = subdev[0]; for (i = 1; i < num_devs; i++) { @@ -766,6 +803,15 @@ struct mtd_info *mtd_concat_create(struct mtd_info *subdev[], /* subdevices to c concat->mtd.flags |= subdev[i]->flags & MTD_WRITEABLE; } + + /* only permit direct mapping if the BDIs are all the same + * - copy-mapping is still permitted + */ + if (concat->mtd.backing_dev_info != + subdev[i]->backing_dev_info) + concat->mtd.backing_dev_info = + &default_backing_dev_info; + concat->mtd.size += subdev[i]->size; concat->mtd.ecc_stats.badblocks += subdev[i]->ecc_stats.badblocks; @@ -796,6 +842,7 @@ struct mtd_info *mtd_concat_create(struct mtd_info *subdev[], /* subdevices to c concat->mtd.unlock = concat_unlock; concat->mtd.suspend = concat_suspend; concat->mtd.resume = concat_resume; + concat->mtd.get_unmapped_area = concat_get_unmapped_area; /* * Combine the erase block size info of the subdevices: diff --git a/drivers/mtd/mtdcore.c b/drivers/mtd/mtdcore.c index 76fe0a1e7a5..fdd6ae85939 100644 --- a/drivers/mtd/mtdcore.c +++ b/drivers/mtd/mtdcore.c @@ -19,9 +19,13 @@ #include <linux/proc_fs.h> #include <linux/mtd/mtd.h> +#include "internal.h" #include "mtdcore.h" + +static struct class *mtd_class; + /* These are exported solely for the purpose of mtd_blkdevs.c. You should not use them for _anything_ else */ DEFINE_MUTEX(mtd_table_mutex); @@ -32,6 +36,160 @@ EXPORT_SYMBOL_GPL(mtd_table); static LIST_HEAD(mtd_notifiers); + +#if defined(CONFIG_MTD_CHAR) || defined(CONFIG_MTD_CHAR_MODULE) +#define MTD_DEVT(index) MKDEV(MTD_CHAR_MAJOR, (index)*2) +#else +#define MTD_DEVT(index) 0 +#endif + +/* REVISIT once MTD uses the driver model better, whoever allocates + * the mtd_info will probably want to use the release() hook... + */ +static void mtd_release(struct device *dev) +{ + struct mtd_info *mtd = dev_to_mtd(dev); + + /* remove /dev/mtdXro node if needed */ + if (MTD_DEVT(mtd->index)) + device_destroy(mtd_class, MTD_DEVT(mtd->index) + 1); +} + +static ssize_t mtd_type_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct mtd_info *mtd = dev_to_mtd(dev); + char *type; + + switch (mtd->type) { + case MTD_ABSENT: + type = "absent"; + break; + case MTD_RAM: + type = "ram"; + break; + case MTD_ROM: + type = "rom"; + break; + case MTD_NORFLASH: + type = "nor"; + break; + case MTD_NANDFLASH: + type = "nand"; + break; + case MTD_DATAFLASH: + type = "dataflash"; + break; + case MTD_UBIVOLUME: + type = "ubi"; + break; + default: + type = "unknown"; + } + + return snprintf(buf, PAGE_SIZE, "%s\n", type); +} +static DEVICE_ATTR(type, S_IRUGO, mtd_type_show, NULL); + +static ssize_t mtd_flags_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct mtd_info *mtd = dev_to_mtd(dev); + + return snprintf(buf, PAGE_SIZE, "0x%lx\n", (unsigned long)mtd->flags); + +} +static DEVICE_ATTR(flags, S_IRUGO, mtd_flags_show, NULL); + +static ssize_t mtd_size_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct mtd_info *mtd = dev_to_mtd(dev); + + return snprintf(buf, PAGE_SIZE, "%llu\n", + (unsigned long long)mtd->size); + +} +static DEVICE_ATTR(size, S_IRUGO, mtd_size_show, NULL); + +static ssize_t mtd_erasesize_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct mtd_info *mtd = dev_to_mtd(dev); + + return snprintf(buf, PAGE_SIZE, "%lu\n", (unsigned long)mtd->erasesize); + +} +static DEVICE_ATTR(erasesize, S_IRUGO, mtd_erasesize_show, NULL); + +static ssize_t mtd_writesize_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct mtd_info *mtd = dev_to_mtd(dev); + + return snprintf(buf, PAGE_SIZE, "%lu\n", (unsigned long)mtd->writesize); + +} +static DEVICE_ATTR(writesize, S_IRUGO, mtd_writesize_show, NULL); + +static ssize_t mtd_oobsize_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct mtd_info *mtd = dev_to_mtd(dev); + + return snprintf(buf, PAGE_SIZE, "%lu\n", (unsigned long)mtd->oobsize); + +} +static DEVICE_ATTR(oobsize, S_IRUGO, mtd_oobsize_show, NULL); + +static ssize_t mtd_numeraseregions_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct mtd_info *mtd = dev_to_mtd(dev); + + return snprintf(buf, PAGE_SIZE, "%u\n", mtd->numeraseregions); + +} +static DEVICE_ATTR(numeraseregions, S_IRUGO, mtd_numeraseregions_show, + NULL); + +static ssize_t mtd_name_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct mtd_info *mtd = dev_to_mtd(dev); + + return snprintf(buf, PAGE_SIZE, "%s\n", mtd->name); + +} +static DEVICE_ATTR(name, S_IRUGO, mtd_name_show, NULL); + +static struct attribute *mtd_attrs[] = { + &dev_attr_type.attr, + &dev_attr_flags.attr, + &dev_attr_size.attr, + &dev_attr_erasesize.attr, + &dev_attr_writesize.attr, + &dev_attr_oobsize.attr, + &dev_attr_numeraseregions.attr, + &dev_attr_name.attr, + NULL, +}; + +struct attribute_group mtd_group = { + .attrs = mtd_attrs, +}; + +struct attribute_group *mtd_groups[] = { + &mtd_group, + NULL, +}; + +static struct device_type mtd_devtype = { + .name = "mtd", + .groups = mtd_groups, + .release = mtd_release, +}; + /** * add_mtd_device - register an MTD device * @mtd: pointer to new MTD device info structure @@ -40,12 +198,27 @@ static LIST_HEAD(mtd_notifiers); * notify each currently active MTD 'user' of its arrival. Returns * zero on success or 1 on failure, which currently will only happen * if the number of present devices exceeds MAX_MTD_DEVICES (i.e. 16) + * or there's a sysfs error. */ int add_mtd_device(struct mtd_info *mtd) { int i; + if (!mtd->backing_dev_info) { + switch (mtd->type) { + case MTD_RAM: + mtd->backing_dev_info = &mtd_bdi_rw_mappable; + break; + case MTD_ROM: + mtd->backing_dev_info = &mtd_bdi_ro_mappable; + break; + default: + mtd->backing_dev_info = &mtd_bdi_unmappable; + break; + } + } + BUG_ON(mtd->writesize == 0); mutex_lock(&mtd_table_mutex); @@ -80,6 +253,23 @@ int add_mtd_device(struct mtd_info *mtd) mtd->name); } + /* Caller should have set dev.parent to match the + * physical device. + */ + mtd->dev.type = &mtd_devtype; + mtd->dev.class = mtd_class; + mtd->dev.devt = MTD_DEVT(i); + dev_set_name(&mtd->dev, "mtd%d", i); + if (device_register(&mtd->dev) != 0) { + mtd_table[i] = NULL; + break; + } + + if (MTD_DEVT(i)) + device_create(mtd_class, mtd->dev.parent, + MTD_DEVT(i) + 1, + NULL, "mtd%dro", i); + DEBUG(0, "mtd: Giving out device %d to %s\n",i, mtd->name); /* No need to get a refcount on the module containing the notifier, since we hold the mtd_table_mutex */ @@ -124,6 +314,8 @@ int del_mtd_device (struct mtd_info *mtd) } else { struct mtd_notifier *not; + device_unregister(&mtd->dev); + /* No need to get a refcount on the module containing the notifier, since we hold the mtd_table_mutex */ list_for_each_entry(not, &mtd_notifiers, list) @@ -393,28 +585,38 @@ done: return ((count < begin+len-off) ? count : begin+len-off); } +#endif /* CONFIG_PROC_FS */ + /*====================================================================*/ /* Init code */ static int __init init_mtd(void) { + mtd_class = class_create(THIS_MODULE, "mtd"); + + if (IS_ERR(mtd_class)) { + pr_err("Error creating mtd class.\n"); + return PTR_ERR(mtd_class); + } +#ifdef CONFIG_PROC_FS if ((proc_mtd = create_proc_entry( "mtd", 0, NULL ))) proc_mtd->read_proc = mtd_read_proc; +#endif /* CONFIG_PROC_FS */ return 0; } static void __exit cleanup_mtd(void) { +#ifdef CONFIG_PROC_FS if (proc_mtd) remove_proc_entry( "mtd", NULL); +#endif /* CONFIG_PROC_FS */ + class_destroy(mtd_class); } module_init(init_mtd); module_exit(cleanup_mtd); -#endif /* CONFIG_PROC_FS */ - - MODULE_LICENSE("GPL"); MODULE_AUTHOR("David Woodhouse <dwmw2@infradead.org>"); MODULE_DESCRIPTION("Core MTD registration and access routines"); diff --git a/drivers/mtd/mtdoops.c b/drivers/mtd/mtdoops.c index 1a6b3beabe8..1060337c06d 100644 --- a/drivers/mtd/mtdoops.c +++ b/drivers/mtd/mtdoops.c @@ -44,6 +44,7 @@ static struct mtdoops_context { int oops_pages; int nextpage; int nextcount; + char *name; void *oops_buf; @@ -273,6 +274,9 @@ static void mtdoops_notify_add(struct mtd_info *mtd) { struct mtdoops_context *cxt = &oops_cxt; + if (cxt->name && !strcmp(mtd->name, cxt->name)) + cxt->mtd_index = mtd->index; + if ((mtd->index != cxt->mtd_index) || cxt->mtd_index < 0) return; @@ -357,8 +361,10 @@ mtdoops_console_write(struct console *co, const char *s, unsigned int count) spin_lock_irqsave(&cxt->writecount_lock, flags); /* Check ready status didn't change whilst waiting for the lock */ - if (!cxt->ready) + if (!cxt->ready) { + spin_unlock_irqrestore(&cxt->writecount_lock, flags); return; + } if (cxt->writecount == 0) { u32 *stamp = cxt->oops_buf; @@ -383,8 +389,12 @@ static int __init mtdoops_console_setup(struct console *co, char *options) { struct mtdoops_context *cxt = co->data; - if (cxt->mtd_index != -1) + if (cxt->mtd_index != -1 || cxt->name) return -EBUSY; + if (options) { + cxt->name = kstrdup(options, GFP_KERNEL); + return 0; + } if (co->index == -1) return -EINVAL; @@ -412,6 +422,7 @@ static int __init mtdoops_console_init(void) cxt->mtd_index = -1; cxt->oops_buf = vmalloc(OOPS_PAGE_SIZE); + spin_lock_init(&cxt->writecount_lock); if (!cxt->oops_buf) { printk(KERN_ERR "Failed to allocate mtdoops buffer workspace\n"); @@ -432,6 +443,7 @@ static void __exit mtdoops_console_exit(void) unregister_mtd_user(&mtdoops_notifier); unregister_console(&mtdoops_console); + kfree(cxt->name); vfree(cxt->oops_buf); } diff --git a/drivers/mtd/mtdpart.c b/drivers/mtd/mtdpart.c index 144e6b613a7..29675edb44b 100644 --- a/drivers/mtd/mtdpart.c +++ b/drivers/mtd/mtdpart.c @@ -48,8 +48,11 @@ static int part_read(struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen, u_char *buf) { struct mtd_part *part = PART(mtd); + struct mtd_ecc_stats stats; int res; + stats = part->master->ecc_stats; + if (from >= mtd->size) len = 0; else if (from + len > mtd->size) @@ -58,9 +61,9 @@ static int part_read(struct mtd_info *mtd, loff_t from, size_t len, len, retlen, buf); if (unlikely(res)) { if (res == -EUCLEAN) - mtd->ecc_stats.corrected++; + mtd->ecc_stats.corrected += part->master->ecc_stats.corrected - stats.corrected; if (res == -EBADMSG) - mtd->ecc_stats.failed++; + mtd->ecc_stats.failed += part->master->ecc_stats.failed - stats.failed; } return res; } @@ -84,6 +87,18 @@ static void part_unpoint(struct mtd_info *mtd, loff_t from, size_t len) part->master->unpoint(part->master, from + part->offset, len); } +static unsigned long part_get_unmapped_area(struct mtd_info *mtd, + unsigned long len, + unsigned long offset, + unsigned long flags) +{ + struct mtd_part *part = PART(mtd); + + offset += part->offset; + return part->master->get_unmapped_area(part->master, len, offset, + flags); +} + static int part_read_oob(struct mtd_info *mtd, loff_t from, struct mtd_oob_ops *ops) { @@ -342,6 +357,12 @@ static struct mtd_part *add_one_partition(struct mtd_info *master, slave->mtd.name = part->name; slave->mtd.owner = master->owner; + slave->mtd.backing_dev_info = master->backing_dev_info; + + /* NOTE: we don't arrange MTDs as a tree; it'd be error-prone + * to have the same data be in two different partitions. + */ + slave->mtd.dev.parent = master->dev.parent; slave->mtd.read = part_read; slave->mtd.write = part_write; @@ -354,6 +375,8 @@ static struct mtd_part *add_one_partition(struct mtd_info *master, slave->mtd.unpoint = part_unpoint; } + if (master->get_unmapped_area) + slave->mtd.get_unmapped_area = part_get_unmapped_area; if (master->read_oob) slave->mtd.read_oob = part_read_oob; if (master->write_oob) @@ -493,7 +516,9 @@ out_register: * This function, given a master MTD object and a partition table, creates * and registers slave MTD objects which are bound to the master according to * the partition definitions. - * (Q: should we register the master MTD object as well?) + * + * We don't register the master, or expect the caller to have done so, + * for reasons of data integrity. */ int add_mtd_partitions(struct mtd_info *master, diff --git a/drivers/mtd/nand/Kconfig b/drivers/mtd/nand/Kconfig index 2ff88791ceb..890936d0275 100644 --- a/drivers/mtd/nand/Kconfig +++ b/drivers/mtd/nand/Kconfig @@ -334,7 +334,7 @@ config MTD_NAND_ATMEL_ECC_NONE endchoice config MTD_NAND_PXA3xx - bool "Support for NAND flash devices on PXA3xx" + tristate "Support for NAND flash devices on PXA3xx" depends on MTD_NAND && PXA3xx help This enables the driver for the NAND flash device found on @@ -427,4 +427,23 @@ config MTD_NAND_SH_FLCTL Several Renesas SuperH CPU has FLCTL. This option enables support for NAND Flash using FLCTL. This driver support SH7723. +config MTD_NAND_DAVINCI + tristate "Support NAND on DaVinci SoC" + depends on ARCH_DAVINCI + help + Enable the driver for NAND flash chips on Texas Instruments + DaVinci processors. + +config MTD_NAND_TXX9NDFMC + tristate "NAND Flash support for TXx9 SoC" + depends on SOC_TX4938 || SOC_TX4939 + help + This enables the NAND flash controller on the TXx9 SoCs. + +config MTD_NAND_SOCRATES + tristate "Support for NAND on Socrates board" + depends on MTD_NAND && SOCRATES + help + Enables support for NAND Flash chips wired onto Socrates board. + endif # MTD_NAND diff --git a/drivers/mtd/nand/Makefile b/drivers/mtd/nand/Makefile index b661586afbf..d33860ac42c 100644 --- a/drivers/mtd/nand/Makefile +++ b/drivers/mtd/nand/Makefile @@ -14,6 +14,7 @@ obj-$(CONFIG_MTD_NAND_AU1550) += au1550nd.o obj-$(CONFIG_MTD_NAND_BF5XX) += bf5xx_nand.o obj-$(CONFIG_MTD_NAND_PPCHAMELEONEVB) += ppchameleonevb.o obj-$(CONFIG_MTD_NAND_S3C2410) += s3c2410.o +obj-$(CONFIG_MTD_NAND_DAVINCI) += davinci_nand.o obj-$(CONFIG_MTD_NAND_DISKONCHIP) += diskonchip.o obj-$(CONFIG_MTD_NAND_H1900) += h1910.o obj-$(CONFIG_MTD_NAND_RTC_FROM4) += rtc_from4.o @@ -36,5 +37,7 @@ obj-$(CONFIG_MTD_NAND_FSL_ELBC) += fsl_elbc_nand.o obj-$(CONFIG_MTD_NAND_FSL_UPM) += fsl_upm.o obj-$(CONFIG_MTD_NAND_SH_FLCTL) += sh_flctl.o obj-$(CONFIG_MTD_NAND_MXC) += mxc_nand.o +obj-$(CONFIG_MTD_NAND_SOCRATES) += socrates_nand.o +obj-$(CONFIG_MTD_NAND_TXX9NDFMC) += txx9ndfmc.o nand-objs := nand_base.o nand_bbt.o diff --git a/drivers/mtd/nand/bf5xx_nand.c b/drivers/mtd/nand/bf5xx_nand.c index 9af2a2cc115..4c2a67ca801 100644 --- a/drivers/mtd/nand/bf5xx_nand.c +++ b/drivers/mtd/nand/bf5xx_nand.c @@ -552,7 +552,6 @@ static void bf5xx_nand_dma_write_buf(struct mtd_info *mtd, static int bf5xx_nand_dma_init(struct bf5xx_nand_info *info) { int ret; - unsigned short val; /* Do not use dma */ if (!hardware_ecc) @@ -560,13 +559,6 @@ static int bf5xx_nand_dma_init(struct bf5xx_nand_info *info) init_completion(&info->dma_completion); -#ifdef CONFIG_BF54x - /* Setup DMAC1 channel mux for NFC which shared with SDH */ - val = bfin_read_DMAC1_PERIMUX(); - val &= 0xFFFE; - bfin_write_DMAC1_PERIMUX(val); - SSYNC(); -#endif /* Request NFC DMA channel */ ret = request_dma(CH_NFC, "BF5XX NFC driver"); if (ret < 0) { @@ -574,7 +566,13 @@ static int bf5xx_nand_dma_init(struct bf5xx_nand_info *info) return ret; } - set_dma_callback(CH_NFC, (void *) bf5xx_nand_dma_irq, (void *) info); +#ifdef CONFIG_BF54x + /* Setup DMAC1 channel mux for NFC which shared with SDH */ + bfin_write_DMAC1_PERIMUX(bfin_read_DMAC1_PERIMUX() & ~1); + SSYNC(); +#endif + + set_dma_callback(CH_NFC, bf5xx_nand_dma_irq, info); /* Turn off the DMA channel first */ disable_dma(CH_NFC); @@ -632,7 +630,7 @@ static int bf5xx_nand_hw_init(struct bf5xx_nand_info *info) /* * Device management interface */ -static int bf5xx_nand_add_partition(struct bf5xx_nand_info *info) +static int __devinit bf5xx_nand_add_partition(struct bf5xx_nand_info *info) { struct mtd_info *mtd = &info->mtd; diff --git a/drivers/mtd/nand/cafe_nand.c b/drivers/mtd/nand/cafe_nand.c index 22a6b2e50e9..7c5b257ce8e 100644 --- a/drivers/mtd/nand/cafe_nand.c +++ b/drivers/mtd/nand/cafe_nand.c @@ -654,6 +654,7 @@ static int __devinit cafe_nand_probe(struct pci_dev *pdev, } cafe = (void *)(&mtd[1]); + mtd->dev.parent = &pdev->dev; mtd->priv = cafe; mtd->owner = THIS_MODULE; diff --git a/drivers/mtd/nand/davinci_nand.c b/drivers/mtd/nand/davinci_nand.c new file mode 100644 index 00000000000..0119220de7d --- /dev/null +++ b/drivers/mtd/nand/davinci_nand.c @@ -0,0 +1,570 @@ +/* + * davinci_nand.c - NAND Flash Driver for DaVinci family chips + * + * Copyright © 2006 Texas Instruments. + * + * Port to 2.6.23 Copyright © 2008 by: + * Sander Huijsen <Shuijsen@optelecom-nkf.com> + * Troy Kisky <troy.kisky@boundarydevices.com> + * Dirk Behme <Dirk.Behme@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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/err.h> +#include <linux/clk.h> +#include <linux/io.h> +#include <linux/mtd/nand.h> +#include <linux/mtd/partitions.h> + +#include <mach/nand.h> + +#include <asm/mach-types.h> + + +/* + * This is a device driver for the NAND flash controller found on the + * various DaVinci family chips. It handles up to four SoC chipselects, + * and some flavors of secondary chipselect (e.g. based on A12) as used + * with multichip packages. + * + * The 1-bit ECC hardware is supported, but not yet the newer 4-bit ECC + * available on chips like the DM355 and OMAP-L137 and needed with the + * more error-prone MLC NAND chips. + * + * This driver assumes EM_WAIT connects all the NAND devices' RDY/nBUSY + * outputs in a "wire-AND" configuration, with no per-chip signals. + */ +struct davinci_nand_info { + struct mtd_info mtd; + struct nand_chip chip; + + struct device *dev; + struct clk *clk; + bool partitioned; + + void __iomem *base; + void __iomem *vaddr; + + uint32_t ioaddr; + uint32_t current_cs; + + uint32_t mask_chipsel; + uint32_t mask_ale; + uint32_t mask_cle; + + uint32_t core_chipsel; +}; + +static DEFINE_SPINLOCK(davinci_nand_lock); + +#define to_davinci_nand(m) container_of(m, struct davinci_nand_info, mtd) + + +static inline unsigned int davinci_nand_readl(struct davinci_nand_info *info, + int offset) +{ + return __raw_readl(info->base + offset); +} + +static inline void davinci_nand_writel(struct davinci_nand_info *info, + int offset, unsigned long value) +{ + __raw_writel(value, info->base + offset); +} + +/*----------------------------------------------------------------------*/ + +/* + * Access to hardware control lines: ALE, CLE, secondary chipselect. + */ + +static void nand_davinci_hwcontrol(struct mtd_info *mtd, int cmd, + unsigned int ctrl) +{ + struct davinci_nand_info *info = to_davinci_nand(mtd); + uint32_t addr = info->current_cs; + struct nand_chip *nand = mtd->priv; + + /* Did the control lines change? */ + if (ctrl & NAND_CTRL_CHANGE) { + if ((ctrl & NAND_CTRL_CLE) == NAND_CTRL_CLE) + addr |= info->mask_cle; + else if ((ctrl & NAND_CTRL_ALE) == NAND_CTRL_ALE) + addr |= info->mask_ale; + + nand->IO_ADDR_W = (void __iomem __force *)addr; + } + + if (cmd != NAND_CMD_NONE) + iowrite8(cmd, nand->IO_ADDR_W); +} + +static void nand_davinci_select_chip(struct mtd_info *mtd, int chip) +{ + struct davinci_nand_info *info = to_davinci_nand(mtd); + uint32_t addr = info->ioaddr; + + /* maybe kick in a second chipselect */ + if (chip > 0) + addr |= info->mask_chipsel; + info->current_cs = addr; + + info->chip.IO_ADDR_W = (void __iomem __force *)addr; + info->chip.IO_ADDR_R = info->chip.IO_ADDR_W; +} + +/*----------------------------------------------------------------------*/ + +/* + * 1-bit hardware ECC ... context maintained for each core chipselect + */ + +static inline uint32_t nand_davinci_readecc_1bit(struct mtd_info *mtd) +{ + struct davinci_nand_info *info = to_davinci_nand(mtd); + + return davinci_nand_readl(info, NANDF1ECC_OFFSET + + 4 * info->core_chipsel); +} + +static void nand_davinci_hwctl_1bit(struct mtd_info *mtd, int mode) +{ + struct davinci_nand_info *info; + uint32_t nandcfr; + unsigned long flags; + + info = to_davinci_nand(mtd); + + /* Reset ECC hardware */ + nand_davinci_readecc_1bit(mtd); + + spin_lock_irqsave(&davinci_nand_lock, flags); + + /* Restart ECC hardware */ + nandcfr = davinci_nand_readl(info, NANDFCR_OFFSET); + nandcfr |= BIT(8 + info->core_chipsel); + davinci_nand_writel(info, NANDFCR_OFFSET, nandcfr); + + spin_unlock_irqrestore(&davinci_nand_lock, flags); +} + +/* + * Read hardware ECC value and pack into three bytes + */ +static int nand_davinci_calculate_1bit(struct mtd_info *mtd, + const u_char *dat, u_char *ecc_code) +{ + unsigned int ecc_val = nand_davinci_readecc_1bit(mtd); + unsigned int ecc24 = (ecc_val & 0x0fff) | ((ecc_val & 0x0fff0000) >> 4); + + /* invert so that erased block ecc is correct */ + ecc24 = ~ecc24; + ecc_code[0] = (u_char)(ecc24); + ecc_code[1] = (u_char)(ecc24 >> 8); + ecc_code[2] = (u_char)(ecc24 >> 16); + + return 0; +} + +static int nand_davinci_correct_1bit(struct mtd_info *mtd, u_char *dat, + u_char *read_ecc, u_char *calc_ecc) +{ + struct nand_chip *chip = mtd->priv; + uint32_t eccNand = read_ecc[0] | (read_ecc[1] << 8) | + (read_ecc[2] << 16); + uint32_t eccCalc = calc_ecc[0] | (calc_ecc[1] << 8) | + (calc_ecc[2] << 16); + uint32_t diff = eccCalc ^ eccNand; + + if (diff) { + if ((((diff >> 12) ^ diff) & 0xfff) == 0xfff) { + /* Correctable error */ + if ((diff >> (12 + 3)) < chip->ecc.size) { + dat[diff >> (12 + 3)] ^= BIT((diff >> 12) & 7); + return 1; + } else { + return -1; + } + } else if (!(diff & (diff - 1))) { + /* Single bit ECC error in the ECC itself, + * nothing to fix */ + return 1; + } else { + /* Uncorrectable error */ + return -1; + } + + } + return 0; +} + +/*----------------------------------------------------------------------*/ + +/* + * NOTE: NAND boot requires ALE == EM_A[1], CLE == EM_A[2], so that's + * how these chips are normally wired. This translates to both 8 and 16 + * bit busses using ALE == BIT(3) in byte addresses, and CLE == BIT(4). + * + * For now we assume that configuration, or any other one which ignores + * the two LSBs for NAND access ... so we can issue 32-bit reads/writes + * and have that transparently morphed into multiple NAND operations. + */ +static void nand_davinci_read_buf(struct mtd_info *mtd, uint8_t *buf, int len) +{ + struct nand_chip *chip = mtd->priv; + + if ((0x03 & ((unsigned)buf)) == 0 && (0x03 & len) == 0) + ioread32_rep(chip->IO_ADDR_R, buf, len >> 2); + else if ((0x01 & ((unsigned)buf)) == 0 && (0x01 & len) == 0) + ioread16_rep(chip->IO_ADDR_R, buf, len >> 1); + else + ioread8_rep(chip->IO_ADDR_R, buf, len); +} + +static void nand_davinci_write_buf(struct mtd_info *mtd, + const uint8_t *buf, int len) +{ + struct nand_chip *chip = mtd->priv; + + if ((0x03 & ((unsigned)buf)) == 0 && (0x03 & len) == 0) + iowrite32_rep(chip->IO_ADDR_R, buf, len >> 2); + else if ((0x01 & ((unsigned)buf)) == 0 && (0x01 & len) == 0) + iowrite16_rep(chip->IO_ADDR_R, buf, len >> 1); + else + iowrite8_rep(chip->IO_ADDR_R, buf, len); +} + +/* + * Check hardware register for wait status. Returns 1 if device is ready, + * 0 if it is still busy. + */ +static int nand_davinci_dev_ready(struct mtd_info *mtd) +{ + struct davinci_nand_info *info = to_davinci_nand(mtd); + + return davinci_nand_readl(info, NANDFSR_OFFSET) & BIT(0); +} + +static void __init nand_dm6446evm_flash_init(struct davinci_nand_info *info) +{ + uint32_t regval, a1cr; + + /* + * NAND FLASH timings @ PLL1 == 459 MHz + * - AEMIF.CLK freq = PLL1/6 = 459/6 = 76.5 MHz + * - AEMIF.CLK period = 1/76.5 MHz = 13.1 ns + */ + regval = 0 + | (0 << 31) /* selectStrobe */ + | (0 << 30) /* extWait (never with NAND) */ + | (1 << 26) /* writeSetup 10 ns */ + | (3 << 20) /* writeStrobe 40 ns */ + | (1 << 17) /* writeHold 10 ns */ + | (0 << 13) /* readSetup 10 ns */ + | (3 << 7) /* readStrobe 60 ns */ + | (0 << 4) /* readHold 10 ns */ + | (3 << 2) /* turnAround ?? ns */ + | (0 << 0) /* asyncSize 8-bit bus */ + ; + a1cr = davinci_nand_readl(info, A1CR_OFFSET); + if (a1cr != regval) { + dev_dbg(info->dev, "Warning: NAND config: Set A1CR " \ + "reg to 0x%08x, was 0x%08x, should be done by " \ + "bootloader.\n", regval, a1cr); + davinci_nand_writel(info, A1CR_OFFSET, regval); + } +} + +/*----------------------------------------------------------------------*/ + +static int __init nand_davinci_probe(struct platform_device *pdev) +{ + struct davinci_nand_pdata *pdata = pdev->dev.platform_data; + struct davinci_nand_info *info; + struct resource *res1; + struct resource *res2; + void __iomem *vaddr; + void __iomem *base; + int ret; + uint32_t val; + nand_ecc_modes_t ecc_mode; + + /* which external chipselect will we be managing? */ + if (pdev->id < 0 || pdev->id > 3) + return -ENODEV; + + info = kzalloc(sizeof(*info), GFP_KERNEL); + if (!info) { + dev_err(&pdev->dev, "unable to allocate memory\n"); + ret = -ENOMEM; + goto err_nomem; + } + + platform_set_drvdata(pdev, info); + + res1 = platform_get_resource(pdev, IORESOURCE_MEM, 0); + res2 = platform_get_resource(pdev, IORESOURCE_MEM, 1); + if (!res1 || !res2) { + dev_err(&pdev->dev, "resource missing\n"); + ret = -EINVAL; + goto err_nomem; + } + + vaddr = ioremap(res1->start, res1->end - res1->start); + base = ioremap(res2->start, res2->end - res2->start); + if (!vaddr || !base) { + dev_err(&pdev->dev, "ioremap failed\n"); + ret = -EINVAL; + goto err_ioremap; + } + + info->dev = &pdev->dev; + info->base = base; + info->vaddr = vaddr; + + info->mtd.priv = &info->chip; + info->mtd.name = dev_name(&pdev->dev); + info->mtd.owner = THIS_MODULE; + + info->mtd.dev.parent = &pdev->dev; + + info->chip.IO_ADDR_R = vaddr; + info->chip.IO_ADDR_W = vaddr; + info->chip.chip_delay = 0; + info->chip.select_chip = nand_davinci_select_chip; + + /* options such as NAND_USE_FLASH_BBT or 16-bit widths */ + info->chip.options = pdata ? pdata->options : 0; + + info->ioaddr = (uint32_t __force) vaddr; + + info->current_cs = info->ioaddr; + info->core_chipsel = pdev->id; + info->mask_chipsel = pdata->mask_chipsel; + + /* use nandboot-capable ALE/CLE masks by default */ + if (pdata && pdata->mask_ale) + info->mask_ale = pdata->mask_cle; + else + info->mask_ale = MASK_ALE; + if (pdata && pdata->mask_cle) + info->mask_cle = pdata->mask_cle; + else + info->mask_cle = MASK_CLE; + + /* Set address of hardware control function */ + info->chip.cmd_ctrl = nand_davinci_hwcontrol; + info->chip.dev_ready = nand_davinci_dev_ready; + + /* Speed up buffer I/O */ + info->chip.read_buf = nand_davinci_read_buf; + info->chip.write_buf = nand_davinci_write_buf; + + /* use board-specific ECC config; else, the best available */ + if (pdata) + ecc_mode = pdata->ecc_mode; + else + ecc_mode = NAND_ECC_HW; + + switch (ecc_mode) { + case NAND_ECC_NONE: + case NAND_ECC_SOFT: + break; + case NAND_ECC_HW: + info->chip.ecc.calculate = nand_davinci_calculate_1bit; + info->chip.ecc.correct = nand_davinci_correct_1bit; + info->chip.ecc.hwctl = nand_davinci_hwctl_1bit; + info->chip.ecc.size = 512; + info->chip.ecc.bytes = 3; + break; + case NAND_ECC_HW_SYNDROME: + /* FIXME implement */ + info->chip.ecc.size = 512; + info->chip.ecc.bytes = 10; + + dev_warn(&pdev->dev, "4-bit ECC nyet supported\n"); + /* FALL THROUGH */ + default: + ret = -EINVAL; + goto err_ecc; + } + info->chip.ecc.mode = ecc_mode; + + info->clk = clk_get(&pdev->dev, "AEMIFCLK"); + if (IS_ERR(info->clk)) { + ret = PTR_ERR(info->clk); + dev_dbg(&pdev->dev, "unable to get AEMIFCLK, err %d\n", ret); + goto err_clk; + } + + ret = clk_enable(info->clk); + if (ret < 0) { + dev_dbg(&pdev->dev, "unable to enable AEMIFCLK, err %d\n", ret); + goto err_clk_enable; + } + + /* EMIF timings should normally be set by the boot loader, + * especially after boot-from-NAND. The *only* reason to + * have this special casing for the DM6446 EVM is to work + * with boot-from-NOR ... with CS0 manually re-jumpered + * (after startup) so it addresses the NAND flash, not NOR. + * Even for dev boards, that's unusually rude... + */ + if (machine_is_davinci_evm()) + nand_dm6446evm_flash_init(info); + + spin_lock_irq(&davinci_nand_lock); + + /* put CSxNAND into NAND mode */ + val = davinci_nand_readl(info, NANDFCR_OFFSET); + val |= BIT(info->core_chipsel); + davinci_nand_writel(info, NANDFCR_OFFSET, val); + + spin_unlock_irq(&davinci_nand_lock); + + /* Scan to find existence of the device(s) */ + ret = nand_scan(&info->mtd, pdata->mask_chipsel ? 2 : 1); + if (ret < 0) { + dev_dbg(&pdev->dev, "no NAND chip(s) found\n"); + goto err_scan; + } + + if (mtd_has_partitions()) { + struct mtd_partition *mtd_parts = NULL; + int mtd_parts_nb = 0; + + if (mtd_has_cmdlinepart()) { + static const char *probes[] __initconst = + { "cmdlinepart", NULL }; + + const char *master_name; + + /* Set info->mtd.name = 0 temporarily */ + master_name = info->mtd.name; + info->mtd.name = (char *)0; + + /* info->mtd.name == 0, means: don't bother checking + <mtd-id> */ + mtd_parts_nb = parse_mtd_partitions(&info->mtd, probes, + &mtd_parts, 0); + + /* Restore info->mtd.name */ + info->mtd.name = master_name; + } + + if (mtd_parts_nb <= 0 && pdata) { + mtd_parts = pdata->parts; + mtd_parts_nb = pdata->nr_parts; + } + + /* Register any partitions */ + if (mtd_parts_nb > 0) { + ret = add_mtd_partitions(&info->mtd, + mtd_parts, mtd_parts_nb); + if (ret == 0) + info->partitioned = true; + } + + } else if (pdata && pdata->nr_parts) { + dev_warn(&pdev->dev, "ignoring %d default partitions on %s\n", + pdata->nr_parts, info->mtd.name); + } + + /* If there's no partition info, just package the whole chip + * as a single MTD device. + */ + if (!info->partitioned) + ret = add_mtd_device(&info->mtd) ? -ENODEV : 0; + + if (ret < 0) + goto err_scan; + + val = davinci_nand_readl(info, NRCSR_OFFSET); + dev_info(&pdev->dev, "controller rev. %d.%d\n", + (val >> 8) & 0xff, val & 0xff); + + return 0; + +err_scan: + clk_disable(info->clk); + +err_clk_enable: + clk_put(info->clk); + +err_ecc: +err_clk: +err_ioremap: + if (base) + iounmap(base); + if (vaddr) + iounmap(vaddr); + +err_nomem: + kfree(info); + return ret; +} + +static int __exit nand_davinci_remove(struct platform_device *pdev) +{ + struct davinci_nand_info *info = platform_get_drvdata(pdev); + int status; + + if (mtd_has_partitions() && info->partitioned) + status = del_mtd_partitions(&info->mtd); + else + status = del_mtd_device(&info->mtd); + + iounmap(info->base); + iounmap(info->vaddr); + + nand_release(&info->mtd); + + clk_disable(info->clk); + clk_put(info->clk); + + kfree(info); + + return 0; +} + +static struct platform_driver nand_davinci_driver = { + .remove = __exit_p(nand_davinci_remove), + .driver = { + .name = "davinci_nand", + }, +}; +MODULE_ALIAS("platform:davinci_nand"); + +static int __init nand_davinci_init(void) +{ + return platform_driver_probe(&nand_davinci_driver, nand_davinci_probe); +} +module_init(nand_davinci_init); + +static void __exit nand_davinci_exit(void) +{ + platform_driver_unregister(&nand_davinci_driver); +} +module_exit(nand_davinci_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Texas Instruments"); +MODULE_DESCRIPTION("Davinci NAND flash driver"); + diff --git a/drivers/mtd/nand/fsl_upm.c b/drivers/mtd/nand/fsl_upm.c index 7815a404a63..d120cd8d726 100644 --- a/drivers/mtd/nand/fsl_upm.c +++ b/drivers/mtd/nand/fsl_upm.c @@ -23,6 +23,10 @@ #include <linux/io.h> #include <asm/fsl_lbc.h> +#define FSL_UPM_WAIT_RUN_PATTERN 0x1 +#define FSL_UPM_WAIT_WRITE_BYTE 0x2 +#define FSL_UPM_WAIT_WRITE_BUFFER 0x4 + struct fsl_upm_nand { struct device *dev; struct mtd_info mtd; @@ -36,8 +40,12 @@ struct fsl_upm_nand { uint8_t upm_addr_offset; uint8_t upm_cmd_offset; void __iomem *io_base; - int rnb_gpio; + int rnb_gpio[NAND_MAX_CHIPS]; + uint32_t mchip_offsets[NAND_MAX_CHIPS]; + uint32_t mchip_count; + uint32_t mchip_number; int chip_delay; + uint32_t wait_flags; }; #define to_fsl_upm_nand(mtd) container_of(mtd, struct fsl_upm_nand, mtd) @@ -46,7 +54,7 @@ static int fun_chip_ready(struct mtd_info *mtd) { struct fsl_upm_nand *fun = to_fsl_upm_nand(mtd); - if (gpio_get_value(fun->rnb_gpio)) + if (gpio_get_value(fun->rnb_gpio[fun->mchip_number])) return 1; dev_vdbg(fun->dev, "busy\n"); @@ -55,9 +63,9 @@ static int fun_chip_ready(struct mtd_info *mtd) static void fun_wait_rnb(struct fsl_upm_nand *fun) { - int cnt = 1000000; + if (fun->rnb_gpio[fun->mchip_number] >= 0) { + int cnt = 1000000; - if (fun->rnb_gpio >= 0) { while (--cnt && !fun_chip_ready(&fun->mtd)) cpu_relax(); if (!cnt) @@ -69,7 +77,9 @@ static void fun_wait_rnb(struct fsl_upm_nand *fun) static void fun_cmd_ctrl(struct mtd_info *mtd, int cmd, unsigned int ctrl) { + struct nand_chip *chip = mtd->priv; struct fsl_upm_nand *fun = to_fsl_upm_nand(mtd); + u32 mar; if (!(ctrl & fun->last_ctrl)) { fsl_upm_end_pattern(&fun->upm); @@ -87,9 +97,28 @@ static void fun_cmd_ctrl(struct mtd_info *mtd, int cmd, unsigned int ctrl) fsl_upm_start_pattern(&fun->upm, fun->upm_cmd_offset); } - fsl_upm_run_pattern(&fun->upm, fun->io_base, cmd); + mar = (cmd << (32 - fun->upm.width)) | + fun->mchip_offsets[fun->mchip_number]; + fsl_upm_run_pattern(&fun->upm, chip->IO_ADDR_R, mar); + + if (fun->wait_flags & FSL_UPM_WAIT_RUN_PATTERN) + fun_wait_rnb(fun); +} + +static void fun_select_chip(struct mtd_info *mtd, int mchip_nr) +{ + struct nand_chip *chip = mtd->priv; + struct fsl_upm_nand *fun = to_fsl_upm_nand(mtd); - fun_wait_rnb(fun); + if (mchip_nr == -1) { + chip->cmd_ctrl(mtd, NAND_CMD_NONE, 0 | NAND_CTRL_CHANGE); + } else if (mchip_nr >= 0) { + fun->mchip_number = mchip_nr; + chip->IO_ADDR_R = fun->io_base + fun->mchip_offsets[mchip_nr]; + chip->IO_ADDR_W = chip->IO_ADDR_R; + } else { + BUG(); + } } static uint8_t fun_read_byte(struct mtd_info *mtd) @@ -115,8 +144,11 @@ static void fun_write_buf(struct mtd_info *mtd, const uint8_t *buf, int len) for (i = 0; i < len; i++) { out_8(fun->chip.IO_ADDR_W, buf[i]); - fun_wait_rnb(fun); + if (fun->wait_flags & FSL_UPM_WAIT_WRITE_BYTE) + fun_wait_rnb(fun); } + if (fun->wait_flags & FSL_UPM_WAIT_WRITE_BUFFER) + fun_wait_rnb(fun); } static int __devinit fun_chip_init(struct fsl_upm_nand *fun, @@ -137,8 +169,10 @@ static int __devinit fun_chip_init(struct fsl_upm_nand *fun, fun->chip.read_buf = fun_read_buf; fun->chip.write_buf = fun_write_buf; fun->chip.ecc.mode = NAND_ECC_SOFT; + if (fun->mchip_count > 1) + fun->chip.select_chip = fun_select_chip; - if (fun->rnb_gpio >= 0) + if (fun->rnb_gpio[0] >= 0) fun->chip.dev_ready = fun_chip_ready; fun->mtd.priv = &fun->chip; @@ -155,7 +189,7 @@ static int __devinit fun_chip_init(struct fsl_upm_nand *fun, goto err; } - ret = nand_scan(&fun->mtd, 1); + ret = nand_scan(&fun->mtd, fun->mchip_count); if (ret) goto err; @@ -185,8 +219,10 @@ static int __devinit fun_probe(struct of_device *ofdev, struct fsl_upm_nand *fun; struct resource io_res; const uint32_t *prop; + int rnb_gpio; int ret; int size; + int i; fun = kzalloc(sizeof(*fun), GFP_KERNEL); if (!fun) @@ -208,7 +244,7 @@ static int __devinit fun_probe(struct of_device *ofdev, if (!prop || size != sizeof(uint32_t)) { dev_err(&ofdev->dev, "can't get UPM address offset\n"); ret = -EINVAL; - goto err2; + goto err1; } fun->upm_addr_offset = *prop; @@ -216,21 +252,40 @@ static int __devinit fun_probe(struct of_device *ofdev, if (!prop || size != sizeof(uint32_t)) { dev_err(&ofdev->dev, "can't get UPM command offset\n"); ret = -EINVAL; - goto err2; + goto err1; } fun->upm_cmd_offset = *prop; - fun->rnb_gpio = of_get_gpio(ofdev->node, 0); - if (fun->rnb_gpio >= 0) { - ret = gpio_request(fun->rnb_gpio, dev_name(&ofdev->dev)); - if (ret) { - dev_err(&ofdev->dev, "can't request RNB gpio\n"); + prop = of_get_property(ofdev->node, + "fsl,upm-addr-line-cs-offsets", &size); + if (prop && (size / sizeof(uint32_t)) > 0) { + fun->mchip_count = size / sizeof(uint32_t); + if (fun->mchip_count >= NAND_MAX_CHIPS) { + dev_err(&ofdev->dev, "too much multiple chips\n"); + goto err1; + } + for (i = 0; i < fun->mchip_count; i++) + fun->mchip_offsets[i] = prop[i]; + } else { + fun->mchip_count = 1; + } + + for (i = 0; i < fun->mchip_count; i++) { + fun->rnb_gpio[i] = -1; + rnb_gpio = of_get_gpio(ofdev->node, i); + if (rnb_gpio >= 0) { + ret = gpio_request(rnb_gpio, dev_name(&ofdev->dev)); + if (ret) { + dev_err(&ofdev->dev, + "can't request RNB gpio #%d\n", i); + goto err2; + } + gpio_direction_input(rnb_gpio); + fun->rnb_gpio[i] = rnb_gpio; + } else if (rnb_gpio == -EINVAL) { + dev_err(&ofdev->dev, "RNB gpio #%d is invalid\n", i); goto err2; } - gpio_direction_input(fun->rnb_gpio); - } else if (fun->rnb_gpio == -EINVAL) { - dev_err(&ofdev->dev, "specified RNB gpio is invalid\n"); - goto err2; } prop = of_get_property(ofdev->node, "chip-delay", NULL); @@ -239,8 +294,15 @@ static int __devinit fun_probe(struct of_device *ofdev, else fun->chip_delay = 50; + prop = of_get_property(ofdev->node, "fsl,upm-wait-flags", &size); + if (prop && size == sizeof(uint32_t)) + fun->wait_flags = *prop; + else + fun->wait_flags = FSL_UPM_WAIT_RUN_PATTERN | + FSL_UPM_WAIT_WRITE_BYTE; + fun->io_base = devm_ioremap_nocache(&ofdev->dev, io_res.start, - io_res.end - io_res.start + 1); + io_res.end - io_res.start + 1); if (!fun->io_base) { ret = -ENOMEM; goto err2; @@ -257,8 +319,11 @@ static int __devinit fun_probe(struct of_device *ofdev, return 0; err2: - if (fun->rnb_gpio >= 0) - gpio_free(fun->rnb_gpio); + for (i = 0; i < fun->mchip_count; i++) { + if (fun->rnb_gpio[i] < 0) + break; + gpio_free(fun->rnb_gpio[i]); + } err1: kfree(fun); @@ -268,12 +333,16 @@ err1: static int __devexit fun_remove(struct of_device *ofdev) { struct fsl_upm_nand *fun = dev_get_drvdata(&ofdev->dev); + int i; nand_release(&fun->mtd); kfree(fun->mtd.name); - if (fun->rnb_gpio >= 0) - gpio_free(fun->rnb_gpio); + for (i = 0; i < fun->mchip_count; i++) { + if (fun->rnb_gpio[i] < 0) + break; + gpio_free(fun->rnb_gpio[i]); + } kfree(fun); diff --git a/drivers/mtd/nand/mxc_nand.c b/drivers/mtd/nand/mxc_nand.c index bad048aca89..f3548d04801 100644 --- a/drivers/mtd/nand/mxc_nand.c +++ b/drivers/mtd/nand/mxc_nand.c @@ -866,6 +866,7 @@ static int __init mxcnd_probe(struct platform_device *pdev) mtd = &host->mtd; mtd->priv = this; mtd->owner = THIS_MODULE; + mtd->dev.parent = &pdev->dev; /* 50 us command delay time */ this->chip_delay = 5; diff --git a/drivers/mtd/nand/nand_base.c b/drivers/mtd/nand/nand_base.c index 5f71371eb1b..3d7ed432fa4 100644 --- a/drivers/mtd/nand/nand_base.c +++ b/drivers/mtd/nand/nand_base.c @@ -82,6 +82,20 @@ static struct nand_ecclayout nand_oob_64 = { .length = 38}} }; +static struct nand_ecclayout nand_oob_128 = { + .eccbytes = 48, + .eccpos = { + 80, 81, 82, 83, 84, 85, 86, 87, + 88, 89, 90, 91, 92, 93, 94, 95, + 96, 97, 98, 99, 100, 101, 102, 103, + 104, 105, 106, 107, 108, 109, 110, 111, + 112, 113, 114, 115, 116, 117, 118, 119, + 120, 121, 122, 123, 124, 125, 126, 127}, + .oobfree = { + {.offset = 2, + .length = 78}} +}; + static int nand_get_device(struct nand_chip *chip, struct mtd_info *mtd, int new_state); @@ -748,6 +762,8 @@ static int nand_wait(struct mtd_info *mtd, struct nand_chip *chip) * @mtd: mtd info structure * @chip: nand chip info structure * @buf: buffer to store read data + * + * Not for syndrome calculating ecc controllers, which use a special oob layout */ static int nand_read_page_raw(struct mtd_info *mtd, struct nand_chip *chip, uint8_t *buf) @@ -758,6 +774,47 @@ static int nand_read_page_raw(struct mtd_info *mtd, struct nand_chip *chip, } /** + * nand_read_page_raw_syndrome - [Intern] read raw page data without ecc + * @mtd: mtd info structure + * @chip: nand chip info structure + * @buf: buffer to store read data + * + * We need a special oob layout and handling even when OOB isn't used. + */ +static int nand_read_page_raw_syndrome(struct mtd_info *mtd, struct nand_chip *chip, + uint8_t *buf) +{ + int eccsize = chip->ecc.size; + int eccbytes = chip->ecc.bytes; + uint8_t *oob = chip->oob_poi; + int steps, size; + + for (steps = chip->ecc.steps; steps > 0; steps--) { + chip->read_buf(mtd, buf, eccsize); + buf += eccsize; + + if (chip->ecc.prepad) { + chip->read_buf(mtd, oob, chip->ecc.prepad); + oob += chip->ecc.prepad; + } + + chip->read_buf(mtd, oob, eccbytes); + oob += eccbytes; + + if (chip->ecc.postpad) { + chip->read_buf(mtd, oob, chip->ecc.postpad); + oob += chip->ecc.postpad; + } + } + + size = mtd->oobsize - (oob - chip->oob_poi); + if (size) + chip->read_buf(mtd, oob, size); + + return 0; +} + +/** * nand_read_page_swecc - [REPLACABLE] software ecc based page read function * @mtd: mtd info structure * @chip: nand chip info structure @@ -1482,6 +1539,8 @@ static int nand_read_oob(struct mtd_info *mtd, loff_t from, * @mtd: mtd info structure * @chip: nand chip info structure * @buf: data buffer + * + * Not for syndrome calculating ecc controllers, which use a special oob layout */ static void nand_write_page_raw(struct mtd_info *mtd, struct nand_chip *chip, const uint8_t *buf) @@ -1491,6 +1550,44 @@ static void nand_write_page_raw(struct mtd_info *mtd, struct nand_chip *chip, } /** + * nand_write_page_raw_syndrome - [Intern] raw page write function + * @mtd: mtd info structure + * @chip: nand chip info structure + * @buf: data buffer + * + * We need a special oob layout and handling even when ECC isn't checked. + */ +static void nand_write_page_raw_syndrome(struct mtd_info *mtd, struct nand_chip *chip, + const uint8_t *buf) +{ + int eccsize = chip->ecc.size; + int eccbytes = chip->ecc.bytes; + uint8_t *oob = chip->oob_poi; + int steps, size; + + for (steps = chip->ecc.steps; steps > 0; steps--) { + chip->write_buf(mtd, buf, eccsize); + buf += eccsize; + + if (chip->ecc.prepad) { + chip->write_buf(mtd, oob, chip->ecc.prepad); + oob += chip->ecc.prepad; + } + + chip->read_buf(mtd, oob, eccbytes); + oob += eccbytes; + + if (chip->ecc.postpad) { + chip->write_buf(mtd, oob, chip->ecc.postpad); + oob += chip->ecc.postpad; + } + } + + size = mtd->oobsize - (oob - chip->oob_poi); + if (size) + chip->write_buf(mtd, oob, size); +} +/** * nand_write_page_swecc - [REPLACABLE] software ecc based page write function * @mtd: mtd info structure * @chip: nand chip info structure @@ -1863,7 +1960,7 @@ static int nand_do_write_oob(struct mtd_info *mtd, loff_t to, } if (unlikely(ops->ooboffs >= len)) { - DEBUG(MTD_DEBUG_LEVEL0, "nand_read_oob: " + DEBUG(MTD_DEBUG_LEVEL0, "nand_do_write_oob: " "Attempt to start write outside oob\n"); return -EINVAL; } @@ -1873,7 +1970,7 @@ static int nand_do_write_oob(struct mtd_info *mtd, loff_t to, ops->ooboffs + ops->ooblen > ((mtd->size >> chip->page_shift) - (to >> chip->page_shift)) * len)) { - DEBUG(MTD_DEBUG_LEVEL0, "nand_read_oob: " + DEBUG(MTD_DEBUG_LEVEL0, "nand_do_write_oob: " "Attempt write beyond end of device\n"); return -EINVAL; } @@ -1929,8 +2026,8 @@ static int nand_write_oob(struct mtd_info *mtd, loff_t to, /* Do not allow writes past end of device */ if (ops->datbuf && (to + ops->len) > mtd->size) { - DEBUG(MTD_DEBUG_LEVEL0, "nand_read_oob: " - "Attempt read beyond end of device\n"); + DEBUG(MTD_DEBUG_LEVEL0, "nand_write_oob: " + "Attempt write beyond end of device\n"); return -EINVAL; } @@ -2555,6 +2652,9 @@ int nand_scan_tail(struct mtd_info *mtd) case 64: chip->ecc.layout = &nand_oob_64; break; + case 128: + chip->ecc.layout = &nand_oob_128; + break; default: printk(KERN_WARNING "No oob scheme defined for " "oobsize %d\n", mtd->oobsize); @@ -2569,10 +2669,6 @@ int nand_scan_tail(struct mtd_info *mtd) * check ECC mode, default to software if 3byte/512byte hardware ECC is * selected and we have 256 byte pagesize fallback to software ECC */ - if (!chip->ecc.read_page_raw) - chip->ecc.read_page_raw = nand_read_page_raw; - if (!chip->ecc.write_page_raw) - chip->ecc.write_page_raw = nand_write_page_raw; switch (chip->ecc.mode) { case NAND_ECC_HW: @@ -2581,6 +2677,10 @@ int nand_scan_tail(struct mtd_info *mtd) chip->ecc.read_page = nand_read_page_hwecc; if (!chip->ecc.write_page) chip->ecc.write_page = nand_write_page_hwecc; + if (!chip->ecc.read_page_raw) + chip->ecc.read_page_raw = nand_read_page_raw; + if (!chip->ecc.write_page_raw) + chip->ecc.write_page_raw = nand_write_page_raw; if (!chip->ecc.read_oob) chip->ecc.read_oob = nand_read_oob_std; if (!chip->ecc.write_oob) @@ -2602,6 +2702,10 @@ int nand_scan_tail(struct mtd_info *mtd) chip->ecc.read_page = nand_read_page_syndrome; if (!chip->ecc.write_page) chip->ecc.write_page = nand_write_page_syndrome; + if (!chip->ecc.read_page_raw) + chip->ecc.read_page_raw = nand_read_page_raw_syndrome; + if (!chip->ecc.write_page_raw) + chip->ecc.write_page_raw = nand_write_page_raw_syndrome; if (!chip->ecc.read_oob) chip->ecc.read_oob = nand_read_oob_syndrome; if (!chip->ecc.write_oob) @@ -2620,6 +2724,8 @@ int nand_scan_tail(struct mtd_info *mtd) chip->ecc.read_page = nand_read_page_swecc; chip->ecc.read_subpage = nand_read_subpage; chip->ecc.write_page = nand_write_page_swecc; + chip->ecc.read_page_raw = nand_read_page_raw; + chip->ecc.write_page_raw = nand_write_page_raw; chip->ecc.read_oob = nand_read_oob_std; chip->ecc.write_oob = nand_write_oob_std; chip->ecc.size = 256; @@ -2632,6 +2738,8 @@ int nand_scan_tail(struct mtd_info *mtd) chip->ecc.read_page = nand_read_page_raw; chip->ecc.write_page = nand_write_page_raw; chip->ecc.read_oob = nand_read_oob_std; + chip->ecc.read_page_raw = nand_read_page_raw; + chip->ecc.write_page_raw = nand_write_page_raw; chip->ecc.write_oob = nand_write_oob_std; chip->ecc.size = mtd->writesize; chip->ecc.bytes = 0; @@ -2676,6 +2784,7 @@ int nand_scan_tail(struct mtd_info *mtd) break; case 4: case 8: + case 16: mtd->subpage_sft = 2; break; } diff --git a/drivers/mtd/nand/plat_nand.c b/drivers/mtd/nand/plat_nand.c index 75f9f4874ec..86e1d08eee0 100644 --- a/drivers/mtd/nand/plat_nand.c +++ b/drivers/mtd/nand/plat_nand.c @@ -30,7 +30,7 @@ struct plat_nand_data { /* * Probe for the NAND device. */ -static int __init plat_nand_probe(struct platform_device *pdev) +static int __devinit plat_nand_probe(struct platform_device *pdev) { struct platform_nand_data *pdata = pdev->dev.platform_data; struct plat_nand_data *data; diff --git a/drivers/mtd/nand/pxa3xx_nand.c b/drivers/mtd/nand/pxa3xx_nand.c index 61b69cc4000..30a8ce6d3e6 100644 --- a/drivers/mtd/nand/pxa3xx_nand.c +++ b/drivers/mtd/nand/pxa3xx_nand.c @@ -170,7 +170,13 @@ static int use_dma = 1; module_param(use_dma, bool, 0444); MODULE_PARM_DESC(use_dma, "enable DMA for data transfering to/from NAND HW"); -#ifdef CONFIG_MTD_NAND_PXA3xx_BUILTIN +/* + * Default NAND flash controller configuration setup by the + * bootloader. This configuration is used only when pdata->keep_config is set + */ +static struct pxa3xx_nand_timing default_timing; +static struct pxa3xx_nand_flash default_flash; + static struct pxa3xx_nand_cmdset smallpage_cmdset = { .read1 = 0x0000, .read2 = 0x0050, @@ -197,6 +203,7 @@ static struct pxa3xx_nand_cmdset largepage_cmdset = { .lock_status = 0x007A, }; +#ifdef CONFIG_MTD_NAND_PXA3xx_BUILTIN static struct pxa3xx_nand_timing samsung512MbX16_timing = { .tCH = 10, .tCS = 0, @@ -296,9 +303,23 @@ static struct pxa3xx_nand_flash *builtin_flash_types[] = { #define NDTR1_tWHR(c) (min((c), 15) << 4) #define NDTR1_tAR(c) (min((c), 15) << 0) +#define tCH_NDTR0(r) (((r) >> 19) & 0x7) +#define tCS_NDTR0(r) (((r) >> 16) & 0x7) +#define tWH_NDTR0(r) (((r) >> 11) & 0x7) +#define tWP_NDTR0(r) (((r) >> 8) & 0x7) +#define tRH_NDTR0(r) (((r) >> 3) & 0x7) +#define tRP_NDTR0(r) (((r) >> 0) & 0x7) + +#define tR_NDTR1(r) (((r) >> 16) & 0xffff) +#define tWHR_NDTR1(r) (((r) >> 4) & 0xf) +#define tAR_NDTR1(r) (((r) >> 0) & 0xf) + /* convert nano-seconds to nand flash controller clock cycles */ #define ns2cycle(ns, clk) (int)(((ns) * (clk / 1000000) / 1000) - 1) +/* convert nand flash controller clock cycles to nano-seconds */ +#define cycle2ns(c, clk) ((((c) + 1) * 1000000 + clk / 500) / (clk / 1000)) + static void pxa3xx_nand_set_timing(struct pxa3xx_nand_info *info, const struct pxa3xx_nand_timing *t) { @@ -920,6 +941,82 @@ static int pxa3xx_nand_config_flash(struct pxa3xx_nand_info *info, return 0; } +static void pxa3xx_nand_detect_timing(struct pxa3xx_nand_info *info, + struct pxa3xx_nand_timing *t) +{ + unsigned long nand_clk = clk_get_rate(info->clk); + uint32_t ndtr0 = nand_readl(info, NDTR0CS0); + uint32_t ndtr1 = nand_readl(info, NDTR1CS0); + + t->tCH = cycle2ns(tCH_NDTR0(ndtr0), nand_clk); + t->tCS = cycle2ns(tCS_NDTR0(ndtr0), nand_clk); + t->tWH = cycle2ns(tWH_NDTR0(ndtr0), nand_clk); + t->tWP = cycle2ns(tWP_NDTR0(ndtr0), nand_clk); + t->tRH = cycle2ns(tRH_NDTR0(ndtr0), nand_clk); + t->tRP = cycle2ns(tRP_NDTR0(ndtr0), nand_clk); + + t->tR = cycle2ns(tR_NDTR1(ndtr1), nand_clk); + t->tWHR = cycle2ns(tWHR_NDTR1(ndtr1), nand_clk); + t->tAR = cycle2ns(tAR_NDTR1(ndtr1), nand_clk); +} + +static int pxa3xx_nand_detect_config(struct pxa3xx_nand_info *info) +{ + uint32_t ndcr = nand_readl(info, NDCR); + struct nand_flash_dev *type = NULL; + uint32_t id = -1; + int i; + + default_flash.page_per_block = ndcr & NDCR_PG_PER_BLK ? 64 : 32; + default_flash.page_size = ndcr & NDCR_PAGE_SZ ? 2048 : 512; + default_flash.flash_width = ndcr & NDCR_DWIDTH_M ? 16 : 8; + default_flash.dfc_width = ndcr & NDCR_DWIDTH_C ? 16 : 8; + + if (default_flash.page_size == 2048) + default_flash.cmdset = &largepage_cmdset; + else + default_flash.cmdset = &smallpage_cmdset; + + /* set info fields needed to __readid */ + info->flash_info = &default_flash; + info->read_id_bytes = (default_flash.page_size == 2048) ? 4 : 2; + info->reg_ndcr = ndcr; + + if (__readid(info, &id)) + return -ENODEV; + + /* Lookup the flash id */ + id = (id >> 8) & 0xff; /* device id is byte 2 */ + for (i = 0; nand_flash_ids[i].name != NULL; i++) { + if (id == nand_flash_ids[i].id) { + type = &nand_flash_ids[i]; + break; + } + } + + if (!type) + return -ENODEV; + + /* fill the missing flash information */ + i = __ffs(default_flash.page_per_block * default_flash.page_size); + default_flash.num_blocks = type->chipsize << (20 - i); + + info->oob_size = (default_flash.page_size == 2048) ? 64 : 16; + + /* calculate addressing information */ + info->col_addr_cycles = (default_flash.page_size == 2048) ? 2 : 1; + + if (default_flash.num_blocks * default_flash.page_per_block > 65536) + info->row_addr_cycles = 3; + else + info->row_addr_cycles = 2; + + pxa3xx_nand_detect_timing(info, &default_timing); + default_flash.timing = &default_timing; + + return 0; +} + static int pxa3xx_nand_detect_flash(struct pxa3xx_nand_info *info, const struct pxa3xx_nand_platform_data *pdata) { @@ -927,6 +1024,10 @@ static int pxa3xx_nand_detect_flash(struct pxa3xx_nand_info *info, uint32_t id = -1; int i; + if (pdata->keep_config) + if (pxa3xx_nand_detect_config(info) == 0) + return 0; + for (i = 0; i<pdata->num_flash; ++i) { f = pdata->flash + i; @@ -1078,6 +1179,7 @@ static int pxa3xx_nand_probe(struct platform_device *pdev) this = &info->nand_chip; mtd->priv = info; + mtd->owner = THIS_MODULE; info->clk = clk_get(&pdev->dev, NULL); if (IS_ERR(info->clk)) { @@ -1117,14 +1219,14 @@ static int pxa3xx_nand_probe(struct platform_device *pdev) goto fail_put_clk; } - r = request_mem_region(r->start, r->end - r->start + 1, pdev->name); + r = request_mem_region(r->start, resource_size(r), pdev->name); if (r == NULL) { dev_err(&pdev->dev, "failed to request memory resource\n"); ret = -EBUSY; goto fail_put_clk; } - info->mmio_base = ioremap(r->start, r->end - r->start + 1); + info->mmio_base = ioremap(r->start, resource_size(r)); if (info->mmio_base == NULL) { dev_err(&pdev->dev, "ioremap() failed\n"); ret = -ENODEV; @@ -1173,7 +1275,7 @@ fail_free_buf: fail_free_io: iounmap(info->mmio_base); fail_free_res: - release_mem_region(r->start, r->end - r->start + 1); + release_mem_region(r->start, resource_size(r)); fail_put_clk: clk_disable(info->clk); clk_put(info->clk); @@ -1186,6 +1288,7 @@ static int pxa3xx_nand_remove(struct platform_device *pdev) { struct mtd_info *mtd = platform_get_drvdata(pdev); struct pxa3xx_nand_info *info = mtd->priv; + struct resource *r; platform_set_drvdata(pdev, NULL); @@ -1198,6 +1301,14 @@ static int pxa3xx_nand_remove(struct platform_device *pdev) info->data_buff, info->data_buff_phys); } else kfree(info->data_buff); + + iounmap(info->mmio_base); + r = platform_get_resource(pdev, IORESOURCE_MEM, 0); + release_mem_region(r->start, resource_size(r)); + + clk_disable(info->clk); + clk_put(info->clk); + kfree(mtd); return 0; } diff --git a/drivers/mtd/nand/sh_flctl.c b/drivers/mtd/nand/sh_flctl.c index 821acb08ff1..2bc896623e2 100644 --- a/drivers/mtd/nand/sh_flctl.c +++ b/drivers/mtd/nand/sh_flctl.c @@ -58,7 +58,7 @@ static struct nand_bbt_descr flctl_4secc_smallpage = { }; static struct nand_bbt_descr flctl_4secc_largepage = { - .options = 0, + .options = NAND_BBT_SCAN2NDPAGE, .offs = 58, .len = 2, .pattern = scan_ff_pattern, @@ -149,7 +149,7 @@ static void wait_wfifo_ready(struct sh_flctl *flctl) printk(KERN_ERR "wait_wfifo_ready(): Timeout occured \n"); } -static int wait_recfifo_ready(struct sh_flctl *flctl) +static int wait_recfifo_ready(struct sh_flctl *flctl, int sector_number) { uint32_t timeout = LOOP_TIMEOUT_MAX; int checked[4]; @@ -183,7 +183,12 @@ static int wait_recfifo_ready(struct sh_flctl *flctl) uint8_t org; int index; - index = data >> 16; + if (flctl->page_size) + index = (512 * sector_number) + + (data >> 16); + else + index = data >> 16; + org = flctl->done_buff[index]; flctl->done_buff[index] = org ^ (data & 0xFF); checked[i] = 1; @@ -238,14 +243,14 @@ static void read_fiforeg(struct sh_flctl *flctl, int rlen, int offset) } } -static int read_ecfiforeg(struct sh_flctl *flctl, uint8_t *buff) +static int read_ecfiforeg(struct sh_flctl *flctl, uint8_t *buff, int sector) { int i; unsigned long *ecc_buf = (unsigned long *)buff; void *fifo_addr = (void *)FLECFIFO(flctl); for (i = 0; i < 4; i++) { - if (wait_recfifo_ready(flctl)) + if (wait_recfifo_ready(flctl , sector)) return 1; ecc_buf[i] = readl(fifo_addr); ecc_buf[i] = be32_to_cpu(ecc_buf[i]); @@ -384,7 +389,8 @@ static void execmd_read_page_sector(struct mtd_info *mtd, int page_addr) read_fiforeg(flctl, 512, 512 * sector); ret = read_ecfiforeg(flctl, - &flctl->done_buff[mtd->writesize + 16 * sector]); + &flctl->done_buff[mtd->writesize + 16 * sector], + sector); if (ret) flctl->hwecc_cant_correct[sector] = 1; diff --git a/drivers/mtd/nand/socrates_nand.c b/drivers/mtd/nand/socrates_nand.c new file mode 100644 index 00000000000..a4519a7bd68 --- /dev/null +++ b/drivers/mtd/nand/socrates_nand.c @@ -0,0 +1,325 @@ +/* + * drivers/mtd/nand/socrates_nand.c + * + * Copyright © 2008 Ilya Yanok, Emcraft Systems + * + * + * 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/slab.h> +#include <linux/module.h> +#include <linux/mtd/mtd.h> +#include <linux/mtd/nand.h> +#include <linux/mtd/partitions.h> +#include <linux/of_platform.h> +#include <linux/io.h> + +#define FPGA_NAND_CMD_MASK (0x7 << 28) +#define FPGA_NAND_CMD_COMMAND (0x0 << 28) +#define FPGA_NAND_CMD_ADDR (0x1 << 28) +#define FPGA_NAND_CMD_READ (0x2 << 28) +#define FPGA_NAND_CMD_WRITE (0x3 << 28) +#define FPGA_NAND_BUSY (0x1 << 15) +#define FPGA_NAND_ENABLE (0x1 << 31) +#define FPGA_NAND_DATA_SHIFT 16 + +struct socrates_nand_host { + struct nand_chip nand_chip; + struct mtd_info mtd; + void __iomem *io_base; + struct device *dev; +}; + +/** + * socrates_nand_write_buf - write buffer to chip + * @mtd: MTD device structure + * @buf: data buffer + * @len: number of bytes to write + */ +static void socrates_nand_write_buf(struct mtd_info *mtd, + const uint8_t *buf, int len) +{ + int i; + struct nand_chip *this = mtd->priv; + struct socrates_nand_host *host = this->priv; + + for (i = 0; i < len; i++) { + out_be32(host->io_base, FPGA_NAND_ENABLE | + FPGA_NAND_CMD_WRITE | + (buf[i] << FPGA_NAND_DATA_SHIFT)); + } +} + +/** + * socrates_nand_read_buf - read chip data into buffer + * @mtd: MTD device structure + * @buf: buffer to store date + * @len: number of bytes to read + */ +static void socrates_nand_read_buf(struct mtd_info *mtd, uint8_t *buf, int len) +{ + int i; + struct nand_chip *this = mtd->priv; + struct socrates_nand_host *host = this->priv; + uint32_t val; + + val = FPGA_NAND_ENABLE | FPGA_NAND_CMD_READ; + + out_be32(host->io_base, val); + for (i = 0; i < len; i++) { + buf[i] = (in_be32(host->io_base) >> + FPGA_NAND_DATA_SHIFT) & 0xff; + } +} + +/** + * socrates_nand_read_byte - read one byte from the chip + * @mtd: MTD device structure + */ +static uint8_t socrates_nand_read_byte(struct mtd_info *mtd) +{ + uint8_t byte; + socrates_nand_read_buf(mtd, &byte, sizeof(byte)); + return byte; +} + +/** + * socrates_nand_read_word - read one word from the chip + * @mtd: MTD device structure + */ +static uint16_t socrates_nand_read_word(struct mtd_info *mtd) +{ + uint16_t word; + socrates_nand_read_buf(mtd, (uint8_t *)&word, sizeof(word)); + return word; +} + +/** + * socrates_nand_verify_buf - Verify chip data against buffer + * @mtd: MTD device structure + * @buf: buffer containing the data to compare + * @len: number of bytes to compare + */ +static int socrates_nand_verify_buf(struct mtd_info *mtd, const u8 *buf, + int len) +{ + int i; + + for (i = 0; i < len; i++) { + if (buf[i] != socrates_nand_read_byte(mtd)) + return -EFAULT; + } + return 0; +} + +/* + * Hardware specific access to control-lines + */ +static void socrates_nand_cmd_ctrl(struct mtd_info *mtd, int cmd, + unsigned int ctrl) +{ + struct nand_chip *nand_chip = mtd->priv; + struct socrates_nand_host *host = nand_chip->priv; + uint32_t val; + + if (cmd == NAND_CMD_NONE) + return; + + if (ctrl & NAND_CLE) + val = FPGA_NAND_CMD_COMMAND; + else + val = FPGA_NAND_CMD_ADDR; + + if (ctrl & NAND_NCE) + val |= FPGA_NAND_ENABLE; + + val |= (cmd & 0xff) << FPGA_NAND_DATA_SHIFT; + + out_be32(host->io_base, val); +} + +/* + * Read the Device Ready pin. + */ +static int socrates_nand_device_ready(struct mtd_info *mtd) +{ + struct nand_chip *nand_chip = mtd->priv; + struct socrates_nand_host *host = nand_chip->priv; + + if (in_be32(host->io_base) & FPGA_NAND_BUSY) + return 0; /* busy */ + return 1; +} + +#ifdef CONFIG_MTD_PARTITIONS +static const char *part_probes[] = { "cmdlinepart", NULL }; +#endif + +/* + * Probe for the NAND device. + */ +static int __devinit socrates_nand_probe(struct of_device *ofdev, + const struct of_device_id *ofid) +{ + struct socrates_nand_host *host; + struct mtd_info *mtd; + struct nand_chip *nand_chip; + int res; + +#ifdef CONFIG_MTD_PARTITIONS + struct mtd_partition *partitions = NULL; + int num_partitions = 0; +#endif + + /* Allocate memory for the device structure (and zero it) */ + host = kzalloc(sizeof(struct socrates_nand_host), GFP_KERNEL); + if (!host) { + printk(KERN_ERR + "socrates_nand: failed to allocate device structure.\n"); + return -ENOMEM; + } + + host->io_base = of_iomap(ofdev->node, 0); + if (host->io_base == NULL) { + printk(KERN_ERR "socrates_nand: ioremap failed\n"); + kfree(host); + return -EIO; + } + + mtd = &host->mtd; + nand_chip = &host->nand_chip; + host->dev = &ofdev->dev; + + nand_chip->priv = host; /* link the private data structures */ + mtd->priv = nand_chip; + mtd->name = "socrates_nand"; + mtd->owner = THIS_MODULE; + mtd->dev.parent = &ofdev->dev; + + /*should never be accessed directly */ + nand_chip->IO_ADDR_R = (void *)0xdeadbeef; + nand_chip->IO_ADDR_W = (void *)0xdeadbeef; + + nand_chip->cmd_ctrl = socrates_nand_cmd_ctrl; + nand_chip->read_byte = socrates_nand_read_byte; + nand_chip->read_word = socrates_nand_read_word; + nand_chip->write_buf = socrates_nand_write_buf; + nand_chip->read_buf = socrates_nand_read_buf; + nand_chip->verify_buf = socrates_nand_verify_buf; + nand_chip->dev_ready = socrates_nand_device_ready; + + nand_chip->ecc.mode = NAND_ECC_SOFT; /* enable ECC */ + + /* TODO: I have no idea what real delay is. */ + nand_chip->chip_delay = 20; /* 20us command delay time */ + + dev_set_drvdata(&ofdev->dev, host); + + /* first scan to find the device and get the page size */ + if (nand_scan_ident(mtd, 1)) { + res = -ENXIO; + goto out; + } + + /* second phase scan */ + if (nand_scan_tail(mtd)) { + res = -ENXIO; + goto out; + } + +#ifdef CONFIG_MTD_PARTITIONS +#ifdef CONFIG_MTD_CMDLINE_PARTS + num_partitions = parse_mtd_partitions(mtd, part_probes, + &partitions, 0); + if (num_partitions < 0) { + res = num_partitions; + goto release; + } +#endif + +#ifdef CONFIG_MTD_OF_PARTS + if (num_partitions == 0) { + num_partitions = of_mtd_parse_partitions(&ofdev->dev, + ofdev->node, + &partitions); + if (num_partitions < 0) { + res = num_partitions; + goto release; + } + } +#endif + if (partitions && (num_partitions > 0)) + res = add_mtd_partitions(mtd, partitions, num_partitions); + else +#endif + res = add_mtd_device(mtd); + + if (!res) + return res; + +#ifdef CONFIG_MTD_PARTITIONS +release: +#endif + nand_release(mtd); + +out: + dev_set_drvdata(&ofdev->dev, NULL); + iounmap(host->io_base); + kfree(host); + return res; +} + +/* + * Remove a NAND device. + */ +static int __devexit socrates_nand_remove(struct of_device *ofdev) +{ + struct socrates_nand_host *host = dev_get_drvdata(&ofdev->dev); + struct mtd_info *mtd = &host->mtd; + + nand_release(mtd); + + dev_set_drvdata(&ofdev->dev, NULL); + iounmap(host->io_base); + kfree(host); + + return 0; +} + +static struct of_device_id socrates_nand_match[] = +{ + { + .compatible = "abb,socrates-nand", + }, + {}, +}; + +MODULE_DEVICE_TABLE(of, socrates_nand_match); + +static struct of_platform_driver socrates_nand_driver = { + .name = "socrates_nand", + .match_table = socrates_nand_match, + .probe = socrates_nand_probe, + .remove = __devexit_p(socrates_nand_remove), +}; + +static int __init socrates_nand_init(void) +{ + return of_register_platform_driver(&socrates_nand_driver); +} + +static void __exit socrates_nand_exit(void) +{ + of_unregister_platform_driver(&socrates_nand_driver); +} + +module_init(socrates_nand_init); +module_exit(socrates_nand_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Ilya Yanok"); +MODULE_DESCRIPTION("NAND driver for Socrates board"); diff --git a/drivers/mtd/nand/txx9ndfmc.c b/drivers/mtd/nand/txx9ndfmc.c new file mode 100644 index 00000000000..81247926489 --- /dev/null +++ b/drivers/mtd/nand/txx9ndfmc.c @@ -0,0 +1,428 @@ +/* + * TXx9 NAND flash memory controller driver + * Based on RBTX49xx patch from CELF patch archive. + * + * 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. + * + * (C) Copyright TOSHIBA CORPORATION 2004-2007 + * All Rights Reserved. + */ +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/delay.h> +#include <linux/mtd/mtd.h> +#include <linux/mtd/nand.h> +#include <linux/mtd/nand_ecc.h> +#include <linux/mtd/partitions.h> +#include <linux/io.h> +#include <asm/txx9/ndfmc.h> + +/* TXX9 NDFMC Registers */ +#define TXX9_NDFDTR 0x00 +#define TXX9_NDFMCR 0x04 +#define TXX9_NDFSR 0x08 +#define TXX9_NDFISR 0x0c +#define TXX9_NDFIMR 0x10 +#define TXX9_NDFSPR 0x14 +#define TXX9_NDFRSTR 0x18 /* not TX4939 */ + +/* NDFMCR : NDFMC Mode Control */ +#define TXX9_NDFMCR_WE 0x80 +#define TXX9_NDFMCR_ECC_ALL 0x60 +#define TXX9_NDFMCR_ECC_RESET 0x60 +#define TXX9_NDFMCR_ECC_READ 0x40 +#define TXX9_NDFMCR_ECC_ON 0x20 +#define TXX9_NDFMCR_ECC_OFF 0x00 +#define TXX9_NDFMCR_CE 0x10 +#define TXX9_NDFMCR_BSPRT 0x04 /* TX4925/TX4926 only */ +#define TXX9_NDFMCR_ALE 0x02 +#define TXX9_NDFMCR_CLE 0x01 +/* TX4939 only */ +#define TXX9_NDFMCR_X16 0x0400 +#define TXX9_NDFMCR_DMAREQ_MASK 0x0300 +#define TXX9_NDFMCR_DMAREQ_NODMA 0x0000 +#define TXX9_NDFMCR_DMAREQ_128 0x0100 +#define TXX9_NDFMCR_DMAREQ_256 0x0200 +#define TXX9_NDFMCR_DMAREQ_512 0x0300 +#define TXX9_NDFMCR_CS_MASK 0x0c +#define TXX9_NDFMCR_CS(ch) ((ch) << 2) + +/* NDFMCR : NDFMC Status */ +#define TXX9_NDFSR_BUSY 0x80 +/* TX4939 only */ +#define TXX9_NDFSR_DMARUN 0x40 + +/* NDFMCR : NDFMC Reset */ +#define TXX9_NDFRSTR_RST 0x01 + +struct txx9ndfmc_priv { + struct platform_device *dev; + struct nand_chip chip; + struct mtd_info mtd; + int cs; + char mtdname[BUS_ID_SIZE + 2]; +}; + +#define MAX_TXX9NDFMC_DEV 4 +struct txx9ndfmc_drvdata { + struct mtd_info *mtds[MAX_TXX9NDFMC_DEV]; + void __iomem *base; + unsigned char hold; /* in gbusclock */ + unsigned char spw; /* in gbusclock */ + struct nand_hw_control hw_control; +#ifdef CONFIG_MTD_PARTITIONS + struct mtd_partition *parts[MAX_TXX9NDFMC_DEV]; +#endif +}; + +static struct platform_device *mtd_to_platdev(struct mtd_info *mtd) +{ + struct nand_chip *chip = mtd->priv; + struct txx9ndfmc_priv *txx9_priv = chip->priv; + return txx9_priv->dev; +} + +static void __iomem *ndregaddr(struct platform_device *dev, unsigned int reg) +{ + struct txx9ndfmc_drvdata *drvdata = platform_get_drvdata(dev); + struct txx9ndfmc_platform_data *plat = dev->dev.platform_data; + + return drvdata->base + (reg << plat->shift); +} + +static u32 txx9ndfmc_read(struct platform_device *dev, unsigned int reg) +{ + return __raw_readl(ndregaddr(dev, reg)); +} + +static void txx9ndfmc_write(struct platform_device *dev, + u32 val, unsigned int reg) +{ + __raw_writel(val, ndregaddr(dev, reg)); +} + +static uint8_t txx9ndfmc_read_byte(struct mtd_info *mtd) +{ + struct platform_device *dev = mtd_to_platdev(mtd); + + return txx9ndfmc_read(dev, TXX9_NDFDTR); +} + +static void txx9ndfmc_write_buf(struct mtd_info *mtd, const uint8_t *buf, + int len) +{ + struct platform_device *dev = mtd_to_platdev(mtd); + void __iomem *ndfdtr = ndregaddr(dev, TXX9_NDFDTR); + u32 mcr = txx9ndfmc_read(dev, TXX9_NDFMCR); + + txx9ndfmc_write(dev, mcr | TXX9_NDFMCR_WE, TXX9_NDFMCR); + while (len--) + __raw_writel(*buf++, ndfdtr); + txx9ndfmc_write(dev, mcr, TXX9_NDFMCR); +} + +static void txx9ndfmc_read_buf(struct mtd_info *mtd, uint8_t *buf, int len) +{ + struct platform_device *dev = mtd_to_platdev(mtd); + void __iomem *ndfdtr = ndregaddr(dev, TXX9_NDFDTR); + + while (len--) + *buf++ = __raw_readl(ndfdtr); +} + +static int txx9ndfmc_verify_buf(struct mtd_info *mtd, const uint8_t *buf, + int len) +{ + struct platform_device *dev = mtd_to_platdev(mtd); + void __iomem *ndfdtr = ndregaddr(dev, TXX9_NDFDTR); + + while (len--) + if (*buf++ != (uint8_t)__raw_readl(ndfdtr)) + return -EFAULT; + return 0; +} + +static void txx9ndfmc_cmd_ctrl(struct mtd_info *mtd, int cmd, + unsigned int ctrl) +{ + struct nand_chip *chip = mtd->priv; + struct txx9ndfmc_priv *txx9_priv = chip->priv; + struct platform_device *dev = txx9_priv->dev; + struct txx9ndfmc_platform_data *plat = dev->dev.platform_data; + + if (ctrl & NAND_CTRL_CHANGE) { + u32 mcr = txx9ndfmc_read(dev, TXX9_NDFMCR); + + mcr &= ~(TXX9_NDFMCR_CLE | TXX9_NDFMCR_ALE | TXX9_NDFMCR_CE); + mcr |= ctrl & NAND_CLE ? TXX9_NDFMCR_CLE : 0; + mcr |= ctrl & NAND_ALE ? TXX9_NDFMCR_ALE : 0; + /* TXX9_NDFMCR_CE bit is 0:high 1:low */ + mcr |= ctrl & NAND_NCE ? TXX9_NDFMCR_CE : 0; + if (txx9_priv->cs >= 0 && (ctrl & NAND_NCE)) { + mcr &= ~TXX9_NDFMCR_CS_MASK; + mcr |= TXX9_NDFMCR_CS(txx9_priv->cs); + } + txx9ndfmc_write(dev, mcr, TXX9_NDFMCR); + } + if (cmd != NAND_CMD_NONE) + txx9ndfmc_write(dev, cmd & 0xff, TXX9_NDFDTR); + if (plat->flags & NDFMC_PLAT_FLAG_DUMMYWRITE) { + /* dummy write to update external latch */ + if ((ctrl & NAND_CTRL_CHANGE) && cmd == NAND_CMD_NONE) + txx9ndfmc_write(dev, 0, TXX9_NDFDTR); + } + mmiowb(); +} + +static int txx9ndfmc_dev_ready(struct mtd_info *mtd) +{ + struct platform_device *dev = mtd_to_platdev(mtd); + + return !(txx9ndfmc_read(dev, TXX9_NDFSR) & TXX9_NDFSR_BUSY); +} + +static int txx9ndfmc_calculate_ecc(struct mtd_info *mtd, const uint8_t *dat, + uint8_t *ecc_code) +{ + struct platform_device *dev = mtd_to_platdev(mtd); + u32 mcr = txx9ndfmc_read(dev, TXX9_NDFMCR); + + mcr &= ~TXX9_NDFMCR_ECC_ALL; + txx9ndfmc_write(dev, mcr | TXX9_NDFMCR_ECC_OFF, TXX9_NDFMCR); + txx9ndfmc_write(dev, mcr | TXX9_NDFMCR_ECC_READ, TXX9_NDFMCR); + ecc_code[1] = txx9ndfmc_read(dev, TXX9_NDFDTR); + ecc_code[0] = txx9ndfmc_read(dev, TXX9_NDFDTR); + ecc_code[2] = txx9ndfmc_read(dev, TXX9_NDFDTR); + txx9ndfmc_write(dev, mcr | TXX9_NDFMCR_ECC_OFF, TXX9_NDFMCR); + return 0; +} + +static void txx9ndfmc_enable_hwecc(struct mtd_info *mtd, int mode) +{ + struct platform_device *dev = mtd_to_platdev(mtd); + u32 mcr = txx9ndfmc_read(dev, TXX9_NDFMCR); + + mcr &= ~TXX9_NDFMCR_ECC_ALL; + txx9ndfmc_write(dev, mcr | TXX9_NDFMCR_ECC_RESET, TXX9_NDFMCR); + txx9ndfmc_write(dev, mcr | TXX9_NDFMCR_ECC_OFF, TXX9_NDFMCR); + txx9ndfmc_write(dev, mcr | TXX9_NDFMCR_ECC_ON, TXX9_NDFMCR); +} + +static void txx9ndfmc_initialize(struct platform_device *dev) +{ + struct txx9ndfmc_platform_data *plat = dev->dev.platform_data; + struct txx9ndfmc_drvdata *drvdata = platform_get_drvdata(dev); + int tmout = 100; + + if (plat->flags & NDFMC_PLAT_FLAG_NO_RSTR) + ; /* no NDFRSTR. Write to NDFSPR resets the NDFMC. */ + else { + /* reset NDFMC */ + txx9ndfmc_write(dev, + txx9ndfmc_read(dev, TXX9_NDFRSTR) | + TXX9_NDFRSTR_RST, + TXX9_NDFRSTR); + while (txx9ndfmc_read(dev, TXX9_NDFRSTR) & TXX9_NDFRSTR_RST) { + if (--tmout == 0) { + dev_err(&dev->dev, "reset failed.\n"); + break; + } + udelay(1); + } + } + /* setup Hold Time, Strobe Pulse Width */ + txx9ndfmc_write(dev, (drvdata->hold << 4) | drvdata->spw, TXX9_NDFSPR); + txx9ndfmc_write(dev, + (plat->flags & NDFMC_PLAT_FLAG_USE_BSPRT) ? + TXX9_NDFMCR_BSPRT : 0, TXX9_NDFMCR); +} + +#define TXX9NDFMC_NS_TO_CYC(gbusclk, ns) \ + DIV_ROUND_UP((ns) * DIV_ROUND_UP(gbusclk, 1000), 1000000) + +static int __init txx9ndfmc_probe(struct platform_device *dev) +{ + struct txx9ndfmc_platform_data *plat = dev->dev.platform_data; +#ifdef CONFIG_MTD_PARTITIONS + static const char *probes[] = { "cmdlinepart", NULL }; +#endif + int hold, spw; + int i; + struct txx9ndfmc_drvdata *drvdata; + unsigned long gbusclk = plat->gbus_clock; + struct resource *res; + + res = platform_get_resource(dev, IORESOURCE_MEM, 0); + if (!res) + return -ENODEV; + drvdata = devm_kzalloc(&dev->dev, sizeof(*drvdata), GFP_KERNEL); + if (!drvdata) + return -ENOMEM; + if (!devm_request_mem_region(&dev->dev, res->start, + resource_size(res), dev_name(&dev->dev))) + return -EBUSY; + drvdata->base = devm_ioremap(&dev->dev, res->start, + resource_size(res)); + if (!drvdata->base) + return -EBUSY; + + hold = plat->hold ?: 20; /* tDH */ + spw = plat->spw ?: 90; /* max(tREADID, tWP, tRP) */ + + hold = TXX9NDFMC_NS_TO_CYC(gbusclk, hold); + spw = TXX9NDFMC_NS_TO_CYC(gbusclk, spw); + if (plat->flags & NDFMC_PLAT_FLAG_HOLDADD) + hold -= 2; /* actual hold time : (HOLD + 2) BUSCLK */ + spw -= 1; /* actual wait time : (SPW + 1) BUSCLK */ + hold = clamp(hold, 1, 15); + drvdata->hold = hold; + spw = clamp(spw, 1, 15); + drvdata->spw = spw; + dev_info(&dev->dev, "CLK:%ldMHz HOLD:%d SPW:%d\n", + (gbusclk + 500000) / 1000000, hold, spw); + + spin_lock_init(&drvdata->hw_control.lock); + init_waitqueue_head(&drvdata->hw_control.wq); + + platform_set_drvdata(dev, drvdata); + txx9ndfmc_initialize(dev); + + for (i = 0; i < MAX_TXX9NDFMC_DEV; i++) { + struct txx9ndfmc_priv *txx9_priv; + struct nand_chip *chip; + struct mtd_info *mtd; +#ifdef CONFIG_MTD_PARTITIONS + int nr_parts; +#endif + + if (!(plat->ch_mask & (1 << i))) + continue; + txx9_priv = kzalloc(sizeof(struct txx9ndfmc_priv), + GFP_KERNEL); + if (!txx9_priv) { + dev_err(&dev->dev, "Unable to allocate " + "TXx9 NDFMC MTD device structure.\n"); + continue; + } + chip = &txx9_priv->chip; + mtd = &txx9_priv->mtd; + mtd->owner = THIS_MODULE; + + mtd->priv = chip; + + chip->read_byte = txx9ndfmc_read_byte; + chip->read_buf = txx9ndfmc_read_buf; + chip->write_buf = txx9ndfmc_write_buf; + chip->verify_buf = txx9ndfmc_verify_buf; + chip->cmd_ctrl = txx9ndfmc_cmd_ctrl; + chip->dev_ready = txx9ndfmc_dev_ready; + chip->ecc.calculate = txx9ndfmc_calculate_ecc; + chip->ecc.correct = nand_correct_data; + chip->ecc.hwctl = txx9ndfmc_enable_hwecc; + chip->ecc.mode = NAND_ECC_HW; + chip->ecc.size = 256; + chip->ecc.bytes = 3; + chip->chip_delay = 100; + chip->controller = &drvdata->hw_control; + + chip->priv = txx9_priv; + txx9_priv->dev = dev; + + if (plat->ch_mask != 1) { + txx9_priv->cs = i; + sprintf(txx9_priv->mtdname, "%s.%u", + dev_name(&dev->dev), i); + } else { + txx9_priv->cs = -1; + strcpy(txx9_priv->mtdname, dev_name(&dev->dev)); + } + if (plat->wide_mask & (1 << i)) + chip->options |= NAND_BUSWIDTH_16; + + if (nand_scan(mtd, 1)) { + kfree(txx9_priv); + continue; + } + mtd->name = txx9_priv->mtdname; + +#ifdef CONFIG_MTD_PARTITIONS + nr_parts = parse_mtd_partitions(mtd, probes, + &drvdata->parts[i], 0); + if (nr_parts > 0) + add_mtd_partitions(mtd, drvdata->parts[i], nr_parts); +#endif + add_mtd_device(mtd); + drvdata->mtds[i] = mtd; + } + + return 0; +} + +static int __exit txx9ndfmc_remove(struct platform_device *dev) +{ + struct txx9ndfmc_drvdata *drvdata = platform_get_drvdata(dev); + int i; + + platform_set_drvdata(dev, NULL); + if (!drvdata) + return 0; + for (i = 0; i < MAX_TXX9NDFMC_DEV; i++) { + struct mtd_info *mtd = drvdata->mtds[i]; + struct nand_chip *chip; + struct txx9ndfmc_priv *txx9_priv; + + if (!mtd) + continue; + chip = mtd->priv; + txx9_priv = chip->priv; + +#ifdef CONFIG_MTD_PARTITIONS + del_mtd_partitions(mtd); + kfree(drvdata->parts[i]); +#endif + del_mtd_device(mtd); + kfree(txx9_priv); + } + return 0; +} + +#ifdef CONFIG_PM +static int txx9ndfmc_resume(struct platform_device *dev) +{ + if (platform_get_drvdata(dev)) + txx9ndfmc_initialize(dev); + return 0; +} +#else +#define txx9ndfmc_resume NULL +#endif + +static struct platform_driver txx9ndfmc_driver = { + .remove = __exit_p(txx9ndfmc_remove), + .resume = txx9ndfmc_resume, + .driver = { + .name = "txx9ndfmc", + .owner = THIS_MODULE, + }, +}; + +static int __init txx9ndfmc_init(void) +{ + return platform_driver_probe(&txx9ndfmc_driver, txx9ndfmc_probe); +} + +static void __exit txx9ndfmc_exit(void) +{ + platform_driver_unregister(&txx9ndfmc_driver); +} + +module_init(txx9ndfmc_init); +module_exit(txx9ndfmc_exit); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("TXx9 SoC NAND flash controller driver"); +MODULE_ALIAS("platform:txx9ndfmc"); diff --git a/drivers/mtd/nftlcore.c b/drivers/mtd/nftlcore.c index d1c4546513f..e3f8495a94c 100644 --- a/drivers/mtd/nftlcore.c +++ b/drivers/mtd/nftlcore.c @@ -15,11 +15,11 @@ #include <asm/errno.h> #include <asm/io.h> #include <asm/uaccess.h> -#include <linux/miscdevice.h> #include <linux/delay.h> #include <linux/slab.h> #include <linux/init.h> #include <linux/hdreg.h> +#include <linux/blkdev.h> #include <linux/kmod.h> #include <linux/mtd/mtd.h> @@ -818,3 +818,4 @@ module_exit(cleanup_nftl); MODULE_LICENSE("GPL"); MODULE_AUTHOR("David Woodhouse <dwmw2@infradead.org>, Fabrice Bellard <fabrice.bellard@netgem.com> et al."); MODULE_DESCRIPTION("Support code for NAND Flash Translation Layer, used on M-Systems DiskOnChip 2000 and Millennium"); +MODULE_ALIAS_BLOCKDEV_MAJOR(NFTL_MAJOR); diff --git a/drivers/mtd/ofpart.c b/drivers/mtd/ofpart.c index 9e45b3f39c0..3e164f0c929 100644 --- a/drivers/mtd/ofpart.c +++ b/drivers/mtd/ofpart.c @@ -46,6 +46,13 @@ int __devinit of_mtd_parse_partitions(struct device *dev, const u32 *reg; int len; + /* check if this is a partition node */ + partname = of_get_property(pp, "name", &len); + if (strcmp(partname, "partition") != 0) { + nr_parts--; + continue; + } + reg = of_get_property(pp, "reg", &len); if (!reg || (len != 2 * sizeof(u32))) { of_node_put(pp); diff --git a/drivers/mtd/onenand/omap2.c b/drivers/mtd/onenand/omap2.c index 77a4f144615..f2e9de1414d 100644 --- a/drivers/mtd/onenand/omap2.c +++ b/drivers/mtd/onenand/omap2.c @@ -294,6 +294,10 @@ static int omap3_onenand_read_bufferram(struct mtd_info *mtd, int area, if (bram_offset & 3 || (size_t)buf & 3 || count < 384) goto out_copy; + /* panic_write() may be in an interrupt context */ + if (in_interrupt()) + goto out_copy; + if (buf >= high_memory) { struct page *p1; @@ -672,6 +676,8 @@ static int __devinit omap2_onenand_probe(struct platform_device *pdev) c->mtd.priv = &c->onenand; c->mtd.owner = THIS_MODULE; + c->mtd.dev.parent = &pdev->dev; + if (c->dma_channel >= 0) { struct onenand_chip *this = &c->onenand; diff --git a/drivers/mtd/onenand/onenand_base.c b/drivers/mtd/onenand/onenand_base.c index 529af271db1..30d6999e5f9 100644 --- a/drivers/mtd/onenand/onenand_base.c +++ b/drivers/mtd/onenand/onenand_base.c @@ -1455,7 +1455,8 @@ static int onenand_write_ops_nolock(struct mtd_info *mtd, loff_t to, struct mtd_oob_ops *ops) { struct onenand_chip *this = mtd->priv; - int written = 0, column, thislen, subpage; + int written = 0, column, thislen = 0, subpage = 0; + int prev = 0, prevlen = 0, prev_subpage = 0, first = 1; int oobwritten = 0, oobcolumn, thisooblen, oobsize; size_t len = ops->len; size_t ooblen = ops->ooblen; @@ -1482,6 +1483,10 @@ static int onenand_write_ops_nolock(struct mtd_info *mtd, loff_t to, return -EINVAL; } + /* Check zero length */ + if (!len) + return 0; + if (ops->mode == MTD_OOB_AUTO) oobsize = this->ecclayout->oobavail; else @@ -1492,79 +1497,121 @@ static int onenand_write_ops_nolock(struct mtd_info *mtd, loff_t to, column = to & (mtd->writesize - 1); /* Loop until all data write */ - while (written < len) { - u_char *wbuf = (u_char *) buf; + while (1) { + if (written < len) { + u_char *wbuf = (u_char *) buf; - thislen = min_t(int, mtd->writesize - column, len - written); - thisooblen = min_t(int, oobsize - oobcolumn, ooblen - oobwritten); + thislen = min_t(int, mtd->writesize - column, len - written); + thisooblen = min_t(int, oobsize - oobcolumn, ooblen - oobwritten); - cond_resched(); + cond_resched(); - this->command(mtd, ONENAND_CMD_BUFFERRAM, to, thislen); + this->command(mtd, ONENAND_CMD_BUFFERRAM, to, thislen); - /* Partial page write */ - subpage = thislen < mtd->writesize; - if (subpage) { - memset(this->page_buf, 0xff, mtd->writesize); - memcpy(this->page_buf + column, buf, thislen); - wbuf = this->page_buf; - } + /* Partial page write */ + subpage = thislen < mtd->writesize; + if (subpage) { + memset(this->page_buf, 0xff, mtd->writesize); + memcpy(this->page_buf + column, buf, thislen); + wbuf = this->page_buf; + } - this->write_bufferram(mtd, ONENAND_DATARAM, wbuf, 0, mtd->writesize); + this->write_bufferram(mtd, ONENAND_DATARAM, wbuf, 0, mtd->writesize); - if (oob) { - oobbuf = this->oob_buf; + if (oob) { + oobbuf = this->oob_buf; - /* We send data to spare ram with oobsize - * to prevent byte access */ - memset(oobbuf, 0xff, mtd->oobsize); - if (ops->mode == MTD_OOB_AUTO) - onenand_fill_auto_oob(mtd, oobbuf, oob, oobcolumn, thisooblen); - else - memcpy(oobbuf + oobcolumn, oob, thisooblen); + /* We send data to spare ram with oobsize + * to prevent byte access */ + memset(oobbuf, 0xff, mtd->oobsize); + if (ops->mode == MTD_OOB_AUTO) + onenand_fill_auto_oob(mtd, oobbuf, oob, oobcolumn, thisooblen); + else + memcpy(oobbuf + oobcolumn, oob, thisooblen); - oobwritten += thisooblen; - oob += thisooblen; - oobcolumn = 0; + oobwritten += thisooblen; + oob += thisooblen; + oobcolumn = 0; + } else + oobbuf = (u_char *) ffchars; + + this->write_bufferram(mtd, ONENAND_SPARERAM, oobbuf, 0, mtd->oobsize); } else - oobbuf = (u_char *) ffchars; + ONENAND_SET_NEXT_BUFFERRAM(this); - this->write_bufferram(mtd, ONENAND_SPARERAM, oobbuf, 0, mtd->oobsize); + /* + * 2 PLANE, MLC, and Flex-OneNAND doesn't support + * write-while-programe feature. + */ + if (!ONENAND_IS_2PLANE(this) && !first) { + ONENAND_SET_PREV_BUFFERRAM(this); - this->command(mtd, ONENAND_CMD_PROG, to, mtd->writesize); + ret = this->wait(mtd, FL_WRITING); - ret = this->wait(mtd, FL_WRITING); + /* In partial page write we don't update bufferram */ + onenand_update_bufferram(mtd, prev, !ret && !prev_subpage); + if (ret) { + written -= prevlen; + printk(KERN_ERR "onenand_write_ops_nolock: write filaed %d\n", ret); + break; + } - /* In partial page write we don't update bufferram */ - onenand_update_bufferram(mtd, to, !ret && !subpage); - if (ONENAND_IS_2PLANE(this)) { - ONENAND_SET_BUFFERRAM1(this); - onenand_update_bufferram(mtd, to + this->writesize, !ret && !subpage); - } + if (written == len) { + /* Only check verify write turn on */ + ret = onenand_verify(mtd, buf - len, to - len, len); + if (ret) + printk(KERN_ERR "onenand_write_ops_nolock: verify failed %d\n", ret); + break; + } - if (ret) { - printk(KERN_ERR "onenand_write_ops_nolock: write filaed %d\n", ret); - break; + ONENAND_SET_NEXT_BUFFERRAM(this); } - /* Only check verify write turn on */ - ret = onenand_verify(mtd, buf, to, thislen); - if (ret) { - printk(KERN_ERR "onenand_write_ops_nolock: verify failed %d\n", ret); - break; - } + this->command(mtd, ONENAND_CMD_PROG, to, mtd->writesize); - written += thislen; + /* + * 2 PLANE, MLC, and Flex-OneNAND wait here + */ + if (ONENAND_IS_2PLANE(this)) { + ret = this->wait(mtd, FL_WRITING); - if (written == len) - break; + /* In partial page write we don't update bufferram */ + onenand_update_bufferram(mtd, to, !ret && !subpage); + if (ret) { + printk(KERN_ERR "onenand_write_ops_nolock: write filaed %d\n", ret); + break; + } + + /* Only check verify write turn on */ + ret = onenand_verify(mtd, buf, to, thislen); + if (ret) { + printk(KERN_ERR "onenand_write_ops_nolock: verify failed %d\n", ret); + break; + } + + written += thislen; + + if (written == len) + break; + + } else + written += thislen; column = 0; + prev_subpage = subpage; + prev = to; + prevlen = thislen; to += thislen; buf += thislen; + first = 0; } + /* In error case, clear all bufferrams */ + if (written != len) + onenand_invalidate_bufferram(mtd, 0, -1); + ops->retlen = written; + ops->oobretlen = oobwritten; return ret; } |